aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander M Pickering <alex@cogarr.net>2024-01-29 16:20:10 -0600
committerAlexander M Pickering <alex@cogarr.net>2024-01-29 16:20:10 -0600
commitc2926c5ec74d7e37da395c8c32a7ff2b4cae7d06 (patch)
treeac2bb208dab1274cdc5e9059ffe014ae19181a4c
downloadfools_rush_in-c2926c5ec74d7e37da395c8c32a7ff2b4cae7d06.tar.gz
fools_rush_in-c2926c5ec74d7e37da395c8c32a7ff2b4cae7d06.tar.bz2
fools_rush_in-c2926c5ec74d7e37da395c8c32a7ff2b4cae7d06.zip
All the files
-rw-r--r--AUTHORS13
-rw-r--r--Makefile94
-rw-r--r--build/.gitignore1
-rw-r--r--build/data/.gitignore1
-rw-r--r--data_src/CampFire.pngbin0 -> 8906 bytes
-rw-r--r--data_src/all-props.pngbin0 -> 14649 bytes
-rw-r--r--data_src/all-props_output.pngbin0 -> 1563403 bytes
-rw-r--r--data_src/any_frame_bot_left.pngbin0 -> 6010 bytes
-rw-r--r--data_src/any_frame_bot_mid.pngbin0 -> 5502 bytes
-rw-r--r--data_src/any_frame_bot_right.pngbin0 -> 6126 bytes
-rw-r--r--data_src/any_frame_mid_left.pngbin0 -> 5772 bytes
-rw-r--r--data_src/any_frame_mid_mid.pngbin0 -> 5340 bytes
-rw-r--r--data_src/any_frame_mid_right.pngbin0 -> 5703 bytes
-rw-r--r--data_src/any_frame_top_left.pngbin0 -> 6160 bytes
-rw-r--r--data_src/any_frame_top_mid.pngbin0 -> 5623 bytes
-rw-r--r--data_src/any_frame_top_right.pngbin0 -> 6103 bytes
-rw-r--r--data_src/bar_fill.pngbin0 -> 5523 bytes
-rw-r--r--data_src/bar_left.pngbin0 -> 9577 bytes
-rw-r--r--data_src/bar_mid.pngbin0 -> 5489 bytes
-rw-r--r--data_src/bar_right.pngbin0 -> 9313 bytes
-rw-r--r--data_src/big_frame_top_left.pngbin0 -> 10647 bytes
-rw-r--r--data_src/big_frame_top_mid.pngbin0 -> 6555 bytes
-rw-r--r--data_src/big_frame_top_right.pngbin0 -> 10628 bytes
-rw-r--r--data_src/body-balance.pngbin0 -> 8459 bytes
-rw-r--r--data_src/camp.pngbin0 -> 18612 bytes
-rw-r--r--data_src/campfire-sprites-without-bg.pngbin0 -> 2660 bytes
-rw-r--r--data_src/character_1.pngbin0 -> 533 bytes
-rw-r--r--data_src/character_2.pngbin0 -> 8452 bytes
-rw-r--r--data_src/character_3.pngbin0 -> 1064 bytes
-rw-r--r--data_src/character_4.pngbin0 -> 349 bytes
-rw-r--r--data_src/cursor.pngbin0 -> 9426 bytes
-rw-r--r--data_src/defat_screen.xcfbin0 -> 130556 bytes
-rw-r--r--data_src/defeat_screen.pngbin0 -> 9611 bytes
-rw-r--r--data_src/dragon-breath.pngbin0 -> 9471 bytes
-rw-r--r--data_src/drum.pngbin0 -> 10873 bytes
-rw-r--r--data_src/e_brood.pngbin0 -> 1448 bytes
-rw-r--r--data_src/e_child.pngbin0 -> 1507 bytes
-rw-r--r--data_src/e_mope.pngbin0 -> 1285 bytes
-rw-r--r--data_src/e_rat.pngbin0 -> 1432 bytes
-rw-r--r--data_src/e_rum.pngbin0 -> 1433 bytes
-rw-r--r--data_src/e_sullen.pngbin0 -> 1809 bytes
-rw-r--r--data_src/g-clef.pngbin0 -> 8578 bytes
-rw-r--r--data_src/join.pngbin0 -> 7567 bytes
-rw-r--r--data_src/join.xcfbin0 -> 147437 bytes
-rw-r--r--data_src/juggler.pngbin0 -> 9254 bytes
-rw-r--r--data_src/kangaroo.pngbin0 -> 8565 bytes
-rw-r--r--data_src/lobby_bg.pngbin0 -> 27788 bytes
-rw-r--r--data_src/main_bg.pngbin0 -> 39601 bytes
-rw-r--r--data_src/main_bg.xcfbin0 -> 138568 bytes
-rw-r--r--data_src/more_room_peices.xcfbin0 -> 27971 bytes
-rw-r--r--data_src/no_action.pngbin0 -> 8405 bytes
-rw-r--r--data_src/pip_dark.pngbin0 -> 7866 bytes
-rw-r--r--data_src/pip_frame.pngbin0 -> 11404 bytes
-rw-r--r--data_src/pip_light.pngbin0 -> 8026 bytes
-rw-r--r--data_src/room.pngbin0 -> 12778 bytes
-rw-r--r--data_src/room.xcfbin0 -> 120091 bytes
-rw-r--r--data_src/room_peices.xcfbin0 -> 52158 bytes
-rw-r--r--data_src/selector_left.pngbin0 -> 8076 bytes
-rw-r--r--data_src/selector_right.pngbin0 -> 8316 bytes
-rw-r--r--data_src/small_frame_left.pngbin0 -> 8573 bytes
-rw-r--r--data_src/small_frame_mid.pngbin0 -> 6600 bytes
-rw-r--r--data_src/small_frame_right.pngbin0 -> 8059 bytes
-rw-r--r--data_src/strong-man.pngbin0 -> 9811 bytes
-rw-r--r--data_src/tear-tracks.pngbin0 -> 12089 bytes
-rw-r--r--data_src/thrown-knife.pngbin0 -> 9500 bytes
-rw-r--r--data_src/tumble.pngbin0 -> 10926 bytes
-rw-r--r--data_src/ui.pngbin0 -> 61207 bytes
-rw-r--r--data_src/ui_big_pieces.pngbin0 -> 49974 bytes
-rw-r--r--data_src/ui_big_pieces.xcfbin0 -> 379032 bytes
-rw-r--r--debug/.gitignore0
-rw-r--r--debug/a_brood.lua.X58
-rw-r--r--debug/a_dance.lua.X63
-rw-r--r--debug/a_drum.lua.X58
-rw-r--r--debug/a_firebreath.lua.X62
-rw-r--r--debug/a_hackysacks.lua.X61
-rw-r--r--debug/a_highjump.lua.X63
-rw-r--r--debug/a_knifeslip.lua.X62
-rw-r--r--debug/a_mope.lua.X56
-rw-r--r--debug/a_pass.lua.X31
-rw-r--r--debug/a_physique.lua.X58
-rw-r--r--debug/a_rat_bite.lua.X57
-rw-r--r--debug/a_rat_scurry.lua.X20
-rw-r--r--debug/a_ruminate.lua.X38
-rw-r--r--debug/a_strum.lua.X41
-rw-r--r--debug/a_sulk.lua.X59
-rw-r--r--debug/a_test.lua.X63
-rw-r--r--debug/a_tumble.lua.X61
-rw-r--r--debug/ability_reg.lua.X26
-rw-r--r--debug/action.lua.X66
-rw-r--r--debug/battle_menu.lua.X152
-rw-r--r--debug/broadphase.lua.X39
-rw-r--r--debug/char.lua.X266
-rw-r--r--debug/char_fool.lua.X22
-rw-r--r--debug/char_jugg.lua.X25
-rw-r--r--debug/char_mage.lua.X26
-rw-r--r--debug/char_tank.lua.X29
-rw-r--r--debug/char_theif.lua.X24
-rw-r--r--debug/color.lua.X11
-rw-r--r--debug/create_party_menu.lua.X114
-rw-r--r--debug/defeat_menu.lua.X63
-rw-r--r--debug/dispatch.lua.X50
-rw-r--r--debug/e_bethany.lua.X25
-rw-r--r--debug/e_child.lua.X25
-rw-r--r--debug/e_mopey_marvin.lua.X25
-rw-r--r--debug/e_rat.lua.X26
-rw-r--r--debug/e_ruminating_randy.lua.X25
-rw-r--r--debug/e_sullen_salley.lua.X25
-rw-r--r--debug/join_party_menu.lua.X72
-rw-r--r--debug/lobby_menu.lua.X88
-rw-r--r--debug/main.lua.X73
-rw-r--r--debug/main_menu.lua.X79
-rw-r--r--debug/party.lua.X46
-rw-r--r--debug/pixelize.lua.X2
-rw-r--r--debug/player.lua.X38
-rw-r--r--debug/room.lua.X89
-rw-r--r--debug/ui.lua.X247
-rw-r--r--debug/util.lua.X26
-rw-r--r--debug/world.lua.X376
-rw-r--r--rewrite.lua31
-rw-r--r--src/a_brood.moon42
-rw-r--r--src/a_dance.moon49
-rw-r--r--src/a_drum.moon48
-rw-r--r--src/a_firebreath.moon51
-rw-r--r--src/a_hackysacks.moon50
-rw-r--r--src/a_highjump.moon49
-rw-r--r--src/a_knifeslip.moon51
-rw-r--r--src/a_mope.moon41
-rw-r--r--src/a_pass.moon34
-rw-r--r--src/a_physique.moon48
-rw-r--r--src/a_rat_bite.moon42
-rw-r--r--src/a_rat_scurry.moon25
-rw-r--r--src/a_ruminate.moon29
-rw-r--r--src/a_strum.moon37
-rw-r--r--src/a_sulk.moon43
-rw-r--r--src/a_test.moon52
-rw-r--r--src/a_tumble.moon51
-rw-r--r--src/ability_reg.moon43
-rw-r--r--src/action.moon58
-rw-r--r--src/battle_menu.moon129
-rw-r--r--src/broadphase.moon69
-rw-r--r--src/bump.lua773
-rw-r--r--src/char.moon307
-rw-r--r--src/char_fool.moon24
-rw-r--r--src/char_jugg.moon30
-rw-r--r--src/char_mage.moon26
-rw-r--r--src/char_tank.moon34
-rw-r--r--src/char_theif.moon25
-rw-r--r--src/color.moon13
-rw-r--r--src/constants.lua7
-rw-r--r--src/constrain.lua109
-rw-r--r--src/create_party_menu.moon103
-rw-r--r--src/defeat_menu.moon66
-rw-r--r--src/dispatch.moon49
-rw-r--r--src/e_bethany.moon28
-rw-r--r--src/e_child.moon28
-rw-r--r--src/e_mopey_marvin.moon28
-rw-r--r--src/e_rat.moon31
-rw-r--r--src/e_ruminating_randy.moon28
-rw-r--r--src/e_sullen_salley.moon29
-rw-r--r--src/ext.lua66
-rw-r--r--src/join_party_menu.moon62
-rw-r--r--src/js/connect.js8
-rw-r--r--src/js/joined.js48
-rw-r--r--src/js/lobby.js80
-rw-r--r--src/lobby_menu.moon80
-rw-r--r--src/main.moon72
-rw-r--r--src/main_menu.moon81
-rw-r--r--src/party.moon58
-rw-r--r--src/pixelize.moon0
-rw-r--r--src/player.moon49
-rw-r--r--src/room.moon152
-rw-r--r--src/ui.moon221
-rw-r--r--src/util.moon37
-rw-r--r--src/world.moon368
-rw-r--r--tools/luastr.sh5
-rw-r--r--tools/recolor.sh4
-rw-r--r--tools/remap.pngbin0 -> 120 bytes
177 files changed, 7421 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..583c0d8
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,13 @@
+Alex Pickering - code
+OpenGameArt.org user Clint Bellanger - some art
+OpenGameArt.org user ansimuzm, @ansimuz - a lot of art
+Pascal Belisle - music & some sound effects
+lospec.com user GrafxKid - color palette
+game-icons.net user Lorc - some art
+game-icons.net user Delapouite - some art
+
+Software:
+Amulet engine (amulet.xyz)
+PeerJS (peerjs.com)
+Moonscript (moonscript.org)
+Bump.lua (https://github.com/kikito/bump.lua)
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b6fd71c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,94 @@
+
+#Binaries
+BLENDER = "D:\Programs\Blender\blender.exe"
+CONVERT = convert #Imagemagik
+MAGICK = magick
+MOONC = lua C:/Users/user/AppData/Roaming/luarocks/lib/luarocks/rocks-5.3/moonscript/0.5.0-1/bin/moonc
+LUASTR = tools/luastr.sh
+CP = cp
+CAT = cat
+CD = cd
+AMULET = amulet
+LUA = lua
+MV = mv
+UNZIP = unzip -o #overwrite without asking
+HTTP_SERVER = python -m http.server #STL
+OPEN_BROWSER = rundll32 url.dll,FileProtocolHandler http://localhost:8000
+ECHO=echo
+TILED=tiled
+7Z=7z
+
+moon_files = $(wildcard src/*.moon)
+lua_files = $(wildcard src/*.lua)
+js_files = $(wildcard src/js/*.js)
+build_moon_files = $(moon_files:src/%.moon=build/%.lua)
+debug_moon_files = $(moon_files:src/%.moon=debug/%.lua.X)
+build_js_files = $(js_files:src/js/%.js=build/%.lua)
+build_lua_files = $(lua_files:src/%.lua=build/%.lua)
+
+#Levels from tiled
+level_src_files = $(wildcard data_src/levels/*.tmx)
+level_lua_files = $(level_src_files:data_src/levels/%.tmx=build/%.lua)
+
+#Sprites from models
+model_files = $(wildcard data_src/models/*.blend")
+raw_imgs = $(model_files:data_src/models/%.blend=data_src/img_raw/%_raw.png)
+static_files = $(wildcard data_src/*.jpg) $(wildcard data_src/*.png)
+static_imgs = $(static_files:data_src/%=build/data/%)
+sprites = $(raw_imgs:data_src/img_raw/%_raw.png=build/data/%.png)
+dev_files = $(build_moon_files) $(build_lua_files) $(debug_moon_files) $(sprites) $(level_lua_files) $(build_js_files) $(static_imgs)
+all_files = $(build_moon_files) $(build_lua_files) $(debug_moon_files) $(sprites) $(build_js_files) $(static_imgs)
+
+
+all: $(all_files) $(sprites)
+ $(AMULET) export -r -a -nozipdir -html build
+ $(7Z) e -obin -aoa Untitled-0.0.0-html.zip
+ $(CD) bin && $(HTTP_SERVER)
+
+dev: $(dev_files)
+ cd build && $(AMULET) 2> err.log || true
+ cat build/err.log | lua rewrite.lua
+
+$(build_js_files) : build/%.lua : src/js/%.js $(LUASTR)
+ $(LUASTR) $< > $@
+
+$(build_moon_files) : build/%.lua : src/%.moon
+ $(MOONC) -o $@ $<
+
+$(debug_moon_files) : debug/%.lua.X : src/%.moon
+ $(MOONC) -X $< > $@
+
+$(build_lua_files) : build/%.lua : src/%.lua
+ $(CP) $< $@
+
+#Convert images to our reduce 64 colors
+$(sprites) : build/data/%.png : data_src/img_raw/%_raw.png
+ $(CONVERT) $^ +dither -remap fantasy-24-1x.png $@
+
+#What's going on? I can't access the dependency
+$(raw_imgs) : data_src/img_raw/%_raw.png : data_src/models/%.blend
+ $(BLENDER) -b $^ -o //../../$(@:%_raw.png=%_raw_) -f 1
+ $(MV) $(@:%_raw.png=%_raw_0001.png) $@
+
+$(static_imgs) : build/data/% : data_src/%
+ $(CP) $< $@
+
+#Dev graphics, only used for tiled and in development
+#See https://legacy.imagemagick.org/Usage/text/ for options that can
+# be put in the text file
+dev_graphic_srcs=$(wildcard data_src/dev_graphics/*.txt)
+dev_graphic_imgs=$(dev_graphic_srcs:%.txt=%.png)
+dev_graphics: $(dev_graphic_imgs)
+ echo "Done creating dev graphics"
+
+$(dev_graphic_imgs) : data_src/dev_graphics/%.png : data_src/dev_graphics/%.txt
+ $(CONVERT) -font "Liberation Mono" -pointsize 12 $(shell cat $<) $@
+
+clean:
+ rm debug/*.X
+ rm build/*.lua
+ rm *.zip
+
+sprites.png : sprites
+ amulet pack -png sprites.png -lua sprites.lua sprites/*.png
+
diff --git a/build/.gitignore b/build/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/build/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/build/data/.gitignore b/build/data/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/build/data/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/data_src/CampFire.png b/data_src/CampFire.png
new file mode 100644
index 0000000..2eda4fd
--- /dev/null
+++ b/data_src/CampFire.png
Binary files differ
diff --git a/data_src/all-props.png b/data_src/all-props.png
new file mode 100644
index 0000000..f54fd36
--- /dev/null
+++ b/data_src/all-props.png
Binary files differ
diff --git a/data_src/all-props_output.png b/data_src/all-props_output.png
new file mode 100644
index 0000000..2c90b83
--- /dev/null
+++ b/data_src/all-props_output.png
Binary files differ
diff --git a/data_src/any_frame_bot_left.png b/data_src/any_frame_bot_left.png
new file mode 100644
index 0000000..d6999e8
--- /dev/null
+++ b/data_src/any_frame_bot_left.png
Binary files differ
diff --git a/data_src/any_frame_bot_mid.png b/data_src/any_frame_bot_mid.png
new file mode 100644
index 0000000..2027f4e
--- /dev/null
+++ b/data_src/any_frame_bot_mid.png
Binary files differ
diff --git a/data_src/any_frame_bot_right.png b/data_src/any_frame_bot_right.png
new file mode 100644
index 0000000..b318142
--- /dev/null
+++ b/data_src/any_frame_bot_right.png
Binary files differ
diff --git a/data_src/any_frame_mid_left.png b/data_src/any_frame_mid_left.png
new file mode 100644
index 0000000..bb57806
--- /dev/null
+++ b/data_src/any_frame_mid_left.png
Binary files differ
diff --git a/data_src/any_frame_mid_mid.png b/data_src/any_frame_mid_mid.png
new file mode 100644
index 0000000..4f720c7
--- /dev/null
+++ b/data_src/any_frame_mid_mid.png
Binary files differ
diff --git a/data_src/any_frame_mid_right.png b/data_src/any_frame_mid_right.png
new file mode 100644
index 0000000..12673f4
--- /dev/null
+++ b/data_src/any_frame_mid_right.png
Binary files differ
diff --git a/data_src/any_frame_top_left.png b/data_src/any_frame_top_left.png
new file mode 100644
index 0000000..94fe20b
--- /dev/null
+++ b/data_src/any_frame_top_left.png
Binary files differ
diff --git a/data_src/any_frame_top_mid.png b/data_src/any_frame_top_mid.png
new file mode 100644
index 0000000..e8a9970
--- /dev/null
+++ b/data_src/any_frame_top_mid.png
Binary files differ
diff --git a/data_src/any_frame_top_right.png b/data_src/any_frame_top_right.png
new file mode 100644
index 0000000..19ef65e
--- /dev/null
+++ b/data_src/any_frame_top_right.png
Binary files differ
diff --git a/data_src/bar_fill.png b/data_src/bar_fill.png
new file mode 100644
index 0000000..90f7b3d
--- /dev/null
+++ b/data_src/bar_fill.png
Binary files differ
diff --git a/data_src/bar_left.png b/data_src/bar_left.png
new file mode 100644
index 0000000..3ddca8e
--- /dev/null
+++ b/data_src/bar_left.png
Binary files differ
diff --git a/data_src/bar_mid.png b/data_src/bar_mid.png
new file mode 100644
index 0000000..ab2fddf
--- /dev/null
+++ b/data_src/bar_mid.png
Binary files differ
diff --git a/data_src/bar_right.png b/data_src/bar_right.png
new file mode 100644
index 0000000..6bc328f
--- /dev/null
+++ b/data_src/bar_right.png
Binary files differ
diff --git a/data_src/big_frame_top_left.png b/data_src/big_frame_top_left.png
new file mode 100644
index 0000000..15b51f7
--- /dev/null
+++ b/data_src/big_frame_top_left.png
Binary files differ
diff --git a/data_src/big_frame_top_mid.png b/data_src/big_frame_top_mid.png
new file mode 100644
index 0000000..80dd23d
--- /dev/null
+++ b/data_src/big_frame_top_mid.png
Binary files differ
diff --git a/data_src/big_frame_top_right.png b/data_src/big_frame_top_right.png
new file mode 100644
index 0000000..ad0012b
--- /dev/null
+++ b/data_src/big_frame_top_right.png
Binary files differ
diff --git a/data_src/body-balance.png b/data_src/body-balance.png
new file mode 100644
index 0000000..f175cda
--- /dev/null
+++ b/data_src/body-balance.png
Binary files differ
diff --git a/data_src/camp.png b/data_src/camp.png
new file mode 100644
index 0000000..9a3d7e0
--- /dev/null
+++ b/data_src/camp.png
Binary files differ
diff --git a/data_src/campfire-sprites-without-bg.png b/data_src/campfire-sprites-without-bg.png
new file mode 100644
index 0000000..f2b5692
--- /dev/null
+++ b/data_src/campfire-sprites-without-bg.png
Binary files differ
diff --git a/data_src/character_1.png b/data_src/character_1.png
new file mode 100644
index 0000000..0dbe9d8
--- /dev/null
+++ b/data_src/character_1.png
Binary files differ
diff --git a/data_src/character_2.png b/data_src/character_2.png
new file mode 100644
index 0000000..204fe07
--- /dev/null
+++ b/data_src/character_2.png
Binary files differ
diff --git a/data_src/character_3.png b/data_src/character_3.png
new file mode 100644
index 0000000..cafd62b
--- /dev/null
+++ b/data_src/character_3.png
Binary files differ
diff --git a/data_src/character_4.png b/data_src/character_4.png
new file mode 100644
index 0000000..23eb994
--- /dev/null
+++ b/data_src/character_4.png
Binary files differ
diff --git a/data_src/cursor.png b/data_src/cursor.png
new file mode 100644
index 0000000..fb99270
--- /dev/null
+++ b/data_src/cursor.png
Binary files differ
diff --git a/data_src/defat_screen.xcf b/data_src/defat_screen.xcf
new file mode 100644
index 0000000..d64c3f5
--- /dev/null
+++ b/data_src/defat_screen.xcf
Binary files differ
diff --git a/data_src/defeat_screen.png b/data_src/defeat_screen.png
new file mode 100644
index 0000000..5c1521a
--- /dev/null
+++ b/data_src/defeat_screen.png
Binary files differ
diff --git a/data_src/dragon-breath.png b/data_src/dragon-breath.png
new file mode 100644
index 0000000..74f2b34
--- /dev/null
+++ b/data_src/dragon-breath.png
Binary files differ
diff --git a/data_src/drum.png b/data_src/drum.png
new file mode 100644
index 0000000..67b2726
--- /dev/null
+++ b/data_src/drum.png
Binary files differ
diff --git a/data_src/e_brood.png b/data_src/e_brood.png
new file mode 100644
index 0000000..15629c8
--- /dev/null
+++ b/data_src/e_brood.png
Binary files differ
diff --git a/data_src/e_child.png b/data_src/e_child.png
new file mode 100644
index 0000000..3a9bc9d
--- /dev/null
+++ b/data_src/e_child.png
Binary files differ
diff --git a/data_src/e_mope.png b/data_src/e_mope.png
new file mode 100644
index 0000000..71c0fad
--- /dev/null
+++ b/data_src/e_mope.png
Binary files differ
diff --git a/data_src/e_rat.png b/data_src/e_rat.png
new file mode 100644
index 0000000..ff9e2a2
--- /dev/null
+++ b/data_src/e_rat.png
Binary files differ
diff --git a/data_src/e_rum.png b/data_src/e_rum.png
new file mode 100644
index 0000000..5b29bd0
--- /dev/null
+++ b/data_src/e_rum.png
Binary files differ
diff --git a/data_src/e_sullen.png b/data_src/e_sullen.png
new file mode 100644
index 0000000..283b0e9
--- /dev/null
+++ b/data_src/e_sullen.png
Binary files differ
diff --git a/data_src/g-clef.png b/data_src/g-clef.png
new file mode 100644
index 0000000..6be305f
--- /dev/null
+++ b/data_src/g-clef.png
Binary files differ
diff --git a/data_src/join.png b/data_src/join.png
new file mode 100644
index 0000000..208d962
--- /dev/null
+++ b/data_src/join.png
Binary files differ
diff --git a/data_src/join.xcf b/data_src/join.xcf
new file mode 100644
index 0000000..7731012
--- /dev/null
+++ b/data_src/join.xcf
Binary files differ
diff --git a/data_src/juggler.png b/data_src/juggler.png
new file mode 100644
index 0000000..9d53fd0
--- /dev/null
+++ b/data_src/juggler.png
Binary files differ
diff --git a/data_src/kangaroo.png b/data_src/kangaroo.png
new file mode 100644
index 0000000..8334e81
--- /dev/null
+++ b/data_src/kangaroo.png
Binary files differ
diff --git a/data_src/lobby_bg.png b/data_src/lobby_bg.png
new file mode 100644
index 0000000..93f336a
--- /dev/null
+++ b/data_src/lobby_bg.png
Binary files differ
diff --git a/data_src/main_bg.png b/data_src/main_bg.png
new file mode 100644
index 0000000..bab1788
--- /dev/null
+++ b/data_src/main_bg.png
Binary files differ
diff --git a/data_src/main_bg.xcf b/data_src/main_bg.xcf
new file mode 100644
index 0000000..d7c27cc
--- /dev/null
+++ b/data_src/main_bg.xcf
Binary files differ
diff --git a/data_src/more_room_peices.xcf b/data_src/more_room_peices.xcf
new file mode 100644
index 0000000..0a8acdb
--- /dev/null
+++ b/data_src/more_room_peices.xcf
Binary files differ
diff --git a/data_src/no_action.png b/data_src/no_action.png
new file mode 100644
index 0000000..d4b7ca3
--- /dev/null
+++ b/data_src/no_action.png
Binary files differ
diff --git a/data_src/pip_dark.png b/data_src/pip_dark.png
new file mode 100644
index 0000000..3725ecf
--- /dev/null
+++ b/data_src/pip_dark.png
Binary files differ
diff --git a/data_src/pip_frame.png b/data_src/pip_frame.png
new file mode 100644
index 0000000..fdc061b
--- /dev/null
+++ b/data_src/pip_frame.png
Binary files differ
diff --git a/data_src/pip_light.png b/data_src/pip_light.png
new file mode 100644
index 0000000..6605fe8
--- /dev/null
+++ b/data_src/pip_light.png
Binary files differ
diff --git a/data_src/room.png b/data_src/room.png
new file mode 100644
index 0000000..27ffd20
--- /dev/null
+++ b/data_src/room.png
Binary files differ
diff --git a/data_src/room.xcf b/data_src/room.xcf
new file mode 100644
index 0000000..cdb9658
--- /dev/null
+++ b/data_src/room.xcf
Binary files differ
diff --git a/data_src/room_peices.xcf b/data_src/room_peices.xcf
new file mode 100644
index 0000000..00322db
--- /dev/null
+++ b/data_src/room_peices.xcf
Binary files differ
diff --git a/data_src/selector_left.png b/data_src/selector_left.png
new file mode 100644
index 0000000..663579e
--- /dev/null
+++ b/data_src/selector_left.png
Binary files differ
diff --git a/data_src/selector_right.png b/data_src/selector_right.png
new file mode 100644
index 0000000..df11e54
--- /dev/null
+++ b/data_src/selector_right.png
Binary files differ
diff --git a/data_src/small_frame_left.png b/data_src/small_frame_left.png
new file mode 100644
index 0000000..f2b081f
--- /dev/null
+++ b/data_src/small_frame_left.png
Binary files differ
diff --git a/data_src/small_frame_mid.png b/data_src/small_frame_mid.png
new file mode 100644
index 0000000..222abb2
--- /dev/null
+++ b/data_src/small_frame_mid.png
Binary files differ
diff --git a/data_src/small_frame_right.png b/data_src/small_frame_right.png
new file mode 100644
index 0000000..53a30f4
--- /dev/null
+++ b/data_src/small_frame_right.png
Binary files differ
diff --git a/data_src/strong-man.png b/data_src/strong-man.png
new file mode 100644
index 0000000..1a5a07c
--- /dev/null
+++ b/data_src/strong-man.png
Binary files differ
diff --git a/data_src/tear-tracks.png b/data_src/tear-tracks.png
new file mode 100644
index 0000000..7ea752d
--- /dev/null
+++ b/data_src/tear-tracks.png
Binary files differ
diff --git a/data_src/thrown-knife.png b/data_src/thrown-knife.png
new file mode 100644
index 0000000..214d566
--- /dev/null
+++ b/data_src/thrown-knife.png
Binary files differ
diff --git a/data_src/tumble.png b/data_src/tumble.png
new file mode 100644
index 0000000..c023acd
--- /dev/null
+++ b/data_src/tumble.png
Binary files differ
diff --git a/data_src/ui.png b/data_src/ui.png
new file mode 100644
index 0000000..d9e7bc9
--- /dev/null
+++ b/data_src/ui.png
Binary files differ
diff --git a/data_src/ui_big_pieces.png b/data_src/ui_big_pieces.png
new file mode 100644
index 0000000..0ae5142
--- /dev/null
+++ b/data_src/ui_big_pieces.png
Binary files differ
diff --git a/data_src/ui_big_pieces.xcf b/data_src/ui_big_pieces.xcf
new file mode 100644
index 0000000..46f0438
--- /dev/null
+++ b/data_src/ui_big_pieces.xcf
Binary files differ
diff --git a/debug/.gitignore b/debug/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/debug/.gitignore
diff --git a/debug/a_brood.lua.X b/debug/a_brood.lua.X
new file mode 100644
index 0000000..ea6e658
--- /dev/null
+++ b/debug/a_brood.lua.X
@@ -0,0 +1,58 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+73 7:[ local _parent_0 = Ability ] >> 4:[ class Brood extends Ability ]
+348 10:[ local room = world.player_party.room ] >> 18:[ room = world.player_party.room ]
+381 11:[ local enemy_party = room.parties[1] ] >> 19:[ enemy_party = room.parties[1] ]
+415 12:[ if enemy_party == party then ] >> 20:[ if enemy_party == party ]
+440 13:[ enemy_party = room.parties[2] ] >> 21:[ enemy_party = room.parties[2] ]
+413 14:[ end ] >> 20:[ if enemy_party == party ]
+508 17:[ for i = 1, 4 do ] >> 23:[ for i = 1,4 ]
+516 18:[ local chars_at_loc = world.room.data.locations[i] ] >> 24:[ chars_at_loc = world.room.data.locations[i] ]
+565 19:[ if world.server then ] >> 25:[ if world.server ]
+583 20:[ local a_chars_at_loc = { } ] >> 26:[ a_chars_at_loc = {} ]
+617 21:[ for _, v in ipairs(chars_at_loc) do ] >> 27:[ for _,v in ipairs(chars_at_loc) ]
+644 22:[ table.insert(a_chars_at_loc, world.player_party:member(v.uname)) ] >> 28:[ table.insert(a_chars_at_loc,world.player_party\member(v.uname)) ]
+607 23:[ end ] >> 27:[ for _,v in ipairs(chars_at_loc) ]
+712 24:[ chars_at_loc = a_chars_at_loc ] >> 29:[ chars_at_loc = a_chars_at_loc ]
+563 25:[ end ] >> 25:[ if world.server ]
+745 26:[ local hp_minus ] >> 30:[ hp_minus = () -> ]
+745 27:[ hp_minus = function() ] >> 30:[ hp_minus = () -> ]
+780 28:[ for _, char in pairs(chars_at_loc) do ] >> 31:[ for _, char in pairs(chars_at_loc) ]
+806 29:[ char:set_field("hp", char.data.hp - 1) ] >> 32:[ char\set_field("hp",char.data.hp - 1) ]
+766 30:[ end ] >> 31:[ for _, char in pairs(chars_at_loc) ]
+745 31:[ end ] >> 30:[ hp_minus = () -> ]
+849 32:[ if world.server then ] >> 33:[ if world.server ]
+867 33:[ hp_minus() ] >> 34:[ hp_minus! ]
+847 34:[ end ] >> 33:[ if world.server ]
+882 35:[ if world.client then ] >> 35:[ if world.client ]
+900 36:[ local ui = ui or require("ui") ] >> 36:[ ui = ui or require "ui" ]
+928 37:[ ui.tween_hit(char, i, hp_minus) ] >> 37:[ ui.tween_hit(char,i, hp_minus) ]
+880 38:[ end ] >> 35:[ if world.client ]
+501 39:[ end ] >> 23:[ for i = 1,4 ]
+973 42:[ return print("TODO!") ] >> 39:[ print("TODO!") ]
+1005 45:[ return print("TODO!") ] >> 42:[ print("TODO!") ]
+198 52:[ _class_0.__parent.__init(self, ...) ] >> 11:[ super(...) ]
+211 53:[ self.requirements = { ] >> 12:[ @requirements = { ]
+233 55:[ "consume_stat", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+248 56:[ "stamina", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+259 57:[ 1 ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+268 60:[ "status", ] >> 14:[ {"status", "active"}, ]
+277 61:[ "active" ] >> 14:[ {"status", "active"}, ]
+293 64:[ "distance", ] >> 15:[ {"distance", 1}, ]
+304 65:[ 1 ] >> 15:[ {"distance", 1}, ]
+211 67:[ } ] >> 12:[ @requirements = { ]
+83 92:[ self.text = "Brood" ] >> 5:[ @text = "Brood" ]
+100 93:[ self.description = "brood" ] >> 6:[ @description = "brood" ]
+124 94:[ self.hits_icon = { ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+138 95:[ 1, ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+140 96:[ 1, ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+142 97:[ 1, ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+144 98:[ 1, ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+146 99:[ 0, ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+148 100:[ 0, ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+150 101:[ 0, ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+152 102:[ 0 ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+124 103:[ } ] >> 7:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+156 104:[ self.speed = 5 ] >> 8:[ @speed = 5 ]
+168 105:[ self.distance = 1 ] >> 9:[ @distance = 1 ]
diff --git a/debug/a_dance.lua.X b/debug/a_dance.lua.X
new file mode 100644
index 0000000..d8512f2
--- /dev/null
+++ b/debug/a_dance.lua.X
@@ -0,0 +1,63 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+29 2:[ local ui = require("ui") ] >> 2:[ ui = require "ui" ]
+66 4:[ Ability = reg.Ability ] >> 3:[ import Ability from reg ]
+72 5:[ local mod = ... ] >> 5:[ mod = ... ]
+102 9:[ local _parent_0 = Ability ] >> 7:[ class Dance extends Ability ]
+376 12:[ local room = world.player_party.room ] >> 20:[ room = world.player_party.room ]
+409 13:[ local my_pos = char.location ] >> 21:[ my_pos = char.location ]
+466 14:[ local char_tbl1, char_tbl2 = nil, nil ] >> 23:[ char_tbl1, char_tbl2 = nil, nil ]
+514 15:[ for distance = 1, 8 do ] >> 24:[ for distance = 1, 8 ]
+523 16:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 25:[ char_tbl1 = room\at_location(my_pos + distance) ]
+574 17:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 26:[ char_tbl2 = room\at_location(my_pos - distance) ]
+627 18:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+625 20:[ end ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+500 21:[ end ] >> 24:[ for distance = 1, 8 ]
+745 24:[ for _, i in pairs({ ] >> 31:[ for _,i in pairs({6,7}) ]
+753 25:[ 6, ] >> 31:[ for _,i in pairs({6,7}) ]
+755 26:[ 7 ] >> 31:[ for _,i in pairs({6,7}) ]
+745 27:[ }) do ] >> 31:[ for _,i in pairs({6,7}) ]
+762 28:[ local chars_at_loc = world.room.data.locations[i] ] >> 32:[ chars_at_loc = world.room.data.locations[i] ]
+811 29:[ if world.server then ] >> 33:[ if world.server ]
+829 30:[ local a_chars_at_loc = { } ] >> 34:[ a_chars_at_loc = {} ]
+863 31:[ for _, v in ipairs(chars_at_loc) do ] >> 35:[ for _,v in ipairs(chars_at_loc) ]
+890 32:[ table.insert(a_chars_at_loc, world.enemy_party:member(v.uname)) ] >> 36:[ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) ]
+853 33:[ end ] >> 35:[ for _,v in ipairs(chars_at_loc) ]
+957 34:[ chars_at_loc = a_chars_at_loc ] >> 37:[ chars_at_loc = a_chars_at_loc ]
+809 35:[ end ] >> 33:[ if world.server ]
+990 36:[ local hp_minus ] >> 38:[ hp_minus = () -> ]
+990 37:[ hp_minus = function() ] >> 38:[ hp_minus = () -> ]
+1025 38:[ for _, char in pairs(chars_at_loc) do ] >> 39:[ for _, char in pairs(chars_at_loc) ]
+1051 39:[ char:set_field("hp", char.data.hp - 1) ] >> 40:[ char\set_field("hp",char.data.hp - 1) ]
+1011 40:[ end ] >> 39:[ for _, char in pairs(chars_at_loc) ]
+990 41:[ end ] >> 38:[ hp_minus = () -> ]
+1094 42:[ if world.server then ] >> 41:[ if world.server ]
+1112 43:[ hp_minus() ] >> 42:[ hp_minus! ]
+1092 44:[ end ] >> 41:[ if world.server ]
+1127 45:[ if world.client then ] >> 43:[ if world.client ]
+1145 46:[ ui = ui or require("ui") ] >> 44:[ ui = ui or require "ui" ]
+1173 47:[ ui.tween_hit(char, 1, hp_minus) ] >> 45:[ ui.tween_hit(char,1, hp_minus) ]
+1125 48:[ end ] >> 43:[ if world.client ]
+735 49:[ end ] >> 31:[ for _,i in pairs({6,7}) ]
+274 56:[ _class_0.__parent.__init(self, "Dance", { }) ] >> 15:[ super("Dance",{}) ]
+294 57:[ self.requirements = { ] >> 16:[ @requirements = { ]
+316 59:[ "status", ] >> 17:[ {"status", "active"}, ]
+325 60:[ "active" ] >> 17:[ {"status", "active"}, ]
+294 62:[ } ] >> 16:[ @requirements = { ]
+112 87:[ self.text = "Dance" ] >> 8:[ @text = "Dance" ]
+129 88:[ self.description = "Shake your body!" ] >> 9:[ @description = "Shake your body!" ]
+164 89:[ self.hits_icon = { ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+178 90:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+180 91:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+182 92:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+184 93:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+186 94:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+188 95:[ 1, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+190 96:[ 1, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+192 97:[ 0 ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+164 98:[ } ] >> 10:[ @hits_icon = {0,0,0,0,0,1,1,0} ]
+196 99:[ self.sprite = "data/body-balance.png" ] >> 11:[ @sprite = "data/body-balance.png" ]
+231 100:[ self.speed = 7 ] >> 12:[ @speed = 7 ]
+244 101:[ self.distance = 1 ] >> 13:[ @distance = 1 ]
+1205 107:[ mod.Tumble = Tumble ] >> 47:[ mod.Tumble = Tumble ]
+1226 108:[ return mod ] >> 49:[ mod ]
diff --git a/debug/a_drum.lua.X b/debug/a_drum.lua.X
new file mode 100644
index 0000000..9b55003
--- /dev/null
+++ b/debug/a_drum.lua.X
@@ -0,0 +1,58 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+29 2:[ local ui = require("ui") ] >> 2:[ ui = require "ui" ]
+66 4:[ Ability = reg.Ability ] >> 3:[ import Ability from reg ]
+72 5:[ local mod = ... ] >> 5:[ mod = ... ]
+101 9:[ local _parent_0 = Ability ] >> 7:[ class Drum extends Ability ]
+362 12:[ local room = world.player_party.room ] >> 20:[ room = world.player_party.room ]
+395 13:[ local my_pos = char.location ] >> 21:[ my_pos = char.location ]
+452 14:[ local char_tbl1, char_tbl2 = nil, nil ] >> 23:[ char_tbl1, char_tbl2 = nil, nil ]
+500 15:[ for distance = 1, 8 do ] >> 24:[ for distance = 1, 8 ]
+509 16:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 25:[ char_tbl1 = room\at_location(my_pos + distance) ]
+560 17:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 26:[ char_tbl2 = room\at_location(my_pos - distance) ]
+613 18:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+611 20:[ end ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+486 21:[ end ] >> 24:[ for distance = 1, 8 ]
+721 24:[ local chars_at_loc = world.room.data.locations[5] ] >> 31:[ chars_at_loc = world.room.data.locations[5] ]
+769 25:[ if world.server then ] >> 32:[ if world.server ]
+786 26:[ local a_chars_at_loc = { } ] >> 33:[ a_chars_at_loc = {} ]
+819 27:[ for _, v in ipairs(chars_at_loc) do ] >> 34:[ for _,v in ipairs(chars_at_loc) ]
+845 28:[ table.insert(a_chars_at_loc, world.enemy_party:member(v.uname)) ] >> 35:[ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) ]
+809 29:[ end ] >> 34:[ for _,v in ipairs(chars_at_loc) ]
+911 30:[ chars_at_loc = a_chars_at_loc ] >> 36:[ chars_at_loc = a_chars_at_loc ]
+767 31:[ end ] >> 32:[ if world.server ]
+943 32:[ local hp_minus ] >> 37:[ hp_minus = () -> ]
+943 33:[ hp_minus = function() ] >> 37:[ hp_minus = () -> ]
+977 34:[ for _, char in pairs(chars_at_loc) do ] >> 38:[ for _, char in pairs(chars_at_loc) ]
+1002 35:[ char:set_field("hp", char.data.hp - 1) ] >> 39:[ char\set_field("hp",char.data.hp - 1) ]
+963 36:[ end ] >> 38:[ for _, char in pairs(chars_at_loc) ]
+943 37:[ end ] >> 37:[ hp_minus = () -> ]
+1044 38:[ if world.server then ] >> 40:[ if world.server ]
+1061 39:[ hp_minus() ] >> 41:[ hp_minus! ]
+1042 40:[ end ] >> 40:[ if world.server ]
+1075 41:[ if world.client then ] >> 42:[ if world.client ]
+1092 42:[ ui = ui or require("ui") ] >> 43:[ ui = ui or require "ui" ]
+1119 43:[ return ui.tween_hit(char, 1, hp_minus) ] >> 44:[ ui.tween_hit(char,1, hp_minus) ]
+1073 44:[ end ] >> 42:[ if world.client ]
+261 51:[ _class_0.__parent.__init(self, "Drum", { }) ] >> 15:[ super("Drum",{}) ]
+280 52:[ self.requirements = { ] >> 16:[ @requirements = { ]
+302 54:[ "status", ] >> 17:[ {"status", "active"}, ]
+311 55:[ "active" ] >> 17:[ {"status", "active"}, ]
+280 57:[ } ] >> 16:[ @requirements = { ]
+111 82:[ self.text = "Drum" ] >> 8:[ @text = "Drum" ]
+127 83:[ self.description = "Rat-a-tat-tat!" ] >> 9:[ @description = "Rat-a-tat-tat!" ]
+160 84:[ self.hits_icon = { ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+174 85:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+176 86:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+178 87:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+180 88:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+182 89:[ 1, ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+184 90:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+186 91:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+188 92:[ 0 ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+160 93:[ } ] >> 10:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+192 94:[ self.sprite = "data/drum.png" ] >> 11:[ @sprite = "data/drum.png" ]
+219 95:[ self.speed = 5 ] >> 12:[ @speed = 5 ]
+231 96:[ self.distance = 1 ] >> 13:[ @distance = 1 ]
+1151 102:[ mod.Tumble = Tumble ] >> 46:[ mod.Tumble = Tumble ]
+1172 103:[ return mod ] >> 48:[ mod ]
diff --git a/debug/a_firebreath.lua.X b/debug/a_firebreath.lua.X
new file mode 100644
index 0000000..057c386
--- /dev/null
+++ b/debug/a_firebreath.lua.X
@@ -0,0 +1,62 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+54 4:[ local mod = ... ] >> 4:[ mod = ... ]
+64 5:[ print("In tubmle, reg is", reg) ] >> 5:[ print("In tubmle, reg is",reg) ]
+120 9:[ local _parent_0 = Ability ] >> 7:[ class FireBreath extends Ability ]
+424 12:[ local room = world.player_party.room ] >> 20:[ room = world.player_party.room ]
+457 13:[ local my_pos = char.location ] >> 21:[ my_pos = char.location ]
+514 14:[ local char_tbl1, char_tbl2 = nil, nil ] >> 23:[ char_tbl1, char_tbl2 = nil, nil ]
+562 15:[ for distance = 1, 8 do ] >> 24:[ for distance = 1, 8 ]
+571 16:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 25:[ char_tbl1 = room\at_location(my_pos + distance) ]
+622 17:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 26:[ char_tbl2 = room\at_location(my_pos - distance) ]
+675 18:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+673 20:[ end ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+548 21:[ end ] >> 24:[ for distance = 1, 8 ]
+783 24:[ print("Doing FireBreath") ] >> 31:[ print("Doing FireBreath") ]
+818 25:[ for i = 5, 8 do ] >> 32:[ for i = 5,8 ]
+826 26:[ local chars_at_loc = world.room.data.locations[i] ] >> 33:[ chars_at_loc = world.room.data.locations[i] ]
+873 27:[ print("chars at loc:", chars_at_loc) ] >> 34:[ print("chars at loc:",chars_at_loc) ]
+914 28:[ if world.server then ] >> 35:[ if world.server ]
+932 29:[ local a_chars_at_loc = { } ] >> 36:[ a_chars_at_loc = {} ]
+966 30:[ for _, v in ipairs(chars_at_loc) do ] >> 37:[ for _,v in ipairs(chars_at_loc) ]
+993 31:[ table.insert(a_chars_at_loc, world.enemy_party:member(v.uname)) ] >> 38:[ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) ]
+956 32:[ end ] >> 37:[ for _,v in ipairs(chars_at_loc) ]
+1060 33:[ chars_at_loc = a_chars_at_loc ] >> 39:[ chars_at_loc = a_chars_at_loc ]
+912 34:[ end ] >> 35:[ if world.server ]
+1093 35:[ local hp_minus ] >> 40:[ hp_minus = () -> ]
+1093 36:[ hp_minus = function() ] >> 40:[ hp_minus = () -> ]
+1128 37:[ for _, char in pairs(chars_at_loc) do ] >> 41:[ for _, char in pairs(chars_at_loc) ]
+1154 38:[ char:set_field("hp", char.data.hp - 1) ] >> 42:[ char\set_field("hp",char.data.hp - 1) ]
+1114 39:[ end ] >> 41:[ for _, char in pairs(chars_at_loc) ]
+1093 40:[ end ] >> 40:[ hp_minus = () -> ]
+1197 41:[ if world.server then ] >> 43:[ if world.server ]
+1215 42:[ hp_minus() ] >> 44:[ hp_minus! ]
+1195 43:[ end ] >> 43:[ if world.server ]
+1230 44:[ if world.client then ] >> 45:[ if world.client ]
+1248 45:[ local ui = ui or require("ui") ] >> 46:[ ui = ui or require "ui" ]
+1276 46:[ ui.tween_hit(char, i - 4, hp_minus) ] >> 47:[ ui.tween_hit(char,i - 4, hp_minus) ]
+1228 47:[ end ] >> 45:[ if world.client ]
+811 48:[ end ] >> 32:[ for i = 5,8 ]
+317 55:[ _class_0.__parent.__init(self, "FireBreath", { }) ] >> 15:[ super("FireBreath",{}) ]
+342 56:[ self.requirements = { ] >> 16:[ @requirements = { ]
+364 58:[ "status", ] >> 17:[ {"status", "active"}, ]
+373 59:[ "active" ] >> 17:[ {"status", "active"}, ]
+342 61:[ } ] >> 16:[ @requirements = { ]
+130 86:[ self.text = "Breath Fire" ] >> 8:[ @text = "Breath Fire" ]
+153 87:[ self.description = "Breath fire, visible for everyone!" ] >> 9:[ @description = "Breath fire, visible for everyone!" ]
+206 88:[ self.sprite = "data/dragon-breath.png" ] >> 10:[ @sprite = "data/dragon-breath.png" ]
+242 89:[ self.hits_icon = { ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+256 90:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+258 91:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+260 92:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+262 93:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+264 94:[ 1, ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+266 95:[ 1, ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+268 96:[ 1, ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+270 97:[ 1 ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+242 98:[ } ] >> 11:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+274 99:[ self.speed = 10 ] >> 12:[ @speed = 10 ]
+287 100:[ self.distance = 1 ] >> 13:[ @distance = 1 ]
+1312 106:[ mod.Tumble = Tumble ] >> 49:[ mod.Tumble = Tumble ]
+1333 107:[ return mod ] >> 51:[ mod ]
diff --git a/debug/a_hackysacks.lua.X b/debug/a_hackysacks.lua.X
new file mode 100644
index 0000000..88adebb
--- /dev/null
+++ b/debug/a_hackysacks.lua.X
@@ -0,0 +1,61 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+54 4:[ local mod = ... ] >> 4:[ mod = ... ]
+64 5:[ print("In tubmle, reg is", reg) ] >> 5:[ print("In tubmle, reg is",reg) ]
+116 9:[ local _parent_0 = Ability ] >> 7:[ class Juggle extends Ability ]
+393 12:[ local room = world.player_party.room ] >> 20:[ room = world.player_party.room ]
+426 13:[ local my_pos = char.location ] >> 21:[ my_pos = char.location ]
+483 14:[ local char_tbl1, char_tbl2 = nil, nil ] >> 23:[ char_tbl1, char_tbl2 = nil, nil ]
+531 15:[ for distance = 1, 8 do ] >> 24:[ for distance = 1, 8 ]
+540 16:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 25:[ char_tbl1 = room\at_location(my_pos + distance) ]
+591 17:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 26:[ char_tbl2 = room\at_location(my_pos - distance) ]
+644 18:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+642 20:[ end ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+517 21:[ end ] >> 24:[ for distance = 1, 8 ]
+759 24:[ for i = 5, 6 do ] >> 31:[ for i = 5,6 ]
+767 25:[ local chars_at_loc = world.room.data.locations[i] ] >> 32:[ chars_at_loc = world.room.data.locations[i] ]
+814 26:[ print("chars at loc:", chars_at_loc) ] >> 33:[ print("chars at loc:",chars_at_loc) ]
+855 27:[ if world.server then ] >> 34:[ if world.server ]
+873 28:[ local a_chars_at_loc = { } ] >> 35:[ a_chars_at_loc = {} ]
+907 29:[ for _, v in ipairs(chars_at_loc) do ] >> 36:[ for _,v in ipairs(chars_at_loc) ]
+934 30:[ table.insert(a_chars_at_loc, world.enemy_party:member(v.uname)) ] >> 37:[ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) ]
+897 31:[ end ] >> 36:[ for _,v in ipairs(chars_at_loc) ]
+1001 32:[ chars_at_loc = a_chars_at_loc ] >> 38:[ chars_at_loc = a_chars_at_loc ]
+853 33:[ end ] >> 34:[ if world.server ]
+1034 34:[ local hp_minus ] >> 39:[ hp_minus = () -> ]
+1034 35:[ hp_minus = function() ] >> 39:[ hp_minus = () -> ]
+1069 36:[ for _, char in pairs(chars_at_loc) do ] >> 40:[ for _, char in pairs(chars_at_loc) ]
+1095 37:[ char:set_field("hp", char.data.hp - 1) ] >> 41:[ char\set_field("hp",char.data.hp - 1) ]
+1055 38:[ end ] >> 40:[ for _, char in pairs(chars_at_loc) ]
+1034 39:[ end ] >> 39:[ hp_minus = () -> ]
+1138 40:[ if world.server then ] >> 42:[ if world.server ]
+1156 41:[ hp_minus() ] >> 43:[ hp_minus! ]
+1136 42:[ end ] >> 42:[ if world.server ]
+1171 43:[ if world.client then ] >> 44:[ if world.client ]
+1189 44:[ local ui = ui or require("ui") ] >> 45:[ ui = ui or require "ui" ]
+1217 45:[ ui.tween_hit(char, i - 4, hp_minus) ] >> 46:[ ui.tween_hit(char,i-4, hp_minus) ]
+1169 46:[ end ] >> 44:[ if world.client ]
+752 47:[ end ] >> 31:[ for i = 5,6 ]
+290 54:[ _class_0.__parent.__init(self, "Juggle", { }) ] >> 15:[ super("Juggle",{}) ]
+311 55:[ self.requirements = { ] >> 16:[ @requirements = { ]
+333 57:[ "status", ] >> 17:[ {"status", "active"}, ]
+342 58:[ "active" ] >> 17:[ {"status", "active"}, ]
+311 60:[ } ] >> 16:[ @requirements = { ]
+126 85:[ self.text = "Juggle Balls" ] >> 8:[ @text = "Juggle Balls" ]
+150 86:[ self.description = "Juggle some balls" ] >> 9:[ @description = "Juggle some balls" ]
+186 87:[ self.hits_icon = { ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+200 88:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+202 89:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+204 90:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+206 91:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+208 92:[ 1, ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+210 93:[ 1, ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+212 94:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+214 95:[ 0 ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+186 96:[ } ] >> 10:[ @hits_icon = {0,0,0,0,1,1,0,0} ]
+218 97:[ self.sprite = "data/juggler.png" ] >> 11:[ @sprite = "data/juggler.png" ]
+248 98:[ self.speed = 4 ] >> 12:[ @speed = 4 ]
+260 99:[ self.distance = 1 ] >> 13:[ @distance = 1 ]
+1251 105:[ mod.Tumble = Tumble ] >> 48:[ mod.Tumble = Tumble ]
+1272 106:[ return mod ] >> 50:[ mod ]
diff --git a/debug/a_highjump.lua.X b/debug/a_highjump.lua.X
new file mode 100644
index 0000000..4ba4501
--- /dev/null
+++ b/debug/a_highjump.lua.X
@@ -0,0 +1,63 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+29 2:[ local ui = require("ui") ] >> 2:[ ui = require "ui" ]
+66 4:[ Ability = reg.Ability ] >> 3:[ import Ability from reg ]
+72 5:[ local mod = ... ] >> 5:[ mod = ... ]
+105 9:[ local _parent_0 = Ability ] >> 7:[ class HighJump extends Ability ]
+394 12:[ local room = world.player_party.room ] >> 20:[ room = world.player_party.room ]
+427 13:[ local my_pos = char.location ] >> 21:[ my_pos = char.location ]
+484 14:[ local char_tbl1, char_tbl2 = nil, nil ] >> 23:[ char_tbl1, char_tbl2 = nil, nil ]
+532 15:[ for distance = 1, 8 do ] >> 24:[ for distance = 1, 8 ]
+541 16:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 25:[ char_tbl1 = room\at_location(my_pos + distance) ]
+592 17:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 26:[ char_tbl2 = room\at_location(my_pos - distance) ]
+645 18:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+643 20:[ end ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+518 21:[ end ] >> 24:[ for distance = 1, 8 ]
+763 24:[ for _, i in pairs({ ] >> 31:[ for _,i in pairs({7,8}) ]
+771 25:[ 7, ] >> 31:[ for _,i in pairs({7,8}) ]
+773 26:[ 8 ] >> 31:[ for _,i in pairs({7,8}) ]
+763 27:[ }) do ] >> 31:[ for _,i in pairs({7,8}) ]
+780 28:[ local chars_at_loc = world.room.data.locations[i] ] >> 32:[ chars_at_loc = world.room.data.locations[i] ]
+829 29:[ if world.server then ] >> 33:[ if world.server ]
+847 30:[ local a_chars_at_loc = { } ] >> 34:[ a_chars_at_loc = {} ]
+881 31:[ for _, v in ipairs(chars_at_loc) do ] >> 35:[ for _,v in ipairs(chars_at_loc) ]
+908 32:[ table.insert(a_chars_at_loc, world.enemy_party:member(v.uname)) ] >> 36:[ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) ]
+871 33:[ end ] >> 35:[ for _,v in ipairs(chars_at_loc) ]
+975 34:[ chars_at_loc = a_chars_at_loc ] >> 37:[ chars_at_loc = a_chars_at_loc ]
+827 35:[ end ] >> 33:[ if world.server ]
+1008 36:[ local hp_minus ] >> 38:[ hp_minus = () -> ]
+1008 37:[ hp_minus = function() ] >> 38:[ hp_minus = () -> ]
+1043 38:[ for _, char in pairs(chars_at_loc) do ] >> 39:[ for _, char in pairs(chars_at_loc) ]
+1069 39:[ char:set_field("hp", char.data.hp - 1) ] >> 40:[ char\set_field("hp",char.data.hp - 1) ]
+1029 40:[ end ] >> 39:[ for _, char in pairs(chars_at_loc) ]
+1008 41:[ end ] >> 38:[ hp_minus = () -> ]
+1112 42:[ if world.server then ] >> 41:[ if world.server ]
+1130 43:[ hp_minus() ] >> 42:[ hp_minus! ]
+1110 44:[ end ] >> 41:[ if world.server ]
+1145 45:[ if world.client then ] >> 43:[ if world.client ]
+1163 46:[ ui = ui or require("ui") ] >> 44:[ ui = ui or require "ui" ]
+1191 47:[ ui.tween_hit(char, 1, hp_minus) ] >> 45:[ ui.tween_hit(char,1, hp_minus) ]
+1143 48:[ end ] >> 43:[ if world.client ]
+753 49:[ end ] >> 31:[ for _,i in pairs({7,8}) ]
+291 56:[ _class_0.__parent.__init(self, "Tumble", { }) ] >> 15:[ super("Tumble",{}) ]
+312 57:[ self.requirements = { ] >> 16:[ @requirements = { ]
+334 59:[ "status", ] >> 17:[ {"status", "active"}, ]
+343 60:[ "active" ] >> 17:[ {"status", "active"}, ]
+312 62:[ } ] >> 16:[ @requirements = { ]
+115 87:[ self.text = "High Jump" ] >> 8:[ @text = "High Jump" ]
+136 88:[ self.description = "Jump up high. Like REALLY high." ] >> 9:[ @description = "Jump up high. Like REALLY high." ]
+186 89:[ self.hits_icon = { ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+200 90:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+202 91:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+204 92:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+206 93:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+208 94:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+210 95:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+212 96:[ 1, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+214 97:[ 1 ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+186 98:[ } ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+218 99:[ self.sprite = "data/kangaroo.png" ] >> 11:[ @sprite = "data/kangaroo.png" ]
+249 100:[ self.speed = 3 ] >> 12:[ @speed = 3 ]
+261 101:[ self.distance = 1 ] >> 13:[ @distance = 1 ]
+1223 107:[ mod.Tumble = Tumble ] >> 47:[ mod.Tumble = Tumble ]
+1244 108:[ return mod ] >> 49:[ mod ]
diff --git a/debug/a_knifeslip.lua.X b/debug/a_knifeslip.lua.X
new file mode 100644
index 0000000..6d99875
--- /dev/null
+++ b/debug/a_knifeslip.lua.X
@@ -0,0 +1,62 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+54 4:[ local mod = ... ] >> 4:[ mod = ... ]
+64 5:[ print("In tubmle, reg is", reg) ] >> 5:[ print("In tubmle, reg is",reg) ]
+119 9:[ local _parent_0 = Ability ] >> 7:[ class KnifeSlip extends Ability ]
+434 12:[ local room = world.player_party.room ] >> 20:[ room = world.player_party.room ]
+467 13:[ local my_pos = char.location ] >> 21:[ my_pos = char.location ]
+524 14:[ local char_tbl1, char_tbl2 = nil, nil ] >> 23:[ char_tbl1, char_tbl2 = nil, nil ]
+572 15:[ for distance = 1, 8 do ] >> 24:[ for distance = 1, 8 ]
+581 16:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 25:[ char_tbl1 = room\at_location(my_pos + distance) ]
+632 17:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 26:[ char_tbl2 = room\at_location(my_pos - distance) ]
+685 18:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+683 20:[ end ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+558 21:[ end ] >> 24:[ for distance = 1, 8 ]
+793 24:[ print("Doing knifeslip") ] >> 31:[ print("Doing knifeslip") ]
+827 25:[ for i = 7, 8 do ] >> 32:[ for i = 7,8 ]
+835 26:[ local chars_at_loc = world.room.data.locations[i] ] >> 33:[ chars_at_loc = world.room.data.locations[i] ]
+882 27:[ print("chars at loc:", chars_at_loc) ] >> 34:[ print("chars at loc:",chars_at_loc) ]
+923 28:[ if world.server then ] >> 35:[ if world.server ]
+941 29:[ local a_chars_at_loc = { } ] >> 36:[ a_chars_at_loc = {} ]
+975 30:[ for _, v in ipairs(chars_at_loc) do ] >> 37:[ for _,v in ipairs(chars_at_loc) ]
+1002 31:[ table.insert(a_chars_at_loc, world.enemy_party:member(v.uname)) ] >> 38:[ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) ]
+965 32:[ end ] >> 37:[ for _,v in ipairs(chars_at_loc) ]
+1069 33:[ chars_at_loc = a_chars_at_loc ] >> 39:[ chars_at_loc = a_chars_at_loc ]
+921 34:[ end ] >> 35:[ if world.server ]
+1102 35:[ local hp_minus ] >> 40:[ hp_minus = () -> ]
+1102 36:[ hp_minus = function() ] >> 40:[ hp_minus = () -> ]
+1137 37:[ for _, char in pairs(chars_at_loc) do ] >> 41:[ for _, char in pairs(chars_at_loc) ]
+1163 38:[ char:set_field("hp", char.data.hp - 2) ] >> 42:[ char\set_field("hp",char.data.hp - 2) ]
+1123 39:[ end ] >> 41:[ for _, char in pairs(chars_at_loc) ]
+1102 40:[ end ] >> 40:[ hp_minus = () -> ]
+1206 41:[ if world.server then ] >> 43:[ if world.server ]
+1224 42:[ hp_minus() ] >> 44:[ hp_minus! ]
+1204 43:[ end ] >> 43:[ if world.server ]
+1239 44:[ if world.client then ] >> 45:[ if world.client ]
+1257 45:[ local ui = ui or require("ui") ] >> 46:[ ui = ui or require "ui" ]
+1285 46:[ ui.tween_hit(char, i - 4, hp_minus) ] >> 47:[ ui.tween_hit(char,i-4, hp_minus) ]
+1237 47:[ end ] >> 45:[ if world.client ]
+820 48:[ end ] >> 32:[ for i = 7,8 ]
+328 55:[ _class_0.__parent.__init(self, "KnifeSlip", { }) ] >> 15:[ super("KnifeSlip",{}) ]
+352 56:[ self.requirements = { ] >> 16:[ @requirements = { ]
+374 58:[ "status", ] >> 17:[ {"status", "active"}, ]
+383 59:[ "active" ] >> 17:[ {"status", "active"}, ]
+352 61:[ } ] >> 16:[ @requirements = { ]
+129 86:[ self.text = "Juggle Knives" ] >> 8:[ @text = "Juggle Knives" ]
+154 87:[ self.description = "Juggle some knives for\nthe people in the back" ] >> 9:[ @description = "Juggle some knives for\nthe people in the back" ]
+219 88:[ self.hits_icon = { ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+233 89:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+235 90:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+237 91:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+239 92:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+241 93:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+243 94:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+245 95:[ 1, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+247 96:[ 1 ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+219 97:[ } ] >> 10:[ @hits_icon = {0,0,0,0,0,0,1,1} ]
+251 98:[ self.sprite = "data/thrown-knife.png" ] >> 11:[ @sprite = "data/thrown-knife.png" ]
+286 99:[ self.speed = 4 ] >> 12:[ @speed = 4 ]
+298 100:[ self.distance = 1 ] >> 13:[ @distance = 1 ]
+1319 106:[ mod.Tumble = Tumble ] >> 49:[ mod.Tumble = Tumble ]
+1340 107:[ return mod ] >> 51:[ mod ]
diff --git a/debug/a_mope.lua.X b/debug/a_mope.lua.X
new file mode 100644
index 0000000..b17248c
--- /dev/null
+++ b/debug/a_mope.lua.X
@@ -0,0 +1,56 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+72 7:[ local _parent_0 = Ability ] >> 4:[ class Mope extends Ability ]
+345 10:[ local room = world.player_party.room ] >> 18:[ room = world.player_party.room ]
+378 11:[ local enemy_party = room.parties[1] ] >> 19:[ enemy_party = room.parties[1] ]
+412 12:[ if enemy_party == party then ] >> 20:[ if enemy_party == party ]
+437 13:[ enemy_party = room.parties[2] ] >> 21:[ enemy_party = room.parties[2] ]
+410 14:[ end ] >> 20:[ if enemy_party == party ]
+498 17:[ local chars_at_loc = world.room.data.locations[4] ] >> 23:[ chars_at_loc = world.room.data.locations[4] ]
+546 18:[ if world.server then ] >> 24:[ if world.server ]
+563 19:[ local a_chars_at_loc = { } ] >> 25:[ a_chars_at_loc = {} ]
+596 20:[ for _, v in ipairs(chars_at_loc) do ] >> 26:[ for _,v in ipairs(chars_at_loc) ]
+622 21:[ table.insert(a_chars_at_loc, world.player_party:member(v.uname)) ] >> 27:[ table.insert(a_chars_at_loc,world.player_party\member(v.uname)) ]
+586 22:[ end ] >> 26:[ for _,v in ipairs(chars_at_loc) ]
+689 23:[ chars_at_loc = a_chars_at_loc ] >> 28:[ chars_at_loc = a_chars_at_loc ]
+544 24:[ end ] >> 24:[ if world.server ]
+721 25:[ local hp_minus ] >> 29:[ hp_minus = () -> ]
+721 26:[ hp_minus = function() ] >> 29:[ hp_minus = () -> ]
+755 27:[ for _, char in pairs(chars_at_loc) do ] >> 30:[ for _, char in pairs(chars_at_loc) ]
+780 28:[ char:set_field("hp", char.data.hp - 2) ] >> 31:[ char\set_field("hp",char.data.hp - 2) ]
+741 29:[ end ] >> 30:[ for _, char in pairs(chars_at_loc) ]
+721 30:[ end ] >> 29:[ hp_minus = () -> ]
+822 31:[ if world.server then ] >> 32:[ if world.server ]
+839 32:[ hp_minus() ] >> 33:[ hp_minus! ]
+820 33:[ end ] >> 32:[ if world.server ]
+853 34:[ if world.client then ] >> 34:[ if world.client ]
+870 35:[ local ui = ui or require("ui") ] >> 35:[ ui = ui or require "ui" ]
+897 36:[ return ui.tween_hit(char, 1, hp_minus) ] >> 36:[ ui.tween_hit(char,1, hp_minus) ]
+851 37:[ end ] >> 34:[ if world.client ]
+942 40:[ return print("TODO!") ] >> 38:[ print("TODO!") ]
+974 43:[ return print("TODO!") ] >> 41:[ print("TODO!") ]
+195 50:[ _class_0.__parent.__init(self, ...) ] >> 11:[ super(...) ]
+208 51:[ self.requirements = { ] >> 12:[ @requirements = { ]
+230 53:[ "consume_stat", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+245 54:[ "stamina", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+256 55:[ 1 ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+265 58:[ "status", ] >> 14:[ {"status", "active"}, ]
+274 59:[ "active" ] >> 14:[ {"status", "active"}, ]
+290 62:[ "distance", ] >> 15:[ {"distance", 1}, ]
+301 63:[ 1 ] >> 15:[ {"distance", 1}, ]
+208 65:[ } ] >> 12:[ @requirements = { ]
+82 90:[ self.text = "Mope" ] >> 5:[ @text = "Mope" ]
+98 91:[ self.description = "mope" ] >> 6:[ @description = "mope" ]
+121 92:[ self.hits_icon = { ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+135 93:[ 0, ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+137 94:[ 0, ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+139 95:[ 0, ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+141 96:[ 1, ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+143 97:[ 0, ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+145 98:[ 0, ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+147 99:[ 0, ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+149 100:[ 0 ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+121 101:[ } ] >> 7:[ @hits_icon = {0,0,0,1,0,0,0,0} ]
+153 102:[ self.speed = 5 ] >> 8:[ @speed = 5 ]
+165 103:[ self.distance = 1 ] >> 9:[ @distance = 1 ]
diff --git a/debug/a_pass.lua.X b/debug/a_pass.lua.X
new file mode 100644
index 0000000..0db16e1
--- /dev/null
+++ b/debug/a_pass.lua.X
@@ -0,0 +1,31 @@
+Pos Lua >> Moon
+2 1:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+49 3:[ Ability = reg.Ability ] >> 3:[ import Ability from reg ]
+55 4:[ local mod = ... ] >> 5:[ mod = ... ]
+84 8:[ local _parent_0 = Ability ] >> 7:[ class Pass extends Ability ]
+306 11:[ return nil ] >> 17:[ nil ]
+326 14:[ local main = require("main") ] >> 20:[ main = require "main" ]
+350 15:[ local infocard = am.group():tag("infocard") ] >> 21:[ infocard = am.group!\tag("infocard") ]
+389 16:[ return main.root("screen"):append(infocard) ] >> 22:[ main.root("screen")\append(infocard) ]
+444 19:[ local main = require("main") ] >> 25:[ main = require "main" ]
+468 20:[ local infocard = main.root("infocard") ] >> 26:[ infocard = main.root("infocard") ]
+503 21:[ return main.root("screen"):remove(infocard) ] >> 27:[ main.root("screen")\remove(infocard) ]
+572 24:[ return print("Pass used") ] >> 30:[ print("Pass used") ]
+233 31:[ _class_0.__parent.__init(self, "Pass", { }) ] >> 14:[ super("Pass",{}) ]
+252 32:[ self.requirements = { } ] >> 15:[ @requirements = {} ]
+94 57:[ self.text = "Pass" ] >> 8:[ @text = "Pass" ]
+110 58:[ self.description = "Do nothing..." ] >> 9:[ @description = "Do nothing..." ]
+142 59:[ self.hits_icon = { ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+156 60:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+158 61:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+160 62:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+162 63:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+164 64:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+166 65:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+168 66:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+170 67:[ 0 ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+142 68:[ } ] >> 10:[ @hits_icon = {0,0,0,0,0,0,0,0} ]
+174 69:[ self.speed = 0 ] >> 11:[ @speed = 0 ]
+186 70:[ self.sprite = "data/no_action.png" ] >> 12:[ @sprite = "data/no_action.png" ]
+592 76:[ mod.Pass = Pass ] >> 32:[ mod.Pass = Pass ]
+609 77:[ return mod ] >> 34:[ mod ]
diff --git a/debug/a_physique.lua.X b/debug/a_physique.lua.X
new file mode 100644
index 0000000..72ceedd
--- /dev/null
+++ b/debug/a_physique.lua.X
@@ -0,0 +1,58 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+29 2:[ local ui = require("ui") ] >> 2:[ ui = require "ui" ]
+66 4:[ Ability = reg.Ability ] >> 3:[ import Ability from reg ]
+72 5:[ local mod = ... ] >> 5:[ mod = ... ]
+105 9:[ local _parent_0 = Ability ] >> 7:[ class Physique extends Ability ]
+381 12:[ local room = world.player_party.room ] >> 20:[ room = world.player_party.room ]
+414 13:[ local my_pos = char.location ] >> 21:[ my_pos = char.location ]
+471 14:[ local char_tbl1, char_tbl2 = nil, nil ] >> 23:[ char_tbl1, char_tbl2 = nil, nil ]
+519 15:[ for distance = 1, 8 do ] >> 24:[ for distance = 1, 8 ]
+528 16:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 25:[ char_tbl1 = room\at_location(my_pos + distance) ]
+579 17:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 26:[ char_tbl2 = room\at_location(my_pos - distance) ]
+632 18:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+630 20:[ end ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+505 21:[ end ] >> 24:[ for distance = 1, 8 ]
+740 24:[ local chars_at_loc = world.room.data.locations[6] ] >> 31:[ chars_at_loc = world.room.data.locations[6] ]
+788 25:[ if world.server then ] >> 32:[ if world.server ]
+805 26:[ local a_chars_at_loc = { } ] >> 33:[ a_chars_at_loc = {} ]
+838 27:[ for _, v in ipairs(chars_at_loc) do ] >> 34:[ for _,v in ipairs(chars_at_loc) ]
+864 28:[ table.insert(a_chars_at_loc, world.enemy_party:member(v.uname)) ] >> 35:[ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) ]
+828 29:[ end ] >> 34:[ for _,v in ipairs(chars_at_loc) ]
+930 30:[ chars_at_loc = a_chars_at_loc ] >> 36:[ chars_at_loc = a_chars_at_loc ]
+786 31:[ end ] >> 32:[ if world.server ]
+962 32:[ local hp_minus ] >> 37:[ hp_minus = () -> ]
+962 33:[ hp_minus = function() ] >> 37:[ hp_minus = () -> ]
+996 34:[ for _, char in pairs(chars_at_loc) do ] >> 38:[ for _, char in pairs(chars_at_loc) ]
+1021 35:[ char:set_field("hp", char.data.hp - 2) ] >> 39:[ char\set_field("hp",char.data.hp - 2) ]
+982 36:[ end ] >> 38:[ for _, char in pairs(chars_at_loc) ]
+962 37:[ end ] >> 37:[ hp_minus = () -> ]
+1063 38:[ if world.server then ] >> 40:[ if world.server ]
+1080 39:[ hp_minus() ] >> 41:[ hp_minus! ]
+1061 40:[ end ] >> 40:[ if world.server ]
+1094 41:[ if world.client then ] >> 42:[ if world.client ]
+1111 42:[ ui = ui or require("ui") ] >> 43:[ ui = ui or require "ui" ]
+1138 43:[ return ui.tween_hit(char, 2, hp_minus) ] >> 44:[ ui.tween_hit(char,2, hp_minus) ]
+1092 44:[ end ] >> 42:[ if world.client ]
+276 51:[ _class_0.__parent.__init(self, "Physique", { }) ] >> 15:[ super("Physique",{}) ]
+299 52:[ self.requirements = { ] >> 16:[ @requirements = { ]
+321 54:[ "status", ] >> 17:[ {"status", "active"}, ]
+330 55:[ "active" ] >> 17:[ {"status", "active"}, ]
+299 57:[ } ] >> 16:[ @requirements = { ]
+115 82:[ self.text = "Physique" ] >> 8:[ @text = "Physique" ]
+135 83:[ self.description = "Big ol' muscles" ] >> 9:[ @description = "Big ol' muscles" ]
+169 84:[ self.hits_icon = { ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+183 85:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+185 86:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+187 87:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+189 88:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+191 89:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+193 90:[ 1, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+195 91:[ 0, ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+197 92:[ 0 ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+169 93:[ } ] >> 10:[ @hits_icon = {0,0,0,0,0,1,0,0} ]
+201 94:[ self.sprite = "data/strong-man.png" ] >> 11:[ @sprite = "data/strong-man.png" ]
+234 95:[ self.speed = 3 ] >> 12:[ @speed = 3 ]
+246 96:[ self.distance = 1 ] >> 13:[ @distance = 1 ]
+1170 102:[ mod.Tumble = Tumble ] >> 46:[ mod.Tumble = Tumble ]
+1191 103:[ return mod ] >> 48:[ mod ]
diff --git a/debug/a_rat_bite.lua.X b/debug/a_rat_bite.lua.X
new file mode 100644
index 0000000..af0bb55
--- /dev/null
+++ b/debug/a_rat_bite.lua.X
@@ -0,0 +1,57 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+75 7:[ local _parent_0 = Ability ] >> 4:[ class RatBite extends Ability ]
+360 10:[ local room = world.player_party.room ] >> 18:[ room = world.player_party.room ]
+393 11:[ local enemy_party = room.parties[1] ] >> 19:[ enemy_party = room.parties[1] ]
+427 12:[ if enemy_party == party then ] >> 20:[ if enemy_party == party ]
+452 13:[ enemy_party = room.parties[2] ] >> 21:[ enemy_party = room.parties[2] ]
+425 14:[ end ] >> 20:[ if enemy_party == party ]
+513 17:[ local chars_at_loc = world.room.data.locations[4] ] >> 23:[ chars_at_loc = world.room.data.locations[4] ]
+561 18:[ if world.server then ] >> 24:[ if world.server ]
+578 19:[ local a_chars_at_loc = { } ] >> 25:[ a_chars_at_loc = {} ]
+611 20:[ for _, v in ipairs(chars_at_loc) do ] >> 26:[ for _,v in ipairs(chars_at_loc) ]
+637 21:[ table.insert(a_chars_at_loc, world.player_party:member(v.uname)) ] >> 27:[ table.insert(a_chars_at_loc,world.player_party\member(v.uname)) ]
+601 22:[ end ] >> 26:[ for _,v in ipairs(chars_at_loc) ]
+704 23:[ chars_at_loc = a_chars_at_loc ] >> 28:[ chars_at_loc = a_chars_at_loc ]
+559 24:[ end ] >> 24:[ if world.server ]
+736 25:[ local hp_minus ] >> 29:[ hp_minus = () -> ]
+736 26:[ hp_minus = function() ] >> 29:[ hp_minus = () -> ]
+770 27:[ for _, char in pairs(chars_at_loc) do ] >> 30:[ for _, char in pairs(chars_at_loc) ]
+795 28:[ char:set_field("hp", char.data.hp - 1) ] >> 31:[ char\set_field("hp",char.data.hp - 1) ]
+756 29:[ end ] >> 30:[ for _, char in pairs(chars_at_loc) ]
+736 30:[ end ] >> 29:[ hp_minus = () -> ]
+837 31:[ if world.server then ] >> 32:[ if world.server ]
+854 32:[ hp_minus() ] >> 33:[ hp_minus! ]
+835 33:[ end ] >> 32:[ if world.server ]
+868 34:[ if world.client then ] >> 34:[ if world.client ]
+885 35:[ local ui = ui or require("ui") ] >> 35:[ ui = ui or require "ui" ]
+912 36:[ ui.tween_hit(char, 1, hp_minus) ] >> 36:[ ui.tween_hit(char,1, hp_minus) ]
+866 37:[ end ] >> 34:[ if world.client ]
+945 38:[ return print("rat bite done, chars_at_loc:", chars_at_loc) ] >> 37:[ print("rat bite done, chars_at_loc:",chars_at_loc) ]
+1010 41:[ return print("TODO!") ] >> 39:[ print("TODO!") ]
+1042 44:[ return print("TODO!") ] >> 42:[ print("TODO!") ]
+210 51:[ _class_0.__parent.__init(self, ...) ] >> 11:[ super(...) ]
+223 52:[ self.requirements = { ] >> 12:[ @requirements = { ]
+245 54:[ "consume_stat", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+260 55:[ "stamina", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+271 56:[ 1 ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+280 59:[ "status", ] >> 14:[ {"status", "active"}, ]
+289 60:[ "active" ] >> 14:[ {"status", "active"}, ]
+305 63:[ "distance", ] >> 15:[ {"distance", 1}, ]
+316 64:[ 1 ] >> 15:[ {"distance", 1}, ]
+223 66:[ } ] >> 12:[ @requirements = { ]
+85 91:[ self.text = "Rat Bite" ] >> 5:[ @text = "Rat Bite" ]
+105 92:[ self.description = "A rat's bite" ] >> 6:[ @description = "A rat's bite" ]
+136 93:[ self.hits_icon = { ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+150 94:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+152 95:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+154 96:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+156 97:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+158 98:[ 1, ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+160 99:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+162 100:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+164 101:[ 0 ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+136 102:[ } ] >> 7:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+168 103:[ self.speed = 3 ] >> 8:[ @speed = 3 ]
+180 104:[ self.distance = 1 ] >> 9:[ @distance = 1 ]
diff --git a/debug/a_rat_scurry.lua.X b/debug/a_rat_scurry.lua.X
new file mode 100644
index 0000000..3e9b68a
--- /dev/null
+++ b/debug/a_rat_scurry.lua.X
@@ -0,0 +1,20 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+77 7:[ local _parent_0 = Ability ] >> 4:[ class RatScurry extends Ability ]
+242 10:[ local room = world.player_party.room ] >> 14:[ room = world.player_party.room ]
+275 11:[ local enemy_party = room.parties[1] ] >> 15:[ enemy_party = room.parties[1] ]
+309 12:[ if enemy_party == party then ] >> 16:[ if enemy_party == party ]
+334 13:[ enemy_party = room.parties[2] ] >> 17:[ enemy_party = room.parties[2] ]
+307 14:[ end ] >> 16:[ if enemy_party == party ]
+395 17:[ return print("Rat scurry used") ] >> 19:[ print("Rat scurry used") ]
+436 20:[ return print("TODO!") ] >> 22:[ print("TODO!") ]
+468 23:[ return print("TODO!") ] >> 25:[ print("TODO!") ]
+147 30:[ _class_0.__parent.__init(self, ...) ] >> 9:[ super(...) ]
+160 31:[ self.requirements = { ] >> 10:[ @requirements = { ]
+182 33:[ "status", ] >> 11:[ {"status", "active"}, ]
+191 34:[ "active" ] >> 11:[ {"status", "active"}, ]
+160 36:[ } ] >> 10:[ @requirements = { ]
+87 61:[ self.text = "Scurry" ] >> 5:[ @text = "Scurry" ]
+105 62:[ self.speed = 5 ] >> 6:[ @speed = 5 ]
+117 63:[ self.distance = 1 ] >> 7:[ @distance = 1 ]
diff --git a/debug/a_ruminate.lua.X b/debug/a_ruminate.lua.X
new file mode 100644
index 0000000..7166706
--- /dev/null
+++ b/debug/a_ruminate.lua.X
@@ -0,0 +1,38 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+76 7:[ local _parent_0 = Ability ] >> 4:[ class Ruminate extends Ability ]
+357 10:[ local room = world.player_party.room ] >> 18:[ room = world.player_party.room ]
+390 11:[ local enemy_party = room.parties[1] ] >> 19:[ enemy_party = room.parties[1] ]
+424 12:[ if enemy_party == party then ] >> 20:[ if enemy_party == party ]
+449 13:[ enemy_party = room.parties[2] ] >> 21:[ enemy_party = room.parties[2] ]
+422 14:[ end ] >> 20:[ if enemy_party == party ]
+526 17:[ for _, member in pairs(party.members) do ] >> 23:[ for _, member in pairs(party.members) ]
+551 18:[ char:set_field("hp", char.data.hp + 1) ] >> 24:[ char\set_field("hp",char.data.hp + 1) ]
+510 19:[ end ] >> 23:[ for _, member in pairs(party.members) ]
+603 22:[ return print("TODO!") ] >> 26:[ print("TODO!") ]
+635 25:[ return print("TODO!") ] >> 29:[ print("TODO!") ]
+207 32:[ _class_0.__parent.__init(self, ...) ] >> 11:[ super(...) ]
+220 33:[ self.requirements = { ] >> 12:[ @requirements = { ]
+242 35:[ "consume_stat", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+257 36:[ "stamina", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+268 37:[ 1 ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+277 40:[ "status", ] >> 14:[ {"status", "active"}, ]
+286 41:[ "active" ] >> 14:[ {"status", "active"}, ]
+302 44:[ "distance", ] >> 15:[ {"distance", 1}, ]
+313 45:[ 1 ] >> 15:[ {"distance", 1}, ]
+220 47:[ } ] >> 12:[ @requirements = { ]
+86 72:[ self.text = "Ruminate" ] >> 5:[ @text = "Ruminate" ]
+106 73:[ self.description = "Ruminate" ] >> 6:[ @description = "Ruminate" ]
+133 74:[ self.hits_icon = { ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+147 75:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+149 76:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+151 77:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+153 78:[ 0, ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+155 79:[ 1, ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+157 80:[ 1, ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+159 81:[ 1, ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+161 82:[ 1 ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+133 83:[ } ] >> 7:[ @hits_icon = {0,0,0,0,1,1,1,1} ]
+165 84:[ self.speed = 5 ] >> 8:[ @speed = 5 ]
+177 85:[ self.distance = 1 ] >> 9:[ @distance = 1 ]
diff --git a/debug/a_strum.lua.X b/debug/a_strum.lua.X
new file mode 100644
index 0000000..9c764af
--- /dev/null
+++ b/debug/a_strum.lua.X
@@ -0,0 +1,41 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+54 4:[ local mod = ... ] >> 4:[ mod = ... ]
+64 5:[ print("In tubmle, reg is", reg) ] >> 5:[ print("In tubmle, reg is",reg) ]
+115 9:[ local _parent_0 = Ability ] >> 7:[ class Strum extends Ability ]
+398 12:[ local room = world.player_party.room ] >> 20:[ room = world.player_party.room ]
+431 13:[ local my_pos = char.location ] >> 21:[ my_pos = char.location ]
+488 14:[ local char_tbl1, char_tbl2 = nil, nil ] >> 23:[ char_tbl1, char_tbl2 = nil, nil ]
+536 15:[ for distance = 1, 8 do ] >> 24:[ for distance = 1, 8 ]
+545 16:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 25:[ char_tbl1 = room\at_location(my_pos + distance) ]
+596 17:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 26:[ char_tbl2 = room\at_location(my_pos - distance) ]
+649 18:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+647 20:[ end ] >> 27:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+522 21:[ end ] >> 24:[ for distance = 1, 8 ]
+757 24:[ print("Doing Strum, party was", party) ] >> 31:[ print("Doing Strum, party was", party) ]
+814 25:[ for _, member in pairs(party.members) do ] >> 32:[ for _, member in pairs(party.members) ]
+839 26:[ member:set_field("hp", member.data.hp + 1) ] >> 33:[ member\set_field("hp",member.data.hp + 1) ]
+798 27:[ end ] >> 32:[ for _, member in pairs(party.members) ]
+296 34:[ _class_0.__parent.__init(self, "Strum", { }) ] >> 15:[ super("Strum",{}) ]
+316 35:[ self.requirements = { ] >> 16:[ @requirements = { ]
+338 37:[ "status", ] >> 17:[ {"status", "active"}, ]
+347 38:[ "active" ] >> 17:[ {"status", "active"}, ]
+316 40:[ } ] >> 16:[ @requirements = { ]
+125 65:[ self.text = "Strum" ] >> 8:[ @text = "Strum" ]
+142 66:[ self.description = "Strum a cord to heal the troupe!" ] >> 9:[ @description = "Strum a cord to heal the troupe!" ]
+193 67:[ self.hits_icon = { ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+207 68:[ 1, ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+209 69:[ 1, ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+211 70:[ 1, ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+213 71:[ 1, ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+215 72:[ 0, ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+217 73:[ 0, ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+219 74:[ 0, ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+221 75:[ 0 ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+193 76:[ } ] >> 10:[ @hits_icon = {1,1,1,1,0,0,0,0} ]
+225 77:[ self.sprite = "data/g-clef.png" ] >> 11:[ @sprite = "data/g-clef.png" ]
+254 78:[ self.speed = 1 ] >> 12:[ @speed = 1 ]
+266 79:[ self.distance = 1 ] >> 13:[ @distance = 1 ]
+882 85:[ mod.Tumble = Tumble ] >> 35:[ mod.Tumble = Tumble ]
+903 86:[ return mod ] >> 37:[ mod ]
diff --git a/debug/a_sulk.lua.X b/debug/a_sulk.lua.X
new file mode 100644
index 0000000..c75fcd4
--- /dev/null
+++ b/debug/a_sulk.lua.X
@@ -0,0 +1,59 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+48 3:[ Ability = reg.Ability ] >> 2:[ import Ability from reg ]
+72 7:[ local _parent_0 = Ability ] >> 4:[ class Sulk extends Ability ]
+345 10:[ local room = world.player_party.room ] >> 18:[ room = world.player_party.room ]
+378 11:[ local enemy_party = room.parties[1] ] >> 19:[ enemy_party = room.parties[1] ]
+412 12:[ if enemy_party == party then ] >> 20:[ if enemy_party == party ]
+437 13:[ enemy_party = room.parties[2] ] >> 21:[ enemy_party = room.parties[2] ]
+410 14:[ end ] >> 20:[ if enemy_party == party ]
+505 17:[ for i = 3, 4 do ] >> 23:[ for i = 3,4 ]
+513 18:[ local chars_at_loc = world.room.data.locations[i] ] >> 24:[ chars_at_loc = world.room.data.locations[i] ]
+562 19:[ if world.server then ] >> 25:[ if world.server ]
+580 20:[ local a_chars_at_loc = { } ] >> 26:[ a_chars_at_loc = {} ]
+614 21:[ for _, v in ipairs(chars_at_loc) do ] >> 27:[ for _,v in ipairs(chars_at_loc) ]
+641 22:[ table.insert(a_chars_at_loc, world.player_party:member(v.uname)) ] >> 28:[ table.insert(a_chars_at_loc,world.player_party\member(v.uname)) ]
+604 23:[ end ] >> 27:[ for _,v in ipairs(chars_at_loc) ]
+709 24:[ chars_at_loc = a_chars_at_loc ] >> 29:[ chars_at_loc = a_chars_at_loc ]
+560 25:[ end ] >> 25:[ if world.server ]
+742 26:[ local hp_minus ] >> 30:[ hp_minus = () -> ]
+742 27:[ hp_minus = function() ] >> 30:[ hp_minus = () -> ]
+777 28:[ for _, char in pairs(chars_at_loc) do ] >> 31:[ for _, char in pairs(chars_at_loc) ]
+803 29:[ char:set_field("hp", char.data.hp - 1) ] >> 32:[ char\set_field("hp",char.data.hp - 1) ]
+763 30:[ end ] >> 31:[ for _, char in pairs(chars_at_loc) ]
+742 31:[ end ] >> 30:[ hp_minus = () -> ]
+846 32:[ if world.server then ] >> 33:[ if world.server ]
+864 33:[ hp_minus() ] >> 34:[ hp_minus! ]
+844 34:[ end ] >> 33:[ if world.server ]
+879 35:[ if world.client then ] >> 35:[ if world.client ]
+897 36:[ local ui = ui or require("ui") ] >> 36:[ ui = ui or require "ui" ]
+925 37:[ ui.tween_hit(char, 8 - i, hp_minus) ] >> 37:[ ui.tween_hit(char,8 - i, hp_minus) ]
+877 38:[ end ] >> 35:[ if world.client ]
+963 39:[ print("rat bite done, chars_at_loc:", chars_at_loc) ] >> 38:[ print("rat bite done, chars_at_loc:",chars_at_loc) ]
+498 40:[ end ] >> 23:[ for i = 3,4 ]
+1028 43:[ return print("TODO!") ] >> 40:[ print("TODO!") ]
+1060 46:[ return print("TODO!") ] >> 43:[ print("TODO!") ]
+195 53:[ _class_0.__parent.__init(self, ...) ] >> 11:[ super(...) ]
+208 54:[ self.requirements = { ] >> 12:[ @requirements = { ]
+230 56:[ "consume_stat", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+245 57:[ "stamina", ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+256 58:[ 1 ] >> 13:[ {"consume_stat", "stamina", 1}, ]
+265 61:[ "status", ] >> 14:[ {"status", "active"}, ]
+274 62:[ "active" ] >> 14:[ {"status", "active"}, ]
+290 65:[ "distance", ] >> 15:[ {"distance", 1}, ]
+301 66:[ 1 ] >> 15:[ {"distance", 1}, ]
+208 68:[ } ] >> 12:[ @requirements = { ]
+82 93:[ self.text = "Sulk" ] >> 5:[ @text = "Sulk" ]
+98 94:[ self.description = "sulk" ] >> 6:[ @description = "sulk" ]
+121 95:[ self.hits_icon = { ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+135 96:[ 0, ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+137 97:[ 0, ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+139 98:[ 1, ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+141 99:[ 1, ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+143 100:[ 0, ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+145 101:[ 0, ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+147 102:[ 0, ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+149 103:[ 0 ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+121 104:[ } ] >> 7:[ @hits_icon = {0,0,1,1,0,0,0,0} ]
+153 105:[ self.speed = 3 ] >> 8:[ @speed = 3 ]
+165 106:[ self.distance = 1 ] >> 9:[ @distance = 1 ]
diff --git a/debug/a_test.lua.X b/debug/a_test.lua.X
new file mode 100644
index 0000000..c6d5eb7
--- /dev/null
+++ b/debug/a_test.lua.X
@@ -0,0 +1,63 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+29 2:[ local ui = require("ui") ] >> 2:[ ui = require "ui" ]
+66 4:[ Ability = reg.Ability ] >> 3:[ import Ability from reg ]
+72 5:[ local mod = ... ] >> 5:[ mod = ... ]
+82 6:[ print("In tubmle, reg is", reg) ] >> 6:[ print("In tubmle, reg is",reg) ]
+138 10:[ local _parent_0 = Ability ] >> 8:[ class Everything extends Ability ]
+417 13:[ local room = world.player_party.room ] >> 21:[ room = world.player_party.room ]
+450 14:[ local my_pos = char.location ] >> 22:[ my_pos = char.location ]
+507 15:[ local char_tbl1, char_tbl2 = nil, nil ] >> 24:[ char_tbl1, char_tbl2 = nil, nil ]
+555 16:[ for distance = 1, 8 do ] >> 25:[ for distance = 1, 8 ]
+564 17:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 26:[ char_tbl1 = room\at_location(my_pos + distance) ]
+615 18:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 27:[ char_tbl2 = room\at_location(my_pos - distance) ]
+668 19:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 28:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+666 21:[ end ] >> 28:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+541 22:[ end ] >> 25:[ for distance = 1, 8 ]
+783 25:[ for i = 5, 8 do ] >> 32:[ for i = 5,8 ]
+791 26:[ print("Doing tumble") ] >> 33:[ print("Doing tumble") ]
+816 27:[ local chars_at_loc = world.room.data.locations[i] ] >> 34:[ chars_at_loc = world.room.data.locations[i] ]
+863 28:[ print("chars at loc:", chars_at_loc) ] >> 35:[ print("chars at loc:",chars_at_loc) ]
+904 29:[ if world.server then ] >> 36:[ if world.server ]
+922 30:[ local a_chars_at_loc = { } ] >> 37:[ a_chars_at_loc = {} ]
+956 31:[ for _, v in ipairs(chars_at_loc) do ] >> 38:[ for _,v in ipairs(chars_at_loc) ]
+983 32:[ table.insert(a_chars_at_loc, world.enemy_party:member(v.uname)) ] >> 39:[ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) ]
+946 33:[ end ] >> 38:[ for _,v in ipairs(chars_at_loc) ]
+1050 34:[ chars_at_loc = a_chars_at_loc ] >> 40:[ chars_at_loc = a_chars_at_loc ]
+902 35:[ end ] >> 36:[ if world.server ]
+1083 36:[ local hp_minus ] >> 41:[ hp_minus = () -> ]
+1083 37:[ hp_minus = function() ] >> 41:[ hp_minus = () -> ]
+1118 38:[ for _, char in pairs(chars_at_loc) do ] >> 42:[ for _, char in pairs(chars_at_loc) ]
+1144 39:[ char:set_field("hp", char.data.hp - 10) ] >> 43:[ char\set_field("hp",char.data.hp - 10) ]
+1104 40:[ end ] >> 42:[ for _, char in pairs(chars_at_loc) ]
+1083 41:[ end ] >> 41:[ hp_minus = () -> ]
+1188 42:[ if world.server then ] >> 44:[ if world.server ]
+1206 43:[ hp_minus() ] >> 45:[ hp_minus! ]
+1186 44:[ end ] >> 44:[ if world.server ]
+1221 45:[ if world.client then ] >> 46:[ if world.client ]
+1239 46:[ ui = ui or require("ui") ] >> 47:[ ui = ui or require "ui" ]
+1267 47:[ ui.tween_hit(char, i - 4, hp_minus) ] >> 48:[ ui.tween_hit(char,i-4, hp_minus) ]
+1219 48:[ end ] >> 46:[ if world.client ]
+776 49:[ end ] >> 32:[ for i = 5,8 ]
+310 56:[ _class_0.__parent.__init(self, "Everything", { }) ] >> 16:[ super("Everything",{}) ]
+335 57:[ self.requirements = { ] >> 17:[ @requirements = { ]
+357 59:[ "status", ] >> 18:[ {"status", "active"}, ]
+366 60:[ "active" ] >> 18:[ {"status", "active"}, ]
+335 62:[ } ] >> 17:[ @requirements = { ]
+148 87:[ self.text = "Everything" ] >> 9:[ @text = "Everything" ]
+170 88:[ self.description = "destroy everything" ] >> 10:[ @description = "destroy everything" ]
+207 89:[ self.hits_icon = { ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+221 90:[ 1, ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+223 91:[ 1, ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+225 92:[ 1, ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+227 93:[ 1, ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+229 94:[ 1, ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+231 95:[ 1, ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+233 96:[ 1, ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+235 97:[ 1 ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+207 98:[ } ] >> 11:[ @hits_icon = {1,1,1,1,1,1,1,1} ]
+239 99:[ self.sprite = "data/tumble.png" ] >> 12:[ @sprite = "data/tumble.png" ]
+268 100:[ self.speed = 5 ] >> 13:[ @speed = 5 ]
+280 101:[ self.distance = 1 ] >> 14:[ @distance = 1 ]
+1301 107:[ mod.Test = Test ] >> 50:[ mod.Test = Test ]
+1318 108:[ return mod ] >> 52:[ mod ]
diff --git a/debug/a_tumble.lua.X b/debug/a_tumble.lua.X
new file mode 100644
index 0000000..301fc76
--- /dev/null
+++ b/debug/a_tumble.lua.X
@@ -0,0 +1,61 @@
+Pos Lua >> Moon
+1 1:[ local reg = require("ability_reg") ] >> 1:[ reg = require "ability_reg" ]
+29 2:[ local ui = require("ui") ] >> 2:[ ui = require "ui" ]
+66 4:[ Ability = reg.Ability ] >> 3:[ import Ability from reg ]
+72 5:[ local mod = ... ] >> 5:[ mod = ... ]
+82 6:[ print("In tubmle, reg is", reg) ] >> 6:[ print("In tubmle, reg is",reg) ]
+134 10:[ local _parent_0 = Ability ] >> 8:[ class Tumble extends Ability ]
+417 13:[ local room = world.player_party.room ] >> 21:[ room = world.player_party.room ]
+450 14:[ local my_pos = char.location ] >> 22:[ my_pos = char.location ]
+507 15:[ local char_tbl1, char_tbl2 = nil, nil ] >> 24:[ char_tbl1, char_tbl2 = nil, nil ]
+555 16:[ for distance = 1, 8 do ] >> 25:[ for distance = 1, 8 ]
+564 17:[ char_tbl1 = room:at_location(my_pos + distance) ] >> 26:[ char_tbl1 = room\at_location(my_pos + distance) ]
+615 18:[ char_tbl2 = room:at_location(my_pos - distance) ] >> 27:[ char_tbl2 = room\at_location(my_pos - distance) ]
+668 19:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) then ] >> 28:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+666 21:[ end ] >> 28:[ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) ]
+541 22:[ end ] >> 25:[ for distance = 1, 8 ]
+776 25:[ print("Doing tumble") ] >> 32:[ print("Doing tumble") ]
+800 26:[ local chars_at_loc = world.room.data.locations[5] ] >> 33:[ chars_at_loc = world.room.data.locations[5] ]
+846 27:[ print("chars at loc:", chars_at_loc) ] >> 34:[ print("chars at loc:",chars_at_loc) ]
+886 28:[ if world.server then ] >> 35:[ if world.server ]
+903 29:[ local a_chars_at_loc = { } ] >> 36:[ a_chars_at_loc = {} ]
+936 30:[ for _, v in ipairs(chars_at_loc) do ] >> 37:[ for _,v in ipairs(chars_at_loc) ]
+962 31:[ table.insert(a_chars_at_loc, world.enemy_party:member(v.uname)) ] >> 38:[ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) ]
+926 32:[ end ] >> 37:[ for _,v in ipairs(chars_at_loc) ]
+1028 33:[ chars_at_loc = a_chars_at_loc ] >> 39:[ chars_at_loc = a_chars_at_loc ]
+884 34:[ end ] >> 35:[ if world.server ]
+1060 35:[ local hp_minus ] >> 40:[ hp_minus = () -> ]
+1060 36:[ hp_minus = function() ] >> 40:[ hp_minus = () -> ]
+1094 37:[ for _, char in pairs(chars_at_loc) do ] >> 41:[ for _, char in pairs(chars_at_loc) ]
+1119 38:[ char:set_field("hp", char.data.hp - 3) ] >> 42:[ char\set_field("hp",char.data.hp - 3) ]
+1080 39:[ end ] >> 41:[ for _, char in pairs(chars_at_loc) ]
+1060 40:[ end ] >> 40:[ hp_minus = () -> ]
+1161 41:[ if world.server then ] >> 43:[ if world.server ]
+1178 42:[ hp_minus() ] >> 44:[ hp_minus! ]
+1159 43:[ end ] >> 43:[ if world.server ]
+1192 44:[ if world.client then ] >> 45:[ if world.client ]
+1209 45:[ ui = ui or require("ui") ] >> 46:[ ui = ui or require "ui" ]
+1236 46:[ return ui.tween_hit(char, 1, hp_minus) ] >> 47:[ ui.tween_hit(char,1, hp_minus) ]
+1190 47:[ end ] >> 45:[ if world.client ]
+314 54:[ _class_0.__parent.__init(self, "Tumble", { }) ] >> 16:[ super("Tumble",{}) ]
+335 55:[ self.requirements = { ] >> 17:[ @requirements = { ]
+357 57:[ "status", ] >> 18:[ {"status", "active"}, ]
+366 58:[ "active" ] >> 18:[ {"status", "active"}, ]
+335 60:[ } ] >> 17:[ @requirements = { ]
+144 85:[ self.text = "Tumble" ] >> 9:[ @text = "Tumble" ]
+162 86:[ self.description = "Tumble around for those nearby" ] >> 10:[ @description = "Tumble around for those nearby" ]
+211 87:[ self.hits_icon = { ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+225 88:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+227 89:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+229 90:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+231 91:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+233 92:[ 1, ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+235 93:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+237 94:[ 0, ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+239 95:[ 0 ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+211 96:[ } ] >> 11:[ @hits_icon = {0,0,0,0,1,0,0,0} ]
+243 97:[ self.sprite = "data/tumble.png" ] >> 12:[ @sprite = "data/tumble.png" ]
+272 98:[ self.speed = 5 ] >> 13:[ @speed = 5 ]
+284 99:[ self.distance = 1 ] >> 14:[ @distance = 1 ]
+1268 105:[ mod.Tumble = Tumble ] >> 49:[ mod.Tumble = Tumble ]
+1289 106:[ return mod ] >> 51:[ mod ]
diff --git a/debug/ability_reg.lua.X b/debug/ability_reg.lua.X
new file mode 100644
index 0000000..1302808
--- /dev/null
+++ b/debug/ability_reg.lua.X
@@ -0,0 +1,26 @@
+Pos Lua >> Moon
+1 1:[ local ui = require("ui") ] >> 1:[ ui = require "ui" ]
+19 2:[ local reg = ... ] >> 2:[ reg = ... ]
+30 3:[ reg.list = { } ] >> 4:[ reg.list = {} ]
+1101 9:[ local main = require("main") ] >> 30:[ main = require "main" ]
+1125 10:[ local infocard = am.group():tag("infocard") ] >> 31:[ infocard = am.group!\tag("infocard") ]
+1164 11:[ main.root("screen"):append(infocard) ] >> 32:[ main.root("screen")\append(infocard) ]
+1203 12:[ return ui.build_infocard(infocard, self.__class) ] >> 33:[ ui.build_infocard(infocard,@@) ]
+1252 15:[ local main = require("main") ] >> 36:[ main = require "main" ]
+1276 16:[ return main.root:remove("infocard") ] >> 37:[ main.root\remove("infocard") ]
+365 22:[ self.sprite = self.__class.sprite ] >> 13:[ @sprite = @@sprite ]
+386 23:[ self.name = name or self.__class.__name ] >> 14:[ @name = name or @.__class.__name ]
+421 24:[ self.data = data ] >> 15:[ @data = data ]
+326 38:[ self.children = { } ] >> 11:[ @children = {} ]
+464 40:[ assert(child.use, "abilities must have a .use") ] >> 18:[ assert(child.use, "abilities must have a .use") ]
+514 41:[ assert(type(child.use) == "function", "abilities must have a .use() function") ] >> 19:[ assert(type(child.use) == "function", "abilities must have a .use() function") ]
+595 42:[ assert(type(child.load) == "function", "abilities must have a .load() that shows their infocard") ] >> 20:[ assert(type(child.load) == "function", "abilities must have a .load() that shows their infocard") ]
+695 43:[ assert(type(child.unload) == "function", "abilities must have an .unload() that removes their infocard") ] >> 21:[ assert(type(child.unload) == "function", "abilities must have an .unload() that removes their infocard") ]
+802 44:[ assert(child.text, "ability must have text") ] >> 22:[ assert(child.text, "ability must have text") ]
+849 45:[ assert(child.description, "ability must have a description") ] >> 23:[ assert(child.description, "ability must have a description") ]
+912 46:[ assert(child.hits_icon, "ability must have a hits icon") ] >> 24:[ assert(child.hits_icon, "ability must have a hits icon") ]
+971 47:[ assert(child.speed, "ability must have a speed") ] >> 25:[ assert(child.speed, "ability must have a speed") ]
+1022 48:[ self.__class.children[child.__name] = child ] >> 26:[ @@.children[child.__name] = child ]
+1058 49:[ reg[child.__name] = child ] >> 27:[ reg[child.__name] = child ]
+1407 53:[ reg["Ability"] = Ability ] >> 41:[ reg["Ability"] = Ability ]
+1433 54:[ return reg ] >> 43:[ return reg ]
diff --git a/debug/action.lua.X b/debug/action.lua.X
new file mode 100644
index 0000000..778ce1a
--- /dev/null
+++ b/debug/action.lua.X
@@ -0,0 +1,66 @@
+Pos Lua >> Moon
+50 1:[ local char = require("char") ] >> 2:[ char = require "char" ]
+72 2:[ local mod = ... ] >> 3:[ mod = ... ]
+83 3:[ mod.msg = { ] >> 5:[ mod.msg = { ]
+96 4:[ "request_class_change", ] >> 6:[ "request_class_change" ,--client to server request ]
+148 5:[ "confirm_class_change", ] >> 7:[ "confirm_class_change" ,-- server to client answer ]
+200 6:[ "deny_class_change", ] >> 8:[ "deny_class_change" ,-- server to client answer ]
+249 7:[ "info_class_change", ] >> 9:[ "info_class_change" ,-- server to client info ]
+296 8:[ "player_joined", ] >> 10:[ "player_joined" ,--client to server hello ]
+339 9:[ "info_player_joined", ] >> 11:[ "info_player_joined" ,-- server to client info ]
+387 10:[ "info_player_dropped", ] >> 12:[ "info_player_dropped" ,-- server to client info ]
+436 11:[ "request_campaign_start", ] >> 13:[ "request_campaign_start" , --client to server, start game ]
+495 12:[ "info_campaign_start", ] >> 14:[ "info_campaign_start" , --server to client, game is starting ]
+557 13:[ "request_player_list", ] >> 15:[ "request_player_list" ,-- client to server request ]
+609 14:[ "respond_player_list", ] >> 16:[ "respond_player_list" ,--server to client respond with player list ]
+677 15:[ "info_room", ] >> 17:[ "info_room" ,-- server to client notify about room info ]
+734 16:[ "info_turnup", ] >> 18:[ "info_turnup", -- sever to client, when is the turn up? ]
+791 17:[ "info_enemy_party", ] >> 19:[ "info_enemy_party", --server to client notify about enemy party ]
+856 18:[ "set_action", ] >> 20:[ "set_action" ,-- client to server, notify action for next turn ]
+920 19:[ "info_timeref", ] >> 21:[ "info_timeref" , --server to client, when the next turn is up ]
+983 20:[ "info_actions", ] >> 22:[ "info_actions" ,-- server to client set animations to play ]
+1043 21:[ "info_deaths", ] >> 23:[ "info_deaths", -- server to client, deaths that are about to happen ]
+1112 22:[ "info_loot", ] >> 24:[ "info_loot" ,--server to client loot for clearing a room ]
+1170 23:[ "request_camp_action", ] >> 25:[ "request_camp_action" ,--client to server what action to take at camp ]
+1241 24:[ "info_camp", ] >> 26:[ "info_camp" ,--server to client actions taken at camp ]
+1296 25:[ "info_defeat" ] >> 27:[ "info_defeat", --server to client, you're done! ]
+83 26:[ } ] >> 5:[ mod.msg = { ]
+1346 27:[ mod.msg_rev = { } ] >> 29:[ mod.msg_rev = {} ]
+1373 28:[ for k, v in pairs(mod.msg) do ] >> 30:[ for k,v in pairs(mod.msg) do ]
+1393 29:[ mod.msg_rev[v] = k ] >> 31:[ mod.msg_rev[v] = k ]
+1363 30:[ end ] >> 30:[ for k,v in pairs(mod.msg) do ]
+1413 31:[ mod.action_reg = { } ] >> 33:[ mod.action_reg = {} ]
+1433 32:[ mod.class_counter = 1 ] >> 34:[ mod.class_counter = 1 ]
+1456 33:[ mod.request_class_change = function(what) ] >> 36:[ mod.request_class_change = (what) -> ]
+1494 34:[ assert(what, "class may not be nil") ] >> 37:[ assert(what, "class may not be nil") ]
+1532 35:[ assert(char.class_order_rev[what], "class must be one of " .. tostring(char.class_order)) ] >> 38:[ assert(char.class_order_rev[what], "class must be one of " .. tostring(char.class_order)) ]
+1623 36:[ local msg_json = am.to_json({ ] >> 39:[ msg_json = am.to_json({msg:"request_class_change", time:am.current_time(), class:what}) ]
+1650 37:[ msg = "request_class_change", ] >> 39:[ msg_json = am.to_json({msg:"request_class_change", time:am.current_time(), class:what}) ]
+1679 38:[ time = am.current_time(), ] >> 39:[ msg_json = am.to_json({msg:"request_class_change", time:am.current_time(), class:what}) ]
+1704 39:[ class = what ] >> 39:[ msg_json = am.to_json({msg:"request_class_change", time:am.current_time(), class:what}) ]
+1623 40:[ }) ] >> 39:[ msg_json = am.to_json({msg:"request_class_change", time:am.current_time(), class:what}) ]
+1712 41:[ return am.eval_js(string.format("CLIENT.send(%q);", msg_json)) ] >> 40:[ am.eval_js(string.format("CLIENT.send(%q);",msg_json)) ]
+1456 42:[ end ] >> 36:[ mod.request_class_change = (what) -> ]
+1768 43:[ mod.start_game = function() ] >> 42:[ mod.start_game = () -> ]
+1792 44:[ local msg_json = am.to_json({ ] >> 43:[ msg_json = am.to_json({msg:"request_campaign_start", time:am.current_time()}) ]
+1819 45:[ msg = "request_campaign_start", ] >> 43:[ msg_json = am.to_json({msg:"request_campaign_start", time:am.current_time()}) ]
+1850 46:[ time = am.current_time() ] >> 43:[ msg_json = am.to_json({msg:"request_campaign_start", time:am.current_time()}) ]
+1792 47:[ }) ] >> 43:[ msg_json = am.to_json({msg:"request_campaign_start", time:am.current_time()}) ]
+1871 48:[ return am.eval_js(string.format("CLIENT.send(%q);", msg_json)) ] >> 44:[ am.eval_js(string.format("CLIENT.send(%q);",msg_json)) ]
+1768 49:[ end ] >> 42:[ mod.start_game = () -> ]
+1927 50:[ mod.sync_players = function() ] >> 46:[ mod.sync_players = () -> ]
+1953 51:[ local msg_json = am.to_json({ ] >> 47:[ msg_json = am.to_json({msg:"request_player_list", time:am.current_time()}) ]
+1980 52:[ msg = "request_player_list", ] >> 47:[ msg_json = am.to_json({msg:"request_player_list", time:am.current_time()}) ]
+2008 53:[ time = am.current_time() ] >> 47:[ msg_json = am.to_json({msg:"request_player_list", time:am.current_time()}) ]
+1953 54:[ }) ] >> 47:[ msg_json = am.to_json({msg:"request_player_list", time:am.current_time()}) ]
+2029 55:[ return am.eval_js(string.format("CLIENT.send(%q);", msg_json)) ] >> 48:[ am.eval_js(string.format("CLIENT.send(%q);",msg_json)) ]
+1927 56:[ end ] >> 46:[ mod.sync_players = () -> ]
+2085 57:[ mod.set_action = function(name) ] >> 50:[ mod.set_action = (name) -> ]
+2113 58:[ local msg_json = am.to_json({ ] >> 51:[ msg_json = am.to_json({ ]
+2143 59:[ msg = "set_action", ] >> 52:[ msg: "set_action" ]
+2164 60:[ time = am.current_time(), ] >> 53:[ time: am.current_time! ]
+2191 61:[ action = name ] >> 54:[ action: name ]
+2113 62:[ }) ] >> 51:[ msg_json = am.to_json({ ]
+2202 63:[ return am.eval_js(string.format("CLIENT.send(%q);", msg_json)) ] >> 56:[ am.eval_js(string.format("CLIENT.send(%q);",msg_json)) ]
+2085 64:[ end ] >> 50:[ mod.set_action = (name) -> ]
+2258 65:[ return mod ] >> 58:[ mod ]
diff --git a/debug/battle_menu.lua.X b/debug/battle_menu.lua.X
new file mode 100644
index 0000000..9df0636
--- /dev/null
+++ b/debug/battle_menu.lua.X
@@ -0,0 +1,152 @@
+Pos Lua >> Moon
+1 1:[ local world = require("world") ] >> 1:[ world = require "world" ]
+25 2:[ local main = require("main") ] >> 2:[ main = require "main" ]
+47 3:[ local color = require("color") ] >> 3:[ color = require "color" ]
+71 4:[ local ui = require("ui") ] >> 4:[ ui = require "ui" ]
+89 5:[ local bp = require("broadphase") ] >> 5:[ bp = require "broadphase" ]
+115 6:[ local pass = require("a_pass") ] >> 6:[ pass = require "a_pass" ]
+155 8:[ Pass = pass.Pass ] >> 7:[ import Pass from pass ]
+161 9:[ local action = require("action") ] >> 8:[ action = require "action" ]
+188 10:[ local mod = ... ] >> 10:[ mod = ... ]
+199 11:[ local pip_width = 24 ] >> 12:[ pip_width = 24 ]
+215 12:[ mod.time_pips = am.group() ] >> 14:[ mod.time_pips = am.group! ]
+241 13:[ local pip_x_start = ((main.pips / 2) + 1) * -pip_width ] >> 15:[ pip_x_start = ((main.pips/2)+1) * -pip_width ]
+286 14:[ local pip_sprites = { } ] >> 16:[ pip_sprites = {} ]
+303 15:[ local ms_per_pip = 1000 ] >> 17:[ ms_per_pip = 1000 ]
+322 16:[ mod.ability_selector = am.group() ] >> 19:[ mod.ability_selector = am.group! ]
+355 17:[ mod.playerturn_up = 0 ] >> 20:[ mod.playerturn_up = 0 ]
+378 18:[ mod.set_playerturn_up = function(self, time) ] >> 22:[ mod.set_playerturn_up = (time) => ]
+413 19:[ mod.playerturn_up = time ] >> 23:[ mod.playerturn_up = time ]
+378 20:[ end ] >> 22:[ mod.set_playerturn_up = (time) => ]
+439 21:[ mod.victory_g = am.group() ] >> 25:[ mod.victory_g = am.group! ]
+465 22:[ mod.victory_text = { } ] >> 26:[ mod.victory_text = {} ]
+488 23:[ mod.action_phase = am.group() ] >> 28:[ mod.action_phase = am.group! --group used to animate characters during battle, also holds dammage values ]
+593 24:[ mod.victory_show = false ] >> 29:[ mod.victory_show = false ]
+618 25:[ mod.loaded_ability = nil ] >> 30:[ mod.loaded_ability = nil ]
+644 26:[ mod.load = function() ] >> 32:[ mod.load = () -> ]
+662 27:[ local pip_trans = am.translate(pip_x_start, 240 - (pip_width / 2)) ] >> 33:[ pip_trans = am.translate(pip_x_start,240 - (pip_width/2)) ]
+728 28:[ for i = 1, main.pips do ] >> 34:[ for i = 1,main.pips ]
+743 29:[ local pip_loc = am.translate(pip_width * i, 0) ] >> 35:[ pip_loc = am.translate(pip_width*i,0) ]
+783 30:[ pip_trans:append(pip_loc ^ am.sprite("data/pip_frame.png", color.white)) ] >> 36:[ pip_trans\append(pip_loc^ am.sprite("data/pip_frame.png",color.white)) ]
+856 31:[ local light_sprite = am.sprite("data/pip_dark.png", color.white) ] >> 37:[ light_sprite = am.sprite("data/pip_dark.png",color.white) ]
+916 32:[ pip_loc:append(light_sprite) ] >> 38:[ pip_loc\append(light_sprite) ]
+947 33:[ table.insert(pip_sprites, light_sprite) ] >> 39:[ table.insert(pip_sprites,light_sprite) ]
+721 34:[ end ] >> 34:[ for i = 1,main.pips ]
+987 35:[ mod.time_pips:append(pip_trans) ] >> 40:[ mod.time_pips\append(pip_trans) ]
+1020 36:[ local ability_buttons = { } ] >> 41:[ ability_buttons = {} ]
+1042 37:[ local ability_trans = am.group() ] >> 42:[ ability_trans = am.group! ]
+1069 38:[ main.root("screen"):append(mod.victory_g) ] >> 43:[ main.root("screen")\append(mod.victory_g) ]
+1122 39:[ for k, v in pairs({ ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1130 40:[ "V", ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1134 41:[ "I", ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1138 42:[ "C", ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1142 43:[ "T", ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1146 44:[ "O", ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1150 45:[ "R", ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1154 46:[ "Y" ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1122 47:[ }) do ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1162 48:[ local n = am.scale(2) ^ am.translate((k - 3.5) * 20, 0) ^ am.text(v, color.fg) ] >> 45:[ n = am.scale(2)^am.translate((k-3.5)*20,0)^am.text(v,color.fg) ]
+1227 49:[ n:action(coroutine.create(function() ] >> 46:[ n\action(coroutine.create(() -> ]
+1267 50:[ while true do ] >> 47:[ while true ]
+1287 51:[ for k, v in pairs(mod.victory_text) do ] >> 48:[ for k,v in pairs(mod.victory_text) ]
+1317 52:[ v.hidden = not mod.victory ] >> 49:[ v.hidden = not mod.victory ]
+1277 53:[ end ] >> 48:[ for k,v in pairs(mod.victory_text) ]
+1348 54:[ coroutine.yield() ] >> 50:[ coroutine.yield! ]
+1262 55:[ end ] >> 47:[ while true ]
+1227 56:[ end)) ] >> 46:[ n\action(coroutine.create(() -> ]
+1372 57:[ n:action(am.series({ ] >> 52:[ n\action(am.series({ ]
+1396 58:[ am.delay(k * 0.25), ] >> 53:[ am.delay(k*0.25), ]
+1417 59:[ am.loop(function() ] >> 54:[ am.loop(() -> am.series({ ]
+1430 60:[ return am.series({ ] >> 54:[ am.loop(() -> am.series({ ]
+1447 61:[ am.tween(n("translate"), 1, { ] >> 55:[ am.tween(n("translate"),1,{y:30},am.ease.sine), ]
+1476 62:[ y = 30 ] >> 55:[ am.tween(n("translate"),1,{y:30},am.ease.sine), ]
+1447 63:[ }, am.ease.sine), ] >> 55:[ am.tween(n("translate"),1,{y:30},am.ease.sine), ]
+1499 64:[ am.tween(n("translate"), 1, { ] >> 56:[ am.tween(n("translate"),1,{y:0},am.ease.sine) ]
+1528 65:[ y = 0 ] >> 56:[ am.tween(n("translate"),1,{y:0},am.ease.sine) ]
+1499 66:[ }, am.ease.sine) ] >> 56:[ am.tween(n("translate"),1,{y:0},am.ease.sine) ]
+1430 67:[ }) ] >> 54:[ am.loop(() -> am.series({ ]
+1417 68:[ end) ] >> 54:[ am.loop(() -> am.series({ ]
+1372 69:[ })) ] >> 52:[ n\action(am.series({ ]
+1560 70:[ table.insert(mod.victory_text, n) ] >> 59:[ table.insert(mod.victory_text,n) ]
+1595 71:[ mod.victory_g:append(n) ] >> 60:[ mod.victory_g\append(n) ]
+1112 72:[ end ] >> 44:[ for k,v in pairs({"V","I","C","T","O","R","Y"}) ]
+1620 73:[ mod.ability_selector:append(ability_trans) ] >> 61:[ mod.ability_selector\append(ability_trans) ]
+1671 74:[ for i = 1, 4 do ] >> 62:[ for i = 1,4 ]
+1678 75:[ local trans_x = (-150 * (i - 2)) + 32 ] >> 63:[ trans_x = (-150 * (i-2))+32 ]
+1708 76:[ local trans_y = 200 ] >> 64:[ trans_y = 200 ]
+1803 77:[ local ability_slot_button = ui.create_any_button(ability_trans, 3, 3, trans_x, trans_y) ] >> 66:[ ability_slot_button = ui.create_any_button(ability_trans,3,3,trans_x,trans_y) ]
+1883 78:[ local lpd = main.world.localplayer.data ] >> 67:[ lpd = main.world.localplayer.data ]
+1919 79:[ print("lpd was:", lpd) ] >> 68:[ print("lpd was:",lpd) ]
+1943 80:[ local ability = lpd.abilities[i] or Pass ] >> 69:[ ability = lpd.abilities[i] or Pass ]
+1980 81:[ print("Ability was", ability.__name) ] >> 70:[ print("Ability was",ability.__name) ]
+2018 82:[ print("Sprite was", ability and ability.sprite) ] >> 71:[ print("Sprite was",ability and ability.sprite) ]
+2067 83:[ local ability_slot_icon = am.sprite(ability and ability.sprite or "data/no_action.png", color.white, "left", "top"):tag("icon") ] >> 72:[ ability_slot_icon = am.sprite(ability and ability.sprite or "data/no_action.png", color.white, "left","top")\tag("icon") ]
+2190 84:[ ability_slot_button.name = ability.__name ] >> 73:[ ability_slot_button.name = ability.__name ]
+2234 85:[ ability_slot_button.ability = ability ] >> 74:[ ability_slot_button.ability = ability ]
+2274 86:[ ability_slot_button.node:append(am.translate(trans_x + 16, trans_y - 16) ^ ability_slot_icon) ] >> 75:[ ability_slot_button.node\append(am.translate(trans_x + 16,trans_y - 16)^ ability_slot_icon) ]
+2368 87:[ table.insert(ability_buttons, ability_slot_button) ] >> 76:[ table.insert(ability_buttons, ability_slot_button) ]
+1664 88:[ end ] >> 62:[ for i = 1,4 ]
+2420 89:[ print("test") ] >> 77:[ print("test") ]
+2435 90:[ main.root("screen"):append(mod.time_pips) ] >> 78:[ main.root("screen")\append(mod.time_pips) ]
+2478 91:[ main.root("screen"):append(mod.ability_selector) ] >> 79:[ main.root("screen")\append(mod.ability_selector) ]
+2528 92:[ main.root("screen"):append(mod.action_phase) ] >> 80:[ main.root("screen")\append(mod.action_phase) ]
+2574 93:[ mod.time_pips:action(coroutine.create(function() ] >> 81:[ mod.time_pips\action(coroutine.create(() -> ]
+2620 94:[ print("Pips in action:", pip_sprites) ] >> 82:[ print("Pips in action:",pip_sprites) ]
+2664 95:[ while true do ] >> 83:[ while true ]
+2675 96:[ if not mod.victory then ] >> 84:[ if not mod.victory ]
+2696 97:[ local currtime = am.eval_js("new Date().getTime()") ] >> 85:[ currtime = am.eval_js("new Date().getTime()") ]
+2746 98:[ local elapsed_time = currtime - main.world.time_ref ] >> 86:[ elapsed_time = currtime - main.world.time_ref ]
+2796 99:[ local npips = math.min(math.floor(elapsed_time / ms_per_pip), main.pips) ] >> 87:[ npips = math.min(math.floor(elapsed_time/ms_per_pip),main.pips) ]
+2871 100:[ for i = 1, npips do ] >> 88:[ for i = 1,npips ]
+2885 101:[ pip_sprites[i].source = "data/pip_light.png" ] >> 89:[ pip_sprites[i].source = "data/pip_light.png" ]
+2864 102:[ end ] >> 88:[ for i = 1,npips ]
+2941 103:[ for i = npips + 2, main.pips do ] >> 90:[ for i = npips+2,main.pips ]
+2967 104:[ if i > 0 and i <= main.pips then ] >> 91:[ if i > 0 and i <= main.pips ]
+2999 105:[ pip_sprites[i].source = "data/pip_dark.png" ] >> 92:[ pip_sprites[i].source = "data/pip_dark.png" ]
+2965 106:[ end ] >> 91:[ if i > 0 and i <= main.pips ]
+2934 107:[ end ] >> 90:[ for i = npips+2,main.pips ]
+2673 108:[ end ] >> 84:[ if not mod.victory ]
+3046 109:[ coroutine.yield() ] >> 93:[ coroutine.yield! ]
+2659 110:[ end ] >> 83:[ while true ]
+2574 111:[ end)) ] >> 81:[ mod.time_pips\action(coroutine.create(() -> ]
+3068 112:[ local touch_indicator = am.group() ] >> 95:[ touch_indicator = am.group! ]
+3097 113:[ local touch_cursor = am.sprite("data/cursor.png", vec4(1, 1, 1, 1), "left", "top") ] >> 96:[ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") ]
+3169 114:[ local touch_loc = am.translate(0, 0) ^ touch_cursor ] >> 97:[ touch_loc = am.translate(0,0)^ touch_cursor ]
+3214 115:[ touch_indicator:append(touch_loc) ] >> 98:[ touch_indicator\append(touch_loc) ]
+3249 116:[ mod.ability_selector:append(touch_indicator) ] >> 99:[ mod.ability_selector\append(touch_indicator) ]
+3295 117:[ return mod.ability_selector:action(coroutine.create(function() ] >> 100:[ mod.ability_selector\action(coroutine.create(() -> ]
+3353 118:[ while true do ] >> 101:[ while true ]
+3364 119:[ if #main.win:active_touches() > 0 then ] >> 102:[ if #main.win\active_touches! > 0 ]
+3399 120:[ local touch = main.win:touch_position(1) ] >> 103:[ touch = main.win\touch_position(1) ]
+3438 121:[ touch_cursor.color = vec4(1, 1, 1, 1) ] >> 104:[ touch_cursor.color = vec4(1,1,1,1) ]
+3477 122:[ touch_loc.x = touch.x ] >> 105:[ touch_loc.x = touch.x ]
+3503 123:[ touch_loc.y = touch.y ] >> 106:[ touch_loc.y = touch.y ]
+3529 124:[ local col_but = bp.check(touch.x, touch.y + 96) ] >> 107:[ col_but = bp.check(touch.x, touch.y + 96) ]
+3577 125:[ if #col_but > 0 then ] >> 108:[ if #col_but > 0 ]
+3596 126:[ touch_cursor.color = vec4(0.5, 0.5, 0.5, 0.5) ] >> 109:[ touch_cursor.color = vec4(0.5,0.5,0.5,0.5) ]
+3575 127:[ end ] >> 108:[ if #col_but > 0 ]
+3645 128:[ if main.win:touch_began(1) then ] >> 110:[ if main.win\touch_began(1) ]
+3675 129:[ print("Checking ability buttons:", ability_buttons, "against col but:", col_but) ] >> 111:[ print("Checking ability buttons:",ability_buttons, "against col but:",col_but) ]
+3774 130:[ for _, button in ipairs(ability_buttons) do ] >> 112:[ for _,button in ipairs(ability_buttons) ]
+3807 131:[ if button == col_but[1] then ] >> 113:[ if button == col_but[1] ]
+3836 132:[ print("Found button") ] >> 114:[ print("Found button") ]
+3865 133:[ action.set_action(button.name) ] >> 115:[ action.set_action(button.name) ]
+3905 134:[ if mod.loaded_ability then ] >> 116:[ if mod.loaded_ability ]
+3933 135:[ mod.loaded_ability:unload() ] >> 117:[ mod.loaded_ability\unload! ]
+3903 136:[ end ] >> 116:[ if mod.loaded_ability ]
+3967 137:[ button.ability:load() ] >> 118:[ button.ability\load! ]
+3995 138:[ mod.loaded_ability = ability ] >> 119:[ mod.loaded_ability = ability ]
+3805 139:[ end ] >> 113:[ if button == col_but[1] ]
+3759 140:[ end ] >> 112:[ for _,button in ipairs(ability_buttons) ]
+3643 141:[ end ] >> 110:[ if main.win\touch_began(1) ]
+3362 142:[ end ] >> 102:[ if #main.win\active_touches! > 0 ]
+4027 143:[ coroutine.yield() ] >> 120:[ coroutine.yield! ]
+3348 144:[ end ] >> 101:[ while true ]
+3295 145:[ end)) ] >> 100:[ mod.ability_selector\action(coroutine.create(() -> ]
+644 146:[ end ] >> 32:[ mod.load = () -> ]
+4051 147:[ mod.unload = function() ] >> 124:[ mod.unload = () -> ]
+4071 148:[ main.root("screen"):remove(mod.time_pips) ] >> 125:[ main.root("screen")\remove(mod.time_pips) ]
+4114 149:[ main.root("screen"):remove(mod.ability_selector) ] >> 126:[ main.root("screen")\remove(mod.ability_selector) ]
+4164 150:[ return main.root("screen"):remove(mod.action_phase) ] >> 127:[ main.root("screen")\remove(mod.action_phase) ]
+4051 151:[ end ] >> 124:[ mod.unload = () -> ]
+4210 152:[ return mod ] >> 129:[ mod ]
diff --git a/debug/broadphase.lua.X b/debug/broadphase.lua.X
new file mode 100644
index 0000000..0bb6623
--- /dev/null
+++ b/debug/broadphase.lua.X
@@ -0,0 +1,39 @@
+Pos Lua >> Moon
+136 1:[ local mod = ... ] >> 5:[ mod = ... ]
+146 2:[ local util = require("util") ] >> 6:[ util = require "util" ]
+168 3:[ local bump = require("bump") ] >> 7:[ bump = require "bump" ]
+190 4:[ local main = require("main") ] >> 8:[ main = require "main" --for debugging ]
+244 6:[ Vec3 = util.Vec3 ] >> 9:[ import Vec3 from util ]
+251 7:[ mod.world = bump.newWorld(8) ] >> 11:[ mod.world = bump.newWorld(8) ]
+344 8:[ mod.add = function(ref, x, y, width, height) ] >> 13:[ mod.add = (ref,x,y,width,height) -> ]
+381 9:[ print("Making phys for ref:", ref) ] >> 14:[ print("Making phys for ref:",ref) ]
+578 10:[ local graphic_x = 0 ] >> 18:[ graphic_x = 0--x/4 ]
+598 11:[ local graphic_y = 0 ] >> 19:[ graphic_y = 0--y --+ (height / 2) ]
+666 12:[ local physx = x ] >> 21:[ physx = x ]
+677 13:[ local physy = y ] >> 22:[ physy = y ]
+775 14:[ local lx = x ] >> 25:[ lx = x --+ (width / 2) ]
+799 15:[ local ly = y ] >> 26:[ ly = y --+ (height / 2) ]
+1214 16:[ local positionnode = ref.node ] >> 36:[ positionnode = ref.node ]
+1591 17:[ mod.world:add(ref, physx, physy, width, height) ] >> 41:[ mod.world\add(ref,physx,physy,width,height) ]
+1687 18:[ print("physics node:", node) ] >> 43:[ print("physics node:",node) ]
+1716 19:[ return node ] >> 44:[ node ]
+344 20:[ end ] >> 13:[ mod.add = (ref,x,y,width,height) -> ]
+1722 21:[ mod.update = function(ref, x, y, newwidth, newheight) ] >> 46:[ mod.update = (ref,x,y,newwidth,newheight) -> ]
+1768 22:[ return mod.world:update(ref, x, y, newwidth, newheight) ] >> 47:[ mod.world\update(ref,x,y,newwidth,newheight) ]
+1722 23:[ end ] >> 46:[ mod.update = (ref,x,y,newwidth,newheight) -> ]
+1814 24:[ mod.move = function(ref, x, y, filter) ] >> 49:[ mod.move = (ref,x,y,filter) -> ]
+1846 25:[ return mod.world:move(ref, x, y, filter) ] >> 50:[ mod.world\move(ref,x,y,filter) ]
+1814 26:[ end ] >> 49:[ mod.move = (ref,x,y,filter) -> ]
+1878 27:[ mod.remove = function(ref) ] >> 52:[ mod.remove = (ref) -> ]
+1901 28:[ return mod.world:remove(ref) ] >> 53:[ mod.world\remove(ref) ]
+1878 29:[ end ] >> 52:[ mod.remove = (ref) -> ]
+1924 30:[ mod.check = function(x, y) ] >> 55:[ mod.check = (x,y) -> ]
+1946 31:[ return mod.world:queryPoint(x, y) ] >> 56:[ mod.world\queryPoint(x,y) ]
+1924 32:[ end ] >> 55:[ mod.check = (x,y) -> ]
+2028 43:[ self.offset = offset ] >> 61:[ @offset = offset ]
+2047 44:[ self.size = size ] >> 62:[ @size = size ]
+2062 45:[ self.extra = extra ] >> 63:[ @extra = extra ]
+2078 60:[ mod.gravity = 0.5 ] >> 65:[ mod.gravity = 0.5 ]
+2096 61:[ mod.term_fall_vel = 10 ] >> 66:[ mod.term_fall_vel = 10 ]
+2119 62:[ mod.PhysGroup = PhysGroup ] >> 67:[ mod.PhysGroup = PhysGroup ]
+2145 63:[ return mod ] >> 68:[ mod ]
diff --git a/debug/char.lua.X b/debug/char.lua.X
new file mode 100644
index 0000000..bc01108
--- /dev/null
+++ b/debug/char.lua.X
@@ -0,0 +1,266 @@
+Pos Lua >> Moon
+2 1:[ local util = require("util") ] >> 2:[ util = require "util" ]
+24 2:[ local broadphase = require("broadphase") ] >> 3:[ broadphase = require "broadphase" ]
+58 3:[ local main = require("main") ] >> 4:[ main = require "main" ]
+80 4:[ local constrain = require("constrain") ] >> 5:[ constrain = require "constrain" ]
+112 5:[ local color = require("color") ] >> 6:[ color = require "color" ]
+136 6:[ local room = require("room") ] >> 7:[ room = require "room" ]
+179 8:[ LobbyRoom = room.LobbyRoom ] >> 8:[ import LobbyRoom from room ]
+185 9:[ local ability_reg = require("ability_reg") ] >> 9:[ ability_reg = require "ability_reg" ]
+223 10:[ local mod = ... ] >> 12:[ mod = ... ]
+233 11:[ mod.characters = { } ] >> 13:[ mod.characters = {} ]
+253 12:[ mod.classes = { } ] >> 14:[ mod.classes = {} ]
+270 13:[ require("char_tank") ] >> 15:[ require "char_tank" ]
+290 14:[ require("char_mage") ] >> 16:[ require "char_mage" ]
+310 15:[ require("char_theif") ] >> 17:[ require "char_theif" ]
+331 16:[ require("char_fool") ] >> 18:[ require "char_fool" ]
+351 17:[ require("char_jugg") ] >> 19:[ require "char_jugg" ]
+371 18:[ mod.class_order = { ] >> 20:[ mod.class_order = { ]
+392 19:[ "Tumbler", ] >> 21:[ "Tumbler", ]
+404 20:[ "Fire Breather", ] >> 22:[ "Fire Breather", ]
+422 21:[ "Juggler", ] >> 23:[ "Juggler", ]
+434 22:[ "Troubador", ] >> 24:[ "Troubador", ]
+448 23:[ "Juggernaut" ] >> 25:[ "Juggernaut" ]
+371 24:[ } ] >> 20:[ mod.class_order = { ]
+463 25:[ mod.class_order_rev = { } ] >> 27:[ mod.class_order_rev = {} ]
+498 26:[ for k, v in ipairs(mod.class_order) do ] >> 28:[ for k,v in ipairs(mod.class_order) ]
+524 27:[ mod.class_order_rev[v] = k ] >> 29:[ mod.class_order_rev[v] = k ]
+488 28:[ end ] >> 28:[ for k,v in ipairs(mod.class_order) ]
+551 29:[ print("After requireing characters, mod.classes was", mod.classes) ] >> 30:[ print("After requireing characters, mod.classes was",mod.classes) ]
+617 30:[ mod.enemies = { } ] >> 31:[ mod.enemies = {} ]
+652 31:[ require("e_bethany") ] >> 33:[ require "e_bethany" ]
+672 32:[ require("e_ruminating_randy") ] >> 34:[ require "e_ruminating_randy" ]
+701 33:[ require("e_mopey_marvin") ] >> 35:[ require "e_mopey_marvin" ]
+726 34:[ require("e_sullen_salley") ] >> 36:[ require "e_sullen_salley" ]
+752 35:[ require("e_child") ] >> 37:[ require "e_child" ]
+770 36:[ print("After requireing rat, mod.enemies was", mod.enemies) ] >> 38:[ print("After requireing rat, mod.enemies was",mod.enemies) ]
+928 45:[ key = "<input>" ] >> 42:[ new:(key = "<input>") => ]
+945 47:[ self.time = 0 ] >> 43:[ @time = 0 ]
+957 48:[ self.key = key ] >> 44:[ @key = key ]
+970 49:[ self.value = false ] >> 45:[ @value = false ]
+1049 71:[ self.anim = anim ] >> 51:[ @anim = anim ]
+1064 72:[ self.interupt = interupt ] >> 52:[ @interupt = interupt ]
+1087 73:[ self.mode = mode ] >> 53:[ @mode = mode ]
+1133 96:[ action = "<input>" ] >> 56:[ new:(action = "<input>") => ]
+1150 98:[ self.time = 0 ] >> 57:[ @time = 0 ]
+1162 99:[ self.action = action ] >> 58:[ @action = action ]
+1180 114:[ mod.sprite_direction_co = function(self) ] >> 60:[ mod.sprite_direction_co = () => ]
+1213 115:[ return function() ] >> 61:[ return () -> ]
+1233 116:[ while true do ] >> 62:[ while true ]
+1242 117:[ self:stop_anim(self.sprites.right) ] >> 63:[ @stop_anim(@sprites.right) ]
+1272 118:[ self:stop_anim(self.sprites.left) ] >> 64:[ @stop_anim(@sprites.left) ]
+1301 119:[ self:stop_anim(self.sprites.stop_right) ] >> 65:[ @stop_anim(@sprites.stop_right) ]
+1336 120:[ self:stop_anim(self.sprites.stop_left) ] >> 66:[ @stop_anim(@sprites.stop_left) ]
+1370 121:[ self:stop_anim(self.sprites.falling_left) ] >> 67:[ @stop_anim(@sprites.falling_left) ]
+1407 122:[ self:stop_anim(self.sprites.falling_right) ] >> 68:[ @stop_anim(@sprites.falling_right) ]
+1459 123:[ local _list_0 = self.inputs ] >> 69:[ for input in *@inputs ]
+1473 126:[ if self.velocity.x > 0.01 then ] >> 70:[ if @velocity.x > 0.01 ]
+1498 127:[ self:set_anim(self.sprites.right, 1, "loop") ] >> 71:[ @set_anim(@sprites.right,1,"loop") ]
+1554 129:[ elseif self.velocity.x < -0.01 then ] >> 73:[ elseif @velocity.x < -0.01 ]
+1580 130:[ self:set_anim(self.sprites.left, 1, "loop") ] >> 74:[ @set_anim(@sprites.left,1,"loop") ]
+1628 134:[ coroutine.yield() ] >> 76:[ coroutine.yield! ]
+1228 135:[ end ] >> 62:[ while true ]
+1213 136:[ end ] >> 61:[ return () -> ]
+1180 137:[ end ] >> 60:[ mod.sprite_direction_co = () => ]
+1646 138:[ mod.can_die_co = function(self) ] >> 78:[ mod.can_die_co = () => ]
+1670 139:[ return function() ] >> 79:[ return () -> ]
+1690 140:[ while not self.dead do ] >> 80:[ while not @dead ]
+1704 141:[ coroutine.yield() ] >> 81:[ coroutine.yield! ]
+1685 142:[ end ] >> 80:[ while not @dead ]
+1734 143:[ for k, v in pairs(self.sprites) do ] >> 83:[ for k,v in pairs @sprites ]
+1755 144:[ if k ~= "idle" then ] >> 84:[ if k ~= "idle" ]
+1772 145:[ self:stop_anim(v) ] >> 85:[ @stop_anim(v) ]
+1753 146:[ end ] >> 84:[ if k ~= "idle" ]
+1724 147:[ end ] >> 83:[ for k,v in pairs @sprites ]
+1790 148:[ if self.sprites.die then ] >> 86:[ if @sprites.die ]
+1807 149:[ self:set_anim(self.sprites.die, 5) ] >> 87:[ @set_anim(@sprites.die,5) ]
+1788 150:[ end ] >> 86:[ if @sprites.die ]
+1835 151:[ return coroutine.yield(true) ] >> 88:[ coroutine.yield(true) ]
+1670 152:[ end ] >> 79:[ return () -> ]
+1646 153:[ end ] >> 78:[ mod.can_die_co = () => ]
+1858 154:[ mod.make_animate = function(c) ] >> 90:[ mod.make_animate = (c) -> ]
+1885 155:[ assertf(c.sprites ~= nil, "Tried to animate something that had no .sprites: %q", c.__class.__name) ] >> 91:[ assertf(c.sprites ~= nil, "Tried to animate something that had no .sprites: %q", c.__class.__name) ]
+1985 156:[ assertf(c.sprites.idle ~= nil and #c.sprites.idle > 0, "Tried to animate something without a .idle animation: %q", c.__class.__name) ] >> 92:[ assertf(c.sprites.idle ~= nil and #c.sprites.idle > 0, "Tried to animate something without a .idle animation: %q", c.__class.__name) ]
+2119 157:[ c.anim_stack = { ] >> 93:[ c.anim_stack = {AnimFrame(c.sprites.idle,0,"loop")} ]
+2135 158:[ AnimFrame(c.sprites.idle, 0, "loop") ] >> 93:[ c.anim_stack = {AnimFrame(c.sprites.idle,0,"loop")} ]
+2119 159:[ } ] >> 93:[ c.anim_stack = {AnimFrame(c.sprites.idle,0,"loop")} ]
+2172 160:[ c.anim = c.sprites.idle ] >> 94:[ c.anim = c.sprites.idle ]
+2197 161:[ c.keyframe = 0 ] >> 95:[ c.keyframe = 0 ]
+2213 162:[ c.animrate = c.animrate or 1 ] >> 96:[ c.animrate = c.animrate or 1 ]
+2243 163:[ c.anim_interupt = 0 ] >> 97:[ c.anim_interupt = 0 ]
+2264 164:[ c.set_anim = function(self, tbl, interupt, mode) ] >> 98:[ c.set_anim = (self,tbl,interupt,mode) -> ]
+2309 165:[ if type(tbl) ~= "table" then ] >> 99:[ if type(tbl) ~= "table" ]
+2334 166:[ error("Tried to set anim to something that was not a table!", 2) ] >> 100:[ error("Tried to set anim to something that was not a table!",2) ]
+2307 167:[ end ] >> 99:[ if type(tbl) ~= "table" ]
+2402 168:[ if #tbl == 0 then ] >> 101:[ if #tbl == 0 ]
+2416 169:[ error("Tried to set anim to an empty table", 2) ] >> 102:[ error("Tried to set anim to an empty table",2) ]
+2400 170:[ end ] >> 101:[ if #tbl == 0 ]
+2467 171:[ if interupt > self.anim_interupt then ] >> 103:[ if interupt > @anim_interupt ]
+2497 172:[ table.insert(self.anim_stack, AnimFrame(tbl, interupt, mode)) ] >> 104:[ table.insert(@anim_stack,AnimFrame(tbl,interupt,mode)) ]
+2555 173:[ self.anim = self.anim_stack[#self.anim_stack].anim ] >> 105:[ @anim = @anim_stack[#@anim_stack].anim ]
+2597 174:[ self.anim_interupt = interupt ] >> 106:[ @anim_interupt = interupt ]
+2465 175:[ end ] >> 103:[ if interupt > @anim_interupt ]
+2264 176:[ end ] >> 98:[ c.set_anim = (self,tbl,interupt,mode) -> ]
+2625 177:[ c.stop_anim = function(self, tbl, err_if_unable) ] >> 108:[ c.stop_anim = (self,tbl,err_if_unable=false) -> ]
+2663 179:[ err_if_unable = false ] >> 108:[ c.stop_anim = (self,tbl,err_if_unable=false) -> ]
+2675 181:[ local anim_found = false ] >> 109:[ anim_found = false ]
+2706 182:[ for k, v in pairs(self.anim_stack) do ] >> 110:[ for k,v in pairs @anim_stack ]
+2730 183:[ if v.anim == tbl then ] >> 111:[ if v.anim == tbl ]
+2785 184:[ anim_found = true ] >> 113:[ anim_found = true ]
+2807 185:[ table.remove(self.anim_stack, k) ] >> 114:[ table.remove(@anim_stack,k) ]
+2728 187:[ end ] >> 111:[ if v.anim == tbl ]
+2696 188:[ end ] >> 110:[ for k,v in pairs @anim_stack ]
+2849 189:[ if err_if_unable then ] >> 116:[ if err_if_unable ]
+2867 190:[ assertf(anim_found, "Could not find animation to remove") ] >> 117:[ assertf(anim_found, "Could not find animation to remove") ]
+2847 191:[ end ] >> 116:[ if err_if_unable ]
+2927 192:[ self.anim = self.anim_stack[#self.anim_stack].anim ] >> 118:[ @anim = @anim_stack[#@anim_stack].anim ]
+2968 193:[ self.anim_interupt = self.anim_stack[#self.anim_stack].interupt ] >> 119:[ @anim_interupt = @anim_stack[#@anim_stack].interupt ]
+2625 194:[ end ] >> 108:[ c.stop_anim = (self,tbl,err_if_unable=false) -> ]
+3022 195:[ return c.node:action(coroutine.create(function() ] >> 121:[ c.node\action(coroutine.create(() -> ]
+3066 196:[ while not c.dead do ] >> 122:[ while not c.dead ]
+3081 197:[ c.keyframe = math.floor(am.current_time() * c.animrate) % #c.anim ] >> 123:[ c.keyframe = math.floor(am.current_time()*c.animrate) % #c.anim ]
+3148 198:[ local spritename = c.anim[c.keyframe + 1] ] >> 124:[ spritename = c.anim[c.keyframe + 1] ]
+3187 199:[ assert(spritename, "Failed to find an appropriate image to draw.") ] >> 125:[ assert(spritename, "Failed to find an appropriate image to draw.") ]
+3291 200:[ c.node("sprite").source = spritename ] >> 127:[ c.node("sprite").source = spritename ]
+3331 201:[ coroutine.yield() ] >> 128:[ coroutine.yield! ]
+3061 202:[ end ] >> 122:[ while not c.dead ]
+3366 203:[ local keyframe_0 = am.current_time() ] >> 130:[ keyframe_0 = am.current_time() ]
+3404 204:[ while c.keyframe < #c.anim - 1 do ] >> 131:[ while c.keyframe < #c.anim - 1 ]
+3433 205:[ c.keyframe = math.floor((am.current_time() - keyframe_0) * c.animrate) ] >> 132:[ c.keyframe = math.floor((am.current_time! - keyframe_0)*c.animrate) ]
+3504 206:[ assert(c.anim[c.keyframe + 1], "Failed to find an appropriate image to draw.") ] >> 133:[ assert(c.anim[c.keyframe + 1], "Failed to find an appropriate image to draw.") ]
+3586 207:[ c.node("sprite").source = c.anim[c.keyframe + 1] ] >> 134:[ c.node("sprite").source = c.anim[c.keyframe + 1] ]
+3638 208:[ coroutine.yield() ] >> 135:[ coroutine.yield! ]
+3399 209:[ end ] >> 131:[ while c.keyframe < #c.anim - 1 ]
+3657 210:[ c:remove() ] >> 136:[ c\remove() ]
+3670 211:[ return coroutine.yield(true) ] >> 137:[ coroutine.yield(true) ]
+3022 212:[ end)) ] >> 121:[ c.node\action(coroutine.create(() -> ]
+1858 213:[ end ] >> 90:[ mod.make_animate = (c) -> ]
+3697 214:[ mod.inherited = { } ] >> 140:[ mod.inherited = {} ]
+3716 215:[ local hp_bar_width = 20 ] >> 141:[ hp_bar_width = 20 ]
+5899 221:[ return string.format("<%s, %s> at (%d)", self.__class.__name, (self.class and self.class.name or "no class"), (self.data and self.data.position or -1)) ] >> 187:[ return string.format( ]
+6264 224:[ for k, v in pairs(self.class) do ] >> 201:[ for k,v in pairs(@class) ]
+6284 225:[ if k:match("^default") then ] >> 202:[ if k\match("^default") ]
+6309 226:[ local field = k:match("^default_(.*)") ] >> 203:[ field = k\match("^default_(.*)") ]
+6348 227:[ if type(v) == "function" then ] >> 204:[ if type(v) == "function" ]
+6376 228:[ self.data[field] = v() ] >> 205:[ @data[field] = v! ]
+6408 230:[ self.data[field] = v ] >> 207:[ @data[field] = v ]
+6282 232:[ end ] >> 202:[ if k\match("^default") ]
+6254 233:[ end ] >> 201:[ for k,v in pairs(@class) ]
+6457 236:[ assert(self.data[name], "Field must exist to be set") ] >> 210:[ assert(@data[name], "Field must exist to be set") ]
+6509 237:[ self.data[name] = value ] >> 211:[ @data[name] = value ]
+6531 238:[ print("my data table is:", self.data) ] >> 212:[ print("my data table is:",@data) ]
+6568 239:[ if name == "hp" then ] >> 213:[ if name == "hp" ]
+6585 240:[ local perc = (self.data.hp / self.data.maxhp) * 20 ] >> 214:[ perc = (@data.hp / @data.maxhp) * 20 ]
+6625 241:[ self.node("hp_fill_scale").x = perc ] >> 215:[ @.node("hp_fill_scale").x = perc ]
+6566 242:[ end ] >> 213:[ if name == "hp" ]
+6679 245:[ print("Serializing char:", self) ] >> 218:[ print("Serializing char:",@) ]
+6710 246:[ print("Name:", self.__class.__name) ] >> 219:[ print("Name:", @@__name) ]
+6737 247:[ print("uname:", self.uname) ] >> 220:[ print("uname:",@uname) ]
+6762 248:[ print("data:", self.data) ] >> 221:[ print("data:",@data) ]
+6785 249:[ local data_abilities = { } ] >> 222:[ data_abilities = {} ]
+6823 250:[ for i, ability in pairs(self.data.abilities) do ] >> 223:[ for i,ability in pairs(@data.abilities) ]
+6850 251:[ data_abilities[i] = ability.__name ] >> 224:[ data_abilities[i] = ability.__name ]
+6807 252:[ end ] >> 223:[ for i,ability in pairs(@data.abilities) ]
+6887 253:[ local data_copy = table.shallow_copy(self.data) ] >> 225:[ data_copy = table.shallow_copy(@data) ]
+6927 254:[ data_copy.abilities = data_abilities ] >> 226:[ data_copy.abilities = data_abilities ]
+6966 255:[ print("class.name:", self.class.name) ] >> 227:[ print("class.name:",@class.name) ]
+7001 256:[ local ret = am.to_json({ ] >> 228:[ ret = am.to_json({name:@@__name, uname:@uname, data:data_copy, class:@class.name}) ]
+7024 257:[ name = self.__class.__name, ] >> 228:[ ret = am.to_json({name:@@__name, uname:@uname, data:data_copy, class:@class.name}) ]
+7040 258:[ uname = self.uname, ] >> 228:[ ret = am.to_json({name:@@__name, uname:@uname, data:data_copy, class:@class.name}) ]
+7053 259:[ data = data_copy, ] >> 228:[ ret = am.to_json({name:@@__name, uname:@uname, data:data_copy, class:@class.name}) ]
+7070 260:[ class = self.class.name ] >> 228:[ ret = am.to_json({name:@@__name, uname:@uname, data:data_copy, class:@class.name}) ]
+7001 261:[ }) ] >> 228:[ ret = am.to_json({name:@@__name, uname:@uname, data:data_copy, class:@class.name}) ]
+7086 262:[ print("Ret is:", ret) ] >> 229:[ print("Ret is:",ret) ]
+7109 263:[ return ret ] >> 230:[ ret ]
+7141 266:[ print("Deserializing character") ] >> 233:[ print("Deserializing character") ]
+7176 267:[ local tbl = am.parse_json(data) ] >> 234:[ tbl = am.parse_json(data) ]
+7206 268:[ if mod.classes[tbl.class] then ] >> 235:[ if mod.classes[tbl.class] ]
+7233 269:[ local data_abilities = { } ] >> 236:[ data_abilities = {} ]
+7278 270:[ for i, ability_name in pairs(tbl.data.abilities) do ] >> 237:[ for i, ability_name in pairs(tbl.data.abilities) ]
+7309 271:[ data_abilities[i] = ability_reg[ability_name] ] >> 238:[ data_abilities[i] = ability_reg[ability_name] ]
+7256 272:[ end ] >> 237:[ for i, ability_name in pairs(tbl.data.abilities) ]
+7358 273:[ tbl.data.abilities = data_abilities ] >> 239:[ tbl.data.abilities = data_abilities ]
+7397 274:[ return mod.inherited[tbl.name](tbl.uname, tbl.data, mod.classes[tbl.class]) ] >> 240:[ return mod.inherited[tbl.name](tbl.uname, tbl.data, mod.classes[tbl.class]) ]
+7481 275:[ elseif mod.enemies[tbl.class] then ] >> 241:[ elseif mod.enemies[tbl.class] ]
+7508 276:[ local e = mod.Enemy(tbl.data, mod.enemies[tbl.class]) ] >> 242:[ e = mod.Enemy(tbl.data, mod.enemies[tbl.class]) ]
+7559 277:[ e.uname = tbl.uname ] >> 243:[ e.uname = tbl.uname ]
+7582 278:[ return e ] >> 244:[ return e ]
+7616 282:[ return print("draw") ] >> 248:[ print("draw") ]
+7647 285:[ return self.node:remove_all() ] >> 250:[ @node\remove_all! ]
+7679 288:[ self.dead = true ] >> 252:[ @dead = true ]
+7694 289:[ print(self, "is dieing, node is", self.node, "and color is", color) ] >> 253:[ print(@,"is dieing, node is",@.node,"and color is",color) ]
+7754 290:[ self.node("char_sprite"):append(am.line(vec2(-10, -10), vec2(10, 10), 5, color.bright)) ] >> 254:[ @.node("char_sprite")\append(am.line(vec2(-10,-10),vec2(10,10),5,color.bright)) ]
+7836 291:[ return self.node("char_sprite"):append(am.line(vec2(10, -10), vec2(-10, 10), 5, color.bright)) ] >> 255:[ @.node("char_sprite")\append(am.line(vec2(10,-10),vec2(-10,10),5,color.bright)) ]
+7941 294:[ self.room = room ] >> 257:[ @room = room ]
+7956 295:[ print("Character", self, "entered room", room) ] >> 258:[ print("Character",@,"entered room",room) ]
+7999 296:[ self.node("char_translate").y = room.floor_y ] >> 259:[ @.node("char_translate").y = room.floor_y ]
+8045 297:[ if room.__class == LobbyRoom then ] >> 260:[ if room.__class == LobbyRoom ]
+8075 298:[ print("Class was lobbyRoom") ] >> 261:[ print("Class was lobbyRoom") ]
+8107 299:[ local rng_x = math.random(-(main.width / 2), (main.width / 2)) ] >> 262:[ rng_x = math.random(-(main.width/2), (main.width/2)) ]
+8163 300:[ print("RNG x:", rng_x) ] >> 263:[ print("RNG x:",rng_x) ]
+8188 301:[ self.node("char_translate").x = rng_x ] >> 264:[ @.node("char_translate").x = rng_x ]
+8233 303:[ print("Class was not LobbyRoom") ] >> 266:[ print("Class was not LobbyRoom") ]
+8269 304:[ local x_pos = self.room:player_location_of(self.data.position) ] >> 267:[ x_pos = @room\player_location_of(@data.position) ]
+8321 305:[ print("Got x pos for", self, x_pos) ] >> 268:[ print("Got x pos for",@,x_pos) ]
+8355 306:[ self.node("char_translate").x = x_pos + math.random(-15, 15) ] >> 269:[ @.node("char_translate").x = x_pos + math.random(-15,15) --some random variance to stop stacking ]
+8480 310:[ assert(newclass, "Cannot set a class to nil") ] >> 271:[ assert(newclass, "Cannot set a class to nil") ]
+8528 311:[ self.class = newclass ] >> 272:[ @class = newclass ]
+8548 312:[ self:calc_class_values() ] >> 273:[ @calc_class_values! ]
+8570 313:[ self.node("char_sprite").source = newclass.sprite ] >> 274:[ @.node("char_sprite").source = newclass.sprite ]
+8645 316:[ self.data.position = pos ] >> 277:[ @data.position = pos ]
+8694 319:[ self.data.location = loc ] >> 280:[ @data.location = loc ]
+3916 325:[ assert(charclass, "Charclass may not be nil") ] >> 147:[ assert(charclass, "Charclass may not be nil") ]
+3964 326:[ self.uname = uname or false ] >> 148:[ @uname = uname or false ]
+3990 327:[ self.data = data or { ] >> 149:[ @data = data or { ]
+4018 328:[ status = "active", ] >> 150:[ status: "active", ]
+4041 329:[ location = -1, ] >> 151:[ location: -1, ]
+4058 330:[ position = charclass.default_position ] >> 152:[ position: charclass.default_position ]
+3990 331:[ } ] >> 149:[ @data = data or { ]
+4092 332:[ self.class = charclass ] >> 154:[ @class = charclass ]
+4113 333:[ assert(self.class.name, "Character classes must have a name") ] >> 155:[ assert(@class.name, "Character classes must have a name") ]
+4173 334:[ self:calc_class_values() ] >> 156:[ @calc_class_values! ]
+4195 335:[ self.node = am.group() ] >> 157:[ @node = am.group! ]
+4270 336:[ self.node:append(am.translate(0, 0):tag("char_translate") ^ am.sprite(self.class.sprite, color.white, "center", "bottom"):tag("char_sprite")) ] >> 160:[ @node\append(am.translate(0,0)\tag("char_translate")^ am.sprite(@class.sprite,color.white,"center","bottom")\tag("char_sprite")) ]
+4401 337:[ print("A character has been created!") ] >> 161:[ print("A character has been created!") ]
+4442 338:[ print(debug.traceback()) ] >> 162:[ print(debug.traceback!) ]
+4487 339:[ local healthbar_trans = am.translate(0, 60) ] >> 164:[ healthbar_trans = am.translate(0,60) ]
+4526 340:[ local healthbar_left = am.sprite("data/bar_left.png", color.white, "left", "center"):tag("hp_l") ] >> 165:[ healthbar_left = am.sprite("data/bar_left.png", color.white,"left","center")\tag("hp_l") ]
+4617 341:[ local healthbar_right = am.sprite("data/bar_right.png", color.white, "right", "center"):tag("hp_r") ] >> 166:[ healthbar_right = am.sprite("data/bar_right.png", color.white, "right","center")\tag("hp_r") ]
+4712 342:[ local healthbar_bar = am.sprite("data/bar_mid.png", color.white, "left", "center"):tag("hp_b") ] >> 167:[ healthbar_bar = am.sprite("data/bar_mid.png", color.white,"left","center")\tag("hp_b") ]
+4801 343:[ local healthbar_fill = am.sprite("data/bar_fill.png", color.white, "left", "center"):tag("hp_f") ] >> 168:[ healthbar_fill = am.sprite("data/bar_fill.png", color.white,"left","center")\tag("hp_f") ]
+4892 344:[ local healthbar_scale = am.scale(hp_bar_width, 1) ] >> 169:[ healthbar_scale = am.scale(hp_bar_width,1) ]
+4937 345:[ local healthbar_fill_scale = am.scale(hp_bar_width + 1, 1):tag("hp_fill_scale") ] >> 170:[ healthbar_fill_scale = am.scale(hp_bar_width + 1,1)\tag("hp_fill_scale") ]
+5012 346:[ healthbar_trans:append(am.translate(-hp_bar_width, 0) ^ healthbar_left) ] >> 171:[ healthbar_trans\append(am.translate(-hp_bar_width,0)^ healthbar_left) ]
+5084 347:[ healthbar_trans:append(am.translate(hp_bar_width, 0) ^ healthbar_right) ] >> 172:[ healthbar_trans\append(am.translate(hp_bar_width,0)^ healthbar_right) ]
+5156 348:[ healthbar_trans:append(am.translate(-hp_bar_width / 2, 0) ^ healthbar_scale ^ healthbar_bar) ] >> 173:[ healthbar_trans\append(am.translate(-hp_bar_width/2,0)^ healthbar_scale^ healthbar_bar) ]
+5246 349:[ healthbar_trans:append(am.translate((-hp_bar_width / 2) - 1, 0) ^ healthbar_fill_scale ^ healthbar_fill) ] >> 174:[ healthbar_trans\append(am.translate((-hp_bar_width/2) - 1,0)^ healthbar_fill_scale^ healthbar_fill) ]
+5348 350:[ self.node("char_sprite"):append(healthbar_trans) ] >> 175:[ @.node("char_sprite")\append(healthbar_trans) ]
+5419 351:[ assert(self.__class.draw, "Characters must have a draw() method") ] >> 177:[ assert(@.__class.draw,"Characters must have a draw() method") ]
+5483 352:[ table.insert(mod.characters, self) ] >> 178:[ table.insert(mod.characters,@) ]
+5516 353:[ constrain(self, "set anim", function(self, value) ] >> 179:[ constrain(@,"set anim", (self,value) -> ]
+5559 354:[ assertf(type(value) == "table", "Tried to set anim on %q to something other than a table (%q)", self, type(value)) ] >> 180:[ assertf(type(value) == "table", "Tried to set anim on %q to something other than a table (%q)",@, type(value)) ]
+5673 355:[ return assert(#value > 0, "Tried to set animation for char to something with 0 frames!") ] >> 181:[ assert(#value > 0, "Tried to set animation for char to something with 0 frames!") ]
+5516 356:[ end) ] >> 179:[ constrain(@,"set anim", (self,value) -> ]
+5761 357:[ return assert(self.__class ~= Character, "Character class must be subclassed") ] >> 183:[ assert(@.__class != Character,"Character class must be subclassed") ]
+3809 371:[ self.classes = { } ] >> 144:[ @classes = {} ]
+3824 372:[ self.players = { } ] >> 145:[ @players = {} -- players singleton, [peerid] = Character ]
+6074 374:[ assert(c, "Inheritance must exist") ] >> 195:[ assert(c, "Inheritance must exist") ]
+6112 375:[ assert(c.__name, "Inherited class must have a .__name") ] >> 196:[ assert(c.__name, "Inherited class must have a .__name") ]
+6170 376:[ self.__class.classes[c.__name] = c ] >> 197:[ @@.classes[c.__name] = c ]
+6197 377:[ mod.inherited[c.__name] = c ] >> 198:[ mod.inherited[c.__name] = c ]
+8716 381:[ mod.enemy_counter = 0 ] >> 282:[ mod.enemy_counter = 0 ]
+8757 385:[ local _parent_0 = Character ] >> 283:[ class Enemy extends Character ]
+8927 388:[ self(data, eclass) ] >> 288:[ @(data, eclass) ]
+8946 389:[ mod.enemy_counter = mod.enemy_counter + 1 ] >> 290:[ mod.enemy_counter += 1 ]
+8990 392:[ return string.format("<%s> at (%d)", self.uname, self.data.position or 0) ] >> 292:[ return string.format("<%s> at (%d)",@uname, @data.position or 0) ]
+9127 395:[ print("Character", self, "entered room", room) ] >> 295:[ print("Character",@,"entered room",room) ]
+9170 396:[ self.room = room ] >> 296:[ @room = room ]
+9185 397:[ self.node("char_translate").y = self.room.floor_y ] >> 297:[ @.node("char_translate").y = @room.floor_y ]
+9230 398:[ self.node("char_translate").x = self.room:enemy_location_of(self.data.position) ] >> 298:[ @.node("char_translate").x = @room\enemy_location_of(@data.position) ]
+9325 401:[ return self.class.select_action(self) ] >> 301:[ return @class.select_action(@) ]
+8794 408:[ _class_0.__parent.__init(self, eclass.name .. ":" .. tostring(mod.enemy_counter), data, eclass) ] >> 285:[ super(eclass.name .. ":" .. tostring(mod.enemy_counter), data, eclass) ]
+8867 409:[ mod.enemy_counter = mod.enemy_counter + 1 ] >> 286:[ mod.enemy_counter += 1 ]
+9357 438:[ mod["Character"] = Character ] >> 303:[ mod["Character"] = Character ]
+9386 439:[ mod["KeyInput"] = KeyInput ] >> 304:[ mod["KeyInput"] = KeyInput ]
+9413 440:[ mod["Enemy"] = Enemy ] >> 305:[ mod["Enemy"] = Enemy ]
+9475 441:[ return mod ] >> 307:[ mod ]
diff --git a/debug/char_fool.lua.X b/debug/char_fool.lua.X
new file mode 100644
index 0000000..9cbf0ca
--- /dev/null
+++ b/debug/char_fool.lua.X
@@ -0,0 +1,22 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+51 3:[ require("a_firebreath") ] >> 3:[ require "a_firebreath" ]
+75 4:[ local mod = ... ] >> 5:[ mod = ... ]
+86 5:[ mod.char = { ] >> 7:[ mod.char = { ]
+105 6:[ name = "Fire Breather", ] >> 8:[ name: "Fire Breather", ]
+141 7:[ default_position = 3, ] >> 9:[ default_position: 3, ]
+166 9:[ reg.FireBreath ] >> 10:[ default_abilities: {reg.FireBreath}, ]
+198 11:[ default_maxhp = 3, ] >> 11:[ default_maxhp: 3, ]
+214 12:[ default_hp = 3, ] >> 12:[ default_hp: 3, ]
+238 13:[ default_maxstamina = 3, ] >> 13:[ default_maxstamina: 3 ]
+258 14:[ default_stamina = 3, ] >> 14:[ default_stamina: 3 ]
+278 15:[ default_maxmana = 3, ] >> 15:[ default_maxmana: 3 ]
+295 16:[ default_mana = 3, ] >> 16:[ default_mana: 3 ]
+306 17:[ sprite = "data/character_4.png" ] >> 17:[ sprite:"data/character_4.png" ]
+86 18:[ } ] >> 7:[ mod.char = { ]
+341 19:[ for k, v in pairs(mod.char) do ] >> 19:[ for k,v in pairs(mod.char) ]
+359 20:[ mod[k] = v ] >> 20:[ mod[k] = v ]
+331 21:[ end ] >> 19:[ for k,v in pairs(mod.char) ]
+371 22:[ char.classes[mod.char.name] = mod.char ] >> 22:[ char.classes[mod.char.name] = mod.char ]
+411 23:[ return mod ] >> 24:[ mod ]
diff --git a/debug/char_jugg.lua.X b/debug/char_jugg.lua.X
new file mode 100644
index 0000000..edff74e
--- /dev/null
+++ b/debug/char_jugg.lua.X
@@ -0,0 +1,25 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+51 3:[ require("a_test") ] >> 3:[ require "a_test" ]
+69 4:[ local mod = ... ] >> 5:[ mod = ... ]
+80 5:[ mod.char = { ] >> 7:[ mod.char = { ]
+99 6:[ name = "Juggernaut", ] >> 8:[ name: "Juggernaut", ]
+132 7:[ default_position = 1, ] >> 9:[ default_position:1, ]
+159 9:[ reg.Everything ] >> 11:[ reg.Everything ]
+193 11:[ default_maxhp = 100, ] >> 13:[ default_maxhp: 100, ]
+211 12:[ default_hp = 100, ] >> 14:[ default_hp: 100, ]
+237 13:[ default_maxstamina = 3, ] >> 15:[ default_maxstamina: 3, ]
+258 14:[ default_stamina = 3, ] >> 16:[ default_stamina: 3, ]
+279 15:[ default_maxmana = 1, ] >> 17:[ default_maxmana: 1, ]
+297 16:[ default_mana = 1, ] >> 18:[ default_mana: 1, ]
+309 17:[ sprite = "data/character_1.png" ] >> 19:[ sprite:"data/character_1.png" ]
+80 18:[ } ] >> 7:[ mod.char = { ]
+345 19:[ for k, v in pairs(mod.char) do ] >> 22:[ for k,v in pairs(mod.char) ]
+363 20:[ mod[k] = v ] >> 23:[ mod[k] = v ]
+335 21:[ end ] >> 22:[ for k,v in pairs(mod.char) ]
+375 22:[ print("reg:", reg) ] >> 25:[ print("reg:",reg) ]
+393 23:[ assert(mod.char.default_abilities[1], "Tumble not found in reg") ] >> 26:[ assert(mod.char.default_abilities[1], "Tumble not found in reg") ]
+458 24:[ char.classes[mod.char.name] = mod.char ] >> 27:[ char.classes[mod.char.name] = mod.char ]
+497 25:[ print("After adding Tumbler, char.classes is", char.classes) ] >> 28:[ print("After adding Tumbler, char.classes is", char.classes) ]
+559 26:[ return mod ] >> 30:[ mod ]
diff --git a/debug/char_mage.lua.X b/debug/char_mage.lua.X
new file mode 100644
index 0000000..48bceec
--- /dev/null
+++ b/debug/char_mage.lua.X
@@ -0,0 +1,26 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+51 3:[ require("a_strum") ] >> 3:[ require "a_strum" ]
+69 4:[ require("a_dance") ] >> 4:[ require "a_dance" ]
+87 5:[ require("a_drum") ] >> 5:[ require "a_drum" ]
+105 6:[ local mod = ... ] >> 7:[ mod = ... ]
+116 7:[ mod.char = { ] >> 9:[ mod.char = { ]
+135 8:[ name = "Troubador", ] >> 10:[ name: "Troubador", ]
+167 9:[ default_position = 3, ] >> 11:[ default_position:3, ]
+191 11:[ reg.Strum, ] >> 12:[ default_abilities: {reg.Strum, reg.Dance, reg.Drum}, ]
+201 12:[ reg.Dance, ] >> 12:[ default_abilities: {reg.Strum, reg.Dance, reg.Drum}, ]
+212 13:[ reg.Drum ] >> 12:[ default_abilities: {reg.Strum, reg.Dance, reg.Drum}, ]
+239 15:[ default_maxhp = 2, ] >> 13:[ default_maxhp: 2, ]
+255 16:[ default_hp = 2, ] >> 14:[ default_hp: 2, ]
+279 17:[ default_maxstamina = 1, ] >> 15:[ default_maxstamina: 1 ]
+299 18:[ default_stamina = 1, ] >> 16:[ default_stamina: 1 ]
+319 19:[ default_maxmana = 5, ] >> 17:[ default_maxmana: 5 ]
+336 20:[ default_mana = 5, ] >> 18:[ default_mana: 5 ]
+347 21:[ sprite = "data/character_3.png" ] >> 19:[ sprite:"data/character_3.png" ]
+116 22:[ } ] >> 9:[ mod.char = { ]
+382 23:[ for k, v in pairs(mod.char) do ] >> 21:[ for k,v in pairs(mod.char) ]
+400 24:[ mod[k] = v ] >> 22:[ mod[k] = v ]
+372 25:[ end ] >> 21:[ for k,v in pairs(mod.char) ]
+412 26:[ char.classes[mod.char.name] = mod.char ] >> 24:[ char.classes[mod.char.name] = mod.char ]
+452 27:[ return mod ] >> 26:[ mod ]
diff --git a/debug/char_tank.lua.X b/debug/char_tank.lua.X
new file mode 100644
index 0000000..4d016df
--- /dev/null
+++ b/debug/char_tank.lua.X
@@ -0,0 +1,29 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+51 3:[ require("a_tumble") ] >> 3:[ require "a_tumble" ]
+70 4:[ require("a_highjump") ] >> 4:[ require "a_highjump" ]
+91 5:[ require("a_physique") ] >> 5:[ require "a_physique" ]
+113 6:[ local mod = ... ] >> 7:[ mod = ... ]
+124 7:[ mod.char = { ] >> 9:[ mod.char = { ]
+143 8:[ name = "Tumbler", ] >> 10:[ name: "Tumbler", ]
+173 9:[ default_position = 1, ] >> 11:[ default_position:1, ]
+200 11:[ reg.Tumble, ] >> 13:[ reg.Tumble, ]
+214 12:[ reg.HighJump, ] >> 14:[ reg.HighJump, ]
+230 13:[ reg.Physique ] >> 15:[ reg.Physique ]
+262 15:[ default_maxhp = 5, ] >> 17:[ default_maxhp: 5, ]
+278 16:[ default_hp = 5, ] >> 18:[ default_hp: 5, ]
+302 17:[ default_maxstamina = 3, ] >> 19:[ default_maxstamina: 3, ]
+323 18:[ default_stamina = 3, ] >> 20:[ default_stamina: 3, ]
+344 19:[ default_maxmana = 1, ] >> 21:[ default_maxmana: 1, ]
+362 20:[ default_mana = 1, ] >> 22:[ default_mana: 1, ]
+374 21:[ sprite = "data/character_1.png" ] >> 23:[ sprite:"data/character_1.png" ]
+124 22:[ } ] >> 9:[ mod.char = { ]
+410 23:[ for k, v in pairs(mod.char) do ] >> 26:[ for k,v in pairs(mod.char) ]
+428 24:[ mod[k] = v ] >> 27:[ mod[k] = v ]
+400 25:[ end ] >> 26:[ for k,v in pairs(mod.char) ]
+440 26:[ print("reg:", reg) ] >> 29:[ print("reg:",reg) ]
+458 27:[ assert(mod.char.default_abilities[1], "Tumble not found in reg") ] >> 30:[ assert(mod.char.default_abilities[1], "Tumble not found in reg") ]
+523 28:[ char.classes[mod.char.name] = mod.char ] >> 31:[ char.classes[mod.char.name] = mod.char ]
+562 29:[ print("After adding Tumbler, char.classes is", char.classes) ] >> 32:[ print("After adding Tumbler, char.classes is", char.classes) ]
+624 30:[ return mod ] >> 34:[ mod ]
diff --git a/debug/char_theif.lua.X b/debug/char_theif.lua.X
new file mode 100644
index 0000000..8354a83
--- /dev/null
+++ b/debug/char_theif.lua.X
@@ -0,0 +1,24 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+51 3:[ require("a_knifeslip") ] >> 3:[ require "a_knifeslip" ]
+73 4:[ require("a_hackysacks") ] >> 4:[ require "a_hackysacks" ]
+97 5:[ local mod = ... ] >> 6:[ mod = ... ]
+108 6:[ mod.char = { ] >> 8:[ mod.char = { ]
+127 7:[ name = "Juggler", ] >> 9:[ name: "Juggler", ]
+157 8:[ default_position = 2, ] >> 10:[ default_position: 2, ]
+182 10:[ reg.KnifeSlip, ] >> 11:[ default_abilities: {reg.KnifeSlip, reg.Juggle}, ]
+196 11:[ reg.Juggle ] >> 11:[ default_abilities: {reg.KnifeSlip, reg.Juggle}, ]
+225 13:[ default_maxhp = 3, ] >> 12:[ default_maxhp: 3, ]
+241 14:[ default_hp = 3, ] >> 13:[ default_hp: 3, ]
+265 15:[ default_maxstamina = 5, ] >> 14:[ default_maxstamina: 5 ]
+285 16:[ default_stamina = 5, ] >> 15:[ default_stamina: 5 ]
+305 17:[ default_maxmana = 1, ] >> 16:[ default_maxmana: 1 ]
+322 18:[ default_mana = 1, ] >> 17:[ default_mana: 1 ]
+333 19:[ sprite = "data/character_2.png" ] >> 18:[ sprite:"data/character_2.png" ]
+108 20:[ } ] >> 8:[ mod.char = { ]
+368 21:[ for k, v in pairs(mod.char) do ] >> 20:[ for k,v in pairs(mod.char) ]
+386 22:[ mod[k] = v ] >> 21:[ mod[k] = v ]
+358 23:[ end ] >> 20:[ for k,v in pairs(mod.char) ]
+398 24:[ char.classes[mod.char.name] = mod.char ] >> 23:[ char.classes[mod.char.name] = mod.char ]
+438 25:[ return mod ] >> 25:[ mod ]
diff --git a/debug/color.lua.X b/debug/color.lua.X
new file mode 100644
index 0000000..f5b6be1
--- /dev/null
+++ b/debug/color.lua.X
@@ -0,0 +1,11 @@
+Pos Lua >> Moon
+1 1:[ local mod = ... ] >> 1:[ mod = ... ]
+12 2:[ mod.fg = vec4(0.983, 0.961, 0.937, 1) ] >> 3:[ mod.fg = vec4(0.983,0.961,0.937,1) ]
+47 3:[ mod.bg = vec4(0.153, 0.153, 0.267, 1) ] >> 4:[ mod.bg = vec4(0.153,0.153,0.267,1) ]
+82 4:[ mod.highlight = vec4(0.949, 0.827, 0.671, 1) ] >> 5:[ mod.highlight = vec4(0.949,0.827,0.671,1) ]
+124 5:[ mod.shadow = vec4(0.286, 0.302, 0.494, 1) ] >> 6:[ mod.shadow = vec4(0.286,0.302,0.494,1) ]
+163 6:[ mod.bright = vec4(0.776, 0.624, 0.647, 1) ] >> 7:[ mod.bright = vec4(0.776,0.624,0.647,1) ]
+202 7:[ mod.dark = vec4(0.545, 0.427, 0.612, 1) ] >> 8:[ mod.dark = vec4(0.545,0.427,0.612,1) ]
+240 8:[ mod.transparent = vec4(0, 0, 0, 0) ] >> 10:[ mod.transparent = vec4(0,0,0,0) ]
+272 9:[ mod.white = vec4(1, 1, 1, 1) ] >> 11:[ mod.white = vec4(1,1,1,1) ]
+299 10:[ return mod ] >> 13:[ mod ]
diff --git a/debug/create_party_menu.lua.X b/debug/create_party_menu.lua.X
new file mode 100644
index 0000000..a18039f
--- /dev/null
+++ b/debug/create_party_menu.lua.X
@@ -0,0 +1,114 @@
+Pos Lua >> Moon
+1 1:[ local main = require("main") ] >> 1:[ main = require "main" ]
+23 2:[ local world = require("world") ] >> 2:[ world = require "world" ]
+47 3:[ local color = require("color") ] >> 3:[ color = require "color" ]
+71 4:[ local ui = require("ui") ] >> 4:[ ui = require "ui" ]
+89 5:[ local bp = require("broadphase") ] >> 5:[ bp = require "broadphase" ]
+115 6:[ local action = require("action") ] >> 6:[ action = require "action" ]
+141 7:[ local char = require("char") ] >> 7:[ char = require "char" ]
+163 8:[ local player = require("player") ] >> 8:[ player = require "player" ]
+189 9:[ local room = require("room") ] >> 9:[ room = require "room" ]
+228 11:[ World = world.World ] >> 10:[ import World from world ]
+253 13:[ Server = world.Server ] >> 11:[ import Server from world ]
+283 15:[ LocalPlayer = player.LocalPlayer ] >> 12:[ import LocalPlayer from player ]
+313 17:[ LobbyRoom = room.LobbyRoom ] >> 13:[ import LobbyRoom from room ]
+320 18:[ local mod = ... ] >> 15:[ mod = ... ]
+331 19:[ mod.node = am.group() ] >> 17:[ mod.node = am.group! ]
+352 20:[ mod.node:append(am.translate(0, main.height / 2) ^ am.text("Creating lobby...", color.fg, "center", "top"):tag("join_id")) ] >> 18:[ mod.node\append(am.translate(0,main.height/2)^ am.text("Creating lobby...", color.fg, "center","top")\tag("join_id")) ]
+470 21:[ mod.loaded = false ] >> 19:[ mod.loaded = false ]
+489 22:[ mod.load = function() ] >> 20:[ mod.load = () -> ]
+507 23:[ mod.loaded = true ] >> 21:[ mod.loaded = true ]
+526 24:[ print("Loading create party") ] >> 22:[ print("Loading create party") ]
+557 25:[ main.root("screen"):append(mod.node) ] >> 23:[ main.root("screen")\append(mod.node) ]
+595 26:[ print("About to create server") ] >> 24:[ print("About to create server") ]
+628 27:[ main["server"] = Server() ] >> 25:[ main["server"] = Server! ]
+654 28:[ print("Created server") ] >> 26:[ print("Created server") ]
+679 29:[ mod.node("join_id").text = "Lobby id:" .. main.server.lobby_id ] >> 27:[ mod.node("join_id").text = "Lobby id:" .. main.server.lobby_id ]
+743 30:[ print("Set join id text") ] >> 28:[ print("Set join id text") ]
+770 31:[ main["world"] = World() ] >> 29:[ main["world"] = World! ]
+794 32:[ main.world:set_room(LobbyRoom()) ] >> 30:[ main.world\set_room(LobbyRoom!) ]
+827 33:[ print("Created world") ] >> 31:[ print("Created world") ]
+851 34:[ main.world:join(main.server.lobby_id) ] >> 32:[ main.world\join(main.server.lobby_id) ]
+890 35:[ print("Joined my own lobby") ] >> 33:[ print("Joined my own lobby") ]
+920 36:[ local start_button = ui.create_any_button(mod.node, 5, 2, (-32 * 5) / 2, 0) ] >> 34:[ start_button = ui.create_any_button(mod.node,5,2,(-32*5)/2,0) ]
+983 37:[ start_button.node("loc"):append(am.scale(1.5) ^ am.translate(12, -10) ^ am.text("GET SILLY", color.fg, "left", "top")) ] >> 35:[ start_button.node("loc")\append(am.scale(1.5)^ am.translate(12,-10)^ am.text("GET SILLY", color.fg, "left", "top")) ]
+1100 38:[ local char_selector = ui.create_char_selector2(mod.node) ] >> 36:[ char_selector = ui.create_char_selector2(mod.node) ]
+1152 39:[ local char_right, char_left, char_text = char_selector[1], char_selector[2], char_selector[3] ] >> 37:[ char_right, char_left, char_text = char_selector[1], char_selector[2], char_selector[3] ]
+1241 40:[ mod.buttons = { ] >> 38:[ mod.buttons = {char_left, char_right, start_button} --save to remove from the physworld later ]
+1256 41:[ char_left, ] >> 38:[ mod.buttons = {char_left, char_right, start_button} --save to remove from the physworld later ]
+1266 42:[ char_right, ] >> 38:[ mod.buttons = {char_left, char_right, start_button} --save to remove from the physworld later ]
+1278 43:[ start_button ] >> 38:[ mod.buttons = {char_left, char_right, start_button} --save to remove from the physworld later ]
+1241 44:[ } ] >> 38:[ mod.buttons = {char_left, char_right, start_button} --save to remove from the physworld later ]
+1336 45:[ local classes = char.class_order ] >> 39:[ classes = char.class_order ]
+1364 46:[ local class_s = 1 ] >> 40:[ class_s = 1 ]
+1377 47:[ print("got char selector:", char_selector) ] >> 41:[ print("got char selector:",char_selector) ]
+1420 48:[ local touch_indicator = am.group() ] >> 42:[ touch_indicator = am.group! ]
+1449 49:[ local touch_cursor = am.sprite("data/cursor.png", vec4(1, 1, 1, 1), "left", "top") ] >> 43:[ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") ]
+1521 50:[ local touch_loc = am.translate(0, 0) ^ touch_cursor ] >> 44:[ touch_loc = am.translate(0,0)^ touch_cursor ]
+1566 51:[ touch_indicator:append(touch_loc) ] >> 45:[ touch_indicator\append(touch_loc) ]
+1667 52:[ start_button.color = color.transparent ] >> 47:[ start_button.color = color.transparent ]
+1707 53:[ char_right.color = color.transparent ] >> 48:[ char_right.color = color.transparent ]
+1745 54:[ char_left.color = color.transparent ] >> 49:[ char_left.color = color.transparent ]
+1782 55:[ mod.node:action("click_interpreter", coroutine.create(function() ] >> 50:[ mod.node\action("click_interpreter",coroutine.create(()-> ]
+1847 56:[ while not main.world.client_open() do ] >> 51:[ while not main.world.client_open! ]
+1879 57:[ coroutine.yield() ] >> 52:[ coroutine.yield! ]
+1842 58:[ end ] >> 51:[ while not main.world.client_open! ]
+1898 59:[ char_left.color = color.white ] >> 53:[ char_left.color = color.white ]
+1930 60:[ char_right.color = color.white ] >> 54:[ char_right.color = color.white ]
+1963 61:[ char_text.text = "Tumbler" ] >> 55:[ char_text.text = "Tumbler" ]
+1992 62:[ main.world:set_local(LocalPlayer(am.eval_js("CLIENT.peer._id"), { }, char.classes.Tumbler)) ] >> 56:[ main.world\set_local(LocalPlayer(am.eval_js("CLIENT.peer._id"),{},char.classes.Tumbler)) ]
+2083 63:[ main.world:load() ] >> 57:[ main.world\load! ]
+2107 64:[ while true do ] >> 58:[ while true ]
+2118 65:[ if #main.win:active_touches() > 0 then ] >> 59:[ if #main.win\active_touches! > 0 ]
+2154 66:[ local touch = main.win:touch_position(1) ] >> 60:[ touch = main.win\touch_position(1) ]
+2193 67:[ touch_cursor.color = vec4(1, 1, 1, 1) ] >> 61:[ touch_cursor.color = vec4(1,1,1,1) ]
+2232 68:[ touch_loc.x = touch.x ] >> 62:[ touch_loc.x = touch.x ]
+2258 69:[ touch_loc.y = touch.y ] >> 63:[ touch_loc.y = touch.y ]
+2284 70:[ local col_but = bp.check(touch.x, touch.y + 64) ] >> 64:[ col_but = bp.check(touch.x, touch.y + 64) ]
+2332 71:[ if #col_but > 0 then ] >> 65:[ if #col_but > 0 ]
+2351 72:[ touch_cursor.color = vec4(0.5, 0.5, 0.5, 0.5) ] >> 66:[ touch_cursor.color = vec4(0.5,0.5,0.5,0.5) ]
+2330 73:[ end ] >> 65:[ if #col_but > 0 ]
+2400 74:[ if #col_but > 0 and main.win:touch_began(1) then ] >> 67:[ if #col_but > 0 and main.win\touch_began(1) ]
+2449 75:[ if col_but[1] == start_button then ] >> 68:[ if col_but[1] == start_button ]
+2483 76:[ print("Starting!") ] >> 69:[ print("Starting!") ]
+2508 77:[ action.start_game() ] >> 70:[ action.start_game! ]
+2533 78:[ coroutine.yield() ] >> 71:[ coroutine.yield! ]
+2447 79:[ end ] >> 68:[ if col_but[1] == start_button ]
+2557 80:[ if col_but[1] == char_left then ] >> 72:[ if col_but[1] == char_left ]
+2588 81:[ print("char left") ] >> 73:[ print("char left") ]
+2613 82:[ class_s = class_s - 1 ] >> 74:[ class_s = class_s - 1 ]
+2641 83:[ start_button.color = color.white ] >> 75:[ start_button.color = color.white ]
+2682 84:[ if class_s == 0 then ] >> 76:[ if class_s == 0 ]
+2703 85:[ class_s = #classes ] >> 77:[ class_s = #classes ]
+2680 86:[ end ] >> 76:[ if class_s == 0 ]
+2555 87:[ end ] >> 72:[ if col_but[1] == char_left ]
+2729 88:[ if col_but[1] == char_right then ] >> 78:[ if col_but[1] == char_right ]
+2761 89:[ print("char right") ] >> 79:[ print("char right") ]
+2787 90:[ class_s = class_s + 1 ] >> 80:[ class_s = class_s + 1 ]
+2815 91:[ start_button.color = color.white ] >> 81:[ start_button.color = color.white ]
+2856 92:[ if class_s > #classes then ] >> 82:[ if class_s > #classes ]
+2883 93:[ class_s = 1 ] >> 83:[ class_s = 1 ]
+2854 94:[ end ] >> 82:[ if class_s > #classes ]
+2727 95:[ end ] >> 78:[ if col_but[1] == char_right ]
+2902 96:[ if col_but[1] == char_left or col_but[1] == char_right then ] >> 84:[ if col_but[1] == char_left or col_but[1] == char_right ]
+2961 97:[ print("class_s", class_s) ] >> 85:[ print("class_s",class_s) ]
+2992 98:[ print("classes:", classes) ] >> 86:[ print("classes:", classes) ]
+3025 99:[ char_text.text = classes[class_s] ] >> 87:[ char_text.text = classes[class_s] ]
+3065 100:[ action.request_class_change(classes[class_s]) ] >> 88:[ action.request_class_change(classes[class_s]) ]
+2900 101:[ end ] >> 84:[ if col_but[1] == char_left or col_but[1] == char_right ]
+2398 102:[ end ] >> 67:[ if #col_but > 0 and main.win\touch_began(1) ]
+3124 104:[ touch_cursor.color = vec4(0, 0, 0, 0) ] >> 91:[ touch_cursor.color = vec4(0,0,0,0) ]
+3162 106:[ coroutine.yield() ] >> 92:[ coroutine.yield! ]
+2102 107:[ end ] >> 58:[ while true ]
+1782 108:[ end)) ] >> 50:[ mod.node\action("click_interpreter",coroutine.create(()-> ]
+3184 109:[ return mod.node:append(touch_indicator) ] >> 94:[ mod.node\append(touch_indicator) ]
+489 110:[ end ] >> 20:[ mod.load = () -> ]
+3218 111:[ mod.unload = function() ] >> 96:[ mod.unload = () -> ]
+3238 112:[ print("Unloading create party") ] >> 97:[ print("Unloading create party") ]
+3271 113:[ main.root("screen"):remove(mod.node) ] >> 98:[ main.root("screen")\remove(mod.node) ]
+3325 114:[ for _, button in pairs(mod.buttons) do ] >> 99:[ for _, button in pairs(mod.buttons) ]
+3347 115:[ bp.remove(button) ] >> 100:[ bp.remove(button) ]
+3309 116:[ end ] >> 99:[ for _, button in pairs(mod.buttons) ]
+3366 117:[ mod.loaded = false ] >> 101:[ mod.loaded = false ]
+3218 118:[ end ] >> 96:[ mod.unload = () -> ]
+3386 119:[ return mod ] >> 103:[ mod ]
diff --git a/debug/defeat_menu.lua.X b/debug/defeat_menu.lua.X
new file mode 100644
index 0000000..811be30
--- /dev/null
+++ b/debug/defeat_menu.lua.X
@@ -0,0 +1,63 @@
+Pos Lua >> Moon
+2 1:[ local main = require("main") ] >> 2:[ main = require "main" ]
+24 2:[ local bp = require("broadphase") ] >> 3:[ bp = require "broadphase" ]
+50 3:[ local color = require("color") ] >> 4:[ color = require "color" ]
+74 4:[ local ui = require("ui") ] >> 5:[ ui = require "ui" ]
+93 5:[ local world = require("world") ] >> 7:[ world = require "world" --delete when done ]
+154 7:[ Server = world.Server ] >> 8:[ import Server from world ]
+178 9:[ World = world.World ] >> 9:[ import World from world ]
+185 10:[ local mod = ... ] >> 10:[ mod = ... ]
+208 11:[ local nwidth = 6 ] >> 13:[ nwidth = 6 ]
+219 12:[ local section_width = 32 ] >> 14:[ section_width = 32 ]
+238 13:[ local start_x = (-32) * (nwidth / 2) ] >> 15:[ start_x = (-32)*(nwidth/2) ]
+265 14:[ mod.node = am.group() ] >> 16:[ mod.node = am.group! ]
+286 15:[ mod.node:append(am.sprite("data/defeat_screen.png")) ] >> 17:[ mod.node\append(am.sprite("data/defeat_screen.png")) ]
+339 16:[ local start_y = 0 ] >> 18:[ start_y = 0 ]
+351 17:[ local padding = 32 ] >> 19:[ padding = 32 ]
+364 18:[ local buttonspecs = { ] >> 20:[ buttonspecs = { ]
+397 20:[ y_off = start_y, ] >> 22:[ y_off: start_y, ]
+414 21:[ text = "Menu" ] >> 23:[ text: "Menu" ]
+364 23:[ } ] >> 20:[ buttonspecs = { ]
+430 24:[ mod.load = function() ] >> 28:[ mod.load = () -> ]
+448 25:[ print("creating main menu") ] >> 29:[ print("creating main menu") ]
+477 26:[ main.root("screen"):append(mod.node) ] >> 30:[ main.root("screen")\append(mod.node) ]
+538 27:[ for name, button_tbl in pairs(buttonspecs) do ] >> 31:[ for name, button_tbl in pairs buttonspecs ]
+559 28:[ local button_extra = ui.create_big_button(mod.node, 6, start_x, button_tbl.y_off) ] >> 32:[ button_extra = ui.create_big_button(mod.node,6,start_x,button_tbl.y_off) ]
+634 29:[ button_extra.node:append(am.translate(start_x + ((nwidth * section_width) / 2), button_tbl.y_off - 32) ^ am.text(button_tbl.text, color.fg)) ] >> 33:[ button_extra.node\append(am.translate(start_x+((nwidth*section_width)/2),button_tbl.y_off - 32)^ am.text(button_tbl.text,color.fg)) ]
+768 30:[ mod[name] = button_extra ] >> 34:[ mod[name] = button_extra ]
+515 31:[ end ] >> 31:[ for name, button_tbl in pairs buttonspecs ]
+794 32:[ local touch_indicator = am.group() ] >> 35:[ touch_indicator = am.group! ]
+823 33:[ local touch_cursor = am.sprite("data/cursor.png", vec4(1, 1, 1, 1), "left", "top") ] >> 36:[ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") ]
+895 34:[ local touch_loc = am.translate(0, 0) ^ touch_cursor ] >> 37:[ touch_loc = am.translate(0,0)^ touch_cursor ]
+940 35:[ touch_indicator:append(touch_loc) ] >> 38:[ touch_indicator\append(touch_loc) ]
+975 36:[ mod.node:append(am.translate(0, 100) ^ am.scale(3) ^ am.text("You failed to\nremain silly")) ] >> 39:[ mod.node\append(am.translate(0,100)^ am.scale(3)^ am.text("You failed to\nremain silly")) ]
+1066 37:[ mod.node:action("click_interpreter", coroutine.create(function() ] >> 40:[ mod.node\action("click_interpreter",coroutine.create(()-> ]
+1131 38:[ while true do ] >> 41:[ while true ]
+1142 39:[ if #main.win:active_touches() > 0 then ] >> 42:[ if #main.win\active_touches! > 0 ]
+1177 40:[ local touch = main.win:touch_position(1) ] >> 43:[ touch = main.win\touch_position(1) ]
+1216 41:[ touch_cursor.color = vec4(1, 1, 1, 1) ] >> 44:[ touch_cursor.color = vec4(1,1,1,1) ]
+1255 42:[ touch_loc.x = touch.x ] >> 45:[ touch_loc.x = touch.x ]
+1281 43:[ touch_loc.y = touch.y ] >> 46:[ touch_loc.y = touch.y ]
+1307 44:[ local col_but = bp.check(touch.x, touch.y + 64) ] >> 47:[ col_but = bp.check(touch.x, touch.y+64) ]
+1353 45:[ if #col_but > 0 then ] >> 48:[ if #col_but > 0 ]
+1372 46:[ touch_cursor.color = vec4(0.5, 0.5, 0.5, 0.5) ] >> 49:[ touch_cursor.color = vec4(0.5,0.5,0.5,0.5) ]
+1351 47:[ end ] >> 48:[ if #col_but > 0 ]
+1421 48:[ if #col_but > 0 then ] >> 50:[ if #col_but > 0 ]
+1440 49:[ print("Collided with button:", col_but) ] >> 51:[ print("Collided with button:",col_but) ]
+1484 50:[ print("mod.create prty was:", mod.create_party) ] >> 52:[ print("mod.create prty was:",mod.create_party) ]
+1538 51:[ if col_but[1] == mod.menu then ] >> 53:[ if col_but[1] == mod.menu ]
+1568 52:[ am.eval_js("location.reload()") ] >> 54:[ am.eval_js("location.reload()") ]
+1536 53:[ end ] >> 53:[ if col_but[1] == mod.menu ]
+1419 54:[ end ] >> 50:[ if #col_but > 0 ]
+1612 56:[ touch_cursor.color = vec4(0, 0, 0, 0) ] >> 56:[ touch_cursor.color = vec4(0,0,0,0) ]
+1650 58:[ coroutine.yield() ] >> 57:[ coroutine.yield! ]
+1126 59:[ end ] >> 41:[ while true ]
+1066 60:[ end)) ] >> 40:[ mod.node\action("click_interpreter",coroutine.create(()-> ]
+1672 61:[ return mod.node:append(touch_indicator) ] >> 59:[ mod.node\append(touch_indicator) ]
+430 62:[ end ] >> 28:[ mod.load = () -> ]
+1706 63:[ mod.unload = function() ] >> 61:[ mod.unload = () -> ]
+1726 64:[ main.root("screen"):remove(mod.node) ] >> 62:[ main.root("screen")\remove(mod.node) ]
+1764 65:[ bp.remove(mod.create_party) ] >> 63:[ bp.remove(mod.create_party) ]
+1793 66:[ return bp.remove(mod.join_party) ] >> 64:[ bp.remove(mod.join_party) ]
+1706 67:[ end ] >> 61:[ mod.unload = () -> ]
+1820 68:[ return mod ] >> 66:[ mod ]
diff --git a/debug/dispatch.lua.X b/debug/dispatch.lua.X
new file mode 100644
index 0000000..b8528ee
--- /dev/null
+++ b/debug/dispatch.lua.X
@@ -0,0 +1,50 @@
+Pos Lua >> Moon
+28 1:[ local mod = ... ] >> 3:[ mod = ... ]
+38 2:[ local char = require("char") ] >> 4:[ char = require "char" ]
+80 4:[ KeyInput = char.KeyInput ] >> 5:[ import KeyInput from char ]
+86 5:[ local main = require("main") ] >> 6:[ main = require "main" ]
+109 6:[ local set_input ] >> 8:[ set_input = (inputs, key, value) -> ]
+109 7:[ set_input = function(inputs, key, value) ] >> 8:[ set_input = (inputs, key, value) -> ]
+160 8:[ for _index_0 = 1, #inputs do ] >> 9:[ for input in *inputs ]
+160 9:[ local input = inputs[_index_0] ] >> 9:[ for input in *inputs ]
+171 10:[ if input.key == key then ] >> 10:[ if input.key == key ]
+233 11:[ input.value = value ] >> 12:[ input.value = value ]
+256 12:[ return ] >> 13:[ return ]
+169 13:[ end ] >> 10:[ if input.key == key ]
+109 15:[ end ] >> 8:[ set_input = (inputs, key, value) -> ]
+264 16:[ local controls = { ] >> 15:[ controls = { ]
+284 17:[ right = "right", ] >> 16:[ right: "right", ]
+300 18:[ left = "left", ] >> 17:[ left: "left", ]
+313 19:[ up = "up", ] >> 18:[ up: "up" ]
+325 20:[ jump = "z", ] >> 19:[ jump: "z", ]
+337 21:[ down = "down", ] >> 20:[ down: "down" ]
+351 22:[ dash = "c", ] >> 21:[ dash: "c" ]
+363 23:[ swing = "x" ] >> 22:[ swing: "x" ]
+264 24:[ } ] >> 15:[ controls = { ]
+371 25:[ mod.control = function(character) ] >> 25:[ mod.control = (character) -> ]
+401 26:[ return character.node:action(coroutine.create(function() ] >> 26:[ character.node\action(coroutine.create(() -> ]
+453 27:[ while true do ] >> 27:[ while true ]
+472 28:[ for k, v in pairs(controls) do ] >> 28:[ for k,v in pairs controls ]
+492 29:[ set_input(character.inputs, k, main.win:key_down(v)) ] >> 29:[ set_input(character.inputs,k,main.win\key_down(v)) ]
+462 30:[ end ] >> 28:[ for k,v in pairs controls ]
+546 31:[ coroutine.yield() ] >> 30:[ coroutine.yield() ]
+448 32:[ end ] >> 27:[ while true ]
+401 33:[ end)) ] >> 26:[ character.node\action(coroutine.create(() -> ]
+371 34:[ end ] >> 25:[ mod.control = (character) -> ]
+569 35:[ mod.slime_ai = function(character) ] >> 33:[ mod.slime_ai = (character) -> ]
+600 36:[ return character.node:action(coroutine.create(function() ] >> 34:[ character.node\action(coroutine.create(() -> ]
+647 37:[ local time_offset = math.random() ] >> 35:[ time_offset = math.random() ]
+677 38:[ local last_action = am.current_time() + time_offset ] >> 36:[ last_action = am.current_time() + time_offset ]
+730 39:[ while true do ] >> 37:[ while true ]
+739 40:[ local time_diff = am.current_time() - last_action ] >> 38:[ time_diff = am.current_time() - last_action ]
+788 41:[ if time_diff > 1.3 then ] >> 39:[ if time_diff > 1.3 ]
+809 42:[ last_action = am.current_time() + math.random() ] >> 40:[ last_action = am.current_time() + math.random() ]
+866 43:[ elseif time_diff > 1.1 then ] >> 41:[ elseif time_diff > 1.1 ]
+887 44:[ set_input(character.inputs, "jump_left", false) ] >> 42:[ set_input(character.inputs,"jump_left",false) ]
+942 45:[ elseif time_diff > 1 then ] >> 43:[ elseif time_diff > 1 ]
+961 46:[ set_input(character.inputs, "jump_left", true) ] >> 44:[ set_input(character.inputs,"jump_left",true) ]
+1010 48:[ coroutine.yield() ] >> 46:[ coroutine.yield() ]
+725 49:[ end ] >> 37:[ while true ]
+600 50:[ end)) ] >> 34:[ character.node\action(coroutine.create(() -> ]
+569 51:[ end ] >> 33:[ mod.slime_ai = (character) -> ]
+1033 52:[ return mod ] >> 49:[ mod ]
diff --git a/debug/e_bethany.lua.X b/debug/e_bethany.lua.X
new file mode 100644
index 0000000..a3fc840
--- /dev/null
+++ b/debug/e_bethany.lua.X
@@ -0,0 +1,25 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+51 3:[ require("a_brood") ] >> 3:[ require "a_brood" ]
+70 4:[ local mod = ... ] >> 5:[ mod = ... ]
+81 5:[ mod.char = { ] >> 7:[ mod.char = { ]
+100 6:[ name = "Brooding Bethany", ] >> 8:[ name: "Brooding Bethany", ]
+125 7:[ cr = 8, ] >> 9:[ cr: 8, ]
+147 8:[ default_position = 2, ] >> 10:[ default_position: 2 ]
+171 10:[ reg.Brood ] >> 11:[ default_abilities: {reg.Brood}, ]
+198 12:[ default_maxhp = 4, ] >> 12:[ default_maxhp: 4, ]
+214 13:[ default_hp = 1, ] >> 13:[ default_hp: 1, ]
+238 14:[ default_maxstamina = 1, ] >> 14:[ default_maxstamina: 1, ]
+259 15:[ default_stamina = 1, ] >> 15:[ default_stamina: 1, ]
+280 16:[ default_maxmana = 0, ] >> 16:[ default_maxmana: 0, ]
+298 17:[ default_mana = 0, ] >> 17:[ default_mana: 0, ]
+310 18:[ sprite = "data/e_brood.png", ] >> 18:[ sprite:"data/e_brood.png", ]
+355 20:[ print("while selecting action, by abilities were:", self.data.abilities) ] >> 20:[ print("while selecting action, by abilities were:",@data.abilities) ]
+425 21:[ return self.data.abilities[1] ] >> 21:[ @data.abilities[1] ]
+81 23:[ } ] >> 7:[ mod.char = { ]
+456 24:[ for k, v in pairs(mod.char) do ] >> 23:[ for k,v in pairs(mod.char) ]
+474 25:[ mod[k] = v ] >> 24:[ mod[k] = v ]
+446 26:[ end ] >> 23:[ for k,v in pairs(mod.char) ]
+486 27:[ char.enemies[mod.char.name] = mod.char ] >> 26:[ char.enemies[mod.char.name] = mod.char ]
+526 28:[ return mod ] >> 28:[ mod ]
diff --git a/debug/e_child.lua.X b/debug/e_child.lua.X
new file mode 100644
index 0000000..6a53917
--- /dev/null
+++ b/debug/e_child.lua.X
@@ -0,0 +1,25 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+51 3:[ require("a_pass") ] >> 3:[ require "a_pass" ]
+69 4:[ local mod = ... ] >> 5:[ mod = ... ]
+80 5:[ mod.char = { ] >> 7:[ mod.char = { ]
+99 6:[ name = "An actual child", ] >> 8:[ name: "An actual child", ]
+123 7:[ cr = 1, ] >> 9:[ cr: 1, ]
+145 8:[ default_position = 1, ] >> 10:[ default_position: 1 ]
+169 10:[ reg.Pass ] >> 11:[ default_abilities: {reg.Pass}, ]
+195 12:[ default_maxhp = 1, ] >> 12:[ default_maxhp: 1, ]
+211 13:[ default_hp = 1, ] >> 13:[ default_hp: 1, ]
+235 14:[ default_maxstamina = 1, ] >> 14:[ default_maxstamina: 1, ]
+256 15:[ default_stamina = 1, ] >> 15:[ default_stamina: 1, ]
+277 16:[ default_maxmana = 0, ] >> 16:[ default_maxmana: 0, ]
+295 17:[ default_mana = 0, ] >> 17:[ default_mana: 0, ]
+307 18:[ sprite = "data/e_child.png", ] >> 18:[ sprite:"data/e_child.png", ]
+352 20:[ print("while selecting action, by abilities were:", self.data.abilities) ] >> 20:[ print("while selecting action, by abilities were:",@data.abilities) ]
+422 21:[ return self.data.abilities[1] ] >> 21:[ @data.abilities[1] ]
+80 23:[ } ] >> 7:[ mod.char = { ]
+453 24:[ for k, v in pairs(mod.char) do ] >> 23:[ for k,v in pairs(mod.char) ]
+471 25:[ mod[k] = v ] >> 24:[ mod[k] = v ]
+443 26:[ end ] >> 23:[ for k,v in pairs(mod.char) ]
+483 27:[ char.enemies[mod.char.name] = mod.char ] >> 26:[ char.enemies[mod.char.name] = mod.char ]
+523 28:[ return mod ] >> 28:[ mod ]
diff --git a/debug/e_mopey_marvin.lua.X b/debug/e_mopey_marvin.lua.X
new file mode 100644
index 0000000..117fece
--- /dev/null
+++ b/debug/e_mopey_marvin.lua.X
@@ -0,0 +1,25 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+51 3:[ require("a_mope") ] >> 3:[ require "a_mope" ]
+69 4:[ local mod = ... ] >> 5:[ mod = ... ]
+80 5:[ mod.char = { ] >> 7:[ mod.char = { ]
+99 6:[ name = "Mopey Marvin", ] >> 8:[ name: "Mopey Marvin", ]
+120 7:[ cr = 5, ] >> 9:[ cr: 5, ]
+142 8:[ default_position = 2, ] >> 10:[ default_position: 2 ]
+166 10:[ reg.Mope ] >> 11:[ default_abilities: {reg.Mope}, ]
+192 12:[ default_maxhp = 4, ] >> 12:[ default_maxhp: 4, ]
+208 13:[ default_hp = 1, ] >> 13:[ default_hp: 1, ]
+232 14:[ default_maxstamina = 1, ] >> 14:[ default_maxstamina: 1, ]
+253 15:[ default_stamina = 1, ] >> 15:[ default_stamina: 1, ]
+274 16:[ default_maxmana = 0, ] >> 16:[ default_maxmana: 0, ]
+292 17:[ default_mana = 0, ] >> 17:[ default_mana: 0, ]
+304 18:[ sprite = "data/e_mope.png", ] >> 18:[ sprite:"data/e_mope.png", ]
+348 20:[ print("while selecting action, by abilities were:", self.data.abilities) ] >> 20:[ print("while selecting action, by abilities were:",@data.abilities) ]
+418 21:[ return self.data.abilities[1] ] >> 21:[ @data.abilities[1] ]
+80 23:[ } ] >> 7:[ mod.char = { ]
+449 24:[ for k, v in pairs(mod.char) do ] >> 23:[ for k,v in pairs(mod.char) ]
+467 25:[ mod[k] = v ] >> 24:[ mod[k] = v ]
+439 26:[ end ] >> 23:[ for k,v in pairs(mod.char) ]
+479 27:[ char.enemies[mod.char.name] = mod.char ] >> 26:[ char.enemies[mod.char.name] = mod.char ]
+519 28:[ return mod ] >> 28:[ mod ]
diff --git a/debug/e_rat.lua.X b/debug/e_rat.lua.X
new file mode 100644
index 0000000..4bb4c86
--- /dev/null
+++ b/debug/e_rat.lua.X
@@ -0,0 +1,26 @@
+Pos Lua >> Moon
+2 1:[ local char = require("char") ] >> 2:[ char = require "char" ]
+24 2:[ local reg = require("ability_reg") ] >> 3:[ reg = require "ability_reg" ]
+52 3:[ require("a_rat_bite") ] >> 4:[ require "a_rat_bite" ]
+74 4:[ local mod = ... ] >> 6:[ mod = ... ]
+85 5:[ return mod ] >> 8:[ return mod ]
+97 6:[ mod.char = { ] >> 10:[ mod.char = { ]
+116 7:[ name = "Rat", ] >> 11:[ name: "Rat", ]
+128 8:[ cr = 1, ] >> 12:[ cr: 1, ]
+150 9:[ default_position = 1, ] >> 13:[ default_position: 1 ]
+174 11:[ reg.RatBite ] >> 14:[ default_abilities: {reg.RatBite}, ]
+203 13:[ default_maxhp = 1, ] >> 15:[ default_maxhp: 1, ]
+219 14:[ default_hp = 1, ] >> 16:[ default_hp: 1, ]
+243 15:[ default_maxstamina = 1, ] >> 17:[ default_maxstamina: 1, ]
+264 16:[ default_stamina = 1, ] >> 18:[ default_stamina: 1, ]
+285 17:[ default_maxmana = 0, ] >> 19:[ default_maxmana: 0, ]
+303 18:[ default_mana = 0, ] >> 20:[ default_mana: 0, ]
+315 19:[ sprite = "data/e_rat.png", ] >> 21:[ sprite:"data/e_rat.png", ]
+358 21:[ print("while selecting action, by abilities were:", self.data.abilities) ] >> 23:[ print("while selecting action, by abilities were:",@data.abilities) ]
+428 22:[ return self.data.abilities[1] ] >> 24:[ @data.abilities[1] ]
+97 24:[ } ] >> 10:[ mod.char = { ]
+459 25:[ for k, v in pairs(mod.char) do ] >> 26:[ for k,v in pairs(mod.char) ]
+477 26:[ mod[k] = v ] >> 27:[ mod[k] = v ]
+449 27:[ end ] >> 26:[ for k,v in pairs(mod.char) ]
+489 28:[ char.enemies[mod.char.name] = mod.char ] >> 29:[ char.enemies[mod.char.name] = mod.char ]
+529 29:[ return mod ] >> 31:[ mod ]
diff --git a/debug/e_ruminating_randy.lua.X b/debug/e_ruminating_randy.lua.X
new file mode 100644
index 0000000..b7b6c45
--- /dev/null
+++ b/debug/e_ruminating_randy.lua.X
@@ -0,0 +1,25 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local reg = require("ability_reg") ] >> 2:[ reg = require "ability_reg" ]
+51 3:[ require("a_ruminate") ] >> 3:[ require "a_ruminate" ]
+73 4:[ local mod = ... ] >> 5:[ mod = ... ]
+84 5:[ mod.char = { ] >> 7:[ mod.char = { ]
+103 6:[ name = "Ruminating Randy", ] >> 8:[ name: "Ruminating Randy", ]
+128 7:[ cr = 5, ] >> 9:[ cr: 5, ]
+150 8:[ default_position = 3, ] >> 10:[ default_position: 3 ]
+174 10:[ reg.Ruminate ] >> 11:[ default_abilities: {reg.Ruminate}, ]
+204 12:[ default_maxhp = 4, ] >> 12:[ default_maxhp: 4, ]
+220 13:[ default_hp = 1, ] >> 13:[ default_hp: 1, ]
+244 14:[ default_maxstamina = 1, ] >> 14:[ default_maxstamina: 1, ]
+265 15:[ default_stamina = 1, ] >> 15:[ default_stamina: 1, ]
+286 16:[ default_maxmana = 0, ] >> 16:[ default_maxmana: 0, ]
+304 17:[ default_mana = 0, ] >> 17:[ default_mana: 0, ]
+316 18:[ sprite = "data/e_rum.png", ] >> 18:[ sprite:"data/e_rum.png", ]
+359 20:[ print("while selecting action, by abilities were:", self.data.abilities) ] >> 20:[ print("while selecting action, by abilities were:",@data.abilities) ]
+429 21:[ return self.data.abilities[1] ] >> 21:[ @data.abilities[1] ]
+84 23:[ } ] >> 7:[ mod.char = { ]
+460 24:[ for k, v in pairs(mod.char) do ] >> 23:[ for k,v in pairs(mod.char) ]
+478 25:[ mod[k] = v ] >> 24:[ mod[k] = v ]
+450 26:[ end ] >> 23:[ for k,v in pairs(mod.char) ]
+490 27:[ char.enemies[mod.char.name] = mod.char ] >> 26:[ char.enemies[mod.char.name] = mod.char ]
+530 28:[ return mod ] >> 28:[ mod ]
diff --git a/debug/e_sullen_salley.lua.X b/debug/e_sullen_salley.lua.X
new file mode 100644
index 0000000..0c73e1c
--- /dev/null
+++ b/debug/e_sullen_salley.lua.X
@@ -0,0 +1,25 @@
+Pos Lua >> Moon
+2 1:[ local char = require("char") ] >> 2:[ char = require "char" ]
+24 2:[ local reg = require("ability_reg") ] >> 3:[ reg = require "ability_reg" ]
+52 3:[ require("a_sulk") ] >> 4:[ require "a_sulk" ]
+70 4:[ local mod = ... ] >> 6:[ mod = ... ]
+81 5:[ mod.char = { ] >> 8:[ mod.char = { ]
+100 6:[ name = "Sullen Sally", ] >> 9:[ name: "Sullen Sally", ]
+121 7:[ cr = 2, ] >> 10:[ cr: 2, ]
+143 8:[ default_position = 1, ] >> 11:[ default_position: 1 ]
+167 10:[ reg.Sulk ] >> 12:[ default_abilities: {reg.Sulk}, ]
+193 12:[ default_maxhp = 3, ] >> 13:[ default_maxhp: 3, ]
+209 13:[ default_hp = 1, ] >> 14:[ default_hp: 1, ]
+233 14:[ default_maxstamina = 1, ] >> 15:[ default_maxstamina: 1, ]
+254 15:[ default_stamina = 1, ] >> 16:[ default_stamina: 1, ]
+275 16:[ default_maxmana = 0, ] >> 17:[ default_maxmana: 0, ]
+293 17:[ default_mana = 0, ] >> 18:[ default_mana: 0, ]
+305 18:[ sprite = "data/e_sullen.png", ] >> 19:[ sprite:"data/e_sullen.png", ]
+351 20:[ print("while selecting action, by abilities were:", self.data.abilities) ] >> 21:[ print("while selecting action, by abilities were:",@data.abilities) ]
+421 21:[ return self.data.abilities[1] ] >> 22:[ @data.abilities[1] ]
+81 23:[ } ] >> 8:[ mod.char = { ]
+452 24:[ for k, v in pairs(mod.char) do ] >> 24:[ for k,v in pairs(mod.char) ]
+470 25:[ mod[k] = v ] >> 25:[ mod[k] = v ]
+442 26:[ end ] >> 24:[ for k,v in pairs(mod.char) ]
+482 27:[ char.enemies[mod.char.name] = mod.char ] >> 27:[ char.enemies[mod.char.name] = mod.char ]
+522 28:[ return mod ] >> 29:[ mod ]
diff --git a/debug/join_party_menu.lua.X b/debug/join_party_menu.lua.X
new file mode 100644
index 0000000..a7b627e
--- /dev/null
+++ b/debug/join_party_menu.lua.X
@@ -0,0 +1,72 @@
+Pos Lua >> Moon
+2 1:[ local main = require("main") ] >> 2:[ main = require "main" ]
+24 2:[ local world = require("world") ] >> 3:[ world = require "world" ]
+48 3:[ local color = require("color") ] >> 4:[ color = require "color" ]
+72 4:[ local bp = require("broadphase") ] >> 5:[ bp = require "broadphase" ]
+98 5:[ local ui = require("ui") ] >> 6:[ ui = require "ui" ]
+133 7:[ World = world.World ] >> 7:[ import World from world ]
+141 8:[ local mod = ... ] >> 9:[ mod = ... ]
+152 9:[ mod.node = am.group() ] >> 11:[ mod.node = am.group! ]
+173 10:[ mod.node:append(am.sprite("data/join.png")) ] >> 12:[ mod.node\append(am.sprite("data/join.png")) ]
+217 11:[ local default_text = "..." ] >> 13:[ default_text = "..." ]
+238 12:[ mod.load = function() ] >> 14:[ mod.load = () -> ]
+256 13:[ mod.loaded = true ] >> 15:[ mod.loaded = true ]
+275 14:[ print("Loading join_party_menu") ] >> 16:[ print("Loading join_party_menu") ]
+309 15:[ main.root("screen"):append(mod.node) ] >> 17:[ main.root("screen")\append(mod.node) ]
+347 16:[ mod.buttons = ui.create_join_input(mod.node) ] >> 18:[ mod.buttons = ui.create_join_input(mod.node) ]
+393 17:[ mod.node:append(am.translate(0, -135) ^ am.text(default_text, color.fg, "center", "top"):tag("join_id")) ] >> 19:[ mod.node\append(am.translate(0,-135)^ am.text(default_text, color.fg, "center","top")\tag("join_id")) ]
+496 18:[ local touch_indicator = am.group() ] >> 20:[ touch_indicator = am.group! ]
+525 19:[ local touch_cursor = am.sprite("data/cursor.png", vec4(1, 1, 1, 1), "left", "top") ] >> 21:[ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") ]
+597 20:[ local backspace = ui.create_any_button(mod.node, 2, 2, 150, -25) ] >> 22:[ backspace = ui.create_any_button(mod.node,2,2,150,-25) ]
+653 21:[ backspace.node:append(am.translate(164, -41) ^ am.scale(2) ^ am.text("<-", color.fg, "left", "top")) ] >> 23:[ backspace.node\append(am.translate(164,-41)^ am.scale(2)^ am.text("<-", color.fg, "left","top")) ]
+751 22:[ local touch_loc = am.translate(0, 0) ^ touch_cursor ] >> 24:[ touch_loc = am.translate(0,0)^ touch_cursor ]
+796 23:[ touch_indicator:append(touch_loc) ] >> 25:[ touch_indicator\append(touch_loc) ]
+831 24:[ mod.node:append(touch_indicator) ] >> 26:[ mod.node\append(touch_indicator) ]
+865 25:[ print("char selector:", char_selector) ] >> 27:[ print("char selector:",char_selector) ]
+904 26:[ return mod.node:action(coroutine.create(function() ] >> 28:[ mod.node\action(coroutine.create(() -> ]
+950 27:[ while true do ] >> 29:[ while true ]
+961 28:[ if #main.win:active_touches() > 0 then ] >> 30:[ if #main.win\active_touches! > 0 ]
+996 29:[ local touch = main.win:touch_position(1) ] >> 31:[ touch = main.win\touch_position(1) ]
+1035 30:[ touch_cursor.color = vec4(1, 1, 1, 1) ] >> 32:[ touch_cursor.color = vec4(1,1,1,1) ]
+1074 31:[ touch_loc.x = touch.x ] >> 33:[ touch_loc.x = touch.x ]
+1100 32:[ touch_loc.y = touch.y ] >> 34:[ touch_loc.y = touch.y ]
+1126 33:[ local col_but = bp.check(touch.x, touch.y + 40) ] >> 35:[ col_but = bp.check(touch.x, touch.y + 40) ]
+1172 34:[ local n = mod.node("join_id") ] >> 36:[ n = mod.node("join_id") ]
+1202 35:[ if #col_but > 0 then ] >> 37:[ if #col_but > 0 ]
+1221 36:[ touch_cursor.color = vec4(0.5, 0.5, 0.5, 0.5) ] >> 38:[ touch_cursor.color = vec4(0.5,0.5,0.5,0.5) ]
+1200 37:[ end ] >> 37:[ if #col_but > 0 ]
+1270 38:[ if main.win:touch_began(1) then ] >> 39:[ if main.win\touch_began(1) ]
+1316 39:[ for _, button in pairs(mod.buttons) do ] >> 40:[ for _, button in pairs(mod.buttons) ]
+1344 40:[ if col_but[1] == button then ] >> 41:[ if col_but[1] == button ]
+1375 41:[ if n.text == default_text then ] >> 42:[ if n.text == default_text ]
+1407 42:[ n.text = "" ] >> 43:[ n.text = "" ]
+1373 43:[ end ] >> 42:[ if n.text == default_text ]
+1426 44:[ print("Found pressed button", button) ] >> 44:[ print("Found pressed button",button) ]
+1470 45:[ n.text = n.text .. button.text ] >> 45:[ n.text = n.text .. button.text ]
+1342 46:[ end ] >> 41:[ if col_but[1] == button ]
+1300 47:[ end ] >> 40:[ for _, button in pairs(mod.buttons) ]
+1508 48:[ if col_but[1] == backspace then ] >> 46:[ if col_but[1] == backspace ]
+1539 49:[ n.text = n.text:sub(1, string.len(n.text) - 1) ] >> 47:[ n.text = n.text\sub(1,string.len(n.text)-1) ]
+1506 50:[ end ] >> 46:[ if col_but[1] == backspace ]
+1268 51:[ end ] >> 39:[ if main.win\touch_began(1) ]
+1589 52:[ if #n.text > 5 then ] >> 48:[ if #n.text > 5 ]
+1607 53:[ print("Time to join!") ] >> 49:[ print("Time to join!") ]
+1635 54:[ local lobby = require("lobby_menu") ] >> 50:[ lobby = require("lobby_menu") ]
+1670 55:[ table.insert(main.action_queue, { ] >> 51:[ table.insert(main.action_queue,{lobby.load,{n.text}}) ]
+1702 56:[ lobby.load, ] >> 51:[ table.insert(main.action_queue,{lobby.load,{n.text}}) ]
+1714 58:[ n.text ] >> 51:[ table.insert(main.action_queue,{lobby.load,{n.text}}) ]
+1670 60:[ }) ] >> 51:[ table.insert(main.action_queue,{lobby.load,{n.text}}) ]
+1729 61:[ mod.unload() ] >> 52:[ mod.unload! ]
+1587 62:[ end ] >> 48:[ if #n.text > 5 ]
+959 63:[ end ] >> 30:[ if #main.win\active_touches! > 0 ]
+1745 64:[ coroutine.yield() ] >> 54:[ coroutine.yield! ]
+945 65:[ end ] >> 29:[ while true ]
+904 66:[ end)) ] >> 28:[ mod.node\action(coroutine.create(() -> ]
+238 67:[ end ] >> 14:[ mod.load = () -> ]
+1767 68:[ mod.unload = function() ] >> 57:[ mod.unload = () -> ]
+1787 69:[ main.root("screen"):remove(mod.node) ] >> 58:[ main.root("screen")\remove(mod.node) ]
+1841 70:[ for _, button in pairs(mod.buttons) do ] >> 59:[ for _, button in pairs(mod.buttons) ]
+1863 71:[ bp.remove(button) ] >> 60:[ bp.remove(button) ]
+1825 72:[ end ] >> 59:[ for _, button in pairs(mod.buttons) ]
+1767 73:[ end ] >> 57:[ mod.unload = () -> ]
+1882 74:[ return mod ] >> 62:[ mod ]
diff --git a/debug/lobby_menu.lua.X b/debug/lobby_menu.lua.X
new file mode 100644
index 0000000..f50cb4d
--- /dev/null
+++ b/debug/lobby_menu.lua.X
@@ -0,0 +1,88 @@
+Pos Lua >> Moon
+1 1:[ local main = require("main") ] >> 1:[ main = require "main" ]
+23 2:[ local color = require("color") ] >> 2:[ color = require "color" ]
+47 3:[ local ui = require("ui") ] >> 3:[ ui = require "ui" ]
+65 4:[ local world = require("world") ] >> 4:[ world = require "world" ]
+89 5:[ local char = require("char") ] >> 5:[ char = require "char" ]
+111 6:[ local player = require("player") ] >> 6:[ player = require "player" ]
+137 7:[ local bp = require("broadphase") ] >> 7:[ bp = require "broadphase" ]
+163 8:[ local action = require("action") ] >> 8:[ action = require "action" ]
+189 9:[ local room = require("room") ] >> 9:[ room = require "room" ]
+228 11:[ World = world.World ] >> 10:[ import World from world ]
+258 13:[ LocalPlayer = player.LocalPlayer ] >> 11:[ import LocalPlayer from player ]
+287 15:[ LobbyRoom = room.LobbyRoom ] >> 12:[ import LobbyRoom from room ]
+294 16:[ local mod = ... ] >> 14:[ mod = ... ]
+305 17:[ mod.node = am.group() ] >> 16:[ mod.node = am.group! ]
+376 18:[ mod.node:append(am.translate(0, main.height / 2) ^ am.text("Join lobby...", color.fg, "center", "top"):tag("join_id")) ] >> 18:[ mod.node\append(am.translate(0,main.height/2)^ am.text("Join lobby...", color.fg, "center","top")\tag("join_id")) ]
+490 19:[ local char_selector = am.translate(0, (main.height / 2) + 64) ] >> 19:[ char_selector = am.translate(0,(main.height/2)+64) ]
+541 20:[ mod.loaded = false ] >> 20:[ mod.loaded = false ]
+560 21:[ mod.load = function(id) ] >> 21:[ mod.load = (id) -> ]
+580 22:[ main.world = World() ] >> 22:[ main.world = World! ]
+601 23:[ main.world:set_room(LobbyRoom()) ] >> 23:[ main.world\set_room(LobbyRoom!) ]
+634 24:[ main.world:join(id) ] >> 24:[ main.world\join(id) ]
+655 25:[ mod.node("join_id").text = "Lobby id:" .. id ] >> 25:[ mod.node("join_id").text = "Lobby id:" .. id ]
+701 26:[ main.root("screen"):append(mod.node) ] >> 26:[ main.root("screen")\append(mod.node) ]
+739 27:[ local touch_indicator = am.group() ] >> 27:[ touch_indicator = am.group! ]
+768 28:[ local touch_cursor = am.sprite("data/cursor.png", vec4(1, 1, 1, 1), "left", "top") ] >> 28:[ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") ]
+840 29:[ local touch_loc = am.translate(0, 0) ^ touch_cursor ] >> 29:[ touch_loc = am.translate(0,0)^ touch_cursor ]
+885 30:[ touch_indicator:append(touch_loc) ] >> 30:[ touch_indicator\append(touch_loc) ]
+920 31:[ char_selector = ui.create_char_selector2(mod.node) ] >> 31:[ char_selector = ui.create_char_selector2(mod.node) ]
+972 32:[ local char_right, char_left, char_text = char_selector[1], char_selector[2], char_selector[3] ] >> 32:[ char_right, char_left, char_text = char_selector[1], char_selector[2], char_selector[3] ]
+1061 33:[ mod.buttons = { ] >> 33:[ mod.buttons = {char_left, char_right} ]
+1076 34:[ char_left, ] >> 33:[ mod.buttons = {char_left, char_right} ]
+1086 35:[ char_right ] >> 33:[ mod.buttons = {char_left, char_right} ]
+1061 36:[ } ] >> 33:[ mod.buttons = {char_left, char_right} ]
+1100 37:[ local classes = char.class_order ] >> 34:[ classes = char.class_order ]
+1128 38:[ local class_s = 1 ] >> 35:[ class_s = 1 ]
+1141 39:[ char_right.color = color.transparent ] >> 36:[ char_right.color = color.transparent ]
+1179 40:[ char_left.color = color.transparent ] >> 37:[ char_left.color = color.transparent ]
+1216 41:[ mod.node:action(coroutine.create(function() ] >> 38:[ mod.node\action(coroutine.create(() -> ]
+1262 42:[ while not main.world.client_open() do ] >> 39:[ while not main.world.client_open! ]
+1294 43:[ coroutine.yield() ] >> 40:[ coroutine.yield! ]
+1257 44:[ end ] >> 39:[ while not main.world.client_open! ]
+1313 45:[ char_left.color = color.white ] >> 41:[ char_left.color = color.white ]
+1345 46:[ char_right.color = color.white ] >> 42:[ char_right.color = color.white ]
+1378 47:[ char_text.text = "Tumbler" ] >> 43:[ char_text.text = "Tumbler" ]
+1407 48:[ main.world:load() ] >> 44:[ main.world\load! ]
+1426 49:[ action.sync_players() ] >> 45:[ action.sync_players! ]
+1454 50:[ while true do ] >> 46:[ while true ]
+1465 51:[ if #main.win:active_touches() > 0 then ] >> 47:[ if #main.win\active_touches! > 0 ]
+1501 52:[ local touch = main.win:touch_position(1) ] >> 48:[ touch = main.win\touch_position(1) ]
+1540 53:[ touch_cursor.color = vec4(1, 1, 1, 1) ] >> 49:[ touch_cursor.color = vec4(1,1,1,1) ]
+1579 54:[ touch_loc.x = touch.x ] >> 50:[ touch_loc.x = touch.x ]
+1605 55:[ touch_loc.y = touch.y ] >> 51:[ touch_loc.y = touch.y ]
+1631 56:[ local col_but = bp.check(touch.x, touch.y + 64) ] >> 52:[ col_but = bp.check(touch.x, touch.y + 64) ]
+1679 57:[ if #col_but > 0 then ] >> 53:[ if #col_but > 0 ]
+1698 58:[ touch_cursor.color = vec4(0.5, 0.5, 0.5, 0.5) ] >> 54:[ touch_cursor.color = vec4(0.5,0.5,0.5,0.5) ]
+1677 59:[ end ] >> 53:[ if #col_but > 0 ]
+1747 60:[ if #col_but > 0 and main.win:touch_began(1) then ] >> 55:[ if #col_but > 0 and main.win\touch_began(1) ]
+1796 61:[ if col_but[1] == char_left then ] >> 56:[ if col_but[1] == char_left ]
+1827 62:[ class_s = class_s - 1 ] >> 57:[ class_s = class_s - 1 ]
+1857 63:[ if class_s == 0 then ] >> 58:[ if class_s == 0 ]
+1878 64:[ class_s = #classes ] >> 59:[ class_s = #classes ]
+1855 65:[ end ] >> 58:[ if class_s == 0 ]
+1794 66:[ end ] >> 56:[ if col_but[1] == char_left ]
+1904 67:[ if col_but[1] == char_right then ] >> 60:[ if col_but[1] == char_right ]
+1936 68:[ class_s = class_s + 1 ] >> 61:[ class_s = class_s + 1 ]
+1966 69:[ if class_s > #classes then ] >> 62:[ if class_s > #classes ]
+1993 70:[ class_s = 1 ] >> 63:[ class_s = 1 ]
+1964 71:[ end ] >> 62:[ if class_s > #classes ]
+1902 72:[ end ] >> 60:[ if col_but[1] == char_right ]
+2010 73:[ char_text.text = classes[class_s] ] >> 64:[ char_text.text = classes[class_s] ]
+2049 74:[ action.request_class_change(classes[class_s]) ] >> 65:[ action.request_class_change(classes[class_s]) ]
+1745 75:[ end ] >> 55:[ if #col_but > 0 and main.win\touch_began(1) ]
+2107 77:[ touch_cursor.color = vec4(0, 0, 0, 0) ] >> 67:[ touch_cursor.color = vec4(0,0,0,0) ]
+2145 79:[ coroutine.yield() ] >> 68:[ coroutine.yield! ]
+1449 80:[ end ] >> 46:[ while true ]
+1216 81:[ end)) ] >> 38:[ mod.node\action(coroutine.create(() -> ]
+2168 82:[ mod.node:append(touch_indicator) ] >> 71:[ mod.node\append(touch_indicator) ]
+2202 83:[ mod.loaded = true ] >> 72:[ mod.loaded = true ]
+560 84:[ end ] >> 21:[ mod.load = (id) -> ]
+2221 85:[ mod.unload = function() ] >> 74:[ mod.unload = () -> ]
+2241 86:[ main.root("screen"):remove(mod.node) ] >> 75:[ main.root("screen")\remove(mod.node) ]
+2295 87:[ for _, button in pairs(mod.buttons) do ] >> 76:[ for _, button in pairs(mod.buttons) ]
+2317 88:[ bp.remove(button) ] >> 77:[ bp.remove(button) ]
+2279 89:[ end ] >> 76:[ for _, button in pairs(mod.buttons) ]
+2336 90:[ mod.loaded = false ] >> 78:[ mod.loaded = false ]
+2221 91:[ end ] >> 74:[ mod.unload = () -> ]
+2356 92:[ return mod ] >> 80:[ mod ]
diff --git a/debug/main.lua.X b/debug/main.lua.X
new file mode 100644
index 0000000..173b3d7
--- /dev/null
+++ b/debug/main.lua.X
@@ -0,0 +1,73 @@
+Pos Lua >> Moon
+1 1:[ local mod = ... ] >> 1:[ mod = ... ]
+12 2:[ require("ext") ] >> 3:[ require "ext" ]
+26 3:[ mod.width = 640 ] >> 4:[ mod.width = 640 ]
+42 4:[ mod.height = 480 ] >> 5:[ mod.height = 480 ]
+59 5:[ local win = am.window({ ] >> 6:[ win = am.window{ ]
+83 6:[ title = "Fools Rush In", ] >> 7:[ title: "Fools Rush In" ]
+107 7:[ width = mod.width, ] >> 8:[ width: mod.width ]
+126 8:[ height = mod.height, ] >> 9:[ height: mod.height ]
+151 9:[ clear_color = vec4(85 / 255, 180 / 255, 255 / 255, 255 / 255) ] >> 10:[ clear_color: vec4(85/255,180/255,255/255,255/255) ]
+59 10:[ }) ] >> 6:[ win = am.window{ ]
+191 11:[ mod.root = am.group() ] >> 12:[ mod.root = am.group() ]
+213 12:[ mod.pips = 5 ] >> 13:[ mod.pips = 5 ]
+226 13:[ local char = require("char") ] >> 14:[ char = require "char" ]
+248 14:[ local player = require("player") ] >> 15:[ player = require "player" ]
+297 16:[ LocalPlayer = player.LocalPlayer ] >> 16:[ import LocalPlayer from player ]
+305 17:[ local util = require("util") ] >> 17:[ util = require "util" ]
+327 18:[ local world = require("world") ] >> 18:[ world = require "world" ]
+368 20:[ World = world.World ] >> 19:[ import World from world ]
+375 21:[ local main_menu = require("main_menu") ] >> 20:[ main_menu = require "main_menu" ]
+407 22:[ local rng_seed = am.eval_js("Math.random()") ] >> 21:[ rng_seed = am.eval_js("Math.random()") ]
+446 23:[ math.randomseed(rng_seed * 1000) ] >> 22:[ math.randomseed(rng_seed*1000) ]
+478 24:[ mod.screenpos = util.Vec2() ] >> 24:[ mod.screenpos = util.Vec2! ]
+506 25:[ mod.reload = function() ] >> 26:[ mod.reload = () -> ]
+561 26:[ mod.screen = am.group():tag("screen") ^ am.translate(0, 0) ] >> 28:[ mod.screen = am.group!\tag("screen")^ am.translate(0,0)--Things that should draw on top of everything else, like a hud ]
+681 27:[ mod.world_close = am.group():tag("close") ^ am.translate(0, 0) ] >> 29:[ mod.world_close = am.group!\tag("close")^ am.translate(0,0)--Things that should draw "close to the camera", fast-parallax background ]
+815 28:[ mod.world = am.translate(0, 0) ^ am.group({ ] >> 30:[ mod.world = am.translate(0,0) ^ am.group({ ]
+860 29:[ am.group():tag("world_front"), ] >> 31:[ am.group!\tag("world_front"), -- things in front of players ]
+922 30:[ am.group():tag("world_characters"), ] >> 32:[ am.group!\tag("world_characters"), -- players ]
+970 31:[ am.group():tag("world_behind") ] >> 33:[ am.group!\tag("world_behind"), -- things behind players ]
+815 32:[ }):tag("world") ] >> 30:[ mod.world = am.translate(0,0) ^ am.group({ ]
+1082 33:[ mod.world_far = am.group():tag("far") ^ am.translate(0, 0) ] >> 35:[ mod.world_far = am.group!\tag("far")^ am.translate(0,0) --Things that move slowly in the background ]
+1183 34:[ mod.background = am.group():tag("bg") ] >> 36:[ mod.background = am.group!\tag("bg")--The background does not move, draws behind everything. ]
+1277 35:[ mod.root = am.group({ ] >> 37:[ mod.root = am.group{ ]
+1300 36:[ mod.background, ] >> 38:[ mod.background, ]
+1318 37:[ mod.world_far, ] >> 39:[ mod.world_far, ]
+1335 38:[ mod.world, ] >> 40:[ mod.world, ]
+1348 39:[ mod.world_close, ] >> 41:[ mod.world_close, ]
+1367 40:[ mod.screen ] >> 42:[ mod.screen, ]
+1277 41:[ }) ] >> 37:[ mod.root = am.group{ ]
+1383 42:[ win.scene = mod.root ] >> 44:[ win.scene = mod.root ]
+1405 43:[ mod.action_queue = { ] >> 45:[ mod.action_queue = { ]
+1429 45:[ main_menu.load, ] >> 46:[ {main_menu.load, {}} ]
+1405 48:[ } ] >> 45:[ mod.action_queue = { ]
+1455 49:[ mod.root:action(coroutine.create(function() ] >> 49:[ mod.root\action(coroutine.create(() -> ]
+1501 50:[ while true do ] >> 50:[ while true ]
+1512 51:[ if mod.server then ] >> 51:[ if mod.server ]
+1528 52:[ mod.server:update() ] >> 52:[ mod.server\update! ]
+1510 53:[ end ] >> 51:[ if mod.server ]
+1550 54:[ coroutine.yield() ] >> 53:[ coroutine.yield! ]
+1496 55:[ end ] >> 50:[ while true ]
+1455 56:[ end)) ] >> 49:[ mod.root\action(coroutine.create(() -> ]
+1572 57:[ return mod.root:action(coroutine.create(function() ] >> 55:[ mod.root\action(coroutine.create(() -> ]
+1618 58:[ while true do ] >> 56:[ while true ]
+1627 59:[ local start_time = am.current_time() ] >> 57:[ start_time = am.current_time! ]
+1660 60:[ mod.world.x = -mod.screenpos.x ] >> 58:[ mod.world.x = -mod.screenpos.x ]
+1694 61:[ mod.world.y = -mod.screenpos.y ] >> 59:[ mod.world.y = -mod.screenpos.y ]
+1730 62:[ if #mod.action_queue > 0 then ] >> 60:[ if #mod.action_queue > 0 ]
+1757 63:[ local action = table.remove(mod.action_queue) ] >> 61:[ action = table.remove(mod.action_queue) ]
+1801 64:[ local f, args = action[1], action[2] ] >> 62:[ f, args = action[1], action[2] ]
+1836 65:[ f(unpack(args)) ] >> 63:[ f(unpack(args)) ]
+1728 66:[ end ] >> 60:[ if #mod.action_queue > 0 ]
+1857 67:[ if mod.world then ] >> 64:[ if mod.world ]
+1872 68:[ mod.world:update() ] >> 65:[ mod.world\update! ]
+1855 69:[ end ] >> 64:[ if mod.world ]
+1893 70:[ local end_time = am.current_time() ] >> 66:[ end_time = am.current_time! ]
+1925 71:[ coroutine.yield() ] >> 68:[ coroutine.yield! ]
+1613 72:[ end ] >> 56:[ while true ]
+1572 73:[ end)) ] >> 55:[ mod.root\action(coroutine.create(() -> ]
+506 74:[ end ] >> 26:[ mod.reload = () -> ]
+1946 75:[ mod.reload() ] >> 70:[ mod.reload! ]
+1958 76:[ mod.win = win ] >> 71:[ mod.win = win ]
+1972 77:[ return mod ] >> 72:[ mod ]
diff --git a/debug/main_menu.lua.X b/debug/main_menu.lua.X
new file mode 100644
index 0000000..f9a715b
--- /dev/null
+++ b/debug/main_menu.lua.X
@@ -0,0 +1,79 @@
+Pos Lua >> Moon
+2 1:[ local main = require("main") ] >> 2:[ main = require "main" ]
+24 2:[ local bp = require("broadphase") ] >> 3:[ bp = require "broadphase" ]
+50 3:[ local color = require("color") ] >> 4:[ color = require "color" ]
+74 4:[ local ui = require("ui") ] >> 5:[ ui = require "ui" ]
+93 5:[ local world = require("world") ] >> 7:[ world = require "world" --delete when done ]
+154 7:[ Server = world.Server ] >> 8:[ import Server from world ]
+178 9:[ World = world.World ] >> 9:[ import World from world ]
+185 10:[ local mod = ... ] >> 10:[ mod = ... ]
+208 11:[ local nwidth = 6 ] >> 13:[ nwidth = 6 ]
+219 12:[ local section_width = 32 ] >> 14:[ section_width = 32 ]
+238 13:[ local start_x = (-32) * (nwidth / 2) ] >> 15:[ start_x = (-32)*(nwidth/2) ]
+265 14:[ mod.node = am.group() ] >> 16:[ mod.node = am.group! ]
+286 15:[ mod.node:append(am.sprite("data/main_bg.png")) ] >> 17:[ mod.node\append(am.sprite("data/main_bg.png")) ]
+333 16:[ local start_y = 0 ] >> 18:[ start_y = 0 ]
+345 17:[ local padding = 32 ] >> 19:[ padding = 32 ]
+358 18:[ local buttonspecs = { ] >> 20:[ buttonspecs = { ]
+399 20:[ y_off = start_y, ] >> 22:[ y_off: start_y, ]
+416 21:[ text = "Create Troupe" ] >> 23:[ text: "Create Troupe" ]
+460 24:[ y_off = start_y + 64 + padding, ] >> 26:[ y_off: start_y + 64 + padding, ]
+492 25:[ text = "Join Troupe" ] >> 27:[ text: "Join Troupe" ]
+358 27:[ } ] >> 20:[ buttonspecs = { ]
+514 28:[ mod.load = function() ] >> 32:[ mod.load = () -> ]
+532 29:[ print("creating main menu") ] >> 33:[ print("creating main menu") ]
+561 30:[ main.root("screen"):append(mod.node) ] >> 34:[ main.root("screen")\append(mod.node) ]
+622 31:[ for name, button_tbl in pairs(buttonspecs) do ] >> 35:[ for name, button_tbl in pairs buttonspecs ]
+643 32:[ local button_extra = ui.create_big_button(mod.node, 6, start_x, button_tbl.y_off) ] >> 36:[ button_extra = ui.create_big_button(mod.node,6,start_x,button_tbl.y_off) ]
+718 33:[ button_extra.node:append(am.translate(start_x + ((nwidth * section_width) / 2), button_tbl.y_off - 32) ^ am.text(button_tbl.text, color.fg)) ] >> 37:[ button_extra.node\append(am.translate(start_x+((nwidth*section_width)/2),button_tbl.y_off - 32)^ am.text(button_tbl.text,color.fg)) ]
+852 34:[ mod[name] = button_extra ] >> 38:[ mod[name] = button_extra ]
+599 35:[ end ] >> 35:[ for name, button_tbl in pairs buttonspecs ]
+878 36:[ local touch_indicator = am.group() ] >> 39:[ touch_indicator = am.group! ]
+907 37:[ local touch_cursor = am.sprite("data/cursor.png", vec4(1, 1, 1, 1), "left", "top") ] >> 40:[ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") ]
+979 38:[ local touch_loc = am.translate(0, 0) ^ touch_cursor ] >> 41:[ touch_loc = am.translate(0,0)^ touch_cursor ]
+1024 39:[ touch_indicator:append(touch_loc) ] >> 42:[ touch_indicator\append(touch_loc) ]
+1059 40:[ mod.node:append(am.translate(0, -150) ^ am.scale(3) ^ am.text("Fools Rush In")) ] >> 43:[ mod.node\append(am.translate(0,-150)^ am.scale(3)^ am.text("Fools Rush In")) ]
+1137 41:[ mod.node:action("click_interpreter", coroutine.create(function() ] >> 44:[ mod.node\action("click_interpreter",coroutine.create(()-> ]
+1202 42:[ while true do ] >> 45:[ while true ]
+1211 43:[ print("Main menu loop") ] >> 46:[ print("Main menu loop") ]
+1240 44:[ if #main.win:active_touches() > 0 then ] >> 47:[ if #main.win\active_touches! > 0 ]
+1275 45:[ local touch = main.win:touch_position(1) ] >> 48:[ touch = main.win\touch_position(1) ]
+1314 46:[ touch_cursor.color = vec4(1, 1, 1, 1) ] >> 49:[ touch_cursor.color = vec4(1,1,1,1) ]
+1353 47:[ touch_loc.x = touch.x ] >> 50:[ touch_loc.x = touch.x ]
+1379 48:[ touch_loc.y = touch.y ] >> 51:[ touch_loc.y = touch.y ]
+1405 49:[ local col_but = bp.check(touch.x, touch.y + 64) ] >> 52:[ col_but = bp.check(touch.x, touch.y+64) ]
+1451 50:[ if #col_but > 0 then ] >> 53:[ if #col_but > 0 ]
+1470 51:[ touch_cursor.color = vec4(0.5, 0.5, 0.5, 0.5) ] >> 54:[ touch_cursor.color = vec4(0.5,0.5,0.5,0.5) ]
+1449 52:[ end ] >> 53:[ if #col_but > 0 ]
+1519 53:[ if #col_but > 0 then ] >> 55:[ if #col_but > 0 ]
+1538 54:[ print("Collided with button:", col_but) ] >> 56:[ print("Collided with button:",col_but) ]
+1582 55:[ print("mod.create prty was:", mod.create_party) ] >> 57:[ print("mod.create prty was:",mod.create_party) ]
+1636 56:[ if col_but[1] == mod.create_party then ] >> 58:[ if col_but[1] == mod.create_party ]
+1674 57:[ print("Creating party") ] >> 59:[ print("Creating party") ]
+1704 58:[ local create_party = require("create_party_menu") ] >> 60:[ create_party = require "create_party_menu" ]
+1753 59:[ table.insert(main.action_queue, { ] >> 61:[ table.insert(main.action_queue,{create_party.load, {}}) ]
+1785 60:[ create_party.load, ] >> 61:[ table.insert(main.action_queue,{create_party.load, {}}) ]
+1753 62:[ }) ] >> 61:[ table.insert(main.action_queue,{create_party.load, {}}) ]
+1815 63:[ mod.unload() ] >> 62:[ mod.unload! ]
+1833 64:[ coroutine.yield() ] >> 63:[ coroutine.yield! ]
+1861 65:[ elseif col_but[1] == mod.join_party then ] >> 64:[ elseif col_but[1] == mod.join_party ]
+1897 66:[ print("Joining party") ] >> 65:[ print("Joining party") ]
+1926 67:[ local join_party = require("join_party_menu") ] >> 66:[ join_party = require "join_party_menu" ]
+1971 68:[ table.insert(main.action_queue, { ] >> 67:[ table.insert(main.action_queue,{join_party.load, {}}) ]
+2003 69:[ join_party.load, ] >> 67:[ table.insert(main.action_queue,{join_party.load, {}}) ]
+1971 71:[ }) ] >> 67:[ table.insert(main.action_queue,{join_party.load, {}}) ]
+2031 72:[ mod.unload() ] >> 68:[ mod.unload! ]
+2049 73:[ coroutine.yield() ] >> 69:[ coroutine.yield! ]
+1517 75:[ end ] >> 55:[ if #col_but > 0 ]
+2078 77:[ touch_cursor.color = vec4(0, 0, 0, 0) ] >> 71:[ touch_cursor.color = vec4(0,0,0,0) ]
+2116 79:[ coroutine.yield() ] >> 72:[ coroutine.yield! ]
+1197 80:[ end ] >> 45:[ while true ]
+1137 81:[ end)) ] >> 44:[ mod.node\action("click_interpreter",coroutine.create(()-> ]
+2138 82:[ return mod.node:append(touch_indicator) ] >> 74:[ mod.node\append(touch_indicator) ]
+514 83:[ end ] >> 32:[ mod.load = () -> ]
+2172 84:[ mod.unload = function() ] >> 76:[ mod.unload = () -> ]
+2192 85:[ main.root("screen"):remove(mod.node) ] >> 77:[ main.root("screen")\remove(mod.node) ]
+2230 86:[ bp.remove(mod.create_party) ] >> 78:[ bp.remove(mod.create_party) ]
+2259 87:[ return bp.remove(mod.join_party) ] >> 79:[ bp.remove(mod.join_party) ]
+2172 88:[ end ] >> 76:[ mod.unload = () -> ]
+2286 89:[ return mod ] >> 81:[ mod ]
diff --git a/debug/party.lua.X b/debug/party.lua.X
new file mode 100644
index 0000000..23cf263
--- /dev/null
+++ b/debug/party.lua.X
@@ -0,0 +1,46 @@
+Pos Lua >> Moon
+81 1:[ local room = require("room") ] >> 3:[ room = require("room") ]
+104 2:[ local main = require("main") ] >> 4:[ main = require("main") ]
+127 3:[ local char = require("char") ] >> 5:[ char = require("char") ]
+171 5:[ Character = char.Character ] >> 6:[ import Character from char ]
+198 7:[ LobbyRoom = room.LobbyRoom ] >> 7:[ import LobbyRoom from room ]
+204 8:[ local mod = ... ] >> 8:[ mod = ... ]
+367 14:[ assert(uname, "cannot find a nil party member") ] >> 18:[ assert(uname, "cannot find a nil party member") ]
+417 15:[ assert(type(uname) == "string", "Member should be called with a peer id") ] >> 19:[ assert(type(uname) == "string", "Member should be called with a peer id") ]
+493 16:[ return self.members[uname] ] >> 20:[ @members[uname] ]
+528 19:[ local i = 0 ] >> 22:[ i = 0 ]
+546 20:[ for k, v in pairs(self.members) do ] >> 23:[ for k,v in pairs(@members) ]
+566 21:[ i = i + 1 ] >> 24:[ i += 1 ]
+536 22:[ end ] >> 23:[ for k,v in pairs(@members) ]
+575 23:[ return i ] >> 25:[ i ]
+604 26:[ assert(member.uname, "cannot add a nil party member") ] >> 27:[ assert(member.uname, "cannot add a nil party member") ]
+660 27:[ assert(type(member.uname) == "string", "Member should be called with a peer id") ] >> 28:[ assert(type(member.uname) == "string", "Member should be called with a peer id") ]
+743 28:[ self.members[member.uname] = member ] >> 29:[ @members[member.uname] = member ]
+777 29:[ member.party = self ] >> 30:[ member.party = @ ]
+796 30:[ return self.node:append(member.node) ] >> 31:[ @node\append(member.node) ]
+852 33:[ self.members[member.uname] = nil ] >> 33:[ @members[member.uname] = nil ]
+883 34:[ member.party = nil ] >> 34:[ member.party = nil ]
+904 35:[ return self.node:remove(member.node) ] >> 35:[ @node\remove(member.node) ]
+954 38:[ self.room = nroom ] >> 37:[ @room = nroom ]
+985 39:[ for id, char in pairs(self.members) do ] >> 38:[ for id, char in pairs(@members) ]
+1005 40:[ char:enter_room(nroom) ] >> 39:[ char\enter_room(nroom) ]
+970 41:[ end ] >> 38:[ for id, char in pairs(@members) ]
+1049 44:[ local members = { } ] >> 42:[ members = {} ]
+1080 45:[ for _, member in pairs(self.members) do ] >> 43:[ for _, member in pairs(@members) ]
+1100 46:[ members[member.uname] = member:serialize() ] >> 44:[ members[member.uname] = member\serialize! ]
+1064 47:[ end ] >> 43:[ for _, member in pairs(@members) ]
+1144 48:[ return am.to_json(members) ] >> 45:[ am.to_json(members) ]
+1191 51:[ local rp = Party() ] >> 48:[ rp = Party! ]
+1205 52:[ local tbl = am.parse_json(data) ] >> 49:[ tbl = am.parse_json(data) ]
+1254 53:[ for uname, payload in pairs(tbl) do ] >> 50:[ for uname, payload in pairs(tbl) ]
+1269 54:[ local tchar = Character.deserialize(payload) ] >> 51:[ tchar = Character.deserialize(payload) ]
+1311 55:[ rp:add_member(tchar) ] >> 52:[ rp\add_member(tchar) ]
+1233 56:[ end ] >> 50:[ for uname, payload in pairs(tbl) ]
+1334 57:[ return rp ] >> 53:[ rp ]
+238 63:[ self.members = { } ] >> 12:[ @members = {} ]
+254 64:[ self.rnode = am.group() ] >> 13:[ @rnode = am.group! ]
+275 65:[ self.node = am.translate(0, 0) ] >> 14:[ @node = am.translate(0,0) ]
+303 66:[ self.rnode:append(self.node) ] >> 15:[ @rnode\append(@node) ]
+326 67:[ self.room = LobbyRoom() ] >> 16:[ @room = LobbyRoom! ]
+1340 82:[ mod["Party"] = Party ] >> 57:[ mod["Party"] = Party ]
+1361 83:[ return mod ] >> 58:[ mod ]
diff --git a/debug/pixelize.lua.X b/debug/pixelize.lua.X
new file mode 100644
index 0000000..a9cea53
--- /dev/null
+++ b/debug/pixelize.lua.X
@@ -0,0 +1,2 @@
+Pos Lua >> Moon
+
diff --git a/debug/player.lua.X b/debug/player.lua.X
new file mode 100644
index 0000000..b2e4078
--- /dev/null
+++ b/debug/player.lua.X
@@ -0,0 +1,38 @@
+Pos Lua >> Moon
+1 1:[ local char = require("char") ] >> 1:[ char = require "char" ]
+23 2:[ local util = require("util") ] >> 2:[ util = require "util" ]
+45 3:[ local main = require("main") ] >> 3:[ main = require "main" ]
+67 4:[ local bp = require("broadphase") ] >> 4:[ bp = require "broadphase" ]
+113 6:[ KeyInput = char.KeyInput ] >> 5:[ import KeyInput from char ]
+135 8:[ Vec2 = util.Vec2 ] >> 6:[ import Vec2 from util ]
+142 9:[ local mod = ... ] >> 8:[ mod = ... ]
+227 13:[ local _parent_0 = char.Character ] >> 10:[ class RemotePlayer extends char.Character ]
+279 19:[ return _class_0.__parent.__init(self, uname, data, charclass) ] >> 12:[ super(uname, data, charclass) ]
+335 51:[ local _parent_0 = char.Character ] >> 14:[ class LocalPlayer extends char.Character ]
+435 54:[ _class_0.__parent.__base.draw(self, ...) ] >> 19:[ super(...) ]
+448 55:[ local healthnode = am.group() ^ { ] >> 20:[ healthnode = am.group() ^ {am.translate(-main.width/2,main.height/2) ^ am.text(string.format("Health:%d", @health), vec4(255,255,255,255), "left","top")\tag "health text"} ]
+475 56:[ am.translate(-main.width / 2, main.height / 2) ^ am.text(string.format("Health:%d", self.health), vec4(255, 255, 255, 255), "left", "top"):tag("health text") ] >> 20:[ healthnode = am.group() ^ {am.translate(-main.width/2,main.height/2) ^ am.text(string.format("Health:%d", @health), vec4(255,255,255,255), "left","top")\tag "health text"} ]
+448 57:[ } ] >> 20:[ healthnode = am.group() ^ {am.translate(-main.width/2,main.height/2) ^ am.text(string.format("Health:%d", @health), vec4(255,255,255,255), "left","top")\tag "health text"} ]
+622 58:[ return self.node:append(healthnode) ] >> 21:[ @node\append(healthnode) ]
+684 61:[ if am.current_time() - self.last_dammage > 1 then ] >> 24:[ if am.current_time! - @last_dammage > 1 ]
+725 62:[ self.health = self.health - ammt ] >> 25:[ @health -= ammt ]
+744 63:[ self.last_dammage = am.current_time(); ] >> 26:[ @last_dammage = am.current_time! ]
+780 64:[ (self.node)("health text").text = string.format("Health:%d", self.health) ] >> 27:[ (@node)("health text").text = string.format("Health:%d",@health) ]
+850 65:[ if self.health == 0 then ] >> 28:[ if @health == 0 ]
+868 66:[ return self:die() ] >> 29:[ @\die! ]
+848 67:[ end ] >> 28:[ if @health == 0 ]
+682 68:[ end ] >> 24:[ if am.current_time! - @last_dammage > 1 ]
+900 71:[ if other.canfall then ] >> 32:[ if other.canfall ]
+918 72:[ return "cross" ] >> 33:[ return "cross" ]
+898 73:[ end ] >> 32:[ if other.canfall ]
+937 74:[ if other.hurtbox then ] >> 34:[ if other.hurtbox ]
+957 75:[ if other.owner ~= self then ] >> 35:[ if other.owner ~= @ ]
+979 76:[ self:take_damage(other, 1) ] >> 36:[ @take_damage(other,1) ]
+955 77:[ end ] >> 35:[ if other.owner ~= @ ]
+1004 78:[ return "cross" ] >> 37:[ return "cross" ]
+1029 80:[ return "slide" ] >> 39:[ return "slide" ]
+1155 84:[ return _class_0.__parent.__base.die(self, ...) ] >> 43:[ super(...) ]
+386 91:[ return _class_0.__parent.__init(self, uname, data, charclass) ] >> 16:[ super(uname, data, charclass) ]
+1169 120:[ mod["RemotePlayer"] = RemotePlayer ] >> 47:[ mod["RemotePlayer"] = RemotePlayer ]
+1204 121:[ mod["LocalPlayer"] = LocalPlayer ] >> 48:[ mod["LocalPlayer"] = LocalPlayer ]
+1237 122:[ return mod ] >> 49:[ mod ]
diff --git a/debug/room.lua.X b/debug/room.lua.X
new file mode 100644
index 0000000..00ec1e5
--- /dev/null
+++ b/debug/room.lua.X
@@ -0,0 +1,89 @@
+Pos Lua >> Moon
+112 1:[ local main = require("main") ] >> 3:[ main = require "main" ]
+134 2:[ local char = require("char") ] >> 4:[ char = require "char" ]
+177 4:[ Character = char.Character ] >> 5:[ import Character from char ]
+183 5:[ local mod = ... ] >> 6:[ mod = ... ]
+1188 11:[ return table.insert(self.parties, party) ] >> 44:[ table.insert(@parties,party) ]
+1241 14:[ return self.data.locations[n] ] >> 47:[ return @data.locations[n] ]
+1290 17:[ return mod.SimpleRoom() ] >> 50:[ mod.SimpleRoom! ]
+1496 20:[ local ser_tbl = { ] >> 58:[ ser_tbl = { ]
+1516 21:[ name = self.__class.__name, ] >> 59:[ name:@@__name, ]
+1534 22:[ data = self.data ] >> 60:[ data:@data ]
+1496 23:[ } ] >> 58:[ ser_tbl = { ]
+1613 24:[ for position, ptbl in pairs(ser_tbl.data.locations) do ] >> 63:[ for position, ptbl in pairs(ser_tbl.data.locations) ]
+1668 25:[ for pid, character in pairs(ptbl) do ] >> 64:[ for pid, character in pairs(ptbl) ]
+1687 26:[ if character.uname then ] >> 65:[ if character.uname ]
+1709 27:[ ptbl[pid] = { ] >> 66:[ ptbl[pid] = {uname:character.uname} ]
+1728 28:[ uname = character.uname ] >> 66:[ ptbl[pid] = {uname:character.uname} ]
+1709 29:[ } ] >> 66:[ ptbl[pid] = {uname:character.uname} ]
+1759 31:[ ptbl[pid] = character:serialize() ] >> 68:[ ptbl[pid] = character\serialize! ]
+1647 33:[ end ] >> 64:[ for pid, character in pairs(ptbl) ]
+1592 34:[ end ] >> 63:[ for position, ptbl in pairs(ser_tbl.data.locations) ]
+1794 35:[ local ret = am.to_json(ser_tbl) ] >> 69:[ ret = am.to_json(ser_tbl) ]
+1822 36:[ return ret ] >> 70:[ ret ]
+1854 39:[ local tbl = am.parse_json(data) ] >> 73:[ tbl = am.parse_json(data) ]
+1882 40:[ local typ = mod.Room.children[tbl.name] ] >> 74:[ typ = mod.Room.children[tbl.name] ]
+1918 41:[ return typ(tbl.data) ] >> 75:[ typ(tbl.data) ]
+1954 44:[ return string.format("<%s>", self.__class.__name) ] >> 78:[ return string.format("<%s>",@@__name) ]
+778 50:[ self.parties = { } ] >> 30:[ @parties = {} ]
+794 51:[ self.actions_taken = { } ] >> 31:[ @actions_taken = {} ]
+816 52:[ self.special = { } ] >> 32:[ @special = {} ]
+832 53:[ self.node = am.group() ] >> 33:[ @node = am.group! ]
+852 54:[ self.data = data or { ] >> 34:[ @data = data or {locations: {{},{},{},{},{},{},{},{}}} ]
+852 65:[ } ] >> 34:[ @data = data or {locations: {{},{},{},{},{},{},{},{}}} ]
+930 66:[ for position, ptbl in pairs(self.data.locations) do ] >> 35:[ for position, ptbl in pairs(@data.locations) ]
+978 67:[ for pid, character in pairs(ptbl) do ] >> 36:[ for pid, character in pairs(ptbl) ]
+997 68:[ if character.uname then ] >> 37:[ if character.uname ]
+1043 69:[ ptbl[pid] = main.world.player_party:member(character.uname) ] >> 39:[ ptbl[pid] = main.world.player_party\member(character.uname) ]
+1117 71:[ ptbl[pid] = Character.deserialize(character) ] >> 41:[ ptbl[pid] = Character.deserialize(character) ]
+957 73:[ end ] >> 36:[ for pid, character in pairs(ptbl) ]
+909 74:[ end ] >> 35:[ for position, ptbl in pairs(@data.locations) ]
+745 88:[ self.children = { } ] >> 28:[ @children = {} ]
+1336 90:[ assert(child.distribute_party, "Rooms must be able to distribute parties") ] >> 53:[ assert(child.distribute_party, "Rooms must be able to distribute parties") ]
+1413 91:[ self.__class.children[child.__name] = child ] >> 54:[ @@.children[child.__name] = child ]
+1449 92:[ mod[child.__name] = child ] >> 55:[ mod[child.__name] = child ]
+2016 99:[ local _parent_0 = Room ] >> 80:[ class LobbyRoom extends Room ]
+2129 102:[ return main.root("bg"):append(self.node) ] >> 86:[ main.root("bg")\append(@node) ]
+2175 105:[ return main.root("bg"):remove(self.node) ] >> 88:[ main.root("bg")\remove(@node) ]
+2278 111:[ assert(position, "Position my not be nil") ] >> 94:[ assert(position, "Position my not be nil") ]
+2323 112:[ return ((main.width / 9) * position) ] >> 95:[ ((main.width/9) * position) ]
+2390 115:[ error("lobbyroom player_position_of") ] >> 98:[ error("lobbyroom player_position_of") ]
+2430 116:[ assert(position, "Position may not be nil") ] >> 99:[ assert(position, "Position may not be nil") ]
+2476 117:[ return -((main.width / 9) * position) ] >> 100:[ -((main.width/9) * position) ]
+2039 124:[ _class_0.__parent.__init(self, ...) ] >> 82:[ super(...) ]
+2052 125:[ self.floor_y = -208 ] >> 83:[ @floor_y = -208 ]
+2070 126:[ return self.node:append(am.sprite("data/lobby_bg.png")) ] >> 84:[ @node\append(am.sprite("data/lobby_bg.png")) ]
+2528 158:[ local _parent_0 = Room ] >> 102:[ class CampRoom extends Room ]
+2683 161:[ return main.root("bg"):append(self.node) ] >> 109:[ main.root("bg")\append(@node) ]
+2730 164:[ return main.root("bg"):remove(self.node) ] >> 112:[ main.root("bg")\remove(@node) ]
+2815 167:[ for _, player in pairs(playerparty) do ] >> 115:[ for _,player in pairs(playerparty) ]
+2838 168:[ table.insert(ret[player.position], player) ] >> 116:[ table.insert(ret[player.position],player) ]
+2800 169:[ end ] >> 115:[ for _,player in pairs(playerparty) ]
+2551 176:[ _class_0.__parent.__init(self, ...) ] >> 104:[ super(...) ]
+2564 177:[ self.floor_y = -207 ] >> 105:[ @floor_y = -207 ]
+2582 178:[ self.node:append(am.sprite("data/camp.png")) ] >> 106:[ @node\append(am.sprite("data/camp.png")) ]
+2625 179:[ self.data.locations = { ] >> 107:[ @data.locations = {{},{},{},{},{},{},{},{}} ]
+2625 188:[ } ] >> 107:[ @data.locations = {{},{},{},{},{},{},{},{}} ]
+2905 220:[ local _parent_0 = Room ] >> 118:[ class SimpleRoom extends Room ]
+3014 223:[ return main.root("bg"):append(self.node) ] >> 124:[ main.root("bg")\append(@node) ]
+3061 226:[ return main.root("bg"):remove(self.node) ] >> 127:[ main.root("bg")\remove(@node) ]
+3156 229:[ for _, player in pairs(playerparty.members) do ] >> 130:[ for _,player in pairs(playerparty.members) ]
+3187 230:[ local loc = self.data.locations[5 - player.data.position] ] >> 131:[ loc = @data.locations[5-player.data.position] ]
+3236 231:[ table.insert(loc, player) ] >> 132:[ table.insert(loc,player) ]
+3264 232:[ player:set_location(loc) ] >> 133:[ player\set_location(loc) ]
+3141 233:[ end ] >> 130:[ for _,player in pairs(playerparty.members) ]
+3305 234:[ for _, enemy in pairs(enemyparty.members) do ] >> 134:[ for _,enemy in pairs(enemyparty.members) ]
+3335 235:[ local loc = self.data.locations[enemy.data.position + 4] ] >> 135:[ loc = @data.locations[enemy.data.position+4] ]
+3383 236:[ table.insert(loc, enemy) ] >> 136:[ table.insert(loc,enemy) ]
+3410 237:[ enemy:set_location(loc) ] >> 137:[ enemy\set_location(loc) ]
+3291 238:[ end ] >> 134:[ for _,enemy in pairs(enemyparty.members) ]
+3473 241:[ assert(position, "Position may not be nil") ] >> 140:[ assert(position, "Position may not be nil") ]
+3519 242:[ return -((main.width / 9) * position) ] >> 141:[ -((main.width/9) * position) ]
+3585 245:[ assert(position, "Position my not be nil") ] >> 144:[ assert(position, "Position my not be nil") ]
+3630 246:[ return ((main.width / 9) * position) ] >> 145:[ ((main.width/9) * position) ]
+2928 253:[ _class_0.__parent.__init(self, ...) ] >> 120:[ super(...) ]
+2941 254:[ self.floor_y = -207 ] >> 121:[ @floor_y = -207 ]
+2959 255:[ return self.node:append(am.sprite("data/room.png")) ] >> 122:[ @node\append(am.sprite("data/room.png")) ]
+3661 284:[ mod.Room = Room ] >> 149:[ mod.Room = Room ]
+3677 285:[ mod.SimpleRoom = SimpleRoom ] >> 150:[ mod.SimpleRoom = SimpleRoom ]
+3706 286:[ return mod ] >> 152:[ mod ]
diff --git a/debug/ui.lua.X b/debug/ui.lua.X
new file mode 100644
index 0000000..a4c941d
--- /dev/null
+++ b/debug/ui.lua.X
@@ -0,0 +1,247 @@
+Pos Lua >> Moon
+24 1:[ local bp = require("broadphase") ] >> 2:[ bp = require "broadphase" ]
+50 2:[ local main = require("main") ] >> 3:[ main = require "main" ]
+72 3:[ local action = require("action") ] >> 4:[ action = require "action" ]
+98 4:[ local color = require("color") ] >> 5:[ color = require "color" ]
+122 5:[ local reg = require("ability_reg") ] >> 6:[ reg = require "ability_reg" ]
+150 6:[ local char = require("char") ] >> 7:[ char = require "char" ]
+189 8:[ Enemy = char.Enemy ] >> 8:[ import Enemy from char ]
+195 9:[ local mod = ... ] >> 9:[ mod = ... ]
+206 10:[ mod.big_frame_top_left = { ] >> 11:[ mod.big_frame_top_left = { ]
+242 11:[ texture = "ui.png", ] >> 12:[ texture: "ui.png", ]
+257 12:[ s1 = 0, ] >> 13:[ s1: 0 ]
+264 13:[ t1 = 0, ] >> 14:[ t1: 0 ]
+271 14:[ s2 = 0, ] >> 15:[ s2: 0 ]
+278 15:[ t2 = 0 ] >> 16:[ t2: 0 ]
+206 16:[ } ] >> 11:[ mod.big_frame_top_left = { ]
+375 17:[ mod.create_big_button = function(node, segments, x, y) ] >> 20:[ mod.create_big_button = (node, segments, x, y) -> ]
+439 18:[ local section_width = 32 ] >> 22:[ section_width = 32 ]
+460 19:[ local ret = { } ] >> 24:[ ret = {} ]
+470 20:[ local trans = am.translate(x, y) ] >> 25:[ trans = am.translate(x,y) ]
+497 21:[ local button = am.group() ] >> 26:[ button = am.group! ]
+517 22:[ button:append(trans ^ am.sprite("data/big_frame_top_left.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 27:[ button\append(trans^ am.sprite("data/big_frame_top_left.png",vec4(1,1,1,1),"left","top")) ]
+615 23:[ for i = 1, segments - 2 do ] >> 28:[ for i = 1,segments - 2 ]
+633 24:[ button:append(am.translate(((-32) * (segments / 2)) + 32 * i, y) ^ am.sprite("data/big_frame_top_mid.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 29:[ button\append(am.translate(((-32)*(segments/2)) + 32*i,y)^ am.sprite("data/big_frame_top_mid.png",vec4(1,1,1,1),"left","top")) ]
+608 25:[ end ] >> 28:[ for i = 1,segments - 2 ]
+761 26:[ button:append(am.translate((32 * ((segments / 2) - 1)), y) ^ am.sprite("data/big_frame_top_right.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 30:[ button\append(am.translate((32*((segments/2)-1)),y)^ am.sprite("data/big_frame_top_right.png",vec4(1,1,1,1),"left","top")) ]
+885 27:[ ret["node"] = button ] >> 31:[ ret["node"] = button ]
+907 28:[ bp.add(ret, x, y, segments * 32, 64) ] >> 32:[ bp.add(ret,x,y,segments*32,64) ]
+939 29:[ node:append(button) ] >> 33:[ node\append(button) ]
+960 30:[ return ret ] >> 34:[ ret ]
+375 31:[ end ] >> 20:[ mod.create_big_button = (node, segments, x, y) -> ]
+966 32:[ mod.create_small_button = function(node, segments, x, y) ] >> 37:[ mod.create_small_button = (node, segments, x, y) -> ]
+1019 33:[ assert(node ~= nil, "node was nil") ] >> 38:[ assert(node ~= nil, "node was nil") ]
+1056 34:[ assert(type(segments) == "number", "segments must be a numbeR") ] >> 39:[ assert(type(segments) == "number","segments must be a numbeR") ]
+1120 35:[ assert(segments > 1, "segments must be at least 2") ] >> 40:[ assert(segments > 1, "segments must be at least 2") ]
+1186 36:[ local section_width = 18 ] >> 42:[ section_width = 18 ]
+1206 37:[ local section_height = 40 ] >> 43:[ section_height = 40 ]
+1228 38:[ local ret = { } ] >> 45:[ ret = {} ]
+1238 39:[ local trans = am.translate(x, y) ] >> 46:[ trans = am.translate(x,y) ]
+1265 40:[ local button = am.group() ] >> 47:[ button = am.group! ]
+1285 41:[ button:append(trans ^ am.sprite("data/small_frame_left.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 48:[ button\append(trans^ am.sprite("data/small_frame_left.png",vec4(1,1,1,1),"left","top")) ]
+1381 42:[ for i = 1, segments - 2 do ] >> 49:[ for i = 1,segments - 2 ]
+1399 43:[ button:append(am.translate(x + (section_width * i), y) ^ am.sprite("data/small_frame_mid.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 50:[ button\append(am.translate(x + (section_width*i),y)^ am.sprite("data/small_frame_mid.png",vec4(1,1,1,1),"left","top")) ]
+1374 44:[ end ] >> 49:[ for i = 1,segments - 2 ]
+1519 45:[ button:append(am.translate(x + (section_width * (segments - 1)), y) ^ am.sprite("data/small_frame_right.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 51:[ button\append(am.translate(x+(section_width*(segments - 1)),y)^ am.sprite("data/small_frame_right.png",vec4(1,1,1,1),"left","top")) ]
+1652 46:[ ret["node"] = button ] >> 52:[ ret["node"] = button ]
+1674 47:[ bp.add(ret, x, y, segments * section_width, section_height) ] >> 53:[ bp.add(ret,x,y,segments*section_width,section_height) ]
+1729 48:[ node:append(button) ] >> 54:[ node\append(button) ]
+1750 49:[ return ret ] >> 55:[ ret ]
+966 50:[ end ] >> 37:[ mod.create_small_button = (node, segments, x, y) -> ]
+1755 51:[ mod.create_any_button = function(node, x_segs, y_segs, x, y) ] >> 57:[ mod.create_any_button = (node, x_segs, y_segs, x, y) -> ]
+1812 52:[ assert(x_segs >= 2, "x must have at least 2 segments") ] >> 58:[ assert(x_segs >= 2, "x must have at least 2 segments") ]
+1868 53:[ assert(y_segs >= 2, "y must have at leats 2 segments") ] >> 59:[ assert(y_segs >= 2, "y must have at leats 2 segments") ]
+1924 54:[ local section_width, section_height = 32, 32 ] >> 60:[ section_width, section_height = 32,32 ]
+1963 55:[ local ret = { } ] >> 61:[ ret = {} ]
+1973 56:[ local button = am.group() ] >> 62:[ button = am.group! ]
+1993 57:[ button:append(am.translate(x, y) ^ am.sprite("data/any_frame_top_left.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 63:[ button\append(am.translate(x,y)^ am.sprite("data/any_frame_top_left.png",vec4(1,1,1,1),"left","top")) ]
+2103 58:[ for i = 1, x_segs - 2 do ] >> 64:[ for i = 1, x_segs - 2 ]
+2120 59:[ button:append(am.translate(x + (i * section_width), y) ^ am.sprite("data/any_frame_top_mid.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 65:[ button\append(am.translate(x + (i * section_width),y)^ am.sprite("data/any_frame_top_mid.png",vec4(1,1,1,1),"left","top")) ]
+2096 60:[ end ] >> 64:[ for i = 1, x_segs - 2 ]
+2244 61:[ button:append(am.translate(x + ((x_segs - 1) * section_width), y) ^ am.sprite("data/any_frame_top_right.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 66:[ button\append(am.translate(x + ((x_segs-1)*section_width),y)^ am.sprite("data/any_frame_top_right.png",vec4(1,1,1,1),"left","top")) ]
+2384 62:[ for i = 1, y_segs - 2 do ] >> 67:[ for i = 1, y_segs - 2 ]
+2401 63:[ button:append(am.translate(x, y - (i * section_height)) ^ am.sprite("data/any_frame_mid_left.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 68:[ button\append(am.translate(x,y - (i * section_height))^ am.sprite("data/any_frame_mid_left.png",vec4(1,1,1,1),"left","top")) ]
+2535 64:[ for j = 1, x_segs - 2 do ] >> 69:[ for j = 1, x_segs - 2 ]
+2553 65:[ button:append(am.translate(x + (j * section_width), y - (i * section_height)) ^ am.sprite("data/any_frame_mid_mid.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 70:[ button\append(am.translate(x + (j * section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_mid.png",vec4(1,1,1,1),"left","top")) ]
+2528 66:[ end ] >> 69:[ for j = 1, x_segs - 2 ]
+2701 67:[ button:append(am.translate(x + ((x_segs - 1) * section_width), y - (i * section_height)) ^ am.sprite("data/any_frame_mid_right.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 71:[ button\append(am.translate(x + ((x_segs-1)*section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_right.png",vec4(1,1,1,1),"left","top")) ]
+2377 68:[ end ] >> 67:[ for i = 1, y_segs - 2 ]
+2857 69:[ button:append(am.translate(x, y - ((y_segs - 1) * section_height)) ^ am.sprite("data/any_frame_bot_left.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 72:[ button\append(am.translate(x,y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_left.png",vec4(1,1,1,1),"left","top")) ]
+2997 70:[ for i = 1, x_segs - 2 do ] >> 73:[ for i = 1, x_segs - 2 ]
+3014 71:[ button:append(am.translate(x + (i * section_width), y - ((y_segs - 1) * section_height)) ^ am.sprite("data/any_frame_bot_mid.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 74:[ button\append(am.translate(x + (i * section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_mid.png",vec4(1,1,1,1),"left","top")) ]
+2990 72:[ end ] >> 73:[ for i = 1, x_segs - 2 ]
+3168 73:[ button:append(am.translate(x + ((x_segs - 1) * section_width), y - ((y_segs - 1) * section_height)) ^ am.sprite("data/any_frame_bot_right.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 75:[ button\append(am.translate(x+((x_segs-1)*section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_right.png",vec4(1,1,1,1),"left","top")) ]
+3329 74:[ button:append(am.translate(x, y):tag("loc")) ] >> 76:[ button\append(am.translate(x,y)\tag("loc")) ]
+3374 75:[ ret["node"] = button ] >> 77:[ ret["node"] = button ]
+3396 76:[ bp.add(ret, x, y, x_segs * section_width, y_segs * section_height) ] >> 78:[ bp.add(ret,x,y,x_segs*section_width,y_segs*section_height) ]
+3456 77:[ node:append(button) ] >> 79:[ node\append(button) ]
+3477 78:[ return ret ] >> 80:[ ret ]
+1755 79:[ end ] >> 57:[ mod.create_any_button = (node, x_segs, y_segs, x, y) -> ]
+3482 80:[ mod.create_char_selector2 = function(node) ] >> 82:[ mod.create_char_selector2 = (node) -> ]
+3521 81:[ local left = mod.create_any_button(node, 2, 2, 96, 100) ] >> 83:[ left = mod.create_any_button(node,2,2,96,100) ]
+3568 82:[ local right = mod.create_any_button(node, 2, 2, -96 - (32 * 2), 100) ] >> 84:[ right = mod.create_any_button(node,2,2,-96-(32*2),100) ]
+3624 83:[ local text = am.text("Loading...", vec4(1, 1, 1, 1), "center", "top") ] >> 85:[ text = am.text("Loading...",vec4(1,1,1,1),"center","top") ]
+3683 84:[ node:append(am.translate(0, 100) ^ text) ] >> 86:[ node\append(am.translate(0,100)^ text) ]
+3724 86:[ left, ] >> 87:[ {left, right, text} ]
+3729 87:[ right, ] >> 87:[ {left, right, text} ]
+3736 88:[ text ] >> 87:[ {left, right, text} ]
+3482 90:[ end ] >> 82:[ mod.create_char_selector2 = (node) -> ]
+3744 91:[ mod.create_lobby_player = function(node, peerid) ] >> 89:[ mod.create_lobby_player = (node, peerid) -> ]
+3789 92:[ local floor_y = -200 ] >> 90:[ floor_y = -200 ]
+3805 93:[ local rng_x = math.random(main.width) - (main.width / 2) ] >> 91:[ rng_x = math.random(main.width) - (main.width/2) ]
+3744 94:[ end ] >> 89:[ mod.create_lobby_player = (node, peerid) -> ]
+3855 95:[ mod.create_char_selector = function(node) ] >> 93:[ mod.create_char_selector = (node) -> ]
+3893 96:[ local s = am.group() ] >> 94:[ s = am.group! ]
+3908 97:[ local slx = (-96 / 2) - 18 ] >> 95:[ slx = (-96/2)-18 ]
+3926 98:[ local sly = (96 / 2) ] >> 96:[ sly = (96/2) ]
+3940 99:[ local srx = (96 / 2) ] >> 97:[ srx = (96/2) ]
+3954 100:[ local sry = (96 / 2) ] >> 98:[ sry = (96/2) ]
+3968 101:[ local scroll_left_node = am.translate(slx, sly) ^ am.sprite("data/selector_left.png", vec4(1, 1, 1, 1), "left", "top") ] >> 99:[ scroll_left_node = am.translate(slx,sly)^ am.sprite("data/selector_left.png", vec4(1,1,1,1), "left","top") ]
+4076 102:[ local scroll_left = { ] >> 100:[ scroll_left = { ]
+4099 103:[ node = scroll_left_node ] >> 101:[ node: scroll_left_node ]
+4076 104:[ } ] >> 100:[ scroll_left = { ]
+4121 105:[ local scroll_right_node = am.translate(srx, sry) ^ am.sprite("data/selector_right.png", vec4(1, 1, 1, 1), "left", "top") ] >> 103:[ scroll_right_node = am.translate(srx,sry)^ am.sprite("data/selector_right.png", vec4(1,1,1,1), "left","top") ]
+4231 106:[ local scroll_right = { ] >> 104:[ scroll_right = { ]
+4255 107:[ node = scroll_right_node ] >> 105:[ node: scroll_right_node ]
+4231 108:[ } ] >> 104:[ scroll_right = { ]
+4278 109:[ s:append(scroll_left_node) ] >> 107:[ s\append(scroll_left_node) ]
+4306 110:[ s:append(scroll_right_node) ] >> 108:[ s\append(scroll_right_node) ]
+4335 111:[ bp.add(scroll_left, slx, sly, 18, 40) ] >> 109:[ bp.add(scroll_left,slx,sly,18,40) ]
+4370 112:[ bp.add(scroll_right, srx, sry, 18, 40) ] >> 110:[ bp.add(scroll_right,srx,sry,18,40) ]
+4406 113:[ node:append(s) ] >> 111:[ node\append(s) ]
+4423 115:[ scroll_left, ] >> 112:[ {scroll_left, scroll_right} ]
+4435 116:[ scroll_right ] >> 112:[ {scroll_left, scroll_right} ]
+3855 118:[ end ] >> 93:[ mod.create_char_selector = (node) -> ]
+4451 119:[ mod.fadeout = function() ] >> 114:[ mod.fadeout = () -> ]
+4472 120:[ local fadeout_walltime = 0.1 ] >> 115:[ fadeout_walltime = 0.1 ]
+4496 121:[ local screen = main.root("screen") ] >> 116:[ screen = main.root("screen") ]
+4526 122:[ local hw = main.width / 2 ] >> 117:[ hw = main.width/2 ]
+4545 123:[ local hh = main.height / 2 ] >> 118:[ hh = main.height/2 ]
+4565 124:[ local bg = color.bg ] >> 119:[ bg = color.bg ]
+4580 125:[ local start_color = vec4(bg.r, bg.g, bg.b, 0) ] >> 120:[ start_color = vec4(bg.r,bg.g,bg.b,0) ]
+4618 126:[ local fadeout = am.rect(-hw, hh, hw, -hh, color.bg):tag("fade") ] >> 121:[ fadeout = am.rect(-hw,hh,hw,-hh,color.bg)\tag("fade") ]
+4673 127:[ return screen:action(am.tween(0.1, { ] >> 122:[ screen\action(am.tween(0.1, { ]
+4711 128:[ color = color.bg ] >> 123:[ color: color.bg ]
+4673 129:[ })) ] >> 122:[ screen\action(am.tween(0.1, { ]
+4451 130:[ end ] >> 114:[ mod.fadeout = () -> ]
+4727 131:[ mod.fadein = function() ] >> 126:[ mod.fadein = () -> ]
+4747 132:[ local fadein_walltime = 0.1 ] >> 127:[ fadein_walltime = 0.1 ]
+4770 133:[ local fade = main.root("fade") ] >> 128:[ fade = main.root("fade") ]
+4796 134:[ return fade:action(am.tween(0.1, { ] >> 129:[ fade\action(am.tween(0.1, { ]
+4832 135:[ color = color.transparent ] >> 130:[ color: color.transparent ]
+4796 136:[ })) ] >> 129:[ fade\action(am.tween(0.1, { ]
+4727 137:[ end ] >> 126:[ mod.fadein = () -> ]
+4857 138:[ mod.create_join_input = function(node) ] >> 133:[ mod.create_join_input = (node) -> ]
+4892 139:[ local segments = 3 ] >> 134:[ segments=3 ]
+4904 140:[ local button_width = segments * 18 ] >> 135:[ button_width = segments*18 ]
+4932 141:[ local button_height = 40 ] >> 136:[ button_height = 40 ]
+4952 142:[ local cell_padding = 18 ] >> 137:[ cell_padding = 18 ]
+4971 143:[ local grid_w = 4 ] >> 138:[ grid_w = 4 ]
+4983 144:[ local grid_h = 4 ] >> 139:[ grid_h = 4 ]
+4995 145:[ local buttons = { } ] >> 140:[ buttons = {} ]
+5056 146:[ local start_x = cell_padding - ((grid_w / 2) * button_width) - (((grid_w - 1) / 2) * cell_padding) ] >> 142:[ start_x = cell_padding - ((grid_w/2) * button_width) - (((grid_w - 1)/2) * cell_padding) ]
+5146 147:[ local start_y = 0 + ((grid_h / 2) * button_height) + (((grid_h - 1) / 2) * cell_padding) ] >> 143:[ start_y = 0 + ((grid_h/2) * button_height) + (((grid_h - 1)/2) * cell_padding) ]
+5233 148:[ for i = 0, grid_h - 1 do ] >> 144:[ for i = 0,grid_h - 1 ]
+5256 149:[ for j = 0, grid_w - 1 do ] >> 145:[ for j = 0, grid_w - 1 ]
+5274 150:[ local button_x = start_x + (j * button_width) + ((j - 1) * cell_padding) ] >> 146:[ button_x = start_x + (j * button_width) + ((j-1) * cell_padding) ]
+5342 151:[ local button_y = start_y - (i * button_height) - ((i - 1) * cell_padding) ] >> 147:[ button_y = start_y - (i * button_height) - ((i-1) * cell_padding) ]
+5411 152:[ local small_button = mod.create_small_button(node, 3, button_x, button_y) ] >> 148:[ small_button = mod.create_small_button(node,3,button_x,button_y) ]
+5479 153:[ table.insert(buttons, small_button) ] >> 149:[ table.insert(buttons, small_button) ]
+5518 154:[ local rnode = small_button.node ] >> 150:[ rnode = small_button.node ]
+5547 155:[ local text_map = { ] >> 151:[ text_map = { ]
+5564 156:[ "0", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5568 157:[ "1", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5572 158:[ "2", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5576 159:[ "3", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5580 160:[ "4", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5584 161:[ "5", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5588 162:[ "6", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5592 163:[ "7", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5596 164:[ "8", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5600 165:[ "9", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5604 166:[ "a", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5608 167:[ "b", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5612 168:[ "c", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5616 169:[ "d", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5620 170:[ "e", ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5624 171:[ "f" ] >> 152:[ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" ]
+5547 172:[ } ] >> 151:[ text_map = { ]
+5636 173:[ local bt = text_map[(i * grid_w) + j + 1] ] >> 154:[ bt = text_map[(i * grid_w) + j + 1] ]
+5675 174:[ rnode:append(am.translate(button_x + 16, button_y - 10) ^ am.text(bt, vec4(1, 1, 1, 1), "left", "top")) ] >> 155:[ rnode\append(am.translate(button_x+16,button_y-10)^ am.text(bt,vec4(1,1,1,1),"left","top")) ]
+5770 175:[ small_button["text"] = bt ] >> 156:[ small_button["text"] = bt ]
+5249 176:[ end ] >> 145:[ for j = 0, grid_w - 1 ]
+5226 177:[ end ] >> 144:[ for i = 0,grid_h - 1 ]
+5801 178:[ local selected = mod.create_big_button(node, 6, -32 * 3, -100) ] >> 158:[ selected = mod.create_big_button(node,6,-32*3,-100) ]
+5854 179:[ return buttons ] >> 159:[ buttons ]
+4857 180:[ end ] >> 133:[ mod.create_join_input = (node) -> ]
+5863 181:[ mod.tween_hit = function(char, target, at_f) ] >> 161:[ mod.tween_hit = (char, target, at_f) -> -- char is a character, target is a location (number) ]
+5958 182:[ local x_from, x_to = char.node("char_translate").x, 0 ] >> 162:[ x_from, x_to = char.node("char_translate").x, 0 ]
+6007 183:[ local room = char.room ] >> 163:[ room = char.room ]
+6027 184:[ if char.__class == Enemy then ] >> 164:[ if char.__class == Enemy then ]
+6113 185:[ x_to = room:player_location_of(target) ] >> 166:[ x_to = room\player_location_of(target) ]
+6217 187:[ x_to = room:enemy_location_of(target) ] >> 169:[ x_to = room\enemy_location_of(target) ]
+6256 189:[ am.wait(am.tween(char.node("char_translate"), 0.1, { ] >> 170:[ am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_to},am.windup)) ]
+6310 190:[ x = x_to ] >> 170:[ am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_to},am.windup)) ]
+6256 191:[ }, am.windup)) ] >> 170:[ am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_to},am.windup)) ]
+6332 192:[ if at_f then ] >> 171:[ if at_f ]
+6340 193:[ at_f() ] >> 172:[ at_f! ]
+6330 194:[ end ] >> 171:[ if at_f ]
+6347 195:[ return am.wait(am.tween(char.node("char_translate"), 0.1, { ] >> 173:[ am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_from},am.linear)) ]
+6401 196:[ x = x_from ] >> 173:[ am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_from},am.linear)) ]
+6347 197:[ }, am.linear)) ] >> 173:[ am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_from},am.linear)) ]
+5863 198:[ end ] >> 161:[ mod.tween_hit = (char, target, at_f) -> -- char is a character, target is a location (number) ]
+6423 199:[ mod.build_infocard = function(node, ability) ] >> 175:[ mod.build_infocard = (node, ability) -> ]
+6464 200:[ local x_segs = 10 ] >> 176:[ x_segs = 10 ]
+6477 201:[ local y_segs = 4 ] >> 177:[ y_segs = 4 ]
+6489 202:[ local x = -310 ] >> 178:[ x = -310 ]
+6499 203:[ local y = 80 ] >> 179:[ y = 80 ]
+6508 204:[ local section_width, section_height = 32, 32 ] >> 180:[ section_width, section_height = 32,32 ]
+6547 205:[ local button = am.group() ] >> 181:[ button = am.group! ]
+6567 206:[ button:append(am.translate(x, y) ^ am.sprite("data/any_frame_top_left.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 182:[ button\append(am.translate(x,y)^ am.sprite("data/any_frame_top_left.png",vec4(1,1,1,1),"left","top")) ]
+6677 207:[ for i = 1, x_segs - 2 do ] >> 183:[ for i = 1, x_segs - 2 ]
+6694 208:[ button:append(am.translate(x + (i * section_width), y) ^ am.sprite("data/any_frame_top_mid.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 184:[ button\append(am.translate(x + (i * section_width),y)^ am.sprite("data/any_frame_top_mid.png",vec4(1,1,1,1),"left","top")) ]
+6670 209:[ end ] >> 183:[ for i = 1, x_segs - 2 ]
+6818 210:[ button:append(am.translate(x + ((x_segs - 1) * section_width), y) ^ am.sprite("data/any_frame_top_right.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 185:[ button\append(am.translate(x + ((x_segs-1)*section_width),y)^ am.sprite("data/any_frame_top_right.png",vec4(1,1,1,1),"left","top")) ]
+6958 211:[ for i = 1, y_segs - 2 do ] >> 186:[ for i = 1, y_segs - 2 ]
+6975 212:[ button:append(am.translate(x, y - (i * section_height)) ^ am.sprite("data/any_frame_mid_left.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 187:[ button\append(am.translate(x,y - (i * section_height))^ am.sprite("data/any_frame_mid_left.png",vec4(1,1,1,1),"left","top")) ]
+7109 213:[ for j = 1, x_segs - 2 do ] >> 188:[ for j = 1, x_segs - 2 ]
+7127 214:[ button:append(am.translate(x + (j * section_width), y - (i * section_height)) ^ am.sprite("data/any_frame_mid_mid.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 189:[ button\append(am.translate(x + (j * section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_mid.png",vec4(1,1,1,1),"left","top")) ]
+7102 215:[ end ] >> 188:[ for j = 1, x_segs - 2 ]
+7275 216:[ button:append(am.translate(x + ((x_segs - 1) * section_width), y - (i * section_height)) ^ am.sprite("data/any_frame_mid_right.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 190:[ button\append(am.translate(x + ((x_segs-1)*section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_right.png",vec4(1,1,1,1),"left","top")) ]
+6951 217:[ end ] >> 186:[ for i = 1, y_segs - 2 ]
+7431 218:[ button:append(am.translate(x, y - ((y_segs - 1) * section_height)) ^ am.sprite("data/any_frame_bot_left.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 191:[ button\append(am.translate(x,y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_left.png",vec4(1,1,1,1),"left","top")) ]
+7571 219:[ for i = 1, x_segs - 2 do ] >> 192:[ for i = 1, x_segs - 2 ]
+7588 220:[ button:append(am.translate(x + (i * section_width), y - ((y_segs - 1) * section_height)) ^ am.sprite("data/any_frame_bot_mid.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 193:[ button\append(am.translate(x + (i * section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_mid.png",vec4(1,1,1,1),"left","top")) ]
+7564 221:[ end ] >> 192:[ for i = 1, x_segs - 2 ]
+7742 222:[ button:append(am.translate(x + ((x_segs - 1) * section_width), y - ((y_segs - 1) * section_height)) ^ am.sprite("data/any_frame_bot_right.png", vec4(1, 1, 1, 1), "left", "top")) ] >> 194:[ button\append(am.translate(x+((x_segs-1)*section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_right.png",vec4(1,1,1,1),"left","top")) ]
+7903 223:[ button:append(am.translate(x, y):tag("loc")) ] >> 195:[ button\append(am.translate(x,y)\tag("loc")) ]
+7948 224:[ button("loc"):append(am.scale(2) ^ am.translate(16, -8) ^ am.text(ability.text, color.fg, "left", "top")) ] >> 196:[ button("loc")\append(am.scale(2)^ am.translate(16,-8)^ am.text(ability.text, color.fg, "left","top")) ]
+8051 225:[ button("loc"):append(am.scale(1) ^ am.translate(16, -48) ^ am.text(ability.description, color.fg, "left", "top")) ] >> 197:[ button("loc")\append(am.scale(1)^ am.translate(16,-48)^ am.text(ability.description, color.fg, "left","top")) ]
+8162 226:[ button("loc"):append(am.scale(1) ^ am.translate(16, -80) ^ am.text("Speed:" .. tostring(ability.speed), color.fg, "left", "top")) ] >> 198:[ button("loc")\append(am.scale(1)^ am.translate(16,-80)^ am.text("Speed:" .. tostring(ability.speed), color.fg, "left","top")) ]
+8289 227:[ button("loc"):append(am.scale(1) ^ am.translate(16, -108) ^ am.text("Hits:", color.fg, "left", "center")) ] >> 199:[ button("loc")\append(am.scale(1)^ am.translate(16,-108)^ am.text("Hits:",color.fg,"left","center")) ]
+8397 228:[ for i = 1, 8 do ] >> 200:[ for i = 1,8 ]
+8404 229:[ button("loc"):append(am.translate(42 + (i * 24), -96) ^ am.sprite("data/pip_frame.png", color.white, "left", "top")) ] >> 201:[ button("loc")\append(am.translate(42 + (i*24), -96)^ am.sprite("data/pip_frame.png",color.white,"left","top")) ]
+8519 230:[ if ability.hits_icon[i] == 1 then ] >> 202:[ if ability.hits_icon[i] == 1 ]
+8549 231:[ button("loc"):append(am.translate(42 + (i * 24), -96) ^ am.sprite("data/pip_light.png", color.white, "left", "top")) ] >> 203:[ button("loc")\append(am.translate(42 + (i*24), -96)^ am.sprite("data/pip_light.png",color.white,"left","top")) ]
+8517 232:[ end ] >> 202:[ if ability.hits_icon[i] == 1 ]
+8390 233:[ end ] >> 200:[ for i = 1,8 ]
+8662 234:[ node:append(button) ] >> 205:[ node\append(button) ]
+8683 235:[ return button ] >> 206:[ button ]
+6423 236:[ end ] >> 175:[ mod.build_infocard = (node, ability) -> ]
+8693 237:[ mod.battle_log = function(text) ] >> 209:[ mod.battle_log = (text) -> ]
+8721 238:[ local sx, sy = math.random(-100, 100), math.random(-40, 40) ] >> 210:[ sx, sy = math.random(-100,100), math.random(-40,40) ]
+8774 239:[ local trans = am.translate(sx, sy) ] >> 211:[ trans = am.translate(sx,sy) ]
+8803 240:[ trans:action(am.tween(3, { ] >> 212:[ trans\action(am.tween(3,{y:sy + 40})) ]
+8830 241:[ y = sy + 40 ] >> 212:[ trans\action(am.tween(3,{y:sy + 40})) ]
+8803 242:[ })) ] >> 212:[ trans\action(am.tween(3,{y:sy + 40})) ]
+8842 243:[ local t = am.text(text, color.fg) ] >> 213:[ t = am.text(text, color.fg) ]
+8871 244:[ t:action(am.tween(3, { ] >> 214:[ t\action(am.tween(3,{color:color.transparent})) ]
+8898 245:[ color = color.transparent ] >> 214:[ t\action(am.tween(3,{color:color.transparent})) ]
+8871 246:[ })) ] >> 214:[ t\action(am.tween(3,{color:color.transparent})) ]
+8920 247:[ t:action(coroutine.create(function() ] >> 215:[ t\action(coroutine.create( () -> ]
+8955 248:[ am.wait(am.delay(4)) ] >> 216:[ am.wait(am.delay(4)) ]
+8978 249:[ return main.root("screen"):remove(trans) ] >> 217:[ main.root("screen")\remove(trans) ]
+8920 250:[ end)) ] >> 215:[ t\action(coroutine.create( () -> ]
+9017 251:[ return main.root("screen"):append(trans ^ t) ] >> 219:[ main.root("screen")\append(trans^ t) ]
+8693 252:[ end ] >> 209:[ mod.battle_log = (text) -> ]
+9055 253:[ return mod ] >> 221:[ mod ]
diff --git a/debug/util.lua.X b/debug/util.lua.X
new file mode 100644
index 0000000..de80fa8
--- /dev/null
+++ b/debug/util.lua.X
@@ -0,0 +1,26 @@
+Pos Lua >> Moon
+24 1:[ local mod = ... ] >> 3:[ mod = ... ]
+64 9:[ self.x = x or 0 ] >> 7:[ @x = x or 0 ]
+78 10:[ self.y = y or 0 ] >> 8:[ @y = y or 0 ]
+92 11:[ self.z = z or 0 ] >> 9:[ @z = z or 0 ]
+171 31:[ return math.abs(self.x * self.y) ] >> 15:[ area: () => math.abs(@x * @y) ]
+209 34:[ return Vec2(a.x - b.x, a.y - b.y) ] >> 17:[ Vec2(a.x-b.x,a.y-b.y) ]
+250 37:[ return Vec2(a.x + b.x, a.y + b.y) ] >> 19:[ Vec2(a.x + b.x, a.y + b.y) ]
+133 43:[ self.x = x or 0 ] >> 13:[ @x = x or 0 ]
+147 44:[ self.y = y or 0 ] >> 14:[ @y = y or 0 ]
+343 59:[ local drawable ] >> 24:[ drawable = (c) -> ]
+343 60:[ drawable = function(c) ] >> 24:[ drawable = (c) -> ]
+362 61:[ return assert(c.sprite or c.anim) ] >> 25:[ assert(c.sprite or c.anim) ]
+343 62:[ end ] >> 24:[ drawable = (c) -> ]
+390 63:[ local anim_co ] >> 27:[ anim_co = (a) -> ]
+390 64:[ anim_co = function(a) ] >> 27:[ anim_co = (a) -> ]
+413 65:[ while true do ] >> 28:[ while true ]
+421 66:[ a.keyframe = math.floor(am.current_time() * 4) % #(a.anim) ] >> 29:[ a.keyframe = math.floor(am.current_time()*4) % #(a.anim) ]
+480 67:[ assert(a.anim[a.keyframe + 1], "Failed to find an appropriate image to draw.") ] >> 30:[ assert(a.anim[a.keyframe + 1], "Failed to find an appropriate image to draw.") ]
+561 68:[ a.node:replace("sprite", am.sprite(a.anim[a.keyframe + 1]):tag("sprite")) ] >> 31:[ a.node\replace("sprite",am.sprite(a.anim[a.keyframe + 1])\tag "sprite") ]
+635 69:[ coroutine.yield() ] >> 32:[ coroutine.yield() ]
+408 70:[ end ] >> 28:[ while true ]
+390 71:[ end ] >> 27:[ anim_co = (a) -> ]
+655 72:[ mod["Vec2"] = Vec2 ] >> 35:[ mod["Vec2"] = Vec2 ]
+674 73:[ mod["Vec3"] = Vec3 ] >> 36:[ mod["Vec3"] = Vec3 ]
+693 74:[ return mod ] >> 37:[ mod ]
diff --git a/debug/world.lua.X b/debug/world.lua.X
new file mode 100644
index 0000000..524eaf3
--- /dev/null
+++ b/debug/world.lua.X
@@ -0,0 +1,376 @@
+Pos Lua >> Moon
+29 1:[ local mod = ... ] >> 3:[ mod = ... ]
+39 2:[ local connect = require("connect") ] >> 4:[ connect = require "connect" ]
+67 3:[ local lobby = require("lobby") ] >> 5:[ lobby = require "lobby" ]
+91 4:[ local joined = require("joined") ] >> 6:[ joined = require "joined" ]
+117 5:[ local party = require("party") ] >> 7:[ party = require "party" ]
+141 6:[ local char = require("char") ] >> 8:[ char = require "char" ]
+184 8:[ Character = char.Character ] >> 9:[ import Character from char ]
+207 10:[ Enemy = char.Enemy ] >> 10:[ import Enemy from char ]
+230 12:[ Party = party.Party ] >> 11:[ import Party from party ]
+237 13:[ local player = require("player") ] >> 12:[ player = require "player" ]
+287 15:[ RemotePlayer = player.RemotePlayer ] >> 13:[ import RemotePlayer from player ]
+318 17:[ LocalPlayer = player.LocalPlayer ] >> 14:[ import LocalPlayer from player ]
+326 18:[ local main = require("main") ] >> 15:[ main = require "main" ]
+348 19:[ local room = require("room") ] >> 16:[ room = require "room" ]
+386 21:[ Room = room.Room ] >> 17:[ import Room from room ]
+413 23:[ LobbyRoom = room.LobbyRoom ] >> 18:[ import LobbyRoom from room ]
+419 24:[ local ui = require("ui") ] >> 19:[ ui = require "ui" ]
+437 25:[ local ability = require("ability_reg") ] >> 20:[ ability = require "ability_reg" ]
+1117 31:[ local peer = msg.peer ] >> 40:[ peer = msg.peer ]
+1135 32:[ self.players[peer] = RemotePlayer(peer, nil, char.classes.Tumbler) ] >> 41:[ @players[peer] = RemotePlayer(peer,nil,char.classes.Tumbler) ]
+1198 33:[ am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 42:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_player_joined",uname:peer,class:"Tumbler"}))) ]
+1262 34:[ msg = "info_player_joined", ] >> 42:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_player_joined",uname:peer,class:"Tumbler"}))) ]
+1289 35:[ uname = peer, ] >> 42:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_player_joined",uname:peer,class:"Tumbler"}))) ]
+1300 36:[ class = "Tumbler" ] >> 42:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_player_joined",uname:peer,class:"Tumbler"}))) ]
+1198 37:[ }))) ] >> 42:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_player_joined",uname:peer,class:"Tumbler"}))) ]
+1318 38:[ if self.host == nil then ] >> 43:[ if @host == nil ]
+1335 39:[ self.host = peer ] >> 44:[ @host = peer ]
+1316 40:[ end ] >> 43:[ if @host == nil ]
+1382 43:[ if self.game_state == "lobby" then ] >> 46:[ if @game_state == "lobby" ]
+1411 44:[ if self.players[msg.peer] then ] >> 47:[ if @players[msg.peer] ]
+1435 45:[ self.players[msg.peer]:set_class(char.classes[msg.class]) ] >> 48:[ @players[msg.peer]\set_class(char.classes[msg.class]) ]
+1501 47:[ self.players[msg.peer] = RemotePlayer(msg.peer, nil, char.classes[msg.class]) ] >> 50:[ @players[msg.peer] = RemotePlayer(msg.peer,nil,char.classes[msg.class]) ]
+1576 49:[ am.eval_js(string.format("GLOBAL.send_message(%q,%q)", msg.peer, am.to_json({ ] >> 51:[ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "confirm_class_change", class: msg.class}))) ]
+1655 50:[ msg = "confirm_class_change", ] >> 51:[ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "confirm_class_change", class: msg.class}))) ]
+1686 51:[ class = msg.class ] >> 51:[ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "confirm_class_change", class: msg.class}))) ]
+1576 52:[ }))) ] >> 51:[ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "confirm_class_change", class: msg.class}))) ]
+1704 53:[ return am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 52:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_class_change",uname:msg.peer,class:msg.class}))) ]
+1768 54:[ msg = "info_class_change", ] >> 52:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_class_change",uname:msg.peer,class:msg.class}))) ]
+1794 55:[ uname = msg.peer, ] >> 52:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_class_change",uname:msg.peer,class:msg.class}))) ]
+1809 56:[ class = msg.class ] >> 52:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_class_change",uname:msg.peer,class:msg.class}))) ]
+1704 57:[ }))) ] >> 52:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_class_change",uname:msg.peer,class:msg.class}))) ]
+1833 59:[ return am.eval_js(string.format("GLOBAL.send_message(%q,%q)", msg.peer, am.to_json({ ] >> 54:[ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "deny_class_change", class:@players[msg.peer].class}))) ]
+1912 60:[ msg = "deny_class_change", ] >> 54:[ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "deny_class_change", class:@players[msg.peer].class}))) ]
+1940 61:[ class = self.players[msg.peer].class ] >> 54:[ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "deny_class_change", class:@players[msg.peer].class}))) ]
+1833 62:[ }))) ] >> 54:[ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "deny_class_change", class:@players[msg.peer].class}))) ]
+2005 66:[ if msg.peer == self.host and self.game_state == "lobby" then ] >> 56:[ if msg.peer == @host and @game_state == "lobby" ]
+2054 67:[ self.player_party = Party() ] >> 57:[ @player_party = Party! ]
+2136 68:[ for _, chartbl in pairs(self.players) do ] >> 59:[ for _, chartbl in pairs(@players) ]
+2157 69:[ chartbl.position = chartbl.class.default_position ] >> 60:[ chartbl.position = chartbl.class.default_position ]
+2211 70:[ chartbl.hp = chartbl.class.default_hp ] >> 61:[ chartbl.hp = chartbl.class.default_hp ]
+2253 71:[ self.player_party:add_member(chartbl) ] >> 62:[ @player_party\add_member(chartbl) ]
+2119 72:[ end ] >> 59:[ for _, chartbl in pairs(@players) ]
+2290 73:[ self:campaign_start() ] >> 63:[ @campaign_start! ]
+2310 74:[ return am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 64:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_campaign_start",time_ref:@player_start_time}))) ]
+2374 75:[ msg = "info_campaign_start", ] >> 64:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_campaign_start",time_ref:@player_start_time}))) ]
+2405 76:[ time_ref = self.player_start_time ] >> 64:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_campaign_start",time_ref:@player_start_time}))) ]
+2310 77:[ }))) ] >> 64:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_campaign_start",time_ref:@player_start_time}))) ]
+2003 78:[ end ] >> 56:[ if msg.peer == @host and @game_state == "lobby" ]
+2451 81:[ self.game_state = "room_entry" ] >> 66:[ @game_state = "room_entry" ]
+2481 82:[ self.player_start_time = am.eval_js("new Date().getTime()") ] >> 67:[ @player_start_time = am.eval_js("new Date().getTime()") ]
+2540 83:[ room = Room.generate(self.cr) ] >> 68:[ room = Room.generate(@cr) ]
+2569 84:[ self.room = room ] >> 69:[ @room = room ]
+2585 85:[ self.enemy_party = self:generate_enemies() ] >> 70:[ @enemy_party = @generate_enemies! ]
+2622 86:[ self.enemy_party:set_room(room) ] >> 71:[ @enemy_party\set_room(room) ]
+2653 87:[ self.player_party:set_room(room) ] >> 72:[ @player_party\set_room(room) ]
+2685 88:[ am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 73:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+2754 89:[ msg = "info_enemy_party", ] >> 74:[ msg: "info_enemy_party", ]
+2784 90:[ data = self.enemy_party:serialize() ] >> 75:[ data: @enemy_party\serialize! ]
+2685 91:[ }))) ] >> 73:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+2820 92:[ room:distribute_party(self.player_party, self.enemy_party) ] >> 77:[ room\distribute_party(@player_party,@enemy_party) ]
+2873 93:[ am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 78:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_room",data:room\serialize!,time_ref:@player_start_time}))) ]
+2937 94:[ msg = "info_room", ] >> 78:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_room",data:room\serialize!,time_ref:@player_start_time}))) ]
+2954 95:[ data = room:serialize(), ] >> 78:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_room",data:room\serialize!,time_ref:@player_start_time}))) ]
+2979 96:[ time_ref = self.player_start_time ] >> 78:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_room",data:room\serialize!,time_ref:@player_start_time}))) ]
+2873 97:[ }))) ] >> 78:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_room",data:room\serialize!,time_ref:@player_start_time}))) ]
+3005 98:[ self.game_state = "room_players" ] >> 79:[ @game_state = "room_players" ]
+3066 101:[ local player_ser = { } ] >> 82:[ player_ser = {} ]
+3105 102:[ for peerid, player in pairs(self.players) do ] >> 83:[ for peerid, player in pairs(@players) ]
+3125 103:[ player_ser[peerid] = player:serialize() ] >> 84:[ player_ser[peerid] = player\serialize! ]
+3084 104:[ end ] >> 83:[ for peerid, player in pairs(@players) ]
+3166 105:[ return am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 85:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"respond_player_list", data:player_ser}))) ]
+3230 106:[ msg = "respond_player_list", ] >> 85:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"respond_player_list", data:player_ser}))) ]
+3258 107:[ data = player_ser ] >> 85:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"respond_player_list", data:player_ser}))) ]
+3166 108:[ }))) ] >> 85:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"respond_player_list", data:player_ser}))) ]
+3298 111:[ if self.game_state == "room_players" then ] >> 87:[ if @game_state == "room_players" ]
+3334 112:[ if not self.dead_players[msg.peer] then ] >> 88:[ if not @dead_players[msg.peer] ]
+3367 113:[ self.set_actions[msg.peer] = ability[msg.action] ] >> 89:[ @set_actions[msg.peer] = ability[msg.action] ]
+3332 114:[ end ] >> 88:[ if not @dead_players[msg.peer] ]
+3296 115:[ end ] >> 87:[ if @game_state == "room_players" ]
+3428 118:[ local msg = am.eval_js("GLOBAL.get_message()") ] >> 91:[ msg = am.eval_js("GLOBAL.get_message()") ]
+3473 119:[ if msg ~= nil then ] >> 92:[ if msg != nil ]
+3490 120:[ if msg.msg == "data" then ] >> 93:[ if msg.msg == "data" ]
+3513 121:[ local info = am.parse_json(msg.data) ] >> 94:[ info = am.parse_json(msg.data) ]
+3548 122:[ info.peer = msg.peer ] >> 95:[ info.peer = msg.peer -- server messages have an extra "peer" field that the client didn't add. ]
+3649 123:[ if self[info.msg] then ] >> 96:[ if @[info.msg] ]
+3667 124:[ self[info.msg](self, info) ] >> 97:[ @[info.msg](@,info) ]
+3701 126:[ print("Failed to find server message handler:", msg, "no handler", info.msg) ] >> 99:[ print("Failed to find server message handler:",msg,"no handler",info.msg) ]
+3787 129:[ print("Msg was nil") ] >> 101:[ print("Msg was nil") ]
+3471 131:[ end ] >> 92:[ if msg != nil ]
+3833 132:[ if self.game_state == "room_players" then ] >> 103:[ if @game_state == "room_players" ]
+3869 133:[ if am.eval_js("new Date().getTime()") > self.player_start_time + 6000 then ] >> 104:[ if am.eval_js("new Date().getTime()") > @player_start_time + 6000 ]
+3982 134:[ local npc_actions = { } ] >> 106:[ npc_actions = {} ]
+4003 135:[ local used_actions = { } ] >> 107:[ used_actions = {} ]
+4025 136:[ local party_index = { } ] >> 108:[ party_index = {} ]
+4046 137:[ local character_index = { } ] >> 109:[ character_index = {} ]
+4088 138:[ for uname, npc in pairs(self.enemy_party.members) do ] >> 110:[ for uname, npc in pairs(@enemy_party.members) ]
+4122 139:[ npc_actions[uname] = npc:select_action() ] >> 111:[ npc_actions[uname] = npc\select_action! ]
+4167 140:[ table.insert(used_actions, npc_actions[uname]) ] >> 112:[ table.insert(used_actions, npc_actions[uname]) ]
+4219 141:[ party_index[npc_actions[uname]] = self.enemy_party ] >> 113:[ party_index[npc_actions[uname]] = @enemy_party ]
+4271 142:[ character_index[npc_actions[uname]] = npc ] >> 114:[ character_index[npc_actions[uname]] = npc ]
+4071 143:[ end ] >> 110:[ for uname, npc in pairs(@enemy_party.members) ]
+4317 144:[ local total_actions = { } ] >> 115:[ total_actions = {} ]
+4350 145:[ for k, v in pairs(npc_actions) do ] >> 116:[ for k,v in pairs(npc_actions) ]
+4375 146:[ total_actions[k] = v.__name ] >> 117:[ total_actions[k] = v.__name ]
+4340 147:[ end ] >> 116:[ for k,v in pairs(npc_actions) ]
+4417 148:[ for k, v in pairs(self.set_actions) do ] >> 118:[ for k,v in pairs(@set_actions) ]
+4443 149:[ total_actions[k] = v.__name ] >> 119:[ total_actions[k] = v.__name ]
+4476 150:[ table.insert(used_actions, v) ] >> 120:[ table.insert(used_actions, v) ]
+4511 151:[ party_index[v] = self.player_party ] >> 121:[ party_index[v] = @player_party ]
+4547 152:[ character_index[v] = self.player_party:member(k) ] >> 122:[ character_index[v] = @player_party\member(k) ]
+4407 153:[ end ] >> 118:[ for k,v in pairs(@set_actions) ]
+4649 154:[ table.sort(used_actions, function(a, b) ] >> 124:[ table.sort(used_actions, (a,b) -> ]
+4688 155:[ return a.speed < b.speed ] >> 125:[ a.speed < b.speed ]
+4649 156:[ end) ] >> 124:[ table.sort(used_actions, (a,b) -> ]
+4757 157:[ for k, v in ipairs(used_actions) do ] >> 128:[ for k,v in ipairs(used_actions) ]
+4784 158:[ local tchar = character_index[v] ] >> 129:[ tchar = character_index[v] ]
+4816 159:[ v.__class.use(self, party_index[v], tchar) ] >> 130:[ v.__class.use(@,party_index[v],tchar) ]
+4747 160:[ end ] >> 128:[ for k,v in ipairs(used_actions) ]
+4858 161:[ am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 131:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+4928 162:[ msg = "info_actions", ] >> 132:[ msg:"info_actions", ]
+4954 163:[ data = total_actions ] >> 133:[ data: total_actions ]
+4858 164:[ }))) ] >> 131:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+5012 165:[ self.game_state = "room_battle_animate" ] >> 136:[ @game_state = "room_battle_animate" ]
+5052 166:[ self.set_actions = { } ] >> 137:[ @set_actions = {} ]
+3867 167:[ end ] >> 104:[ if am.eval_js("new Date().getTime()") > @player_start_time + 6000 ]
+3831 168:[ end ] >> 103:[ if @game_state == "room_players" ]
+5074 169:[ if self.game_state == "room_battle_animate" then ] >> 138:[ if @game_state == "room_battle_animate" --only exists for 1 tick, calc dammge, check if room is done, check if we're defeated, ect. ]
+5207 170:[ local _ = self.calculate_damage ] >> 139:[ @calculate_damage ]
+5228 171:[ self.player_start_time = am.eval_js("new Date().getTime()") + 500 ] >> 140:[ @player_start_time = am.eval_js("new Date().getTime()")+500 --500 ms for animations ]
+5315 172:[ self.game_state = "room_players" ] >> 141:[ @game_state = "room_players" ]
+5347 173:[ am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 142:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+5416 174:[ msg = "info_timeref", ] >> 143:[ msg:"info_timeref", ]
+5445 175:[ time_ref = self.player_start_time ] >> 144:[ time_ref:@player_start_time ]
+5347 176:[ }))) ] >> 142:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+5475 177:[ local dead_characters = { } ] >> 146:[ dead_characters = {} ]
+5517 178:[ for uname, char in pairs(self.player_party.members) do ] >> 147:[ for uname, char in pairs(@player_party.members) ]
+5553 179:[ if char.data.hp <= 0 then ] >> 148:[ if char.data.hp <= 0 ]
+5577 180:[ table.insert(dead_characters, uname) ] >> 149:[ table.insert(dead_characters,uname) ]
+5618 181:[ char:die() ] >> 150:[ char\die! ]
+5633 182:[ self.player_party:remove_member(char) ] >> 151:[ @player_party\remove_member(char) ]
+5672 183:[ self.dead_players[uname] = true ] >> 152:[ @dead_players[uname] = true ]
+5551 184:[ end ] >> 148:[ if char.data.hp <= 0 ]
+5499 185:[ end ] >> 147:[ for uname, char in pairs(@player_party.members) ]
+5721 186:[ for uname, char in pairs(self.enemy_party.members) do ] >> 153:[ for uname, char in pairs(@enemy_party.members) ]
+5756 187:[ if char.data.hp <= 0 then ] >> 154:[ if char.data.hp <= 0 ]
+5780 188:[ table.insert(dead_characters, uname) ] >> 155:[ table.insert(dead_characters,uname) ]
+5821 189:[ char:die() ] >> 156:[ char\die! ]
+5836 190:[ self.enemy_party:remove_member(char) ] >> 157:[ @enemy_party\remove_member(char) ]
+5754 191:[ end ] >> 154:[ if char.data.hp <= 0 ]
+5703 192:[ end ] >> 153:[ for uname, char in pairs(@enemy_party.members) ]
+5872 193:[ am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 158:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+5941 194:[ msg = "info_deaths", ] >> 159:[ msg:"info_deaths", ]
+5965 195:[ data = dead_characters ] >> 160:[ data:dead_characters ]
+5872 196:[ }))) ] >> 158:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+5994 197:[ if self.player_party:nmembers() == 0 then ] >> 162:[ if @player_party\nmembers! == 0 ]
+6028 198:[ self.game_state = "defeat" ] >> 163:[ @game_state = "defeat" ]
+6060 199:[ elseif self.enemy_party:nmembers() == 0 then ] >> 164:[ elseif @enemy_party\nmembers! == 0 ]
+6093 200:[ am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 165:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+6163 201:[ msg = "info_loot", ] >> 166:[ msg:"info_loot" ]
+6189 202:[ time_ref = self.player_start_time ] >> 167:[ time_ref:@player_start_time ]
+6093 203:[ }))) ] >> 165:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+6277 204:[ self.player_start_time = am.eval_js("new Date().getTime()") ] >> 170:[ @player_start_time = am.eval_js("new Date().getTime()") ]
+6337 205:[ self.game_state = "victory" ] >> 171:[ @game_state = "victory" ]
+5072 207:[ end ] >> 138:[ if @game_state == "room_battle_animate" --only exists for 1 tick, calc dammge, check if room is done, check if we're defeated, ect. ]
+6365 208:[ if self.game_state == "defeat" then ] >> 172:[ if @game_state == "defeat" ]
+6393 209:[ am.eval_js(string.format("GLOBAL.broadcast(%q)", am.to_json({ ] >> 173:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+6462 210:[ msg = "info_defeat" ] >> 174:[ msg:"info_defeat" ]
+6393 211:[ }))) ] >> 173:[ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ ]
+6487 212:[ self.game_state = "done" ] >> 176:[ @game_state = "done" ]
+6363 213:[ end ] >> 172:[ if @game_state == "defeat" ]
+6512 214:[ if self.game_state == "victory" then ] >> 177:[ if @game_state == "victory" ]
+6543 215:[ if am.eval_js("new Date().getTime()") > self.player_start_time + 6000 then ] >> 178:[ if am.eval_js("new Date().getTime()") > @player_start_time + 6000 -- a few seconds for victory! (and animations) ]
+6658 216:[ self.cr = self.cr + 1 ] >> 179:[ @cr += 1 ]
+6671 217:[ return self:campaign_start() ] >> 180:[ @campaign_start! ]
+6541 218:[ end ] >> 178:[ if am.eval_js("new Date().getTime()") > @player_start_time + 6000 -- a few seconds for victory! (and animations) ]
+6510 219:[ end ] >> 177:[ if @game_state == "victory" ]
+6717 222:[ return print("calculating dammage...") ] >> 183:[ print("calculating dammage...") ]
+6776 225:[ local enemies = { } ] >> 186:[ enemies = {} ]
+6791 226:[ local tcr = self.cr ] >> 187:[ tcr = @cr ]
+6803 227:[ local possible_enemies = { } ] >> 188:[ possible_enemies = {} ]
+6837 228:[ for k, v in pairs(char.enemies) do ] >> 189:[ for k,v in pairs(char.enemies) ]
+6861 229:[ table.insert(possible_enemies, v) ] >> 190:[ table.insert(possible_enemies,v) ]
+6827 230:[ end ] >> 189:[ for k,v in pairs(char.enemies) ]
+6896 231:[ local enemy_party = Party() ] >> 191:[ enemy_party = Party! ]
+6924 232:[ while tcr > 0 and #possible_enemies > 0 do ] >> 192:[ while tcr > 0 and #possible_enemies > 0 ]
+6962 233:[ local filtered_enemies = { } ] >> 193:[ filtered_enemies = {} ]
+7002 234:[ for _, enemy in pairs(possible_enemies) do ] >> 194:[ for _, enemy in pairs(possible_enemies) ]
+7033 235:[ if enemy.cr <= tcr then ] >> 195:[ if enemy.cr <= tcr ]
+7055 236:[ table.insert(filtered_enemies, enemy) ] >> 196:[ table.insert(filtered_enemies,enemy) ]
+7031 237:[ end ] >> 195:[ if enemy.cr <= tcr ]
+6987 238:[ end ] >> 194:[ for _, enemy in pairs(possible_enemies) ]
+7095 239:[ table.shuffle(filtered_enemies) ] >> 197:[ table.shuffle(filtered_enemies) ]
+7130 240:[ local rng_enemy = table.remove(filtered_enemies) ] >> 198:[ rng_enemy = table.remove(filtered_enemies) ]
+7176 241:[ tcr = tcr - rng_enemy.cr ] >> 199:[ tcr = tcr - rng_enemy.cr ]
+7204 242:[ enemy_party:add_member(Enemy(nil, rng_enemy)) ] >> 200:[ enemy_party\add_member(Enemy(nil,rng_enemy)) ]
+6919 243:[ end ] >> 192:[ while tcr > 0 and #possible_enemies > 0 ]
+7251 244:[ return enemy_party ] >> 201:[ enemy_party ]
+496 250:[ self.server = true ] >> 24:[ @server = true ]
+513 251:[ self.client = false ] >> 25:[ @client = false ]
+531 252:[ am.eval_js(connect) ] >> 26:[ am.eval_js(connect) ]
+553 253:[ am.eval_js(lobby) ] >> 27:[ am.eval_js(lobby) ]
+573 254:[ self.lobby_id = am.eval_js("GLOBAL.lobby_id") ] >> 28:[ @lobby_id = am.eval_js("GLOBAL.lobby_id") ]
+617 255:[ self.game_state = "lobby" ] >> 29:[ @game_state = "lobby" --lobby, room_entry, room_players, room_battle_animate, victory, camp_entry, camp_players_animate, defeat, done ]
+753 256:[ self.game_state_extra = 0 ] >> 30:[ @game_state_extra = 0 ]
+777 257:[ self.players = { } ] >> 31:[ @players = {} --[peer_id] = tbl ]
+811 258:[ self.set_actions = { } ] >> 32:[ @set_actions = {} --[peer_id] = "name" ]
+852 259:[ self.player_party = nil ] >> 33:[ @player_party = nil -- the party, created at campaign start ]
+914 260:[ self.host = nil ] >> 34:[ @host = nil --who is the lobby host, with the power to start the game? ]
+987 261:[ self.enemy_party = nil ] >> 35:[ @enemy_party = nil -- The enemy party ]
+1027 262:[ self.updates = { } ] >> 36:[ @updates = {} ]
+1043 263:[ self.dead_players = { } ] >> 37:[ @dead_players = {} --[peer_id] = true ]
+1083 264:[ self.cr = 1 ] >> 38:[ @cr = 1 ]
+7663 284:[ am.eval_js(joined) ] >> 221:[ am.eval_js(joined) ]
+7684 285:[ return am.eval_js("CLIENT.join('" .. id .. "');") ] >> 222:[ am.eval_js("CLIENT.join('" .. id .. "');") ]
+7748 288:[ return am.eval_js("CLIENT.open") ] >> 224:[ am.eval_js("CLIENT.open") ]
+7793 291:[ return am.eval_js("CLIENT.peer") ] >> 226:[ am.eval_js("CLIENT.peer") ]
+7853 294:[ return self.localplayer:set_class(char.classes[msg.class]) ] >> 228:[ @localplayer\set_class(char.classes[msg.class]) ]
+7932 297:[ return self.localplayer:set_class(char.classes[msg.class]) ] >> 230:[ @localplayer\set_class(char.classes[msg.class]) ]
+8036 300:[ for peerid, chardata in pairs(msg.data) do ] >> 232:[ for peerid, chardata in pairs(msg.data) ]
+8058 301:[ if not self.player_party:member(peerid) then ] >> 233:[ if not @player_party\member(peerid) ]
+8098 302:[ if peerid == am.eval_js("CLIENT.peer._id") then ] >> 234:[ if peerid == am.eval_js("CLIENT.peer._id") ]
+8144 303:[ self.localplayer = Character.deserialize(chardata) ] >> 235:[ @localplayer = Character.deserialize(chardata) ]
+8196 304:[ self.player_party:add_member(self.localplayer) ] >> 236:[ @player_party\add_member(@localplayer) ]
+8240 305:[ self.localplayer:enter_room(self.player_party.room) ] >> 237:[ @localplayer\enter_room(@player_party.room) ]
+8298 307:[ local newplayer = Character.deserialize(chardata) ] >> 239:[ newplayer = Character.deserialize(chardata) ]
+8347 308:[ self.player_party:add_member(newplayer) ] >> 240:[ @player_party\add_member(newplayer) ]
+8388 309:[ newplayer:enter_room(self.player_party.room) ] >> 241:[ newplayer\enter_room(@player_party.room) ]
+8441 312:[ print("Do nothing...") ] >> 243:[ print("Do nothing...") ]
+8013 314:[ end ] >> 232:[ for peerid, chardata in pairs(msg.data) ]
+8497 317:[ if msg.uname == self.localplayer.uname then ] >> 245:[ if msg.uname == @localplayer.uname ]
+8533 318:[ return ] >> 246:[ return ]
+8495 319:[ end ] >> 245:[ if msg.uname == @localplayer.uname ]
+8544 320:[ if not self.player_party:member(msg.uname) then ] >> 247:[ if not @player_party\member(msg.uname) ]
+8584 321:[ return self.player_party:add_member(Character.deserialize(msg.class)) ] >> 248:[ @player_party\add_member(Character.deserialize(msg.class)) ]
+8653 323:[ return self.player_party:member(msg.uname):set_class(char.classes[msg.class]) ] >> 250:[ @player_party\member(msg.uname)\set_class(char.classes[msg.class]) ]
+8754 327:[ if msg.uname == am.eval_js("CLIENT.peer._id") then ] >> 252:[ if msg.uname == am.eval_js("CLIENT.peer._id") ]
+8803 328:[ if self.localplayer ~= nil then ] >> 253:[ if @localplayer != nil ]
+8828 329:[ return ] >> 254:[ return ]
+8801 330:[ end ] >> 253:[ if @localplayer != nil ]
+8838 331:[ self.localplayer = LocalPlayer(msg.uname, { }, char.classes[msg.class]) ] >> 255:[ @localplayer = LocalPlayer(msg.uname, {}, char.classes[msg.class]) ]
+8908 332:[ self.player_party:add_member(self.localplayer) ] >> 256:[ @player_party\add_member(@localplayer) ]
+8950 333:[ return self.localplayer:enter_room(self.player_party.room) ] >> 257:[ @localplayer\enter_room(@player_party.room) ]
+9002 334:[ elseif not self.player_party:member(msg.uname) then ] >> 258:[ elseif not @player_party\member(msg.uname) ]
+9042 335:[ local newplayer = RemotePlayer(msg.uname, nil, char.classes[msg.class]) ] >> 259:[ newplayer = RemotePlayer(msg.uname, nil, char.classes[msg.class]) ]
+9111 336:[ self.player_party:add_member(newplayer) ] >> 260:[ @player_party\add_member(newplayer) ]
+9150 337:[ return newplayer:enter_room(self.player_party.room) ] >> 261:[ newplayer\enter_room(@player_party.room) ]
+9201 339:[ return print("Do nothing") ] >> 263:[ print("Do nothing") ]
+9254 343:[ local lobby_menu = require("lobby_menu") ] >> 265:[ lobby_menu = require "lobby_menu" ]
+9290 344:[ local create_party_menu = require("create_party_menu") ] >> 266:[ create_party_menu = require "create_party_menu" ]
+9342 345:[ if create_party_menu.loaded then ] >> 267:[ if create_party_menu.loaded ]
+9371 346:[ create_party_menu.unload() ] >> 268:[ create_party_menu.unload! ]
+9340 347:[ end ] >> 267:[ if create_party_menu.loaded ]
+9401 348:[ if lobby_menu.loaded then ] >> 269:[ if lobby_menu.loaded ]
+9423 349:[ lobby_menu.unload() ] >> 270:[ lobby_menu.unload! ]
+9399 350:[ end ] >> 269:[ if lobby_menu.loaded ]
+9444 351:[ local battle_menu = require("battle_menu") ] >> 271:[ battle_menu = require "battle_menu" ]
+9482 352:[ battle_menu.load() ] >> 272:[ battle_menu.load! ]
+9502 353:[ ui.fadeout() ] >> 273:[ ui.fadeout! ]
+9516 354:[ self.time_ref = msg.time_ref ] >> 274:[ @time_ref = msg.time_ref ]
+9564 357:[ self.room = Room.deserialize(msg.data) ] >> 276:[ @room = Room.deserialize(msg.data) ]
+9601 358:[ self.time_ref = msg.time_ref ] >> 277:[ @time_ref = msg.time_ref ]
+9628 359:[ self:set_room(self.room) ] >> 278:[ @set_room(@room) ]
+9647 360:[ local battle_menu = require("battle_menu") ] >> 279:[ battle_menu = require "battle_menu" ]
+9685 361:[ battle_menu.victory = false ] >> 280:[ battle_menu.victory = false ]
+9715 362:[ return main.root:remove("infocard") ] >> 281:[ main.root\remove("infocard") ]
+9770 365:[ main.root:remove("infocard") ] >> 283:[ main.root\remove("infocard") ]
+9801 366:[ self.time_ref = msg.time_ref ] >> 284:[ @time_ref = msg.time_ref ]
+9858 369:[ if self.enemy_party then ] >> 286:[ if @enemy_party ]
+9875 370:[ self.node:remove(self.enemy_party.rnode) ] >> 287:[ @node\remove(@enemy_party.rnode) ]
+9856 371:[ end ] >> 286:[ if @enemy_party ]
+9910 372:[ self.enemy_party = Party.deserialize(msg.data) ] >> 288:[ @enemy_party = Party.deserialize(msg.data) ]
+9955 373:[ self.enemy_party:set_room(self.room) ] >> 289:[ @enemy_party\set_room(@room) ]
+9988 374:[ if self.room.__class ~= LobbyRoom then ] >> 290:[ if @room.__class != LobbyRoom ]
+10019 375:[ self.enemy_party:set_room(self.room) ] >> 291:[ @enemy_party\set_room(@room) ]
+9986 376:[ end ] >> 290:[ if @room.__class != LobbyRoom ]
+10050 377:[ local battle_menu = require("battle_menu") ] >> 292:[ battle_menu = require "battle_menu" ]
+10088 378:[ battle_menu.victory = false ] >> 293:[ battle_menu.victory = false ]
+10118 379:[ return main.root:remove("infocard") ] >> 294:[ main.root\remove("infocard") ]
+10208 382:[ local battle_menu = require("battle_menu") ] >> 297:[ battle_menu = require("battle_menu") ]
+10247 383:[ local defeat_menu = require("defeat_menu") ] >> 298:[ defeat_menu = require("defeat_menu") ]
+10286 384:[ table.insert(main.action_queue, { ] >> 299:[ table.insert(main.action_queue,{battle_menu.unload, {}}) ]
+10318 385:[ battle_menu.unload, ] >> 299:[ table.insert(main.action_queue,{battle_menu.unload, {}}) ]
+10286 387:[ }) ] >> 299:[ table.insert(main.action_queue,{battle_menu.unload, {}}) ]
+10345 388:[ return table.insert(main.action_queue, { ] >> 300:[ table.insert(main.action_queue,{defeat_menu.load,{}}) ]
+10377 389:[ defeat_menu.load, ] >> 300:[ table.insert(main.action_queue,{defeat_menu.load,{}}) ]
+10345 391:[ }) ] >> 300:[ table.insert(main.action_queue,{defeat_menu.load,{}}) ]
+10422 394:[ self.time_ref = msg.time_ref ] >> 302:[ @time_ref = msg.time_ref ]
+10449 395:[ local battle_menu = require("battle_menu") ] >> 303:[ battle_menu = require "battle_menu" ]
+10487 396:[ battle_menu.victory = true ] >> 304:[ battle_menu.victory = true ]
+10516 397:[ return main.root:remove("infocard") ] >> 305:[ main.root\remove("infocard") ]
+10592 400:[ for uname, updated in pairs(msg.data) do ] >> 307:[ for uname, updated in pairs(msg.data) ]
+10612 401:[ local tchar = self.player_party:member(uname) or self.enemy_party:member(uname) ] >> 308:[ tchar = @player_party\member(uname) or @enemy_party\member(uname) ]
+10691 402:[ for k, v in pairs(updated) do ] >> 309:[ for k,v in pairs(updated) ]
+10711 403:[ tchar.data[k] = v ] >> 310:[ tchar.data[k] = v ]
+10681 404:[ end ] >> 309:[ for k,v in pairs(updated) ]
+10571 405:[ end ] >> 307:[ for uname, updated in pairs(msg.data) ]
+10769 408:[ for _, uname in pairs(msg.data) do ] >> 312:[ for _, uname in pairs(msg.data) ]
+10791 409:[ if self.player_party:member(uname) then ] >> 313:[ if @player_party\member(uname) ]
+10824 410:[ local tchar = self.player_party:member(uname) ] >> 314:[ tchar = @player_party\member(uname) ]
+10864 411:[ tchar:die() ] >> 315:[ tchar\die! ]
+10879 412:[ self.player_party:remove_member(tchar) ] >> 316:[ @player_party\remove_member(tchar) ]
+10923 413:[ elseif self.enemy_party:member(uname) then ] >> 317:[ elseif @enemy_party\member(uname) ]
+10955 414:[ local tchar = self.enemy_party:member(uname) ] >> 318:[ tchar = @enemy_party\member(uname) ]
+10994 415:[ tchar:die() ] >> 319:[ tchar\die! ]
+11009 416:[ self.enemy_party:remove_member(tchar) ] >> 320:[ @enemy_party\remove_member(tchar) ]
+10754 418:[ end ] >> 312:[ for _, uname in pairs(msg.data) ]
+11094 421:[ for uname, action_name in pairs(msg.data) do ] >> 322:[ for uname, action_name in pairs(msg.data) ]
+11114 422:[ local action = ability[action_name] ] >> 323:[ action = ability[action_name] ]
+11149 423:[ if self.player_party:member(uname) then ] >> 324:[ if @player_party\member(uname) ]
+11182 424:[ action.use(self, self.player_party, self.player_party:member(uname)) ] >> 325:[ action.use(@,@player_party,@player_party\member(uname)) ]
+11242 425:[ ui.battle_log(string.format("%s used %s", self.player_party:member(uname).class.name, action.text)) ] >> 326:[ ui.battle_log(string.format("%s used %s",@player_party\member(uname).class.name,action.text)) ]
+11345 426:[ elseif self.enemy_party:member(uname) then ] >> 327:[ elseif @enemy_party\member(uname) ]
+11377 427:[ action.use(self, self.enemy_party, self.enemy_party:member(uname)) ] >> 328:[ action.use(@,@enemy_party,@enemy_party\member(uname)) ]
+11435 428:[ ui.battle_log(string.format("%s used %s", uname, action.text)) ] >> 329:[ ui.battle_log(string.format("%s used %s",uname,action.text)) ]
+11069 430:[ end ] >> 322:[ for uname, action_name in pairs(msg.data) ]
+11498 431:[ return main.root:remove("infocard") ] >> 330:[ main.root\remove("infocard") ]
+11544 434:[ local msg = am.eval_js("CLIENT.get()") ] >> 332:[ msg = am.eval_js("CLIENT.get()") ]
+11581 435:[ if msg ~= nil then ] >> 333:[ if msg != nil ]
+11596 436:[ local info = am.parse_json(msg) ] >> 334:[ info = am.parse_json(msg) ]
+11627 437:[ if self[info.msg] then ] >> 335:[ if @[info.msg] ]
+11644 438:[ return self[info.msg](self, info) ] >> 336:[ @[info.msg](@,info) ]
+11676 440:[ return print("Failed to find client message handler", info) ] >> 338:[ print("Failed to find client message handler", info) ]
+11579 442:[ end ] >> 333:[ if msg != nil ]
+11755 445:[ self.localplayer = player ] >> 340:[ @localplayer = player ]
+11779 446:[ self.player_party:add_member(player) ] >> 341:[ @player_party\add_member(player) ]
+11814 447:[ return self.localplayer:enter_room(self.player_party.room) ] >> 342:[ @localplayer\enter_room(@player_party.room) ]
+11881 450:[ self.room = room ] >> 344:[ @room = room ]
+11896 451:[ assert(self.room, "cannot set a nil room") ] >> 345:[ assert(@room, "cannot set a nil room") ]
+11937 452:[ assert(self.room.load, "rooms must have a .load") ] >> 346:[ assert(@room.load, "rooms must have a .load") ]
+11987 453:[ if self.player_party.room then ] >> 347:[ if @player_party.room ]
+12010 454:[ self.player_party.room:unload() ] >> 348:[ @player_party.room\unload! ]
+11985 455:[ end ] >> 347:[ if @player_party.room ]
+12082 456:[ self.room:load() ] >> 350:[ @room\load! ]
+12124 457:[ self.player_party:set_room(room) ] >> 352:[ @player_party\set_room(room) ]
+12157 458:[ if self.enemy_party then ] >> 353:[ if @enemy_party ]
+12176 459:[ if self.enemy_party.room then ] >> 354:[ if @enemy_party.room ]
+12199 460:[ self.enemy_party.room:unload() ] >> 355:[ @enemy_party.room\unload! ]
+12174 461:[ end ] >> 354:[ if @enemy_party.room ]
+12228 462:[ self.enemy_party:set_room(self.room) ] >> 356:[ @enemy_party\set_room(@room) ]
+12155 463:[ end ] >> 353:[ if @enemy_party ]
+12303 464:[ if self.enemy_party then ] >> 358:[ if @enemy_party ]
+12391 465:[ return self.node:append(self.enemy_party.rnode) ] >> 361:[ @node\append(@enemy_party.rnode) ]
+12301 466:[ end ] >> 358:[ if @enemy_party ]
+12439 469:[ return main.root("world_characters"):append(self.node) ] >> 363:[ main.root("world_characters")\append(@node) ]
+7289 475:[ self.client = true ] >> 205:[ @client = true ]
+7306 476:[ self.server = false ] >> 206:[ @server = false ]
+7324 477:[ am.eval_js(connect) ] >> 207:[ am.eval_js(connect) ]
+7346 478:[ self.players = { } ] >> 208:[ @players = {} ]
+7362 479:[ self.player_party = Party() ] >> 209:[ @player_party = Party! ]
+7387 480:[ self.enemy_party = nil ] >> 210:[ @enemy_party = nil ]
+7408 481:[ main.root("world_characters"):append(self.player_party.node) ] >> 211:[ main.root("world_characters")\append(@player_party.node) ]
+7467 482:[ self.parties = { ] >> 212:[ @parties = {@player_party} ]
+7479 483:[ self.player_party ] >> 212:[ @parties = {@player_party} ]
+7467 484:[ } ] >> 212:[ @parties = {@player_party} ]
+7496 485:[ self.node = am.group() ] >> 213:[ @node = am.group! ]
+7516 486:[ self.node:append(self.player_party.node) ] >> 214:[ @node\append(@player_party.node) ]
+7551 487:[ self.node:action(coroutine.create(function() ] >> 215:[ @node\action(coroutine.create(()-> ]
+7594 488:[ while true do ] >> 216:[ while true ]
+7604 489:[ coroutine.yield() ] >> 217:[ coroutine.yield! ]
+7589 490:[ end ] >> 216:[ while true ]
+7551 491:[ end)) ] >> 215:[ @node\action(coroutine.create(()-> ]
+7628 492:[ self.localplayer = nil ] >> 219:[ @localplayer = nil ]
+12485 507:[ mod["World"] = World ] >> 366:[ mod["World"] = World ]
+12506 508:[ mod["Server"] = Server ] >> 367:[ mod["Server"] = Server ]
+12529 509:[ return mod ] >> 368:[ mod ]
diff --git a/rewrite.lua b/rewrite.lua
new file mode 100644
index 0000000..f132993
--- /dev/null
+++ b/rewrite.lua
@@ -0,0 +1,31 @@
+--[[Script to rewrite a stack traceback in terms of moonscript instead of lua]]
+
+for data in io.lines() do
+ local filename, linenum, rest = data:gmatch("(.*%.lua):(%d+):(.*)")()
+ if filename and linenum and rest then
+ local _,_,stripped_filename = filename:find("(%S+)")
+ local moonfilename = filename:gsub(".lua$",".moon")
+
+ --If our file is not moonscript, we won't have a debug file
+ local debugfile = io.open("debug/" .. stripped_filename.. ".X")
+ if not debugfile then
+ print("not debugfile")
+ print(data)
+ goto next
+ end
+
+ --Skip first line
+ debugfile:read("*l")
+ for line in debugfile:lines() do
+ _,_,pos,lua,moon = line:find("(%d+)%s+(%d+):%b[] >> (%d+)")
+ if tonumber(linenum) == tonumber(lua) then
+ print(string.format("\t%s:%d: %s",moonfilename,moon,rest))
+ goto next
+ end
+ end
+ else
+ print(data)
+ end
+ ::next::
+end
+
diff --git a/src/a_brood.moon b/src/a_brood.moon
new file mode 100644
index 0000000..4c94ca6
--- /dev/null
+++ b/src/a_brood.moon
@@ -0,0 +1,42 @@
+reg = require "ability_reg"
+import Ability from reg
+
+class Brood extends Ability
+ @text = "Brood"
+ @description = "brood"
+ @hits_icon = {1,1,1,1,0,0,0,0}
+ @speed = 5
+ @distance = 1
+ new: (...)=>
+ super(...)
+ @requirements = {
+ {"consume_stat", "stamina", 1},
+ {"status", "active"},
+ {"distance", 1},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ enemy_party = room.parties[1]
+ if enemy_party == party
+ enemy_party = room.parties[2]
+ use: (world, party, char)->
+ for i = 1,4
+ chars_at_loc = world.room.data.locations[i]
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.player_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 1)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,i, hp_minus)
+ load: ()=>
+ print("TODO!")
+
+ unload:()=>
+ print("TODO!")
diff --git a/src/a_dance.moon b/src/a_dance.moon
new file mode 100644
index 0000000..1b07ddb
--- /dev/null
+++ b/src/a_dance.moon
@@ -0,0 +1,49 @@
+reg = require "ability_reg"
+ui = require "ui"
+import Ability from reg
+
+mod = ...
+
+class Dance extends Ability
+ @text = "Dance"
+ @description = "Shake your body!"
+ @hits_icon = {0,0,0,0,0,1,1,0}
+ @sprite = "data/body-balance.png"
+ @speed = 7
+ @distance = 1
+ new: (...)=>
+ super("Dance",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ for _,i in pairs({6,7})
+ chars_at_loc = world.room.data.locations[i]
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 1)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,1, hp_minus)
+
+mod.Tumble = Tumble
+
+mod
diff --git a/src/a_drum.moon b/src/a_drum.moon
new file mode 100644
index 0000000..4464816
--- /dev/null
+++ b/src/a_drum.moon
@@ -0,0 +1,48 @@
+reg = require "ability_reg"
+ui = require "ui"
+import Ability from reg
+
+mod = ...
+
+class Drum extends Ability
+ @text = "Drum"
+ @description = "Rat-a-tat-tat!"
+ @hits_icon = {0,0,0,0,1,0,0,0}
+ @sprite = "data/drum.png"
+ @speed = 5
+ @distance = 1
+ new: (...)=>
+ super("Drum",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ chars_at_loc = world.room.data.locations[5]
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 1)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,1, hp_minus)
+
+mod.Tumble = Tumble
+
+mod
diff --git a/src/a_firebreath.moon b/src/a_firebreath.moon
new file mode 100644
index 0000000..e8c9f73
--- /dev/null
+++ b/src/a_firebreath.moon
@@ -0,0 +1,51 @@
+reg = require "ability_reg"
+import Ability from reg
+
+mod = ...
+print("In tubmle, reg is",reg)
+
+class FireBreath extends Ability
+ @text = "Breath Fire"
+ @description = "Breath fire, visible for everyone!"
+ @sprite = "data/dragon-breath.png"
+ @hits_icon = {0,0,0,0,1,1,1,1}
+ @speed = 10
+ @distance = 1
+ new: (...)=>
+ super("FireBreath",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ print("Doing FireBreath")
+ for i = 5,8
+ chars_at_loc = world.room.data.locations[i]
+ print("chars at loc:",chars_at_loc)
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 1)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,i - 4, hp_minus)
+
+mod.Tumble = Tumble
+
+mod
diff --git a/src/a_hackysacks.moon b/src/a_hackysacks.moon
new file mode 100644
index 0000000..2939c80
--- /dev/null
+++ b/src/a_hackysacks.moon
@@ -0,0 +1,50 @@
+reg = require "ability_reg"
+import Ability from reg
+
+mod = ...
+print("In tubmle, reg is",reg)
+
+class Juggle extends Ability
+ @text = "Juggle Balls"
+ @description = "Juggle some balls"
+ @hits_icon = {0,0,0,0,1,1,0,0}
+ @sprite = "data/juggler.png"
+ @speed = 4
+ @distance = 1
+ new: (...)=>
+ super("Juggle",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ for i = 5,6
+ chars_at_loc = world.room.data.locations[i]
+ print("chars at loc:",chars_at_loc)
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 1)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,i-4, hp_minus)
+
+mod.Tumble = Tumble
+
+mod
diff --git a/src/a_highjump.moon b/src/a_highjump.moon
new file mode 100644
index 0000000..878dc4f
--- /dev/null
+++ b/src/a_highjump.moon
@@ -0,0 +1,49 @@
+reg = require "ability_reg"
+ui = require "ui"
+import Ability from reg
+
+mod = ...
+
+class HighJump extends Ability
+ @text = "High Jump"
+ @description = "Jump up high. Like REALLY high."
+ @hits_icon = {0,0,0,0,0,0,1,1}
+ @sprite = "data/kangaroo.png"
+ @speed = 3
+ @distance = 1
+ new: (...)=>
+ super("Tumble",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ for _,i in pairs({7,8})
+ chars_at_loc = world.room.data.locations[i]
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 1)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,1, hp_minus)
+
+mod.Tumble = Tumble
+
+mod
diff --git a/src/a_knifeslip.moon b/src/a_knifeslip.moon
new file mode 100644
index 0000000..89c8b55
--- /dev/null
+++ b/src/a_knifeslip.moon
@@ -0,0 +1,51 @@
+reg = require "ability_reg"
+import Ability from reg
+
+mod = ...
+print("In tubmle, reg is",reg)
+
+class KnifeSlip extends Ability
+ @text = "Juggle Knives"
+ @description = "Juggle some knives for\nthe people in the back"
+ @hits_icon = {0,0,0,0,0,0,1,1}
+ @sprite = "data/thrown-knife.png"
+ @speed = 4
+ @distance = 1
+ new: (...)=>
+ super("KnifeSlip",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ print("Doing knifeslip")
+ for i = 7,8
+ chars_at_loc = world.room.data.locations[i]
+ print("chars at loc:",chars_at_loc)
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 2)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,i-4, hp_minus)
+
+mod.Tumble = Tumble
+
+mod
diff --git a/src/a_mope.moon b/src/a_mope.moon
new file mode 100644
index 0000000..8ea4cd1
--- /dev/null
+++ b/src/a_mope.moon
@@ -0,0 +1,41 @@
+reg = require "ability_reg"
+import Ability from reg
+
+class Mope extends Ability
+ @text = "Mope"
+ @description = "mope"
+ @hits_icon = {0,0,0,1,0,0,0,0}
+ @speed = 5
+ @distance = 1
+ new: (...)=>
+ super(...)
+ @requirements = {
+ {"consume_stat", "stamina", 1},
+ {"status", "active"},
+ {"distance", 1},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ enemy_party = room.parties[1]
+ if enemy_party == party
+ enemy_party = room.parties[2]
+ use: (world, party, char)->
+ chars_at_loc = world.room.data.locations[4]
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.player_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 2)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,1, hp_minus)
+ load: ()=>
+ print("TODO!")
+
+ unload:()=>
+ print("TODO!")
diff --git a/src/a_pass.moon b/src/a_pass.moon
new file mode 100644
index 0000000..1f9ed21
--- /dev/null
+++ b/src/a_pass.moon
@@ -0,0 +1,34 @@
+
+reg = require "ability_reg"
+import Ability from reg
+
+mod = ...
+
+class Pass extends Ability
+ @text = "Pass"
+ @description = "Do nothing..."
+ @hits_icon = {0,0,0,0,0,0,0,0}
+ @speed = 0
+ @sprite = "data/no_action.png"
+ new: (...)=>
+ super("Pass",{})
+ @requirements = {}
+ target: (world, party, char) =>
+ nil
+
+ load: () =>
+ main = require "main"
+ infocard = am.group!\tag("infocard")
+ main.root("screen")\append(infocard)
+
+ unload: () =>
+ main = require "main"
+ infocard = main.root("infocard")
+ main.root("screen")\remove(infocard)
+
+ use: (world, party, char)=>
+ print("Pass used")
+
+mod.Pass = Pass
+
+mod
diff --git a/src/a_physique.moon b/src/a_physique.moon
new file mode 100644
index 0000000..6f1b94b
--- /dev/null
+++ b/src/a_physique.moon
@@ -0,0 +1,48 @@
+reg = require "ability_reg"
+ui = require "ui"
+import Ability from reg
+
+mod = ...
+
+class Physique extends Ability
+ @text = "Physique"
+ @description = "Big ol' muscles"
+ @hits_icon = {0,0,0,0,0,1,0,0}
+ @sprite = "data/strong-man.png"
+ @speed = 3
+ @distance = 1
+ new: (...)=>
+ super("Physique",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ chars_at_loc = world.room.data.locations[6]
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 2)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,2, hp_minus)
+
+mod.Tumble = Tumble
+
+mod
diff --git a/src/a_rat_bite.moon b/src/a_rat_bite.moon
new file mode 100644
index 0000000..9c55269
--- /dev/null
+++ b/src/a_rat_bite.moon
@@ -0,0 +1,42 @@
+reg = require "ability_reg"
+import Ability from reg
+
+class RatBite extends Ability
+ @text = "Rat Bite"
+ @description = "A rat's bite"
+ @hits_icon = {0,0,0,0,1,0,0,0}
+ @speed = 3
+ @distance = 1
+ new: (...)=>
+ super(...)
+ @requirements = {
+ {"consume_stat", "stamina", 1},
+ {"status", "active"},
+ {"distance", 1},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ enemy_party = room.parties[1]
+ if enemy_party == party
+ enemy_party = room.parties[2]
+ use: (world, party, char)->
+ chars_at_loc = world.room.data.locations[4]
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.player_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 1)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,1, hp_minus)
+ print("rat bite done, chars_at_loc:",chars_at_loc)
+ load: ()=>
+ print("TODO!")
+
+ unload:()=>
+ print("TODO!")
diff --git a/src/a_rat_scurry.moon b/src/a_rat_scurry.moon
new file mode 100644
index 0000000..cd00e0a
--- /dev/null
+++ b/src/a_rat_scurry.moon
@@ -0,0 +1,25 @@
+reg = require "ability_reg"
+import Ability from reg
+
+class RatScurry extends Ability
+ @text = "Scurry"
+ @speed = 5
+ @distance = 1
+ new: (...)=>
+ super(...)
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ enemy_party = room.parties[1]
+ if enemy_party == party
+ enemy_party = room.parties[2]
+ use: (world, party, char)->
+ print("Rat scurry used")
+
+ load: ()=>
+ print("TODO!")
+
+ unload:()=>
+ print("TODO!")
diff --git a/src/a_ruminate.moon b/src/a_ruminate.moon
new file mode 100644
index 0000000..cf84eef
--- /dev/null
+++ b/src/a_ruminate.moon
@@ -0,0 +1,29 @@
+reg = require "ability_reg"
+import Ability from reg
+
+class Ruminate extends Ability
+ @text = "Ruminate"
+ @description = "Ruminate"
+ @hits_icon = {0,0,0,0,1,1,1,1}
+ @speed = 5
+ @distance = 1
+ new: (...)=>
+ super(...)
+ @requirements = {
+ {"consume_stat", "stamina", 1},
+ {"status", "active"},
+ {"distance", 1},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ enemy_party = room.parties[1]
+ if enemy_party == party
+ enemy_party = room.parties[2]
+ use: (world, party, char)->
+ for _, member in pairs(party.members)
+ char\set_field("hp",char.data.hp + 1)
+ load: ()=>
+ print("TODO!")
+
+ unload:()=>
+ print("TODO!")
diff --git a/src/a_strum.moon b/src/a_strum.moon
new file mode 100644
index 0000000..392a582
--- /dev/null
+++ b/src/a_strum.moon
@@ -0,0 +1,37 @@
+reg = require "ability_reg"
+import Ability from reg
+
+mod = ...
+print("In tubmle, reg is",reg)
+
+class Strum extends Ability
+ @text = "Strum"
+ @description = "Strum a cord to heal the troupe!"
+ @hits_icon = {1,1,1,1,0,0,0,0}
+ @sprite = "data/g-clef.png"
+ @speed = 1
+ @distance = 1
+ new: (...)=>
+ super("Strum",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ print("Doing Strum, party was", party)
+ for _, member in pairs(party.members)
+ member\set_field("hp",member.data.hp + 1)
+
+mod.Tumble = Tumble
+
+mod
diff --git a/src/a_sulk.moon b/src/a_sulk.moon
new file mode 100644
index 0000000..bf5d0a3
--- /dev/null
+++ b/src/a_sulk.moon
@@ -0,0 +1,43 @@
+reg = require "ability_reg"
+import Ability from reg
+
+class Sulk extends Ability
+ @text = "Sulk"
+ @description = "sulk"
+ @hits_icon = {0,0,1,1,0,0,0,0}
+ @speed = 3
+ @distance = 1
+ new: (...)=>
+ super(...)
+ @requirements = {
+ {"consume_stat", "stamina", 1},
+ {"status", "active"},
+ {"distance", 1},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ enemy_party = room.parties[1]
+ if enemy_party == party
+ enemy_party = room.parties[2]
+ use: (world, party, char)->
+ for i = 3,4
+ chars_at_loc = world.room.data.locations[i]
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.player_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 1)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,8 - i, hp_minus)
+ print("rat bite done, chars_at_loc:",chars_at_loc)
+ load: ()=>
+ print("TODO!")
+
+ unload:()=>
+ print("TODO!")
diff --git a/src/a_test.moon b/src/a_test.moon
new file mode 100644
index 0000000..5c8f1b0
--- /dev/null
+++ b/src/a_test.moon
@@ -0,0 +1,52 @@
+reg = require "ability_reg"
+ui = require "ui"
+import Ability from reg
+
+mod = ...
+print("In tubmle, reg is",reg)
+
+class Everything extends Ability
+ @text = "Everything"
+ @description = "destroy everything"
+ @hits_icon = {1,1,1,1,1,1,1,1}
+ @sprite = "data/tumble.png"
+ @speed = 5
+ @distance = 1
+ new: (...)=>
+ super("Everything",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ for i = 5,8
+ print("Doing tumble")
+ chars_at_loc = world.room.data.locations[i]
+ print("chars at loc:",chars_at_loc)
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 10)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,i-4, hp_minus)
+
+mod.Test = Test
+
+mod
diff --git a/src/a_tumble.moon b/src/a_tumble.moon
new file mode 100644
index 0000000..d3c38bd
--- /dev/null
+++ b/src/a_tumble.moon
@@ -0,0 +1,51 @@
+reg = require "ability_reg"
+ui = require "ui"
+import Ability from reg
+
+mod = ...
+print("In tubmle, reg is",reg)
+
+class Tumble extends Ability
+ @text = "Tumble"
+ @description = "Tumble around for those nearby"
+ @hits_icon = {0,0,0,0,1,0,0,0}
+ @sprite = "data/tumble.png"
+ @speed = 5
+ @distance = 1
+ new: (...)=>
+ super("Tumble",{})
+ @requirements = {
+ {"status", "active"},
+ }
+ target: (world, party, char) =>
+ room = world.player_party.room
+ my_pos = char.location
+ --search outward for a target
+ char_tbl1, char_tbl2 = nil, nil
+ for distance = 1, 8
+ char_tbl1 = room\at_location(my_pos + distance)
+ char_tbl2 = room\at_location(my_pos - distance)
+ if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0)
+ break
+
+ use: (world, party, char)->
+ print("Doing tumble")
+ chars_at_loc = world.room.data.locations[5]
+ print("chars at loc:",chars_at_loc)
+ if world.server
+ a_chars_at_loc = {}
+ for _,v in ipairs(chars_at_loc)
+ table.insert(a_chars_at_loc,world.enemy_party\member(v.uname))
+ chars_at_loc = a_chars_at_loc
+ hp_minus = () ->
+ for _, char in pairs(chars_at_loc)
+ char\set_field("hp",char.data.hp - 3)
+ if world.server
+ hp_minus!
+ if world.client
+ ui = ui or require "ui"
+ ui.tween_hit(char,1, hp_minus)
+
+mod.Tumble = Tumble
+
+mod
diff --git a/src/ability_reg.moon b/src/ability_reg.moon
new file mode 100644
index 0000000..4724043
--- /dev/null
+++ b/src/ability_reg.moon
@@ -0,0 +1,43 @@
+ui = require "ui"
+reg = ...
+
+reg.list = {}
+
+-- name - string, the name of the ability
+-- infocard, the table of info and description to draw when looking at the ability
+-- data, the table that contains data and functions for running the ability
+-- visuals, the table of sprites that the data needs to function
+class Ability
+ @children = {}
+ new: (name, data) =>
+ @sprite = @@sprite
+ @name = name or @.__class.__name
+ @data = data
+
+ @__inherited: (child) =>
+ assert(child.use, "abilities must have a .use")
+ assert(type(child.use) == "function", "abilities must have a .use() function")
+ assert(type(child.load) == "function", "abilities must have a .load() that shows their infocard")
+ assert(type(child.unload) == "function", "abilities must have an .unload() that removes their infocard")
+ assert(child.text, "ability must have text")
+ assert(child.description, "ability must have a description")
+ assert(child.hits_icon, "ability must have a hits icon")
+ assert(child.speed, "ability must have a speed")
+ @@.children[child.__name] = child
+ reg[child.__name] = child
+
+ load: () =>
+ main = require "main"
+ infocard = am.group!\tag("infocard")
+ main.root("screen")\append(infocard)
+ ui.build_infocard(infocard,@@)
+
+ unload: () =>
+ main = require "main"
+ main.root\remove("infocard")
+ --__tostring: () =>
+ -- return string.format("<%s, %s>",@.__class.__name, @name or ".name missing")
+
+reg["Ability"] = Ability
+
+return reg
diff --git a/src/action.moon b/src/action.moon
new file mode 100644
index 0000000..e9b1150
--- /dev/null
+++ b/src/action.moon
@@ -0,0 +1,58 @@
+-- Actions to send to the server, and to receive
+char = require "char"
+mod = ...
+
+mod.msg = {
+ "request_class_change" ,--client to server request
+ "confirm_class_change" ,-- server to client answer
+ "deny_class_change" ,-- server to client answer
+ "info_class_change" ,-- server to client info
+ "player_joined" ,--client to server hello
+ "info_player_joined" ,-- server to client info
+ "info_player_dropped" ,-- server to client info
+ "request_campaign_start" , --client to server, start game
+ "info_campaign_start" , --server to client, game is starting
+ "request_player_list" ,-- client to server request
+ "respond_player_list" ,--server to client respond with player list
+ "info_room" ,-- server to client notify about room info
+ "info_turnup", -- sever to client, when is the turn up?
+ "info_enemy_party", --server to client notify about enemy party
+ "set_action" ,-- client to server, notify action for next turn
+ "info_timeref" , --server to client, when the next turn is up
+ "info_actions" ,-- server to client set animations to play
+ "info_deaths", -- server to client, deaths that are about to happen
+ "info_loot" ,--server to client loot for clearing a room
+ "request_camp_action" ,--client to server what action to take at camp
+ "info_camp" ,--server to client actions taken at camp
+ "info_defeat", --server to client, you're done!
+}
+mod.msg_rev = {}
+for k,v in pairs(mod.msg) do
+ mod.msg_rev[v] = k
+
+mod.action_reg = {}
+mod.class_counter = 1
+
+mod.request_class_change = (what) ->
+ assert(what, "class may not be nil")
+ assert(char.class_order_rev[what], "class must be one of " .. tostring(char.class_order))
+ msg_json = am.to_json({msg:"request_class_change", time:am.current_time(), class:what})
+ am.eval_js(string.format("CLIENT.send(%q);",msg_json))
+
+mod.start_game = () ->
+ msg_json = am.to_json({msg:"request_campaign_start", time:am.current_time()})
+ am.eval_js(string.format("CLIENT.send(%q);",msg_json))
+
+mod.sync_players = () ->
+ msg_json = am.to_json({msg:"request_player_list", time:am.current_time()})
+ am.eval_js(string.format("CLIENT.send(%q);",msg_json))
+
+mod.set_action = (name) ->
+ msg_json = am.to_json({
+ msg: "set_action"
+ time: am.current_time!
+ action: name
+ })
+ am.eval_js(string.format("CLIENT.send(%q);",msg_json))
+
+mod
diff --git a/src/battle_menu.moon b/src/battle_menu.moon
new file mode 100644
index 0000000..84b8aa9
--- /dev/null
+++ b/src/battle_menu.moon
@@ -0,0 +1,129 @@
+world = require "world"
+main = require "main"
+color = require "color"
+ui = require "ui"
+bp = require "broadphase"
+pass = require "a_pass"
+import Pass from pass
+action = require "action"
+
+mod = ...
+
+pip_width = 24
+
+mod.time_pips = am.group!
+pip_x_start = ((main.pips/2)+1) * -pip_width
+pip_sprites = {}
+ms_per_pip = 1000
+
+mod.ability_selector = am.group!
+mod.playerturn_up = 0
+
+mod.set_playerturn_up = (time) =>
+ mod.playerturn_up = time
+
+mod.victory_g = am.group!
+mod.victory_text = {}
+
+mod.action_phase = am.group! --group used to animate characters during battle, also holds dammage values
+mod.victory_show = false
+mod.loaded_ability = nil
+
+mod.load = () ->
+ pip_trans = am.translate(pip_x_start,240 - (pip_width/2))
+ for i = 1,main.pips
+ pip_loc = am.translate(pip_width*i,0)
+ pip_trans\append(pip_loc^ am.sprite("data/pip_frame.png",color.white))
+ light_sprite = am.sprite("data/pip_dark.png",color.white)
+ pip_loc\append(light_sprite)
+ table.insert(pip_sprites,light_sprite)
+ mod.time_pips\append(pip_trans)
+ ability_buttons = {}
+ ability_trans = am.group!
+ main.root("screen")\append(mod.victory_g)
+ for k,v in pairs({"V","I","C","T","O","R","Y"})
+ n = am.scale(2)^am.translate((k-3.5)*20,0)^am.text(v,color.fg)
+ n\action(coroutine.create(() ->
+ while true
+ for k,v in pairs(mod.victory_text)
+ v.hidden = not mod.victory
+ coroutine.yield!
+ ))
+ n\action(am.series({
+ am.delay(k*0.25),
+ am.loop(() -> am.series({
+ am.tween(n("translate"),1,{y:30},am.ease.sine),
+ am.tween(n("translate"),1,{y:0},am.ease.sine)
+ }))
+ }))
+ table.insert(mod.victory_text,n)
+ mod.victory_g\append(n)
+ mod.ability_selector\append(ability_trans)
+ for i = 1,4
+ trans_x = (-150 * (i-2))+32
+ trans_y = 200
+ --ui.create_button needs to be relative to global for hitboxes to work right
+ ability_slot_button = ui.create_any_button(ability_trans,3,3,trans_x,trans_y)
+ lpd = main.world.localplayer.data
+ print("lpd was:",lpd)
+ ability = lpd.abilities[i] or Pass
+ print("Ability was",ability.__name)
+ print("Sprite was",ability and ability.sprite)
+ ability_slot_icon = am.sprite(ability and ability.sprite or "data/no_action.png", color.white, "left","top")\tag("icon")
+ ability_slot_button.name = ability.__name
+ ability_slot_button.ability = ability
+ ability_slot_button.node\append(am.translate(trans_x + 16,trans_y - 16)^ ability_slot_icon)
+ table.insert(ability_buttons, ability_slot_button)
+ print("test")
+ main.root("screen")\append(mod.time_pips)
+ main.root("screen")\append(mod.ability_selector)
+ main.root("screen")\append(mod.action_phase)
+ mod.time_pips\action(coroutine.create(() ->
+ print("Pips in action:",pip_sprites)
+ while true
+ if not mod.victory
+ currtime = am.eval_js("new Date().getTime()")
+ elapsed_time = currtime - main.world.time_ref
+ npips = math.min(math.floor(elapsed_time/ms_per_pip),main.pips)
+ for i = 1,npips
+ pip_sprites[i].source = "data/pip_light.png"
+ for i = npips+2,main.pips
+ if i > 0 and i <= main.pips
+ pip_sprites[i].source = "data/pip_dark.png"
+ coroutine.yield!
+ ))
+ touch_indicator = am.group!
+ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top")
+ touch_loc = am.translate(0,0)^ touch_cursor
+ touch_indicator\append(touch_loc)
+ mod.ability_selector\append(touch_indicator)
+ mod.ability_selector\action(coroutine.create(() ->
+ while true
+ if #main.win\active_touches! > 0
+ touch = main.win\touch_position(1)
+ touch_cursor.color = vec4(1,1,1,1)
+ touch_loc.x = touch.x
+ touch_loc.y = touch.y
+ col_but = bp.check(touch.x, touch.y + 96)
+ if #col_but > 0
+ touch_cursor.color = vec4(0.5,0.5,0.5,0.5)
+ if main.win\touch_began(1)
+ print("Checking ability buttons:",ability_buttons, "against col but:",col_but)
+ for _,button in ipairs(ability_buttons)
+ if button == col_but[1]
+ print("Found button")
+ action.set_action(button.name)
+ if mod.loaded_ability
+ mod.loaded_ability\unload!
+ button.ability\load!
+ mod.loaded_ability = ability
+ coroutine.yield!
+ ))
+
+
+mod.unload = () ->
+ main.root("screen")\remove(mod.time_pips)
+ main.root("screen")\remove(mod.ability_selector)
+ main.root("screen")\remove(mod.action_phase)
+
+mod
diff --git a/src/broadphase.moon b/src/broadphase.moon
new file mode 100644
index 0000000..540680e
--- /dev/null
+++ b/src/broadphase.moon
@@ -0,0 +1,69 @@
+
+--[[I was going to write my own physics engine, but decided to just use a library]]
+
+--[[Keeps track of the collisions in the world]]
+mod = ...
+util = require "util"
+bump = require "bump"
+main = require "main" --for debugging
+import Vec3 from util
+
+mod.world = bump.newWorld(8)
+--passes the bottom left x,y and the width,height of the object
+mod.add = (ref,x,y,width,height) ->
+ print("Making phys for ref:",ref)
+ --print("At the time of adding, main.screenpos is",main.screenpos, "x:", x, "y:",y)
+ --print("Width:",width, "height", height)
+ --print("ref.node is", ref.node)
+ graphic_x = 0--x/4
+ graphic_y = 0--y --+ (height / 2)
+ --print("graphic_y:",graphic_y)
+ physx = x
+ physy = y
+ --lx = x - main.screenpos.x - (width / 2)
+ --ly = y - main.screenpos.y - (height / 2)
+ lx = x --+ (width / 2)
+ ly = y --+ (height / 2)
+ --tn = am.translate(lx,ly)\tag("position")
+ --node = tn ^ am.rect(0,0,width,height,vec4(0.5,0.5,0.5,0.5))\tag("sprite")
+ --(main.world("world_platforms"))\append(node) --for debugging
+ --print("ref node:",ref.node)
+ --print("positionnode:",(ref.node)("position"))
+ --print("ref's children:")
+ --for k,v in ref.node\child_pairs()
+ --print(k,v)
+ --[[The debug display for the physics box]]
+ positionnode = ref.node
+ --positionnode\append(am.rect(-width/2,-height/2, width/2,height/2,vec4(1.0,0.0,0.0,1.0))\tag("dsprite"))
+ --positionnode\append(am.rect(x , y , x + width, y - height,vec4(1.0,0.0,0.0,0.5))\tag("dsprite"))
+ --positionnode\append(am.rect(lx,ly,lx + width,ly + height,vec4(0.5,0.5,0.5,0.5))\tag("dsprite"))
+ --[[The actual physics object for bump.lua]]
+ mod.world\add(ref,physx,physy,width,height)
+ --mod.world\add(ref,x - (width/2),y,width,height)
+ print("physics node:",node)
+ node
+
+mod.update = (ref,x,y,newwidth,newheight) ->
+ mod.world\update(ref,x,y,newwidth,newheight)
+
+mod.move = (ref,x,y,filter) ->
+ mod.world\move(ref,x,y,filter)
+
+mod.remove = (ref) ->
+ mod.world\remove(ref)
+
+mod.check = (x,y) ->
+ mod.world\queryPoint(x,y)
+
+
+class PhysGroup
+ new: (offset, size, extra = {}) =>
+ @offset = offset
+ @size = size
+ @extra = extra
+
+mod.gravity = 0.5
+mod.term_fall_vel = 10
+mod.PhysGroup = PhysGroup
+mod
+
diff --git a/src/bump.lua b/src/bump.lua
new file mode 100644
index 0000000..6dabca7
--- /dev/null
+++ b/src/bump.lua
@@ -0,0 +1,773 @@
+local bump = {
+ _VERSION = 'bump v3.1.7',
+ _URL = 'https://github.com/kikito/bump.lua',
+ _DESCRIPTION = 'A collision detection library for Lua',
+ _LICENSE = [[
+ MIT LICENSE
+
+ Copyright (c) 2014 Enrique García Cota
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ ]]
+}
+
+------------------------------------------
+-- Auxiliary functions
+------------------------------------------
+local DELTA = 1e-10 -- floating-point margin of error
+
+local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max
+
+local function sign(x)
+ if x > 0 then return 1 end
+ if x == 0 then return 0 end
+ return -1
+end
+
+local function nearest(x, a, b)
+ if abs(a - x) < abs(b - x) then return a else return b end
+end
+
+local function assertType(desiredType, value, name)
+ if type(value) ~= desiredType then
+ error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')')
+ end
+end
+
+local function assertIsPositiveNumber(value, name)
+ if type(value) ~= 'number' or value <= 0 then
+ error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')')
+ end
+end
+
+local function assertIsRect(x,y,w,h)
+ assertType('number', x, 'x')
+ assertType('number', y, 'y')
+ assertIsPositiveNumber(w, 'w')
+ assertIsPositiveNumber(h, 'h')
+end
+
+local defaultFilter = function()
+ return 'slide'
+end
+
+------------------------------------------
+-- Rectangle functions
+------------------------------------------
+
+local function rect_getNearestCorner(x,y,w,h, px, py)
+ return nearest(px, x, x+w), nearest(py, y, y+h)
+end
+
+-- This is a generalized implementation of the liang-barsky algorithm, which also returns
+-- the normals of the sides where the segment intersects.
+-- Returns nil if the segment never touches the rect
+-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge
+local function rect_getSegmentIntersectionIndices(x,y,w,h, x1,y1,x2,y2, ti1,ti2)
+ ti1, ti2 = ti1 or 0, ti2 or 1
+ local dx, dy = x2-x1, y2-y1
+ local nx, ny
+ local nx1, ny1, nx2, ny2 = 0,0,0,0
+ local p, q, r
+
+ for side = 1,4 do
+ if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left
+ elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right
+ elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top
+ else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom
+ end
+
+ if p == 0 then
+ if q <= 0 then return nil end
+ else
+ r = q / p
+ if p < 0 then
+ if r > ti2 then return nil
+ elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny
+ end
+ else -- p > 0
+ if r < ti1 then return nil
+ elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny
+ end
+ end
+ end
+ end
+
+ return ti1,ti2, nx1,ny1, nx2,ny2
+end
+
+-- Calculates the minkowsky difference between 2 rects, which is another rect
+local function rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
+ return x2 - x1 - w1,
+ y2 - y1 - h1,
+ w1 + w2,
+ h1 + h2
+end
+
+local function rect_containsPoint(x,y,w,h, px,py)
+ return px - x > DELTA and py - y > DELTA and
+ x + w - px > DELTA and y + h - py > DELTA
+end
+
+local function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2)
+ return x1 < x2+w2 and x2 < x1+w1 and
+ y1 < y2+h2 and y2 < y1+h1
+end
+
+local function rect_getSquareDistance(x1,y1,w1,h1, x2,y2,w2,h2)
+ local dx = x1 - x2 + (w1 - w2)/2
+ local dy = y1 - y2 + (h1 - h2)/2
+ return dx*dx + dy*dy
+end
+
+local function rect_detectCollision(x1,y1,w1,h1, x2,y2,w2,h2, goalX, goalY)
+ goalX = goalX or x1
+ goalY = goalY or y1
+
+ local dx, dy = goalX - x1, goalY - y1
+ local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
+
+ local overlaps, ti, nx, ny
+
+ if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other
+ local px, py = rect_getNearestCorner(x,y,w,h, 0, 0)
+ local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection
+ ti = -wi * hi -- ti is the negative area of intersection
+ overlaps = true
+ else
+ local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge)
+
+ -- item tunnels into other
+ if ti1
+ and ti1 < 1
+ and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner
+ and (0 < ti1 + DELTA
+ or 0 == ti1 and ti2 > 0)
+ then
+ ti, nx, ny = ti1, nx1, ny1
+ overlaps = false
+ end
+ end
+
+ if not ti then return end
+
+ local tx, ty
+
+ if overlaps then
+ if dx == 0 and dy == 0 then
+ -- intersecting and not moving - use minimum displacement vector
+ local px, py = rect_getNearestCorner(x,y,w,h, 0,0)
+ if abs(px) < abs(py) then py = 0 else px = 0 end
+ nx, ny = sign(px), sign(py)
+ tx, ty = x1 + px, y1 + py
+ else
+ -- intersecting and moving - move in the opposite direction
+ local ti1, _
+ ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1)
+ if not ti1 then return end
+ tx, ty = x1 + dx * ti1, y1 + dy * ti1
+ end
+ else -- tunnel
+ tx, ty = x1 + dx * ti, y1 + dy * ti
+ end
+
+ return {
+ overlaps = overlaps,
+ ti = ti,
+ move = {x = dx, y = dy},
+ normal = {x = nx, y = ny},
+ touch = {x = tx, y = ty},
+ itemRect = {x = x1, y = y1, w = w1, h = h1},
+ otherRect = {x = x2, y = y2, w = w2, h = h2}
+ }
+end
+
+------------------------------------------
+-- Grid functions
+------------------------------------------
+
+local function grid_toWorld(cellSize, cx, cy)
+ return (cx - 1)*cellSize, (cy-1)*cellSize
+end
+
+local function grid_toCell(cellSize, x, y)
+ return floor(x / cellSize) + 1, floor(y / cellSize) + 1
+end
+
+-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing",
+-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf
+-- It has been modified to include both cells when the ray "touches a grid corner",
+-- and with a different exit condition
+
+local function grid_traverse_initStep(cellSize, ct, t1, t2)
+ local v = t2 - t1
+ if v > 0 then
+ return 1, cellSize / v, ((ct + v) * cellSize - t1) / v
+ elseif v < 0 then
+ return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v
+ else
+ return 0, math.huge, math.huge
+ end
+end
+
+local function grid_traverse(cellSize, x1,y1,x2,y2, f)
+ local cx1,cy1 = grid_toCell(cellSize, x1,y1)
+ local cx2,cy2 = grid_toCell(cellSize, x2,y2)
+ local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2)
+ local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2)
+ local cx,cy = cx1,cy1
+
+ f(cx, cy)
+
+ -- The default implementation had an infinite loop problem when
+ -- approaching the last cell in some occassions. We finish iterating
+ -- when we are *next* to the last cell
+ while abs(cx - cx2) + abs(cy - cy2) > 1 do
+ if tx < ty then
+ tx, cx = tx + dx, cx + stepX
+ f(cx, cy)
+ else
+ -- Addition: include both cells when going through corners
+ if tx == ty then f(cx + stepX, cy) end
+ ty, cy = ty + dy, cy + stepY
+ f(cx, cy)
+ end
+ end
+
+ -- If we have not arrived to the last cell, use it
+ if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end
+
+end
+
+local function grid_toCellRect(cellSize, x,y,w,h)
+ local cx,cy = grid_toCell(cellSize, x, y)
+ local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize)
+ return cx, cy, cr - cx + 1, cb - cy + 1
+end
+
+------------------------------------------
+-- Responses
+------------------------------------------
+
+local touch = function(world, col, x,y,w,h, goalX, goalY, filter)
+ return col.touch.x, col.touch.y, {}, 0
+end
+
+local cross = function(world, col, x,y,w,h, goalX, goalY, filter)
+ local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
+ return goalX, goalY, cols, len
+end
+
+local slide = function(world, col, x,y,w,h, goalX, goalY, filter)
+ goalX = goalX or x
+ goalY = goalY or y
+
+ local tch, move = col.touch, col.move
+ if move.x ~= 0 or move.y ~= 0 then
+ if col.normal.x ~= 0 then
+ goalX = tch.x
+ else
+ goalY = tch.y
+ end
+ end
+
+ col.slide = {x = goalX, y = goalY}
+
+ x,y = tch.x, tch.y
+ local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
+ return goalX, goalY, cols, len
+end
+
+local bounce = function(world, col, x,y,w,h, goalX, goalY, filter)
+ goalX = goalX or x
+ goalY = goalY or y
+
+ local tch, move = col.touch, col.move
+ local tx, ty = tch.x, tch.y
+
+ local bx, by = tx, ty
+
+ if move.x ~= 0 or move.y ~= 0 then
+ local bnx, bny = goalX - tx, goalY - ty
+ if col.normal.x == 0 then bny = -bny else bnx = -bnx end
+ bx, by = tx + bnx, ty + bny
+ end
+
+ col.bounce = {x = bx, y = by}
+ x,y = tch.x, tch.y
+ goalX, goalY = bx, by
+
+ local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
+ return goalX, goalY, cols, len
+end
+
+------------------------------------------
+-- World
+------------------------------------------
+
+local World = {}
+local World_mt = {__index = World}
+
+-- Private functions and methods
+
+local function sortByWeight(a,b) return a.weight < b.weight end
+
+local function sortByTiAndDistance(a,b)
+ if a.ti == b.ti then
+ local ir, ar, br = a.itemRect, a.otherRect, b.otherRect
+ local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h)
+ local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h)
+ return ad < bd
+ end
+ return a.ti < b.ti
+end
+
+local function addItemToCell(self, item, cx, cy)
+ self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'})
+ local row = self.rows[cy]
+ row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})}
+ local cell = row[cx]
+ self.nonEmptyCells[cell] = true
+ if not cell.items[item] then
+ cell.items[item] = true
+ cell.itemCount = cell.itemCount + 1
+ end
+end
+
+local function removeItemFromCell(self, item, cx, cy)
+ local row = self.rows[cy]
+ if not row or not row[cx] or not row[cx].items[item] then return false end
+
+ local cell = row[cx]
+ cell.items[item] = nil
+ cell.itemCount = cell.itemCount - 1
+ if cell.itemCount == 0 then
+ self.nonEmptyCells[cell] = nil
+ end
+ return true
+end
+
+local function getDictItemsInCellRect(self, cl,ct,cw,ch)
+ local items_dict = {}
+ for cy=ct,ct+ch-1 do
+ local row = self.rows[cy]
+ if row then
+ for cx=cl,cl+cw-1 do
+ local cell = row[cx]
+ if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling
+ for item,_ in pairs(cell.items) do
+ items_dict[item] = true
+ end
+ end
+ end
+ end
+ end
+
+ return items_dict
+end
+
+local function getCellsTouchedBySegment(self, x1,y1,x2,y2)
+
+ local cells, cellsLen, visited = {}, 0, {}
+
+ grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy)
+ local row = self.rows[cy]
+ if not row then return end
+ local cell = row[cx]
+ if not cell or visited[cell] then return end
+
+ visited[cell] = true
+ cellsLen = cellsLen + 1
+ cells[cellsLen] = cell
+ end)
+
+ return cells, cellsLen
+end
+
+local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter)
+ local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2)
+ local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1
+ local visited, itemInfo, itemInfoLen = {},{},0
+ for i=1,len do
+ cell = cells[i]
+ for item in pairs(cell.items) do
+ if not visited[item] then
+ visited[item] = true
+ if (not filter or filter(item)) then
+ rect = self.rects[item]
+ l,t,w,h = rect.x,rect.y,rect.w,rect.h
+
+ ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1)
+ if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then
+ -- the sorting is according to the t of an infinite line, not the segment
+ tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge)
+ itemInfoLen = itemInfoLen + 1
+ itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)}
+ end
+ end
+ end
+ end
+ end
+ table.sort(itemInfo, sortByWeight)
+ return itemInfo, itemInfoLen
+end
+
+local function getResponseByName(self, name)
+ local response = self.responses[name]
+ if not response then
+ error(('Unknown collision type: %s (%s)'):format(name, type(name)))
+ end
+ return response
+end
+
+
+-- Misc Public Methods
+
+function World:addResponse(name, response)
+ self.responses[name] = response
+end
+
+function World:project(item, x,y,w,h, goalX, goalY, filter)
+ assertIsRect(x,y,w,h)
+
+ goalX = goalX or x
+ goalY = goalY or y
+ filter = filter or defaultFilter
+
+ local collisions, len = {}, 0
+
+ local visited = {}
+ if item ~= nil then visited[item] = true end
+
+ -- This could probably be done with less cells using a polygon raster over the cells instead of a
+ -- bounding rect of the whole movement. Conditional to building a queryPolygon method
+ local tl, tt = min(goalX, x), min(goalY, y)
+ local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h)
+ local tw, th = tr-tl, tb-tt
+
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th)
+
+ local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
+
+ for other,_ in pairs(dictItemsInCellRect) do
+ if not visited[other] then
+ visited[other] = true
+
+ local responseName = filter(item, other)
+ if responseName then
+ local ox,oy,ow,oh = self:getRect(other)
+ local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY)
+
+ if col then
+ col.other = other
+ col.item = item
+ col.type = responseName
+
+ len = len + 1
+ collisions[len] = col
+ end
+ end
+ end
+ end
+
+ table.sort(collisions, sortByTiAndDistance)
+
+ return collisions, len
+end
+
+function World:countCells()
+ local count = 0
+ for _,row in pairs(self.rows) do
+ for _,_ in pairs(row) do
+ count = count + 1
+ end
+ end
+ return count
+end
+
+function World:hasItem(item)
+ return not not self.rects[item]
+end
+
+function World:getItems()
+ local items, len = {}, 0
+ for item,_ in pairs(self.rects) do
+ len = len + 1
+ items[len] = item
+ end
+ return items, len
+end
+
+function World:countItems()
+ local len = 0
+ for _ in pairs(self.rects) do len = len + 1 end
+ return len
+end
+
+function World:getRect(item)
+ local rect = self.rects[item]
+ if not rect then
+ error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.')
+ end
+ return rect.x, rect.y, rect.w, rect.h
+end
+
+function World:toWorld(cx, cy)
+ return grid_toWorld(self.cellSize, cx, cy)
+end
+
+function World:toCell(x,y)
+ return grid_toCell(self.cellSize, x, y)
+end
+
+
+--- Query methods
+
+function World:queryRect(x,y,w,h, filter)
+
+ assertIsRect(x,y,w,h)
+
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
+ local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
+
+ local items, len = {}, 0
+
+ local rect
+ for item,_ in pairs(dictItemsInCellRect) do
+ rect = self.rects[item]
+ if (not filter or filter(item))
+ and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h)
+ then
+ len = len + 1
+ items[len] = item
+ end
+ end
+
+ return items, len
+end
+
+function World:queryPoint(x,y, filter)
+ local cx,cy = self:toCell(x,y)
+ local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1)
+
+ local items, len = {}, 0
+
+ local rect
+ for item,_ in pairs(dictItemsInCellRect) do
+ rect = self.rects[item]
+ if (not filter or filter(item))
+ and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y)
+ then
+ len = len + 1
+ items[len] = item
+ end
+ end
+
+ return items, len
+end
+
+function World:querySegment(x1, y1, x2, y2, filter)
+ local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
+ local items = {}
+ for i=1, len do
+ items[i] = itemInfo[i].item
+ end
+ return items, len
+end
+
+function World:querySegmentWithCoords(x1, y1, x2, y2, filter)
+ local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
+ local dx, dy = x2-x1, y2-y1
+ local info, ti1, ti2
+ for i=1, len do
+ info = itemInfo[i]
+ ti1 = info.ti1
+ ti2 = info.ti2
+
+ info.weight = nil
+ info.x1 = x1 + dx * ti1
+ info.y1 = y1 + dy * ti1
+ info.x2 = x1 + dx * ti2
+ info.y2 = y1 + dy * ti2
+ end
+ return itemInfo, len
+end
+
+
+--- Main methods
+
+function World:add(item, x,y,w,h)
+ local rect = self.rects[item]
+ if rect then
+ error('Item ' .. tostring(item) .. ' added to the world twice.')
+ end
+ assertIsRect(x,y,w,h)
+
+ self.rects[item] = {x=x,y=y,w=w,h=h}
+
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
+ for cy = ct, ct+ch-1 do
+ for cx = cl, cl+cw-1 do
+ addItemToCell(self, item, cx, cy)
+ end
+ end
+
+ return item
+end
+
+function World:remove(item)
+ local x,y,w,h = self:getRect(item)
+
+ self.rects[item] = nil
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
+ for cy = ct, ct+ch-1 do
+ for cx = cl, cl+cw-1 do
+ removeItemFromCell(self, item, cx, cy)
+ end
+ end
+end
+
+function World:update(item, x2,y2,w2,h2)
+ local x1,y1,w1,h1 = self:getRect(item)
+ w2,h2 = w2 or w1, h2 or h1
+ assertIsRect(x2,y2,w2,h2)
+
+ if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then
+
+ local cellSize = self.cellSize
+ local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1)
+ local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2)
+
+ if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then
+
+ local cr1, cb1 = cl1+cw1-1, ct1+ch1-1
+ local cr2, cb2 = cl2+cw2-1, ct2+ch2-1
+ local cyOut
+
+ for cy = ct1, cb1 do
+ cyOut = cy < ct2 or cy > cb2
+ for cx = cl1, cr1 do
+ if cyOut or cx < cl2 or cx > cr2 then
+ removeItemFromCell(self, item, cx, cy)
+ end
+ end
+ end
+
+ for cy = ct2, cb2 do
+ cyOut = cy < ct1 or cy > cb1
+ for cx = cl2, cr2 do
+ if cyOut or cx < cl1 or cx > cr1 then
+ addItemToCell(self, item, cx, cy)
+ end
+ end
+ end
+
+ end
+
+ local rect = self.rects[item]
+ rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2
+
+ end
+end
+
+function World:move(item, goalX, goalY, filter)
+ local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter)
+
+ self:update(item, actualX, actualY)
+
+ return actualX, actualY, cols, len
+end
+
+function World:check(item, goalX, goalY, filter)
+ filter = filter or defaultFilter
+
+ local visited = {[item] = true}
+ local visitedFilter = function(itm, other)
+ if visited[other] then return false end
+ return filter(itm, other)
+ end
+
+ local cols, len = {}, 0
+
+ local x,y,w,h = self:getRect(item)
+
+ local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter)
+
+ while projected_len > 0 do
+ local col = projected_cols[1]
+ len = len + 1
+ cols[len] = col
+
+ visited[col.other] = true
+
+ local response = getResponseByName(self, col.type)
+
+ goalX, goalY, projected_cols, projected_len = response(
+ self,
+ col,
+ x, y, w, h,
+ goalX, goalY,
+ visitedFilter
+ )
+ end
+
+ return goalX, goalY, cols, len
+end
+
+
+-- Public library functions
+
+bump.newWorld = function(cellSize)
+ cellSize = cellSize or 64
+ assertIsPositiveNumber(cellSize, 'cellSize')
+ local world = setmetatable({
+ cellSize = cellSize,
+ rects = {},
+ rows = {},
+ nonEmptyCells = {},
+ responses = {}
+ }, World_mt)
+
+ world:addResponse('touch', touch)
+ world:addResponse('cross', cross)
+ world:addResponse('slide', slide)
+ world:addResponse('bounce', bounce)
+
+ return world
+end
+
+bump.rect = {
+ getNearestCorner = rect_getNearestCorner,
+ getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices,
+ getDiff = rect_getDiff,
+ containsPoint = rect_containsPoint,
+ isIntersecting = rect_isIntersecting,
+ getSquareDistance = rect_getSquareDistance,
+ detectCollision = rect_detectCollision
+}
+
+bump.responses = {
+ touch = touch,
+ cross = cross,
+ slide = slide,
+ bounce = bounce
+}
+
+return bump
diff --git a/src/char.moon b/src/char.moon
new file mode 100644
index 0000000..5e1a35c
--- /dev/null
+++ b/src/char.moon
@@ -0,0 +1,307 @@
+
+util = require "util"
+broadphase = require "broadphase"
+main = require "main"
+constrain = require "constrain"
+color = require "color"
+room = require "room"
+import LobbyRoom from room
+ability_reg = require "ability_reg"
+
+
+mod = ...
+mod.characters = {}
+mod.classes = {}
+require "char_tank"
+require "char_mage"
+require "char_theif"
+require "char_fool"
+require "char_jugg"
+mod.class_order = {
+ "Tumbler",
+ "Fire Breather",
+ "Juggler",
+ "Troubador",
+ "Juggernaut"
+}
+mod.class_order_rev = {}
+for k,v in ipairs(mod.class_order)
+ mod.class_order_rev[v] = k
+print("After requireing characters, mod.classes was",mod.classes)
+mod.enemies = {}
+--require "e_rat"
+require "e_bethany"
+require "e_ruminating_randy"
+require "e_mopey_marvin"
+require "e_sullen_salley"
+require "e_child"
+print("After requireing rat, mod.enemies was",mod.enemies)
+--print("test")
+--[[Stores a single input, and the time it was inputed]]
+class KeyInput
+ new:(key = "<input>") =>
+ @time = 0
+ @key = key
+ @value = false
+
+--print "test"
+
+class AnimFrame
+ new:(anim,interupt,mode) =>
+ @anim = anim
+ @interupt = interupt
+ @mode = mode
+
+class ActionInput
+ new:(action = "<input>") =>
+ @time = 0
+ @action = action
+
+mod.sprite_direction_co = () =>
+ return () ->
+ while true
+ @stop_anim(@sprites.right)
+ @stop_anim(@sprites.left)
+ @stop_anim(@sprites.stop_right)
+ @stop_anim(@sprites.stop_left)
+ @stop_anim(@sprites.falling_left)
+ @stop_anim(@sprites.falling_right)
+ for input in *@inputs
+ if @velocity.x > 0.01
+ @set_anim(@sprites.right,1,"loop")
+ break
+ elseif @velocity.x < -0.01
+ @set_anim(@sprites.left,1,"loop")
+ break
+ coroutine.yield!
+
+mod.can_die_co = () =>
+ return () ->
+ while not @dead
+ coroutine.yield!
+
+ for k,v in pairs @sprites
+ if k ~= "idle"
+ @stop_anim(v)
+ if @sprites.die
+ @set_anim(@sprites.die,5)
+ coroutine.yield(true)
+
+mod.make_animate = (c) ->
+ assertf(c.sprites ~= nil, "Tried to animate something that had no .sprites: %q", c.__class.__name)
+ assertf(c.sprites.idle ~= nil and #c.sprites.idle > 0, "Tried to animate something without a .idle animation: %q", c.__class.__name)
+ c.anim_stack = {AnimFrame(c.sprites.idle,0,"loop")}
+ c.anim = c.sprites.idle
+ c.keyframe = 0
+ c.animrate = c.animrate or 1
+ c.anim_interupt = 0
+ c.set_anim = (self,tbl,interupt,mode) ->
+ if type(tbl) ~= "table"
+ error("Tried to set anim to something that was not a table!",2)
+ if #tbl == 0
+ error("Tried to set anim to an empty table",2)
+ if interupt > @anim_interupt
+ table.insert(@anim_stack,AnimFrame(tbl,interupt,mode))
+ @anim = @anim_stack[#@anim_stack].anim
+ @anim_interupt = interupt
+
+ c.stop_anim = (self,tbl,err_if_unable=false) ->
+ anim_found = false
+ for k,v in pairs @anim_stack
+ if v.anim == tbl
+ --print("Found anim to remove")
+ anim_found = true
+ table.remove(@anim_stack,k)
+ break
+ if err_if_unable
+ assertf(anim_found, "Could not find animation to remove")
+ @anim = @anim_stack[#@anim_stack].anim
+ @anim_interupt = @anim_stack[#@anim_stack].interupt
+
+ c.node\action(coroutine.create(() ->
+ while not c.dead
+ c.keyframe = math.floor(am.current_time()*c.animrate) % #c.anim
+ spritename = c.anim[c.keyframe + 1]
+ assert(spritename, "Failed to find an appropriate image to draw.")
+ --print("Setting:",spritename)
+ c.node("sprite").source = spritename
+ coroutine.yield!
+ --we are dead
+ keyframe_0 = am.current_time()
+ while c.keyframe < #c.anim - 1
+ c.keyframe = math.floor((am.current_time! - keyframe_0)*c.animrate)
+ assert(c.anim[c.keyframe + 1], "Failed to find an appropriate image to draw.")
+ c.node("sprite").source = c.anim[c.keyframe + 1]
+ coroutine.yield!
+ c\remove()
+ coroutine.yield(true)
+ ))
+
+mod.inherited = {}
+hp_bar_width = 20
+--[[The character, extended to make both players and ai]]
+class Character
+ @classes = {}
+ @players = {} -- players singleton, [peerid] = Character
+ new:(uname, data, charclass) =>
+ assert(charclass, "Charclass may not be nil")
+ @uname = uname or false
+ @data = data or {
+ status: "active",
+ location: -1,
+ position: charclass.default_position
+ }
+ @class = charclass
+ assert(@class.name, "Character classes must have a name")
+ @calc_class_values!
+ @node = am.group!
+
+ --print("Creating character!",uname,data,charclass)
+ @node\append(am.translate(0,0)\tag("char_translate")^ am.sprite(@class.sprite,color.white,"center","bottom")\tag("char_sprite"))
+ print("A character has been created!")
+ print(debug.traceback!)
+ --Draw healthbar
+ healthbar_trans = am.translate(0,60)
+ healthbar_left = am.sprite("data/bar_left.png", color.white,"left","center")\tag("hp_l")
+ healthbar_right = am.sprite("data/bar_right.png", color.white, "right","center")\tag("hp_r")
+ healthbar_bar = am.sprite("data/bar_mid.png", color.white,"left","center")\tag("hp_b")
+ healthbar_fill = am.sprite("data/bar_fill.png", color.white,"left","center")\tag("hp_f")
+ healthbar_scale = am.scale(hp_bar_width,1)
+ healthbar_fill_scale = am.scale(hp_bar_width + 1,1)\tag("hp_fill_scale")
+ healthbar_trans\append(am.translate(-hp_bar_width,0)^ healthbar_left)
+ healthbar_trans\append(am.translate(hp_bar_width,0)^ healthbar_right)
+ healthbar_trans\append(am.translate(-hp_bar_width/2,0)^ healthbar_scale^ healthbar_bar)
+ healthbar_trans\append(am.translate((-hp_bar_width/2) - 1,0)^ healthbar_fill_scale^ healthbar_fill)
+ @.node("char_sprite")\append(healthbar_trans)
+ --End draw healthbar
+ assert(@.__class.draw,"Characters must have a draw() method")
+ table.insert(mod.characters,@)
+ constrain(@,"set anim", (self,value) ->
+ assertf(type(value) == "table", "Tried to set anim on %q to something other than a table (%q)",@, type(value))
+ assert(#value > 0, "Tried to set animation for char to something with 0 frames!")
+ )
+ assert(@.__class != Character,"Character class must be subclassed")
+ --main.root("world_characters")\append(@node)
+
+ __tostring: () =>
+ return string.format(
+ "<%s, %s> at (%d)",
+ @.__class.__name,
+ (@class and @class.name or "no class"),
+ ( @data and @data.position or -1)
+ )
+
+ @__inherited: (c) =>
+ assert(c, "Inheritance must exist")
+ assert(c.__name, "Inherited class must have a .__name")
+ @@.classes[c.__name] = c
+ mod.inherited[c.__name] = c
+
+ calc_class_values: () =>
+ for k,v in pairs(@class)
+ if k\match("^default")
+ field = k\match("^default_(.*)")
+ if type(v) == "function"
+ @data[field] = v!
+ else
+ @data[field] = v
+
+ set_field: (name, value) =>
+ assert(@data[name], "Field must exist to be set")
+ @data[name] = value
+ print("my data table is:",@data)
+ if name == "hp"
+ perc = (@data.hp / @data.maxhp) * 20
+ @.node("hp_fill_scale").x = perc
+
+ serialize: () =>
+ print("Serializing char:",@)
+ print("Name:", @@__name)
+ print("uname:",@uname)
+ print("data:",@data)
+ data_abilities = {}
+ for i,ability in pairs(@data.abilities)
+ data_abilities[i] = ability.__name
+ data_copy = table.shallow_copy(@data)
+ data_copy.abilities = data_abilities
+ print("class.name:",@class.name)
+ ret = am.to_json({name:@@__name, uname:@uname, data:data_copy, class:@class.name})
+ print("Ret is:",ret)
+ ret
+
+ deserialize: (data) ->
+ print("Deserializing character")
+ tbl = am.parse_json(data)
+ if mod.classes[tbl.class]
+ data_abilities = {}
+ for i, ability_name in pairs(tbl.data.abilities)
+ data_abilities[i] = ability_reg[ability_name]
+ tbl.data.abilities = data_abilities
+ return mod.inherited[tbl.name](tbl.uname, tbl.data, mod.classes[tbl.class])
+ elseif mod.enemies[tbl.class]
+ e = mod.Enemy(tbl.data, mod.enemies[tbl.class])
+ e.uname = tbl.uname
+ return e
+
+
+ draw:(screenpos) =>
+ print("draw")
+ remove: () =>
+ @node\remove_all!
+ die: () =>
+ @dead = true
+ print(@,"is dieing, node is",@.node,"and color is",color)
+ @.node("char_sprite")\append(am.line(vec2(-10,-10),vec2(10,10),5,color.bright))
+ @.node("char_sprite")\append(am.line(vec2(10,-10),vec2(-10,10),5,color.bright))
+ enter_room: (room) =>
+ @room = room
+ print("Character",@,"entered room",room)
+ @.node("char_translate").y = room.floor_y
+ if room.__class == LobbyRoom
+ print("Class was lobbyRoom")
+ rng_x = math.random(-(main.width/2), (main.width/2))
+ print("RNG x:",rng_x)
+ @.node("char_translate").x = rng_x
+ else
+ print("Class was not LobbyRoom")
+ x_pos = @room\player_location_of(@data.position)
+ print("Got x pos for",@,x_pos)
+ @.node("char_translate").x = x_pos + math.random(-15,15) --some random variance to stop stacking
+ set_class: (newclass) =>
+ assert(newclass, "Cannot set a class to nil")
+ @class = newclass
+ @calc_class_values!
+ @.node("char_sprite").source = newclass.sprite
+
+ set_position: (pos) =>
+ @data.position = pos
+
+ set_location: (loc) =>
+ @data.location = loc
+
+mod.enemy_counter = 0
+class Enemy extends Character
+ new: (data, eclass) =>
+ super(eclass.name .. ":" .. tostring(mod.enemy_counter), data, eclass)
+ mod.enemy_counter += 1
+ nwuname: (uname, data, eclass) ->
+ @(data, eclass)
+
+ mod.enemy_counter += 1
+ __tostring: () =>
+ return string.format("<%s> at (%d)",@uname, @data.position or 0)
+
+ enter_room: (room) => --overload character's to get enemy locations
+ print("Character",@,"entered room",room)
+ @room = room
+ @.node("char_translate").y = @room.floor_y
+ @.node("char_translate").x = @room\enemy_location_of(@data.position)
+
+ select_action: () =>
+ return @class.select_action(@)
+
+mod["Character"] = Character
+mod["KeyInput"] = KeyInput
+mod["Enemy"] = Enemy
+--Things that extend the character class
+mod
diff --git a/src/char_fool.moon b/src/char_fool.moon
new file mode 100644
index 0000000..d468e3b
--- /dev/null
+++ b/src/char_fool.moon
@@ -0,0 +1,24 @@
+char = require "char"
+reg = require "ability_reg"
+require "a_firebreath"
+
+mod = ...
+
+mod.char = {
+ name: "Fire Breather",
+ default_position: 3,
+ default_abilities: {reg.FireBreath},
+ default_maxhp: 3,
+ default_hp: 3,
+ default_maxstamina: 3
+ default_stamina: 3
+ default_maxmana: 3
+ default_mana: 3
+ sprite:"data/character_4.png"
+}
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+char.classes[mod.char.name] = mod.char
+
+mod
diff --git a/src/char_jugg.moon b/src/char_jugg.moon
new file mode 100644
index 0000000..4adc9bb
--- /dev/null
+++ b/src/char_jugg.moon
@@ -0,0 +1,30 @@
+char = require "char"
+reg = require "ability_reg"
+require "a_test"
+
+mod = ...
+
+mod.char = {
+ name: "Juggernaut",
+ default_position:1,
+ default_abilities: {
+ reg.Everything
+ },
+ default_maxhp: 100,
+ default_hp: 100,
+ default_maxstamina: 3,
+ default_stamina: 3,
+ default_maxmana: 1,
+ default_mana: 1,
+ sprite:"data/character_1.png"
+}
+
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+print("reg:",reg)
+assert(mod.char.default_abilities[1], "Tumble not found in reg")
+char.classes[mod.char.name] = mod.char
+print("After adding Tumbler, char.classes is", char.classes)
+
+mod
diff --git a/src/char_mage.moon b/src/char_mage.moon
new file mode 100644
index 0000000..c0ff680
--- /dev/null
+++ b/src/char_mage.moon
@@ -0,0 +1,26 @@
+char = require "char"
+reg = require "ability_reg"
+require "a_strum"
+require "a_dance"
+require "a_drum"
+
+mod = ...
+
+mod.char = {
+ name: "Troubador",
+ default_position:3,
+ default_abilities: {reg.Strum, reg.Dance, reg.Drum},
+ default_maxhp: 2,
+ default_hp: 2,
+ default_maxstamina: 1
+ default_stamina: 1
+ default_maxmana: 5
+ default_mana: 5
+ sprite:"data/character_3.png"
+}
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+char.classes[mod.char.name] = mod.char
+
+mod
diff --git a/src/char_tank.moon b/src/char_tank.moon
new file mode 100644
index 0000000..6017ac1
--- /dev/null
+++ b/src/char_tank.moon
@@ -0,0 +1,34 @@
+char = require "char"
+reg = require "ability_reg"
+require "a_tumble"
+require "a_highjump"
+require "a_physique"
+
+mod = ...
+
+mod.char = {
+ name: "Tumbler",
+ default_position:1,
+ default_abilities: {
+ reg.Tumble,
+ reg.HighJump,
+ reg.Physique
+ },
+ default_maxhp: 5,
+ default_hp: 5,
+ default_maxstamina: 3,
+ default_stamina: 3,
+ default_maxmana: 1,
+ default_mana: 1,
+ sprite:"data/character_1.png"
+}
+
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+print("reg:",reg)
+assert(mod.char.default_abilities[1], "Tumble not found in reg")
+char.classes[mod.char.name] = mod.char
+print("After adding Tumbler, char.classes is", char.classes)
+
+mod
diff --git a/src/char_theif.moon b/src/char_theif.moon
new file mode 100644
index 0000000..6a074ac
--- /dev/null
+++ b/src/char_theif.moon
@@ -0,0 +1,25 @@
+char = require "char"
+reg = require "ability_reg"
+require "a_knifeslip"
+require "a_hackysacks"
+
+mod = ...
+
+mod.char = {
+ name: "Juggler",
+ default_position: 2,
+ default_abilities: {reg.KnifeSlip, reg.Juggle},
+ default_maxhp: 3,
+ default_hp: 3,
+ default_maxstamina: 5
+ default_stamina: 5
+ default_maxmana: 1
+ default_mana: 1
+ sprite:"data/character_2.png"
+}
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+char.classes[mod.char.name] = mod.char
+
+mod
diff --git a/src/color.moon b/src/color.moon
new file mode 100644
index 0000000..1f87234
--- /dev/null
+++ b/src/color.moon
@@ -0,0 +1,13 @@
+mod = ...
+
+mod.fg = vec4(0.983,0.961,0.937,1)
+mod.bg = vec4(0.153,0.153,0.267,1)
+mod.highlight = vec4(0.949,0.827,0.671,1)
+mod.shadow = vec4(0.286,0.302,0.494,1)
+mod.bright = vec4(0.776,0.624,0.647,1)
+mod.dark = vec4(0.545,0.427,0.612,1)
+
+mod.transparent = vec4(0,0,0,0)
+mod.white = vec4(1,1,1,1)
+
+mod
diff --git a/src/constants.lua b/src/constants.lua
new file mode 100644
index 0000000..b2894bf
--- /dev/null
+++ b/src/constants.lua
@@ -0,0 +1,7 @@
+main = require("main")
+
+mod = ...
+
+mod.floor_y = 128
+
+mod
diff --git a/src/constrain.lua b/src/constrain.lua
new file mode 100644
index 0000000..56f3b6a
--- /dev/null
+++ b/src/constrain.lua
@@ -0,0 +1,109 @@
+--[[
+A function that allows adding constraints on tables.
+This works by makeing a shallow copy of the table,
+and setting __index and __newindex metamethods of the (now empty) table,
+that access the appropriate values of the shallow copy after doing checks.
+
+constrain(tbl, ("get " | "set ") + "field name", function(tbl, value)
+ --this function is called with the table ("self") and the
+ --value that is being set or retrived
+end)
+
+This module is just a function that can add constraints on a table. Use
+it like this:
+
+ local constrain = require("constrain")
+ local my_tbl = {}
+ constrain(my_tbl,"get my_field",function(self,value)
+ --Here, self is my_tbl, value will be
+ --"my_field"
+ assert(value > 0 and value < 20, "my_value must be between 0 and 20")
+ end)
+ --From now on, if my_tbl.my_field gets set to anything outside of (0:20),
+ --an error will be thrown.
+
+This function should be totally transparent to the outside.
+]]
+
+local constrained_tables = {}
+
+return function(tbl,trigger,func)
+ local is_empty = true
+ for k,v in pairs(tbl) do
+ is_empty = false
+ break;
+ end
+ local meta = getmetatable(tbl)
+
+ --This table has never had constrain() called on it before,
+ --make the shallow copy with hooks and stuff
+ if constrained_tables[tbl] == nil then
+ --Copy all the variables into a shallow copy
+ local shallow_copy = {}
+ for k,v in pairs(tbl) do
+ shallow_copy[k] = v
+ tbl[k] = nil
+ end
+
+ --Set the shallow copy's metatable to the original table's
+ --metatable
+ setmetatable(shallow_copy,meta)
+
+ --Set the original table's metatable to the hookable thing
+ local t_meta = {}
+ t_meta.get_hooks = {}
+ t_meta.set_hooks = {}
+ t_meta.__index = function(self,key)
+ local ret = shallow_copy[key]
+ for _,hook in pairs(t_meta.get_hooks[key] or {}) do
+ hook(self,ret)
+ end
+ return ret
+ end
+ t_meta.__newindex = function(self,key,value)
+ for _,hook in pairs(t_meta.set_hooks[key] or {}) do
+ hook(self,value)
+ end
+ shallow_copy[key] = value
+ end
+ t_meta.__pairs = function(self)
+ return pairs(shallow_copy)
+ end
+ t_meta.__len = function(self)
+ return #shallow_copy
+ end
+ t_meta.__call = function(self,...)
+ return shallow_copy(...)
+ end
+ t_meta.__mode = meta and meta.__mode or ""
+ t_meta.__gc = meta and meta.__gc
+ t_meta.__metatable = meta
+
+ setmetatable(tbl,t_meta)
+ constrained_tables[tbl] = t_meta
+ end
+
+ --By this point, tbl is a "constrainable" table. we can just
+ --add functios to it's get_hooks and set_hooks to do whatever checking we need
+ --functions added to get_hooks should be
+ -- function(self,value) ... end
+ --functions added to set_hooks should be
+ -- function(self,value) ... end
+ --
+ local getset,field = string.match(trigger,"(get) (.+)")
+ if not getset then
+ getset,field = string.match(trigger,"(set) (.+)")
+ end
+ --local getset,field = string.match(trigger,"(get|set) (.+)")
+ assert(getset,"constrain() must be called with \"get\" or \"set\" as the first word in the pattern")
+ assert(field,"constrain() must specify a field to trigger on")
+ if getset == "get" then
+ local gh = constrained_tables[tbl].get_hooks
+ gh[field] = gh[field] or {}
+ table.insert(gh[field],func)
+ elseif getset == "set" then
+ local sh = constrained_tables[tbl].set_hooks
+ sh[field] = sh[field] or {}
+ table.insert(sh[field],func)
+ end
+end
diff --git a/src/create_party_menu.moon b/src/create_party_menu.moon
new file mode 100644
index 0000000..b31c9d5
--- /dev/null
+++ b/src/create_party_menu.moon
@@ -0,0 +1,103 @@
+main = require "main"
+world = require "world"
+color = require "color"
+ui = require "ui"
+bp = require "broadphase"
+action = require "action"
+char = require "char"
+player = require "player"
+room = require "room"
+import World from world
+import Server from world
+import LocalPlayer from player
+import LobbyRoom from room
+
+mod = ...
+
+mod.node = am.group!
+mod.node\append(am.translate(0,main.height/2)^ am.text("Creating lobby...", color.fg, "center","top")\tag("join_id"))
+mod.loaded = false
+mod.load = () ->
+ mod.loaded = true
+ print("Loading create party")
+ main.root("screen")\append(mod.node)
+ print("About to create server")
+ main["server"] = Server!
+ print("Created server")
+ mod.node("join_id").text = "Lobby id:" .. main.server.lobby_id
+ print("Set join id text")
+ main["world"] = World!
+ main.world\set_room(LobbyRoom!)
+ print("Created world")
+ main.world\join(main.server.lobby_id)
+ print("Joined my own lobby")
+ start_button = ui.create_any_button(mod.node,5,2,(-32*5)/2,0)
+ start_button.node("loc")\append(am.scale(1.5)^ am.translate(12,-10)^ am.text("GET SILLY", color.fg, "left", "top"))
+ char_selector = ui.create_char_selector2(mod.node)
+ char_right, char_left, char_text = char_selector[1], char_selector[2], char_selector[3]
+ mod.buttons = {char_left, char_right, start_button} --save to remove from the physworld later
+ classes = char.class_order
+ class_s = 1
+ print("got char selector:",char_selector)
+ touch_indicator = am.group!
+ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top")
+ touch_loc = am.translate(0,0)^ touch_cursor
+ touch_indicator\append(touch_loc)
+ --Set everything transparent, give it color when we're connected
+ start_button.color = color.transparent
+ char_right.color = color.transparent
+ char_left.color = color.transparent
+ mod.node\action("click_interpreter",coroutine.create(()->
+ while not main.world.client_open!
+ coroutine.yield!
+ char_left.color = color.white
+ char_right.color = color.white
+ char_text.text = "Tumbler"
+ main.world\set_local(LocalPlayer(am.eval_js("CLIENT.peer._id"),{},char.classes.Tumbler))
+ main.world\load!
+ while true
+ if #main.win\active_touches! > 0
+ touch = main.win\touch_position(1)
+ touch_cursor.color = vec4(1,1,1,1)
+ touch_loc.x = touch.x
+ touch_loc.y = touch.y
+ col_but = bp.check(touch.x, touch.y + 64)
+ if #col_but > 0
+ touch_cursor.color = vec4(0.5,0.5,0.5,0.5)
+ if #col_but > 0 and main.win\touch_began(1)
+ if col_but[1] == start_button
+ print("Starting!")
+ action.start_game!
+ coroutine.yield!
+ if col_but[1] == char_left
+ print("char left")
+ class_s = class_s - 1
+ start_button.color = color.white
+ if class_s == 0
+ class_s = #classes
+ if col_but[1] == char_right
+ print("char right")
+ class_s = class_s + 1
+ start_button.color = color.white
+ if class_s > #classes
+ class_s = 1
+ if col_but[1] == char_left or col_but[1] == char_right
+ print("class_s",class_s)
+ print("classes:", classes)
+ char_text.text = classes[class_s]
+ action.request_class_change(classes[class_s])
+
+ else
+ touch_cursor.color = vec4(0,0,0,0)
+ coroutine.yield!
+ ))
+ mod.node\append(touch_indicator)
+
+mod.unload = () ->
+ print("Unloading create party")
+ main.root("screen")\remove(mod.node)
+ for _, button in pairs(mod.buttons)
+ bp.remove(button)
+ mod.loaded = false
+
+mod
diff --git a/src/defeat_menu.moon b/src/defeat_menu.moon
new file mode 100644
index 0000000..6810519
--- /dev/null
+++ b/src/defeat_menu.moon
@@ -0,0 +1,66 @@
+
+main = require "main"
+bp = require "broadphase"
+color = require "color"
+ui = require "ui"
+
+world = require "world" --delete when done
+import Server from world
+import World from world
+mod = ...
+
+-- 4 parts?
+nwidth = 6
+section_width = 32
+start_x = (-32)*(nwidth/2)
+mod.node = am.group!
+mod.node\append(am.sprite("data/defeat_screen.png"))
+start_y = 0
+padding = 32
+buttonspecs = {
+ menu: {
+ y_off: start_y,
+ text: "Menu"
+ },
+}
+
+
+mod.load = () ->
+ print("creating main menu")
+ main.root("screen")\append(mod.node)
+ for name, button_tbl in pairs buttonspecs
+ button_extra = ui.create_big_button(mod.node,6,start_x,button_tbl.y_off)
+ button_extra.node\append(am.translate(start_x+((nwidth*section_width)/2),button_tbl.y_off - 32)^ am.text(button_tbl.text,color.fg))
+ mod[name] = button_extra
+ touch_indicator = am.group!
+ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top")
+ touch_loc = am.translate(0,0)^ touch_cursor
+ touch_indicator\append(touch_loc)
+ mod.node\append(am.translate(0,100)^ am.scale(3)^ am.text("You failed to\nremain silly"))
+ mod.node\action("click_interpreter",coroutine.create(()->
+ while true
+ if #main.win\active_touches! > 0
+ touch = main.win\touch_position(1)
+ touch_cursor.color = vec4(1,1,1,1)
+ touch_loc.x = touch.x
+ touch_loc.y = touch.y
+ col_but = bp.check(touch.x, touch.y+64)
+ if #col_but > 0
+ touch_cursor.color = vec4(0.5,0.5,0.5,0.5)
+ if #col_but > 0
+ print("Collided with button:",col_but)
+ print("mod.create prty was:",mod.create_party)
+ if col_but[1] == mod.menu
+ am.eval_js("location.reload()")
+ else
+ touch_cursor.color = vec4(0,0,0,0)
+ coroutine.yield!
+ ))
+ mod.node\append(touch_indicator)
+
+mod.unload = () ->
+ main.root("screen")\remove(mod.node)
+ bp.remove(mod.create_party)
+ bp.remove(mod.join_party)
+
+mod
diff --git a/src/dispatch.moon b/src/dispatch.moon
new file mode 100644
index 0000000..1455da8
--- /dev/null
+++ b/src/dispatch.moon
@@ -0,0 +1,49 @@
+--[[ Dispatches events ]]
+
+mod = ...
+char = require "char"
+import KeyInput from char
+main = require "main"
+
+set_input = (inputs, key, value) ->
+ for input in *inputs
+ if input.key == key
+ --print("Setting", key, " to", value)
+ input.value = value
+ return
+
+controls = {
+ right: "right",
+ left: "left",
+ up: "up"
+ jump: "z",
+ down: "down"
+ dash: "c"
+ swing: "x"
+}
+
+mod.control = (character) ->
+ character.node\action(coroutine.create(() ->
+ while true
+ for k,v in pairs controls
+ set_input(character.inputs,k,main.win\key_down(v))
+ coroutine.yield()
+ ))
+
+mod.slime_ai = (character) ->
+ character.node\action(coroutine.create(() ->
+ time_offset = math.random()
+ last_action = am.current_time() + time_offset
+ while true
+ time_diff = am.current_time() - last_action
+ if time_diff > 1.3
+ last_action = am.current_time() + math.random()
+ elseif time_diff > 1.1
+ set_input(character.inputs,"jump_left",false)
+ elseif time_diff > 1
+ set_input(character.inputs,"jump_left",true)
+
+ coroutine.yield()
+ ))
+
+mod
diff --git a/src/e_bethany.moon b/src/e_bethany.moon
new file mode 100644
index 0000000..b589c79
--- /dev/null
+++ b/src/e_bethany.moon
@@ -0,0 +1,28 @@
+char = require "char"
+reg = require "ability_reg"
+require "a_brood"
+
+mod = ...
+
+mod.char = {
+ name: "Brooding Bethany",
+ cr: 8,
+ default_position: 2
+ default_abilities: {reg.Brood},
+ default_maxhp: 4,
+ default_hp: 1,
+ default_maxstamina: 1,
+ default_stamina: 1,
+ default_maxmana: 0,
+ default_mana: 0,
+ sprite:"data/e_brood.png",
+ select_action: () =>
+ print("while selecting action, by abilities were:",@data.abilities)
+ @data.abilities[1]
+}
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+char.enemies[mod.char.name] = mod.char
+
+mod
diff --git a/src/e_child.moon b/src/e_child.moon
new file mode 100644
index 0000000..90266b3
--- /dev/null
+++ b/src/e_child.moon
@@ -0,0 +1,28 @@
+char = require "char"
+reg = require "ability_reg"
+require "a_pass"
+
+mod = ...
+
+mod.char = {
+ name: "An actual child",
+ cr: 1,
+ default_position: 1
+ default_abilities: {reg.Pass},
+ default_maxhp: 1,
+ default_hp: 1,
+ default_maxstamina: 1,
+ default_stamina: 1,
+ default_maxmana: 0,
+ default_mana: 0,
+ sprite:"data/e_child.png",
+ select_action: () =>
+ print("while selecting action, by abilities were:",@data.abilities)
+ @data.abilities[1]
+}
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+char.enemies[mod.char.name] = mod.char
+
+mod
diff --git a/src/e_mopey_marvin.moon b/src/e_mopey_marvin.moon
new file mode 100644
index 0000000..d5685c7
--- /dev/null
+++ b/src/e_mopey_marvin.moon
@@ -0,0 +1,28 @@
+char = require "char"
+reg = require "ability_reg"
+require "a_mope"
+
+mod = ...
+
+mod.char = {
+ name: "Mopey Marvin",
+ cr: 5,
+ default_position: 2
+ default_abilities: {reg.Mope},
+ default_maxhp: 4,
+ default_hp: 1,
+ default_maxstamina: 1,
+ default_stamina: 1,
+ default_maxmana: 0,
+ default_mana: 0,
+ sprite:"data/e_mope.png",
+ select_action: () =>
+ print("while selecting action, by abilities were:",@data.abilities)
+ @data.abilities[1]
+}
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+char.enemies[mod.char.name] = mod.char
+
+mod
diff --git a/src/e_rat.moon b/src/e_rat.moon
new file mode 100644
index 0000000..d1357f3
--- /dev/null
+++ b/src/e_rat.moon
@@ -0,0 +1,31 @@
+
+char = require "char"
+reg = require "ability_reg"
+require "a_rat_bite"
+
+mod = ...
+
+return mod
+
+mod.char = {
+ name: "Rat",
+ cr: 1,
+ default_position: 1
+ default_abilities: {reg.RatBite},
+ default_maxhp: 1,
+ default_hp: 1,
+ default_maxstamina: 1,
+ default_stamina: 1,
+ default_maxmana: 0,
+ default_mana: 0,
+ sprite:"data/e_rat.png",
+ select_action: () =>
+ print("while selecting action, by abilities were:",@data.abilities)
+ @data.abilities[1]
+}
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+char.enemies[mod.char.name] = mod.char
+
+mod
diff --git a/src/e_ruminating_randy.moon b/src/e_ruminating_randy.moon
new file mode 100644
index 0000000..b316aaf
--- /dev/null
+++ b/src/e_ruminating_randy.moon
@@ -0,0 +1,28 @@
+char = require "char"
+reg = require "ability_reg"
+require "a_ruminate"
+
+mod = ...
+
+mod.char = {
+ name: "Ruminating Randy",
+ cr: 5,
+ default_position: 3
+ default_abilities: {reg.Ruminate},
+ default_maxhp: 4,
+ default_hp: 1,
+ default_maxstamina: 1,
+ default_stamina: 1,
+ default_maxmana: 0,
+ default_mana: 0,
+ sprite:"data/e_rum.png",
+ select_action: () =>
+ print("while selecting action, by abilities were:",@data.abilities)
+ @data.abilities[1]
+}
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+char.enemies[mod.char.name] = mod.char
+
+mod
diff --git a/src/e_sullen_salley.moon b/src/e_sullen_salley.moon
new file mode 100644
index 0000000..b82a4d3
--- /dev/null
+++ b/src/e_sullen_salley.moon
@@ -0,0 +1,29 @@
+
+char = require "char"
+reg = require "ability_reg"
+require "a_sulk"
+
+mod = ...
+
+mod.char = {
+ name: "Sullen Sally",
+ cr: 2,
+ default_position: 1
+ default_abilities: {reg.Sulk},
+ default_maxhp: 3,
+ default_hp: 1,
+ default_maxstamina: 1,
+ default_stamina: 1,
+ default_maxmana: 0,
+ default_mana: 0,
+ sprite:"data/e_sullen.png",
+ select_action: () =>
+ print("while selecting action, by abilities were:",@data.abilities)
+ @data.abilities[1]
+}
+for k,v in pairs(mod.char)
+ mod[k] = v
+
+char.enemies[mod.char.name] = mod.char
+
+mod
diff --git a/src/ext.lua b/src/ext.lua
new file mode 100644
index 0000000..a556dc7
--- /dev/null
+++ b/src/ext.lua
@@ -0,0 +1,66 @@
+-- Override tostring to display more info about the table
+local old_tostring = tostring
+local numtabs = 0
+local printed_tables = {}
+--print = log
+--
+ --for obj in *@physobjs
+ --bp.add(t,obj.offset.x,obj.offset.y,obj.size.x,obj.size.y)
+
+local function tostring_helper(el)
+ assert(type(el) == "table", "Tried to call helper with something that was not a table, it was a " .. type(el))
+ local mt = getmetatable(el)
+ if mt and mt.__tostring then
+ return mt.__tostring(el)
+ elseif printed_tables[el] == true then
+ return old_tostring(el)
+ else
+ printed_tables[el] = true
+ numtabs = numtabs + 1
+ local strbuilder = {"{"}
+ for k,v in pairs(el) do
+ local key,value
+ if type(k) == "table" then
+ key = tostring_helper(k)
+ else
+ key = old_tostring(k)
+ end
+ if type(v) == "table" then
+ value = tostring_helper(v)
+ else
+ value = old_tostring(v)
+ end
+ strbuilder[#strbuilder + 1] = string.format("%s%s : %s", string.rep("\t",numtabs), key, value)
+ end
+ strbuilder[#strbuilder + 1] = string.rep("\t",numtabs - 1) .. "}"
+ numtabs = numtabs - 1
+ return table.concat(strbuilder,"\n")
+ end
+
+end
+function tostring(el)
+ printed_tables = {}
+ if type(el) == "table" then
+ return tostring_helper(el)
+ end
+ return old_tostring(el)
+end
+
+-- Functions to save my hands
+function printf(fmt, ...)
+ print(string.format(fmt,...))
+end
+function errorf(fmt, ...)
+ --Our error isn't actually in this function, it's 1 above us (1) = 2
+ error(string.format(fmt,...),2)
+end
+function assertf(bool, fmt, ...)
+ assert(type(fmt) == "string", "Assertf arg #2 was \"" .. type(fmt) .. "\", expected string")
+ if not bool then
+ args = {fmt}
+ for k,v in ipairs({...}) do
+ table.insert(args,tostring(v))
+ end
+ error(string.format(unpack(args)),2)
+ end
+end
diff --git a/src/join_party_menu.moon b/src/join_party_menu.moon
new file mode 100644
index 0000000..3f7d309
--- /dev/null
+++ b/src/join_party_menu.moon
@@ -0,0 +1,62 @@
+
+main = require "main"
+world = require "world"
+color = require "color"
+bp = require "broadphase"
+ui = require "ui"
+import World from world
+
+mod = ...
+
+mod.node = am.group!
+mod.node\append(am.sprite("data/join.png"))
+default_text = "..."
+mod.load = () ->
+ mod.loaded = true
+ print("Loading join_party_menu")
+ main.root("screen")\append(mod.node)
+ mod.buttons = ui.create_join_input(mod.node)
+ mod.node\append(am.translate(0,-135)^ am.text(default_text, color.fg, "center","top")\tag("join_id"))
+ touch_indicator = am.group!
+ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top")
+ backspace = ui.create_any_button(mod.node,2,2,150,-25)
+ backspace.node\append(am.translate(164,-41)^ am.scale(2)^ am.text("<-", color.fg, "left","top"))
+ touch_loc = am.translate(0,0)^ touch_cursor
+ touch_indicator\append(touch_loc)
+ mod.node\append(touch_indicator)
+ print("char selector:",char_selector)
+ mod.node\action(coroutine.create(() ->
+ while true
+ if #main.win\active_touches! > 0
+ touch = main.win\touch_position(1)
+ touch_cursor.color = vec4(1,1,1,1)
+ touch_loc.x = touch.x
+ touch_loc.y = touch.y
+ col_but = bp.check(touch.x, touch.y + 40)
+ n = mod.node("join_id")
+ if #col_but > 0
+ touch_cursor.color = vec4(0.5,0.5,0.5,0.5)
+ if main.win\touch_began(1)
+ for _, button in pairs(mod.buttons)
+ if col_but[1] == button
+ if n.text == default_text
+ n.text = ""
+ print("Found pressed button",button)
+ n.text = n.text .. button.text
+ if col_but[1] == backspace
+ n.text = n.text\sub(1,string.len(n.text)-1)
+ if #n.text > 5
+ print("Time to join!")
+ lobby = require("lobby_menu")
+ table.insert(main.action_queue,{lobby.load,{n.text}})
+ mod.unload!
+
+ coroutine.yield!
+ ))
+
+mod.unload = () ->
+ main.root("screen")\remove(mod.node)
+ for _, button in pairs(mod.buttons)
+ bp.remove(button)
+
+mod
diff --git a/src/js/connect.js b/src/js/connect.js
new file mode 100644
index 0000000..9811107
--- /dev/null
+++ b/src/js/connect.js
@@ -0,0 +1,8 @@
+(()=>{function e(e,t,n,r){Object.defineProperty(e,t,{get:n,set:r,enumerable:!0,configurable:!0})}function t(e){return e&&e.__esModule?e.default:e}class n{constructor(){this.chunkedMTU=16300,this._dataCount=1,this.chunk=e=>{let t=[],n=e.byteLength,r=Math.ceil(n/this.chunkedMTU),i=0,o=0;for(;o<n;){let s=Math.min(n,o+this.chunkedMTU),a=e.slice(o,s),c={__peerData:this._dataCount,n:i,data:a,total:r};t.push(c),o=s,i++}return this._dataCount++,t}}}class r{append_buffer(e){this.flush(),this._parts.push(e)}append(e){this._pieces.push(e)}flush(){if(this._pieces.length>0){let e=new Uint8Array(this._pieces);this._parts.push(e),this._pieces=[]}}toArrayBuffer(){let e=[];for(let t of this._parts)e.push(t);return function(e){let t=0;for(let n of e)t+=n.byteLength;let n=new Uint8Array(t),r=0;for(let t of e){let e=new Uint8Array(t.buffer,t.byteOffset,t.byteLength);n.set(e,r),r+=t.byteLength}return n}(e).buffer}constructor(){this.encoder=new TextEncoder,this._pieces=[],this._parts=[]}}function i(e){return new s(e).unpack()}function o(e){let t=new a,n=t.pack(e);return n instanceof Promise?n.then(()=>t.getBuffer()):t.getBuffer()}class s{unpack(){let e;let t=this.unpack_uint8();if(t<128)return t;if((224^t)<32)return(224^t)-32;if((e=160^t)<=15)return this.unpack_raw(e);if((e=176^t)<=15)return this.unpack_string(e);if((e=144^t)<=15)return this.unpack_array(e);if((e=128^t)<=15)return this.unpack_map(e);switch(t){case 192:return null;case 193:case 212:case 213:case 214:case 215:return;case 194:return!1;case 195:return!0;case 202:return this.unpack_float();case 203:return this.unpack_double();case 204:return this.unpack_uint8();case 205:return this.unpack_uint16();case 206:return this.unpack_uint32();case 207:return this.unpack_uint64();case 208:return this.unpack_int8();case 209:return this.unpack_int16();case 210:return this.unpack_int32();case 211:return this.unpack_int64();case 216:return e=this.unpack_uint16(),this.unpack_string(e);case 217:return e=this.unpack_uint32(),this.unpack_string(e);case 218:return e=this.unpack_uint16(),this.unpack_raw(e);case 219:return e=this.unpack_uint32(),this.unpack_raw(e);case 220:return e=this.unpack_uint16(),this.unpack_array(e);case 221:return e=this.unpack_uint32(),this.unpack_array(e);case 222:return e=this.unpack_uint16(),this.unpack_map(e);case 223:return e=this.unpack_uint32(),this.unpack_map(e)}}unpack_uint8(){let e=255&this.dataView[this.index];return this.index++,e}unpack_uint16(){let e=this.read(2),t=(255&e[0])*256+(255&e[1]);return this.index+=2,t}unpack_uint32(){let e=this.read(4),t=((256*e[0]+e[1])*256+e[2])*256+e[3];return this.index+=4,t}unpack_uint64(){let e=this.read(8),t=((((((256*e[0]+e[1])*256+e[2])*256+e[3])*256+e[4])*256+e[5])*256+e[6])*256+e[7];return this.index+=8,t}unpack_int8(){let e=this.unpack_uint8();return e<128?e:e-256}unpack_int16(){let e=this.unpack_uint16();return e<32768?e:e-65536}unpack_int32(){let e=this.unpack_uint32();return e<2147483648?e:e-4294967296}unpack_int64(){let e=this.unpack_uint64();return e<0x7fffffffffffffff?e:e-18446744073709552e3}unpack_raw(e){if(this.length<this.index+e)throw Error(`BinaryPackFailure: index is out of range ${this.index} ${e} ${this.length}`);let t=this.dataBuffer.slice(this.index,this.index+e);return this.index+=e,t}unpack_string(e){let t,n;let r=this.read(e),i=0,o="";for(;i<e;)(t=r[i])<160?(n=t,i++):(192^t)<32?(n=(31&t)<<6|63&r[i+1],i+=2):(224^t)<16?(n=(15&t)<<12|(63&r[i+1])<<6|63&r[i+2],i+=3):(n=(7&t)<<18|(63&r[i+1])<<12|(63&r[i+2])<<6|63&r[i+3],i+=4),o+=String.fromCodePoint(n);return this.index+=e,o}unpack_array(e){let t=Array(e);for(let n=0;n<e;n++)t[n]=this.unpack();return t}unpack_map(e){let t={};for(let n=0;n<e;n++)t[this.unpack()]=this.unpack();return t}unpack_float(){let e=this.unpack_uint32();return(0==e>>31?1:-1)*(8388607&e|8388608)*2**((e>>23&255)-127-23)}unpack_double(){let e=this.unpack_uint32(),t=this.unpack_uint32(),n=(e>>20&2047)-1023;return(0==e>>31?1:-1)*((1048575&e|1048576)*2**(n-20)+t*2**(n-52))}read(e){let t=this.index;if(t+e<=this.length)return this.dataView.subarray(t,t+e);throw Error("BinaryPackFailure: read index out of range")}constructor(e){this.index=0,this.dataBuffer=e,this.dataView=new Uint8Array(this.dataBuffer),this.length=this.dataBuffer.byteLength}}class a{getBuffer(){return this._bufferBuilder.toArrayBuffer()}pack(e){if("string"==typeof e)this.pack_string(e);else if("number"==typeof e)Math.floor(e)===e?this.pack_integer(e):this.pack_double(e);else if("boolean"==typeof e)!0===e?this._bufferBuilder.append(195):!1===e&&this._bufferBuilder.append(194);else if(void 0===e)this._bufferBuilder.append(192);else if("object"==typeof e){if(null===e)this._bufferBuilder.append(192);else{let t=e.constructor;if(e instanceof Array){let t=this.pack_array(e);if(t instanceof Promise)return t.then(()=>this._bufferBuilder.flush())}else if(e instanceof ArrayBuffer)this.pack_bin(new Uint8Array(e));else if("BYTES_PER_ELEMENT"in e)this.pack_bin(new Uint8Array(e.buffer,e.byteOffset,e.byteLength));else if(e instanceof Date)this.pack_string(e.toString());else if(e instanceof Blob)return e.arrayBuffer().then(e=>{this.pack_bin(new Uint8Array(e)),this._bufferBuilder.flush()});else if(t==Object||t.toString().startsWith("class")){let t=this.pack_object(e);if(t instanceof Promise)return t.then(()=>this._bufferBuilder.flush())}else throw Error(`Type "${t.toString()}" not yet supported`)}}else throw Error(`Type "${typeof e}" not yet supported`);this._bufferBuilder.flush()}pack_bin(e){let t=e.length;if(t<=15)this.pack_uint8(160+t);else if(t<=65535)this._bufferBuilder.append(218),this.pack_uint16(t);else if(t<=4294967295)this._bufferBuilder.append(219),this.pack_uint32(t);else throw Error("Invalid length");this._bufferBuilder.append_buffer(e)}pack_string(e){let t=this._textEncoder.encode(e),n=t.length;if(n<=15)this.pack_uint8(176+n);else if(n<=65535)this._bufferBuilder.append(216),this.pack_uint16(n);else if(n<=4294967295)this._bufferBuilder.append(217),this.pack_uint32(n);else throw Error("Invalid length");this._bufferBuilder.append_buffer(t)}pack_array(e){let t=e.length;if(t<=15)this.pack_uint8(144+t);else if(t<=65535)this._bufferBuilder.append(220),this.pack_uint16(t);else if(t<=4294967295)this._bufferBuilder.append(221),this.pack_uint32(t);else throw Error("Invalid length");let n=r=>{if(r<t){let t=this.pack(e[r]);return t instanceof Promise?t.then(()=>n(r+1)):n(r+1)}};return n(0)}pack_integer(e){if(e>=-32&&e<=127)this._bufferBuilder.append(255&e);else if(e>=0&&e<=255)this._bufferBuilder.append(204),this.pack_uint8(e);else if(e>=-128&&e<=127)this._bufferBuilder.append(208),this.pack_int8(e);else if(e>=0&&e<=65535)this._bufferBuilder.append(205),this.pack_uint16(e);else if(e>=-32768&&e<=32767)this._bufferBuilder.append(209),this.pack_int16(e);else if(e>=0&&e<=4294967295)this._bufferBuilder.append(206),this.pack_uint32(e);else if(e>=-2147483648&&e<=2147483647)this._bufferBuilder.append(210),this.pack_int32(e);else if(e>=-0x8000000000000000&&e<=0x7fffffffffffffff)this._bufferBuilder.append(211),this.pack_int64(e);else if(e>=0&&e<=18446744073709552e3)this._bufferBuilder.append(207),this.pack_uint64(e);else throw Error("Invalid integer")}pack_double(e){let t=0;e<0&&(t=1,e=-e);let n=Math.floor(Math.log(e)/Math.LN2),r=Math.floor((e/2**n-1)*4503599627370496),i=t<<31|n+1023<<20|r/4294967296&1048575;this._bufferBuilder.append(203),this.pack_int32(i),this.pack_int32(r%4294967296)}pack_object(e){let t=Object.keys(e),n=t.length;if(n<=15)this.pack_uint8(128+n);else if(n<=65535)this._bufferBuilder.append(222),this.pack_uint16(n);else if(n<=4294967295)this._bufferBuilder.append(223),this.pack_uint32(n);else throw Error("Invalid length");let r=n=>{if(n<t.length){let i=t[n];if(e.hasOwnProperty(i)){this.pack(i);let t=this.pack(e[i]);if(t instanceof Promise)return t.then(()=>r(n+1))}return r(n+1)}};return r(0)}pack_uint8(e){this._bufferBuilder.append(e)}pack_uint16(e){this._bufferBuilder.append(e>>8),this._bufferBuilder.append(255&e)}pack_uint32(e){let t=4294967295&e;this._bufferBuilder.append((4278190080&t)>>>24),this._bufferBuilder.append((16711680&t)>>>16),this._bufferBuilder.append((65280&t)>>>8),this._bufferBuilder.append(255&t)}pack_uint64(e){let t=e/4294967296,n=e%4294967296;this._bufferBuilder.append((4278190080&t)>>>24),this._bufferBuilder.append((16711680&t)>>>16),this._bufferBuilder.append((65280&t)>>>8),this._bufferBuilder.append(255&t),this._bufferBuilder.append((4278190080&n)>>>24),this._bufferBuilder.append((16711680&n)>>>16),this._bufferBuilder.append((65280&n)>>>8),this._bufferBuilder.append(255&n)}pack_int8(e){this._bufferBuilder.append(255&e)}pack_int16(e){this._bufferBuilder.append((65280&e)>>8),this._bufferBuilder.append(255&e)}pack_int32(e){this._bufferBuilder.append(e>>>24&255),this._bufferBuilder.append((16711680&e)>>>16),this._bufferBuilder.append((65280&e)>>>8),this._bufferBuilder.append(255&e)}pack_int64(e){let t=Math.floor(e/4294967296),n=e%4294967296;this._bufferBuilder.append((4278190080&t)>>>24),this._bufferBuilder.append((16711680&t)>>>16),this._bufferBuilder.append((65280&t)>>>8),this._bufferBuilder.append(255&t),this._bufferBuilder.append((4278190080&n)>>>24),this._bufferBuilder.append((16711680&n)>>>16),this._bufferBuilder.append((65280&n)>>>8),this._bufferBuilder.append(255&n)}constructor(){this._bufferBuilder=new r,this._textEncoder=new TextEncoder}}let c=!0,l=!0;function p(e,t,n){let r=e.match(t);return r&&r.length>=n&&parseInt(r[n],10)}function d(e,t,n){if(!e.RTCPeerConnection)return;let r=e.RTCPeerConnection.prototype,i=r.addEventListener;r.addEventListener=function(e,r){if(e!==t)return i.apply(this,arguments);let o=e=>{let t=n(e);t&&(r.handleEvent?r.handleEvent(t):r(t))};return this._eventMap=this._eventMap||{},this._eventMap[t]||(this._eventMap[t]=new Map),this._eventMap[t].set(r,o),i.apply(this,[e,o])};let o=r.removeEventListener;r.removeEventListener=function(e,n){if(e!==t||!this._eventMap||!this._eventMap[t]||!this._eventMap[t].has(n))return o.apply(this,arguments);let r=this._eventMap[t].get(n);return this._eventMap[t].delete(n),0===this._eventMap[t].size&&delete this._eventMap[t],0===Object.keys(this._eventMap).length&&delete this._eventMap,o.apply(this,[e,r])},Object.defineProperty(r,"on"+t,{get(){return this["_on"+t]},set(e){this["_on"+t]&&(this.removeEventListener(t,this["_on"+t]),delete this["_on"+t]),e&&this.addEventListener(t,this["_on"+t]=e)},enumerable:!0,configurable:!0})}function h(e){return"boolean"!=typeof e?Error("Argument type: "+typeof e+". Please use a boolean."):(c=e,e?"adapter.js logging disabled":"adapter.js logging enabled")}function u(e){return"boolean"!=typeof e?Error("Argument type: "+typeof e+". Please use a boolean."):(l=!e,"adapter.js deprecation warnings "+(e?"disabled":"enabled"))}function f(){"object"!=typeof window||c||"undefined"==typeof console||"function"!=typeof console.log||console.log.apply(console,arguments)}function m(e,t){l&&console.warn(e+" is deprecated, please use "+t+" instead.")}function g(e){return"[object Object]"===Object.prototype.toString.call(e)}function y(e,t,n){let r=n?"outbound-rtp":"inbound-rtp",i=new Map;if(null===t)return i;let o=[];return e.forEach(e=>{"track"===e.type&&e.trackIdentifier===t.id&&o.push(e)}),o.forEach(t=>{e.forEach(n=>{n.type===r&&n.trackId===t.id&&function e(t,n,r){!n||r.has(n.id)||(r.set(n.id,n),Object.keys(n).forEach(i=>{i.endsWith("Id")?e(t,t.get(n[i]),r):i.endsWith("Ids")&&n[i].forEach(n=>{e(t,t.get(n),r)})}))}(e,n,i)})}),i}var _,C,v,b,k,S,T,R,w,P,E,D,x,I,M,O,j={};function L(e,t){let n=e&&e.navigator;if(!n.mediaDevices)return;let r=function(e){if("object"!=typeof e||e.mandatory||e.optional)return e;let t={};return Object.keys(e).forEach(n=>{if("require"===n||"advanced"===n||"mediaSource"===n)return;let r="object"==typeof e[n]?e[n]:{ideal:e[n]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);let i=function(e,t){return e?e+t.charAt(0).toUpperCase()+t.slice(1):"deviceId"===t?"sourceId":t};if(void 0!==r.ideal){t.optional=t.optional||[];let e={};"number"==typeof r.ideal?(e[i("min",n)]=r.ideal,t.optional.push(e),(e={})[i("max",n)]=r.ideal):e[i("",n)]=r.ideal,t.optional.push(e)}void 0!==r.exact&&"number"!=typeof r.exact?(t.mandatory=t.mandatory||{},t.mandatory[i("",n)]=r.exact):["min","max"].forEach(e=>{void 0!==r[e]&&(t.mandatory=t.mandatory||{},t.mandatory[i(e,n)]=r[e])})}),e.advanced&&(t.optional=(t.optional||[]).concat(e.advanced)),t},i=function(e,i){if(t.version>=61)return i(e);if((e=JSON.parse(JSON.stringify(e)))&&"object"==typeof e.audio){let t=function(e,t,n){t in e&&!(n in e)&&(e[n]=e[t],delete e[t])};t((e=JSON.parse(JSON.stringify(e))).audio,"autoGainControl","googAutoGainControl"),t(e.audio,"noiseSuppression","googNoiseSuppression"),e.audio=r(e.audio)}if(e&&"object"==typeof e.video){let o=e.video.facingMode;o=o&&("object"==typeof o?o:{ideal:o});let s=t.version<66;if(o&&("user"===o.exact||"environment"===o.exact||"user"===o.ideal||"environment"===o.ideal)&&!(n.mediaDevices.getSupportedConstraints&&n.mediaDevices.getSupportedConstraints().facingMode&&!s)){let t;if(delete e.video.facingMode,"environment"===o.exact||"environment"===o.ideal?t=["back","rear"]:("user"===o.exact||"user"===o.ideal)&&(t=["front"]),t)return n.mediaDevices.enumerateDevices().then(n=>{let s=(n=n.filter(e=>"videoinput"===e.kind)).find(e=>t.some(t=>e.label.toLowerCase().includes(t)));return!s&&n.length&&t.includes("back")&&(s=n[n.length-1]),s&&(e.video.deviceId=o.exact?{exact:s.deviceId}:{ideal:s.deviceId}),e.video=r(e.video),f("chrome: "+JSON.stringify(e)),i(e)})}e.video=r(e.video)}return f("chrome: "+JSON.stringify(e)),i(e)},o=function(e){return t.version>=64?e:{name:({PermissionDeniedError:"NotAllowedError",PermissionDismissedError:"NotAllowedError",InvalidStateError:"NotAllowedError",DevicesNotFoundError:"NotFoundError",ConstraintNotSatisfiedError:"OverconstrainedError",TrackStartError:"NotReadableError",MediaDeviceFailedDueToShutdown:"NotAllowedError",MediaDeviceKillSwitchOn:"NotAllowedError",TabCaptureError:"AbortError",ScreenCaptureError:"AbortError",DeviceCaptureError:"AbortError"})[e.name]||e.name,message:e.message,constraint:e.constraint||e.constraintName,toString(){return this.name+(this.message&&": ")+this.message}}};if(n.getUserMedia=(function(e,t,r){i(e,e=>{n.webkitGetUserMedia(e,t,e=>{r&&r(o(e))})})}).bind(n),n.mediaDevices.getUserMedia){let e=n.mediaDevices.getUserMedia.bind(n.mediaDevices);n.mediaDevices.getUserMedia=function(t){return i(t,t=>e(t).then(e=>{if(t.audio&&!e.getAudioTracks().length||t.video&&!e.getVideoTracks().length)throw e.getTracks().forEach(e=>{e.stop()}),new DOMException("","NotFoundError");return e},e=>Promise.reject(o(e))))}}}function A(e,t){if((!e.navigator.mediaDevices||!("getDisplayMedia"in e.navigator.mediaDevices))&&e.navigator.mediaDevices){if("function"!=typeof t){console.error("shimGetDisplayMedia: getSourceId argument is not a function");return}e.navigator.mediaDevices.getDisplayMedia=function(n){return t(n).then(t=>{let r=n.video&&n.video.width,i=n.video&&n.video.height,o=n.video&&n.video.frameRate;return n.video={mandatory:{chromeMediaSource:"desktop",chromeMediaSourceId:t,maxFrameRate:o||3}},r&&(n.video.mandatory.maxWidth=r),i&&(n.video.mandatory.maxHeight=i),e.navigator.mediaDevices.getUserMedia(n)})}}}function B(e){e.MediaStream=e.MediaStream||e.webkitMediaStream}function F(e){if("object"!=typeof e||!e.RTCPeerConnection||"ontrack"in e.RTCPeerConnection.prototype)d(e,"track",e=>(e.transceiver||Object.defineProperty(e,"transceiver",{value:{receiver:e.receiver}}),e));else{Object.defineProperty(e.RTCPeerConnection.prototype,"ontrack",{get(){return this._ontrack},set(e){this._ontrack&&this.removeEventListener("track",this._ontrack),this.addEventListener("track",this._ontrack=e)},enumerable:!0,configurable:!0});let t=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(){return this._ontrackpoly||(this._ontrackpoly=t=>{t.stream.addEventListener("addtrack",n=>{let r;r=e.RTCPeerConnection.prototype.getReceivers?this.getReceivers().find(e=>e.track&&e.track.id===n.track.id):{track:n.track};let i=new Event("track");i.track=n.track,i.receiver=r,i.transceiver={receiver:r},i.streams=[t.stream],this.dispatchEvent(i)}),t.stream.getTracks().forEach(n=>{let r;r=e.RTCPeerConnection.prototype.getReceivers?this.getReceivers().find(e=>e.track&&e.track.id===n.id):{track:n};let i=new Event("track");i.track=n,i.receiver=r,i.transceiver={receiver:r},i.streams=[t.stream],this.dispatchEvent(i)})},this.addEventListener("addstream",this._ontrackpoly)),t.apply(this,arguments)}}}function U(e){if("object"==typeof e&&e.RTCPeerConnection&&!("getSenders"in e.RTCPeerConnection.prototype)&&"createDTMFSender"in e.RTCPeerConnection.prototype){let t=function(e,t){return{track:t,get dtmf(){return void 0===this._dtmf&&("audio"===t.kind?this._dtmf=e.createDTMFSender(t):this._dtmf=null),this._dtmf},_pc:e}};if(!e.RTCPeerConnection.prototype.getSenders){e.RTCPeerConnection.prototype.getSenders=function(){return this._senders=this._senders||[],this._senders.slice()};let n=e.RTCPeerConnection.prototype.addTrack;e.RTCPeerConnection.prototype.addTrack=function(e,r){let i=n.apply(this,arguments);return i||(i=t(this,e),this._senders.push(i)),i};let r=e.RTCPeerConnection.prototype.removeTrack;e.RTCPeerConnection.prototype.removeTrack=function(e){r.apply(this,arguments);let t=this._senders.indexOf(e);-1!==t&&this._senders.splice(t,1)}}let n=e.RTCPeerConnection.prototype.addStream;e.RTCPeerConnection.prototype.addStream=function(e){this._senders=this._senders||[],n.apply(this,[e]),e.getTracks().forEach(e=>{this._senders.push(t(this,e))})};let r=e.RTCPeerConnection.prototype.removeStream;e.RTCPeerConnection.prototype.removeStream=function(e){this._senders=this._senders||[],r.apply(this,[e]),e.getTracks().forEach(e=>{let t=this._senders.find(t=>t.track===e);t&&this._senders.splice(this._senders.indexOf(t),1)})}}else if("object"==typeof e&&e.RTCPeerConnection&&"getSenders"in e.RTCPeerConnection.prototype&&"createDTMFSender"in e.RTCPeerConnection.prototype&&e.RTCRtpSender&&!("dtmf"in e.RTCRtpSender.prototype)){let t=e.RTCPeerConnection.prototype.getSenders;e.RTCPeerConnection.prototype.getSenders=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e},Object.defineProperty(e.RTCRtpSender.prototype,"dtmf",{get(){return void 0===this._dtmf&&("audio"===this.track.kind?this._dtmf=this._pc.createDTMFSender(this.track):this._dtmf=null),this._dtmf}})}}function z(e){if(!e.RTCPeerConnection)return;let t=e.RTCPeerConnection.prototype.getStats;e.RTCPeerConnection.prototype.getStats=function(){let[e,n,r]=arguments;if(arguments.length>0&&"function"==typeof e)return t.apply(this,arguments);if(0===t.length&&(0==arguments.length||"function"!=typeof e))return t.apply(this,[]);let i=function(e){let t={};return e.result().forEach(e=>{let n={id:e.id,timestamp:e.timestamp,type:{localcandidate:"local-candidate",remotecandidate:"remote-candidate"}[e.type]||e.type};e.names().forEach(t=>{n[t]=e.stat(t)}),t[n.id]=n}),t},o=function(e){return new Map(Object.keys(e).map(t=>[t,e[t]]))};return arguments.length>=2?t.apply(this,[function(e){n(o(i(e)))},e]):new Promise((e,n)=>{t.apply(this,[function(t){e(o(i(t)))},n])}).then(n,r)}}function N(e){if(!("object"==typeof e&&e.RTCPeerConnection&&e.RTCRtpSender&&e.RTCRtpReceiver))return;if(!("getStats"in e.RTCRtpSender.prototype)){let t=e.RTCPeerConnection.prototype.getSenders;t&&(e.RTCPeerConnection.prototype.getSenders=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e});let n=e.RTCPeerConnection.prototype.addTrack;n&&(e.RTCPeerConnection.prototype.addTrack=function(){let e=n.apply(this,arguments);return e._pc=this,e}),e.RTCRtpSender.prototype.getStats=function(){let e=this;return this._pc.getStats().then(t=>y(t,e.track,!0))}}if(!("getStats"in e.RTCRtpReceiver.prototype)){let t=e.RTCPeerConnection.prototype.getReceivers;t&&(e.RTCPeerConnection.prototype.getReceivers=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e}),d(e,"track",e=>(e.receiver._pc=e.srcElement,e)),e.RTCRtpReceiver.prototype.getStats=function(){let e=this;return this._pc.getStats().then(t=>y(t,e.track,!1))}}if(!("getStats"in e.RTCRtpSender.prototype&&"getStats"in e.RTCRtpReceiver.prototype))return;let t=e.RTCPeerConnection.prototype.getStats;e.RTCPeerConnection.prototype.getStats=function(){if(arguments.length>0&&arguments[0]instanceof e.MediaStreamTrack){let e,t,n;let r=arguments[0];return(this.getSenders().forEach(t=>{t.track===r&&(e?n=!0:e=t)}),this.getReceivers().forEach(e=>(e.track===r&&(t?n=!0:t=e),e.track===r)),n||e&&t)?Promise.reject(new DOMException("There are more than one sender or receiver for the track.","InvalidAccessError")):e?e.getStats():t?t.getStats():Promise.reject(new DOMException("There is no sender or receiver for the track.","InvalidAccessError"))}return t.apply(this,arguments)}}function $(e){e.RTCPeerConnection.prototype.getLocalStreams=function(){return this._shimmedLocalStreams=this._shimmedLocalStreams||{},Object.keys(this._shimmedLocalStreams).map(e=>this._shimmedLocalStreams[e][0])};let t=e.RTCPeerConnection.prototype.addTrack;e.RTCPeerConnection.prototype.addTrack=function(e,n){if(!n)return t.apply(this,arguments);this._shimmedLocalStreams=this._shimmedLocalStreams||{};let r=t.apply(this,arguments);return this._shimmedLocalStreams[n.id]?-1===this._shimmedLocalStreams[n.id].indexOf(r)&&this._shimmedLocalStreams[n.id].push(r):this._shimmedLocalStreams[n.id]=[n,r],r};let n=e.RTCPeerConnection.prototype.addStream;e.RTCPeerConnection.prototype.addStream=function(e){this._shimmedLocalStreams=this._shimmedLocalStreams||{},e.getTracks().forEach(e=>{if(this.getSenders().find(t=>t.track===e))throw new DOMException("Track already exists.","InvalidAccessError")});let t=this.getSenders();n.apply(this,arguments);let r=this.getSenders().filter(e=>-1===t.indexOf(e));this._shimmedLocalStreams[e.id]=[e].concat(r)};let r=e.RTCPeerConnection.prototype.removeStream;e.RTCPeerConnection.prototype.removeStream=function(e){return this._shimmedLocalStreams=this._shimmedLocalStreams||{},delete this._shimmedLocalStreams[e.id],r.apply(this,arguments)};let i=e.RTCPeerConnection.prototype.removeTrack;e.RTCPeerConnection.prototype.removeTrack=function(e){return this._shimmedLocalStreams=this._shimmedLocalStreams||{},e&&Object.keys(this._shimmedLocalStreams).forEach(t=>{let n=this._shimmedLocalStreams[t].indexOf(e);-1!==n&&this._shimmedLocalStreams[t].splice(n,1),1===this._shimmedLocalStreams[t].length&&delete this._shimmedLocalStreams[t]}),i.apply(this,arguments)}}function J(e,t){if(!e.RTCPeerConnection)return;if(e.RTCPeerConnection.prototype.addTrack&&t.version>=65)return $(e);let n=e.RTCPeerConnection.prototype.getLocalStreams;e.RTCPeerConnection.prototype.getLocalStreams=function(){let e=n.apply(this);return this._reverseStreams=this._reverseStreams||{},e.map(e=>this._reverseStreams[e.id])};let r=e.RTCPeerConnection.prototype.addStream;e.RTCPeerConnection.prototype.addStream=function(t){if(this._streams=this._streams||{},this._reverseStreams=this._reverseStreams||{},t.getTracks().forEach(e=>{if(this.getSenders().find(t=>t.track===e))throw new DOMException("Track already exists.","InvalidAccessError")}),!this._reverseStreams[t.id]){let n=new e.MediaStream(t.getTracks());this._streams[t.id]=n,this._reverseStreams[n.id]=t,t=n}r.apply(this,[t])};let i=e.RTCPeerConnection.prototype.removeStream;function o(e,t){let n=t.sdp;return Object.keys(e._reverseStreams||[]).forEach(t=>{let r=e._reverseStreams[t],i=e._streams[r.id];n=n.replace(RegExp(i.id,"g"),r.id)}),new RTCSessionDescription({type:t.type,sdp:n})}e.RTCPeerConnection.prototype.removeStream=function(e){this._streams=this._streams||{},this._reverseStreams=this._reverseStreams||{},i.apply(this,[this._streams[e.id]||e]),delete this._reverseStreams[this._streams[e.id]?this._streams[e.id].id:e.id],delete this._streams[e.id]},e.RTCPeerConnection.prototype.addTrack=function(t,n){if("closed"===this.signalingState)throw new DOMException("The RTCPeerConnection's signalingState is 'closed'.","InvalidStateError");let r=[].slice.call(arguments,1);if(1!==r.length||!r[0].getTracks().find(e=>e===t))throw new DOMException("The adapter.js addTrack polyfill only supports a single stream which is associated with the specified track.","NotSupportedError");if(this.getSenders().find(e=>e.track===t))throw new DOMException("Track already exists.","InvalidAccessError");this._streams=this._streams||{},this._reverseStreams=this._reverseStreams||{};let i=this._streams[n.id];if(i)i.addTrack(t),Promise.resolve().then(()=>{this.dispatchEvent(new Event("negotiationneeded"))});else{let r=new e.MediaStream([t]);this._streams[n.id]=r,this._reverseStreams[r.id]=n,this.addStream(r)}return this.getSenders().find(e=>e.track===t)},["createOffer","createAnswer"].forEach(function(t){let n=e.RTCPeerConnection.prototype[t];e.RTCPeerConnection.prototype[t]=({[t](){let e=arguments,t=arguments.length&&"function"==typeof arguments[0];return t?n.apply(this,[t=>{let n=o(this,t);e[0].apply(null,[n])},t=>{e[1]&&e[1].apply(null,t)},arguments[2]]):n.apply(this,arguments).then(e=>o(this,e))}})[t]});let s=e.RTCPeerConnection.prototype.setLocalDescription;e.RTCPeerConnection.prototype.setLocalDescription=function(){var e,t;let n;return arguments.length&&arguments[0].type&&(arguments[0]=(e=this,t=arguments[0],n=t.sdp,Object.keys(e._reverseStreams||[]).forEach(t=>{let r=e._reverseStreams[t],i=e._streams[r.id];n=n.replace(RegExp(r.id,"g"),i.id)}),new RTCSessionDescription({type:t.type,sdp:n}))),s.apply(this,arguments)};let a=Object.getOwnPropertyDescriptor(e.RTCPeerConnection.prototype,"localDescription");Object.defineProperty(e.RTCPeerConnection.prototype,"localDescription",{get(){let e=a.get.apply(this);return""===e.type?e:o(this,e)}}),e.RTCPeerConnection.prototype.removeTrack=function(e){let t;if("closed"===this.signalingState)throw new DOMException("The RTCPeerConnection's signalingState is 'closed'.","InvalidStateError");if(!e._pc)throw new DOMException("Argument 1 of RTCPeerConnection.removeTrack does not implement interface RTCRtpSender.","TypeError");if(e._pc!==this)throw new DOMException("Sender was not created by this connection.","InvalidAccessError");this._streams=this._streams||{},Object.keys(this._streams).forEach(n=>{this._streams[n].getTracks().find(t=>e.track===t)&&(t=this._streams[n])}),t&&(1===t.getTracks().length?this.removeStream(this._reverseStreams[t.id]):t.removeTrack(e.track),this.dispatchEvent(new Event("negotiationneeded")))}}function V(e,t){!e.RTCPeerConnection&&e.webkitRTCPeerConnection&&(e.RTCPeerConnection=e.webkitRTCPeerConnection),e.RTCPeerConnection&&t.version<53&&["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(t){let n=e.RTCPeerConnection.prototype[t];e.RTCPeerConnection.prototype[t]=({[t](){return arguments[0]=new("addIceCandidate"===t?e.RTCIceCandidate:e.RTCSessionDescription)(arguments[0]),n.apply(this,arguments)}})[t]})}function G(e,t){d(e,"negotiationneeded",e=>{let n=e.target;if(!(t.version<72)&&(!n.getConfiguration||"plan-b"!==n.getConfiguration().sdpSemantics)||"stable"===n.signalingState)return e})}e(j,"shimMediaStream",()=>B),e(j,"shimOnTrack",()=>F),e(j,"shimGetSendersWithDtmf",()=>U),e(j,"shimGetStats",()=>z),e(j,"shimSenderReceiverGetStats",()=>N),e(j,"shimAddTrackRemoveTrackWithNative",()=>$),e(j,"shimAddTrackRemoveTrack",()=>J),e(j,"shimPeerConnection",()=>V),e(j,"fixNegotiationNeeded",()=>G),e(j,"shimGetUserMedia",()=>L),e(j,"shimGetDisplayMedia",()=>A);var W={};function H(e,t){let n=e&&e.navigator,r=e&&e.MediaStreamTrack;if(n.getUserMedia=function(e,t,r){m("navigator.getUserMedia","navigator.mediaDevices.getUserMedia"),n.mediaDevices.getUserMedia(e).then(t,r)},!(t.version>55&&"autoGainControl"in n.mediaDevices.getSupportedConstraints())){let e=function(e,t,n){t in e&&!(n in e)&&(e[n]=e[t],delete e[t])},t=n.mediaDevices.getUserMedia.bind(n.mediaDevices);if(n.mediaDevices.getUserMedia=function(n){return"object"==typeof n&&"object"==typeof n.audio&&(e((n=JSON.parse(JSON.stringify(n))).audio,"autoGainControl","mozAutoGainControl"),e(n.audio,"noiseSuppression","mozNoiseSuppression")),t(n)},r&&r.prototype.getSettings){let t=r.prototype.getSettings;r.prototype.getSettings=function(){let n=t.apply(this,arguments);return e(n,"mozAutoGainControl","autoGainControl"),e(n,"mozNoiseSuppression","noiseSuppression"),n}}if(r&&r.prototype.applyConstraints){let t=r.prototype.applyConstraints;r.prototype.applyConstraints=function(n){return"audio"===this.kind&&"object"==typeof n&&(e(n=JSON.parse(JSON.stringify(n)),"autoGainControl","mozAutoGainControl"),e(n,"noiseSuppression","mozNoiseSuppression")),t.apply(this,[n])}}}}function Y(e,t){e.navigator.mediaDevices&&"getDisplayMedia"in e.navigator.mediaDevices||!e.navigator.mediaDevices||(e.navigator.mediaDevices.getDisplayMedia=function(n){if(!(n&&n.video)){let e=new DOMException("getDisplayMedia without video constraints is undefined");return e.name="NotFoundError",e.code=8,Promise.reject(e)}return!0===n.video?n.video={mediaSource:t}:n.video.mediaSource=t,e.navigator.mediaDevices.getUserMedia(n)})}function K(e){"object"==typeof e&&e.RTCTrackEvent&&"receiver"in e.RTCTrackEvent.prototype&&!("transceiver"in e.RTCTrackEvent.prototype)&&Object.defineProperty(e.RTCTrackEvent.prototype,"transceiver",{get(){return{receiver:this.receiver}}})}function X(e,t){if("object"!=typeof e||!(e.RTCPeerConnection||e.mozRTCPeerConnection))return;!e.RTCPeerConnection&&e.mozRTCPeerConnection&&(e.RTCPeerConnection=e.mozRTCPeerConnection),t.version<53&&["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(t){let n=e.RTCPeerConnection.prototype[t];e.RTCPeerConnection.prototype[t]=({[t](){return arguments[0]=new("addIceCandidate"===t?e.RTCIceCandidate:e.RTCSessionDescription)(arguments[0]),n.apply(this,arguments)}})[t]});let n={inboundrtp:"inbound-rtp",outboundrtp:"outbound-rtp",candidatepair:"candidate-pair",localcandidate:"local-candidate",remotecandidate:"remote-candidate"},r=e.RTCPeerConnection.prototype.getStats;e.RTCPeerConnection.prototype.getStats=function(){let[e,i,o]=arguments;return r.apply(this,[e||null]).then(e=>{if(t.version<53&&!i)try{e.forEach(e=>{e.type=n[e.type]||e.type})}catch(t){if("TypeError"!==t.name)throw t;e.forEach((t,r)=>{e.set(r,Object.assign({},t,{type:n[t.type]||t.type}))})}return e}).then(i,o)}}function q(e){if(!("object"==typeof e&&e.RTCPeerConnection&&e.RTCRtpSender)||e.RTCRtpSender&&"getStats"in e.RTCRtpSender.prototype)return;let t=e.RTCPeerConnection.prototype.getSenders;t&&(e.RTCPeerConnection.prototype.getSenders=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e});let n=e.RTCPeerConnection.prototype.addTrack;n&&(e.RTCPeerConnection.prototype.addTrack=function(){let e=n.apply(this,arguments);return e._pc=this,e}),e.RTCRtpSender.prototype.getStats=function(){return this.track?this._pc.getStats(this.track):Promise.resolve(new Map)}}function Q(e){if(!("object"==typeof e&&e.RTCPeerConnection&&e.RTCRtpSender)||e.RTCRtpSender&&"getStats"in e.RTCRtpReceiver.prototype)return;let t=e.RTCPeerConnection.prototype.getReceivers;t&&(e.RTCPeerConnection.prototype.getReceivers=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e}),d(e,"track",e=>(e.receiver._pc=e.srcElement,e)),e.RTCRtpReceiver.prototype.getStats=function(){return this._pc.getStats(this.track)}}function Z(e){!e.RTCPeerConnection||"removeStream"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.removeStream=function(e){m("removeStream","removeTrack"),this.getSenders().forEach(t=>{t.track&&e.getTracks().includes(t.track)&&this.removeTrack(t)})})}function ee(e){e.DataChannel&&!e.RTCDataChannel&&(e.RTCDataChannel=e.DataChannel)}function et(e){if(!("object"==typeof e&&e.RTCPeerConnection))return;let t=e.RTCPeerConnection.prototype.addTransceiver;t&&(e.RTCPeerConnection.prototype.addTransceiver=function(){this.setParametersPromises=[];let e=arguments[1]&&arguments[1].sendEncodings;void 0===e&&(e=[]);let n=(e=[...e]).length>0;n&&e.forEach(e=>{if("rid"in e&&!/^[a-z0-9]{0,16}$/i.test(e.rid))throw TypeError("Invalid RID value provided.");if("scaleResolutionDownBy"in e&&!(parseFloat(e.scaleResolutionDownBy)>=1))throw RangeError("scale_resolution_down_by must be >= 1.0");if("maxFramerate"in e&&!(parseFloat(e.maxFramerate)>=0))throw RangeError("max_framerate must be >= 0.0")});let r=t.apply(this,arguments);if(n){let{sender:t}=r,n=t.getParameters();"encodings"in n&&(1!==n.encodings.length||0!==Object.keys(n.encodings[0]).length)||(n.encodings=e,t.sendEncodings=e,this.setParametersPromises.push(t.setParameters(n).then(()=>{delete t.sendEncodings}).catch(()=>{delete t.sendEncodings})))}return r})}function en(e){if(!("object"==typeof e&&e.RTCRtpSender))return;let t=e.RTCRtpSender.prototype.getParameters;t&&(e.RTCRtpSender.prototype.getParameters=function(){let e=t.apply(this,arguments);return"encodings"in e||(e.encodings=[].concat(this.sendEncodings||[{}])),e})}function er(e){if(!("object"==typeof e&&e.RTCPeerConnection))return;let t=e.RTCPeerConnection.prototype.createOffer;e.RTCPeerConnection.prototype.createOffer=function(){return this.setParametersPromises&&this.setParametersPromises.length?Promise.all(this.setParametersPromises).then(()=>t.apply(this,arguments)).finally(()=>{this.setParametersPromises=[]}):t.apply(this,arguments)}}function ei(e){if(!("object"==typeof e&&e.RTCPeerConnection))return;let t=e.RTCPeerConnection.prototype.createAnswer;e.RTCPeerConnection.prototype.createAnswer=function(){return this.setParametersPromises&&this.setParametersPromises.length?Promise.all(this.setParametersPromises).then(()=>t.apply(this,arguments)).finally(()=>{this.setParametersPromises=[]}):t.apply(this,arguments)}}e(W,"shimOnTrack",()=>K),e(W,"shimPeerConnection",()=>X),e(W,"shimSenderGetStats",()=>q),e(W,"shimReceiverGetStats",()=>Q),e(W,"shimRemoveStream",()=>Z),e(W,"shimRTCDataChannel",()=>ee),e(W,"shimAddTransceiver",()=>et),e(W,"shimGetParameters",()=>en),e(W,"shimCreateOffer",()=>er),e(W,"shimCreateAnswer",()=>ei),e(W,"shimGetUserMedia",()=>H),e(W,"shimGetDisplayMedia",()=>Y);var eo={};function es(e){if("object"==typeof e&&e.RTCPeerConnection){if("getLocalStreams"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.getLocalStreams=function(){return this._localStreams||(this._localStreams=[]),this._localStreams}),!("addStream"in e.RTCPeerConnection.prototype)){let t=e.RTCPeerConnection.prototype.addTrack;e.RTCPeerConnection.prototype.addStream=function(e){this._localStreams||(this._localStreams=[]),this._localStreams.includes(e)||this._localStreams.push(e),e.getAudioTracks().forEach(n=>t.call(this,n,e)),e.getVideoTracks().forEach(n=>t.call(this,n,e))},e.RTCPeerConnection.prototype.addTrack=function(e,...n){return n&&n.forEach(e=>{this._localStreams?this._localStreams.includes(e)||this._localStreams.push(e):this._localStreams=[e]}),t.apply(this,arguments)}}"removeStream"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.removeStream=function(e){this._localStreams||(this._localStreams=[]);let t=this._localStreams.indexOf(e);if(-1===t)return;this._localStreams.splice(t,1);let n=e.getTracks();this.getSenders().forEach(e=>{n.includes(e.track)&&this.removeTrack(e)})})}}function ea(e){if("object"==typeof e&&e.RTCPeerConnection&&("getRemoteStreams"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[]}),!("onaddstream"in e.RTCPeerConnection.prototype))){Object.defineProperty(e.RTCPeerConnection.prototype,"onaddstream",{get(){return this._onaddstream},set(e){this._onaddstream&&(this.removeEventListener("addstream",this._onaddstream),this.removeEventListener("track",this._onaddstreampoly)),this.addEventListener("addstream",this._onaddstream=e),this.addEventListener("track",this._onaddstreampoly=e=>{e.streams.forEach(e=>{if(this._remoteStreams||(this._remoteStreams=[]),this._remoteStreams.includes(e))return;this._remoteStreams.push(e);let t=new Event("addstream");t.stream=e,this.dispatchEvent(t)})})}});let t=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(){let e=this;return this._onaddstreampoly||this.addEventListener("track",this._onaddstreampoly=function(t){t.streams.forEach(t=>{if(e._remoteStreams||(e._remoteStreams=[]),e._remoteStreams.indexOf(t)>=0)return;e._remoteStreams.push(t);let n=new Event("addstream");n.stream=t,e.dispatchEvent(n)})}),t.apply(e,arguments)}}}function ec(e){if("object"!=typeof e||!e.RTCPeerConnection)return;let t=e.RTCPeerConnection.prototype,n=t.createOffer,r=t.createAnswer,i=t.setLocalDescription,o=t.setRemoteDescription,s=t.addIceCandidate;t.createOffer=function(e,t){let r=arguments.length>=2?arguments[2]:arguments[0],i=n.apply(this,[r]);return t?(i.then(e,t),Promise.resolve()):i},t.createAnswer=function(e,t){let n=arguments.length>=2?arguments[2]:arguments[0],i=r.apply(this,[n]);return t?(i.then(e,t),Promise.resolve()):i};let a=function(e,t,n){let r=i.apply(this,[e]);return n?(r.then(t,n),Promise.resolve()):r};t.setLocalDescription=a,a=function(e,t,n){let r=o.apply(this,[e]);return n?(r.then(t,n),Promise.resolve()):r},t.setRemoteDescription=a,a=function(e,t,n){let r=s.apply(this,[e]);return n?(r.then(t,n),Promise.resolve()):r},t.addIceCandidate=a}function el(e){let t=e&&e.navigator;if(t.mediaDevices&&t.mediaDevices.getUserMedia){let e=t.mediaDevices,n=e.getUserMedia.bind(e);t.mediaDevices.getUserMedia=e=>n(ep(e))}!t.getUserMedia&&t.mediaDevices&&t.mediaDevices.getUserMedia&&(t.getUserMedia=(function(e,n,r){t.mediaDevices.getUserMedia(e).then(n,r)}).bind(t))}function ep(e){return e&&void 0!==e.video?Object.assign({},e,{video:function e(t){return g(t)?Object.keys(t).reduce(function(n,r){let i=g(t[r]),o=i?e(t[r]):t[r],s=i&&!Object.keys(o).length;return void 0===o||s?n:Object.assign(n,{[r]:o})},{}):t}(e.video)}):e}function ed(e){if(!e.RTCPeerConnection)return;let t=e.RTCPeerConnection;e.RTCPeerConnection=function(e,n){if(e&&e.iceServers){let t=[];for(let n=0;n<e.iceServers.length;n++){let r=e.iceServers[n];void 0===r.urls&&r.url?(m("RTCIceServer.url","RTCIceServer.urls"),(r=JSON.parse(JSON.stringify(r))).urls=r.url,delete r.url,t.push(r)):t.push(e.iceServers[n])}e.iceServers=t}return new t(e,n)},e.RTCPeerConnection.prototype=t.prototype,"generateCertificate"in t&&Object.defineProperty(e.RTCPeerConnection,"generateCertificate",{get:()=>t.generateCertificate})}function eh(e){"object"==typeof e&&e.RTCTrackEvent&&"receiver"in e.RTCTrackEvent.prototype&&!("transceiver"in e.RTCTrackEvent.prototype)&&Object.defineProperty(e.RTCTrackEvent.prototype,"transceiver",{get(){return{receiver:this.receiver}}})}function eu(e){let t=e.RTCPeerConnection.prototype.createOffer;e.RTCPeerConnection.prototype.createOffer=function(e){if(e){void 0!==e.offerToReceiveAudio&&(e.offerToReceiveAudio=!!e.offerToReceiveAudio);let t=this.getTransceivers().find(e=>"audio"===e.receiver.track.kind);!1===e.offerToReceiveAudio&&t?"sendrecv"===t.direction?t.setDirection?t.setDirection("sendonly"):t.direction="sendonly":"recvonly"===t.direction&&(t.setDirection?t.setDirection("inactive"):t.direction="inactive"):!0!==e.offerToReceiveAudio||t||this.addTransceiver("audio",{direction:"recvonly"}),void 0!==e.offerToReceiveVideo&&(e.offerToReceiveVideo=!!e.offerToReceiveVideo);let n=this.getTransceivers().find(e=>"video"===e.receiver.track.kind);!1===e.offerToReceiveVideo&&n?"sendrecv"===n.direction?n.setDirection?n.setDirection("sendonly"):n.direction="sendonly":"recvonly"===n.direction&&(n.setDirection?n.setDirection("inactive"):n.direction="inactive"):!0!==e.offerToReceiveVideo||n||this.addTransceiver("video",{direction:"recvonly"})}return t.apply(this,arguments)}}function ef(e){"object"!=typeof e||e.AudioContext||(e.AudioContext=e.webkitAudioContext)}e(eo,"shimLocalStreamsAPI",()=>es),e(eo,"shimRemoteStreamsAPI",()=>ea),e(eo,"shimCallbacksAPI",()=>ec),e(eo,"shimGetUserMedia",()=>el),e(eo,"shimConstraints",()=>ep),e(eo,"shimRTCIceServerUrls",()=>ed),e(eo,"shimTrackEventTransceiver",()=>eh),e(eo,"shimCreateOfferLegacy",()=>eu),e(eo,"shimAudioContext",()=>ef);var em={};e(em,"shimRTCIceCandidate",()=>e_),e(em,"shimRTCIceCandidateRelayProtocol",()=>eC),e(em,"shimMaxMessageSize",()=>ev),e(em,"shimSendThrowTypeError",()=>eb),e(em,"shimConnectionState",()=>ek),e(em,"removeExtmapAllowMixed",()=>eS),e(em,"shimAddIceCandidateNullOrEmpty",()=>eT),e(em,"shimParameterlessSetLocalDescription",()=>eR);var eg={};let ey={};function e_(e){if(!e.RTCIceCandidate||e.RTCIceCandidate&&"foundation"in e.RTCIceCandidate.prototype)return;let n=e.RTCIceCandidate;e.RTCIceCandidate=function(e){if("object"==typeof e&&e.candidate&&0===e.candidate.indexOf("a=")&&((e=JSON.parse(JSON.stringify(e))).candidate=e.candidate.substring(2)),e.candidate&&e.candidate.length){let r=new n(e),i=t(eg).parseCandidate(e.candidate);for(let e in i)e in r||Object.defineProperty(r,e,{value:i[e]});return r.toJSON=function(){return{candidate:r.candidate,sdpMid:r.sdpMid,sdpMLineIndex:r.sdpMLineIndex,usernameFragment:r.usernameFragment}},r}return new n(e)},e.RTCIceCandidate.prototype=n.prototype,d(e,"icecandidate",t=>(t.candidate&&Object.defineProperty(t,"candidate",{value:new e.RTCIceCandidate(t.candidate),writable:"false"}),t))}function eC(e){!e.RTCIceCandidate||e.RTCIceCandidate&&"relayProtocol"in e.RTCIceCandidate.prototype||d(e,"icecandidate",e=>{if(e.candidate){let n=t(eg).parseCandidate(e.candidate.candidate);"relay"===n.type&&(e.candidate.relayProtocol=({0:"tls",1:"tcp",2:"udp"})[n.priority>>24])}return e})}function ev(e,n){if(!e.RTCPeerConnection)return;"sctp"in e.RTCPeerConnection.prototype||Object.defineProperty(e.RTCPeerConnection.prototype,"sctp",{get(){return void 0===this._sctp?null:this._sctp}});let r=function(e){if(!e||!e.sdp)return!1;let n=t(eg).splitSections(e.sdp);return n.shift(),n.some(e=>{let n=t(eg).parseMLine(e);return n&&"application"===n.kind&&-1!==n.protocol.indexOf("SCTP")})},i=function(e){let t=e.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);if(null===t||t.length<2)return -1;let n=parseInt(t[1],10);return n!=n?-1:n},o=function(e){let t=65536;return"firefox"===n.browser&&(t=n.version<57?-1===e?16384:2147483637:n.version<60?57===n.version?65535:65536:2147483637),t},s=function(e,r){let i=65536;"firefox"===n.browser&&57===n.version&&(i=65535);let o=t(eg).matchPrefix(e.sdp,"a=max-message-size:");return o.length>0?i=parseInt(o[0].substring(19),10):"firefox"===n.browser&&-1!==r&&(i=2147483637),i},a=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(){if(this._sctp=null,"chrome"===n.browser&&n.version>=76){let{sdpSemantics:e}=this.getConfiguration();"plan-b"===e&&Object.defineProperty(this,"sctp",{get(){return void 0===this._sctp?null:this._sctp},enumerable:!0,configurable:!0})}if(r(arguments[0])){let e;let t=i(arguments[0]),n=o(t),r=s(arguments[0],t);e=0===n&&0===r?Number.POSITIVE_INFINITY:0===n||0===r?Math.max(n,r):Math.min(n,r);let a={};Object.defineProperty(a,"maxMessageSize",{get:()=>e}),this._sctp=a}return a.apply(this,arguments)}}function eb(e){if(!(e.RTCPeerConnection&&"createDataChannel"in e.RTCPeerConnection.prototype))return;function t(e,t){let n=e.send;e.send=function(){let r=arguments[0],i=r.length||r.size||r.byteLength;if("open"===e.readyState&&t.sctp&&i>t.sctp.maxMessageSize)throw TypeError("Message too large (can send a maximum of "+t.sctp.maxMessageSize+" bytes)");return n.apply(e,arguments)}}let n=e.RTCPeerConnection.prototype.createDataChannel;e.RTCPeerConnection.prototype.createDataChannel=function(){let e=n.apply(this,arguments);return t(e,this),e},d(e,"datachannel",e=>(t(e.channel,e.target),e))}function ek(e){if(!e.RTCPeerConnection||"connectionState"in e.RTCPeerConnection.prototype)return;let t=e.RTCPeerConnection.prototype;Object.defineProperty(t,"connectionState",{get(){return({completed:"connected",checking:"connecting"})[this.iceConnectionState]||this.iceConnectionState},enumerable:!0,configurable:!0}),Object.defineProperty(t,"onconnectionstatechange",{get(){return this._onconnectionstatechange||null},set(e){this._onconnectionstatechange&&(this.removeEventListener("connectionstatechange",this._onconnectionstatechange),delete this._onconnectionstatechange),e&&this.addEventListener("connectionstatechange",this._onconnectionstatechange=e)},enumerable:!0,configurable:!0}),["setLocalDescription","setRemoteDescription"].forEach(e=>{let n=t[e];t[e]=function(){return this._connectionstatechangepoly||(this._connectionstatechangepoly=e=>{let t=e.target;if(t._lastConnectionState!==t.connectionState){t._lastConnectionState=t.connectionState;let n=new Event("connectionstatechange",e);t.dispatchEvent(n)}return e},this.addEventListener("iceconnectionstatechange",this._connectionstatechangepoly)),n.apply(this,arguments)}})}function eS(e,t){if(!e.RTCPeerConnection||"chrome"===t.browser&&t.version>=71||"safari"===t.browser&&t.version>=605)return;let n=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(t){if(t&&t.sdp&&-1!==t.sdp.indexOf("\na=extmap-allow-mixed")){let n=t.sdp.split("\n").filter(e=>"a=extmap-allow-mixed"!==e.trim()).join("\n");e.RTCSessionDescription&&t instanceof e.RTCSessionDescription?arguments[0]=new e.RTCSessionDescription({type:t.type,sdp:n}):t.sdp=n}return n.apply(this,arguments)}}function eT(e,t){if(!(e.RTCPeerConnection&&e.RTCPeerConnection.prototype))return;let n=e.RTCPeerConnection.prototype.addIceCandidate;n&&0!==n.length&&(e.RTCPeerConnection.prototype.addIceCandidate=function(){return arguments[0]?("chrome"===t.browser&&t.version<78||"firefox"===t.browser&&t.version<68||"safari"===t.browser)&&arguments[0]&&""===arguments[0].candidate?Promise.resolve():n.apply(this,arguments):(arguments[1]&&arguments[1].apply(null),Promise.resolve())})}function eR(e,t){if(!(e.RTCPeerConnection&&e.RTCPeerConnection.prototype))return;let n=e.RTCPeerConnection.prototype.setLocalDescription;n&&0!==n.length&&(e.RTCPeerConnection.prototype.setLocalDescription=function(){let e=arguments[0]||{};if("object"!=typeof e||e.type&&e.sdp)return n.apply(this,arguments);if(!(e={type:e.type,sdp:e.sdp}).type)switch(this.signalingState){case"stable":case"have-local-offer":case"have-remote-pranswer":e.type="offer";break;default:e.type="answer"}return e.sdp||"offer"!==e.type&&"answer"!==e.type?n.apply(this,[e]):("offer"===e.type?this.createOffer:this.createAnswer).apply(this).then(e=>n.apply(this,[e]))})}ey.generateIdentifier=function(){return Math.random().toString(36).substring(2,12)},ey.localCName=ey.generateIdentifier(),ey.splitLines=function(e){return e.trim().split("\n").map(e=>e.trim())},ey.splitSections=function(e){return e.split("\nm=").map((e,t)=>(t>0?"m="+e:e).trim()+"\r\n")},ey.getDescription=function(e){let t=ey.splitSections(e);return t&&t[0]},ey.getMediaSections=function(e){let t=ey.splitSections(e);return t.shift(),t},ey.matchPrefix=function(e,t){return ey.splitLines(e).filter(e=>0===e.indexOf(t))},ey.parseCandidate=function(e){let t;let n={foundation:(t=0===e.indexOf("a=candidate:")?e.substring(12).split(" "):e.substring(10).split(" "))[0],component:{1:"rtp",2:"rtcp"}[t[1]]||t[1],protocol:t[2].toLowerCase(),priority:parseInt(t[3],10),ip:t[4],address:t[4],port:parseInt(t[5],10),type:t[7]};for(let e=8;e<t.length;e+=2)switch(t[e]){case"raddr":n.relatedAddress=t[e+1];break;case"rport":n.relatedPort=parseInt(t[e+1],10);break;case"tcptype":n.tcpType=t[e+1];break;case"ufrag":n.ufrag=t[e+1],n.usernameFragment=t[e+1];break;default:void 0===n[t[e]]&&(n[t[e]]=t[e+1])}return n},ey.writeCandidate=function(e){let t=[];t.push(e.foundation);let n=e.component;"rtp"===n?t.push(1):"rtcp"===n?t.push(2):t.push(n),t.push(e.protocol.toUpperCase()),t.push(e.priority),t.push(e.address||e.ip),t.push(e.port);let r=e.type;return t.push("typ"),t.push(r),"host"!==r&&e.relatedAddress&&e.relatedPort&&(t.push("raddr"),t.push(e.relatedAddress),t.push("rport"),t.push(e.relatedPort)),e.tcpType&&"tcp"===e.protocol.toLowerCase()&&(t.push("tcptype"),t.push(e.tcpType)),(e.usernameFragment||e.ufrag)&&(t.push("ufrag"),t.push(e.usernameFragment||e.ufrag)),"candidate:"+t.join(" ")},ey.parseIceOptions=function(e){return e.substring(14).split(" ")},ey.parseRtpMap=function(e){let t=e.substring(9).split(" "),n={payloadType:parseInt(t.shift(),10)};return t=t[0].split("/"),n.name=t[0],n.clockRate=parseInt(t[1],10),n.channels=3===t.length?parseInt(t[2],10):1,n.numChannels=n.channels,n},ey.writeRtpMap=function(e){let t=e.payloadType;void 0!==e.preferredPayloadType&&(t=e.preferredPayloadType);let n=e.channels||e.numChannels||1;return"a=rtpmap:"+t+" "+e.name+"/"+e.clockRate+(1!==n?"/"+n:"")+"\r\n"},ey.parseExtmap=function(e){let t=e.substring(9).split(" ");return{id:parseInt(t[0],10),direction:t[0].indexOf("/")>0?t[0].split("/")[1]:"sendrecv",uri:t[1],attributes:t.slice(2).join(" ")}},ey.writeExtmap=function(e){return"a=extmap:"+(e.id||e.preferredId)+(e.direction&&"sendrecv"!==e.direction?"/"+e.direction:"")+" "+e.uri+(e.attributes?" "+e.attributes:"")+"\r\n"},ey.parseFmtp=function(e){let t;let n={},r=e.substring(e.indexOf(" ")+1).split(";");for(let e=0;e<r.length;e++)n[(t=r[e].trim().split("="))[0].trim()]=t[1];return n},ey.writeFmtp=function(e){let t="",n=e.payloadType;if(void 0!==e.preferredPayloadType&&(n=e.preferredPayloadType),e.parameters&&Object.keys(e.parameters).length){let r=[];Object.keys(e.parameters).forEach(t=>{void 0!==e.parameters[t]?r.push(t+"="+e.parameters[t]):r.push(t)}),t+="a=fmtp:"+n+" "+r.join(";")+"\r\n"}return t},ey.parseRtcpFb=function(e){let t=e.substring(e.indexOf(" ")+1).split(" ");return{type:t.shift(),parameter:t.join(" ")}},ey.writeRtcpFb=function(e){let t="",n=e.payloadType;return void 0!==e.preferredPayloadType&&(n=e.preferredPayloadType),e.rtcpFeedback&&e.rtcpFeedback.length&&e.rtcpFeedback.forEach(e=>{t+="a=rtcp-fb:"+n+" "+e.type+(e.parameter&&e.parameter.length?" "+e.parameter:"")+"\r\n"}),t},ey.parseSsrcMedia=function(e){let t=e.indexOf(" "),n={ssrc:parseInt(e.substring(7,t),10)},r=e.indexOf(":",t);return r>-1?(n.attribute=e.substring(t+1,r),n.value=e.substring(r+1)):n.attribute=e.substring(t+1),n},ey.parseSsrcGroup=function(e){let t=e.substring(13).split(" ");return{semantics:t.shift(),ssrcs:t.map(e=>parseInt(e,10))}},ey.getMid=function(e){let t=ey.matchPrefix(e,"a=mid:")[0];if(t)return t.substring(6)},ey.parseFingerprint=function(e){let t=e.substring(14).split(" ");return{algorithm:t[0].toLowerCase(),value:t[1].toUpperCase()}},ey.getDtlsParameters=function(e,t){return{role:"auto",fingerprints:ey.matchPrefix(e+t,"a=fingerprint:").map(ey.parseFingerprint)}},ey.writeDtlsParameters=function(e,t){let n="a=setup:"+t+"\r\n";return e.fingerprints.forEach(e=>{n+="a=fingerprint:"+e.algorithm+" "+e.value+"\r\n"}),n},ey.parseCryptoLine=function(e){let t=e.substring(9).split(" ");return{tag:parseInt(t[0],10),cryptoSuite:t[1],keyParams:t[2],sessionParams:t.slice(3)}},ey.writeCryptoLine=function(e){return"a=crypto:"+e.tag+" "+e.cryptoSuite+" "+("object"==typeof e.keyParams?ey.writeCryptoKeyParams(e.keyParams):e.keyParams)+(e.sessionParams?" "+e.sessionParams.join(" "):"")+"\r\n"},ey.parseCryptoKeyParams=function(e){if(0!==e.indexOf("inline:"))return null;let t=e.substring(7).split("|");return{keyMethod:"inline",keySalt:t[0],lifeTime:t[1],mkiValue:t[2]?t[2].split(":")[0]:void 0,mkiLength:t[2]?t[2].split(":")[1]:void 0}},ey.writeCryptoKeyParams=function(e){return e.keyMethod+":"+e.keySalt+(e.lifeTime?"|"+e.lifeTime:"")+(e.mkiValue&&e.mkiLength?"|"+e.mkiValue+":"+e.mkiLength:"")},ey.getCryptoParameters=function(e,t){return ey.matchPrefix(e+t,"a=crypto:").map(ey.parseCryptoLine)},ey.getIceParameters=function(e,t){let n=ey.matchPrefix(e+t,"a=ice-ufrag:")[0],r=ey.matchPrefix(e+t,"a=ice-pwd:")[0];return n&&r?{usernameFragment:n.substring(12),password:r.substring(10)}:null},ey.writeIceParameters=function(e){let t="a=ice-ufrag:"+e.usernameFragment+"\r\na=ice-pwd:"+e.password+"\r\n";return e.iceLite&&(t+="a=ice-lite\r\n"),t},ey.parseRtpParameters=function(e){let t={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]},n=ey.splitLines(e)[0].split(" ");t.profile=n[2];for(let r=3;r<n.length;r++){let i=n[r],o=ey.matchPrefix(e,"a=rtpmap:"+i+" ")[0];if(o){let n=ey.parseRtpMap(o),r=ey.matchPrefix(e,"a=fmtp:"+i+" ");switch(n.parameters=r.length?ey.parseFmtp(r[0]):{},n.rtcpFeedback=ey.matchPrefix(e,"a=rtcp-fb:"+i+" ").map(ey.parseRtcpFb),t.codecs.push(n),n.name.toUpperCase()){case"RED":case"ULPFEC":t.fecMechanisms.push(n.name.toUpperCase())}}}ey.matchPrefix(e,"a=extmap:").forEach(e=>{t.headerExtensions.push(ey.parseExtmap(e))});let r=ey.matchPrefix(e,"a=rtcp-fb:* ").map(ey.parseRtcpFb);return t.codecs.forEach(e=>{r.forEach(t=>{e.rtcpFeedback.find(e=>e.type===t.type&&e.parameter===t.parameter)||e.rtcpFeedback.push(t)})}),t},ey.writeRtpDescription=function(e,t){let n="";n+="m="+e+" "+(t.codecs.length>0?"9":"0")+" "+(t.profile||"UDP/TLS/RTP/SAVPF")+" "+t.codecs.map(e=>void 0!==e.preferredPayloadType?e.preferredPayloadType:e.payloadType).join(" ")+"\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\n",t.codecs.forEach(e=>{n+=ey.writeRtpMap(e)+ey.writeFmtp(e)+ey.writeRtcpFb(e)});let r=0;return t.codecs.forEach(e=>{e.maxptime>r&&(r=e.maxptime)}),r>0&&(n+="a=maxptime:"+r+"\r\n"),t.headerExtensions&&t.headerExtensions.forEach(e=>{n+=ey.writeExtmap(e)}),n},ey.parseRtpEncodingParameters=function(e){let t;let n=[],r=ey.parseRtpParameters(e),i=-1!==r.fecMechanisms.indexOf("RED"),o=-1!==r.fecMechanisms.indexOf("ULPFEC"),s=ey.matchPrefix(e,"a=ssrc:").map(e=>ey.parseSsrcMedia(e)).filter(e=>"cname"===e.attribute),a=s.length>0&&s[0].ssrc,c=ey.matchPrefix(e,"a=ssrc-group:FID").map(e=>e.substring(17).split(" ").map(e=>parseInt(e,10)));c.length>0&&c[0].length>1&&c[0][0]===a&&(t=c[0][1]),r.codecs.forEach(e=>{if("RTX"===e.name.toUpperCase()&&e.parameters.apt){let r={ssrc:a,codecPayloadType:parseInt(e.parameters.apt,10)};a&&t&&(r.rtx={ssrc:t}),n.push(r),i&&((r=JSON.parse(JSON.stringify(r))).fec={ssrc:a,mechanism:o?"red+ulpfec":"red"},n.push(r))}}),0===n.length&&a&&n.push({ssrc:a});let l=ey.matchPrefix(e,"b=");return l.length&&(l=0===l[0].indexOf("b=TIAS:")?parseInt(l[0].substring(7),10):0===l[0].indexOf("b=AS:")?950*parseInt(l[0].substring(5),10)-16e3:void 0,n.forEach(e=>{e.maxBitrate=l})),n},ey.parseRtcpParameters=function(e){let t={},n=ey.matchPrefix(e,"a=ssrc:").map(e=>ey.parseSsrcMedia(e)).filter(e=>"cname"===e.attribute)[0];n&&(t.cname=n.value,t.ssrc=n.ssrc);let r=ey.matchPrefix(e,"a=rtcp-rsize");t.reducedSize=r.length>0,t.compound=0===r.length;let i=ey.matchPrefix(e,"a=rtcp-mux");return t.mux=i.length>0,t},ey.writeRtcpParameters=function(e){let t="";return e.reducedSize&&(t+="a=rtcp-rsize\r\n"),e.mux&&(t+="a=rtcp-mux\r\n"),void 0!==e.ssrc&&e.cname&&(t+="a=ssrc:"+e.ssrc+" cname:"+e.cname+"\r\n"),t},ey.parseMsid=function(e){let t;let n=ey.matchPrefix(e,"a=msid:");if(1===n.length)return{stream:(t=n[0].substring(7).split(" "))[0],track:t[1]};let r=ey.matchPrefix(e,"a=ssrc:").map(e=>ey.parseSsrcMedia(e)).filter(e=>"msid"===e.attribute);if(r.length>0)return{stream:(t=r[0].value.split(" "))[0],track:t[1]}},ey.parseSctpDescription=function(e){let t;let n=ey.parseMLine(e),r=ey.matchPrefix(e,"a=max-message-size:");r.length>0&&(t=parseInt(r[0].substring(19),10)),isNaN(t)&&(t=65536);let i=ey.matchPrefix(e,"a=sctp-port:");if(i.length>0)return{port:parseInt(i[0].substring(12),10),protocol:n.fmt,maxMessageSize:t};let o=ey.matchPrefix(e,"a=sctpmap:");if(o.length>0){let e=o[0].substring(10).split(" ");return{port:parseInt(e[0],10),protocol:e[1],maxMessageSize:t}}},ey.writeSctpDescription=function(e,t){let n=[];return n="DTLS/SCTP"!==e.protocol?["m="+e.kind+" 9 "+e.protocol+" "+t.protocol+"\r\n","c=IN IP4 0.0.0.0\r\n","a=sctp-port:"+t.port+"\r\n"]:["m="+e.kind+" 9 "+e.protocol+" "+t.port+"\r\n","c=IN IP4 0.0.0.0\r\n","a=sctpmap:"+t.port+" "+t.protocol+" 65535\r\n"],void 0!==t.maxMessageSize&&n.push("a=max-message-size:"+t.maxMessageSize+"\r\n"),n.join("")},ey.generateSessionId=function(){return Math.random().toString().substr(2,22)},ey.writeSessionBoilerplate=function(e,t,n){return"v=0\r\no="+(n||"thisisadapterortc")+" "+(e||ey.generateSessionId())+" "+(void 0!==t?t:2)+" IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"},ey.getDirection=function(e,t){let n=ey.splitLines(e);for(let e=0;e<n.length;e++)switch(n[e]){case"a=sendrecv":case"a=sendonly":case"a=recvonly":case"a=inactive":return n[e].substring(2)}return t?ey.getDirection(t):"sendrecv"},ey.getKind=function(e){return ey.splitLines(e)[0].split(" ")[0].substring(2)},ey.isRejected=function(e){return"0"===e.split(" ",2)[1]},ey.parseMLine=function(e){let t=ey.splitLines(e)[0].substring(2).split(" ");return{kind:t[0],port:parseInt(t[1],10),protocol:t[2],fmt:t.slice(3).join(" ")}},ey.parseOLine=function(e){let t=ey.matchPrefix(e,"o=")[0].substring(2).split(" ");return{username:t[0],sessionId:t[1],sessionVersion:parseInt(t[2],10),netType:t[3],addressType:t[4],address:t[5]}},ey.isValidSDP=function(e){if("string"!=typeof e||0===e.length)return!1;let t=ey.splitLines(e);for(let e=0;e<t.length;e++)if(t[e].length<2||"="!==t[e].charAt(1))return!1;return!0},eg=ey;let ew=function({window:e}={},t={shimChrome:!0,shimFirefox:!0,shimSafari:!0}){let n=function(e){let t={browser:null,version:null};if(void 0===e||!e.navigator||!e.navigator.userAgent)return t.browser="Not a browser.",t;let{navigator:n}=e;return n.mozGetUserMedia?(t.browser="firefox",t.version=p(n.userAgent,/Firefox\/(\d+)\./,1)):n.webkitGetUserMedia||!1===e.isSecureContext&&e.webkitRTCPeerConnection?(t.browser="chrome",t.version=p(n.userAgent,/Chrom(e|ium)\/(\d+)\./,2)):e.RTCPeerConnection&&n.userAgent.match(/AppleWebKit\/(\d+)\./)?(t.browser="safari",t.version=p(n.userAgent,/AppleWebKit\/(\d+)\./,1),t.supportsUnifiedPlan=e.RTCRtpTransceiver&&"currentDirection"in e.RTCRtpTransceiver.prototype):t.browser="Not a supported browser.",t}(e),r={browserDetails:n,commonShim:em,extractVersion:p,disableLog:h,disableWarnings:u,sdp:eg};switch(n.browser){case"chrome":if(!j||!j.shimPeerConnection||!t.shimChrome){f("Chrome shim is not included in this adapter release.");break}if(null===n.version){f("Chrome shim can not determine version, not shimming.");break}f("adapter.js shimming chrome."),r.browserShim=j,eT(e,n),eR(e,n),j.shimGetUserMedia(e,n),j.shimMediaStream(e,n),j.shimPeerConnection(e,n),j.shimOnTrack(e,n),j.shimAddTrackRemoveTrack(e,n),j.shimGetSendersWithDtmf(e,n),j.shimGetStats(e,n),j.shimSenderReceiverGetStats(e,n),j.fixNegotiationNeeded(e,n),e_(e,n),eC(e,n),ek(e,n),ev(e,n),eb(e,n),eS(e,n);break;case"firefox":if(!W||!W.shimPeerConnection||!t.shimFirefox){f("Firefox shim is not included in this adapter release.");break}f("adapter.js shimming firefox."),r.browserShim=W,eT(e,n),eR(e,n),W.shimGetUserMedia(e,n),W.shimPeerConnection(e,n),W.shimOnTrack(e,n),W.shimRemoveStream(e,n),W.shimSenderGetStats(e,n),W.shimReceiverGetStats(e,n),W.shimRTCDataChannel(e,n),W.shimAddTransceiver(e,n),W.shimGetParameters(e,n),W.shimCreateOffer(e,n),W.shimCreateAnswer(e,n),e_(e,n),ek(e,n),ev(e,n),eb(e,n);break;case"safari":if(!eo||!t.shimSafari){f("Safari shim is not included in this adapter release.");break}f("adapter.js shimming safari."),r.browserShim=eo,eT(e,n),eR(e,n),eo.shimRTCIceServerUrls(e,n),eo.shimCreateOfferLegacy(e,n),eo.shimCallbacksAPI(e,n),eo.shimLocalStreamsAPI(e,n),eo.shimRemoteStreamsAPI(e,n),eo.shimTrackEventTransceiver(e,n),eo.shimGetUserMedia(e,n),eo.shimAudioContext(e,n),e_(e,n),eC(e,n),ev(e,n),eb(e,n),eS(e,n);break;default:f("Unsupported browser!")}return r}({window:"undefined"==typeof window?void 0:window}),eP=ew.default||ew,eE=new class{isWebRTCSupported(){return"undefined"!=typeof RTCPeerConnection}isBrowserSupported(){let e=this.getBrowser(),t=this.getVersion();return!!this.supportedBrowsers.includes(e)&&("chrome"===e?t>=this.minChromeVersion:"firefox"===e?t>=this.minFirefoxVersion:"safari"===e&&!this.isIOS&&t>=this.minSafariVersion)}getBrowser(){return eP.browserDetails.browser}getVersion(){return eP.browserDetails.version||0}isUnifiedPlanSupported(){let e;let t=this.getBrowser(),n=eP.browserDetails.version||0;if("chrome"===t&&n<this.minChromeVersion)return!1;if("firefox"===t&&n>=this.minFirefoxVersion)return!0;if(!window.RTCRtpTransceiver||!("currentDirection"in RTCRtpTransceiver.prototype))return!1;let r=!1;try{(e=new RTCPeerConnection).addTransceiver("audio"),r=!0}catch(e){}finally{e&&e.close()}return r}toString(){return`Supports:
+ browser:${this.getBrowser()}
+ version:${this.getVersion()}
+ isIOS:${this.isIOS}
+ isWebRTCSupported:${this.isWebRTCSupported()}
+ isBrowserSupported:${this.isBrowserSupported()}
+ isUnifiedPlanSupported:${this.isUnifiedPlanSupported()}`}constructor(){this.isIOS=["iPad","iPhone","iPod"].includes(navigator.platform),this.supportedBrowsers=["firefox","chrome","safari"],this.minFirefoxVersion=59,this.minChromeVersion=72,this.minSafariVersion=605}},eD=e=>!e||/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.test(e),ex=()=>Math.random().toString(36).slice(2),eI={iceServers:[{urls:"stun:stun.l.google.com:19302"},{urls:["turn:eu-0.turn.peerjs.com:3478","turn:us-0.turn.peerjs.com:3478"],username:"peerjs",credential:"peerjsp"}],sdpSemantics:"unified-plan"},eM=new class extends n{noop(){}blobToArrayBuffer(e,t){let n=new FileReader;return n.onload=function(e){e.target&&t(e.target.result)},n.readAsArrayBuffer(e),n}binaryStringToArrayBuffer(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=255&e.charCodeAt(n);return t.buffer}isSecure(){return"https:"===location.protocol}constructor(...e){super(...e),this.CLOUD_HOST="0.peerjs.com",this.CLOUD_PORT=443,this.chunkedBrowsers={Chrome:1,chrome:1},this.defaultConfig=eI,this.browser=eE.getBrowser(),this.browserVersion=eE.getVersion(),this.pack=o,this.unpack=i,this.supports=function(){let e;let t={browser:eE.isBrowserSupported(),webRTC:eE.isWebRTCSupported(),audioVideo:!1,data:!1,binaryBlob:!1,reliable:!1};if(!t.webRTC)return t;try{let n;e=new RTCPeerConnection(eI),t.audioVideo=!0;try{n=e.createDataChannel("_PEERJSTEST",{ordered:!0}),t.data=!0,t.reliable=!!n.ordered;try{n.binaryType="blob",t.binaryBlob=!eE.isIOS}catch(e){}}catch(e){}finally{n&&n.close()}}catch(e){}finally{e&&e.close()}return t}(),this.validateId=eD,this.randomToken=ex}};(_=w||(w={}))[_.Disabled=0]="Disabled",_[_.Errors=1]="Errors",_[_.Warnings=2]="Warnings",_[_.All=3]="All";var eO=new class{get logLevel(){return this._logLevel}set logLevel(e){this._logLevel=e}log(...e){this._logLevel>=3&&this._print(3,...e)}warn(...e){this._logLevel>=2&&this._print(2,...e)}error(...e){this._logLevel>=1&&this._print(1,...e)}setLogFunction(e){this._print=e}_print(e,...t){let n=["PeerJS: ",...t];for(let e in n)n[e]instanceof Error&&(n[e]="("+n[e].name+") "+n[e].message);e>=3?console.log(...n):e>=2?console.warn("WARNING",...n):e>=1&&console.error("ERROR",...n)}constructor(){this._logLevel=0}},ej={},eL=Object.prototype.hasOwnProperty,eA="~";function eB(){}function eF(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function eU(e,t,n,r,i){if("function"!=typeof n)throw TypeError("The listener must be a function");var o=new eF(n,r||e,i),s=eA?eA+t:t;return e._events[s]?e._events[s].fn?e._events[s]=[e._events[s],o]:e._events[s].push(o):(e._events[s]=o,e._eventsCount++),e}function ez(e,t){0==--e._eventsCount?e._events=new eB:delete e._events[t]}function eN(){this._events=new eB,this._eventsCount=0}Object.create&&(eB.prototype=Object.create(null),new eB().__proto__||(eA=!1)),eN.prototype.eventNames=function(){var e,t,n=[];if(0===this._eventsCount)return n;for(t in e=this._events)eL.call(e,t)&&n.push(eA?t.slice(1):t);return Object.getOwnPropertySymbols?n.concat(Object.getOwnPropertySymbols(e)):n},eN.prototype.listeners=function(e){var t=eA?eA+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var r=0,i=n.length,o=Array(i);r<i;r++)o[r]=n[r].fn;return o},eN.prototype.listenerCount=function(e){var t=eA?eA+e:e,n=this._events[t];return n?n.fn?1:n.length:0},eN.prototype.emit=function(e,t,n,r,i,o){var s=eA?eA+e:e;if(!this._events[s])return!1;var a,c,l=this._events[s],p=arguments.length;if(l.fn){switch(l.once&&this.removeListener(e,l.fn,void 0,!0),p){case 1:return l.fn.call(l.context),!0;case 2:return l.fn.call(l.context,t),!0;case 3:return l.fn.call(l.context,t,n),!0;case 4:return l.fn.call(l.context,t,n,r),!0;case 5:return l.fn.call(l.context,t,n,r,i),!0;case 6:return l.fn.call(l.context,t,n,r,i,o),!0}for(c=1,a=Array(p-1);c<p;c++)a[c-1]=arguments[c];l.fn.apply(l.context,a)}else{var d,h=l.length;for(c=0;c<h;c++)switch(l[c].once&&this.removeListener(e,l[c].fn,void 0,!0),p){case 1:l[c].fn.call(l[c].context);break;case 2:l[c].fn.call(l[c].context,t);break;case 3:l[c].fn.call(l[c].context,t,n);break;case 4:l[c].fn.call(l[c].context,t,n,r);break;default:if(!a)for(d=1,a=Array(p-1);d<p;d++)a[d-1]=arguments[d];l[c].fn.apply(l[c].context,a)}}return!0},eN.prototype.on=function(e,t,n){return eU(this,e,t,n,!1)},eN.prototype.once=function(e,t,n){return eU(this,e,t,n,!0)},eN.prototype.removeListener=function(e,t,n,r){var i=eA?eA+e:e;if(!this._events[i])return this;if(!t)return ez(this,i),this;var o=this._events[i];if(o.fn)o.fn!==t||r&&!o.once||n&&o.context!==n||ez(this,i);else{for(var s=0,a=[],c=o.length;s<c;s++)(o[s].fn!==t||r&&!o[s].once||n&&o[s].context!==n)&&a.push(o[s]);a.length?this._events[i]=1===a.length?a[0]:a:ez(this,i)}return this},eN.prototype.removeAllListeners=function(e){var t;return e?(t=eA?eA+e:e,this._events[t]&&ez(this,t)):(this._events=new eB,this._eventsCount=0),this},eN.prototype.off=eN.prototype.removeListener,eN.prototype.addListener=eN.prototype.on,eN.prefixed=eA,eN.EventEmitter=eN,ej=eN,(C=P||(P={})).Data="data",C.Media="media",(v=E||(E={})).BrowserIncompatible="browser-incompatible",v.Disconnected="disconnected",v.InvalidID="invalid-id",v.InvalidKey="invalid-key",v.Network="network",v.PeerUnavailable="peer-unavailable",v.SslUnavailable="ssl-unavailable",v.ServerError="server-error",v.SocketError="socket-error",v.SocketClosed="socket-closed",v.UnavailableID="unavailable-id",v.WebRTC="webrtc",(b=D||(D={})).NegotiationFailed="negotiation-failed",b.ConnectionClosed="connection-closed",(k=x||(x={})).NotOpenYet="not-open-yet",k.MessageToBig="message-too-big",(S=I||(I={})).Binary="binary",S.BinaryUTF8="binary-utf8",S.JSON="json",S.None="raw",(T=M||(M={})).Message="message",T.Disconnected="disconnected",T.Error="error",T.Close="close",(R=O||(O={})).Heartbeat="HEARTBEAT",R.Candidate="CANDIDATE",R.Offer="OFFER",R.Answer="ANSWER",R.Open="OPEN",R.Error="ERROR",R.IdTaken="ID-TAKEN",R.InvalidKey="INVALID-KEY",R.Leave="LEAVE",R.Expire="EXPIRE";var e$={};e$=JSON.parse('{"name":"peerjs","version":"1.5.2","keywords":["peerjs","webrtc","p2p","rtc"],"description":"PeerJS client","homepage":"https://peerjs.com","bugs":{"url":"https://github.com/peers/peerjs/issues"},"repository":{"type":"git","url":"https://github.com/peers/peerjs"},"license":"MIT","contributors":["Michelle Bu <michelle@michellebu.com>","afrokick <devbyru@gmail.com>","ericz <really.ez@gmail.com>","Jairo <kidandcat@gmail.com>","Jonas Gloning <34194370+jonasgloning@users.noreply.github.com>","Jairo Caro-Accino Viciana <jairo@galax.be>","Carlos Caballero <carlos.caballero.gonzalez@gmail.com>","hc <hheennrryy@gmail.com>","Muhammad Asif <capripio@gmail.com>","PrashoonB <prashoonbhattacharjee@gmail.com>","Harsh Bardhan Mishra <47351025+HarshCasper@users.noreply.github.com>","akotynski <aleksanderkotbury@gmail.com>","lmb <i@lmb.io>","Jairooo <jairocaro@msn.com>","Moritz Stückler <moritz.stueckler@gmail.com>","Simon <crydotsnakegithub@gmail.com>","Denis Lukov <denismassters@gmail.com>","Philipp Hancke <fippo@andyet.net>","Hans Oksendahl <hansoksendahl@gmail.com>","Jess <jessachandler@gmail.com>","khankuan <khankuan@gmail.com>","DUODVK <kurmanov.work@gmail.com>","XiZhao <kwang1imsa@gmail.com>","Matthias Lohr <matthias@lohr.me>","=frank tree <=frnktrb@googlemail.com>","Andre Eckardt <aeckardt@outlook.com>","Chris Cowan <agentme49@gmail.com>","Alex Chuev <alex@chuev.com>","alxnull <alxnull@e.mail.de>","Yemel Jardi <angel.jardi@gmail.com>","Ben Parnell <benjaminparnell.94@gmail.com>","Benny Lichtner <bennlich@gmail.com>","fresheneesz <bitetrudpublic@gmail.com>","bob.barstead@exaptive.com <bob.barstead@exaptive.com>","chandika <chandika@gmail.com>","emersion <contact@emersion.fr>","Christopher Van <cvan@users.noreply.github.com>","eddieherm <edhermoso@gmail.com>","Eduardo Pinho <enet4mikeenet@gmail.com>","Evandro Zanatta <ezanatta@tray.net.br>","Gardner Bickford <gardner@users.noreply.github.com>","Gian Luca <gianluca.cecchi@cynny.com>","PatrickJS <github@gdi2290.com>","jonnyf <github@jonathanfoss.co.uk>","Hizkia Felix <hizkifw@gmail.com>","Hristo Oskov <hristo.oskov@gmail.com>","Isaac Madwed <i.madwed@gmail.com>","Ilya Konanykhin <ilya.konanykhin@gmail.com>","jasonbarry <jasbarry@me.com>","Jonathan Burke <jonathan.burke.1311@googlemail.com>","Josh Hamit <josh.hamit@gmail.com>","Jordan Austin <jrax86@gmail.com>","Joel Wetzell <jwetzell@yahoo.com>","xizhao <kevin.wang@cloudera.com>","Alberto Torres <kungfoobar@gmail.com>","Jonathan Mayol <mayoljonathan@gmail.com>","Jefferson Felix <me@jsfelix.dev>","Rolf Erik Lekang <me@rolflekang.com>","Kevin Mai-Husan Chia <mhchia@users.noreply.github.com>","Pepijn de Vos <pepijndevos@gmail.com>","JooYoung <qkdlql@naver.com>","Tobias Speicher <rootcommander@gmail.com>","Steve Blaurock <sblaurock@gmail.com>","Kyrylo Shegeda <shegeda@ualberta.ca>","Diwank Singh Tomer <singh@diwank.name>","Sören Balko <Soeren.Balko@gmail.com>","Arpit Solanki <solankiarpit1997@gmail.com>","Yuki Ito <yuki@gnnk.net>","Artur Zayats <zag2art@gmail.com>"],"funding":{"type":"opencollective","url":"https://opencollective.com/peer"},"collective":{"type":"opencollective","url":"https://opencollective.com/peer"},"files":["dist/*"],"sideEffects":["lib/global.ts","lib/supports.ts"],"main":"dist/bundler.cjs","module":"dist/bundler.mjs","browser-minified":"dist/peerjs.min.js","browser-unminified":"dist/peerjs.js","browser-minified-cbor":"dist/serializer.cbor.mjs","browser-minified-msgpack":"dist/serializer.msgpack.mjs","types":"dist/types.d.ts","engines":{"node":">= 14"},"targets":{"types":{"source":"lib/exports.ts"},"main":{"source":"lib/exports.ts","sourceMap":{"inlineSources":true}},"module":{"source":"lib/exports.ts","includeNodeModules":["eventemitter3"],"sourceMap":{"inlineSources":true}},"browser-minified":{"context":"browser","outputFormat":"global","optimize":true,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 80, safari >= 15"},"source":"lib/global.ts"},"browser-unminified":{"context":"browser","outputFormat":"global","optimize":false,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 80, safari >= 15"},"source":"lib/global.ts"},"browser-minified-cbor":{"context":"browser","outputFormat":"esmodule","isLibrary":true,"optimize":true,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 102, safari >= 15"},"source":"lib/dataconnection/StreamConnection/Cbor.ts"},"browser-minified-msgpack":{"context":"browser","outputFormat":"esmodule","isLibrary":true,"optimize":true,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 102, safari >= 15"},"source":"lib/dataconnection/StreamConnection/MsgPack.ts"}},"scripts":{"contributors":"git-authors-cli --print=false && prettier --write package.json && git add package.json package-lock.json && git commit -m \\"chore(contributors): update and sort contributors list\\"","check":"tsc --noEmit && tsc -p e2e/tsconfig.json --noEmit","watch":"parcel watch","build":"rm -rf dist && parcel build","prepublishOnly":"npm run build","test":"jest","test:watch":"jest --watch","coverage":"jest --coverage --collectCoverageFrom=\\"./lib/**\\"","format":"prettier --write .","format:check":"prettier --check .","semantic-release":"semantic-release","e2e":"wdio run e2e/wdio.local.conf.ts","e2e:bstack":"wdio run e2e/wdio.bstack.conf.ts"},"devDependencies":{"@parcel/config-default":"^2.9.3","@parcel/packager-ts":"^2.9.3","@parcel/transformer-typescript-tsc":"^2.9.3","@parcel/transformer-typescript-types":"^2.9.3","@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@swc/core":"^1.3.27","@swc/jest":"^0.2.24","@types/jasmine":"^4.3.4","@wdio/browserstack-service":"^8.11.2","@wdio/cli":"^8.11.2","@wdio/globals":"^8.11.2","@wdio/jasmine-framework":"^8.11.2","@wdio/local-runner":"^8.11.2","@wdio/spec-reporter":"^8.11.2","@wdio/types":"^8.10.4","http-server":"^14.1.1","jest":"^29.3.1","jest-environment-jsdom":"^29.3.1","mock-socket":"^9.0.0","parcel":"^2.9.3","prettier":"^3.0.0","semantic-release":"^21.0.0","ts-node":"^10.9.1","typescript":"^5.0.0","wdio-geckodriver-service":"^5.0.1"},"dependencies":{"@msgpack/msgpack":"^2.8.0","cbor-x":"1.5.4","eventemitter3":"^4.0.7","peerjs-js-binarypack":"^2.1.0","webrtc-adapter":"^8.0.0"},"alias":{"process":false,"buffer":false}}');class eJ extends ej.EventEmitter{start(e,t){this._id=e;let n=`${this._baseUrl}&id=${e}&token=${t}`;!this._socket&&this._disconnected&&(this._socket=new WebSocket(n+"&version="+e$.version),this._disconnected=!1,this._socket.onmessage=e=>{let t;try{t=JSON.parse(e.data),eO.log("Server message received:",t)}catch(t){eO.log("Invalid server message",e.data);return}this.emit(M.Message,t)},this._socket.onclose=e=>{this._disconnected||(eO.log("Socket closed.",e),this._cleanup(),this._disconnected=!0,this.emit(M.Disconnected))},this._socket.onopen=()=>{this._disconnected||(this._sendQueuedMessages(),eO.log("Socket open"),this._scheduleHeartbeat())})}_scheduleHeartbeat(){this._wsPingTimer=setTimeout(()=>{this._sendHeartbeat()},this.pingInterval)}_sendHeartbeat(){if(!this._wsOpen()){eO.log("Cannot send heartbeat, because socket closed");return}let e=JSON.stringify({type:O.Heartbeat});this._socket.send(e),this._scheduleHeartbeat()}_wsOpen(){return!!this._socket&&1===this._socket.readyState}_sendQueuedMessages(){let e=[...this._messagesQueue];for(let t of(this._messagesQueue=[],e))this.send(t)}send(e){if(this._disconnected)return;if(!this._id){this._messagesQueue.push(e);return}if(!e.type){this.emit(M.Error,"Invalid message");return}if(!this._wsOpen())return;let t=JSON.stringify(e);this._socket.send(t)}close(){this._disconnected||(this._cleanup(),this._disconnected=!0)}_cleanup(){this._socket&&(this._socket.onopen=this._socket.onmessage=this._socket.onclose=null,this._socket.close(),this._socket=void 0),clearTimeout(this._wsPingTimer)}constructor(e,t,n,r,i,o=5e3){super(),this.pingInterval=o,this._disconnected=!0,this._messagesQueue=[],this._baseUrl=(e?"wss://":"ws://")+t+":"+n+r+"peerjs?key="+i}}class eV{startConnection(e){let t=this._startPeerConnection();if(this.connection.peerConnection=t,this.connection.type===P.Media&&e._stream&&this._addTracksToConnection(e._stream,t),e.originator){let n=this.connection,r={ordered:!!e.reliable},i=t.createDataChannel(n.label,r);n._initializeDataChannel(i),this._makeOffer()}else this.handleSDP("OFFER",e.sdp)}_startPeerConnection(){eO.log("Creating RTCPeerConnection.");let e=new RTCPeerConnection(this.connection.provider.options.config);return this._setupListeners(e),e}_setupListeners(e){let t=this.connection.peer,n=this.connection.connectionId,r=this.connection.type,i=this.connection.provider;eO.log("Listening for ICE candidates."),e.onicecandidate=e=>{e.candidate&&e.candidate.candidate&&(eO.log(`Received ICE candidates for ${t}:`,e.candidate),i.socket.send({type:O.Candidate,payload:{candidate:e.candidate,type:r,connectionId:n},dst:t}))},e.oniceconnectionstatechange=()=>{switch(e.iceConnectionState){case"failed":eO.log("iceConnectionState is failed, closing connections to "+t),this.connection.emitError(D.NegotiationFailed,"Negotiation of connection to "+t+" failed."),this.connection.close();break;case"closed":eO.log("iceConnectionState is closed, closing connections to "+t),this.connection.emitError(D.ConnectionClosed,"Connection to "+t+" closed."),this.connection.close();break;case"disconnected":eO.log("iceConnectionState changed to disconnected on the connection with "+t);break;case"completed":e.onicecandidate=()=>{}}this.connection.emit("iceStateChanged",e.iceConnectionState)},eO.log("Listening for data channel"),e.ondatachannel=e=>{eO.log("Received data channel");let r=e.channel;i.getConnection(t,n)._initializeDataChannel(r)},eO.log("Listening for remote stream"),e.ontrack=e=>{eO.log("Received remote stream");let r=e.streams[0],o=i.getConnection(t,n);o.type===P.Media&&this._addStreamToMediaConnection(r,o)}}cleanup(){eO.log("Cleaning up PeerConnection to "+this.connection.peer);let e=this.connection.peerConnection;if(!e)return;this.connection.peerConnection=null,e.onicecandidate=e.oniceconnectionstatechange=e.ondatachannel=e.ontrack=()=>{};let t="closed"!==e.signalingState,n=!1,r=this.connection.dataChannel;r&&(n=!!r.readyState&&"closed"!==r.readyState),(t||n)&&e.close()}async _makeOffer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createOffer(this.connection.options.constraints);eO.log("Created offer."),this.connection.options.sdpTransform&&"function"==typeof this.connection.options.sdpTransform&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp);try{await e.setLocalDescription(n),eO.log("Set localDescription:",n,`for:${this.connection.peer}`);let r={sdp:n,type:this.connection.type,connectionId:this.connection.connectionId,metadata:this.connection.metadata};if(this.connection.type===P.Data){let e=this.connection;r={...r,label:e.label,reliable:e.reliable,serialization:e.serialization}}t.socket.send({type:O.Offer,payload:r,dst:this.connection.peer})}catch(e){"OperationError: Failed to set local offer sdp: Called in wrong state: kHaveRemoteOffer"!=e&&(t.emitError(E.WebRTC,e),eO.log("Failed to setLocalDescription, ",e))}}catch(e){t.emitError(E.WebRTC,e),eO.log("Failed to createOffer, ",e)}}async _makeAnswer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createAnswer();eO.log("Created answer."),this.connection.options.sdpTransform&&"function"==typeof this.connection.options.sdpTransform&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp);try{await e.setLocalDescription(n),eO.log("Set localDescription:",n,`for:${this.connection.peer}`),t.socket.send({type:O.Answer,payload:{sdp:n,type:this.connection.type,connectionId:this.connection.connectionId},dst:this.connection.peer})}catch(e){t.emitError(E.WebRTC,e),eO.log("Failed to setLocalDescription, ",e)}}catch(e){t.emitError(E.WebRTC,e),eO.log("Failed to create answer, ",e)}}async handleSDP(e,t){t=new RTCSessionDescription(t);let n=this.connection.peerConnection,r=this.connection.provider;eO.log("Setting remote description",t);try{await n.setRemoteDescription(t),eO.log(`Set remoteDescription:${e} for:${this.connection.peer}`),"OFFER"===e&&await this._makeAnswer()}catch(e){r.emitError(E.WebRTC,e),eO.log("Failed to setRemoteDescription, ",e)}}async handleCandidate(e){eO.log("handleCandidate:",e);try{await this.connection.peerConnection.addIceCandidate(e),eO.log(`Added ICE candidate for:${this.connection.peer}`)}catch(e){this.connection.provider.emitError(E.WebRTC,e),eO.log("Failed to handleCandidate, ",e)}}_addTracksToConnection(e,t){if(eO.log(`add tracks from stream ${e.id} to peer connection`),!t.addTrack)return eO.error("Your browser does't support RTCPeerConnection#addTrack. Ignored.");e.getTracks().forEach(n=>{t.addTrack(n,e)})}_addStreamToMediaConnection(e,t){eO.log(`add stream ${e.id} to media connection ${t.connectionId}`),t.addStream(e)}constructor(e){this.connection=e}}class eG extends ej.EventEmitter{emitError(e,t){eO.error("Error:",t),this.emit("error",new eW(`${e}`,t))}}class eW extends Error{constructor(e,t){"string"==typeof t?super(t):(super(),Object.assign(this,t)),this.type=e}}class eH extends eG{get open(){return this._open}constructor(e,t,n){super(),this.peer=e,this.provider=t,this.options=n,this._open=!1,this.metadata=n.metadata}}class eY extends eH{get type(){return P.Media}get localStream(){return this._localStream}get remoteStream(){return this._remoteStream}_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{eO.log(`DC#${this.connectionId} dc connection success`),this.emit("willCloseOnRemote")},this.dataChannel.onclose=()=>{eO.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}addStream(e){eO.log("Receiving stream",e),this._remoteStream=e,super.emit("stream",e)}handleMessage(e){let t=e.type,n=e.payload;switch(e.type){case O.Answer:this._negotiator.handleSDP(t,n.sdp),this._open=!0;break;case O.Candidate:this._negotiator.handleCandidate(n.candidate);break;default:eO.warn(`Unrecognized message type:${t} from peer:${this.peer}`)}}answer(e,t={}){if(this._localStream){eO.warn("Local stream already exists on this MediaConnection. Are you answering a call twice?");return}for(let n of(this._localStream=e,t&&t.sdpTransform&&(this.options.sdpTransform=t.sdpTransform),this._negotiator.startConnection({...this.options._payload,_stream:e}),this.provider._getMessages(this.connectionId)))this.handleMessage(n);this._open=!0}close(){this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this._localStream=null,this._remoteStream=null,this.provider&&(this.provider._removeConnection(this),this.provider=null),this.options&&this.options._stream&&(this.options._stream=null),this.open&&(this._open=!1,super.emit("close"))}constructor(e,t,n){super(e,t,n),this._localStream=this.options._stream,this.connectionId=this.options.connectionId||eY.ID_PREFIX+eM.randomToken(),this._negotiator=new eV(this),this._localStream&&this._negotiator.startConnection({_stream:this._localStream,originator:!0})}}eY.ID_PREFIX="mc_";class eK{_buildRequest(e){let t=this._options.secure?"https":"http",{host:n,port:r,path:i,key:o}=this._options,s=new URL(`${t}://${n}:${r}${i}${o}/${e}`);return s.searchParams.set("ts",`${Date.now()}${Math.random()}`),s.searchParams.set("version",e$.version),fetch(s.href,{referrerPolicy:this._options.referrerPolicy})}async retrieveId(){try{let e=await this._buildRequest("id");if(200!==e.status)throw Error(`Error. Status:${e.status}`);return e.text()}catch(t){eO.error("Error retrieving ID",t);let e="";throw"/"===this._options.path&&this._options.host!==eM.CLOUD_HOST&&(e=" If you passed in a `path` to your self-hosted PeerServer, you'll also need to pass in that same path when creating a new Peer."),Error("Could not get an ID from the server."+e)}}async listAllPeers(){try{let e=await this._buildRequest("peers");if(200!==e.status){if(401===e.status){let e="";throw e=this._options.host===eM.CLOUD_HOST?"It looks like you're using the cloud server. You can email team@peerjs.com to enable peer listing for your API key.":"You need to enable `allow_discovery` on your self-hosted PeerServer to use this feature.",Error("It doesn't look like you have permission to list peers IDs. "+e)}throw Error(`Error. Status:${e.status}`)}return e.json()}catch(e){throw eO.error("Error retrieving list peers",e),Error("Could not get list peers from the server."+e)}}constructor(e){this._options=e}}class eX extends eH{get type(){return P.Data}_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{eO.log(`DC#${this.connectionId} dc connection success`),this._open=!0,this.emit("open")},this.dataChannel.onmessage=e=>{eO.log(`DC#${this.connectionId} dc onmessage:`,e.data)},this.dataChannel.onclose=()=>{eO.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}close(e){if(e?.flush){this.send({__peerData:{type:"close"}});return}this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this.provider&&(this.provider._removeConnection(this),this.provider=null),this.dataChannel&&(this.dataChannel.onopen=null,this.dataChannel.onmessage=null,this.dataChannel.onclose=null,this.dataChannel=null),this.open&&(this._open=!1,super.emit("close"))}send(e,t=!1){if(!this.open){this.emitError(x.NotOpenYet,"Connection is not open. You should listen for the `open` event before sending messages.");return}return this._send(e,t)}async handleMessage(e){let t=e.payload;switch(e.type){case O.Answer:await this._negotiator.handleSDP(e.type,t.sdp);break;case O.Candidate:await this._negotiator.handleCandidate(t.candidate);break;default:eO.warn("Unrecognized message type:",e.type,"from peer:",this.peer)}}constructor(e,t,n){super(e,t,n),this.connectionId=this.options.connectionId||eX.ID_PREFIX+ex(),this.label=this.options.label||this.connectionId,this.reliable=!!this.options.reliable,this._negotiator=new eV(this),this._negotiator.startConnection(this.options._payload||{originator:!0,reliable:this.reliable})}}eX.ID_PREFIX="dc_",eX.MAX_BUFFERED_AMOUNT=8388608;class eq extends eX{get bufferSize(){return this._bufferSize}_initializeDataChannel(e){super._initializeDataChannel(e),this.dataChannel.binaryType="arraybuffer",this.dataChannel.addEventListener("message",e=>this._handleDataMessage(e))}_bufferedSend(e){(this._buffering||!this._trySend(e))&&(this._buffer.push(e),this._bufferSize=this._buffer.length)}_trySend(e){if(!this.open)return!1;if(this.dataChannel.bufferedAmount>eX.MAX_BUFFERED_AMOUNT)return this._buffering=!0,setTimeout(()=>{this._buffering=!1,this._tryBuffer()},50),!1;try{this.dataChannel.send(e)}catch(e){return eO.error(`DC#:${this.connectionId} Error when sending:`,e),this._buffering=!0,this.close(),!1}return!0}_tryBuffer(){if(!this.open||0===this._buffer.length)return;let e=this._buffer[0];this._trySend(e)&&(this._buffer.shift(),this._bufferSize=this._buffer.length,this._tryBuffer())}close(e){if(e?.flush){this.send({__peerData:{type:"close"}});return}this._buffer=[],this._bufferSize=0,super.close()}constructor(...e){super(...e),this._buffer=[],this._bufferSize=0,this._buffering=!1}}class eQ extends eq{close(e){super.close(e),this._chunkedData={}}_handleDataMessage({data:e}){let t=i(e),n=t.__peerData;if(n){if("close"===n.type){this.close();return}this._handleChunk(t);return}this.emit("data",t)}_handleChunk(e){let t=e.__peerData,n=this._chunkedData[t]||{data:[],count:0,total:e.total};if(n.data[e.n]=new Uint8Array(e.data),n.count++,this._chunkedData[t]=n,n.total===n.count){delete this._chunkedData[t];let e=function(e){let t=0;for(let n of e)t+=n.byteLength;let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.byteLength;return n}(n.data);this._handleDataMessage({data:e})}}_send(e,t){let n=o(e);if(n instanceof Promise)return this._send_blob(n);if(!t&&n.byteLength>this.chunker.chunkedMTU){this._sendChunks(n);return}this._bufferedSend(n)}async _send_blob(e){let t=await e;if(t.byteLength>this.chunker.chunkedMTU){this._sendChunks(t);return}this._bufferedSend(t)}_sendChunks(e){let t=this.chunker.chunk(e);for(let e of(eO.log(`DC#${this.connectionId} Try to send ${t.length} chunks...`),t))this.send(e,!0)}constructor(e,t,r){super(e,t,r),this.chunker=new n,this.serialization=I.Binary,this._chunkedData={}}}class eZ extends eq{_handleDataMessage({data:e}){super.emit("data",e)}_send(e,t){this._bufferedSend(e)}constructor(...e){super(...e),this.serialization=I.None}}class e0 extends eq{_handleDataMessage({data:e}){let t=this.parse(this.decoder.decode(e)),n=t.__peerData;if(n&&"close"===n.type){this.close();return}this.emit("data",t)}_send(e,t){let n=this.encoder.encode(this.stringify(e));if(n.byteLength>=eM.chunkedMTU){this.emitError(x.MessageToBig,"Message too big for JSON channel");return}this._bufferedSend(n)}constructor(...e){super(...e),this.serialization=I.JSON,this.encoder=new TextEncoder,this.decoder=new TextDecoder,this.stringify=JSON.stringify,this.parse=JSON.parse}}class e1 extends eG{get id(){return this._id}get options(){return this._options}get open(){return this._open}get socket(){return this._socket}get connections(){let e=Object.create(null);for(let[t,n]of this._connections)e[t]=n;return e}get destroyed(){return this._destroyed}get disconnected(){return this._disconnected}_createServerConnection(){let e=new eJ(this._options.secure,this._options.host,this._options.port,this._options.path,this._options.key,this._options.pingInterval);return e.on(M.Message,e=>{this._handleMessage(e)}),e.on(M.Error,e=>{this._abort(E.SocketError,e)}),e.on(M.Disconnected,()=>{this.disconnected||(this.emitError(E.Network,"Lost connection to server."),this.disconnect())}),e.on(M.Close,()=>{this.disconnected||this._abort(E.SocketClosed,"Underlying socket is already closed.")}),e}_initialize(e){this._id=e,this.socket.start(e,this._options.token)}_handleMessage(e){let t=e.type,n=e.payload,r=e.src;switch(t){case O.Open:this._lastServerId=this.id,this._open=!0,this.emit("open",this.id);break;case O.Error:this._abort(E.ServerError,n.msg);break;case O.IdTaken:this._abort(E.UnavailableID,`ID "${this.id}" is taken`);break;case O.InvalidKey:this._abort(E.InvalidKey,`API KEY "${this._options.key}" is invalid`);break;case O.Leave:eO.log(`Received leave message from ${r}`),this._cleanupPeer(r),this._connections.delete(r);break;case O.Expire:this.emitError(E.PeerUnavailable,`Could not connect to peer ${r}`);break;case O.Offer:{let e=n.connectionId,t=this.getConnection(r,e);if(t&&(t.close(),eO.warn(`Offer received for existing Connection ID:${e}`)),n.type===P.Media){let i=new eY(r,this,{connectionId:e,_payload:n,metadata:n.metadata});t=i,this._addConnection(r,t),this.emit("call",i)}else if(n.type===P.Data){let i=new this._serializers[n.serialization](r,this,{connectionId:e,_payload:n,metadata:n.metadata,label:n.label,serialization:n.serialization,reliable:n.reliable});t=i,this._addConnection(r,t),this.emit("connection",i)}else{eO.warn(`Received malformed connection type:${n.type}`);return}for(let n of this._getMessages(e))t.handleMessage(n);break}default:{if(!n){eO.warn(`You received a malformed message from ${r} of type ${t}`);return}let i=n.connectionId,o=this.getConnection(r,i);o&&o.peerConnection?o.handleMessage(e):i?this._storeMessage(i,e):eO.warn("You received an unrecognized message:",e)}}}_storeMessage(e,t){this._lostMessages.has(e)||this._lostMessages.set(e,[]),this._lostMessages.get(e).push(t)}_getMessages(e){let t=this._lostMessages.get(e);return t?(this._lostMessages.delete(e),t):[]}connect(e,t={}){if(t={serialization:"default",...t},this.disconnected){eO.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect, or call reconnect on this peer if you believe its ID to still be available."),this.emitError(E.Disconnected,"Cannot connect to new Peer after disconnecting from server.");return}let n=new this._serializers[t.serialization](e,this,t);return this._addConnection(e,n),n}call(e,t,n={}){if(this.disconnected){eO.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect."),this.emitError(E.Disconnected,"Cannot connect to new Peer after disconnecting from server.");return}if(!t){eO.error("To call a peer, you must provide a stream from your browser's `getUserMedia`.");return}let r=new eY(e,this,{...n,_stream:t});return this._addConnection(e,r),r}_addConnection(e,t){eO.log(`add connection ${t.type}:${t.connectionId} to peerId:${e}`),this._connections.has(e)||this._connections.set(e,[]),this._connections.get(e).push(t)}_removeConnection(e){let t=this._connections.get(e.peer);if(t){let n=t.indexOf(e);-1!==n&&t.splice(n,1)}this._lostMessages.delete(e.connectionId)}getConnection(e,t){let n=this._connections.get(e);if(!n)return null;for(let e of n)if(e.connectionId===t)return e;return null}_delayedAbort(e,t){setTimeout(()=>{this._abort(e,t)},0)}_abort(e,t){eO.error("Aborting!"),this.emitError(e,t),this._lastServerId?this.disconnect():this.destroy()}destroy(){this.destroyed||(eO.log(`Destroy peer with ID:${this.id}`),this.disconnect(),this._cleanup(),this._destroyed=!0,this.emit("close"))}_cleanup(){for(let e of this._connections.keys())this._cleanupPeer(e),this._connections.delete(e);this.socket.removeAllListeners()}_cleanupPeer(e){let t=this._connections.get(e);if(t)for(let e of t)e.close()}disconnect(){if(this.disconnected)return;let e=this.id;eO.log(`Disconnect peer with ID:${e}`),this._disconnected=!0,this._open=!1,this.socket.close(),this._lastServerId=e,this._id=null,this.emit("disconnected",e)}reconnect(){if(this.disconnected&&!this.destroyed)eO.log(`Attempting reconnection to server with ID ${this._lastServerId}`),this._disconnected=!1,this._initialize(this._lastServerId);else if(this.destroyed)throw Error("This peer cannot reconnect to the server. It has already been destroyed.");else if(this.disconnected||this.open)throw Error(`Peer ${this.id} cannot reconnect because it is not disconnected from the server!`);else eO.error("In a hurry? We're still trying to make the initial connection!")}listAllPeers(e=e=>{}){this._api.listAllPeers().then(t=>e(t)).catch(e=>this._abort(E.ServerError,e))}constructor(e,t){let n;if(super(),this._serializers={raw:eZ,json:e0,binary:eQ,"binary-utf8":eQ,default:eQ},this._id=null,this._lastServerId=null,this._destroyed=!1,this._disconnected=!1,this._open=!1,this._connections=new Map,this._lostMessages=new Map,e&&e.constructor==Object?t=e:e&&(n=e.toString()),t={debug:0,host:eM.CLOUD_HOST,port:eM.CLOUD_PORT,path:"/",key:e1.DEFAULT_KEY,token:eM.randomToken(),config:eM.defaultConfig,referrerPolicy:"strict-origin-when-cross-origin",serializers:{},...t},this._options=t,this._serializers={...this._serializers,...this.options.serializers},"/"===this._options.host&&(this._options.host=window.location.hostname),this._options.path&&("/"!==this._options.path[0]&&(this._options.path="/"+this._options.path),"/"!==this._options.path[this._options.path.length-1]&&(this._options.path+="/")),void 0===this._options.secure&&this._options.host!==eM.CLOUD_HOST?this._options.secure=eM.isSecure():this._options.host==eM.CLOUD_HOST&&(this._options.secure=!0),this._options.logFunction&&eO.setLogFunction(this._options.logFunction),eO.logLevel=this._options.debug||0,this._api=new eK(t),this._socket=this._createServerConnection(),!eM.supports.audioVideo&&!eM.supports.data){this._delayedAbort(E.BrowserIncompatible,"The current browser does not support WebRTC");return}if(n&&!eM.validateId(n)){this._delayedAbort(E.InvalidID,`ID "${n}" is invalid`);return}n?this._initialize(n):this._api.retrieveId().then(e=>this._initialize(e)).catch(e=>this._abort(E.ServerError,e))}}e1.DEFAULT_KEY="peerjs",window.peerjs={Peer:e1,util:eM},window.Peer=e1})();
+//# sourceMappingURL=peerjs.min.js.map
diff --git a/src/js/joined.js b/src/js/joined.js
new file mode 100644
index 0000000..0011625
--- /dev/null
+++ b/src/js/joined.js
@@ -0,0 +1,48 @@
+var s = document.createElement('script');
+s.setAttribute('src','https://unpkg.com/peerjs@1.5.2/dist/peerjs.min.js');
+document.body.appendChild(s);
+CLIENT = {};
+CLIENT["message_queue"] = [];
+CLIENT["open"] = false;
+CLIENT.lobby_id = null;
+CLIENT["join"] = function(id) {
+ if(CLIENT.lobby_id != null){
+ console.log("Somehow called .join() twice")
+ return;
+ }
+ CLIENT.lobby_id = id;
+ console.log("lobby_id:" + CLIENT.lobby_id);
+ var peer = new Peer();
+ peer.on("open",function(){
+ CLIENT.peer = peer;
+ var conn = CLIENT.peer.connect("ANGRY_ADVENTURE_" + CLIENT.lobby_id);
+ CLIENT.conn = conn
+ console.log("conn is:");
+ console.log(conn);
+ conn.on("open",function(){
+ console.log("Opened peer");
+ CLIENT.open = true
+ conn.on("data",function(data) {
+ console.log("Got data:" + data);
+ CLIENT.message_queue.push(data);
+ })
+ });
+
+ })
+ peer.on('error',function(err){
+ console.log("Error on peer:");
+ console.log(err);
+ CLIENT.error = err;
+ });
+};
+CLIENT.send = function(data) {
+ CLIENT.conn.send(data);
+};
+CLIENT.get = function(){
+ if(CLIENT.message_queue.length >= 1){
+ return CLIENT.message_queue.shift();
+ }else{
+ return null;
+ }
+}
+true;
diff --git a/src/js/lobby.js b/src/js/lobby.js
new file mode 100644
index 0000000..e012b92
--- /dev/null
+++ b/src/js/lobby.js
@@ -0,0 +1,80 @@
+var s = document.createElement('script');
+s.setAttribute('src','https://unpkg.com/peerjs@1.5.2/dist/peerjs.min.js');
+document.body.appendChild(s);
+function genRanHex(size) {
+ return Array.apply(null,Array(size)).map(() => Math.floor(Math.random() * 16).toString(16)).join('');
+}
+GLOBAL = {}
+GLOBAL.lobby_id = genRanHex(6);
+console.log("lobby_id:" + GLOBAL.lobby_id);
+var peer = new Peer("ANGRY_ADVENTURE_" + GLOBAL.lobby_id, {"debug": 3});
+var peer_id = null;
+var poo = peer.on('open', function(id) {
+ console.log('My peer ID is: ' + id);
+ peer.on("connection",function(conn){
+ console.log("Got a connection!")
+ GLOBAL.connections[conn.peer] = conn
+ conn.send("Hello!")
+ GLOBAL.message_queue.push({
+ "msg": "data",
+ "peer": conn.peer,
+ "data": '{"msg":"player_joined"}'
+ })
+ conn.on("data",function(data){
+ console.log("Got some data from a peer!:" + data)
+ GLOBAL.message_queue.push({
+ "msg": "data",
+ "peer": conn.peer,
+ "data": data
+ })
+ })
+ conn.on("error",function(err){
+ console.log("Error on a connection:" + err)
+ GLOBAL.message_queue.push({
+ "msg": "error",
+ "peer": conn.peer,
+ "err": err
+ })
+ })
+ })
+ GLOBAL.peer_id = id;
+});
+console.log("poo:" + poo);
+console.log("Peer:",peer)
+var poe = peer.on('error',function(err){
+ console.log("Error on peer:")
+ console.log(err);
+ GLOBAL.error = err;
+});
+GLOBAL.peer = peer;
+GLOBAL.peer_id = () => peer_id;
+GLOBAL.get_error = () => error;
+GLOBAL.connections = {};
+GLOBAL["get_message"] = function(){
+ if(GLOBAL.message_queue.length >= 1){
+ return GLOBAL.message_queue.shift();
+ }else{
+ return null;
+ }
+}
+GLOBAL["send_message"] = function(who, data){
+ if(GLOBAL.connections[who]._open){
+ GLOBAL.connections[who].send(data);
+ return true
+ }
+ return false;
+}
+GLOBAL["broadcast"] = function(data){
+ console.log("Server broadcasting message:")
+ console.log(data)
+ for(var peerid in GLOBAL.connections){
+ if (GLOBAL.connections[peerid]._open){
+ GLOBAL.connections[peerid].send(data);
+ }
+ }
+ return true;
+}
+GLOBAL["get_peers"] = function() {
+ return GLOBAL.connections;
+}
+GLOBAL.message_queue = [];
diff --git a/src/lobby_menu.moon b/src/lobby_menu.moon
new file mode 100644
index 0000000..23081dc
--- /dev/null
+++ b/src/lobby_menu.moon
@@ -0,0 +1,80 @@
+main = require "main"
+color = require "color"
+ui = require "ui"
+world = require "world"
+char = require "char"
+player = require "player"
+bp = require "broadphase"
+action = require "action"
+room = require "room"
+import World from world
+import LocalPlayer from player
+import LobbyRoom from room
+
+mod = ...
+
+mod.node = am.group!
+--mod.node\append(am.sprite("data/lobby_bg.png"))
+mod.node\append(am.translate(0,main.height/2)^ am.text("Join lobby...", color.fg, "center","top")\tag("join_id"))
+char_selector = am.translate(0,(main.height/2)+64)
+mod.loaded = false
+mod.load = (id) ->
+ main.world = World!
+ main.world\set_room(LobbyRoom!)
+ main.world\join(id)
+ mod.node("join_id").text = "Lobby id:" .. id
+ main.root("screen")\append(mod.node)
+ touch_indicator = am.group!
+ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top")
+ touch_loc = am.translate(0,0)^ touch_cursor
+ touch_indicator\append(touch_loc)
+ char_selector = ui.create_char_selector2(mod.node)
+ char_right, char_left, char_text = char_selector[1], char_selector[2], char_selector[3]
+ mod.buttons = {char_left, char_right}
+ classes = char.class_order
+ class_s = 1
+ char_right.color = color.transparent
+ char_left.color = color.transparent
+ mod.node\action(coroutine.create(() ->
+ while not main.world.client_open!
+ coroutine.yield!
+ char_left.color = color.white
+ char_right.color = color.white
+ char_text.text = "Tumbler"
+ main.world\load!
+ action.sync_players!
+ while true
+ if #main.win\active_touches! > 0
+ touch = main.win\touch_position(1)
+ touch_cursor.color = vec4(1,1,1,1)
+ touch_loc.x = touch.x
+ touch_loc.y = touch.y
+ col_but = bp.check(touch.x, touch.y + 64)
+ if #col_but > 0
+ touch_cursor.color = vec4(0.5,0.5,0.5,0.5)
+ if #col_but > 0 and main.win\touch_began(1)
+ if col_but[1] == char_left
+ class_s = class_s - 1
+ if class_s == 0
+ class_s = #classes
+ if col_but[1] == char_right
+ class_s = class_s + 1
+ if class_s > #classes
+ class_s = 1
+ char_text.text = classes[class_s]
+ action.request_class_change(classes[class_s])
+ else
+ touch_cursor.color = vec4(0,0,0,0)
+ coroutine.yield!
+
+ ))
+ mod.node\append(touch_indicator)
+ mod.loaded = true
+
+mod.unload = () ->
+ main.root("screen")\remove(mod.node)
+ for _, button in pairs(mod.buttons)
+ bp.remove(button)
+ mod.loaded = false
+
+mod
diff --git a/src/main.moon b/src/main.moon
new file mode 100644
index 0000000..fca600f
--- /dev/null
+++ b/src/main.moon
@@ -0,0 +1,72 @@
+mod = ...
+
+require "ext"
+mod.width = 640
+mod.height = 480
+win = am.window{
+ title: "Fools Rush In"
+ width: mod.width
+ height: mod.height
+ clear_color: vec4(85/255,180/255,255/255,255/255)
+}
+mod.root = am.group()
+mod.pips = 5
+char = require "char"
+player = require "player"
+import LocalPlayer from player
+util = require "util"
+world = require "world"
+import World from world
+main_menu = require "main_menu"
+rng_seed = am.eval_js("Math.random()")
+math.randomseed(rng_seed*1000)
+
+mod.screenpos = util.Vec2!
+
+mod.reload = () ->
+ --[[Groups for different layers]]
+ mod.screen = am.group!\tag("screen")^ am.translate(0,0)--Things that should draw on top of everything else, like a hud
+ mod.world_close = am.group!\tag("close")^ am.translate(0,0)--Things that should draw "close to the camera", fast-parallax background
+ mod.world = am.translate(0,0) ^ am.group({
+ am.group!\tag("world_front"), -- things in front of players
+ am.group!\tag("world_characters"), -- players
+ am.group!\tag("world_behind"), -- things behind players
+ })\tag("world")--Characters, the world, players, ect.
+ mod.world_far = am.group!\tag("far")^ am.translate(0,0) --Things that move slowly in the background
+ mod.background = am.group!\tag("bg")--The background does not move, draws behind everything.
+ mod.root = am.group{
+ mod.background,
+ mod.world_far,
+ mod.world,
+ mod.world_close,
+ mod.screen,
+ }
+ win.scene = mod.root
+ mod.action_queue = {
+ {main_menu.load, {}}
+ }
+
+ mod.root\action(coroutine.create(() ->
+ while true
+ if mod.server
+ mod.server\update!
+ coroutine.yield!
+ ))
+ mod.root\action(coroutine.create(() ->
+ while true
+ start_time = am.current_time!
+ mod.world.x = -mod.screenpos.x
+ mod.world.y = -mod.screenpos.y
+ if #mod.action_queue > 0
+ action = table.remove(mod.action_queue)
+ f, args = action[1], action[2]
+ f(unpack(args))
+ if mod.world
+ mod.world\update!
+ end_time = am.current_time!
+
+ coroutine.yield!
+ ))
+mod.reload!
+mod.win = win
+mod
diff --git a/src/main_menu.moon b/src/main_menu.moon
new file mode 100644
index 0000000..fee6fb5
--- /dev/null
+++ b/src/main_menu.moon
@@ -0,0 +1,81 @@
+
+main = require "main"
+bp = require "broadphase"
+color = require "color"
+ui = require "ui"
+
+world = require "world" --delete when done
+import Server from world
+import World from world
+mod = ...
+
+-- 4 parts?
+nwidth = 6
+section_width = 32
+start_x = (-32)*(nwidth/2)
+mod.node = am.group!
+mod.node\append(am.sprite("data/main_bg.png"))
+start_y = 0
+padding = 32
+buttonspecs = {
+ create_party: {
+ y_off: start_y,
+ text: "Create Troupe"
+ },
+ join_party: {
+ y_off: start_y + 64 + padding,
+ text: "Join Troupe"
+ }
+}
+
+
+mod.load = () ->
+ print("creating main menu")
+ main.root("screen")\append(mod.node)
+ for name, button_tbl in pairs buttonspecs
+ button_extra = ui.create_big_button(mod.node,6,start_x,button_tbl.y_off)
+ button_extra.node\append(am.translate(start_x+((nwidth*section_width)/2),button_tbl.y_off - 32)^ am.text(button_tbl.text,color.fg))
+ mod[name] = button_extra
+ touch_indicator = am.group!
+ touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top")
+ touch_loc = am.translate(0,0)^ touch_cursor
+ touch_indicator\append(touch_loc)
+ mod.node\append(am.translate(0,-150)^ am.scale(3)^ am.text("Fools Rush In"))
+ mod.node\action("click_interpreter",coroutine.create(()->
+ while true
+ print("Main menu loop")
+ if #main.win\active_touches! > 0
+ touch = main.win\touch_position(1)
+ touch_cursor.color = vec4(1,1,1,1)
+ touch_loc.x = touch.x
+ touch_loc.y = touch.y
+ col_but = bp.check(touch.x, touch.y+64)
+ if #col_but > 0
+ touch_cursor.color = vec4(0.5,0.5,0.5,0.5)
+ if #col_but > 0
+ print("Collided with button:",col_but)
+ print("mod.create prty was:",mod.create_party)
+ if col_but[1] == mod.create_party
+ print("Creating party")
+ create_party = require "create_party_menu"
+ table.insert(main.action_queue,{create_party.load, {}})
+ mod.unload!
+ coroutine.yield!
+ elseif col_but[1] == mod.join_party
+ print("Joining party")
+ join_party = require "join_party_menu"
+ table.insert(main.action_queue,{join_party.load, {}})
+ mod.unload!
+ coroutine.yield!
+ else
+ touch_cursor.color = vec4(0,0,0,0)
+ coroutine.yield!
+ ))
+ mod.node\append(touch_indicator)
+
+mod.unload = () ->
+ main.root("screen")\remove(mod.node)
+ bp.remove(mod.create_party)
+ bp.remove(mod.join_party)
+
+mod
diff --git a/src/party.moon b/src/party.moon
new file mode 100644
index 0000000..1c2fb7b
--- /dev/null
+++ b/src/party.moon
@@ -0,0 +1,58 @@
+-- Describes a player or enemy's party
+-- Parties can exist without any members
+room = require("room")
+main = require("main")
+char = require("char")
+import Character from char
+import LobbyRoom from room
+mod = ...
+
+class Party
+ new: =>
+ @members = {}
+ @rnode = am.group!
+ @node = am.translate(0,0)
+ @rnode\append(@node)
+ @room = LobbyRoom!
+ member: (uname) =>
+ assert(uname, "cannot find a nil party member")
+ assert(type(uname) == "string", "Member should be called with a peer id")
+ @members[uname]
+ nmembers: () =>
+ i = 0
+ for k,v in pairs(@members)
+ i += 1
+ i
+ add_member: (member) =>
+ assert(member.uname, "cannot add a nil party member")
+ assert(type(member.uname) == "string", "Member should be called with a peer id")
+ @members[member.uname] = member
+ member.party = @
+ @node\append(member.node)
+ remove_member: (member) =>
+ @members[member.uname] = nil
+ member.party = nil
+ @node\remove(member.node)
+ set_room: (nroom) =>
+ @room = nroom
+ for id, char in pairs(@members)
+ char\enter_room(nroom)
+
+ serialize: () =>
+ members = {}
+ for _, member in pairs(@members)
+ members[member.uname] = member\serialize!
+ am.to_json(members)
+
+ deserialize: (data) ->
+ rp = Party!
+ tbl = am.parse_json(data)
+ for uname, payload in pairs(tbl)
+ tchar = Character.deserialize(payload)
+ rp\add_member(tchar)
+ rp
+
+
+
+mod["Party"] = Party
+mod
diff --git a/src/pixelize.moon b/src/pixelize.moon
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pixelize.moon
diff --git a/src/player.moon b/src/player.moon
new file mode 100644
index 0000000..076f9bb
--- /dev/null
+++ b/src/player.moon
@@ -0,0 +1,49 @@
+char = require "char"
+util = require "util"
+main = require "main"
+bp = require "broadphase"
+import KeyInput from char
+import Vec2 from util
+
+mod = ...
+--[[A player that we receive information about]]
+class RemotePlayer extends char.Character
+ new: (uname, data, charclass) =>
+ super(uname, data, charclass)
+
+class LocalPlayer extends char.Character
+ new:(uname, data, charclass) =>
+ super(uname, data, charclass)
+
+ draw: (...) =>
+ super(...)
+ healthnode = am.group() ^ {am.translate(-main.width/2,main.height/2) ^ am.text(string.format("Health:%d", @health), vec4(255,255,255,255), "left","top")\tag "health text"}
+ @node\append(healthnode)
+
+ take_damage: (_from, ammt) =>
+ if am.current_time! - @last_dammage > 1
+ @health -= ammt
+ @last_dammage = am.current_time!
+ (@node)("health text").text = string.format("Health:%d",@health)
+ if @health == 0
+ @\die!
+
+ filter: (other) =>
+ if other.canfall
+ return "cross"
+ if other.hurtbox
+ if other.owner ~= @
+ @take_damage(other,1)
+ return "cross"
+ else
+ return "slide"
+ die: (...) =>
+ --[[When the player dies, wait 100 ms, load the level, and drop them on the spawnpoint]]
+
+ super(...)
+
+
+
+mod["RemotePlayer"] = RemotePlayer
+mod["LocalPlayer"] = LocalPlayer
+mod
diff --git a/src/room.moon b/src/room.moon
new file mode 100644
index 0000000..e578f50
--- /dev/null
+++ b/src/room.moon
@@ -0,0 +1,152 @@
+-- Room, can contain one or more parties
+-- When things like abilities happen, they always happen on the room.
+main = require "main"
+char = require "char"
+import Character from char
+mod = ...
+
+-- Each room has 8 position, 4 for players, 4 for enemies
+-- Room types:
+-- normal : p p p p e e e e
+-- 4 3 2 1 1 2 3 4
+-- surrounded: e e p p p p e e
+-- 2 1 1 2 2 1 1 2
+-- 3 3 3 3
+-- 4 4 4 4
+-- ambush : e p p e e p p e
+-- 1 1 1 1 1 1 1 1
+-- 2 2 2 2 2 2 2 2
+-- 3 3 3 3 3 3 3 3
+-- 4 4 4 4 4 4 4 4
+-- skirmish: e p e p e p e p
+-- 1 1 1 1 1 1 1 1
+-- 2 2 2 2 2 2 2 2
+-- 3 3 3 3 3 3 3 3
+-- 4 4 4 4 4 4 4 4
+
+class Room
+ @children = {}
+ new: (data) =>
+ @parties = {}
+ @actions_taken = {}
+ @special = {}
+ @node = am.group!
+ @data = data or {locations: {{},{},{},{},{},{},{},{}}}
+ for position, ptbl in pairs(@data.locations)
+ for pid, character in pairs(ptbl)
+ if character.uname
+ --player character
+ ptbl[pid] = main.world.player_party\member(character.uname)
+ else
+ ptbl[pid] = Character.deserialize(character)
+
+ add_party: (party) =>
+ table.insert(@parties,party)
+
+ at_location: (n) ->
+ return @data.locations[n]
+
+ generate: (...) ->
+ mod.SimpleRoom!
+
+ @__inherited: (child) =>
+ assert(child.distribute_party, "Rooms must be able to distribute parties")
+ @@.children[child.__name] = child
+ mod[child.__name] = child
+
+ serialize: () =>
+ ser_tbl = {
+ name:@@__name,
+ data:@data
+ }
+ --Rewrite locations to serialize characters
+ for position, ptbl in pairs(ser_tbl.data.locations)
+ for pid, character in pairs(ptbl)
+ if character.uname
+ ptbl[pid] = {uname:character.uname}
+ else
+ ptbl[pid] = character\serialize!
+ ret = am.to_json(ser_tbl)
+ ret
+
+ deserialize: (data) ->
+ tbl = am.parse_json(data)
+ typ = mod.Room.children[tbl.name]
+ typ(tbl.data)
+
+ __tostring: () =>
+ return string.format("<%s>",@@__name)
+
+class LobbyRoom extends Room
+ new: (...) =>
+ super(...)
+ @floor_y = -208
+ @node\append(am.sprite("data/lobby_bg.png"))
+ load:() =>
+ main.root("bg")\append(@node)
+ unload:() =>
+ main.root("bg")\remove(@node)
+
+ distribute_party: (...) =>
+ {}
+
+ enemy_location_of: (position) =>
+ assert(position, "Position my not be nil")
+ ((main.width/9) * position)
+
+ player_location_of: (position) =>
+ error("lobbyroom player_position_of")
+ assert(position, "Position may not be nil")
+ -((main.width/9) * position)
+
+class CampRoom extends Room
+ new: (...) =>
+ super(...)
+ @floor_y = -207
+ @node\append(am.sprite("data/camp.png"))
+ @data.locations = {{},{},{},{},{},{},{},{}}
+ load:() =>
+ main.root("bg")\append(@node)
+
+ unload:() =>
+ main.root("bg")\remove(@node)
+
+ distribute_party: (playerparty) =>
+ for _,player in pairs(playerparty)
+ table.insert(ret[player.position],player)
+
+class SimpleRoom extends Room
+ new: (...) =>
+ super(...)
+ @floor_y = -207
+ @node\append(am.sprite("data/room.png"))
+ load:() =>
+ main.root("bg")\append(@node)
+
+ unload:() =>
+ main.root("bg")\remove(@node)
+
+ distribute_party:(playerparty, enemyparty) =>
+ for _,player in pairs(playerparty.members)
+ loc = @data.locations[5-player.data.position]
+ table.insert(loc,player)
+ player\set_location(loc)
+ for _,enemy in pairs(enemyparty.members)
+ loc = @data.locations[enemy.data.position+4]
+ table.insert(loc,enemy)
+ enemy\set_location(loc)
+
+ player_location_of: (position) =>
+ assert(position, "Position may not be nil")
+ -((main.width/9) * position)
+
+ enemy_location_of: (position) =>
+ assert(position, "Position my not be nil")
+ ((main.width/9) * position)
+
+
+
+mod.Room = Room
+mod.SimpleRoom = SimpleRoom
+
+mod
diff --git a/src/ui.moon b/src/ui.moon
new file mode 100644
index 0000000..f46d8b2
--- /dev/null
+++ b/src/ui.moon
@@ -0,0 +1,221 @@
+--sprite packing sepcs
+bp = require "broadphase"
+main = require "main"
+action = require "action"
+color = require "color"
+reg = require "ability_reg"
+char = require "char"
+import Enemy from char
+mod = ...
+
+mod.big_frame_top_left = {
+ texture: "ui.png",
+ s1: 0
+ t1: 0
+ s2: 0
+ t2: 0
+ --hold on, maybe don't do this since we would have to mess with imagebuffers and textures
+}
+
+mod.create_big_button = (node, segments, x, y) ->
+ --Constants
+ section_width = 32
+
+ ret = {}
+ trans = am.translate(x,y)
+ button = am.group!
+ button\append(trans^ am.sprite("data/big_frame_top_left.png",vec4(1,1,1,1),"left","top"))
+ for i = 1,segments - 2
+ button\append(am.translate(((-32)*(segments/2)) + 32*i,y)^ am.sprite("data/big_frame_top_mid.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate((32*((segments/2)-1)),y)^ am.sprite("data/big_frame_top_right.png",vec4(1,1,1,1),"left","top"))
+ ret["node"] = button
+ bp.add(ret,x,y,segments*32,64)
+ node\append(button)
+ ret
+
+
+mod.create_small_button = (node, segments, x, y) ->
+ assert(node ~= nil, "node was nil")
+ assert(type(segments) == "number","segments must be a numbeR")
+ assert(segments > 1, "segments must be at least 2")
+ --Constants
+ section_width = 18
+ section_height = 40
+
+ ret = {}
+ trans = am.translate(x,y)
+ button = am.group!
+ button\append(trans^ am.sprite("data/small_frame_left.png",vec4(1,1,1,1),"left","top"))
+ for i = 1,segments - 2
+ button\append(am.translate(x + (section_width*i),y)^ am.sprite("data/small_frame_mid.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x+(section_width*(segments - 1)),y)^ am.sprite("data/small_frame_right.png",vec4(1,1,1,1),"left","top"))
+ ret["node"] = button
+ bp.add(ret,x,y,segments*section_width,section_height)
+ node\append(button)
+ ret
+
+mod.create_any_button = (node, x_segs, y_segs, x, y) ->
+ assert(x_segs >= 2, "x must have at least 2 segments")
+ assert(y_segs >= 2, "y must have at leats 2 segments")
+ section_width, section_height = 32,32
+ ret = {}
+ button = am.group!
+ button\append(am.translate(x,y)^ am.sprite("data/any_frame_top_left.png",vec4(1,1,1,1),"left","top"))
+ for i = 1, x_segs - 2
+ button\append(am.translate(x + (i * section_width),y)^ am.sprite("data/any_frame_top_mid.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x + ((x_segs-1)*section_width),y)^ am.sprite("data/any_frame_top_right.png",vec4(1,1,1,1),"left","top"))
+ for i = 1, y_segs - 2
+ button\append(am.translate(x,y - (i * section_height))^ am.sprite("data/any_frame_mid_left.png",vec4(1,1,1,1),"left","top"))
+ for j = 1, x_segs - 2
+ button\append(am.translate(x + (j * section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_mid.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x + ((x_segs-1)*section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_right.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x,y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_left.png",vec4(1,1,1,1),"left","top"))
+ for i = 1, x_segs - 2
+ button\append(am.translate(x + (i * section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_mid.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x+((x_segs-1)*section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_right.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x,y)\tag("loc"))
+ ret["node"] = button
+ bp.add(ret,x,y,x_segs*section_width,y_segs*section_height)
+ node\append(button)
+ ret
+
+mod.create_char_selector2 = (node) ->
+ left = mod.create_any_button(node,2,2,96,100)
+ right = mod.create_any_button(node,2,2,-96-(32*2),100)
+ text = am.text("Loading...",vec4(1,1,1,1),"center","top")
+ node\append(am.translate(0,100)^ text)
+ {left, right, text}
+
+mod.create_lobby_player = (node, peerid) ->
+ floor_y = -200
+ rng_x = math.random(main.width) - (main.width/2)
+
+mod.create_char_selector = (node) ->
+ s = am.group!
+ slx = (-96/2)-18
+ sly = (96/2)
+ srx = (96/2)
+ sry = (96/2)
+ scroll_left_node = am.translate(slx,sly)^ am.sprite("data/selector_left.png", vec4(1,1,1,1), "left","top")
+ scroll_left = {
+ node: scroll_left_node
+ }
+ scroll_right_node = am.translate(srx,sry)^ am.sprite("data/selector_right.png", vec4(1,1,1,1), "left","top")
+ scroll_right = {
+ node: scroll_right_node
+ }
+ s\append(scroll_left_node)
+ s\append(scroll_right_node)
+ bp.add(scroll_left,slx,sly,18,40)
+ bp.add(scroll_right,srx,sry,18,40)
+ node\append(s)
+ {scroll_left, scroll_right}
+
+mod.fadeout = () ->
+ fadeout_walltime = 0.1
+ screen = main.root("screen")
+ hw = main.width/2
+ hh = main.height/2
+ bg = color.bg
+ start_color = vec4(bg.r,bg.g,bg.b,0)
+ fadeout = am.rect(-hw,hh,hw,-hh,color.bg)\tag("fade")
+ screen\action(am.tween(0.1, {
+ color: color.bg
+ }))
+
+mod.fadein = () ->
+ fadein_walltime = 0.1
+ fade = main.root("fade")
+ fade\action(am.tween(0.1, {
+ color: color.transparent
+ }))
+
+mod.create_join_input = (node) ->
+ segments=3
+ button_width = segments*18
+ button_height = 40
+ cell_padding = 18
+ grid_w = 4
+ grid_h = 4
+ buttons = {}
+ --but = mod.create_small_button(node,3,-36,0)
+ start_x = cell_padding - ((grid_w/2) * button_width) - (((grid_w - 1)/2) * cell_padding)
+ start_y = 0 + ((grid_h/2) * button_height) + (((grid_h - 1)/2) * cell_padding)
+ for i = 0,grid_h - 1
+ for j = 0, grid_w - 1
+ button_x = start_x + (j * button_width) + ((j-1) * cell_padding)
+ button_y = start_y - (i * button_height) - ((i-1) * cell_padding)
+ small_button = mod.create_small_button(node,3,button_x,button_y)
+ table.insert(buttons, small_button)
+ rnode = small_button.node
+ text_map = {
+ "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"
+ }
+ bt = text_map[(i * grid_w) + j + 1]
+ rnode\append(am.translate(button_x+16,button_y-10)^ am.text(bt,vec4(1,1,1,1),"left","top"))
+ small_button["text"] = bt
+
+ selected = mod.create_big_button(node,6,-32*3,-100)
+ buttons
+
+mod.tween_hit = (char, target, at_f) -> -- char is a character, target is a location (number)
+ x_from, x_to = char.node("char_translate").x, 0
+ room = char.room
+ if char.__class == Enemy then
+ --x_from = room\enemy_location_of(char.data.location)
+ x_to = room\player_location_of(target)
+ else
+ --x_from = room\player_location_of(char.data.location)
+ x_to = room\enemy_location_of(target)
+ am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_to},am.windup))
+ if at_f
+ at_f!
+ am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_from},am.linear))
+
+mod.build_infocard = (node, ability) ->
+ x_segs = 10
+ y_segs = 4
+ x = -310
+ y = 80
+ section_width, section_height = 32,32
+ button = am.group!
+ button\append(am.translate(x,y)^ am.sprite("data/any_frame_top_left.png",vec4(1,1,1,1),"left","top"))
+ for i = 1, x_segs - 2
+ button\append(am.translate(x + (i * section_width),y)^ am.sprite("data/any_frame_top_mid.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x + ((x_segs-1)*section_width),y)^ am.sprite("data/any_frame_top_right.png",vec4(1,1,1,1),"left","top"))
+ for i = 1, y_segs - 2
+ button\append(am.translate(x,y - (i * section_height))^ am.sprite("data/any_frame_mid_left.png",vec4(1,1,1,1),"left","top"))
+ for j = 1, x_segs - 2
+ button\append(am.translate(x + (j * section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_mid.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x + ((x_segs-1)*section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_right.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x,y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_left.png",vec4(1,1,1,1),"left","top"))
+ for i = 1, x_segs - 2
+ button\append(am.translate(x + (i * section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_mid.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x+((x_segs-1)*section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_right.png",vec4(1,1,1,1),"left","top"))
+ button\append(am.translate(x,y)\tag("loc"))
+ button("loc")\append(am.scale(2)^ am.translate(16,-8)^ am.text(ability.text, color.fg, "left","top"))
+ button("loc")\append(am.scale(1)^ am.translate(16,-48)^ am.text(ability.description, color.fg, "left","top"))
+ button("loc")\append(am.scale(1)^ am.translate(16,-80)^ am.text("Speed:" .. tostring(ability.speed), color.fg, "left","top"))
+ button("loc")\append(am.scale(1)^ am.translate(16,-108)^ am.text("Hits:",color.fg,"left","center"))
+ for i = 1,8
+ button("loc")\append(am.translate(42 + (i*24), -96)^ am.sprite("data/pip_frame.png",color.white,"left","top"))
+ if ability.hits_icon[i] == 1
+ button("loc")\append(am.translate(42 + (i*24), -96)^ am.sprite("data/pip_light.png",color.white,"left","top"))
+
+ node\append(button)
+ button
+
+
+mod.battle_log = (text) ->
+ sx, sy = math.random(-100,100), math.random(-40,40)
+ trans = am.translate(sx,sy)
+ trans\action(am.tween(3,{y:sy + 40}))
+ t = am.text(text, color.fg)
+ t\action(am.tween(3,{color:color.transparent}))
+ t\action(coroutine.create( () ->
+ am.wait(am.delay(4))
+ main.root("screen")\remove(trans)
+ ))
+ main.root("screen")\append(trans^ t)
+
+mod
diff --git a/src/util.moon b/src/util.moon
new file mode 100644
index 0000000..3066d86
--- /dev/null
+++ b/src/util.moon
@@ -0,0 +1,37 @@
+--[[Utility classes]]
+
+mod = ...
+
+class Vec3
+ new:(x,y,z) =>
+ @x = x or 0
+ @y = y or 0
+ @z = z or 0
+
+class Vec2
+ new: (x,y) =>
+ @x = x or 0
+ @y = y or 0
+ area: () => math.abs(@x * @y)
+ __sub: (a,b) ->
+ Vec2(a.x-b.x,a.y-b.y)
+ __add: (a,b) ->
+ Vec2(a.x + b.x, a.y + b.y)
+
+--getmetatable(Vec2).__sub = (a,b) ->
+ --Vec2(a.x-b.x,b.x-b.y)
+
+drawable = (c) ->
+ assert(c.sprite or c.anim)
+
+anim_co = (a) ->
+ while true
+ a.keyframe = math.floor(am.current_time()*4) % #(a.anim)
+ assert(a.anim[a.keyframe + 1], "Failed to find an appropriate image to draw.")
+ a.node\replace("sprite",am.sprite(a.anim[a.keyframe + 1])\tag "sprite")
+ coroutine.yield()
+
+
+mod["Vec2"] = Vec2
+mod["Vec3"] = Vec3
+mod
diff --git a/src/world.moon b/src/world.moon
new file mode 100644
index 0000000..c7b106c
--- /dev/null
+++ b/src/world.moon
@@ -0,0 +1,368 @@
+-- Handles singleton logic
+
+mod = ...
+connect = require "connect"
+lobby = require "lobby"
+joined = require "joined"
+party = require "party"
+char = require "char"
+import Character from char
+import Enemy from char
+import Party from party
+player = require "player"
+import RemotePlayer from player
+import LocalPlayer from player
+main = require "main"
+room = require "room"
+import Room from room
+import LobbyRoom from room
+ui = require "ui"
+ability = require "ability_reg"
+
+class Server
+ new:() =>
+ @server = true
+ @client = false
+ am.eval_js(connect)
+ am.eval_js(lobby)
+ @lobby_id = am.eval_js("GLOBAL.lobby_id")
+ @game_state = "lobby" --lobby, room_entry, room_players, room_battle_animate, victory, camp_entry, camp_players_animate, defeat, done
+ @game_state_extra = 0
+ @players = {} --[peer_id] = tbl
+ @set_actions = {} --[peer_id] = "name"
+ @player_party = nil -- the party, created at campaign start
+ @host = nil --who is the lobby host, with the power to start the game?
+ @enemy_party = nil -- The enemy party
+ @updates = {}
+ @dead_players = {} --[peer_id] = true
+ @cr = 1
+ player_joined:(msg)=>
+ peer = msg.peer
+ @players[peer] = RemotePlayer(peer,nil,char.classes.Tumbler)
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_player_joined",uname:peer,class:"Tumbler"})))
+ if @host == nil
+ @host = peer
+ request_class_change:(msg)=>
+ if @game_state == "lobby"
+ if @players[msg.peer]
+ @players[msg.peer]\set_class(char.classes[msg.class])
+ else
+ @players[msg.peer] = RemotePlayer(msg.peer,nil,char.classes[msg.class])
+ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "confirm_class_change", class: msg.class})))
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_class_change",uname:msg.peer,class:msg.class})))
+ else
+ am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "deny_class_change", class:@players[msg.peer].class})))
+ request_campaign_start:(msg)=>
+ if msg.peer == @host and @game_state == "lobby"
+ @player_party = Party!
+ --Set everyone's position, hp, ect.
+ for _, chartbl in pairs(@players)
+ chartbl.position = chartbl.class.default_position
+ chartbl.hp = chartbl.class.default_hp
+ @player_party\add_member(chartbl)
+ @campaign_start!
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_campaign_start",time_ref:@player_start_time})))
+ campaign_start: =>
+ @game_state = "room_entry"
+ @player_start_time = am.eval_js("new Date().getTime()")
+ room = Room.generate(@cr)
+ @room = room
+ @enemy_party = @generate_enemies!
+ @enemy_party\set_room(room)
+ @player_party\set_room(room)
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({
+ msg: "info_enemy_party",
+ data: @enemy_party\serialize!
+ })))
+ room\distribute_party(@player_party,@enemy_party)
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_room",data:room\serialize!,time_ref:@player_start_time})))
+ @game_state = "room_players"
+
+ request_player_list:(msg)=>
+ player_ser = {}
+ for peerid, player in pairs(@players)
+ player_ser[peerid] = player\serialize!
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"respond_player_list", data:player_ser})))
+ set_action: (msg)=>
+ if @game_state == "room_players"
+ if not @dead_players[msg.peer]
+ @set_actions[msg.peer] = ability[msg.action]
+ update:() =>
+ msg = am.eval_js("GLOBAL.get_message()")
+ if msg != nil
+ if msg.msg == "data"
+ info = am.parse_json(msg.data)
+ info.peer = msg.peer -- server messages have an extra "peer" field that the client didn't add.
+ if @[info.msg]
+ @[info.msg](@,info)
+ else
+ print("Failed to find server message handler:",msg,"no handler",info.msg)
+ else
+ print("Msg was nil")
+ --actual game loop
+ if @game_state == "room_players"
+ if am.eval_js("new Date().getTime()") > @player_start_time + 6000
+ --Generate some random actions from npcs
+ npc_actions = {}
+ used_actions = {}
+ party_index = {}
+ character_index = {}
+ for uname, npc in pairs(@enemy_party.members)
+ npc_actions[uname] = npc\select_action!
+ table.insert(used_actions, npc_actions[uname])
+ party_index[npc_actions[uname]] = @enemy_party
+ character_index[npc_actions[uname]] = npc
+ total_actions = {}
+ for k,v in pairs(npc_actions)
+ total_actions[k] = v.__name
+ for k,v in pairs(@set_actions)
+ total_actions[k] = v.__name
+ table.insert(used_actions, v)
+ party_index[v] = @player_party
+ character_index[v] = @player_party\member(k)
+ --Lock in actions, and send it back out (500 ms)
+ table.sort(used_actions, (a,b) ->
+ a.speed < b.speed
+ )
+ -- actually do the actions
+ for k,v in ipairs(used_actions)
+ tchar = character_index[v]
+ v.__class.use(@,party_index[v],tchar)
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({
+ msg:"info_actions",
+ data: total_actions
+ })))
+ --sort by speed and apply
+ @game_state = "room_battle_animate"
+ @set_actions = {}
+ if @game_state == "room_battle_animate" --only exists for 1 tick, calc dammge, check if room is done, check if we're defeated, ect.
+ @calculate_damage
+ @player_start_time = am.eval_js("new Date().getTime()")+500 --500 ms for animations
+ @game_state = "room_players"
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({
+ msg:"info_timeref",
+ time_ref:@player_start_time
+ })))
+ dead_characters = {}
+ for uname, char in pairs(@player_party.members)
+ if char.data.hp <= 0
+ table.insert(dead_characters,uname)
+ char\die!
+ @player_party\remove_member(char)
+ @dead_players[uname] = true
+ for uname, char in pairs(@enemy_party.members)
+ if char.data.hp <= 0
+ table.insert(dead_characters,uname)
+ char\die!
+ @enemy_party\remove_member(char)
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({
+ msg:"info_deaths",
+ data:dead_characters
+ })))
+ if @player_party\nmembers! == 0
+ @game_state = "defeat"
+ elseif @enemy_party\nmembers! == 0
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({
+ msg:"info_loot"
+ time_ref:@player_start_time
+ --Don't actually do loot, just go to the next room
+ })))
+ @player_start_time = am.eval_js("new Date().getTime()")
+ @game_state = "victory"
+ if @game_state == "defeat"
+ am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({
+ msg:"info_defeat"
+ })))
+ @game_state = "done"
+ if @game_state == "victory"
+ if am.eval_js("new Date().getTime()") > @player_start_time + 6000 -- a few seconds for victory! (and animations)
+ @cr += 1
+ @campaign_start!
+
+ calculate_dammage:() =>
+ print("calculating dammage...")
+
+ generate_enemies:() =>
+ enemies = {}
+ tcr = @cr
+ possible_enemies = {}
+ for k,v in pairs(char.enemies)
+ table.insert(possible_enemies,v)
+ enemy_party = Party!
+ while tcr > 0 and #possible_enemies > 0
+ filtered_enemies = {}
+ for _, enemy in pairs(possible_enemies)
+ if enemy.cr <= tcr
+ table.insert(filtered_enemies,enemy)
+ table.shuffle(filtered_enemies)
+ rng_enemy = table.remove(filtered_enemies)
+ tcr = tcr - rng_enemy.cr
+ enemy_party\add_member(Enemy(nil,rng_enemy))
+ enemy_party
+
+class World
+ new:() =>
+ @client = true
+ @server = false
+ am.eval_js(connect)
+ @players = {}
+ @player_party = Party!
+ @enemy_party = nil
+ main.root("world_characters")\append(@player_party.node)
+ @parties = {@player_party}
+ @node = am.group!
+ @node\append(@player_party.node)
+ @node\action(coroutine.create(()->
+ while true
+ coroutine.yield!
+ ))
+ @localplayer = nil
+ join:(id) =>
+ am.eval_js(joined)
+ am.eval_js("CLIENT.join('" .. id .. "');")
+ client_open:() =>
+ am.eval_js("CLIENT.open")
+ client_id:() =>
+ am.eval_js("CLIENT.peer")
+ confirm_class_change: (msg) =>
+ @localplayer\set_class(char.classes[msg.class])
+ deny_class_change: (msg) =>
+ @localplayer\set_class(char.classes[msg.class])
+ respond_player_list: (msg) =>
+ for peerid, chardata in pairs(msg.data)
+ if not @player_party\member(peerid)
+ if peerid == am.eval_js("CLIENT.peer._id")
+ @localplayer = Character.deserialize(chardata)
+ @player_party\add_member(@localplayer)
+ @localplayer\enter_room(@player_party.room)
+ else
+ newplayer = Character.deserialize(chardata)
+ @player_party\add_member(newplayer)
+ newplayer\enter_room(@player_party.room)
+ else
+ print("Do nothing...")
+ info_class_change: (msg) =>
+ if msg.uname == @localplayer.uname
+ return
+ if not @player_party\member(msg.uname)
+ @player_party\add_member(Character.deserialize(msg.class))
+ else
+ @player_party\member(msg.uname)\set_class(char.classes[msg.class])
+ info_player_joined: (msg) =>
+ if msg.uname == am.eval_js("CLIENT.peer._id")
+ if @localplayer != nil
+ return
+ @localplayer = LocalPlayer(msg.uname, {}, char.classes[msg.class])
+ @player_party\add_member(@localplayer)
+ @localplayer\enter_room(@player_party.room)
+ elseif not @player_party\member(msg.uname)
+ newplayer = RemotePlayer(msg.uname, nil, char.classes[msg.class])
+ @player_party\add_member(newplayer)
+ newplayer\enter_room(@player_party.room)
+ else
+ print("Do nothing")
+ info_campaign_start: (msg) =>
+ lobby_menu = require "lobby_menu"
+ create_party_menu = require "create_party_menu"
+ if create_party_menu.loaded
+ create_party_menu.unload!
+ if lobby_menu.loaded
+ lobby_menu.unload!
+ battle_menu = require "battle_menu"
+ battle_menu.load!
+ ui.fadeout!
+ @time_ref = msg.time_ref
+ info_room: (msg) =>
+ @room = Room.deserialize(msg.data)
+ @time_ref = msg.time_ref
+ @set_room(@room)
+ battle_menu = require "battle_menu"
+ battle_menu.victory = false
+ main.root\remove("infocard")
+ info_timeref: (msg) =>
+ main.root\remove("infocard")
+ @time_ref = msg.time_ref
+ info_enemy_party: (msg) =>
+ if @enemy_party
+ @node\remove(@enemy_party.rnode)
+ @enemy_party = Party.deserialize(msg.data)
+ @enemy_party\set_room(@room)
+ if @room.__class != LobbyRoom
+ @enemy_party\set_room(@room)
+ battle_menu = require "battle_menu"
+ battle_menu.victory = false
+ main.root\remove("infocard")
+ --@node\append(@enemy_party.node)
+ info_defeat: (msg) =>
+ battle_menu = require("battle_menu")
+ defeat_menu = require("defeat_menu")
+ table.insert(main.action_queue,{battle_menu.unload, {}})
+ table.insert(main.action_queue,{defeat_menu.load,{}})
+ info_loot: (msg) =>
+ @time_ref = msg.time_ref
+ battle_menu = require "battle_menu"
+ battle_menu.victory = true
+ main.root\remove("infocard")
+ info_updates: (msg) =>
+ for uname, updated in pairs(msg.data)
+ tchar = @player_party\member(uname) or @enemy_party\member(uname)
+ for k,v in pairs(updated)
+ tchar.data[k] = v
+ info_deaths: (msg) =>
+ for _, uname in pairs(msg.data)
+ if @player_party\member(uname)
+ tchar = @player_party\member(uname)
+ tchar\die!
+ @player_party\remove_member(tchar)
+ elseif @enemy_party\member(uname)
+ tchar = @enemy_party\member(uname)
+ tchar\die!
+ @enemy_party\remove_member(tchar)
+ info_actions: (msg) =>
+ for uname, action_name in pairs(msg.data)
+ action = ability[action_name]
+ if @player_party\member(uname)
+ action.use(@,@player_party,@player_party\member(uname))
+ ui.battle_log(string.format("%s used %s",@player_party\member(uname).class.name,action.text))
+ elseif @enemy_party\member(uname)
+ action.use(@,@enemy_party,@enemy_party\member(uname))
+ ui.battle_log(string.format("%s used %s",uname,action.text))
+ main.root\remove("infocard")
+ update: () =>
+ msg = am.eval_js("CLIENT.get()")
+ if msg != nil
+ info = am.parse_json(msg)
+ if @[info.msg]
+ @[info.msg](@,info)
+ else
+ print("Failed to find client message handler", info)
+ set_local: (player) =>
+ @localplayer = player
+ @player_party\add_member(player)
+ @localplayer\enter_room(@player_party.room)
+ set_room: (room) =>
+ @room = room
+ assert(@room, "cannot set a nil room")
+ assert(@room.load, "rooms must have a .load")
+ if @player_party.room
+ @player_party.room\unload!
+ --@node\remove(@player_party.room.node)
+ @room\load!
+ --@node\append(room.node)
+ @player_party\set_room(room)
+ if @enemy_party
+ if @enemy_party.room
+ @enemy_party.room\unload!
+ @enemy_party\set_room(@room)
+ --new_rat = Enemy(nil,char.enemies.Rat)
+ if @enemy_party
+ --@enemy_party\add_member(new_rat)
+ --@enemy_party\set_room(room)
+ @node\append(@enemy_party.rnode)
+ load: () =>
+ main.root("world_characters")\append(@node)
+
+
+mod["World"] = World
+mod["Server"] = Server
+mod
diff --git a/tools/luastr.sh b/tools/luastr.sh
new file mode 100644
index 0000000..5b50c7d
--- /dev/null
+++ b/tools/luastr.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+echo "return [=====["
+echo "$(cat $1)"
+echo "]=====]"
diff --git a/tools/recolor.sh b/tools/recolor.sh
new file mode 100644
index 0000000..7f1a41f
--- /dev/null
+++ b/tools/recolor.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+# Usage: tools/recolor.sh <input> <output>
+
+magick $1 -dither Riemersma -remap tools/remap.png $2
diff --git a/tools/remap.png b/tools/remap.png
new file mode 100644
index 0000000..d146124
--- /dev/null
+++ b/tools/remap.png
Binary files differ