From 4ec1b9bdb71c44547e90bf9de58cef953a2bc885 Mon Sep 17 00:00:00 2001 From: MrMelon Date: Sun, 12 Sep 2021 00:25:10 +0100 Subject: [PATCH] First commit --- .gitignore | 2 + LICENSE | 9 + Makefile | 26 + assets/collision/airship.json | 1 + assets/collision/mira.json | 1 + assets/collision/polus.json | 1 + assets/collision/skeld.json | 1 + gamedataprocessor.go | 157 ++++++ gamerpcprocessor.go | 101 ++++ go.mod | 26 + go.sum | 2 + keyhandler.go | 80 +++ mousehandler.go | 79 +++ movementprocessor.go | 41 ++ packetprocessor.go | 172 ++++++ project.json | 9 + renderer.go | 273 ++++++++++ settings.go | 37 ++ settingswindow.go | 225 ++++++++ src/enum/disconnectreason.go | 61 +++ src/enum/gamekeyword.go | 15 + src/enum/gameoverreason.go | 11 + src/enum/gamestate.go | 8 + src/enum/go.mod | 3 + src/enum/hat.go | 119 +++++ src/enum/killdistance.go | 16 + src/enum/limbostate.go | 7 + src/enum/map.go | 20 + src/enum/pet.go | 16 + src/enum/platform.go | 24 + src/enum/playercollision.go | 6 + src/enum/playercolor.go | 75 +++ src/enum/playerspeed.go | 6 + src/enum/quickchatmode.go | 6 + src/enum/reportoutcome.go | 9 + src/enum/reportreason.go | 8 + src/enum/rpccall.go | 36 ++ src/enum/screenstate.go | 9 + src/enum/skin.go | 23 + src/enum/spawnflag.go | 6 + src/enum/spawntype.go | 13 + src/enum/systemtype.go | 53 ++ src/enum/taskbarmode.go | 7 + src/enum/tasktypes.go | 64 +++ src/gamedata/01_data.go | 23 + src/gamedata/02_rpc.go | 501 ++++++++++++++++++ src/gamedata/04_spawn.go | 36 ++ src/gamedata/05_despawn.go | 16 + src/gamedata/06_scenechange.go | 23 + src/gamedata/07_ready.go | 16 + src/gamedata/08_changesettings.go | 5 + src/gamedata/205_clientinfo.go | 23 + src/gamedata/go.mod | 13 + src/innernetobjects/gamedata.go | 35 ++ src/innernetobjects/go.mod | 17 + src/innernetobjects/innernetobject.go | 8 + src/innernetobjects/mirashipstatus.go | 33 ++ src/innernetobjects/playercontrol.go | 32 ++ src/innernetobjects/playernetworktransform.go | 35 ++ src/innernetobjects/playerphysics.go | 9 + src/innernetobjects/polusshipstatus.go | 35 ++ src/innernetobjects/shipstatus.go | 50 ++ src/innernetobjects/skeldshipstatus.go | 34 ++ src/innernetobjects/votebansystem.go | 46 ++ src/mapdata/go.mod | 10 + src/mapdata/lobby.go | 16 + src/packets/00_hostgameC2S.go | 24 + src/packets/00_hostgameS2C.go | 19 + src/packets/01_joingameC2S.go | 19 + src/packets/01_joingameS2C.go | 19 + src/packets/01_joingameS2G.go | 25 + src/packets/02_startgameH2G.go | 19 + src/packets/03_removegameS2C.go | 22 + src/packets/04_removeplayerH2C.go | 28 + src/packets/04_removeplayerS2G.go | 31 ++ src/packets/05_gamedataall.go | 36 ++ src/packets/06_gamedatato.go | 38 ++ src/packets/07_joinedgameH2C.go | 37 ++ src/packets/08_endgameH2G.go | 27 + src/packets/09_getgamelist.go | 5 + src/packets/10_altergame.go | 27 + src/packets/11_kickplayer.go | 30 ++ src/packets/12_waitforhostC2H.go | 24 + src/packets/13_redirect.go | 26 + src/packets/14_reselectserverS2C.go | 42 ++ src/packets/16_GetGameListV2C2S.go | 29 + src/packets/16_GetGameListV2S2C.go | 30 ++ src/packets/17_reportplayerC2S.go | 27 + src/packets/17_reportplayerS2C.go | 30 ++ src/packets/gamepacket.go | 16 + src/packets/go.mod | 15 + src/protocol/gamelist.go | 60 +++ src/protocol/gameoptionsdata.go | 96 ++++ src/protocol/go.mod | 6 + src/protocol/hazelmessage.go | 59 +++ src/protocol/netcomponent.go | 29 + src/protocol/packet.go | 241 +++++++++ src/protocol/packethandler.go | 161 ++++++ src/protocol/playerdata.go | 53 ++ src/states/go.mod | 3 + src/states/skeld.go | 5 + src/systemtypes/autodoors.go | 42 ++ src/systemtypes/decon.go | 28 + src/systemtypes/doors.go | 44 ++ src/systemtypes/go.mod | 13 + src/systemtypes/hqhud.go | 31 ++ src/systemtypes/hudoverride.go | 15 + src/systemtypes/lifesupp.go | 24 + src/systemtypes/medscan.go | 21 + src/systemtypes/reactor.go | 25 + src/systemtypes/sabotage.go | 15 + src/systemtypes/securitycamera.go | 21 + src/systemtypes/switch.go | 21 + src/systemtypes/systemtype.go | 16 + src/systemtypes/userconsolepair.go | 18 + src/util/collision.go | 11 + src/util/countsetbits.go | 13 + src/util/gamecode.go | 58 ++ src/util/go.mod | 7 + src/util/lerp.go | 21 + src/util/masterserver.go | 10 + src/util/onlyletters.go | 10 + src/util/vec2.go | 10 + state.go | 111 ++++ sus.go | 295 +++++++++++ version.go | 27 + 126 files changed, 5145 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 assets/collision/airship.json create mode 100644 assets/collision/mira.json create mode 100644 assets/collision/polus.json create mode 100644 assets/collision/skeld.json create mode 100644 gamedataprocessor.go create mode 100644 gamerpcprocessor.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 keyhandler.go create mode 100644 mousehandler.go create mode 100644 movementprocessor.go create mode 100644 packetprocessor.go create mode 100644 project.json create mode 100644 renderer.go create mode 100644 settings.go create mode 100644 settingswindow.go create mode 100644 src/enum/disconnectreason.go create mode 100644 src/enum/gamekeyword.go create mode 100644 src/enum/gameoverreason.go create mode 100644 src/enum/gamestate.go create mode 100644 src/enum/go.mod create mode 100644 src/enum/hat.go create mode 100644 src/enum/killdistance.go create mode 100644 src/enum/limbostate.go create mode 100644 src/enum/map.go create mode 100644 src/enum/pet.go create mode 100644 src/enum/platform.go create mode 100644 src/enum/playercollision.go create mode 100644 src/enum/playercolor.go create mode 100644 src/enum/playerspeed.go create mode 100644 src/enum/quickchatmode.go create mode 100644 src/enum/reportoutcome.go create mode 100644 src/enum/reportreason.go create mode 100644 src/enum/rpccall.go create mode 100644 src/enum/screenstate.go create mode 100644 src/enum/skin.go create mode 100644 src/enum/spawnflag.go create mode 100644 src/enum/spawntype.go create mode 100644 src/enum/systemtype.go create mode 100644 src/enum/taskbarmode.go create mode 100644 src/enum/tasktypes.go create mode 100644 src/gamedata/01_data.go create mode 100644 src/gamedata/02_rpc.go create mode 100644 src/gamedata/04_spawn.go create mode 100644 src/gamedata/05_despawn.go create mode 100644 src/gamedata/06_scenechange.go create mode 100644 src/gamedata/07_ready.go create mode 100644 src/gamedata/08_changesettings.go create mode 100644 src/gamedata/205_clientinfo.go create mode 100644 src/gamedata/go.mod create mode 100644 src/innernetobjects/gamedata.go create mode 100644 src/innernetobjects/go.mod create mode 100644 src/innernetobjects/innernetobject.go create mode 100644 src/innernetobjects/mirashipstatus.go create mode 100644 src/innernetobjects/playercontrol.go create mode 100644 src/innernetobjects/playernetworktransform.go create mode 100644 src/innernetobjects/playerphysics.go create mode 100644 src/innernetobjects/polusshipstatus.go create mode 100644 src/innernetobjects/shipstatus.go create mode 100644 src/innernetobjects/skeldshipstatus.go create mode 100644 src/innernetobjects/votebansystem.go create mode 100644 src/mapdata/go.mod create mode 100644 src/mapdata/lobby.go create mode 100644 src/packets/00_hostgameC2S.go create mode 100644 src/packets/00_hostgameS2C.go create mode 100644 src/packets/01_joingameC2S.go create mode 100644 src/packets/01_joingameS2C.go create mode 100644 src/packets/01_joingameS2G.go create mode 100644 src/packets/02_startgameH2G.go create mode 100644 src/packets/03_removegameS2C.go create mode 100644 src/packets/04_removeplayerH2C.go create mode 100644 src/packets/04_removeplayerS2G.go create mode 100644 src/packets/05_gamedataall.go create mode 100644 src/packets/06_gamedatato.go create mode 100644 src/packets/07_joinedgameH2C.go create mode 100644 src/packets/08_endgameH2G.go create mode 100644 src/packets/09_getgamelist.go create mode 100644 src/packets/10_altergame.go create mode 100644 src/packets/11_kickplayer.go create mode 100644 src/packets/12_waitforhostC2H.go create mode 100644 src/packets/13_redirect.go create mode 100644 src/packets/14_reselectserverS2C.go create mode 100644 src/packets/16_GetGameListV2C2S.go create mode 100644 src/packets/16_GetGameListV2S2C.go create mode 100644 src/packets/17_reportplayerC2S.go create mode 100644 src/packets/17_reportplayerS2C.go create mode 100644 src/packets/gamepacket.go create mode 100644 src/packets/go.mod create mode 100644 src/protocol/gamelist.go create mode 100644 src/protocol/gameoptionsdata.go create mode 100644 src/protocol/go.mod create mode 100644 src/protocol/hazelmessage.go create mode 100644 src/protocol/netcomponent.go create mode 100644 src/protocol/packet.go create mode 100644 src/protocol/packethandler.go create mode 100644 src/protocol/playerdata.go create mode 100644 src/states/go.mod create mode 100644 src/states/skeld.go create mode 100644 src/systemtypes/autodoors.go create mode 100644 src/systemtypes/decon.go create mode 100644 src/systemtypes/doors.go create mode 100644 src/systemtypes/go.mod create mode 100644 src/systemtypes/hqhud.go create mode 100644 src/systemtypes/hudoverride.go create mode 100644 src/systemtypes/lifesupp.go create mode 100644 src/systemtypes/medscan.go create mode 100644 src/systemtypes/reactor.go create mode 100644 src/systemtypes/sabotage.go create mode 100644 src/systemtypes/securitycamera.go create mode 100644 src/systemtypes/switch.go create mode 100644 src/systemtypes/systemtype.go create mode 100644 src/systemtypes/userconsolepair.go create mode 100644 src/util/collision.go create mode 100644 src/util/countsetbits.go create mode 100644 src/util/gamecode.go create mode 100644 src/util/go.mod create mode 100644 src/util/lerp.go create mode 100644 src/util/masterserver.go create mode 100644 src/util/onlyletters.go create mode 100644 src/util/vec2.go create mode 100644 state.go create mode 100644 sus.go create mode 100644 version.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aaea3dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist/ +package/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b54e66f --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +Copyright 2020 Sean + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b82e43 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ + + +all: + make build-main + +build-main: + mkdir -p dist/ && go build -o dist/susapp . + +package: + ./scripts/package-susapp.sh + +deb: + make clean && \ + make all && \ + make package + +clean: + rm -rf dist/ && \ + rm -rf package/ + + +.PHONY: run +run: + make && \ + cd ./dist/ && \ + ./susapp diff --git a/assets/collision/airship.json b/assets/collision/airship.json new file mode 100644 index 0000000..be8788b --- /dev/null +++ b/assets/collision/airship.json @@ -0,0 +1 @@ +[[{"x":-16.03356,"y":-0.8336114},{"x":-16.42589,"y":-0.8337003},{"x":-16.42541,"y":0.9505637},{"x":-16.57034,"y":0.9829645},{"x":-16.81557,"y":1.143003},{"x":-16.95277,"y":1.327235},{"x":-17.52295,"y":1.328595},{"x":-17.7697,"y":1.2033},{"x":-19.55016,"y":1.209893},{"x":-19.76917,"y":1.341429},{"x":-20.50605,"y":1.334634},{"x":-21.49481,"y":0.9486741},{"x":-21.97912,"y":0.9304763},{"x":-22.04113,"y":0.8538507},{"x":-22.30273,"y":0.8202428},{"x":-22.61555,"y":0.7253085},{"x":-22.9622,"y":0.7579792},{"x":-23.17584,"y":0.6119347},{"x":-23.30625,"y":0.3478366},{"x":-23.789,"y":0.0429384},{"x":-24.16054,"y":-0.3017496},{"x":-24.50533,"y":-0.3955403},{"x":-24.74094,"y":-0.8068387},{"x":-24.80811,"y":-1.062746},{"x":-24.83992,"y":-1.312127},{"x":-24.80346,"y":-1.510181},{"x":-24.81113,"y":-1.915542},{"x":-24.86881,"y":-1.989878},{"x":-24.85874,"y":-2.201746},{"x":-24.83265,"y":-2.553008},{"x":-24.57797,"y":-2.659963},{"x":-24.22581,"y":-2.664566},{"x":-24.19022,"y":-2.795648},{"x":-24.39435,"y":-2.890892},{"x":-24.72721,"y":-2.980601},{"x":-24.60085,"y":-3.258168},{"x":-24.26596,"y":-3.551901},{"x":-23.75923,"y":-3.765429},{"x":-23.26645,"y":-3.895437},{"x":-22.51438,"y":-3.995637},{"x":-16.43034,"y":-3.992238},{"x":-16.44197,"y":-3.290086},{"x":-16.99006,"y":-3.25973},{"x":-17.10056,"y":-3.094273},{"x":-16.95137,"y":-2.99458},{"x":-16.42274,"y":-2.913776},{"x":-16.42371,"y":-1.629756},{"x":-16.06307,"y":-1.633793}],[{"x":-20.10272,"y":-1.295893},{"x":-20.11193,"y":-1.295943},{"x":-19.70463,"y":-1.296661},{"x":-19.60422,"y":-1.033932},{"x":-19.46632,"y":-0.9376636},{"x":-19.32085,"y":-1.019393},{"x":-19.17467,"y":-1.158321},{"x":-19.01633,"y":-1.214837},{"x":-18.67784,"y":-1.081395},{"x":-18.68177,"y":-0.9104306},{"x":-19.32391,"y":-0.7300696},{"x":-19.32608,"y":-0.5958933},{"x":-19.92985,"y":-0.5814762},{"x":-20.12269,"y":-0.8691257},{"x":-20.12657,"y":-1.30092}],[{"x":-16.3651,"y":-0.831529},{"x":-15.95491,"y":-0.8274707},{"x":-15.96028,"y":-0.3324225},{"x":-13.8947,"y":-0.3213733},{"x":-13.90263,"y":-0.1285609}],[{"x":-16.28759,"y":-1.668149},{"x":-15.94066,"y":-1.671024},{"x":-15.93988,"y":-1.829822},{"x":-11.56497,"y":-1.836438},{"x":-11.56514,"y":-2.698946}],[{"x":-12.81856,"y":-0.003014922},{"x":-12.82327,"y":-0.3569322},{"x":-8.911867,"y":-0.3470472},{"x":-8.911534,"y":-0.8289247},{"x":-8.498337,"y":-0.8312216}],[{"x":-10.38579,"y":-2.741977},{"x":-10.39086,"y":-1.834694},{"x":-8.893422,"y":-1.837674},{"x":-8.890488,"y":-1.636299},{"x":-8.533977,"y":-1.63752}],[{"x":-8.639915,"y":-7.992267},{"x":-9.020933,"y":-7.994788},{"x":-8.998542,"y":-9.290235},{"x":-9.796662,"y":-10.09782},{"x":-14.96192,"y":-10.10381},{"x":-15.59402,"y":-9.424212},{"x":-15.5827,"y":-8.194454},{"x":-14.77676,"y":-7.678015},{"x":-12.93738,"y":-7.682444},{"x":-13.9208,"y":-8.602203},{"x":-13.91713,"y":-9.233377},{"x":-13.09354,"y":-9.239123},{"x":-11.13614,"y":-7.195169},{"x":-11.13431,"y":-5.974114},{"x":-13.2091,"y":-3.545341},{"x":-13.79709,"y":-3.532378},{"x":-13.78797,"y":-4.244041},{"x":-12.73769,"y":-5.565},{"x":-14.50105,"y":-5.562207},{"x":-15.14126,"y":-4.667821},{"x":-15.15109,"y":-3.604205},{"x":-14.95765,"y":-3.48847},{"x":-14.29364,"y":-3.075033},{"x":-14.14488,"y":-2.702812},{"x":-11.56075,"y":-2.6872},{"x":-11.55719,"y":-2.134918}],[{"x":-10.38602,"y":-2.14999},{"x":-10.392,"y":-2.66634},{"x":-9.832569,"y":-2.683666},{"x":-9.014154,"y":-3.364316},{"x":-9.005865,"y":-7.186296},{"x":-8.599945,"y":-7.19381}],[{"x":-12.80776,"y":-0.1880744},{"x":-12.81205,"y":0.4562474},{"x":-12.0029,"y":0.4557033},{"x":-11.50556,"y":1.210751},{"x":-11.50788,"y":3.20061},{"x":-12.4618,"y":3.17539},{"x":-12.47126,"y":2.171422},{"x":-12.65883,"y":2.067768},{"x":-13.35524,"y":2.0438},{"x":-13.46784,"y":2.17068},{"x":-13.82986,"y":2.176549},{"x":-13.91401,"y":2.075269},{"x":-14.26823,"y":2.079014},{"x":-14.33605,"y":2.44395},{"x":-14.95626,"y":2.441928},{"x":-14.94309,"y":1.236872},{"x":-14.57808,"y":0.4582416},{"x":-13.90006,"y":0.4574582},{"x":-13.89166,"y":-0.2717739}],[{"x":-8.791619,"y":-1.640076},{"x":-8.477066,"y":-1.652369},{"x":-8.379571,"y":-1.808783},{"x":-0.445395,"y":-1.799595},{"x":-0.4410331,"y":-2.036575},{"x":-0.6242429,"y":-2.039573},{"x":-0.7431719,"y":-1.910856},{"x":-1.83415,"y":-1.89835},{"x":-1.60571,"y":-2.644397},{"x":-1.580572,"y":-3.069704},{"x":1.865649,"y":-3.065668},{"x":2.03713,"y":-2.778933},{"x":1.685836,"y":-2.576459},{"x":1.649244,"y":-2.385174},{"x":2.040159,"y":-2.118975},{"x":2.048617,"y":-1.91668},{"x":1.14473,"y":-1.913548},{"x":0.9921035,"y":-2.037229},{"x":0.8112863,"y":-2.034278},{"x":0.8117996,"y":-1.816262},{"x":2.797431,"y":-1.805758},{"x":2.803455,"y":-0.4749102},{"x":2.896795,"y":-0.3570683},{"x":3.030597,"y":-0.3568428},{"x":3.08345,"y":-0.4869919},{"x":3.083286,"y":-1.113721},{"x":4.010863,"y":-1.101774},{"x":4.008285,"y":-0.6730426},{"x":4.367944,"y":-0.6712218}],[{"x":-8.83287,"y":-0.8354387},{"x":-8.49282,"y":-0.8382605},{"x":-8.184837,"y":-0.733498},{"x":-8.186178,"y":-0.4499176},{"x":-8.452353,"y":-0.29589},{"x":-8.442046,"y":-0.03380224},{"x":-8.088207,"y":0.0967712},{"x":-7.92689,"y":0.3655184},{"x":-7.925612,"y":1.401187},{"x":-7.639612,"y":1.401898},{"x":-7.523113,"y":1.349831},{"x":-7.290526,"y":1.335646},{"x":-7.10547,"y":1.422877},{"x":-6.33165,"y":1.419261},{"x":-6.159031,"y":1.33948},{"x":-5.942391,"y":1.334746},{"x":-5.776771,"y":1.406438},{"x":-5.004841,"y":1.408416},{"x":-4.844049,"y":1.352117},{"x":-4.608784,"y":1.352113},{"x":-4.456556,"y":1.40971},{"x":-3.523595,"y":1.412577},{"x":-3.520935,"y":0.9213981},{"x":-2.465536,"y":0.8827403},{"x":-2.474633,"y":1.314879},{"x":-1.337851,"y":1.377257},{"x":-1.323959,"y":3.023837},{"x":-1.618507,"y":3.447274},{"x":-1.609065,"y":5.558088},{"x":-1.834859,"y":5.78409},{"x":-2.293582,"y":5.801613},{"x":-2.304999,"y":6.677835},{"x":-1.520619,"y":6.680501},{"x":-1.520418,"y":7.443104}],[{"x":-0.3275228,"y":7.319745},{"x":-0.3270569,"y":6.667881},{"x":0.01766863,"y":6.66899},{"x":0.01241617,"y":2.920892},{"x":0.04391556,"y":2.739981},{"x":0.03591204,"y":1.305636},{"x":0.1272773,"y":1.222615},{"x":0.2902149,"y":1.120902},{"x":0.2959781,"y":0.6275588},{"x":0.2466311,"y":0.3748338},{"x":0.4014665,"y":0.1959238},{"x":0.5784051,"y":0.08054905},{"x":0.5582557,"y":-0.1377602},{"x":0.6901353,"y":-0.2651669},{"x":0.813319,"y":-0.1825323},{"x":1.882595,"y":-0.1882137},{"x":1.886091,"y":-0.9860865},{"x":1.98844,"y":-1.07367},{"x":2.121817,"y":-0.9885357},{"x":2.079613,"y":0.3314859},{"x":4.0155,"y":0.3134927},{"x":4.017383,"y":0.1146271},{"x":4.410442,"y":0.1145072}],[{"x":-3.789423,"y":8.060939},{"x":-3.360394,"y":8.058386},{"x":-3.361936,"y":7.585318},{"x":-1.518651,"y":7.582849},{"x":-1.51629,"y":6.897281}],[{"x":-0.3276537,"y":6.740432},{"x":-0.3267135,"y":7.58643},{"x":2.348417,"y":7.593921},{"x":2.340089,"y":8.057134},{"x":2.838824,"y":8.065406}],[{"x":-3.854213,"y":8.853719},{"x":-3.356832,"y":8.854605},{"x":-3.324265,"y":9.093654},{"x":-2.802774,"y":9.424075},{"x":-1.606528,"y":9.42758},{"x":-1.398799,"y":9.331013},{"x":-1.240515,"y":9.30058},{"x":-1.124339,"y":9.321713},{"x":-0.8871441,"y":9.414958},{"x":0.2057831,"y":9.418169},{"x":0.353672,"y":9.342675},{"x":0.5148584,"y":9.281999},{"x":0.6768073,"y":9.316348},{"x":0.8391489,"y":9.421098},{"x":2.01583,"y":9.419906},{"x":2.169501,"y":9.29791},{"x":2.33303,"y":9.234224},{"x":2.33536,"y":8.86784},{"x":2.874809,"y":8.866095}],[{"x":-3.525622,"y":8.858198},{"x":-3.885725,"y":8.85726},{"x":-3.888912,"y":9.050066},{"x":-4.097179,"y":9.064512},{"x":-4.3266,"y":9.153421},{"x":-4.323749,"y":10.11809},{"x":-4.537128,"y":10.76189},{"x":-4.719747,"y":11.13734},{"x":-5.025217,"y":11.59097},{"x":-5.225776,"y":11.59822},{"x":-5.334115,"y":11.66203},{"x":-5.343767,"y":11.82},{"x":-5.692319,"y":12.23553},{"x":-6.077424,"y":12.52052},{"x":-6.623687,"y":12.75151},{"x":-6.782733,"y":12.6902},{"x":-6.89463,"y":12.7574},{"x":-6.892749,"y":12.8636},{"x":-7.074221,"y":12.98624},{"x":-7.754135,"y":13.16192},{"x":-8.523164,"y":13.23847},{"x":-8.684267,"y":13.13656},{"x":-8.85572,"y":13.14779},{"x":-8.938745,"y":13.25068},{"x":-9.333408,"y":13.24967},{"x":-10.03124,"y":13.14435},{"x":-10.7647,"y":12.90052},{"x":-10.87891,"y":12.73576},{"x":-11.00628,"y":12.7222},{"x":-11.10844,"y":12.7604},{"x":-11.46173,"y":12.59708},{"x":-11.73124,"y":12.40194},{"x":-11.7576,"y":12.16802},{"x":-11.90705,"y":12.01866},{"x":-12.1746,"y":11.93242},{"x":-12.32829,"y":11.7779},{"x":-12.35934,"y":11.59476},{"x":-12.63039,"y":11.56134},{"x":-12.83441,"y":11.41108},{"x":-13.14259,"y":10.93692},{"x":-13.32554,"y":10.52832},{"x":-13.30396,"y":9.197877},{"x":-13.46796,"y":9.05337},{"x":-13.4741,"y":8.031837},{"x":-13.42152,"y":7.49264},{"x":-13.22832,"y":6.830685},{"x":-13.00173,"y":6.34046},{"x":-12.61687,"y":5.833189},{"x":-12.38425,"y":5.733351},{"x":-12.23875,"y":5.453109},{"x":-11.69708,"y":5.035163},{"x":-11.22427,"y":4.761021},{"x":-10.90824,"y":4.712702},{"x":-10.71547,"y":4.532854},{"x":-10.38377,"y":4.426901},{"x":-9.824739,"y":4.294689},{"x":-9.326833,"y":4.237856},{"x":-8.720805,"y":4.275678},{"x":-8.27245,"y":4.235127},{"x":-7.612947,"y":4.337722},{"x":-7.018895,"y":4.504151},{"x":-6.868068,"y":4.650097},{"x":-6.766322,"y":4.68113},{"x":-6.624613,"y":4.695828},{"x":-6.114302,"y":4.944323},{"x":-5.643821,"y":5.269239},{"x":-5.340237,"y":5.5402},{"x":-5.296103,"y":5.742735},{"x":-5.100102,"y":5.787309},{"x":-4.989989,"y":5.899269},{"x":-4.74347,"y":6.223245},{"x":-4.45425,"y":6.776549},{"x":-4.261594,"y":7.296413},{"x":-4.163108,"y":7.869389},{"x":-3.914319,"y":7.86322},{"x":-3.910859,"y":8.064019},{"x":-3.560334,"y":8.060819}],[{"x":-7.714983,"y":5.43726},{"x":-7.359429,"y":5.553212},{"x":-6.982174,"y":5.757483},{"x":-6.377391,"y":6.213044},{"x":-6.001368,"y":6.623106},{"x":-5.650852,"y":7.170923}],[{"x":-8.266265,"y":7.298437},{"x":-8.267296,"y":5.851799}],[{"x":-7.57402,"y":7.917111},{"x":-6.056797,"y":7.926265},{"x":-6.136981,"y":7.756708},{"x":-7.462361,"y":7.77766},{"x":-7.554103,"y":7.916078}],[{"x":-9.918072,"y":7.925048},{"x":-11.50218,"y":7.915313},{"x":-11.36237,"y":7.781813},{"x":-10.11233,"y":7.77387},{"x":-9.910605,"y":7.927512}],[{"x":-9.312279,"y":7.313175},{"x":-9.311869,"y":5.828625}],[{"x":-11.93514,"y":7.345225},{"x":-11.71328,"y":6.926175},{"x":-11.47842,"y":6.583897},{"x":-11.19006,"y":6.266077},{"x":-10.82989,"y":5.958067},{"x":-10.4353,"y":5.706132},{"x":-10.16161,"y":5.56689},{"x":-9.851611,"y":5.456525}],[{"x":-7.526872,"y":8.955747},{"x":-6.029075,"y":8.950937}],[{"x":-5.488227,"y":9.383634},{"x":-5.570149,"y":9.376133},{"x":-5.696403,"y":9.719774},{"x":-5.939435,"y":10.11046},{"x":-6.258337,"y":10.48634},{"x":-6.576073,"y":10.77546},{"x":-6.862606,"y":10.97215},{"x":-7.260777,"y":11.2153},{"x":-7.580778,"y":11.33738},{"x":-7.754492,"y":11.54428},{"x":-7.416903,"y":11.44315},{"x":-6.986969,"y":11.22209},{"x":-6.677588,"y":11.0135},{"x":-6.294706,"y":10.71251},{"x":-6.038608,"y":10.43536},{"x":-5.839064,"y":10.15715},{"x":-5.673316,"y":9.863144},{"x":-5.548763,"y":9.577497},{"x":-5.476987,"y":9.375607}],[{"x":-8.265376,"y":9.51088},{"x":-8.263732,"y":11.1425}],[{"x":-8.886846,"y":-7.995717},{"x":-8.532104,"y":-7.997131},{"x":-8.532892,"y":-8.679031},{"x":-8.036824,"y":-8.664539},{"x":-8.043002,"y":-10.4675},{"x":-8.529308,"y":-10.46552},{"x":-8.539575,"y":-11.90405},{"x":-8.924159,"y":-11.90229}],[{"x":-8.925779,"y":-7.195223},{"x":-8.542553,"y":-7.196155},{"x":-8.525546,"y":-5.954915},{"x":-6.683332,"y":-5.956091},{"x":-6.678199,"y":-9.546827},{"x":-5.875857,"y":-9.545488},{"x":-5.882681,"y":-8.876453},{"x":-2.228813,"y":-8.873413},{"x":-2.202244,"y":-11.00626},{"x":-1.736679,"y":-11.01017},{"x":-1.748498,"y":-11.96629},{"x":-1.48267,"y":-11.96517}],[{"x":-8.88196,"y":-12.70318},{"x":-8.550611,"y":-12.70105},{"x":-8.546182,"y":-13.17776},{"x":-1.741881,"y":-13.16913},{"x":-1.748652,"y":-12.78946},{"x":-1.4775,"y":-12.79245}],[{"x":-8.643423,"y":-11.90162},{"x":-9.009799,"y":-11.90135},{"x":-9.008525,"y":-11.35979},{"x":-16.95227,"y":-11.36104},{"x":-16.95452,"y":-13.26016},{"x":-15.15803,"y":-13.26459},{"x":-15.01583,"y":-13.26782},{"x":-14.98855,"y":-13.07643},{"x":-14.6182,"y":-13.09101},{"x":-14.45284,"y":-13.14448},{"x":-14.44932,"y":-14.2475},{"x":-14.57198,"y":-14.41945},{"x":-14.59052,"y":-14.93393},{"x":-14.51265,"y":-15.13859},{"x":-14.31196,"y":-15.15483},{"x":-14.26082,"y":-15.28112},{"x":-14.2628,"y":-16.81508},{"x":-14.16203,"y":-16.92076},{"x":-13.67783,"y":-16.94882},{"x":-13.54266,"y":-16.81123},{"x":-13.53373,"y":-15.18775},{"x":-12.90455,"y":-15.18857},{"x":-12.76699,"y":-15.12105},{"x":-12.7116,"y":-14.99375},{"x":-12.71666,"y":-14.37298},{"x":-12.86688,"y":-14.27475},{"x":-12.85941,"y":-13.15348},{"x":-12.72164,"y":-13.08498},{"x":-12.31283,"y":-13.08239},{"x":-12.29272,"y":-13.24584},{"x":-10.43949,"y":-13.25609},{"x":-8.999346,"y":-13.27575},{"x":-8.994207,"y":-12.70773},{"x":-8.641171,"y":-12.70565}],[{"x":2.545268,"y":8.859646},{"x":2.923118,"y":8.85927},{"x":2.917882,"y":9.496401},{"x":3.329931,"y":9.43081},{"x":3.528259,"y":9.505051},{"x":3.661227,"y":9.701638},{"x":5.628694,"y":9.692849},{"x":5.973226,"y":9.471859},{"x":5.975547,"y":7.876566},{"x":5.758133,"y":7.731698},{"x":4.521097,"y":7.723544},{"x":4.528158,"y":6.634421},{"x":4.2855,"y":6.233686},{"x":2.927807,"y":6.228854},{"x":2.93457,"y":8.066785},{"x":2.602454,"y":8.064691}],[{"x":16.51671,"y":8.646623},{"x":16.17768,"y":8.646716},{"x":16.15307,"y":7.9775},{"x":15.05678,"y":7.974468},{"x":15.0619,"y":8.526336},{"x":14.97818,"y":8.712512},{"x":13.86187,"y":8.45585},{"x":13.80307,"y":8.290793},{"x":13.79954,"y":7.758854},{"x":11.10097,"y":7.751239},{"x":11.10399,"y":7.647718},{"x":10.50988,"y":7.646337},{"x":10.50865,"y":7.739585},{"x":9.735133,"y":7.738641},{"x":9.546408,"y":7.862723},{"x":9.543139,"y":9.509998},{"x":9.856339,"y":9.71242},{"x":13.79913,"y":9.716454},{"x":13.79992,"y":9.141828},{"x":13.85311,"y":9.046237},{"x":14.95903,"y":9.29871},{"x":15.05949,"y":9.457063},{"x":15.06446,"y":9.941607},{"x":16.16467,"y":9.946694},{"x":16.17507,"y":9.443351},{"x":16.5641,"y":9.438322}],[{"x":10.46487,"y":6.304631},{"x":14.24713,"y":6.270213},{"x":14.2531,"y":5.556904},{"x":10.48865,"y":5.568405},{"x":10.48424,"y":6.307353}],[{"x":4.084074,"y":0.1168281},{"x":4.456663,"y":0.1191483},{"x":4.456962,"y":0.5659752},{"x":5.623947,"y":0.564113},{"x":5.618522,"y":1.433488},{"x":5.475291,"y":1.438951},{"x":5.470914,"y":3.680179},{"x":6.078482,"y":3.669206},{"x":6.2293,"y":3.513},{"x":6.70134,"y":3.368061},{"x":7.000872,"y":3.33035},{"x":7.007857,"y":1.434924},{"x":6.804887,"y":1.437576},{"x":6.820045,"y":0.5788898},{"x":7.370389,"y":0.5779886},{"x":7.664962,"y":0.4793224},{"x":7.946116,"y":0.5770458},{"x":8.659927,"y":0.5758769},{"x":8.66353,"y":1.443222},{"x":8.090346,"y":1.441057},{"x":8.093046,"y":2.185557},{"x":8.451802,"y":2.308688},{"x":8.549082,"y":2.496261},{"x":9.209348,"y":2.496133},{"x":9.195152,"y":3.348684},{"x":9.377824,"y":3.368267},{"x":9.575262,"y":3.503911},{"x":9.686353,"y":3.596242},{"x":9.964178,"y":3.602835},{"x":10.14088,"y":3.492202},{"x":10.57477,"y":3.378777},{"x":10.57335,"y":2.329448},{"x":10.08278,"y":2.328432},{"x":10.08187,"y":1.43716},{"x":9.843735,"y":1.435984},{"x":9.840565,"y":0.5657735},{"x":10.48561,"y":0.5688565},{"x":10.77119,"y":0.4898677},{"x":11.07022,"y":0.5716506},{"x":11.70528,"y":0.5746858},{"x":11.71127,"y":1.425143},{"x":11.19475,"y":1.422741},{"x":11.19523,"y":2.827469},{"x":12.1708,"y":2.829023},{"x":12.1765,"y":2.457471},{"x":13.19447,"y":2.460473},{"x":13.20183,"y":1.618829},{"x":12.91092,"y":1.430835},{"x":12.9204,"y":0.5770671},{"x":13.48023,"y":0.5765182},{"x":13.81051,"y":0.4958534},{"x":14.12737,"y":0.5758836},{"x":14.74144,"y":0.5764709},{"x":14.74242,"y":1.424285},{"x":14.27012,"y":1.441725},{"x":14.29426,"y":3.498686},{"x":15.25976,"y":3.486726},{"x":15.26095,"y":2.604861},{"x":15.37234,"y":2.454789},{"x":15.99119,"y":2.45368},{"x":16.09971,"y":2.582138},{"x":16.35321,"y":2.57881},{"x":16.49044,"y":2.495174},{"x":16.71728,"y":2.417478},{"x":16.80511,"y":2.217203},{"x":16.63855,"y":2.037951},{"x":16.4264,"y":1.891837},{"x":16.33355,"y":1.675533},{"x":16.45134,"y":1.547075},{"x":16.3927,"y":1.432017},{"x":15.95241,"y":1.437393},{"x":15.94727,"y":0.5693587},{"x":17.25088,"y":0.572821},{"x":17.24765,"y":0.1191841},{"x":17.65965,"y":0.1213853}],[{"x":4.130811,"y":-0.6807295},{"x":4.454815,"y":-0.6857201},{"x":4.446455,"y":-1.000261},{"x":5.622482,"y":-1.000065},{"x":5.635356,"y":-1.904223},{"x":5.450109,"y":-2.019036},{"x":5.446249,"y":-4.150476},{"x":7.442302,"y":-4.147058},{"x":7.43352,"y":-1.880722},{"x":6.792991,"y":-1.878254},{"x":6.800576,"y":-1.000716},{"x":11.70343,"y":-0.9970887},{"x":11.71421,"y":-1.922409},{"x":11.53619,"y":-2.03492},{"x":11.53099,"y":-4.151629},{"x":12.41465,"y":-4.159852},{"x":12.4172,"y":-3.870794},{"x":13.34505,"y":-3.873369}],[{"x":13.23814,"y":-3.873506},{"x":13.51192,"y":-3.867576},{"x":13.51274,"y":-1.877506},{"x":12.90263,"y":-1.873911},{"x":12.91444,"y":-0.996398},{"x":17.25184,"y":-0.9903842},{"x":17.24835,"y":-0.6918265},{"x":17.57413,"y":-0.6859772}],[{"x":17.3673,"y":-0.6723289},{"x":17.63895,"y":-0.6722896},{"x":17.72919,"y":-0.7998894},{"x":17.71904,"y":-0.9896551},{"x":17.92686,"y":-0.985401},{"x":17.94152,"y":-1.267054},{"x":19.43565,"y":-1.280648},{"x":19.43298,"y":-0.780601},{"x":20.35838,"y":-0.7849787},{"x":20.69626,"y":-1.048021},{"x":20.6809,"y":-2.50104},{"x":24.59558,"y":-2.506082},{"x":24.59263,"y":-1.01672},{"x":24.84958,"y":-0.7467367},{"x":24.85712,"y":-0.008633208},{"x":25.21321,"y":-0.008343983}],[{"x":17.31441,"y":0.1223279},{"x":17.71636,"y":0.1285996},{"x":17.71715,"y":0.4492794},{"x":17.8786,"y":0.5616432},{"x":17.87829,"y":3.728341},{"x":16.89021,"y":3.721987},{"x":16.89806,"y":5.255869},{"x":18.35498,"y":5.251333},{"x":18.31859,"y":4.547088},{"x":17.42471,"y":4.537665},{"x":17.42406,"y":4.373316},{"x":18.37393,"y":4.389128},{"x":18.61214,"y":4.567176},{"x":19.1179,"y":4.830626},{"x":19.26223,"y":5.192926},{"x":19.26349,"y":6.04189}],[{"x":20.46556,"y":6.019821},{"x":20.46805,"y":5.180863},{"x":20.61453,"y":5.050004},{"x":20.72546,"y":4.974508},{"x":20.97356,"y":4.927974},{"x":20.99036,"y":4.217092},{"x":19.52394,"y":4.219594},{"x":19.52042,"y":3.914912},{"x":19.05264,"y":3.909714},{"x":19.05498,"y":1.085529},{"x":19.56385,"y":1.093853},{"x":19.66157,"y":1.037768},{"x":20.0708,"y":1.035189},{"x":20.0787,"y":0.8743849},{"x":23.23643,"y":0.8599467},{"x":23.45657,"y":1.109525},{"x":23.45831,"y":2.095474},{"x":20.36347,"y":2.091295},{"x":20.36407,"y":3.033036},{"x":24.71535,"y":3.02161},{"x":24.73088,"y":2.152309},{"x":24.59524,"y":2.061545},{"x":24.59386,"y":1.029787},{"x":24.81646,"y":1.031042},{"x":24.87513,"y":0.784597},{"x":25.24172,"y":0.7875271}],[{"x":16.22179,"y":9.449925},{"x":16.592,"y":9.438324},{"x":16.59107,"y":9.799691},{"x":16.87871,"y":9.794971},{"x":16.92276,"y":10.31483},{"x":17.01632,"y":10.65433},{"x":17.21138,"y":11.11462},{"x":17.39006,"y":11.29192},{"x":18.45865,"y":10.58858},{"x":18.91402,"y":10.84526},{"x":18.93848,"y":11.36767},{"x":18.21631,"y":11.9339},{"x":18.38113,"y":12.13898},{"x":18.97462,"y":12.31696},{"x":19.51716,"y":12.42964},{"x":19.68595,"y":12.28891},{"x":19.81967,"y":12.22006},{"x":19.98791,"y":12.2399},{"x":20.263,"y":12.43252},{"x":20.84539,"y":12.33231},{"x":21.57466,"y":11.97358},{"x":20.841,"y":11.53123},{"x":20.82343,"y":10.82067},{"x":21.34241,"y":10.61472},{"x":22.39914,"y":11.30403},{"x":22.61796,"y":10.99029},{"x":22.75916,"y":10.68193},{"x":22.85105,"y":10.24614},{"x":22.90274,"y":9.794479},{"x":23.72962,"y":9.795464},{"x":23.7276,"y":9.4373},{"x":24.12314,"y":9.44237}],[{"x":16.26713,"y":8.641379},{"x":16.5984,"y":8.641698},{"x":16.59088,"y":8.362716},{"x":16.85259,"y":8.350898},{"x":16.90724,"y":7.96319},{"x":17.04374,"y":7.638478},{"x":17.12429,"y":7.4979},{"x":17.2701,"y":7.363121},{"x":18.39698,"y":7.98732},{"x":18.75015,"y":7.645493},{"x":18.76575,"y":6.911261},{"x":18.09283,"y":6.498992},{"x":18.49096,"y":6.280388},{"x":18.97352,"y":6.11521},{"x":19.27451,"y":6.077439},{"x":19.27451,"y":5.385819}],[{"x":20.44999,"y":5.35021},{"x":20.45315,"y":6.072403},{"x":20.69072,"y":6.095522},{"x":21.06207,"y":6.208974},{"x":21.33657,"y":6.313317},{"x":21.72756,"y":6.524127},{"x":21.00678,"y":6.97053},{"x":21.03955,"y":7.697658},{"x":21.39902,"y":8.000195},{"x":22.51733,"y":7.360611},{"x":22.68085,"y":7.515319},{"x":22.83217,"y":7.828372},{"x":23.71497,"y":7.834609},{"x":23.71838,"y":8.643151},{"x":24.11422,"y":8.641211}],[{"x":18.82711,"y":9.06955},{"x":19.10436,"y":9.007864},{"x":19.47142,"y":8.891902},{"x":19.89151,"y":8.762231},{"x":20.26792,"y":8.893024},{"x":20.68389,"y":9.02286},{"x":20.94927,"y":9.026827},{"x":21.07219,"y":9.240953},{"x":20.87274,"y":9.32188},{"x":20.62355,"y":9.315363},{"x":20.16726,"y":9.579005},{"x":19.88674,"y":9.651817},{"x":19.60579,"y":9.591384},{"x":19.38572,"y":9.449701},{"x":19.14858,"y":9.328643},{"x":18.9145,"y":9.303246},{"x":18.71852,"y":9.193658},{"x":18.82066,"y":9.081324}],[{"x":23.83367,"y":8.656083},{"x":24.17862,"y":8.646399},{"x":24.18925,"y":5.929824},{"x":24.41528,"y":5.925169},{"x":24.55492,"y":5.789554},{"x":24.56074,"y":5.577405},{"x":24.51148,"y":4.556463},{"x":27.55369,"y":4.553153},{"x":27.54975,"y":4.833504},{"x":28.04312,"y":4.831136},{"x":28.04484,"y":4.660454},{"x":33.23555,"y":4.646424},{"x":33.23402,"y":3.796052}],[{"x":23.80908,"y":9.448383},{"x":24.18992,"y":9.442202},{"x":24.20322,"y":9.769654},{"x":24.34319,"y":9.795302},{"x":24.62013,"y":9.813514},{"x":24.78232,"y":9.883697},{"x":24.94532,"y":10.0641},{"x":27.52629,"y":10.05588},{"x":27.55009,"y":5.704739},{"x":28.04051,"y":5.702101},{"x":28.32124,"y":5.717602},{"x":28.4997,"y":5.997903},{"x":28.82189,"y":5.999148},{"x":28.81489,"y":6.844117},{"x":28.65712,"y":6.838084},{"x":28.66715,"y":7.54099},{"x":28.78553,"y":7.491501},{"x":28.9524,"y":7.492608},{"x":29.12366,"y":7.392945},{"x":29.27793,"y":7.40689},{"x":29.4008,"y":7.51863},{"x":29.46143,"y":7.724545},{"x":29.80367,"y":7.727155},{"x":29.81748,"y":6.834863},{"x":29.68183,"y":6.833421},{"x":29.68577,"y":5.984917},{"x":30.36588,"y":5.983824},{"x":30.36858,"y":6.826981},{"x":30.19855,"y":6.834031},{"x":30.20333,"y":7.760964},{"x":30.51846,"y":7.755438},{"x":30.58057,"y":7.550571},{"x":30.70789,"y":7.402945},{"x":30.82529,"y":7.387939},{"x":30.94207,"y":7.426145},{"x":31.06532,"y":7.577645},{"x":31.233,"y":7.540353},{"x":31.36461,"y":7.477885},{"x":31.36016,"y":7.455339},{"x":31.35309,"y":6.832047},{"x":31.22327,"y":6.82622},{"x":31.22967,"y":5.980998},{"x":31.85829,"y":5.98956},{"x":31.85102,"y":6.834255},{"x":31.67953,"y":6.837724},{"x":31.69362,"y":7.573009},{"x":31.85805,"y":7.522245},{"x":32.06666,"y":7.54718},{"x":32.17828,"y":7.454453},{"x":32.28524,"y":7.431738},{"x":32.48058,"y":7.493668},{"x":32.56321,"y":7.724831},{"x":32.8264,"y":7.720573},{"x":32.8418,"y":6.838792},{"x":32.72615,"y":6.834598},{"x":32.72024,"y":6.003712},{"x":33.34249,"y":5.998977},{"x":33.34361,"y":6.840816},{"x":33.18965,"y":6.830325},{"x":33.19407,"y":7.740204},{"x":33.44451,"y":7.748093},{"x":33.51972,"y":7.59394},{"x":33.61355,"y":7.471326},{"x":33.7496,"y":7.445954},{"x":33.91323,"y":7.541088},{"x":34.00943,"y":7.658882},{"x":34.16102,"y":7.554367},{"x":34.34412,"y":7.493284},{"x":34.33792,"y":6.842475},{"x":34.2001,"y":6.837014},{"x":34.20088,"y":6.001976},{"x":35.00393,"y":5.99776},{"x":34.99923,"y":4.660586},{"x":34.42128,"y":4.655818},{"x":34.43039,"y":3.775892}],[{"x":24.96806,"y":-0.008893895},{"x":25.29822,"y":-0.008040488},{"x":25.29513,"y":-0.4554807},{"x":26.51419,"y":-0.452412},{"x":26.54314,"y":-0.5716544},{"x":26.82701,"y":-0.5710855},{"x":26.82866,"y":1.079186},{"x":26.5874,"y":1.079621},{"x":26.53876,"y":1.007093},{"x":25.29172,"y":1.012162},{"x":25.30005,"y":0.7960464},{"x":24.92213,"y":0.7961385}],[{"x":29.96085,"y":-1.266877},{"x":29.58326,"y":-1.264201},{"x":29.58097,"y":-1.022481},{"x":28.34134,"y":-1.014801},{"x":28.25593,"y":-0.9411207},{"x":28.02172,"y":-0.9342293},{"x":28.01121,"y":-2.637663},{"x":28.2767,"y":-2.634093},{"x":28.30942,"y":-2.505496},{"x":29.58003,"y":-2.49714},{"x":29.5684,"y":-2.078724},{"x":29.93364,"y":-2.080135}],[{"x":4.370167,"y":-12.81249},{"x":4.710572,"y":-12.81282},{"x":4.707167,"y":-13.11517},{"x":6.50629,"y":-13.1103},{"x":6.495592,"y":-14.29286},{"x":5.247363,"y":-14.29567},{"x":5.243162,"y":-15.08895},{"x":8.385489,"y":-15.08733},{"x":9.752759,"y":-15.6652},{"x":9.775661,"y":-16.81787},{"x":10.7572,"y":-16.81301},{"x":10.75556,"y":-14.82494},{"x":9.504161,"y":-14.81039},{"x":8.373489,"y":-14.31838},{"x":7.676992,"y":-14.3109},{"x":7.678667,"y":-13.10184},{"x":9.33233,"y":-13.10819},{"x":9.337194,"y":-10.93457},{"x":10.69647,"y":-10.93515},{"x":10.69263,"y":-9.326107},{"x":11.43536,"y":-9.320176},{"x":11.43323,"y":-9.152807},{"x":11.98803,"y":-9.151772}],[{"x":4.32737,"y":-12.0095},{"x":4.727947,"y":-12.01022},{"x":4.69901,"y":-9.960796},{"x":7.284973,"y":-9.969092},{"x":7.377474,"y":-10.16938},{"x":7.53201,"y":-10.24158},{"x":7.732461,"y":-10.29306},{"x":7.831082,"y":-10.54931},{"x":8.012747,"y":-10.64477},{"x":8.200947,"y":-10.63187},{"x":8.325757,"y":-10.46359},{"x":8.447688,"y":-10.25914},{"x":8.649251,"y":-10.20599},{"x":8.840059,"y":-10.02825},{"x":9.179604,"y":-10.00085},{"x":9.375246,"y":-10.03905},{"x":9.622617,"y":-10.0322},{"x":9.596861,"y":-7.827077},{"x":9.733359,"y":-7.821468},{"x":9.726049,"y":-6.921235},{"x":9.193755,"y":-6.920204},{"x":9.201669,"y":-5.847182},{"x":11.45066,"y":-5.838676},{"x":11.45749,"y":-6.005068},{"x":11.7724,"y":-6.005147}],[{"x":11.91983,"y":-6.651103},{"x":11.43875,"y":-6.649197},{"x":11.43361,"y":-6.921025},{"x":10.89167,"y":-6.927078},{"x":10.89565,"y":-7.792764},{"x":11.38012,"y":-8.026577},{"x":11.39481,"y":-8.504828},{"x":11.8462,"y":-8.518177}],[{"x":7.803439,"y":-11.81633},{"x":7.972384,"y":-11.90594},{"x":8.191087,"y":-11.92311},{"x":8.41914,"y":-11.85654},{"x":8.559229,"y":-11.81633}],[{"x":29.63205,"y":-1.270287},{"x":30.04259,"y":-1.265967},{"x":30.04222,"y":-0.9589438},{"x":31.95887,"y":-0.9821733},{"x":31.98013,"y":-2.309789},{"x":32.03697,"y":-2.419954},{"x":32.13166,"y":-2.419305},{"x":32.19104,"y":-2.274939},{"x":32.18625,"y":-1.397807},{"x":32.65844,"y":-1.308104},{"x":32.80399,"y":-1.168286},{"x":32.81144,"y":-0.7221938},{"x":32.57572,"y":0.3165706},{"x":32.51032,"y":0.7073128},{"x":31.13059,"y":0.8734886},{"x":30.81089,"y":1.259735},{"x":30.03755,"y":1.423906},{"x":30.02572,"y":1.989071},{"x":30.85481,"y":1.985681},{"x":31.07541,"y":1.916072},{"x":31.31409,"y":1.983877},{"x":32.35394,"y":1.979551},{"x":32.38552,"y":2.087662},{"x":33.20528,"y":2.095015},{"x":33.20879,"y":2.99391},{"x":33.11339,"y":3.133411},{"x":33.0251,"y":3.022423},{"x":33.01538,"y":2.703999},{"x":32.41338,"y":2.689663},{"x":32.41671,"y":3.773757},{"x":33.23489,"y":3.781551},{"x":33.23623,"y":4.150618}],[{"x":34.41613,"y":4.105896},{"x":34.42494,"y":3.77648},{"x":35.55035,"y":3.76019},{"x":35.56692,"y":2.703616},{"x":34.76677,"y":2.709988},{"x":34.7392,"y":3.057398},{"x":34.65187,"y":3.164597},{"x":34.55305,"y":3.062527},{"x":34.52196,"y":2.097046},{"x":35.55614,"y":2.10332},{"x":35.57702,"y":2.011288},{"x":36.37502,"y":1.993334},{"x":36.48242,"y":1.913596},{"x":36.60581,"y":1.895214},{"x":36.76078,"y":1.909804},{"x":36.77514,"y":1.59312},{"x":37.71261,"y":1.590941},{"x":37.71786,"y":1.509012},{"x":39.59365,"y":1.496941},{"x":39.58691,"y":1.273642},{"x":39.76616,"y":1.277226},{"x":39.76548,"y":-0.3858772},{"x":39.59784,"y":-0.3800054},{"x":39.60247,"y":-2.166032},{"x":39.78012,"y":-2.162822},{"x":39.77282,"y":-3.743397},{"x":39.56306,"y":-3.749745},{"x":39.55266,"y":-4.141446},{"x":38.96266,"y":-3.7969},{"x":38.1321,"y":-4.17856},{"x":36.65881,"y":-4.175798},{"x":36.66886,"y":-4.021609},{"x":35.60856,"y":-3.336062},{"x":35.88046,"y":-3.101389},{"x":36.27645,"y":-3.269305},{"x":36.76783,"y":-2.843386},{"x":36.7737,"y":-2.629478},{"x":37.73983,"y":-2.600035},{"x":37.82119,"y":-2.467725},{"x":38.26999,"y":-2.494005},{"x":38.56419,"y":-2.176709},{"x":38.5711,"y":-1.834906},{"x":37.71861,"y":-1.686539},{"x":37.5928,"y":-1.414512},{"x":36.17735,"y":-1.44299},{"x":36.04073,"y":-1.527819},{"x":35.52038,"y":-1.306454},{"x":34.1306,"y":-2.346388},{"x":34.1545,"y":-2.921303},{"x":33.87265,"y":-2.9713},{"x":33.64423,"y":-3.263165},{"x":33.55372,"y":-3.504774},{"x":33.66428,"y":-3.626854},{"x":33.96923,"y":-3.622187},{"x":33.98045,"y":-3.862042},{"x":33.71236,"y":-4.074553},{"x":33.69054,"y":-4.181582},{"x":33.13437,"y":-4.17722},{"x":33.13283,"y":-4.952418}],[{"x":29.67477,"y":-2.080447},{"x":30.03953,"y":-2.077792},{"x":30.04152,"y":-2.409874},{"x":31.00941,"y":-2.42762},{"x":31.01302,"y":-1.710206},{"x":31.095,"y":-1.632653},{"x":31.22884,"y":-1.758518},{"x":31.21152,"y":-3.011549},{"x":30.011,"y":-3.006915},{"x":30.02134,"y":-4.181694},{"x":31.95038,"y":-4.181936},{"x":31.94147,"y":-4.982609}],[{"x":11.66818,"y":-6.010667},{"x":12.04166,"y":-6.007255},{"x":12.35067,"y":-5.906975},{"x":12.48136,"y":-5.758442},{"x":12.55682,"y":-5.650322},{"x":12.55944,"y":-5.534685},{"x":13.19915,"y":-5.538996},{"x":13.21112,"y":-5.651604},{"x":13.95297,"y":-5.650913},{"x":14.04543,"y":-5.825204},{"x":14.23148,"y":-5.935197},{"x":14.44239,"y":-6.021726},{"x":15.01721,"y":-6.018828},{"x":15.01684,"y":-5.70316},{"x":15.28448,"y":-5.703488},{"x":15.28519,"y":-5.811196},{"x":17.15269,"y":-5.803948},{"x":17.28468,"y":-5.882973},{"x":17.5261,"y":-6.025017},{"x":18.10297,"y":-6.022561},{"x":18.0975,"y":-5.648492},{"x":18.7179,"y":-5.646848},{"x":18.70556,"y":-4.48939},{"x":17.24664,"y":-4.493034},{"x":17.25615,"y":-4.160796},{"x":17.94451,"y":-3.686165},{"x":20.40288,"y":-3.68711},{"x":20.3946,"y":-4.502329},{"x":19.90919,"y":-4.501302},{"x":19.93807,"y":-5.657718},{"x":20.19437,"y":-5.860076},{"x":20.4719,"y":-5.860795},{"x":20.48564,"y":-6.912728},{"x":19.93501,"y":-6.927544},{"x":19.9352,"y":-8.054311},{"x":21.10738,"y":-8.076041},{"x":21.10897,"y":-8.340407},{"x":21.48705,"y":-8.339218}],[{"x":11.62584,"y":-6.63205},{"x":12.04062,"y":-6.632315},{"x":12.03518,"y":-6.918284},{"x":12.6667,"y":-6.923772},{"x":12.66561,"y":-8.04015},{"x":12.04936,"y":-8.049082},{"x":12.04692,"y":-8.509334},{"x":11.50758,"y":-8.515589}],[{"x":11.53321,"y":-9.143434},{"x":12.03702,"y":-9.139459},{"x":12.03226,"y":-9.355042},{"x":12.561,"y":-9.349961},{"x":12.66747,"y":-9.301188},{"x":12.73792,"y":-9.301228},{"x":12.79714,"y":-9.398258},{"x":12.76946,"y":-11.04868},{"x":12.83964,"y":-11.21926},{"x":13.16295,"y":-11.49791},{"x":15.06858,"y":-11.51625},{"x":15.15434,"y":-11.73109},{"x":17.51911,"y":-11.74314},{"x":17.56006,"y":-11.58142},{"x":18.07401,"y":-11.58419},{"x":18.11588,"y":-11.73979},{"x":20.48231,"y":-11.73459},{"x":20.47447,"y":-10.47591},{"x":19.93372,"y":-10.47463},{"x":19.9336,"y":-9.305803},{"x":21.08979,"y":-9.313205},{"x":21.13617,"y":-9.166745},{"x":21.4399,"y":-9.133141}],[{"x":13.89004,"y":-6.912827},{"x":14.43694,"y":-6.909325},{"x":14.47433,"y":-6.679531},{"x":14.99404,"y":-6.673645},{"x":15.03087,"y":-6.902806},{"x":15.71456,"y":-6.904119},{"x":15.69517,"y":-8.065161},{"x":15.01492,"y":-8.067219},{"x":15.00838,"y":-8.476792},{"x":14.43446,"y":-8.486504},{"x":14.3189,"y":-8.359094},{"x":14.1006,"y":-8.3048},{"x":14.01884,"y":-8.088633},{"x":13.90464,"y":-8.079865},{"x":13.88788,"y":-6.927657}],[{"x":13.88314,"y":-6.910269},{"x":14.43694,"y":-6.909325},{"x":14.48723,"y":-6.675228},{"x":15.01555,"y":-6.673645},{"x":15.03087,"y":-6.902806},{"x":15.63477,"y":-6.919079},{"x":15.63533,"y":-8.050201},{"x":15.04115,"y":-8.071589},{"x":15.05208,"y":-8.485534},{"x":14.43446,"y":-8.486504},{"x":14.42776,"y":-8.062546},{"x":13.89032,"y":-8.043935},{"x":13.88341,"y":-6.905281}],[{"x":13.87022,"y":-6.909733},{"x":14.46705,"y":-6.913627},{"x":14.49499,"y":-6.745507},{"x":15.01125,"y":-6.744769},{"x":15.03087,"y":-6.902805},{"x":15.62678,"y":-6.914086},{"x":15.62759,"y":-8.054941},{"x":15.44877,"y":-8.071518},{"x":15.36105,"y":-8.282927},{"x":15.18491,"y":-8.326846},{"x":15.05208,"y":-8.485534},{"x":14.43446,"y":-8.486504},{"x":14.42776,"y":-8.062546},{"x":13.89279,"y":-8.039896},{"x":13.88036,"y":-6.904354}],[{"x":15.68829,"y":-9.332767},{"x":15.69585,"y":-10.46734},{"x":15.44886,"y":-10.46865},{"x":15.36264,"y":-10.70482},{"x":15.11582,"y":-10.87538},{"x":14.09668,"y":-10.84296},{"x":13.82293,"y":-10.68182},{"x":13.72097,"y":-10.52894},{"x":13.70159,"y":-9.34051},{"x":13.76106,"y":-9.264834},{"x":13.86623,"y":-9.280823},{"x":13.92744,"y":-9.325546},{"x":14.4407,"y":-9.326488},{"x":14.48466,"y":-9.16911},{"x":14.9952,"y":-9.160357},{"x":15.02256,"y":-9.327901},{"x":15.69389,"y":-9.336746}],[{"x":21.1508,"y":-8.346367},{"x":21.53413,"y":-8.347633},{"x":21.51758,"y":-6.341946},{"x":21.68291,"y":-6.316859},{"x":21.80279,"y":-6.191787},{"x":21.77194,"y":-5.863787},{"x":22.00528,"y":-5.733915},{"x":22.24481,"y":-5.835661},{"x":22.52473,"y":-5.634049},{"x":22.3841,"y":-5.348256},{"x":22.64115,"y":-5.3192},{"x":22.82317,"y":-5.09792},{"x":26.05718,"y":-5.102329},{"x":26.13982,"y":-5.173426},{"x":26.26974,"y":-5.247285},{"x":26.391,"y":-5.441343},{"x":26.7892,"y":-5.445518},{"x":26.79752,"y":-5.072999},{"x":27.59477,"y":-5.081031},{"x":27.73421,"y":-5.213257},{"x":27.85027,"y":-5.249107},{"x":29.97207,"y":-5.241584},{"x":30.18326,"y":-5.098794},{"x":30.96158,"y":-5.085246},{"x":31.06723,"y":-5.176783},{"x":31.18582,"y":-5.216821},{"x":31.31787,"y":-5.174421},{"x":31.41771,"y":-5.086376},{"x":31.95201,"y":-5.083991},{"x":31.94058,"y":-4.486775}],[{"x":21.19219,"y":-9.143792},{"x":21.5288,"y":-9.140752},{"x":21.52507,"y":-9.744277},{"x":22.34755,"y":-10.59708},{"x":25.82591,"y":-10.59993},{"x":26.38422,"y":-9.92147},{"x":26.37686,"y":-9.360614},{"x":26.01472,"y":-9.286954},{"x":25.78516,"y":-9.140631},{"x":25.78226,"y":-8.225286},{"x":24.68164,"y":-8.254874},{"x":24.68,"y":-9.103557},{"x":24.51435,"y":-9.20449},{"x":24.13246,"y":-9.212402},{"x":23.95899,"y":-9.035179},{"x":23.94953,"y":-8.224118},{"x":23.61876,"y":-7.977326},{"x":23.6324,"y":-6.948333},{"x":23.96543,"y":-6.721925},{"x":26.33543,"y":-6.723498},{"x":26.33204,"y":-6.314115},{"x":26.79491,"y":-6.313346},{"x":26.78657,"y":-6.72128},{"x":27.36106,"y":-6.721763},{"x":27.37912,"y":-6.894475},{"x":28.02413,"y":-6.899055},{"x":28.30912,"y":-7.137161},{"x":29.87564,"y":-7.137565},{"x":30.20742,"y":-6.86943},{"x":30.79156,"y":-6.84772},{"x":30.82874,"y":-6.585979},{"x":33.31768,"y":-6.595332},{"x":33.32219,"y":-5.098347},{"x":33.1452,"y":-5.106394},{"x":33.13522,"y":-4.403719}],[{"x":3.911768,"y":13.85266},{"x":3.904901,"y":14.37323},{"x":3.055537,"y":14.36932},{"x":3.055922,"y":16.17195},{"x":5.002434,"y":16.1563},{"x":5.19547,"y":16.04318},{"x":5.425587,"y":16.00746},{"x":5.737369,"y":16.10522},{"x":5.825475,"y":16.15562},{"x":6.264071,"y":16.14885},{"x":6.264014,"y":15.87451},{"x":7.026298,"y":15.87314},{"x":7.040138,"y":15.64909},{"x":7.854608,"y":15.6553},{"x":7.853753,"y":16.15864},{"x":14.36638,"y":16.14839},{"x":14.36831,"y":15.49349},{"x":14.80551,"y":15.48425},{"x":14.82727,"y":15.85531},{"x":15.69049,"y":15.86464},{"x":15.83488,"y":15.94158},{"x":15.87892,"y":16.13076},{"x":16.50696,"y":16.14207},{"x":16.51411,"y":15.76042},{"x":17.33245,"y":15.75774},{"x":17.34869,"y":15.27619},{"x":17.49837,"y":15.2232},{"x":17.49736,"y":14.87859},{"x":17.37148,"y":14.82247},{"x":17.37835,"y":14.2142},{"x":14.79822,"y":14.2169},{"x":14.79074,"y":14.54793},{"x":14.37511,"y":14.54386},{"x":14.37879,"y":13.67128},{"x":7.868568,"y":13.67035},{"x":7.868159,"y":14.25339},{"x":7.039716,"y":14.26473},{"x":7.038588,"y":13.67892},{"x":6.098576,"y":13.67701},{"x":6.085096,"y":14.3654},{"x":5.136877,"y":14.35259},{"x":5.132302,"y":13.84139},{"x":3.901567,"y":13.84832}],[{"x":8.585021,"y":14.63922},{"x":8.585626,"y":15.49938},{"x":8.980776,"y":15.56786},{"x":9.390569,"y":15.46082},{"x":9.824985,"y":15.58001},{"x":10.2297,"y":15.4572},{"x":10.628,"y":15.58025},{"x":11.00074,"y":15.44356},{"x":11.37054,"y":15.56585},{"x":11.83204,"y":15.45158},{"x":12.23334,"y":15.59741},{"x":12.63161,"y":15.43584},{"x":13.01441,"y":15.61513},{"x":13.26388,"y":15.59196},{"x":13.4397,"y":15.47673},{"x":13.45238,"y":14.58945},{"x":13.17107,"y":14.44966},{"x":12.91651,"y":14.45591},{"x":12.6266,"y":14.67003},{"x":12.24875,"y":14.45107},{"x":11.80452,"y":14.66617},{"x":11.39094,"y":14.45237},{"x":10.99775,"y":14.66232},{"x":10.60459,"y":14.45356},{"x":10.21147,"y":14.70947},{"x":9.833596,"y":14.45473},{"x":9.404706,"y":14.68518},{"x":8.991151,"y":14.45601},{"x":8.695064,"y":14.45646},{"x":8.588435,"y":14.64316}],[{"x":-1.65789,"y":-12.76731},{"x":-1.316503,"y":-12.76596},{"x":-1.316166,"y":-13.14125},{"x":4.247796,"y":-13.1429},{"x":4.249872,"y":-12.81367},{"x":4.588986,"y":-12.81202}],[{"x":-1.701628,"y":-11.95378},{"x":-1.312271,"y":-11.95691},{"x":-1.250281,"y":-11.7014},{"x":4.178205,"y":-11.70233},{"x":4.203038,"y":-12.00892},{"x":4.617874,"y":-12.00456}],[{"x":-9.862005,"y":-3.885463},{"x":-11.53702,"y":-3.885463},{"x":-11.53702,"y":-4.412988},{"x":-9.862005,"y":-4.412988},{"x":-9.862005,"y":-3.885463}],[{"x":-9.862005,"y":-8.798404},{"x":-11.53702,"y":-8.798404},{"x":-11.53702,"y":-9.311112},{"x":-9.862005,"y":-9.311112},{"x":-9.862005,"y":-8.798404}],[{"x":-9.750773,"y":6.649997},{"x":-10.23127,"y":6.649997},{"x":-10.23127,"y":6.248957},{"x":-9.750773,"y":6.248957},{"x":-9.750773,"y":6.649997}],[{"x":-10.66876,"y":7.337173},{"x":-11.14925,"y":7.337173},{"x":-11.14925,"y":6.93937},{"x":-10.66876,"y":6.93937},{"x":-10.66876,"y":7.337173}],[{"x":-3.049332,"y":-10.10524},{"x":-5.352185,"y":-10.10524},{"x":-5.352185,"y":-10.759},{"x":-3.049332,"y":-10.759},{"x":-3.049332,"y":-10.10524}],[{"x":-3.049332,"y":-11.89438},{"x":-5.352185,"y":-11.89438},{"x":-5.352185,"y":-12.515},{"x":-3.049332,"y":-12.515},{"x":-3.049332,"y":-11.89438}],[{"x":23.09994,"y":0.06535864},{"x":20.56582,"y":0.06535864},{"x":20.56582,"y":-0.1327237},{"x":23.09994,"y":-0.1327237},{"x":23.09994,"y":0.06535864}],[{"x":23.4185,"y":-1.03898},{"x":21.84005,"y":-1.03898},{"x":21.84005,"y":-1.916657},{"x":23.4185,"y":-1.916657},{"x":23.4185,"y":-1.03898}],[{"x":26.48842,"y":9.075023},{"x":25.31991,"y":9.075023},{"x":25.31991,"y":7.648748},{"x":26.48842,"y":7.648748},{"x":26.48842,"y":9.075023}],[{"x":26.48842,"y":6.588984},{"x":25.31991,"y":6.588984},{"x":25.31991,"y":5.19517},{"x":26.48842,"y":5.19517},{"x":26.48842,"y":6.588984}],[{"x":6.719895,"y":-11.36304},{"x":5.47974,"y":-11.36304},{"x":5.47974,"y":-11.81518},{"x":6.719895,"y":-11.81518},{"x":6.719895,"y":-11.36304}],[{"x":8.738053,"y":-11.35507},{"x":7.510914,"y":-11.35507},{"x":7.510914,"y":-11.81518},{"x":8.738053,"y":-11.81518},{"x":8.738053,"y":-11.35507}],[{"x":-0.3291866,"y":0},{"x":0.3291866,"y":0.3291866},{"x":0.6583732,"y":0},{"x":0.3291866,"y":0.3291866},{"x":-0.6583732,"y":0}],[{"x":-0.2542553,"y":0},{"x":0.2542553,"y":0.2542553},{"x":0.5085106,"y":0},{"x":0.2542553,"y":0.2542553},{"x":-0.5085106,"y":0}],[{"x":13.24433,"y":-4.499919},{"x":13.24878,"y":-5.010986},{"x":14.49186,"y":-5.016655},{"x":14.49051,"y":-5.261805},{"x":14.98502,"y":-5.262676},{"x":14.98584,"y":-5.009169},{"x":15.32951,"y":-5.008646},{"x":15.33351,"y":-5.205608},{"x":16.98253,"y":-5.187002},{"x":16.9779,"y":-5.00614},{"x":17.56651,"y":-5.005245},{"x":17.57601,"y":-5.257058},{"x":18.06361,"y":-5.255412},{"x":18.06785,"y":-5.001053},{"x":18.68115,"y":-5.000103},{"x":18.67963,"y":-4.52184},{"x":17.02291,"y":-4.528776},{"x":17.01885,"y":-3.042992},{"x":20.45203,"y":-3.045387},{"x":20.47937,"y":-4.531784},{"x":19.95031,"y":-4.533699},{"x":19.95813,"y":-5.00501},{"x":20.54358,"y":-5.01227},{"x":20.54195,"y":-6.947409},{"x":19.94732,"y":-6.945374},{"x":19.95498,"y":-7.420257},{"x":21.13659,"y":-7.425197},{"x":21.13324,"y":-7.627992},{"x":21.49961,"y":-7.636}],[{"x":11.50533,"y":-9.172968},{"x":11.98279,"y":-9.176331},{"x":11.98922,"y":-9.375989},{"x":12.63209,"y":-9.375942},{"x":12.67537,"y":-9.300284},{"x":12.74272,"y":-9.306814},{"x":12.75727,"y":-9.359571},{"x":12.73983,"y":-11.0426},{"x":12.75965,"y":-11.10165},{"x":12.77119,"y":-11.19073},{"x":12.83158,"y":-11.22746},{"x":12.83995,"y":-11.28305},{"x":12.88717,"y":-11.3183},{"x":12.92459,"y":-11.39211},{"x":12.9953,"y":-11.42959},{"x":13.03014,"y":-11.47267},{"x":13.11683,"y":-11.48415},{"x":13.21314,"y":-11.53158},{"x":14.9728,"y":-11.53044},{"x":15.04783,"y":-11.54779},{"x":15.08521,"y":-11.57961},{"x":15.05895,"y":-11.61776},{"x":15.01808,"y":-11.67069},{"x":15.01304,"y":-11.77359},{"x":17.57772,"y":-11.77746},{"x":17.56416,"y":-11.59646},{"x":18.05105,"y":-11.59746},{"x":18.05142,"y":-11.77575},{"x":20.53649,"y":-11.77254},{"x":20.54081,"y":-9.841003},{"x":19.95102,"y":-9.831125},{"x":19.94536,"y":-9.357937},{"x":21.14175,"y":-9.348281},{"x":21.13952,"y":-9.19059},{"x":21.4812,"y":-9.189327}],[{"x":12.52561,"y":-4.56645},{"x":12.52597,"y":-5.008878},{"x":11.9911,"y":-5.009898},{"x":11.98614,"y":-5.249488},{"x":11.49835,"y":-5.251798}],[{"x":18.06502,"y":-6.945622},{"x":18.67189,"y":-6.941694},{"x":18.68556,"y":-7.4117},{"x":18.0414,"y":-7.428703},{"x":18.05231,"y":-7.733563},{"x":17.58204,"y":-7.742327},{"x":17.57026,"y":-7.412163},{"x":16.94886,"y":-7.41368},{"x":16.94239,"y":-6.941051},{"x":17.58263,"y":-6.939175},{"x":17.58446,"y":-6.696082},{"x":18.06134,"y":-6.696437},{"x":18.06373,"y":-6.947431}],[{"x":13.91727,"y":-9.354623},{"x":14.48683,"y":-9.352476},{"x":14.48688,"y":-9.185989},{"x":14.97638,"y":-9.187501},{"x":14.97462,"y":-9.350637},{"x":15.69605,"y":-9.347918},{"x":15.68239,"y":-9.825948},{"x":14.9872,"y":-9.836722},{"x":14.98026,"y":-10.06423},{"x":15.04466,"y":-10.12155},{"x":15.04745,"y":-10.16676},{"x":14.97416,"y":-10.19373},{"x":14.67902,"y":-10.18203},{"x":14.32132,"y":-10.18746},{"x":14.04608,"y":-10.15917},{"x":13.87163,"y":-10.05492},{"x":13.80922,"y":-9.98138},{"x":13.74488,"y":-9.852007},{"x":13.75978,"y":-9.417733},{"x":13.74012,"y":-9.351611},{"x":13.76781,"y":-9.289933},{"x":13.8448,"y":-9.306457},{"x":13.87092,"y":-9.340744},{"x":13.91896,"y":-9.356215}],[{"x":11.51666,"y":-7.752146},{"x":11.98988,"y":-7.755126},{"x":11.98642,"y":-7.42875},{"x":12.64315,"y":-7.4232},{"x":12.65182,"y":-6.945045},{"x":11.98836,"y":-6.930521},{"x":11.98985,"y":-6.679015},{"x":11.64352,"y":-6.681569}],[{"x":18.04378,"y":-9.351138},{"x":18.67557,"y":-9.353913},{"x":18.68013,"y":-9.838058},{"x":18.06372,"y":-9.842699},{"x":18.0513,"y":-10.15337},{"x":17.56822,"y":-10.15438},{"x":17.57474,"y":-9.839076},{"x":16.9479,"y":-9.835162},{"x":16.94909,"y":-9.349567},{"x":17.57149,"y":-9.347048},{"x":17.57037,"y":-9.181834},{"x":18.04671,"y":-9.183824},{"x":18.05039,"y":-9.350534}],[{"x":14.49625,"y":-6.924356},{"x":13.90842,"y":-6.937685},{"x":13.91825,"y":-7.427229},{"x":14.48365,"y":-7.42862},{"x":14.49392,"y":-7.749632},{"x":14.98278,"y":-7.738917},{"x":14.98528,"y":-7.429673},{"x":15.66884,"y":-7.423855},{"x":15.66691,"y":-6.938859},{"x":14.97106,"y":-6.935668},{"x":14.97844,"y":-6.703414},{"x":14.49264,"y":-6.700523},{"x":14.49782,"y":-6.924356}],[{"x":-2.535742,"y":0.8500072},{"x":-2.049354,"y":0.2889237},{"x":-2.341152,"y":-0.1494614},{"x":-4.450888,"y":-0.1274282},{"x":-6.332565,"y":0.04715132},{"x":-6.0899,"y":0.4930227},{"x":-5.613416,"y":0.8124823},{"x":-3.814947,"y":0.8868923},{"x":-2.468606,"y":0.8575804}],[{"x":34.75221,"y":-0.4173391},{"x":37.05008,"y":-0.4006316},{"x":37.04756,"y":0.04533567},{"x":36.83799,"y":0.2537852},{"x":36.00438,"y":0.2639198},{"x":35.91608,"y":0.4882321},{"x":34.75167,"y":0.4806468},{"x":34.62226,"y":-0.2338717},{"x":34.75784,"y":-0.425309}],[{"x":-2.6355,"y":-0.5922},{"x":-1.787077,"y":-0.253946},{"x":-1.6275,"y":-0.0952},{"x":-1.817253,"y":0.3178226},{"x":-2.393979,"y":0.9440416},{"x":-5.896077,"y":0.9261262},{"x":-6.5835,"y":0.2478},{"x":-6.6325,"y":-0.1442},{"x":-5.7645,"y":-0.6061999},{"x":-2.6355,"y":-0.5922}],[{"x":-5.974792,"y":-2.229493},{"x":-5.71295,"y":-1.798917},{"x":-2.441465,"y":-1.779338},{"x":-2.137287,"y":-2.116978},{"x":-1.82655,"y":-2.507366},{"x":-1.889125,"y":-2.759274},{"x":-2.679174,"y":-3.243704},{"x":-6.227367,"y":-3.306208},{"x":-6.56071,"y":-2.783395},{"x":-6.484576,"y":-2.538279},{"x":-5.943035,"y":-2.210702}],[{"x":-6.516907,"y":6.984062},{"x":-6.733539,"y":6.920181},{"x":-6.969766,"y":6.987423},{"x":-6.738074,"y":7.085166},{"x":-6.516907,"y":6.984062}],[{"x":-6.486807,"y":9.517362},{"x":-6.703438,"y":9.453481},{"x":-6.939665,"y":9.520723},{"x":-6.707974,"y":9.618465},{"x":-6.486807,"y":9.517362}],[{"x":-7.227407,"y":10.26986},{"x":-7.444038,"y":10.20598},{"x":-7.680265,"y":10.27322},{"x":-7.448574,"y":10.37097},{"x":-7.227407,"y":10.26986}]] \ No newline at end of file diff --git a/assets/collision/mira.json b/assets/collision/mira.json new file mode 100644 index 0000000..b08442d --- /dev/null +++ b/assets/collision/mira.json @@ -0,0 +1 @@ +[[{"x":3.71337,"y":-2.238192},{"x":3.687777,"y":-2.593762},{"x":-6.192677,"y":-2.624138},{"x":-6.184436,"y":-0.03365517},{"x":-6.713911,"y":0.02841848},{"x":-6.665236,"y":4.055113},{"x":-2.499738,"y":4.00852},{"x":-2.52292,"y":3.110758},{"x":-2.509094,"y":2.238056},{"x":-2.771595,"y":2.170679},{"x":-2.80248,"y":-0.7535518},{"x":3.695479,"y":-0.6665705}],[{"x":7.211442,"y":2.161355},{"x":8.762964,"y":2.16075},{"x":8.751773,"y":5.226181},{"x":11.12137,"y":5.24228},{"x":11.12562,"y":4.513843},{"x":11.26416,"y":4.509835},{"x":11.25983,"y":5.654586}],[{"x":3.695692,"y":-2.227394},{"x":13.57364,"y":-2.236449},{"x":13.57352,"y":-0.3868256},{"x":13.71494,"y":-0.3952844},{"x":13.70653,"y":-2.223386},{"x":17.07831,"y":-2.222195},{"x":17.08636,"y":1.597083},{"x":13.69615,"y":1.581925},{"x":13.68949,"y":0.692555},{"x":13.57186,"y":0.69209},{"x":13.56429,"y":3.41575},{"x":13.7098,"y":3.405168},{"x":13.69926,"y":2.460877},{"x":16.67167,"y":2.455469},{"x":16.67134,"y":5.129327},{"x":16.25138,"y":4.924525},{"x":15.90153,"y":5.098832},{"x":14.85329,"y":5.113066},{"x":14.64129,"y":4.891927},{"x":14.30725,"y":5.120798},{"x":14.09058,"y":5.117527},{"x":14.05816,"y":5.476541},{"x":13.71774,"y":5.463151},{"x":13.71342,"y":4.504309},{"x":13.56084,"y":4.517361},{"x":13.55398,"y":5.708665}],[{"x":3.680702,"y":-0.7193847},{"x":11.23482,"y":-0.7322021},{"x":11.25548,"y":3.415659},{"x":11.12786,"y":3.419772},{"x":10.75617,"y":3.248261},{"x":10.73357,"y":0.1335044},{"x":3.80519,"y":0.1373074},{"x":3.818368,"y":2.171448},{"x":4.95494,"y":2.163688}],[{"x":24.3919,"y":5.646832},{"x":24.40391,"y":5.442618},{"x":29.13271,"y":5.463554},{"x":29.13235,"y":-0.5738076},{"x":26.70813,"y":-0.5683059},{"x":26.73149,"y":-1.518903},{"x":26.95974,"y":-1.376958},{"x":28.65404,"y":-1.375125},{"x":28.65683,"y":-2.748982},{"x":20.33384,"y":-2.765088},{"x":20.3395,"y":-3.770453},{"x":17.93382,"y":-3.75399},{"x":17.96961,"y":-1.380442},{"x":20.87004,"y":-1.529158},{"x":20.88352,"y":-0.57613},{"x":17.95704,"y":-0.5691274},{"x":17.97062,"y":0.8038477},{"x":18.93854,"y":0.7800388},{"x":18.93254,"y":1.598521},{"x":17.98071,"y":1.602374},{"x":17.96151,"y":5.457216},{"x":22.08923,"y":5.464901}],[{"x":21.05723,"y":5.580141},{"x":21.04314,"y":1.607991},{"x":20.09641,"y":1.596745},{"x":20.0989,"y":0.7935047},{"x":21.20391,"y":0.7903333},{"x":21.19257,"y":5.576942}],[{"x":11.25491,"y":5.620545},{"x":11.25136,"y":7.638169},{"x":12.73018,"y":7.648466},{"x":16.70739,"y":11.60392},{"x":16.70363,"y":15.95111}],[{"x":13.56464,"y":5.627538},{"x":13.55336,"y":6.262187},{"x":17.82847,"y":10.52911}],[{"x":22.0916,"y":5.628248},{"x":22.10105,"y":6.269244},{"x":17.82738,"y":10.53223}],[{"x":18.96374,"y":15.99181},{"x":18.95209,"y":11.60822},{"x":22.92673,"y":7.648565},{"x":23.77438,"y":7.633254},{"x":24.39512,"y":7.429035},{"x":24.40486,"y":5.632056}],[{"x":7.220767,"y":8.833663},{"x":7.22247,"y":13.32097},{"x":7.355358,"y":13.31475},{"x":7.347641,"y":9.647293},{"x":12.08101,"y":9.675213},{"x":12.10811,"y":14.14455},{"x":7.977394,"y":14.13185},{"x":7.965653,"y":14.56834},{"x":5.895285,"y":14.56914},{"x":5.340263,"y":13.75509},{"x":4.994918,"y":13.86703},{"x":4.97183,"y":10.77085},{"x":4.854324,"y":10.77326},{"x":4.82991,"y":14.37352},{"x":3.731284,"y":14.37308},{"x":2.88799,"y":13.48531},{"x":2.05535,"y":13.47907},{"x":1.203818,"y":14.39449},{"x":0.08164263,"y":14.39737},{"x":0.09667063,"y":9.66668},{"x":4.986578,"y":9.656466},{"x":4.97504,"y":8.829696}],[{"x":16.70916,"y":15.85023},{"x":16.70025,"y":16.77103},{"x":12.87164,"y":16.76122},{"x":12.86896,"y":20.66066},{"x":13.45543,"y":20.44748},{"x":13.97733,"y":20.66153},{"x":13.98698,"y":20.99139},{"x":15.44601,"y":20.98349},{"x":15.45127,"y":20.65192},{"x":15.75683,"y":20.46402},{"x":16.55697,"y":20.66496},{"x":16.56016,"y":17.77095},{"x":16.69854,"y":17.78921},{"x":16.69959,"y":21.05262},{"x":17.28384,"y":21.04772}],[{"x":17.29037,"y":21.03862},{"x":17.28807,"y":21.8748},{"x":12.74644,"y":21.8763},{"x":12.75951,"y":22.57441},{"x":13.10651,"y":23.37466},{"x":13.73439,"y":24.10774},{"x":15.4092,"y":25.05675},{"x":16.66343,"y":25.38271},{"x":17.84418,"y":25.4593},{"x":19.55612,"y":25.26398},{"x":21.22287,"y":24.61058},{"x":22.23365,"y":23.81404},{"x":22.85189,"y":22.84578},{"x":22.946,"y":21.83673},{"x":18.39528,"y":21.88615},{"x":18.39615,"y":21.04512}],[{"x":18.39268,"y":21.06862},{"x":19.00124,"y":21.0586},{"x":18.97912,"y":19.36902},{"x":19.09182,"y":19.36764},{"x":19.07861,"y":20.87417},{"x":19.93716,"y":20.85499},{"x":19.93814,"y":21.016},{"x":20.60378,"y":20.99647},{"x":20.59911,"y":20.85824},{"x":22.44798,"y":20.86359},{"x":22.43852,"y":20.04767},{"x":22.78794,"y":20.04396},{"x":22.81651,"y":16.90077},{"x":19.54131,"y":16.91623},{"x":19.54423,"y":16.77063},{"x":19.10311,"y":16.76015},{"x":19.10099,"y":18.36283},{"x":18.95522,"y":18.3579},{"x":18.97521,"y":15.8332}],[{"x":7.212324,"y":9.84881},{"x":7.219448,"y":2.166317}],[{"x":4.975575,"y":9.639039},{"x":4.948083,"y":2.163976}],[{"x":16.568,"y":19.02069},{"x":16.128,"y":19.02069},{"x":16.128,"y":18.061},{"x":16.568,"y":18.061},{"x":16.568,"y":19.02069}],[{"x":14.298,"y":19.32877},{"x":12.868,"y":19.32877},{"x":12.868,"y":18.837},{"x":14.298,"y":18.837},{"x":14.298,"y":19.32877}],[{"x":17.9349,"y":5.586789},{"x":18.00781,"y":5.227939},{"x":18.67603,"y":4.793009},{"x":19.14774,"y":5.096714},{"x":19.15981,"y":5.218315},{"x":20.28024,"y":5.215115},{"x":20.59529,"y":4.862257},{"x":21.07071,"y":5.041684},{"x":21.05624,"y":5.598478},{"x":17.9349,"y":5.586789}],[{"x":20.47344,"y":3.445615},{"x":20.90137,"y":3.802666},{"x":20.90182,"y":4.066891},{"x":20.46002,"y":4.36205},{"x":20.02631,"y":4.068808},{"x":20.01486,"y":3.775074},{"x":20.47344,"y":3.445615}],[{"x":20.57644,"y":2.875615},{"x":21.00437,"y":3.232666},{"x":21.00482,"y":3.496891},{"x":20.56302,"y":3.79205},{"x":20.12931,"y":3.498807},{"x":20.11786,"y":3.205074},{"x":20.57644,"y":2.875615}],[{"x":13.56155,"y":17.24444},{"x":13.35024,"y":17.03253},{"x":13.13379,"y":17.16789},{"x":12.972,"y":17.29},{"x":12.9723,"y":18.24428},{"x":14.402,"y":18.26},{"x":14.442,"y":17.2},{"x":13.56155,"y":17.24444}],[{"x":-0.2100389,"y":0},{"x":0.2100389,"y":0.2100389},{"x":0.4200778,"y":0},{"x":0.2100389,"y":0.2100389},{"x":-0.4200778,"y":0}],[{"x":-0.15,"y":0},{"x":0.15,"y":0.15},{"x":0.3,"y":0},{"x":0.15,"y":0.15},{"x":-0.3,"y":0}],[{"x":-0.1982777,"y":0},{"x":0.1982777,"y":0.1982777},{"x":0.3965554,"y":0},{"x":0.1982777,"y":0.1982777},{"x":-0.3965554,"y":0}],[{"x":-0.1982777,"y":0},{"x":0.1982777,"y":0.1982777},{"x":0.3965554,"y":0},{"x":0.1982777,"y":0.1982777},{"x":-0.3965554,"y":0}],[{"x":21.03973,"y":3.091555},{"x":19.02226,"y":3.091555},{"x":19.02226,"y":2.636287},{"x":21.03973,"y":2.636287},{"x":21.03973,"y":3.091555}],[{"x":4.878,"y":13.541},{"x":4.248,"y":13.541},{"x":4.248,"y":13.351},{"x":4.878,"y":13.351},{"x":4.878,"y":13.541}],[{"x":4.878,"y":12.713},{"x":4.248,"y":12.713},{"x":4.248,"y":12.523},{"x":4.878,"y":12.523},{"x":4.878,"y":12.713}],[{"x":4.878,"y":11.676},{"x":4.248,"y":11.676},{"x":4.248,"y":11.486},{"x":4.878,"y":11.486},{"x":4.878,"y":11.676}],[{"x":-6.140824,"y":2.366569},{"x":-6.841852,"y":2.366569},{"x":-6.841852,"y":1.44923},{"x":-6.140824,"y":1.44923},{"x":-6.140824,"y":2.366569}],[{"x":-2.1,"y":2.1913},{"x":-2.78,"y":2.1913},{"x":-2.78,"y":1.08},{"x":-2.1,"y":1.08},{"x":-2.1,"y":2.1913}],[{"x":24.92,"y":3.80943},{"x":23.16,"y":3.80943},{"x":23.16,"y":0.9315456},{"x":24.92,"y":0.9315456},{"x":24.92,"y":3.80943}],[{"x":27.78,"y":3.80943},{"x":26.02,"y":3.80943},{"x":26.02,"y":0.9315456},{"x":27.78,"y":0.9315456},{"x":27.78,"y":3.80943}],[{"x":28.753,"y":6.152},{"x":28.073,"y":6.152},{"x":28.073,"y":5.172},{"x":28.753,"y":5.172},{"x":28.753,"y":6.152}],[{"x":29.165,"y":3.735192},{"x":28.755,"y":3.735192},{"x":28.755,"y":1.065314},{"x":29.165,"y":1.065314},{"x":29.165,"y":3.735192}],[{"x":29.145,"y":5.06},{"x":28.735,"y":5.06},{"x":28.735,"y":4.28},{"x":29.145,"y":4.28},{"x":29.145,"y":5.06}],[{"x":19.027,"y":2.864326},{"x":18.617,"y":2.864326},{"x":18.617,"y":2.661},{"x":19.027,"y":2.661},{"x":19.027,"y":2.864326}],[{"x":0.7080002,"y":13.566},{"x":0.07800007,"y":13.566},{"x":0.07800007,"y":13.376},{"x":0.7080002,"y":13.376},{"x":0.7080002,"y":13.566}],[{"x":0.5790001,"y":13.4695},{"x":0.08900023,"y":13.4695},{"x":0.08900023,"y":12.682},{"x":0.5790001,"y":12.682},{"x":0.5790001,"y":13.4695}],[{"x":0.7080002,"y":12.703},{"x":0.07800007,"y":12.703},{"x":0.07800007,"y":12.513},{"x":0.7080002,"y":12.513},{"x":0.7080002,"y":12.703}],[{"x":0.5790001,"y":12.779},{"x":0.08900023,"y":12.779},{"x":0.08900023,"y":11.809},{"x":0.5790001,"y":11.809},{"x":0.5790001,"y":12.779}],[{"x":0.7080002,"y":11.701},{"x":0.07800007,"y":11.701},{"x":0.07800007,"y":11.511},{"x":0.7080002,"y":11.511},{"x":0.7080002,"y":11.701}],[{"x":1.022,"y":11.5454},{"x":0.2020001,"y":11.5454},{"x":0.2020001,"y":11.114},{"x":1.022,"y":11.114},{"x":1.022,"y":11.5454}],[{"x":0.7080002,"y":10.187},{"x":0.07800007,"y":10.187},{"x":0.07800007,"y":9.997},{"x":0.7080002,"y":9.997},{"x":0.7080002,"y":10.187}],[{"x":11.635,"y":9.885},{"x":7.905001,"y":9.885},{"x":7.905001,"y":9.655001},{"x":11.635,"y":9.655001},{"x":11.635,"y":9.885}],[{"x":10.53,"y":11.48153},{"x":8.13,"y":11.48153},{"x":8.13,"y":10.605},{"x":10.53,"y":10.605},{"x":10.53,"y":11.48153}],[{"x":10.50025,"y":4.627614},{"x":9.624989,"y":4.627614},{"x":9.624989,"y":4.265},{"x":10.50025,"y":4.265},{"x":10.50025,"y":4.627614}],[{"x":22.63,"y":16.915},{"x":19.55,"y":16.915},{"x":19.55,"y":16.765},{"x":22.63,"y":16.765},{"x":22.63,"y":16.915}],[{"x":22.095,"y":18.117},{"x":19.965,"y":18.117},{"x":19.965,"y":17.61187},{"x":22.095,"y":17.61187},{"x":22.095,"y":18.117}],[{"x":19.871,"y":23.511},{"x":18.441,"y":23.511},{"x":18.441,"y":23.261},{"x":19.871,"y":23.261},{"x":19.871,"y":23.511}],[{"x":17.135,"y":23.425},{"x":15.705,"y":23.425},{"x":15.705,"y":23.175},{"x":17.135,"y":23.175},{"x":17.135,"y":23.425}],[{"x":21.845,"y":23.615},{"x":20.415,"y":23.615},{"x":20.415,"y":23.365},{"x":21.845,"y":23.365},{"x":21.845,"y":23.615}],[{"x":15.625,"y":23.475},{"x":14.195,"y":23.475},{"x":14.195,"y":23.225},{"x":15.625,"y":23.225},{"x":15.625,"y":23.475}],[{"x":15.399,"y":24.317},{"x":13.949,"y":24.317},{"x":13.949,"y":23.967},{"x":15.399,"y":23.967},{"x":15.399,"y":24.317}],[{"x":15.756,"y":22.593},{"x":14.306,"y":22.593},{"x":14.306,"y":22.243},{"x":15.756,"y":22.243},{"x":15.756,"y":22.593}],[{"x":17.316,"y":22.679},{"x":15.866,"y":22.679},{"x":15.866,"y":22.329},{"x":17.316,"y":22.329},{"x":17.316,"y":22.679}],[{"x":19.706,"y":22.699},{"x":18.256,"y":22.699},{"x":18.256,"y":22.349},{"x":19.706,"y":22.349},{"x":19.706,"y":22.699}],[{"x":22.68,"y":22.702},{"x":21.23,"y":22.702},{"x":21.23,"y":22.352},{"x":22.68,"y":22.352},{"x":22.68,"y":22.702}],[{"x":16.23,"y":25.004},{"x":14.8,"y":25.004},{"x":14.8,"y":24.754},{"x":16.23,"y":24.754},{"x":16.23,"y":25.004}],[{"x":21.038,"y":24.766},{"x":19.588,"y":24.766},{"x":19.588,"y":24.416},{"x":21.038,"y":24.416},{"x":21.038,"y":24.766}],[{"x":19.367,"y":-2.518978},{"x":18.68754,"y":-2.518978},{"x":18.68754,"y":-2.839065},{"x":19.367,"y":-2.839065},{"x":19.367,"y":-2.518978}],[{"x":17.001,"y":1.865},{"x":16.231,"y":1.865},{"x":16.231,"y":0.875},{"x":17.001,"y":0.875},{"x":17.001,"y":1.865}],[{"x":15.925,"y":1.865},{"x":15.155,"y":1.865},{"x":15.155,"y":0.875},{"x":15.925,"y":0.875},{"x":15.925,"y":1.865}],[{"x":14.539,"y":1.865},{"x":13.769,"y":1.865},{"x":13.769,"y":0.875},{"x":14.539,"y":0.875},{"x":14.539,"y":1.865}],[{"x":14.72,"y":-1.882},{"x":13.7,"y":-1.882},{"x":13.7,"y":-2.232},{"x":14.72,"y":-2.232},{"x":14.72,"y":-1.882}],[{"x":17.08,"y":-1.879},{"x":16.06,"y":-1.879},{"x":16.06,"y":-2.229},{"x":17.08,"y":-2.229},{"x":17.08,"y":-1.879}],[{"x":20.427,"y":2.613},{"x":20.427,"y":2.153},{"x":20.797,"y":1.793},{"x":21.057,"y":1.953},{"x":20.427,"y":2.613}],[{"x":9.81149,"y":13.37871},{"x":9.815001,"y":12.48},{"x":11.2387,"y":12.45026},{"x":11.25847,"y":11.15674},{"x":11.47295,"y":11.17267},{"x":11.465,"y":10.86},{"x":12.11164,"y":10.50449},{"x":12.11447,"y":13.38711},{"x":9.81149,"y":13.37871}],[{"x":22.0661,"y":19.44129},{"x":21.59683,"y":19.86015},{"x":20.43074,"y":19.8552},{"x":20.04636,"y":19.4479},{"x":19.75579,"y":19.44509},{"x":19.736,"y":18.319},{"x":20.446,"y":17.909},{"x":21.616,"y":17.909},{"x":22.076,"y":18.279},{"x":22.0661,"y":19.44129}],[{"x":16.60246,"y":5.043699},{"x":16.4291,"y":4.853531},{"x":16.10734,"y":5.052094},{"x":15.05132,"y":5.074209},{"x":14.85036,"y":4.780072},{"x":14.47607,"y":5.052905},{"x":14.26516,"y":5.059663},{"x":14.26178,"y":5.7193},{"x":16.94973,"y":5.700385},{"x":16.94588,"y":5.045084},{"x":16.60246,"y":5.043699}]] \ No newline at end of file diff --git a/assets/collision/polus.json b/assets/collision/polus.json new file mode 100644 index 0000000..d6fe23b --- /dev/null +++ b/assets/collision/polus.json @@ -0,0 +1 @@ +[[{"x":4.468355,"y":-25.19164},{"x":4.642188,"y":-25.4748},{"x":5.859158,"y":-25.97136},{"x":7.379907,"y":-26.17045},{"x":9.318335,"y":-25.85212},{"x":9.691259,"y":-25.51607}],[{"x":12.85454,"y":-25.81685},{"x":13.21841,"y":-26.08871},{"x":15.07888,"y":-26.52698},{"x":17.51235,"y":-26.34335},{"x":19.41673,"y":-26.09551},{"x":19.68621,"y":-25.85137}],[{"x":31.83391,"y":-17.77156},{"x":31.16781,"y":-16.94566},{"x":30.99393,"y":-16.22257},{"x":32.84105,"y":-16.14146},{"x":32.84063,"y":-15.6644},{"x":31.01169,"y":-15.65007},{"x":31.19056,"y":-14.61455},{"x":32.02383,"y":-13.94915},{"x":33.24313,"y":-13.53686},{"x":34.74972,"y":-13.62016},{"x":34.87443,"y":-11.78193}],[{"x":1.163654,"y":-8.0028},{"x":11.41751,"y":-7.992749},{"x":11.42015,"y":-9.245324},{"x":11.07954,"y":-9.219257},{"x":10.46802,"y":-9.175865},{"x":10.10005,"y":-8.969795},{"x":9.726253,"y":-8.987286},{"x":9.737602,"y":-9.120133},{"x":7.71242,"y":-9.124039},{"x":7.702194,"y":-8.981174},{"x":2.543196,"y":-8.972954},{"x":2.216505,"y":-9.236499},{"x":1.586406,"y":-8.953769},{"x":1.588347,"y":-10.41234},{"x":1.889818,"y":-10.42414},{"x":1.875751,"y":-11.16787},{"x":1.56425,"y":-11.43982},{"x":1.58513,"y":-13.01106},{"x":4.34094,"y":-13.01894},{"x":4.351696,"y":-11.30745},{"x":3.332622,"y":-11.3479},{"x":3.13042,"y":-11.16702},{"x":3.143933,"y":-10.42473},{"x":4.461988,"y":-10.43749},{"x":4.454574,"y":-13.01405},{"x":4.805012,"y":-13.01713},{"x":4.800202,"y":-13.99301},{"x":4.261829,"y":-13.97445}],[{"x":6.059853,"y":-13.98625},{"x":6.056842,"y":-13.01136},{"x":6.365222,"y":-13.00313},{"x":6.363031,"y":-10.41905},{"x":6.883934,"y":-10.43037},{"x":6.866985,"y":-11.38156},{"x":6.683385,"y":-11.38787},{"x":6.68111,"y":-13.98867},{"x":6.055891,"y":-13.98866}],[{"x":6.660733,"y":-13.96993},{"x":11.38878,"y":-13.96052},{"x":11.4079,"y":-10.2124},{"x":11.06013,"y":-10.21909},{"x":11.0841,"y":-10.41887},{"x":8.096922,"y":-10.41325},{"x":8.091213,"y":-11.39648},{"x":11.39786,"y":-11.37933}],[{"x":4.623094,"y":-13.24155},{"x":4.616889,"y":-17.96251}],[{"x":6.22798,"y":-17.95516},{"x":6.21671,"y":-13.17807}],[{"x":4.329903,"y":-17.80734},{"x":4.848855,"y":-17.80634},{"x":4.866112,"y":-18.79152},{"x":3.729993,"y":-18.78318},{"x":3.712253,"y":-18.00079},{"x":4.014073,"y":-18.00014},{"x":4.023358,"y":-16.33687},{"x":3.302862,"y":-15.68751},{"x":2.222462,"y":-15.69674},{"x":2.171762,"y":-16.38062},{"x":1.870695,"y":-16.55518},{"x":1.413249,"y":-16.39653},{"x":1.182948,"y":-15.71359},{"x":0.315588,"y":-15.69561},{"x":0.3355923,"y":-16.98934},{"x":0.5901208,"y":-17.0723},{"x":0.5943825,"y":-17.20404},{"x":0.8280015,"y":-17.29915},{"x":0.8179843,"y":-18.00991},{"x":2.493732,"y":-18.01904},{"x":2.49319,"y":-21.35849},{"x":2.079746,"y":-21.36394},{"x":2.078685,"y":-20.61282},{"x":2.35622,"y":-20.60777},{"x":2.368558,"y":-18.95865},{"x":2.076443,"y":-18.97806},{"x":1.885863,"y":-18.75928},{"x":1.117207,"y":-18.76488},{"x":0.8289199,"y":-19.03341},{"x":0.8386979,"y":-19.20543},{"x":1.388847,"y":-19.19547},{"x":1.596443,"y":-19.43404},{"x":1.575914,"y":-19.69593},{"x":1.435838,"y":-19.77969},{"x":0.8422089,"y":-19.78425},{"x":0.8446279,"y":-19.98297},{"x":0.3109691,"y":-20.59622},{"x":0.8417668,"y":-20.61433},{"x":0.8157988,"y":-21.63438},{"x":0.3295369,"y":-21.70924},{"x":0.3366578,"y":-22.43502},{"x":2.793279,"y":-22.44424},{"x":2.792992,"y":-23.2027},{"x":1.461118,"y":-23.19614},{"x":1.31747,"y":-23.37593},{"x":0.31354,"y":-23.61072},{"x":0.3137934,"y":-24.99285},{"x":4.304623,"y":-24.96215},{"x":4.311398,"y":-23.18888},{"x":4.065456,"y":-23.198},{"x":4.066553,"y":-22.43078},{"x":4.303843,"y":-22.44131},{"x":4.305241,"y":-21.84784},{"x":5.262867,"y":-21.85781},{"x":5.281539,"y":-22.83879},{"x":4.633771,"y":-22.82001},{"x":4.633868,"y":-25.707}],[{"x":6.121937,"y":-17.81924},{"x":6.125292,"y":-18.77762},{"x":6.825426,"y":-18.76699},{"x":6.839787,"y":-21.85088},{"x":6.540904,"y":-21.85227},{"x":6.540395,"y":-22.78975},{"x":7.153381,"y":-22.81718},{"x":7.160014,"y":-17.79613},{"x":6.122066,"y":-17.81233}],[{"x":9.454855,"y":-25.96852},{"x":9.431522,"y":-21.63816},{"x":12.19316,"y":-21.62917},{"x":12.18578,"y":-20.95824},{"x":12.39046,"y":-20.95943},{"x":12.36431,"y":-22.44428},{"x":10.78102,"y":-22.44403},{"x":10.67416,"y":-22.7368},{"x":10.25398,"y":-22.84688},{"x":10.11095,"y":-23.10449},{"x":10.11586,"y":-23.96232},{"x":11.675,"y":-23.98213},{"x":11.98683,"y":-24.2565},{"x":12.07754,"y":-24.65993},{"x":12.11319,"y":-25.18934},{"x":13.01008,"y":-25.17044},{"x":14.1152,"y":-24.04868},{"x":14.13201,"y":-22.4912},{"x":13.70978,"y":-22.44748},{"x":13.69046,"y":-20.95766},{"x":13.92116,"y":-20.93906},{"x":13.92223,"y":-21.61344},{"x":14.35059,"y":-21.61405},{"x":14.33282,"y":-24.663},{"x":12.82521,"y":-26.22824}],[{"x":19.41761,"y":-26.89183},{"x":19.41428,"y":-23.9883},{"x":18.17723,"y":-24.03974},{"x":18.07403,"y":-23.80378},{"x":17.23896,"y":-23.79051},{"x":17.24987,"y":-22.49761},{"x":17.55,"y":-22.48637},{"x":17.55483,"y":-22.85442},{"x":19.63054,"y":-22.86251},{"x":19.63194,"y":-20.89175},{"x":19.23694,"y":-20.54975},{"x":19.22899,"y":-19.82631},{"x":20.36378,"y":-19.8261},{"x":20.34842,"y":-20.56401},{"x":20.06929,"y":-20.57189},{"x":19.98769,"y":-20.68674},{"x":19.72377,"y":-20.70757},{"x":19.73185,"y":-25.68816},{"x":20.47411,"y":-25.67962},{"x":20.4746,"y":-23.78805},{"x":20.92922,"y":-23.78977},{"x":20.91884,"y":-25.68327},{"x":22.64618,"y":-25.65857},{"x":22.64977,"y":-24.8419},{"x":21.57603,"y":-24.84848},{"x":21.57071,"y":-23.77324},{"x":22.64747,"y":-23.79463},{"x":22.65281,"y":-23.03153},{"x":23.25619,"y":-23.03007},{"x":23.27328,"y":-23.97827},{"x":22.75698,"y":-23.98265},{"x":22.74235,"y":-25.67866},{"x":25.35623,"y":-25.67291},{"x":25.34784,"y":-25.31824},{"x":25.6509,"y":-25.32745},{"x":25.64791,"y":-25.98885}],[{"x":25.36081,"y":-24.2927},{"x":26.78346,"y":-24.34598},{"x":26.9757,"y":-24.08747},{"x":26.94914,"y":-20.68051},{"x":27.24854,"y":-20.39237},{"x":33.72597,"y":-20.36598}],[{"x":25.60491,"y":-25.32418},{"x":28.29795,"y":-25.32301},{"x":28.56379,"y":-25.11761},{"x":28.55518,"y":-21.54346},{"x":28.8744,"y":-21.15343},{"x":33.65784,"y":-21.16417}],[{"x":33.62392,"y":-20.36062},{"x":33.74138,"y":-20.37108},{"x":33.98942,"y":-19.83228},{"x":34.34132,"y":-19.84434},{"x":35.3353,"y":-18.84248},{"x":38.09467,"y":-18.81666},{"x":38.33393,"y":-19.0464},{"x":38.4529,"y":-18.96912},{"x":38.45289,"y":-18.25862},{"x":38.2281,"y":-17.98815},{"x":37.93935,"y":-17.9932}],[{"x":33.62782,"y":-21.26315},{"x":33.6063,"y":-20.97958},{"x":33.72726,"y":-20.98293},{"x":33.81273,"y":-21.31415},{"x":34.1008,"y":-21.30166},{"x":34.48833,"y":-21.31375},{"x":34.62857,"y":-21.45783},{"x":34.63147,"y":-22.14026},{"x":35.06453,"y":-22.50477},{"x":37.82113,"y":-22.5367},{"x":37.87976,"y":-22.09232},{"x":38.05656,"y":-21.70172},{"x":38.46301,"y":-21.59719},{"x":39.04375,"y":-21.58111},{"x":39.37287,"y":-21.19324},{"x":39.36033,"y":-20.183},{"x":39.15706,"y":-19.87025},{"x":39.17252,"y":-19.18412},{"x":39.29811,"y":-19.09086},{"x":39.48417,"y":-19.31238}],[{"x":39.86636,"y":-11.77958},{"x":39.85297,"y":-18.35519},{"x":39.66782,"y":-18.8361},{"x":39.34599,"y":-19.17971}],[{"x":38.26079,"y":-11.81432},{"x":38.25633,"y":-18.10769}],[{"x":24.79112,"y":-8.297972},{"x":24.60275,"y":-8.309859},{"x":24.60161,"y":-9.347557},{"x":24.93522,"y":-9.361137},{"x":24.93626,"y":-9.281505},{"x":25.99243,"y":-9.277384},{"x":25.98319,"y":-8.309302},{"x":24.76362,"y":-8.30243},{"x":24.76752,"y":-6.596991},{"x":28.4891,"y":-6.597414},{"x":28.45923,"y":-7.883276},{"x":28.0819,"y":-7.898211},{"x":27.26835,"y":-8.311927},{"x":27.25182,"y":-9.284495},{"x":27.67033,"y":-9.284494},{"x":27.64346,"y":-9.429042},{"x":30.11549,"y":-9.410277},{"x":30.1084,"y":-9.26344},{"x":30.76909,"y":-9.30785},{"x":30.7845,"y":-8.539226},{"x":29.29904,"y":-8.507231},{"x":29.30643,"y":-7.69715},{"x":29.00145,"y":-7.67394},{"x":28.97839,"y":-7.201445},{"x":29.26816,"y":-7.067931},{"x":30.77146,"y":-6.979141},{"x":32.51074,"y":-6.985449},{"x":32.5293,"y":-7.183598},{"x":32.63257,"y":-7.192318},{"x":32.61849,"y":-5.816308},{"x":33.23944,"y":-5.262764},{"x":34.96804,"y":-5.275941},{"x":35.62245,"y":-5.857418},{"x":36.93289,"y":-5.911241},{"x":36.93803,"y":-6.817193},{"x":36.95642,"y":-7.421597},{"x":37.01499,"y":-7.498929},{"x":37.21291,"y":-7.564006},{"x":37.30141,"y":-7.493206},{"x":39.42292,"y":-7.540127},{"x":39.60057,"y":-7.493062},{"x":39.68266,"y":-7.383775},{"x":39.69355,"y":-6.823347},{"x":41.01453,"y":-6.819366},{"x":41.00942,"y":-8.521671},{"x":39.64987,"y":-8.511548},{"x":39.65253,"y":-8.208335},{"x":37.28139,"y":-8.251371},{"x":36.92989,"y":-8.531352},{"x":36.92381,"y":-9.269684},{"x":37.82073,"y":-9.275565},{"x":37.82654,"y":-9.387205},{"x":38.14684,"y":-9.405945},{"x":38.16234,"y":-9.270851},{"x":41.01444,"y":-9.254274},{"x":41.00807,"y":-10.88156},{"x":39.70001,"y":-10.87982},{"x":39.71012,"y":-11.84312},{"x":41.33603,"y":-11.85758}],[{"x":38.43769,"y":-10.88076},{"x":38.44409,"y":-11.83403},{"x":32.30521,"y":-11.84694},{"x":32.30261,"y":-11.41184},{"x":24.60489,"y":-11.42625},{"x":24.60373,"y":-10.31385},{"x":24.93105,"y":-10.3164},{"x":24.9278,"y":-10.46288},{"x":32.50663,"y":-10.4634},{"x":32.49953,"y":-9.272195},{"x":32.03727,"y":-9.283109},{"x":32.03503,"y":-8.515261},{"x":32.50163,"y":-8.525516},{"x":32.50784,"y":-8.143551},{"x":32.61598,"y":-8.14327},{"x":32.60762,"y":-8.518142},{"x":35.66286,"y":-8.51203},{"x":35.66552,"y":-9.276382},{"x":35.50318,"y":-9.27581},{"x":35.49751,"y":-9.757851},{"x":35.39729,"y":-9.761734},{"x":35.39405,"y":-9.366034},{"x":34.50618,"y":-9.353576},{"x":34.50914,"y":-9.807557},{"x":33.2071,"y":-9.797601},{"x":33.23382,"y":-9.251056},{"x":32.62589,"y":-9.238699},{"x":32.60516,"y":-10.88941},{"x":35.38573,"y":-10.884},{"x":35.39033,"y":-10.73012},{"x":35.502,"y":-10.7328},{"x":35.49858,"y":-10.87894},{"x":37.81333,"y":-10.87426},{"x":37.81733,"y":-10.3591},{"x":38.1471,"y":-10.35867},{"x":38.14536,"y":-10.87858},{"x":38.43985,"y":-10.88128}],[{"x":17.69288,"y":-12.43071},{"x":17.92506,"y":-12.44274},{"x":17.91992,"y":-12.73771},{"x":18.13973,"y":-13.14827},{"x":18.93378,"y":-13.03682},{"x":18.93032,"y":-13.74802},{"x":23.40946,"y":-13.73718},{"x":23.41752,"y":-10.04595},{"x":17.67672,"y":-10.04396},{"x":17.69304,"y":-10.65219},{"x":19.11459,"y":-10.72677},{"x":19.19703,"y":-10.83733},{"x":20.77446,"y":-10.84928},{"x":21.06972,"y":-11.36423},{"x":23.21907,"y":-11.98615},{"x":23.20252,"y":-12.70573},{"x":22.73357,"y":-12.50255},{"x":22.23224,"y":-13.00422},{"x":20.03675,"y":-12.9942},{"x":20.02708,"y":-12.52135},{"x":19.64642,"y":-12.50077},{"x":19.58921,"y":-12.24613},{"x":19.4246,"y":-12.09461},{"x":19.07064,"y":-12.11856},{"x":19.0561,"y":-11.69396},{"x":17.68979,"y":-11.67623},{"x":17.69365,"y":-12.43585}],[{"x":2.592513,"y":-8.025942},{"x":4.280735,"y":-7.398408},{"x":4.290099,"y":-4.471621},{"x":4.004474,"y":-4.254463},{"x":3.906721,"y":-3.920575},{"x":4.174633,"y":-3.568251},{"x":4.674113,"y":-3.396756},{"x":5.219826,"y":-3.662916},{"x":5.343629,"y":-3.998812},{"x":5.228846,"y":-4.248911},{"x":4.984394,"y":-4.46321},{"x":4.982031,"y":-7.379822},{"x":6.230354,"y":-7.271032}],[{"x":23.14033,"y":-6.594245},{"x":23.80352,"y":-6.434936},{"x":23.8171,"y":-3.478621},{"x":23.53148,"y":-3.261463},{"x":23.43372,"y":-2.927575},{"x":23.70163,"y":-2.575251},{"x":24.20111,"y":-2.403756},{"x":24.74683,"y":-2.669916},{"x":24.87063,"y":-3.005812},{"x":24.75585,"y":-3.255911},{"x":24.51139,"y":-3.47021},{"x":24.50903,"y":-6.386822},{"x":24.79138,"y":-6.653455}],[{"x":5.413117,"y":-8.351901},{"x":7.054794,"y":-8.276548},{"x":8.697342,"y":-8.510894},{"x":10.10862,"y":-8.303051},{"x":10.58135,"y":-8.118687},{"x":11.00681,"y":-7.484922},{"x":12.62112,"y":-8.204644},{"x":14.23929,"y":-8.974723},{"x":14.40617,"y":-7.258311},{"x":14.29719,"y":-2.043067},{"x":15.70589,"y":-1.51522},{"x":17.53691,"y":-1.506043},{"x":19.03707,"y":-2.029848},{"x":18.93472,"y":-7.274326},{"x":19.20114,"y":-8.927617},{"x":22.39211,"y":-7.533558},{"x":23.25742,"y":-7.609662},{"x":23.75232,"y":-7.498259}],[{"x":37.91449,"y":-20.31673},{"x":35.00881,"y":-20.31673},{"x":35.00881,"y":-20.94084},{"x":37.91449,"y":-20.94084},{"x":37.91449,"y":-20.31673}],[{"x":26.02585,"y":-6.64},{"x":24.817,"y":-6.64},{"x":24.817,"y":-6.970619},{"x":26.02585,"y":-6.970619},{"x":26.02585,"y":-6.64}],[{"x":11.77179,"y":-19.62465},{"x":11.83028,"y":-18.52067},{"x":13.4037,"y":-18.52042},{"x":13.44733,"y":-15.98955},{"x":13.68375,"y":-15.79114},{"x":13.66221,"y":-15.21871},{"x":13.32477,"y":-14.72645},{"x":11.799,"y":-14.733},{"x":11.589,"y":-14.723},{"x":9.954119,"y":-14.72645},{"x":10.04784,"y":-19.60156},{"x":10.24256,"y":-19.60833},{"x":10.2018,"y":-15.63915},{"x":12.86989,"y":-15.59822},{"x":12.99521,"y":-16.18124},{"x":13.21599,"y":-16.24457},{"x":13.21647,"y":-17.73367},{"x":11.58001,"y":-17.74002},{"x":11.55608,"y":-19.60504},{"x":11.77179,"y":-19.62465}],[{"x":22.90844,"y":-18.77619},{"x":23.78691,"y":-18.78369},{"x":23.8818,"y":-17.88425},{"x":23.83846,"y":-16.25285},{"x":24.49371,"y":-16.31531},{"x":24.48757,"y":-16.49074},{"x":27.01841,"y":-16.46032},{"x":27.30907,"y":-16.29564},{"x":28.57129,"y":-16.29163},{"x":28.57926,"y":-16.8445},{"x":28.90884,"y":-16.85584},{"x":28.90578,"y":-15.3694},{"x":15.61483,"y":-15.35862},{"x":15.62812,"y":-20.79835},{"x":17.24424,"y":-20.7777},{"x":17.25434,"y":-21.51932},{"x":17.56835,"y":-21.50746},{"x":17.57353,"y":-20.74102},{"x":17.96331,"y":-20.58899},{"x":17.9802,"y":-19.81679},{"x":15.96782,"y":-19.82635},{"x":15.96752,"y":-18.91302},{"x":16.61518,"y":-18.76492},{"x":16.62491,"y":-18.02716},{"x":16.45051,"y":-18.01276},{"x":16.46912,"y":-16.46037},{"x":16.88453,"y":-16.43662},{"x":17.00264,"y":-16.34876},{"x":21.93412,"y":-16.3298},{"x":21.9517,"y":-16.55123},{"x":23.38988,"y":-16.55636},{"x":23.38412,"y":-18.00589},{"x":22.89434,"y":-18.00573},{"x":22.90844,"y":-18.77619}],[{"x":21.63955,"y":-18.01692},{"x":17.88043,"y":-18.02346},{"x":17.88024,"y":-18.81244},{"x":18.13617,"y":-18.86934},{"x":21.36609,"y":-18.88367},{"x":21.63298,"y":-18.80225},{"x":21.63955,"y":-18.01692}],[{"x":25.04842,"y":-18.75886},{"x":25.3532,"y":-18.74552},{"x":25.34759,"y":-19.82735},{"x":21.61452,"y":-19.81656},{"x":21.65306,"y":-22.25628},{"x":22.08868,"y":-22.44897},{"x":22.14978,"y":-20.58658},{"x":25.36657,"y":-20.61784},{"x":25.35131,"y":-23.00266},{"x":24.53445,"y":-23.02254},{"x":24.5266,"y":-23.98506},{"x":25.34336,"y":-23.98594},{"x":25.33836,"y":-24.35742},{"x":25.65167,"y":-24.35947},{"x":25.67025,"y":-18.97481},{"x":28.91121,"y":-18.98343},{"x":28.9024,"y":-17.81323},{"x":28.5792,"y":-17.8131},{"x":28.57479,"y":-18.02147},{"x":25.03823,"y":-18.00087},{"x":25.04842,"y":-18.75886}],[{"x":26.86648,"y":-7.398125},{"x":26.58685,"y":-7.362242},{"x":26.25525,"y":-7.46819},{"x":26.14983,"y":-7.690572},{"x":26.36041,"y":-8.006925},{"x":26.76204,"y":-8.007652},{"x":27.03698,"y":-7.880741},{"x":27.13068,"y":-7.605845},{"x":26.86648,"y":-7.398125}],[{"x":5.790905,"y":-19.75676},{"x":3.727096,"y":-19.75676},{"x":3.727096,"y":-20.96208},{"x":5.790905,"y":-20.96208},{"x":5.790905,"y":-19.75676}],[{"x":26.83904,"y":-14.12032},{"x":26.22075,"y":-14.01883},{"x":25.39735,"y":-14.13572},{"x":24.71892,"y":-13.72345},{"x":25.51597,"y":-13.2925},{"x":27.56451,"y":-13.30547},{"x":28.30361,"y":-13.6028},{"x":28.42681,"y":-13.89908},{"x":26.83904,"y":-14.12032}],[{"x":13.3388,"y":-6.003677},{"x":12.77013,"y":-6.260994},{"x":12.46447,"y":-6.640699},{"x":12.43997,"y":-7.028212},{"x":12.30328,"y":-7.102982},{"x":12.30591,"y":-7.660471},{"x":12.69129,"y":-8.015654},{"x":13.37645,"y":-7.761706},{"x":13.74081,"y":-7.960196},{"x":14.04209,"y":-7.840932},{"x":14.03963,"y":-7.349671},{"x":13.97028,"y":-6.885252},{"x":13.74353,"y":-6.663803},{"x":13.74,"y":-6.37474},{"x":13.3388,"y":-6.003677}],[{"x":20.79322,"y":-7.794276},{"x":20.20507,"y":-7.589413},{"x":20.23226,"y":-7.977169},{"x":19.84346,"y":-8.353935},{"x":19.24475,"y":-8.107004},{"x":19.61344,"y":-7.113365},{"x":19.65041,"y":-6.401253},{"x":20.03278,"y":-6.080639},{"x":21.13224,"y":-6.860869},{"x":21.15035,"y":-7.431738},{"x":20.79322,"y":-7.794276}],[{"x":9.379,"y":-11.10174},{"x":8.338992,"y":-11.10174},{"x":8.338992,"y":-11.9455},{"x":9.379,"y":-11.9455},{"x":9.379,"y":-11.10174}],[{"x":11.147,"y":-11.10174},{"x":10.10699,"y":-11.10174},{"x":10.10699,"y":-11.9455},{"x":11.147,"y":-11.9455},{"x":11.147,"y":-11.10174}],[{"x":11.147,"y":-12.94574},{"x":10.10699,"y":-12.94574},{"x":10.10699,"y":-13.7895},{"x":11.147,"y":-13.7895},{"x":11.147,"y":-12.94574}],[{"x":9.37,"y":-12.94574},{"x":8.329992,"y":-12.94574},{"x":8.329992,"y":-13.7895},{"x":9.37,"y":-13.7895},{"x":9.37,"y":-12.94574}],[{"x":4.303,"y":-24.20208},{"x":3.323,"y":-24.20208},{"x":3.323,"y":-24.508},{"x":4.303,"y":-24.508},{"x":4.303,"y":-24.20208}],[{"x":22.72785,"y":-17.09251},{"x":22.22638,"y":-17.09251},{"x":22.22638,"y":-17.53884},{"x":22.72785,"y":-17.53884},{"x":22.72785,"y":-17.09251}],[{"x":24.79221,"y":-21.106},{"x":22.99592,"y":-21.106},{"x":22.99592,"y":-22.106},{"x":24.79221,"y":-22.106},{"x":24.79221,"y":-21.106}],[{"x":34.16841,"y":-5.455648},{"x":33.56845,"y":-5.455648},{"x":33.56845,"y":-5.682265},{"x":34.16841,"y":-5.682265},{"x":34.16841,"y":-5.455648}],[{"x":35.77344,"y":-6.916783},{"x":33.96344,"y":-6.916783},{"x":33.96344,"y":-7.673556},{"x":35.77344,"y":-7.673556},{"x":35.77344,"y":-6.916783}],[{"x":18.05003,"y":-25.48757},{"x":17.57003,"y":-25.48757},{"x":17.57003,"y":-25.70757},{"x":18.05003,"y":-25.70757},{"x":18.05003,"y":-25.48757}],[{"x":18.77,"y":-25.59},{"x":18.29,"y":-25.59},{"x":18.29,"y":-25.81},{"x":18.77,"y":-25.81},{"x":18.77,"y":-25.59}],[{"x":32.08207,"y":-11.78346},{"x":31.60206,"y":-11.78346},{"x":31.60206,"y":-12.00346},{"x":32.08207,"y":-12.00346},{"x":32.08207,"y":-11.78346}],[{"x":12.63623,"y":-11.89787},{"x":12.15623,"y":-11.89787},{"x":12.15623,"y":-12.11787},{"x":12.63623,"y":-12.11787},{"x":12.63623,"y":-11.89787}],[{"x":7.059857,"y":-16.24459},{"x":6.579858,"y":-16.24459},{"x":6.579858,"y":-16.46459},{"x":7.059857,"y":-16.46459},{"x":7.059857,"y":-16.24459}],[{"x":1.571452,"y":-10.26345},{"x":1.819386,"y":-10.43345},{"x":2.262032,"y":-10.0961},{"x":1.571452,"y":-10.26345}],[{"x":1.334,"y":-24.403},{"x":0.3184614,"y":-24.39593},{"x":0.3239999,"y":-24.973},{"x":0.674,"y":-24.973},{"x":0.6772294,"y":-24.77208},{"x":1.324769,"y":-24.75715},{"x":1.334,"y":-24.403}],[{"x":8.508034,"y":-23.93293},{"x":8.629685,"y":-24.40218},{"x":8.92936,"y":-24.96339},{"x":9.599236,"y":-25.21856},{"x":9.57372,"y":-22.93649},{"x":8.644652,"y":-23.21924},{"x":8.508034,"y":-23.93293}],[{"x":11.67719,"y":-16.53101},{"x":11.29618,"y":-16.53452},{"x":11.06358,"y":-16.58503},{"x":10.94267,"y":-16.71943},{"x":10.93917,"y":-16.87988},{"x":11.06962,"y":-17.01637},{"x":11.56154,"y":-17.0409},{"x":11.88714,"y":-16.91338},{"x":11.89764,"y":-16.71949},{"x":11.67719,"y":-16.53101}],[{"x":21.01822,"y":-17.32524},{"x":21.40012,"y":-17.168},{"x":21.40401,"y":-17.06523},{"x":21.00678,"y":-16.91912},{"x":20.329,"y":-16.81467},{"x":18.87623,"y":-16.81245},{"x":18.07026,"y":-16.91774},{"x":17.70623,"y":-17.03189},{"x":17.71012,"y":-17.188},{"x":17.74012,"y":-17.208},{"x":18.04012,"y":-17.328},{"x":18.83012,"y":-17.448},{"x":20.22012,"y":-17.448},{"x":21.01822,"y":-17.32524}],[{"x":27.34005,"y":-6.938899},{"x":27.93693,"y":-7.230727},{"x":28.42066,"y":-6.909329},{"x":27.80876,"y":-6.653845},{"x":27.34005,"y":-6.938899}],[{"x":16.65074,"y":-10.12311},{"x":16.00821,"y":-10.30374},{"x":15.95542,"y":-10.52961},{"x":16.30542,"y":-10.77919},{"x":16.83255,"y":-10.98451},{"x":17.15542,"y":-10.73961},{"x":17.15542,"y":-10.18961},{"x":16.65074,"y":-10.12311}],[{"x":4.995,"y":-3.943596},{"x":4.688505,"y":-4.143763},{"x":4.360964,"y":-3.921563},{"x":4.666411,"y":-3.684916},{"x":4.995,"y":-3.943596}],[{"x":24.537,"y":-2.915734},{"x":24.2305,"y":-3.115901},{"x":23.90296,"y":-2.893701},{"x":24.20841,"y":-2.657055},{"x":24.537,"y":-2.915734}],[{"x":28.8746,"y":-18.89537},{"x":32.58327,"y":-17.87351},{"x":31.65491,"y":-17.43377},{"x":28.904,"y":-18.19115},{"x":28.8746,"y":-18.89537}],[{"x":31.08879,"y":-13.97953},{"x":30.4176,"y":-13.91366},{"x":30.037,"y":-14.07843},{"x":30.39278,"y":-14.2843},{"x":30.83512,"y":-14.17834},{"x":31.08879,"y":-13.97953}],[{"x":32.36998,"y":-12.94724},{"x":33.05407,"y":-13.68856},{"x":34.27784,"y":-13.63817},{"x":32.97806,"y":-11.86354},{"x":32.30158,"y":-11.74354},{"x":32.36998,"y":-12.94724}],[{"x":-0.3,"y":0},{"x":0.3,"y":0.3},{"x":0.6,"y":0},{"x":0.3,"y":0.3},{"x":-0.6,"y":0}]] \ No newline at end of file diff --git a/assets/collision/skeld.json b/assets/collision/skeld.json new file mode 100644 index 0000000..2ad2528 --- /dev/null +++ b/assets/collision/skeld.json @@ -0,0 +1 @@ +[[{"x":0.2362284,"y":-7.726103},{"x":2.013968,"y":-7.715738},{"x":2.011803,"y":-10.30677},{"x":6.082888,"y":-10.30429},{"x":6.962117,"y":-9.435246},{"x":6.962895,"y":-6.817364},{"x":6.706806,"y":-6.619056},{"x":6.232102,"y":-6.602984},{"x":6.176251,"y":-6.938542},{"x":3.060317,"y":-6.960407},{"x":3.011859,"y":-6.6034},{"x":0.2607511,"y":-6.571015}],[{"x":0.8954936,"y":-6.595578},{"x":0.08411826,"y":-6.767831},{"x":0.05762036,"y":-4.452175}],[{"x":0.07118475,"y":-8.635859},{"x":0.08622147,"y":-7.654803},{"x":0.9666418,"y":-7.737996}],[{"x":-1.460379,"y":-8.631944},{"x":-1.462344,"y":-4.483645}],[{"x":-7.368402,"y":1.584177},{"x":-6.555078,"y":1.57672},{"x":-6.557156,"y":4.008165},{"x":-4.60883,"y":5.933904},{"x":1.670171,"y":5.95224},{"x":4.543502,"y":3.066632},{"x":4.553433,"y":1.587478},{"x":5.637702,"y":1.583386}],[{"x":-0.2999234,"y":-5.142708},{"x":-0.2951662,"y":-4.452511},{"x":1.997185,"y":-4.436467},{"x":4.564693,"y":-1.878594},{"x":4.549082,"y":0.4435357},{"x":5.659974,"y":0.4535991}],[{"x":-7.401506,"y":0.4553653},{"x":-6.580028,"y":0.4451815},{"x":-6.567801,"y":-1.97265},{"x":-4.072343,"y":-4.44449},{"x":-1.812371,"y":-4.433851},{"x":-1.811063,"y":-5.56378}],[{"x":14.56604,"y":-4.229232},{"x":15.64946,"y":-4.230859},{"x":15.65051,"y":-2.836758},{"x":17.51895,"y":-2.849281},{"x":19.1273,"y":-4.154813},{"x":19.13222,"y":-5.43117},{"x":17.50414,"y":-6.75653},{"x":15.63534,"y":-6.741789},{"x":15.64649,"y":-5.373594},{"x":14.97682,"y":-5.367826}],[{"x":4.952006,"y":1.57443},{"x":6.752982,"y":1.585721},{"x":7.033926,"y":1.475315},{"x":7.801225,"y":1.480557},{"x":7.989945,"y":2.125443},{"x":8.366776,"y":2.596887},{"x":8.386627,"y":3.642199},{"x":9.479401,"y":3.64006},{"x":11.58195,"y":1.583232},{"x":10.50412,"y":1.58118},{"x":10.46297,"y":1.088706},{"x":10.14301,"y":0.6494932},{"x":10.15121,"y":-1.103494},{"x":10.2626,"y":-1.332904}],[{"x":4.956852,"y":0.4148467},{"x":7.101061,"y":0.4562553},{"x":7.263814,"y":0.3892393},{"x":8.363285,"y":0.3629824},{"x":8.846294,"y":0.135911},{"x":8.820242,"y":-1.119593},{"x":8.771524,"y":-1.311896}],[{"x":8.222275,"y":-4.019724},{"x":7.755623,"y":-4.004018},{"x":7.610951,"y":-4.239169},{"x":7.185065,"y":-4.238694},{"x":7.009696,"y":-4.505672},{"x":7.019401,"y":-5.130587},{"x":6.245888,"y":-5.137787},{"x":6.252361,"y":-4.420362},{"x":6.121649,"y":-4.258716},{"x":5.744867,"y":-4.264971},{"x":5.575344,"y":-4.418731},{"x":5.553154,"y":-5.131573},{"x":4.840867,"y":-5.133445},{"x":4.825717,"y":-4.378825},{"x":4.445279,"y":-4.140391},{"x":5.200883,"y":-3.392043},{"x":5.570336,"y":-3.395252},{"x":6.07529,"y":-2.981499},{"x":6.987266,"y":-2.986429},{"x":7.193809,"y":-2.857087},{"x":8.241753,"y":-2.880474}],[{"x":6.565016,"y":-11.72411},{"x":6.987641,"y":-11.79513},{"x":7.896163,"y":-11.79405},{"x":8.188145,"y":-11.67882},{"x":8.455678,"y":-11.41463},{"x":8.826189,"y":-11.20363},{"x":8.838142,"y":-10.35416},{"x":8.728677,"y":-9.931278}],[{"x":6.575041,"y":-12.87208},{"x":7.911303,"y":-12.93333},{"x":8.129161,"y":-13.3627},{"x":8.807487,"y":-13.87456},{"x":8.824697,"y":-14.20615},{"x":7.358915,"y":-14.20941},{"x":7.3613,"y":-14.74166},{"x":8.720889,"y":-14.71317},{"x":8.717556,"y":-14.93626},{"x":9.488122,"y":-14.93082},{"x":10.1447,"y":-14.29308},{"x":10.12637,"y":-13.5075},{"x":10.55909,"y":-12.89536},{"x":10.55823,"y":-12.24413},{"x":10.14061,"y":-11.52396},{"x":10.14535,"y":-10.80471},{"x":11.66409,"y":-10.79793},{"x":11.65609,"y":-10.42626},{"x":10.26556,"y":-10.42765},{"x":10.24841,"y":-9.86418}],[{"x":15.17467,"y":-4.419531},{"x":12.64121,"y":-4.44558},{"x":12.63906,"y":-2.948531},{"x":10.23936,"y":-3.092088},{"x":10.22813,"y":-0.9974822}],[{"x":10.24439,"y":-10.28774},{"x":10.25753,"y":-7.140551},{"x":12.62506,"y":-7.155302},{"x":12.63361,"y":-5.415534},{"x":15.18507,"y":-5.4197}],[{"x":8.746116,"y":-10.24708},{"x":8.741796,"y":-6.048161},{"x":11.1656,"y":-6.042319},{"x":11.16558,"y":-4.080832},{"x":7.765801,"y":-4.095026}],[{"x":7.799847,"y":-2.876018},{"x":8.758554,"y":-2.886162},{"x":8.773032,"y":-0.922443}],[{"x":-14.81697,"y":-11.22099},{"x":-11.38458,"y":-11.21203},{"x":-11.37703,"y":-14.06057},{"x":-10.22454,"y":-14.0326},{"x":-10.21113,"y":-10.33264},{"x":-7.675366,"y":-10.31545}],[{"x":-7.695002,"y":-10.31545},{"x":-7.695909,"y":-9.451391},{"x":-10.11512,"y":-9.437295},{"x":-10.13741,"y":-7.76803},{"x":-6.895009,"y":-7.855294},{"x":-6.745681,"y":-7.990388},{"x":-5.197094,"y":-7.945567},{"x":-5.198377,"y":-8.148785},{"x":-6.449782,"y":-9.321177},{"x":-6.435663,"y":-11.45714},{"x":-7.44524,"y":-12.40876},{"x":-8.85337,"y":-12.36879},{"x":-8.805441,"y":-14.07697}],[{"x":-8.805441,"y":-14.07697},{"x":-5.374582,"y":-14.06365}],[{"x":-14.81383,"y":-12.35692},{"x":-12.88635,"y":-12.35124},{"x":-12.88583,"y":-15.19543},{"x":-5.270284,"y":-15.17392}],[{"x":-18.90468,"y":-4.917564},{"x":-19.21733,"y":-4.914915},{"x":-19.25125,"y":-3.487691},{"x":-20.80061,"y":-3.466249},{"x":-20.82514,"y":-1.781794},{"x":-21.77498,"y":-1.781393},{"x":-22.19863,"y":-2.061523},{"x":-22.18964,"y":-2.351079},{"x":-22.92343,"y":-2.855991},{"x":-22.93843,"y":-3.568924},{"x":-22.23674,"y":-3.921036},{"x":-21.91414,"y":-4.717618},{"x":-21.90926,"y":-5.25623},{"x":-21.48744,"y":-5.424995},{"x":-21.48222,"y":-5.773309},{"x":-21.60671,"y":-5.917229},{"x":-21.98811,"y":-5.893168},{"x":-21.98224,"y":-6.374194},{"x":-22.23363,"y":-6.524475},{"x":-22.58218,"y":-6.172986},{"x":-22.88113,"y":-6.147035},{"x":-22.92309,"y":-8.299733},{"x":-21.77963,"y":-9.033682},{"x":-20.80727,"y":-9.037098},{"x":-20.82744,"y":-7.339758},{"x":-19.23684,"y":-7.333115},{"x":-19.2312,"y":-6.027665},{"x":-18.87985,"y":-6.043848}],[{"x":1.514437,"y":-12.88248},{"x":0.9000312,"y":-12.87428},{"x":0.9177916,"y":-17.50703},{"x":-3.021377,"y":-17.54297},{"x":-5.077038,"y":-15.46409},{"x":-5.080357,"y":-15.18036},{"x":-5.737282,"y":-15.17595}],[{"x":0.02917442,"y":-7.893129},{"x":0.03507514,"y":-9.129563},{"x":0.9134803,"y":-9.266632},{"x":0.9189174,"y":-11.73293},{"x":1.625746,"y":-11.7288}],[{"x":-5.709477,"y":-14.03647},{"x":-5.07719,"y":-14.0213},{"x":-5.074898,"y":-11.00069},{"x":-4.480464,"y":-10.70592},{"x":-4.46635,"y":-10.3222},{"x":-2.930291,"y":-9.498736},{"x":-2.852392,"y":-9.117779},{"x":-1.468678,"y":-9.132879},{"x":-1.450918,"y":-8.018958}],[{"x":-17.66159,"y":-8.313709},{"x":-17.66675,"y":-9.546221},{"x":-19.51328,"y":-9.537824},{"x":-19.51333,"y":-13.05503},{"x":-18.33128,"y":-13.99051},{"x":-14.8677,"y":-13.99421},{"x":-14.86014,"y":-12.34251},{"x":-14.46036,"y":-12.34108}],[{"x":-16.16101,"y":-8.316375},{"x":-16.16359,"y":-9.572023},{"x":-14.88622,"y":-9.570913},{"x":-14.88741,"y":-11.21787},{"x":-14.32632,"y":-11.20666}],[{"x":0.97958,"y":-11.76019},{"x":6.992189,"y":-11.7485}],[{"x":0.9887347,"y":-12.91188},{"x":4.368167,"y":-12.876},{"x":4.358963,"y":-13.96144}],[{"x":5.864928,"y":-13.42998},{"x":5.870777,"y":-12.87888},{"x":6.897767,"y":-12.87967}],[{"x":-15.03895,"y":1.597145},{"x":-6.570155,"y":1.575777}],[{"x":-15.1985,"y":0.4579078},{"x":-10.23706,"y":0.4484872},{"x":-10.23486,"y":-0.4350799}],[{"x":-8.766523,"y":-0.6480405},{"x":-8.767955,"y":0.4350485},{"x":-6.58421,"y":0.4233765}],[{"x":-14.63285,"y":-4.891836},{"x":-16.24534,"y":-4.88406},{"x":-16.24988,"y":-1.635784}],[{"x":-17.56018,"y":-1.635784},{"x":-17.5364,"y":-3.693102},{"x":-17.65632,"y":-4.623451},{"x":-17.45963,"y":-4.732869},{"x":-17.46077,"y":-4.988844},{"x":-19.02956,"y":-5.000385}],[{"x":-16.16554,"y":-9.545579},{"x":-16.20093,"y":-7.663517},{"x":-16.33388,"y":-7.573332},{"x":-16.31617,"y":-6.972282},{"x":-16.15963,"y":-6.06042},{"x":-14.49686,"y":-6.07415}],[{"x":-18.98414,"y":-6.040682},{"x":-17.65042,"y":-6.052687},{"x":-17.67024,"y":-9.558265}],[{"x":-14.56532,"y":1.662872},{"x":-14.90609,"y":1.650298},{"x":-14.88668,"y":2.885079},{"x":-18.32795,"y":2.894751},{"x":-19.52896,"y":1.864622},{"x":-19.52396,"y":-1.570519},{"x":-17.71379,"y":-1.537421},{"x":-17.68546,"y":-2.032646}],[{"x":-14.55745,"y":0.4402371},{"x":-14.85756,"y":0.4410935},{"x":-14.83369,"y":-1.524606},{"x":-16.12798,"y":-1.591758},{"x":-16.13887,"y":-2.05983}],[{"x":4.390248,"y":-12.89099},{"x":4.412769,"y":-14.44803},{"x":4.073301,"y":-14.74718},{"x":1.539549,"y":-14.71471},{"x":1.546466,"y":-15.85303},{"x":2.15479,"y":-15.84039},{"x":2.124448,"y":-16.75854},{"x":1.752654,"y":-16.75457},{"x":2.532989,"y":-17.46411},{"x":2.886859,"y":-17.46804},{"x":2.883881,"y":-16.92094},{"x":3.369814,"y":-17.0816},{"x":3.656673,"y":-16.95642},{"x":4.00171,"y":-16.79352},{"x":4.377112,"y":-16.82868},{"x":4.413591,"y":-17.07326},{"x":4.874448,"y":-17.04011},{"x":5.397761,"y":-16.8904},{"x":5.406366,"y":-17.49508},{"x":5.998445,"y":-16.9528},{"x":6.004679,"y":-15.85465},{"x":6.492789,"y":-15.85043},{"x":6.49343,"y":-14.44846},{"x":5.879263,"y":-14.47317},{"x":5.880812,"y":-12.86388}],[{"x":-15.45731,"y":-4.882467},{"x":-14.46863,"y":-4.895476},{"x":-14.47101,"y":-3.812231},{"x":-13.55466,"y":-2.877563},{"x":-13.45982,"y":-3.3577},{"x":-13.1225,"y":-3.353385},{"x":-13.08784,"y":-2.893233},{"x":-12.57413,"y":-2.867874},{"x":-11.92476,"y":-3.50242},{"x":-11.93444,"y":-4.685921},{"x":-12.92189,"y":-4.678928},{"x":-12.92335,"y":-5.555384},{"x":-12.75325,"y":-5.688099},{"x":-12.42195,"y":-5.651134},{"x":-12.38069,"y":-5.922665},{"x":-11.94423,"y":-5.987844},{"x":-11.91953,"y":-6.434968},{"x":-12.05945,"y":-6.434929},{"x":-12.06055,"y":-7.300493},{"x":-14.32722,"y":-7.307874},{"x":-14.32708,"y":-6.44428},{"x":-14.46767,"y":-6.447207},{"x":-14.47318,"y":-6.049348},{"x":-15.37626,"y":-6.048749}],[{"x":-9.896128,"y":0.09396344},{"x":-9.906479,"y":-1.014971},{"x":-11.16,"y":-1.020813},{"x":-11.12743,"y":-4.796037},{"x":-10.33474,"y":-5.799318},{"x":-5.22831,"y":-5.799926},{"x":-5.220803,"y":-5.351019},{"x":-5.824704,"y":-4.780326},{"x":-5.461809,"y":-4.228658},{"x":-6.392942,"y":-3.4428},{"x":-6.636661,"y":-3.934034},{"x":-6.987354,"y":-3.613638},{"x":-6.967412,"y":-1.019102},{"x":-8.405022,"y":-1.028633},{"x":-8.408207,"y":0.2032199}],[{"x":3.33,"y":-8.261624},{"x":3.33,"y":-9.313201},{"x":5.765989,"y":-9.313227},{"x":5.742,"y":-8.261624},{"x":3.33,"y":-8.261624}],[{"x":17.88076,"y":-6.179931},{"x":17.45793,"y":-6.386068},{"x":17.72214,"y":-6.643311},{"x":18.11662,"y":-6.310138},{"x":17.88076,"y":-6.179931}],[{"x":18.50979,"y":-4.473197},{"x":18.06764,"y":-4.467421},{"x":17.77722,"y":-4.937558},{"x":18.57143,"y":-4.977489},{"x":18.50979,"y":-4.473197}],[{"x":17.65212,"y":-3.412386},{"x":17.2644,"y":-3.7608},{"x":17.72674,"y":-3.964799},{"x":18.16902,"y":-3.658661},{"x":17.65212,"y":-3.412386}],[{"x":-0.6781706,"y":-13.55313},{"x":-0.5186334,"y":-13.65023},{"x":-0.356359,"y":-13.66372},{"x":-0.1854412,"y":-13.57278},{"x":-0.1816655,"y":-13.09209},{"x":-0.2930961,"y":-12.94377}],[{"x":-3.246386,"y":-12.28785},{"x":-3.709773,"y":-12.07104},{"x":-4.100151,"y":-12.41943},{"x":-4.086681,"y":-12.96832},{"x":-3.531352,"y":-13.16967},{"x":-3.234791,"y":-12.87577},{"x":-3.238448,"y":-13.60751},{"x":-2.558987,"y":-13.60691},{"x":-2.55681,"y":-14.15934},{"x":-2.294851,"y":-14.15913},{"x":-2.283567,"y":-14.56503},{"x":-1.581326,"y":-14.83519},{"x":-1.148905,"y":-14.39955},{"x":-1.143201,"y":-14.16828},{"x":-0.6143369,"y":-14.17394},{"x":-0.6246856,"y":-13.58801},{"x":-0.2763608,"y":-12.96624},{"x":-0.283671,"y":-11.13291},{"x":-1.927792,"y":-11.11836},{"x":-1.925223,"y":-11.35053},{"x":-3.245068,"y":-11.37238},{"x":-3.246306,"y":-12.29369}],[{"x":-15.80511,"y":-11.39031},{"x":-15.91522,"y":-10.76074},{"x":-16.07204,"y":-10.53166},{"x":-18.71611,"y":-10.4536},{"x":-19.24034,"y":-10.09965},{"x":-19.53993,"y":-10.4893},{"x":-19.49779,"y":-13.02382},{"x":-18.53159,"y":-13.02814},{"x":-18.40778,"y":-12.43455},{"x":-16.19409,"y":-12.45347},{"x":-15.88496,"y":-12.03066},{"x":-15.80511,"y":-11.39031}],[{"x":-15.83031,"y":0.8088304},{"x":-15.94042,"y":1.436132},{"x":-16.09725,"y":1.664383},{"x":-18.74131,"y":1.742164},{"x":-19.26554,"y":2.094836},{"x":-19.56513,"y":1.706586},{"x":-19.52299,"y":-0.8187767},{"x":-18.55679,"y":-0.823085},{"x":-18.43298,"y":-0.2316318},{"x":-16.21929,"y":-0.2504853},{"x":-15.91016,"y":0.1707977},{"x":-15.83031,"y":0.8088304}],[{"x":-16.22955,"y":-12.49125},{"x":-15.74837,"y":-12.49532},{"x":-15.72997,"y":-10.52501},{"x":-16.2295,"y":-10.52501}],[{"x":-16.25475,"y":-0.2881263},{"x":-15.77357,"y":-0.2921864},{"x":-15.75517,"y":1.671011},{"x":-16.2547,"y":1.671011}],[{"x":16.476,"y":-5.988},{"x":15.54,"y":-5.988},{"x":15.54,"y":-6.78},{"x":16.476,"y":-6.78},{"x":16.476,"y":-5.988}],[{"x":16.476,"y":-2.772},{"x":15.54,"y":-2.772},{"x":15.54,"y":-3.564},{"x":16.476,"y":-3.564},{"x":16.476,"y":-2.772}],[{"x":-6.957,"y":-2.549},{"x":-8.067,"y":-2.549},{"x":-8.067,"y":-3.379},{"x":-6.957,"y":-3.379},{"x":-6.957,"y":-2.549}],[{"x":-10.077,"y":-2.549},{"x":-11.187,"y":-2.549},{"x":-11.187,"y":-3.379},{"x":-10.077,"y":-3.379},{"x":-10.077,"y":-2.549}],[{"x":-6.957,"y":-1.229},{"x":-8.067,"y":-1.229},{"x":-8.067,"y":-2.059},{"x":-6.957,"y":-2.059},{"x":-6.957,"y":-1.229}],[{"x":-10.077,"y":-1.229},{"x":-11.187,"y":-1.229},{"x":-11.187,"y":-2.059},{"x":-10.077,"y":-2.059},{"x":-10.077,"y":-1.229}],[{"x":-2.910683,"y":2.348959},{"x":-2.188303,"y":2.785507},{"x":-1.933683,"y":3.511235},{"x":-2.064806,"y":3.991863},{"x":-2.610682,"y":4.384067},{"x":-3.341935,"y":4.587026},{"x":-4.275301,"y":4.375321},{"x":-4.825263,"y":3.795358},{"x":-4.775846,"y":2.94697},{"x":-4.058721,"y":2.382533},{"x":-2.910683,"y":2.348959}],[{"x":2.014117,"y":2.371759},{"x":2.736497,"y":2.808307},{"x":2.991117,"y":3.534035},{"x":2.859994,"y":4.014663},{"x":2.314118,"y":4.406867},{"x":1.582865,"y":4.609826},{"x":0.6494999,"y":4.398121},{"x":0.09953787,"y":3.818158},{"x":0.1489543,"y":2.96977},{"x":0.8660797,"y":2.405333},{"x":2.014117,"y":2.371759}],[{"x":-0.3918829,"y":-0.2814406},{"x":0.3304967,"y":0.1551068},{"x":0.5851166,"y":0.8808349},{"x":0.4539942,"y":1.361462},{"x":-0.09188195,"y":1.753667},{"x":-0.8231351,"y":1.956626},{"x":-1.7565,"y":1.744921},{"x":-2.306462,"y":1.164958},{"x":-2.257046,"y":0.31657},{"x":-1.53992,"y":-0.2478667},{"x":-0.3918829,"y":-0.2814406}],[{"x":-2.939483,"y":-2.830241},{"x":-2.217103,"y":-2.393693},{"x":-1.962484,"y":-1.667965},{"x":-2.093606,"y":-1.187338},{"x":-2.639482,"y":-0.7951334},{"x":-3.370735,"y":-0.5921744},{"x":-4.304101,"y":-0.8038793},{"x":-4.854062,"y":-1.383842},{"x":-4.804646,"y":-2.23223},{"x":-4.087521,"y":-2.796667},{"x":-2.939483,"y":-2.830241}],[{"x":2.054917,"y":-2.830241},{"x":2.777297,"y":-2.393693},{"x":3.031917,"y":-1.667965},{"x":2.900795,"y":-1.187338},{"x":2.354918,"y":-0.7951334},{"x":1.623665,"y":-0.5921744},{"x":0.6902999,"y":-0.8038793},{"x":0.1403379,"y":-1.383842},{"x":0.1897544,"y":-2.23223},{"x":0.9068797,"y":-2.796667},{"x":2.054917,"y":-2.830241}]] \ No newline at end of file diff --git a/gamedataprocessor.go b/gamedataprocessor.go new file mode 100644 index 0000000..3ec2c50 --- /dev/null +++ b/gamedataprocessor.go @@ -0,0 +1,157 @@ +package main + +import ( + "encoding/hex" + "fmt" + + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/gamedata" + "codehub.onpointcoding.net/sean/go-susapp/innernetobjects" + "codehub.onpointcoding.net/sean/go-susapp/mapdata" + "codehub.onpointcoding.net/sean/go-susapp/packets" + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type GameDataProcessor struct { + sus *SusApp + gameRpcProcessor *GameRPCProcessor + subpacketTagMap map[byte]func(net *protocol.PacketHandler, GameID int32, h *protocol.Hazel) +} + +func NewGameDataProcessor(sus *SusApp) *GameDataProcessor { + proc := &GameDataProcessor{sus: sus, gameRpcProcessor: NewGameRPCProcessor(sus)} + proc.subpacketTagMap = map[byte]func(net *protocol.PacketHandler, GameID int32, h *protocol.Hazel){ + 0x01: proc.captureGameData, + 0x02: proc.captureGameRPC, + 0x04: proc.captureGameSpawn, + 0x05: proc.captureGameDespawn, + } + return proc +} + +func (proc *GameDataProcessor) receiveGameDataSubpacket(net *protocol.PacketHandler, GameID int32, h []*protocol.Hazel) { + for i := 0; i < len(h); i++ { + v, ok := proc.subpacketTagMap[h[i].GetTag()] + if ok { + v(net, GameID, h[i]) + } else { + fmt.Printf("Unable to parse gamedata subpackets with tag of %x\n", h[i].GetTag()) + fmt.Printf("Packet: %v\n", hex.EncodeToString(h[i].GetUnderlyingPacket().Dump())) + } + } +} + +func (proc *GameDataProcessor) captureGameData(net *protocol.PacketHandler, GameID int32, h *protocol.Hazel) { + a := &gamedata.DataH2G{} + a.Read(h.GetUnderlyingPacket()) + + netId := a.Component.NetID + if netId != 0 { + b, ok := proc.sus.state.CurrentGame.NetObjects[netId] + if ok { + (*b).Read(a.Component.RawData, false) + } + } +} + +func (proc *GameDataProcessor) captureGameRPC(net *protocol.PacketHandler, GameID int32, h *protocol.Hazel) { + proc.gameRpcProcessor.receiveGameRpcCall(net, GameID, gamedata.NewRpcFromHazel(h)) +} + +func (proc *GameDataProcessor) captureGameSpawn(net *protocol.PacketHandler, GameID int32, h *protocol.Hazel) { + a := &gamedata.SpawnH2G{} + a.Read(h.GetUnderlyingPacket()) + + if len(a.Components) >= 1 { + z := a.Components[0] + netId := z.NetID + switch a.SpawnType { + case enum.SPAWNTYPE_GAME_DATA: + b := &innernetobjects.GameData{} + b.Read(z.ComponentData.GetUnderlyingPacket(), true) + var c innernetobjects.InnerNetObject = b + proc.sus.state.CurrentGame.NetObjects[netId] = &c + proc.sus.state.CurrentGame.GameDataNetID = netId + fmt.Printf("GameData Net ID: %v\n", netId) + case enum.SPAWNTYPE_SKELD_SHIP_STATUS: + b := innernetobjects.NewSkeldShipStatus() + b.Read(z.ComponentData.GetUnderlyingPacket(), true) + var c innernetobjects.InnerNetObject = b + proc.sus.state.CurrentGame.NetObjects[netId] = &c + proc.sus.state.CurrentGame.MapNetObjectNetID = netId + fmt.Printf("SkeldShipStatus Net ID: %v\n", netId) + case enum.SPAWNTYPE_MIRA_SHIP_STATUS: + b := innernetobjects.NewMiraShipStatus() + b.Read(z.ComponentData.GetUnderlyingPacket(), true) + var c innernetobjects.InnerNetObject = b + proc.sus.state.CurrentGame.NetObjects[netId] = &c + proc.sus.state.CurrentGame.MapNetObjectNetID = netId + fmt.Printf("MiraShipStatus Net ID: %v\n", netId) + case enum.SPAWNTYPE_POLUS_SHIP_STATUS: + b := innernetobjects.NewPolusShipStatus() + b.Read(z.ComponentData.GetUnderlyingPacket(), true) + var c innernetobjects.InnerNetObject = b + proc.sus.state.CurrentGame.NetObjects[netId] = &c + proc.sus.state.CurrentGame.MapNetObjectNetID = netId + fmt.Printf("PolusShipStatus Net ID: %v\n", netId) + case enum.SPAWNTYPE_PLAYER_CONTROL: + b := &innernetobjects.PlayerControl{} + b.Read(z.ComponentData.GetUnderlyingPacket(), true) + var c innernetobjects.InnerNetObject = b + proc.sus.state.CurrentGame.NetObjects[netId] = &c + y := &PlayerObject{} + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerNetID[netId] = y + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerClientID[a.OwnerID] = y + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerClientID[a.OwnerID].PlayerClientNetID = netId + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerClientID[a.OwnerID].PlayerControllerNetID = netId + fmt.Printf("Player Net ID: %v\n", netId) + fmt.Printf("Player Owner ID: %v\n", a.OwnerID) + + isMyPlayer := a.OwnerID == proc.sus.state.CurrentGame.ClientID + if isMyPlayer { + fmt.Println("Owner ID matches current Client ID so its my player controller") + proc.sus.state.CurrentGame.ClientPlayerNetID = z.NetID + + // Send RPC CheckName + proc.sus.state.CurrentGame.CheckNameNonce = net.GetNonce() + var d1 gamedata.RpcSub = &gamedata.RpcCheckName{Name: proc.sus.state.Settings.PlayerName} + var d2 packets.GameDataSubpacket = gamedata.NewRpcFromRpcSub(proc.sus.state.CurrentGame.ClientPlayerNetID, &d1) + var d3 protocol.HazelPayload = packets.NewGameDataToSingleSubpacket(proc.sus.state.CurrentGame.GameID, proc.sus.state.CurrentGame.HostClientID, &d2) + net.SendReliablePacket(proc.sus.state.CurrentGame.CheckNameNonce, []*protocol.Hazel{protocol.NewHazelFromPayload(&d3)}) + + // Send RPC CheckColor + proc.sus.state.CurrentGame.CheckColorNonce = net.GetNonce() + var e1 gamedata.RpcSub = &gamedata.RpcCheckColor{Color: proc.sus.state.Settings.PlayerColor} + var e2 packets.GameDataSubpacket = gamedata.NewRpcFromRpcSub(proc.sus.state.CurrentGame.ClientPlayerNetID, &e1) + var e3 protocol.HazelPayload = packets.NewGameDataToSingleSubpacket(proc.sus.state.CurrentGame.GameID, proc.sus.state.CurrentGame.HostClientID, &e2) + net.SendReliablePacket(proc.sus.state.CurrentGame.CheckColorNonce, []*protocol.Hazel{protocol.NewHazelFromPayload(&e3)}) + } + + if len(a.Components) >= 2 { + y := a.Components[1].NetID + d := innernetobjects.PlayerPhysics{} + d.Read(a.Components[1].ComponentData.GetUnderlyingPacket(), true) + var e innernetobjects.InnerNetObject = &d + proc.sus.state.CurrentGame.NetObjects[y] = &e + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerClientID[a.OwnerID].PlayerPhysicsNetID = y + } + if len(a.Components) >= 3 { + y := a.Components[2].NetID + d := innernetobjects.PlayerNetworkTransform{} + d.Read(a.Components[2].ComponentData.GetUnderlyingPacket(), true) + var e innernetobjects.InnerNetObject = &d + proc.sus.state.CurrentGame.NetObjects[y] = &e + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerClientID[a.OwnerID].PlayerNetworkTransform = y + + if isMyPlayer { + d.TargetPosition = mapdata.LobbySpawnPositions[int(b.PlayerID)%len(mapdata.LobbySpawnPositions)] + proc.sus.movementProcessor.Tick(0) + } + } + } + } +} + +func (proc *GameDataProcessor) captureGameDespawn(net *protocol.PacketHandler, GameID int32, h *protocol.Hazel) { + +} diff --git a/gamerpcprocessor.go b/gamerpcprocessor.go new file mode 100644 index 0000000..fb24609 --- /dev/null +++ b/gamerpcprocessor.go @@ -0,0 +1,101 @@ +package main + +import ( + "encoding/hex" + "fmt" + + "codehub.onpointcoding.net/sean/go-susapp/gamedata" + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type GameRPCProcessor struct { + sus *SusApp + rpcCallMap map[byte]func(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) +} + +func NewGameRPCProcessor(sus *SusApp) *GameRPCProcessor { + proc := &GameRPCProcessor{sus: sus} + proc.rpcCallMap = map[byte]func(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc){ + 0x00: proc.captureRpcPlayAnimation, + 0x02: proc.captureRpcSyncSettings, + 0x03: proc.captureRpcSetInfected, + 0x04: proc.captureRpcExiled, + 0x06: proc.captureRpcSetName, + 0x08: proc.captureRpcSetColor, + 0x09: proc.captureRpcSetHat, + 0x0a: proc.captureRpcSetSkin, + 0x11: proc.captureRpcSetPet, + 0x12: proc.captureRpcSetStartCounter, + } + return proc +} + +func (proc *GameRPCProcessor) receiveGameRpcCall(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + v, ok := proc.rpcCallMap[rpc.RPCCallID] + if ok { + v(net, GameID, rpc) + } else { + fmt.Printf("Unable to parse gamedata rpc call with id of %x\n", rpc.RPCCallID) + fmt.Printf("Packet: %v\n", hex.EncodeToString(rpc.UnderlyingHazel.GetUnderlyingPacket().Dump())) + } +} + +func (proc *GameRPCProcessor) captureRpcPlayAnimation(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + +} + +func (proc *GameRPCProcessor) captureRpcSyncSettings(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + a := &gamedata.RpcSyncSettings{} + a.ReadFromPacket(rpc.UnderlyingHazel.GetUnderlyingPacket()) + if a.Options.IsDefaults { + proc.sus.state.CurrentGame.GameOptions = protocol.NewDefaultGameOptionsData() + } else { + proc.sus.state.CurrentGame.GameOptions = a.Options + } +} + +func (proc *GameRPCProcessor) captureRpcSetInfected(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + +} + +func (proc *GameRPCProcessor) captureRpcExiled(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + +} + +func (proc *GameRPCProcessor) captureRpcSetName(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + a := &gamedata.RpcSetName{} + a.ReadFromPacket(rpc.UnderlyingHazel.GetUnderlyingPacket()) + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerNetID[rpc.SenderNetID].PlayerName = a.Name +} + +func (proc *GameRPCProcessor) captureRpcSetColor(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + a := &gamedata.RpcSetColor{} + a.ReadFromPacket(rpc.UnderlyingHazel.GetUnderlyingPacket()) + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerNetID[rpc.SenderNetID].PlayerColor = a.Color +} + +func (proc *GameRPCProcessor) captureRpcSetHat(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + a := &gamedata.RpcSetHat{} + a.ReadFromPacket(rpc.UnderlyingHazel.GetUnderlyingPacket()) + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerNetID[rpc.SenderNetID].PlayerHat = a.HatID +} + +func (proc *GameRPCProcessor) captureRpcSetSkin(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + a := &gamedata.RpcSetSkin{} + a.ReadFromPacket(rpc.UnderlyingHazel.GetUnderlyingPacket()) + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerNetID[rpc.SenderNetID].PlayerSkin = a.SkinID +} + +func (proc *GameRPCProcessor) captureRpcSetPet(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + a := &gamedata.RpcSetPet{} + a.ReadFromPacket(rpc.UnderlyingHazel.GetUnderlyingPacket()) + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerNetID[rpc.SenderNetID].PlayerPet = a.PetID +} + +func (proc *GameRPCProcessor) captureRpcSetStartCounter(net *protocol.PacketHandler, GameID int32, rpc *gamedata.Rpc) { + a := &gamedata.RpcSetStartCounter{} + a.ReadFromPacket(rpc.UnderlyingHazel.GetUnderlyingPacket()) + if proc.sus.state.CurrentGame != nil { + proc.sus.state.CurrentGame.StartGameTimer = a.TimeRemaining + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4b87733 --- /dev/null +++ b/go.mod @@ -0,0 +1,26 @@ +module codehub.onpointcoding.net/sean/go-susapp + +go 1.17 + +replace ( + codehub.onpointcoding.net/sean/go-susapp/enum => ./src/enum + codehub.onpointcoding.net/sean/go-susapp/gamedata => ./src/gamedata + codehub.onpointcoding.net/sean/go-susapp/innernetobjects => ./src/innernetobjects + codehub.onpointcoding.net/sean/go-susapp/mapdata => ./src/mapdata + codehub.onpointcoding.net/sean/go-susapp/packets => ./src/packets + codehub.onpointcoding.net/sean/go-susapp/protocol => ./src/protocol + codehub.onpointcoding.net/sean/go-susapp/systemtypes => ./src/systemtypes + codehub.onpointcoding.net/sean/go-susapp/util => ./src/util +) + +require ( + codehub.onpointcoding.net/sean/go-susapp/enum v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/gamedata v0.0.0-00010101000000-000000000000 + codehub.onpointcoding.net/sean/go-susapp/innernetobjects v0.0.0-00010101000000-000000000000 + codehub.onpointcoding.net/sean/go-susapp/packets v0.0.0-00010101000000-000000000000 + codehub.onpointcoding.net/sean/go-susapp/protocol v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/systemtypes v1.0.0 // indirect + codehub.onpointcoding.net/sean/go-susapp/util v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/mapdata v1.0.0 + github.com/gotk3/gotk3 v0.6.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7fc94be --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gotk3/gotk3 v0.6.1 h1:GJ400a0ecEEWrzjBvzBzH+pB/esEMIGdB9zPSmBdoeo= +github.com/gotk3/gotk3 v0.6.1/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= diff --git a/keyhandler.go b/keyhandler.go new file mode 100644 index 0000000..6b5a47f --- /dev/null +++ b/keyhandler.go @@ -0,0 +1,80 @@ +package main + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "github.com/gotk3/gotk3/gdk" + "github.com/gotk3/gotk3/gtk" +) + +type KeyInputHandler struct { + state *State +} + +func NewKeyInputHandler(state *State) *KeyInputHandler { + return &KeyInputHandler{state} +} + +func (input *KeyInputHandler) keyPressEvent(win *gtk.ApplicationWindow, ev *gdk.Event) { + keyEvent := gdk.EventKeyNewFromEvent(ev) + k := gdk.KeyvalToUpper(keyEvent.KeyVal()) + if input.state.Screen == enum.SCREEN_TITLE && input.state.IsTypingReady { + if k >= gdk.KEY_A && k <= gdk.KEY_Z { + input.state.TypedGameID = input.state.TypedGameID + string(gdk.KeyvalToUnicode(k)) + } else if k == gdk.KEY_BackSpace { + if len(input.state.TypedGameID) > 0 { + input.state.TypedGameID = input.state.TypedGameID[:len(input.state.TypedGameID)-1] + } + } + } else if input.state.Screen == enum.SCREEN_LOBBY { + if k == gdk.KEY_W { + input.state.HoldingUp = true + } + if k == gdk.KEY_A { + input.state.HoldingLeft = true + } + if k == gdk.KEY_S { + input.state.HoldingDown = true + } + if k == gdk.KEY_D { + input.state.HoldingRight = true + } + input.updateMovementDirections() + } +} + +func (input *KeyInputHandler) keyReleaseEvent(win *gtk.ApplicationWindow, ev *gdk.Event) { + keyEvent := gdk.EventKeyNewFromEvent(ev) + k := gdk.KeyvalToUpper(keyEvent.KeyVal()) + if input.state.Screen == enum.SCREEN_LOBBY { + if k == gdk.KEY_W { + input.state.HoldingUp = false + } + if k == gdk.KEY_A { + input.state.HoldingLeft = false + } + if k == gdk.KEY_S { + input.state.HoldingDown = false + } + if k == gdk.KEY_D { + input.state.HoldingRight = false + } + input.updateMovementDirections() + } +} + +func (input *KeyInputHandler) updateMovementDirections() { + input.state.MovementX = 0 + input.state.MovementY = 0 + if input.state.HoldingUp { + input.state.MovementY-- + } + if input.state.HoldingLeft { + input.state.MovementX-- + } + if input.state.HoldingDown { + input.state.MovementY++ + } + if input.state.HoldingRight { + input.state.MovementX++ + } +} diff --git a/mousehandler.go b/mousehandler.go new file mode 100644 index 0000000..d0b27a2 --- /dev/null +++ b/mousehandler.go @@ -0,0 +1,79 @@ +package main + +import ( + "time" + + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/util" + "github.com/gotk3/gotk3/gdk" + "github.com/gotk3/gotk3/gtk" +) + +type MouseInputHandler struct { + state *State + renderer *Renderer + playGame func() + openSettings func() + disconnectFromGame func() + prevTime int64 +} + +func NewMouseInputHandler(state *State, renderer *Renderer, playGame func(), openSettings func(), disconnectFromGame func()) *MouseInputHandler { + return &MouseInputHandler{state, renderer, playGame, openSettings, disconnectFromGame, time.Now().UnixMilli()} +} + +/*func (input *MouseInputHandler) pollingEvent(delta int64) { + +}*/ + +func (input *MouseInputHandler) motionEvent(da *gtk.DrawingArea, event *gdk.Event) bool { + eventMotion := gdk.EventMotionNewFromEvent(event) + input.renderer.mouseX, input.renderer.mouseY = eventMotion.MotionVal() + return false +} + +func (input *MouseInputHandler) buttonPressEvent(window *gtk.ApplicationWindow, ev *gdk.Event) bool { + btn := gdk.EventButtonNewFromEvent(ev) + switch btn.Button() { + case gdk.BUTTON_PRIMARY: + w := input.renderer.w + h := input.renderer.h + if input.state.Screen == enum.SCREEN_TITLE { + if util.CollisionIntersectCenter(input.renderer.mouseX, input.renderer.mouseY, w/2, h/2, 200, 40) { + input.playGame() + } else if util.CollisionIntersectCenter(input.renderer.mouseX, input.renderer.mouseY, w-40, h-40, 40, 40) { + input.openSettings() + } else { + if util.CollisionIntersectCenter(input.renderer.mouseX, input.renderer.mouseY, w/2, h/2-60, 200, 40) { + input.state.IsTypingReady = true + } else { + input.state.IsTypingReady = false + } + } + } else if input.state.Screen == enum.SCREEN_LOBBY { + if input.state.IsLobbyMenuOpen { + mw := w / 2 + mh := h / 2 + overlayBottom := mh + 200 + if util.CollisionIntersectCenter(input.renderer.mouseX, input.renderer.mouseY, mw, overlayBottom-40, 200, 40) { + input.state.IsLobbyMenuOpen = false + } else if util.CollisionIntersectCenter(input.renderer.mouseX, input.renderer.mouseY, mw, overlayBottom-100, 200, 40) { + input.disconnectFromGame() + } + } else { + if util.CollisionIntersectCenter(input.renderer.mouseX, input.renderer.mouseY, w-40, 40, 40, 40) { + input.state.IsLobbyMenuOpen = true + } + } + } else if input.state.Screen == enum.SCREEN_ERROR { + mw := w / 2 + mh := h / 2 + if util.CollisionIntersectCenter(input.renderer.mouseX, input.renderer.mouseY, mw+190, mh-190, 40, 40) { + input.state.Screen = enum.SCREEN_TITLE + input.state.IsLobbyMenuOpen = false + } + } + return true + } + return false +} diff --git a/movementprocessor.go b/movementprocessor.go new file mode 100644 index 0000000..a30ecd3 --- /dev/null +++ b/movementprocessor.go @@ -0,0 +1,41 @@ +package main + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/gamedata" + "codehub.onpointcoding.net/sean/go-susapp/innernetobjects" + "codehub.onpointcoding.net/sean/go-susapp/packets" + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type MovementProcessor struct { + sus *SusApp +} + +func (proc *MovementProcessor) Tick(delta int64) { + if proc.sus.state.CurrentGame != nil && proc.sus.networkHandler != nil { + p := protocol.NewEmptyPacket() + cp, ok := proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerNetID[proc.sus.state.CurrentGame.ClientPlayerNetID] + if ok { + netId := cp.PlayerNetworkTransform + var c0 innernetobjects.InnerNetObject = *proc.sus.state.CurrentGame.NetObjects[netId] + a, ok := c0.(*innernetobjects.PlayerNetworkTransform) + if ok { + a.LastSequenceID++ + prevVelocityZero := a.TargetVelocity.X == 0 && a.TargetVelocity.Y == 0 + a.TargetVelocity.X = enum.PLAYER_SPEED * proc.sus.state.CurrentGame.GameOptions.PlayerSpeedModifier * proc.sus.state.MovementX / 2 + a.TargetVelocity.Y = enum.PLAYER_SPEED * proc.sus.state.CurrentGame.GameOptions.PlayerSpeedModifier * proc.sus.state.MovementY / 2 + nextVelocityZero := a.TargetVelocity.X == 0 && a.TargetVelocity.Y == 0 + if !nextVelocityZero || prevVelocityZero != nextVelocityZero { + a.TargetPosition.X += a.TargetVelocity.X * (float32(delta) / 1000) + a.TargetPosition.Y -= a.TargetVelocity.Y * (float32(delta) / 1000) + c0.Write(p, false) + var c1 protocol.UpdateComponent = protocol.UpdateComponent{NetID: netId, RawData: p} + var c2 packets.GameDataSubpacket = &gamedata.DataH2G{Component: &c1} + var c3 protocol.HazelPayload = packets.NewGameDataSingleSubpacket(proc.sus.state.CurrentGame.GameID, &c2) + proc.sus.networkHandler.SendNormalPacket([]*protocol.Hazel{protocol.NewHazelFromPayload(&c3)}) + } + } + } + } +} diff --git a/packetprocessor.go b/packetprocessor.go new file mode 100644 index 0000000..4b14140 --- /dev/null +++ b/packetprocessor.go @@ -0,0 +1,172 @@ +package main + +import ( + "encoding/hex" + "fmt" + + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/gamedata" + "codehub.onpointcoding.net/sean/go-susapp/innernetobjects" + "codehub.onpointcoding.net/sean/go-susapp/packets" + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/util" +) + +type PacketProcessor struct { + sus *SusApp + gameDataProcessor *GameDataProcessor + normalPacketTagMap map[byte]func(net *protocol.PacketHandler, h *protocol.Hazel) + reliablePacketTagMap map[byte]func(net *protocol.PacketHandler, nonce uint16, h *protocol.Hazel) +} + +func NewPacketProcessor(sus *SusApp) *PacketProcessor { + proc := &PacketProcessor{sus: sus, gameDataProcessor: NewGameDataProcessor(sus)} + proc.normalPacketTagMap = map[byte]func(net *protocol.PacketHandler, h *protocol.Hazel){ + 0x05: proc.captureUnreliableGameDataAll, + 0x06: proc.captureUnreliableGameDataTo, + } + proc.reliablePacketTagMap = map[byte]func(net *protocol.PacketHandler, nonce uint16, h *protocol.Hazel){ + 0x04: proc.captureRemovePlayer, + 0x05: proc.captureGameDataAll, + 0x06: proc.captureGameDataTo, + 0x07: proc.captureJoinedGame, + 0x0a: proc.captureAlterGame, + 0x0d: proc.captureRedirect, + } + return proc +} + +func (proc *PacketProcessor) receiveNormalPacket(net *protocol.PacketHandler, h []*protocol.Hazel) { + for i := 0; i < len(h); i++ { + v, ok := proc.normalPacketTagMap[h[i].GetTag()] + if ok { + v(net, h[i]) + } else { + fmt.Printf("Unable to parse normal packet with tag of %x\n", h[i].GetTag()) + fmt.Printf("Packet %v\n", hex.EncodeToString(h[i].GetUnderlyingPacket().Dump())) + } + } +} + +func (proc *PacketProcessor) receiveReliablePacket(net *protocol.PacketHandler, nonce uint16, h []*protocol.Hazel) { + for i := 0; i < len(h); i++ { + v, ok := proc.reliablePacketTagMap[h[i].GetTag()] + if ok { + v(net, nonce, h[i]) + net.SendAcknowledgementPacket(nonce, 0xff) + } else { + fmt.Printf("Unable to parse packets with tag of %x\n", h[i].GetTag()) + fmt.Printf("Packet: %v\n", hex.EncodeToString(h[i].GetUnderlyingPacket().Dump())) + } + } +} + +func (proc *PacketProcessor) captureGameDataAll(net *protocol.PacketHandler, nonce uint16, h *protocol.Hazel) { + a := &packets.GameDataAll{} + a.Read(h.GetUnderlyingPacket()) + proc.gameDataProcessor.receiveGameDataSubpacket(net, a.GameID, a.Subpackets) +} + +func (proc *PacketProcessor) captureGameDataTo(net *protocol.PacketHandler, nonce uint16, h *protocol.Hazel) { + a := &packets.GameDataTo{} + a.Read(h.GetUnderlyingPacket()) + + if a.TargetClientID == proc.sus.state.CurrentGame.ClientID { + proc.gameDataProcessor.receiveGameDataSubpacket(net, a.GameID, a.Subpackets) + } +} + +func (proc *PacketProcessor) captureUnreliableGameDataAll(net *protocol.PacketHandler, h *protocol.Hazel) { + a := &packets.GameDataAll{} + a.Read(h.GetUnderlyingPacket()) + proc.gameDataProcessor.receiveGameDataSubpacket(net, a.GameID, a.Subpackets) +} + +func (proc *PacketProcessor) captureUnreliableGameDataTo(net *protocol.PacketHandler, h *protocol.Hazel) { + a := &packets.GameDataTo{} + a.Read(h.GetUnderlyingPacket()) + if a.TargetClientID == proc.sus.state.CurrentGame.ClientID { + proc.gameDataProcessor.receiveGameDataSubpacket(net, a.GameID, a.Subpackets) + } +} + +func (proc *PacketProcessor) captureJoinedGame(net *protocol.PacketHandler, nonce uint16, h *protocol.Hazel) { + a := &packets.JoinedGameH2C{} + a.Read(h.GetUnderlyingPacket()) + fmt.Printf("Joined game: %v\n", a.GameID) + fmt.Printf("Client id: %v\n", a.JoinedClientID) + fmt.Printf("Host client id: %v\n", a.HostClientID) + proc.sus.state.CurrentGame = &CurrentGameData{ + GameID: a.GameID, + ClientID: a.JoinedClientID, + HostClientID: a.HostClientID, + OtherClientIDs: make([]uint32, len(a.OtherClientIDs)), + GamePrivacy: 0, + GameOptions: protocol.NewDefaultGameOptionsData(), + NetObjects: map[uint32]*innernetobjects.InnerNetObject{}, + PlayerNetObjects: &PlayerNetObjectMaps{}, + } + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerClientID = map[uint32]*PlayerObject{} + proc.sus.state.CurrentGame.PlayerNetObjects.ByPlayerNetID = map[uint32]*PlayerObject{} + for j := 0; j < len(a.OtherClientIDs); j++ { + proc.sus.state.CurrentGame.OtherClientIDs[j] = a.OtherClientIDs[j] + } + proc.sus.state.Screen = enum.SCREEN_LOBBY + + // Send platform + var b1 packets.GameDataSubpacket = &gamedata.ClientInfoC2S{ + ClientID: proc.sus.state.CurrentGame.ClientID, + PlatformID: enum.PLATFORM_LinuxPlayer, + } + var b2 protocol.HazelPayload = packets.NewGameDataSingleSubpacket(proc.sus.state.CurrentGame.GameID, &b1) + net.SendReliablePacket(net.GetNonce(), []*protocol.Hazel{protocol.NewHazelFromPayload(&b2)}) + + // Send scene change + var c1 packets.GameDataSubpacket = &gamedata.SceneChangeC2H{ + ClientID: proc.sus.state.CurrentGame.ClientID, + SceneName: "OnlineGame", + } + var c2 protocol.HazelPayload = packets.NewGameDataSingleSubpacket(proc.sus.state.CurrentGame.GameID, &c1) + net.SendReliablePacket(net.GetNonce(), []*protocol.Hazel{protocol.NewHazelFromPayload(&c2)}) +} + +func (proc *PacketProcessor) captureAlterGame(net *protocol.PacketHandler, nonce uint16, h *protocol.Hazel) { + a := &packets.AlterGameH2G{} + a.Read(h.GetUnderlyingPacket()) + fmt.Printf("Alter game: %v\n", a.GameID) + fmt.Printf("Tag ID: %v\n", a.TagID) + fmt.Printf("Tag value: %v\n", a.TagValue) + if a.GameID == proc.sus.state.CurrentGame.GameID && a.TagID == 1 { + proc.sus.state.CurrentGame.GamePrivacy = a.TagValue + } +} + +func (proc *PacketProcessor) captureRedirect(net *protocol.PacketHandler, nonce uint16, h *protocol.Hazel) { + a := &packets.RedirectS2C{} + a.Read(h.GetUnderlyingPacket()) + fmt.Printf("Redirect: %v:%v", a.IPAddress.String(), a.Port) + if proc.sus.networkHandler != nil { + proc.sus.networkHandler.SendNormalDisconnectPacket() + proc.sus.networkHandler.Close() + proc.sus.networkHandler = nil + } + proc.sus.setupPacketHandler(&a.IPAddress, int(a.Port), func(ph *protocol.PacketHandler) { + fmt.Println("Sending first packet to new server") + ph.SendHelloPacket(ph.GetNonce(), 0, proc.sus.state.Version, proc.sus.state.Settings.PlayerName, 0, proc.sus.state.Settings.Language, proc.sus.state.Settings.QuickChatMode) + y, _ := util.CodeToGameID(proc.sus.state.TypedGameID) + var z protocol.HazelPayload = &packets.JoinGameC2S{GameID: y} + ph.SendReliablePacket(ph.GetNonce(), []*protocol.Hazel{ + protocol.NewHazelFromPayload(&z), + }) + }) +} + +func (proc *PacketProcessor) captureRemovePlayer(net *protocol.PacketHandler, nonce uint16, h *protocol.Hazel) { + a := &packets.RemovePlayerS2G{} + a.Read(h.GetUnderlyingPacket()) + if a.GameID == proc.sus.state.CurrentGame.GameID && a.HostClientID == proc.sus.state.CurrentGame.ClientID { + fmt.Println("Host player disconnect and left me in charge") + fmt.Println("I can't host a game so I'm disconnecting too") + net.SendNormalDisconnectPacket() + } +} diff --git a/project.json b/project.json new file mode 100644 index 0000000..ad02d87 --- /dev/null +++ b/project.json @@ -0,0 +1,9 @@ +{ + "Package": "melonapp", + "Version": "1.0-0", + "Section": "base", + "Priority": "optional", + "Architecture": "amd64", + "Maintainer": "MrMelon54 ", + "Description": "Melon App" +} diff --git a/renderer.go b/renderer.go new file mode 100644 index 0000000..1042cb3 --- /dev/null +++ b/renderer.go @@ -0,0 +1,273 @@ +package main + +import ( + "math" + "strconv" + "time" + + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/innernetobjects" + "codehub.onpointcoding.net/sean/go-susapp/util" + + "github.com/gotk3/gotk3/cairo" + "github.com/gotk3/gotk3/gtk" + "github.com/gotk3/gotk3/pango" +) + +type Renderer struct { + state *State + da *gtk.DrawingArea + cr *cairo.Context + w float64 + h float64 + mouseX float64 + mouseY float64 + delta int64 +} + +var defaultRenderFont string = "Noto Sans" + +func NewRenderer(state *State) *Renderer { + return &Renderer{state: state} +} + +func (r *Renderer) Draw(da *gtk.DrawingArea, cr *cairo.Context) { + r.delta = time.Now().UnixMilli() + r.da = da + r.cr = cr + allocation := da.GetAllocation() + r.w = float64(allocation.GetWidth()) + r.h = float64(allocation.GetHeight()) + + versionText := GetVersionFormatted(r.state.Version) + + // Background + r.DrawFilledRectangle(0, 0, r.w, r.h, 0x14181c) + + // Version numbers + r.DrawText(10, 10, versionText, "Noto Sans Display", 16, 0xffffff) + + if r.state.Screen == enum.SCREEN_TITLE { + colorLen := enum.GetPlayerTotalColors() + for i := 0; i < colorLen; i++ { + r.DrawFilledRectangle(float64(i)*20+(r.w-float64(colorLen)-float64(colorLen*20))/2, 0, 20, 20, enum.GetPlayerMainColor(i)) + r.DrawFilledRectangle(float64(i)*20+(r.w-float64(colorLen)-float64(colorLen*20))/2, 20, 20, 20, enum.GetPlayerShadedColor(i)) + } + + r.DrawButton(r.w-120, 20, 100, 40, r.state.Settings.PlayerName, defaultRenderFont, 16) + r.DrawButton(r.w-120, 60, 100, 40, r.state.Settings.ServerAddress, defaultRenderFont, 16) + r.DrawButton(r.w-120, 100, 100, 40, strconv.FormatInt(r.state.Settings.ServerPort, 10), defaultRenderFont, 16) + + r.DrawTextInput((r.w-200)/2, (r.h-20)/2-60, 200, 40, r.state.TypedGameID, defaultRenderFont, 16, r.state.IsTypingReady) + r.DrawButton((r.w-200)/2, (r.h-20)/2, 200, 40, "Play", defaultRenderFont, 16) + r.DrawButton(r.w-60, r.h-60, 40, 40, "⚙", defaultRenderFont, 16) + } else if r.state.Screen == enum.SCREEN_WAITING { + mw := r.w / 2 + mh := r.h / 2 + a := float64(r.delta/10) * (math.Pi / 180) + b := a + math.Pi/2 + c := a + math.Pi + d := a + math.Pi*3/2 + r.DrawFilledRoundedRectangle(mw+50*math.Sin(a)-20, mh+50*math.Cos(a)-20, 40, 40, 1, 20, 0xffffff) + r.DrawFilledRoundedRectangle(mw+50*math.Sin(b)-20, mh+50*math.Cos(b)-20, 40, 40, 1, 20, 0xffffff) + r.DrawFilledRoundedRectangle(mw+50*math.Sin(c)-20, mh+50*math.Cos(c)-20, 40, 40, 1, 20, 0xffffff) + r.DrawFilledRoundedRectangle(mw+50*math.Sin(d)-20, mh+50*math.Cos(d)-20, 40, 40, 1, 20, 0xffffff) + } else if r.state.Screen == enum.SCREEN_LOBBY { + r.DrawButton((r.w-100)/2, r.h-60, 100, 40, util.CodeFromGameID(r.state.CurrentGame.GameID), defaultRenderFont, 16) + r.DrawButton(r.w-60, 20, 40, 40, "⚙", defaultRenderFont, 16) + + if r.state.CurrentGame.GamePrivacy == 1 { + r.DrawButton((r.w-100)/2+120, r.h-60, 100, 40, "Public", defaultRenderFont, 16) + } else if r.state.CurrentGame.GamePrivacy == 0 { + r.DrawButton((r.w-100)/2+120, r.h-60, 100, 40, "Private", defaultRenderFont, 16) + } + + r.DrawPlayers() + + if r.state.IsLobbyMenuOpen { + mw := r.w / 2 + mh := r.h / 2 + overlayBottom := mh + 200 + + r.DrawFilledRectangle(0, 0, r.w, r.h, 0x80000000) + r.DrawFilledRoundedRectangle(mw-200, mh-200, 400, 400, 1, 20, 0x000000) + + r.DrawButton((r.w-200)/2, overlayBottom-60, 200, 40, "Return to Game", defaultRenderFont, 16) + r.DrawButton((r.w-200)/2, overlayBottom-120, 200, 40, "Leave Game", defaultRenderFont, 16) + } + } else if r.state.Screen == enum.SCREEN_ERROR { + mw := r.w / 2 + mh := r.h / 2 + r.DrawFilledRoundedRectangle(mw-200, mh-200, 400, 400, 1, 20, 0x000000) + + r.DrawText(mw-190, mh-190, "Disconnected", defaultRenderFont, 16, 0xffffff) + r.DrawButton(mw+170, mh-210, 40, 40, "×", defaultRenderFont, 16) + if r.state.DisconnectReason == 0xfe { + r.DrawWrappedText(mw-190, mh-156, 380, r.state.DisconnectStringReason, defaultRenderFont, 16, 0xffffff) + } else { + r.DrawWrappedText(mw-190, mh-156, 380, enum.DisconnectMessageText(r.state.DisconnectReason), defaultRenderFont, 16, 0xffffff) + } + } +} + +func (r *Renderer) SetRGB(red int, green int, blue int, alpha int) { + r.cr.SetSourceRGBA(float64(red)/0xff, float64(green)/0xff, float64(blue)/0xff, 0xff/float64(alpha)) +} + +func (r *Renderer) SetHex(hex int) { + r.SetRGB((hex/(256*256))%256, (hex/256)%256, hex%256, (hex/(256*256*256))%256) +} + +func (r *Renderer) DrawRectangle(x float64, y float64, w float64, h float64, color int) { + r.SetHex(color) + r.cr.Rectangle(x, y, w, h) + r.cr.Stroke() +} + +func (r *Renderer) DrawFilledRectangle(x float64, y float64, w float64, h float64, color int) { + r.SetHex(color) + r.cr.Rectangle(x, y, w, h) + r.cr.Fill() +} + +func (r *Renderer) DrawBorderedRectangle(x float64, y float64, w float64, h float64, color int, border int) { + r.DrawFilledRectangle(x, y, w, h, color) + r.DrawRectangle(x, y, w, h, border) +} + +func (r *Renderer) DrawRoundedRectangle(x float64, y float64, w float64, h float64, aspect float64, corner_radius float64, color int) { + a := corner_radius / aspect + d := math.Pi / 180 + r.cr.NewPath() + r.cr.Arc(x+w-a, y+a, a, -90*d, 0*d) + r.cr.Arc(x+w-a, y+h-a, a, 0*d, 90*d) + r.cr.Arc(x+a, y+h-a, a, 90*d, 180*d) + r.cr.Arc(x+a, y+a, a, 180*d, 270*d) + r.cr.ClosePath() + r.SetHex(color) + r.cr.Stroke() +} + +func (r *Renderer) DrawFilledRoundedRectangle(x float64, y float64, w float64, h float64, aspect float64, corner_radius float64, color int) { + a := corner_radius / aspect + d := math.Pi / 180 + r.cr.NewPath() + r.cr.Arc(x+w-a, y+a, a, -90*d, 0*d) + r.cr.Arc(x+w-a, y+h-a, a, 0*d, 90*d) + r.cr.Arc(x+a, y+h-a, a, 90*d, 180*d) + r.cr.Arc(x+a, y+a, a, 180*d, 270*d) + r.cr.ClosePath() + r.SetHex(color) + r.cr.FillPreserve() +} + +func (r *Renderer) DrawBorderedRoundedRectangle(x float64, y float64, w float64, h float64, aspect float64, corner_radius float64, color int, border int) { + a := corner_radius / aspect + d := math.Pi / 180 + r.cr.NewPath() + r.cr.Arc(x+w-a, y+a, a, -90*d, 0*d) + r.cr.Arc(x+w-a, y+h-a, a, 0*d, 90*d) + r.cr.Arc(x+a, y+h-a, a, 90*d, 180*d) + r.cr.Arc(x+a, y+a, a, 180*d, 270*d) + r.cr.ClosePath() + r.SetHex(color) + r.cr.FillPreserve() + r.SetHex(border) + r.cr.Stroke() +} + +func (r *Renderer) DrawText(x float64, y float64, text string, fontFamily string, fontSize float64, color int) { + layout := pango.CairoCreateLayout(r.cr) + font := pango.FontDescriptionFromString("sans normal 16") + font.SetFamily(fontFamily) + + layout.SetFontDescription(font) + layout.SetText(text, len(text)) + + r.SetHex(color) + r.cr.MoveTo(x, y) + pango.CairoUpdateLayout(r.cr, layout) + pango.CairoShowLayout(r.cr, layout) +} + +func (r *Renderer) DrawWrappedText(x float64, y float64, w float64, text string, fontFamily string, fontSize float64, color int) { + layout := pango.CairoCreateLayout(r.cr) + layout.SetWidth(int(w) * pango.SCALE) + font := pango.FontDescriptionFromString("sans normal 16") + font.SetFamily(fontFamily) + + layout.SetFontDescription(font) + layout.SetText(text, len(text)) + + r.SetHex(color) + r.cr.MoveTo(x, y) + pango.CairoUpdateLayout(r.cr, layout) + pango.CairoShowLayout(r.cr, layout) +} + +func (r *Renderer) DrawButton(x float64, y float64, w float64, h float64, text string, fontFamily string, fontSize float64) { + r.DrawFilledRoundedRectangle(x, y, w-4, h-4, w/h, 10, 0x000000) + + if r.mouseX > x && r.mouseX < x+w && r.mouseY > y && r.mouseY < y+h { + r.DrawRoundedRectangle(x, y, w-4, h-4, w/h, 10, 0x00ff00) + } else { + r.DrawRoundedRectangle(x, y, w-4, h-4, w/h, 10, 0xffffff) + } + + layout := pango.CairoCreateLayout(r.cr) + font := pango.FontDescriptionFromString("sans normal 16") + font.SetFamily(fontFamily) + + layout.SetFontDescription(font) + layout.SetText(text, len(text)) + + textWidth, textHeight := layout.GetSize() + textWidth /= pango.SCALE + textHeight /= pango.SCALE + + r.SetHex(0xffffff) + r.cr.MoveTo(x+(w-4-float64(textWidth))/2, y+(h-4-float64(textHeight))/2) + pango.CairoUpdateLayout(r.cr, layout) + pango.CairoShowLayout(r.cr, layout) +} + +func (r *Renderer) DrawTextInput(x float64, y float64, w float64, h float64, text string, fontFamily string, fontSize float64, inFocus bool) { + if r.mouseX > x && r.mouseX < x+w && r.mouseY > y && r.mouseY < y+h { + r.DrawFilledRectangle(x, y+h-6, w-4, 2, 0x00ffff) + } else { + r.DrawFilledRectangle(x, y+h-6, w-4, 2, 0xffffff) + } + + layout := pango.CairoCreateLayout(r.cr) + font := pango.FontDescriptionFromString("sans normal 16") + font.SetFamily(fontFamily) + + layout.SetFontDescription(font) + layout.SetText(text, len(text)) + + textWidth, textHeight := layout.GetSize() + textWidth /= pango.SCALE + textHeight /= pango.SCALE + + r.SetHex(0xffffff) + r.cr.MoveTo(x+(w-float64(textWidth))/2, y+(h-12-float64(textHeight))/2) + pango.CairoUpdateLayout(r.cr, layout) + pango.CairoShowLayout(r.cr, layout) + + if inFocus && r.delta%1600 < 800 { + r.DrawFilledRectangle(x+(w+float64(textWidth))/2, y+4, 2, 20, 0xffffff) + } +} + +func (r *Renderer) DrawPlayers() { + for _, item := range r.state.CurrentGame.PlayerNetObjects.ByPlayerClientID { + var t innernetobjects.InnerNetObject = *r.state.CurrentGame.NetObjects[item.PlayerNetworkTransform] + a, ok := t.(*innernetobjects.PlayerNetworkTransform) + if ok { + x := r.w/2 + float64(a.TargetPosition.X)*50 + y := r.h/2 - float64(a.TargetPosition.Y)*50 + r.DrawFilledRectangle(float64(x-5), float64(y-5), 10, 10, enum.GetPlayerMainColor(int(item.PlayerColor))) + r.DrawText(x, y, strconv.FormatFloat(float64(a.TargetPosition.X), 'e', 4, 64)+strconv.FormatFloat(float64(a.TargetPosition.Y), 'e', 4, 64), defaultRenderFont, 10, 0xffffff) + } + } +} diff --git a/settings.go b/settings.go new file mode 100644 index 0000000..5891546 --- /dev/null +++ b/settings.go @@ -0,0 +1,37 @@ +package main + +import ( + "encoding/json" + + "codehub.onpointcoding.net/sean/go-susapp/enum" +) + +type SettingsJSON struct { + PlayerName string `json:"PlayerName"` + PlayerColor byte `json:"PlayerColor"` + ServerAddress string `json:"ServerAddress"` + ServerPort int64 `json:"ServerPort"` + Language uint32 `json:"Language"` + QuickChatMode byte `json:"QuickChatMode"` +} + +func UnmarshalSettings(data []byte) (SettingsJSON, error) { + var r SettingsJSON + err := json.Unmarshal(data, &r) + return r, err +} + +func (r *SettingsJSON) Marshal() ([]byte, error) { + return json.Marshal(r) +} + +func NewDefaultSettingsJSON() SettingsJSON { + return SettingsJSON{ + PlayerName: "Player 1", + PlayerColor: 0, + ServerAddress: "localhost", + ServerPort: 22023, + Language: enum.GAMEKEYWORD_ALL, + QuickChatMode: enum.QUICK_CHAT_ONLY, + } +} diff --git a/settingswindow.go b/settingswindow.go new file mode 100644 index 0000000..ab954d1 --- /dev/null +++ b/settingswindow.go @@ -0,0 +1,225 @@ +package main + +import ( + "fmt" + "log" + "strconv" + + "codehub.onpointcoding.net/sean/go-susapp/enum" + "github.com/gotk3/gotk3/glib" + "github.com/gotk3/gotk3/gtk" +) + +var langaugeIdxMap = []int{ + enum.GAMEKEYWORD_ALL, + enum.GAMEKEYWORD_OTHER, + enum.GAMEKEYWORD_SPANISH, + enum.GAMEKEYWORD_KOREAN, + enum.GAMEKEYWORD_RUSSIAN, + enum.GAMEKEYWORD_PORTUGUESE, + enum.GAMEKEYWORD_ARABIC, + enum.GAMEKEYWORD_FILIPINO, + enum.GAMEKEYWORD_POLISH, + enum.GAMEKEYWORD_ENGLISH, + enum.GAMEKEYWORD_JAPANESE, +} +var quickChatIdxMap = []int{ + enum.FREE_CHAT_OR_QUICK_CHAT, + enum.QUICK_CHAT_ONLY, +} + +func openSettingsWindow(application *gtk.Application, mainWindow *gtk.ApplicationWindow, state *State) { + optWindow, err := gtk.ApplicationWindowNew(application) + if err != nil { + log.Fatal("Could not create application window.", err) + } + // Set ApplicationWindow Properties + optWindow.SetTitle("Sus App > Settings") + optWindow.SetDefaultSize(350, 350) + + layout, _ := gtk.LayoutNew(nil, nil) + optWindow.Add(layout) + + _, usernameEntry := addTextWidget(layout, 8, "Username:", state.Settings.PlayerName) + _, colorEntry := addColorWidget(layout, 50, "Color:", state.Settings.PlayerColor) + _, serverEntry := addTextWidget(layout, 92, "Address:", state.Settings.ServerAddress) + _, portEntry := addTextWidget(layout, 134, "Port:", strconv.FormatInt(state.Settings.ServerPort, 10)) + _, languageEntry := addLanguageWidget(layout, 176, "Language:", state.Settings.Language) + _, quickChatEntry := addQuickChatWidget(layout, 218, "Quick Chat:", state.Settings.QuickChatMode) + + applyBtn, _ := gtk.ButtonNewWithLabel("Apply") + layout.Put(applyBtn, 8, 218) + applyBtn.SetSizeRequest(334, 34) + applyBtn.Connect("clicked", func(b *gtk.Button) { + // + // Username + // + a1, err := usernameEntry.GetText() + if err == nil { + state.Settings.PlayerName = a1 + } else { + showSettingsErrorDialog(optWindow, "Failed to get new player name") + } + + // + // Server + // + a2, err := serverEntry.GetText() + if err == nil { + state.Settings.ServerAddress = a2 + } else { + showSettingsErrorDialog(optWindow, "Failed to get new server address") + } + + // + // Port + // + a3, err := portEntry.GetText() + if err == nil { + b3, err := strconv.ParseInt(a3, 10, 64) + if err == nil { + state.Settings.ServerPort = b3 + } else { + showSettingsErrorDialog(optWindow, fmt.Sprintf("Failed to parse port %v as it is not a valid integer", a3)) + } + } else { + showSettingsErrorDialog(optWindow, "Failed to get new port") + } + + // + // Language + // + a4 := languageEntry.GetActive() + state.Settings.Language = uint32(langaugeIdxMap[a4]) + + // + // Quick chat mode + // + a5 := quickChatEntry.GetActive() + state.Settings.QuickChatMode = byte(quickChatIdxMap[a5]) + + // + // Color + // + a6 := colorEntry.GetActive() + state.Settings.PlayerColor = byte(a6) + + // + // Save + // + state.SaveSettings() + }) + + optWindow.ShowAll() +} + +func showSettingsErrorDialog(optWindow *gtk.ApplicationWindow, msg string) { + mDialog := gtk.MessageDialogNew(optWindow, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg) + mDialog.SetTitle("Sus App Error") + mDialog.Run() + mDialog.Destroy() +} + +func addTextWidget(layout *gtk.Layout, y int, txt string, orig string) (*gtk.Label, *gtk.Entry) { + label, _ := gtk.LabelNew(txt) + layout.Put(label, 8, y+6) + + entry, _ := gtk.EntryNew() + layout.Put(entry, 100, y) + entry.SetSizeRequest(242, 34) + entry.SetText(orig) + + return label, entry +} + +func addLanguageWidget(layout *gtk.Layout, y int, txt string, orig uint32) (*gtk.Label, *gtk.ComboBox) { + var origIter *gtk.TreeIter + langStore, _ := gtk.ListStoreNew(glib.TYPE_UINT, glib.TYPE_STRING) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_ALL, "All", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_OTHER, "Other", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_SPANISH, "Spanish", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_KOREAN, "Korean", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_RUSSIAN, "Russian", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_PORTUGUESE, "Portuguese", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_ARABIC, "Arabic", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_FILIPINO, "Filipino", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_POLISH, "Polish", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_ENGLISH, "English", orig, origIter) + _, origIter = addRow(langStore, enum.GAMEKEYWORD_JAPANESE, "Japanese", orig, origIter) + + label, _ := gtk.LabelNew(txt) + layout.Put(label, 8, y+6) + + combo, _ := gtk.ComboBoxNewWithModel(langStore) + rendererText, _ := gtk.CellRendererTextNew() + combo.PackStart(rendererText, true) + combo.AddAttribute(rendererText, "text", 1) + combo.SetSizeRequest(242, 34) + combo.SetActiveIter(origIter) + layout.Put(combo, 100, y) + + return label, combo +} + +func addQuickChatWidget(layout *gtk.Layout, y int, txt string, orig byte) (*gtk.Label, *gtk.ComboBox) { + var origIter *gtk.TreeIter + modeStore, _ := gtk.ListStoreNew(glib.TYPE_UINT, glib.TYPE_STRING) + _, origIter = addRow(modeStore, enum.FREE_CHAT_OR_QUICK_CHAT, "Free Chat or Quick Chat", uint32(orig), origIter) + _, origIter = addRow(modeStore, enum.QUICK_CHAT_ONLY, "Quick Chat Only", uint32(orig), origIter) + + label, _ := gtk.LabelNew(txt) + layout.Put(label, 8, y+6) + + combo, _ := gtk.ComboBoxNewWithModel(modeStore) + rendererText, _ := gtk.CellRendererTextNew() + combo.PackStart(rendererText, true) + combo.AddAttribute(rendererText, "text", 1) + combo.SetSizeRequest(242, 34) + combo.SetActiveIter(origIter) + layout.Put(combo, 100, y) + + return label, combo +} + +func addColorWidget(layout *gtk.Layout, y int, txt string, orig byte) (*gtk.Label, *gtk.ComboBox) { + var origIter *gtk.TreeIter + modeStore, _ := gtk.ListStoreNew(glib.TYPE_UINT, glib.TYPE_STRING) + + for i := 0; i < enum.GetPlayerTotalColors(); i++ { + _, origIter = addRow(modeStore, uint32(i), enum.GetPlayerColorName(i), uint32(orig), origIter) + } + + label, _ := gtk.LabelNew(txt) + layout.Put(label, 8, y+6) + + combo, _ := gtk.ComboBoxNewWithModel(modeStore) + rendererText, _ := gtk.CellRendererTextNew() + combo.PackStart(rendererText, true) + combo.AddAttribute(rendererText, "text", 1) + combo.SetSizeRequest(242, 34) + combo.SetActiveIter(origIter) + layout.Put(combo, 100, y) + + return label, combo +} + +// Append a row to the list store for the tree view +func addRow(listStore *gtk.ListStore, id uint32, feature string, origId uint32, origIter *gtk.TreeIter) (*gtk.TreeIter, *gtk.TreeIter) { + // Get an iterator for a new row at the end of the list store + iter := listStore.Append() + + if id == origId { + origIter = iter + } + + // Set the contents of the list store row that the iterator represents + err := listStore.Set(iter, + []int{0, 1}, + []interface{}{id, feature}) + + if err != nil { + log.Fatal("Unable to add row:", err) + } + + return iter, origIter +} diff --git a/src/enum/disconnectreason.go b/src/enum/disconnectreason.go new file mode 100644 index 0000000..28d972a --- /dev/null +++ b/src/enum/disconnectreason.go @@ -0,0 +1,61 @@ +package enum + +const ( + DISCONNECT_EXIT_GAME = 0 + DISCONNECT_GAME_FULL = 1 + DISCONNECT_GAME_STARTED = 2 + DISCONNECT_GAME_NOT_FOUND = 3 + DISCONNECT_INCORRECT_VERSION = 5 + DISCONNECT_BANNED = 6 + DISCONNECT_KICKED = 7 + DISCONNECT_CUSTOM = 8 + DISCONNECT_INVALID_NAME = 9 + DISCONNECT_HACKING = 10 + DISCONNECT_NOT_AUTHORIZED = 11 + DISCONNECT_DESTROY = 16 + DISCONNECT_ERROR = 17 + DISCONNECT_INCORRECT_GAME = 18 + DISCONNECT_SERVER_REQUEST = 19 + DISCONNECT_SERVER_FULL = 20 + DISCONNECT_FOCUS_LOST_BACKGROUND = 207 +) + +func DisconnectMessageText(reason byte) string { + switch reason { + case DISCONNECT_EXIT_GAME: + return "Exit game" + case DISCONNECT_GAME_FULL: + return "Game full" + case DISCONNECT_GAME_STARTED: + return "Game started" + case DISCONNECT_GAME_NOT_FOUND: + return "Game not found" + case DISCONNECT_INCORRECT_VERSION: + return "Incorrect version" + case DISCONNECT_BANNED: + return "Banned" + case DISCONNECT_KICKED: + return "Kicked" + case DISCONNECT_CUSTOM: + return "Custom" + case DISCONNECT_INVALID_NAME: + return "Invalid name" + case DISCONNECT_HACKING: + return "Hacking" + case DISCONNECT_NOT_AUTHORIZED: + return "Not authorized" + case DISCONNECT_DESTROY: + return "Destroy" + case DISCONNECT_ERROR: + return "Error" + case DISCONNECT_INCORRECT_GAME: + return "Incorrect game" + case DISCONNECT_SERVER_REQUEST: + return "Server request" + case DISCONNECT_SERVER_FULL: + return "Server full" + case DISCONNECT_FOCUS_LOST_BACKGROUND: + return "Focus lost background" + } + return "Lost connection" +} diff --git a/src/enum/gamekeyword.go b/src/enum/gamekeyword.go new file mode 100644 index 0000000..b8a1bd3 --- /dev/null +++ b/src/enum/gamekeyword.go @@ -0,0 +1,15 @@ +package enum + +const ( + GAMEKEYWORD_ALL = 0 + GAMEKEYWORD_OTHER = 1 + GAMEKEYWORD_SPANISH = 2 + GAMEKEYWORD_KOREAN = 4 + GAMEKEYWORD_RUSSIAN = 8 + GAMEKEYWORD_PORTUGUESE = 16 + GAMEKEYWORD_ARABIC = 32 + GAMEKEYWORD_FILIPINO = 64 + GAMEKEYWORD_POLISH = 128 + GAMEKEYWORD_ENGLISH = 256 + GAMEKEYWORD_JAPANESE = 512 +) diff --git a/src/enum/gameoverreason.go b/src/enum/gameoverreason.go new file mode 100644 index 0000000..7bb8f32 --- /dev/null +++ b/src/enum/gameoverreason.go @@ -0,0 +1,11 @@ +package enum + +const ( + GAMEOVERREASON_CREWMATES_BY_VOTE = 0 + GAMEOVERREASON_CREWMATES_BY_TASK = 1 + GAMEOVERREASON_IMPOSTORS_BY_VOTE = 2 + GAMEOVERREASON_IMPOSTORS_BY_KILL = 3 + GAMEOVERREASON_IMPOSTORS_BY_SABOTAGE = 4 + GAMEOVERREASON_IMPOSTOR_DISCONNECT = 5 + GAMEOVERREASON_CREWMATE_DISCONNECT = 6 +) diff --git a/src/enum/gamestate.go b/src/enum/gamestate.go new file mode 100644 index 0000000..d8cf140 --- /dev/null +++ b/src/enum/gamestate.go @@ -0,0 +1,8 @@ +package enum + +const ( + GAMESTATE_NOT_STARTED = 0 + GAMESTATE_STARTED = 1 + GAMESTATE_ENDED = 2 + GAMESTATE_DESTROYED = 3 +) diff --git a/src/enum/go.mod b/src/enum/go.mod new file mode 100644 index 0000000..59d4ce2 --- /dev/null +++ b/src/enum/go.mod @@ -0,0 +1,3 @@ +module codehub.onpointcoding.net/sean/go-susapp/enum + +go 1.17 diff --git a/src/enum/hat.go b/src/enum/hat.go new file mode 100644 index 0000000..c414a2f --- /dev/null +++ b/src/enum/hat.go @@ -0,0 +1,119 @@ +package enum + +const ( + HAT_NONE = 0 + HAT_ASTRONAUT = 1 + HAT_BASEBALL_CAP = 2 + HAT_BRAIN_SLUG = 3 + HAT_BUSH_HAT = 4 + HAT_CAPTAIN_HAT = 5 + HAT_DOUBLE_TOP_HAT = 6 + HAT_FLOWERPOT = 7 + HAT_GOGGLES = 8 + HAT_HARD_HAT = 9 + HAT_MILITARY_HAT = 10 + HAT_PAPER_HAT = 11 + HAT_PARTY_HAT = 12 + HAT_POLICE_HAT = 13 + HAT_STETHOSCOPE = 14 + HAT_TOP_HAT = 15 + HAT_TOWEL_WIZARD = 16 + HAT_USHANKA = 17 + HAT_VIKING = 18 + HAT_WALL_GUARD_CAP = 19 + HAT_SNOWMAN = 20 + HAT_REINDEER_ANTLERS = 21 + HAT_CHRISTMAS_LIGHTS = 22 + HAT_SANTA_HAT = 23 + HAT_CHRISTMAS_TREE = 24 + HAT_CHRISTMAS_PRESENT = 25 + HAT_CANDY_CANES = 26 + HAT_ELF_HAT = 27 + HAT_NEW_YEARS_2018 = 28 + HAT_WHITE_HAT = 29 + HAT_CROWN = 30 + HAT_EYEBROWS = 31 + HAT_HALO = 32 + HAT_HERO_CAP = 33 + HAT_PIP_CAP = 34 + HAT_PLUNGER = 35 + HAT_SCUBA_MASK = 36 + HAT_HENRY_STICKMIN = 37 + HAT_STRAW_HAT = 38 + HAT_TEN_GALLON_HAT = 39 + HAT_THIRD_EYE = 40 + HAT_TOILET_PAPER = 41 + HAT_TOPPAT_CLAN_LEADER = 42 + HAT_BLACK_FEDORA = 43 + HAT_SKI_GOGGLES = 44 + HAT_HEARING_PROTECTION = 45 + HAT_HAZMAT_MASK = 46 + HAT_FACE_MASK = 47 + HAT_MIRA_SECURITY_CAP = 48 + HAT_SAFARI_HAT = 49 + HAT_BANANA = 50 + HAT_BEANIE = 51 + HAT_BEAR_EARS = 52 + HAT_CHEESE = 53 + HAT_CHERRY = 54 + HAT_EGG = 55 + HAT_GREEN_FEDORA = 56 + HAT_FLAMINGO = 57 + HAT_FLOWER_PIN = 58 + HAT_KNIGHT_HELMET = 59 + HAT_PLANT = 60 + HAT_BAT_EYES = 61 + HAT_BAT_WINGS = 62 + HAT_HORNS = 63 + HAT_MOHAWK = 64 + HAT_PUMPKIN = 65 + HAT_SCARY_PAPER_BAG = 66 + HAT_WITCH_HAT = 67 + HAT_WOLF_EARS = 68 + HAT_PIRATE_HAT = 69 + HAT_PLAGUE_DOCTOR = 70 + HAT_MACHETE = 71 + HAT_HOCKEY_MASK = 72 + HAT_MINER_HELMET = 73 + HAT_WINTER_CAP = 74 + HAT_ARCHAEOLOGIST_HAT = 75 + HAT_ANTENNA = 76 + HAT_BALLOON = 77 + HAT_BIRD_NEST = 78 + HAT_BLACK_BELT = 79 + HAT_CAUTION_SIGN = 80 + HAT_CHEF_HAT = 81 + HAT_COP_HAT = 82 + HAT_DO_RAG = 83 + HAT_DUM_STICKER = 84 + HAT_FEZ = 85 + HAT_GENERAL_HAT = 86 + HAT_POMPADOUR_HAIR = 87 + HAT_HUNTER_HAT = 88 + HAT_JUNGLE_HAT = 89 + HAT_MINI_CREWMATE = 90 + HAT_NINJA_MASK = 91 + HAT_RAM_HORNS = 92 + HAT_MINI_CREWMATE_SNOWMAN = 93 + HAT_GEOFF_KEIGHLEY_MASK = 94 + HAT_DAVE_PANPA_CAP = 95 + HAT_ELLIE_ROSE_HAIR = 96 + HAT_SVEN_SVENSSON_HAT = 97 + HAT_BURT_CURTIS_HAT = 98 + HAT_ELLRY_MOHAWK = 99 + HAT_THOMAS_CHESTERSHIRE_MONOCLES = 100 + HAT_WIZARD_HAT = 101 + HAT_FREDRICK_MUENSTER_HAT = 102 + HAT_MR_MACBETH_HAT = 103 + HAT_TOPPAT_HENRY_STICKMIN_HAT = 104 + HAT_TOPPAT_ELLIE_ROSE_HAT = 105 + HAT_GEOFFREY_PLUMB_HAT = 106 + HAT_ANGRY_EYEBROWS = 107 + HAT_CHOCOLATE_ICE_CREAM = 108 + HAT_HEART_PIN = 109 + HAT_PONYTAIL = 110 + HAT_RUBBER_GLOVE = 111 + HAT_UNICORN_HORN = 112 + HAT_ZIPPER = 113 + HAT_RIGHT_HAND_MAN_HAT = 114 +) diff --git a/src/enum/killdistance.go b/src/enum/killdistance.go new file mode 100644 index 0000000..62cf016 --- /dev/null +++ b/src/enum/killdistance.go @@ -0,0 +1,16 @@ +package enum + +const ( + KILLDISTANCE_SHORT = 0 + KILLDISTANCE_NORMAL = 1 + KILLDISTANCE_LONG = 2 +) + +var distanceMap = []float64{1, 1.8, 2.5} + +func GetKillDistanceMagnitude(a int) float64 { + if a < 0 || a > len(distanceMap) { + return 0 + } + return distanceMap[a] +} diff --git a/src/enum/limbostate.go b/src/enum/limbostate.go new file mode 100644 index 0000000..0240b1d --- /dev/null +++ b/src/enum/limbostate.go @@ -0,0 +1,7 @@ +package enum + +const ( + LIMBOSTATE_PRE_SPAWN = 0 + LIMBOSTATE_NOT_LIMBO = 1 + LIMBOSTATE_WAITING_FOR_HOST = 2 +) diff --git a/src/enum/map.go b/src/enum/map.go new file mode 100644 index 0000000..eb5f679 --- /dev/null +++ b/src/enum/map.go @@ -0,0 +1,20 @@ +package enum + +const ( + MAP_THE_SKELD = 0 + MAP_MIRA_HQ = 1 + MAP_POLUS = 2 + MAP_THE_AIRSHIP = 3 +) + +func MapBitfield(a []int) byte { + var b byte = 0 + for i := 0; i < len(a); i++ { + b |= 1 << a[i] + } + return b +} + +func BitfieldHasMap(b byte, a int) bool { + return b&(1< len(playerColorNames) { + return "Invalid Color" + } + return playerColorNames[a] +} + +func GetPlayerMainColor(a int) int { + if a < 0 || a > len(playerColors) { + return 0 + } + return playerColors[a] +} + +func GetPlayerShadedColor(a int) int { + if a < 0 || a > len(playerShadedColors) { + return 0 + } + return playerShadedColors[a] +} diff --git a/src/enum/playerspeed.go b/src/enum/playerspeed.go new file mode 100644 index 0000000..2bb6d19 --- /dev/null +++ b/src/enum/playerspeed.go @@ -0,0 +1,6 @@ +package enum + +const ( + PLAYER_SPEED = 4.5 + PLAYER_GHOST_SPEED = 3.0 +) diff --git a/src/enum/quickchatmode.go b/src/enum/quickchatmode.go new file mode 100644 index 0000000..f8f9450 --- /dev/null +++ b/src/enum/quickchatmode.go @@ -0,0 +1,6 @@ +package enum + +const ( + FREE_CHAT_OR_QUICK_CHAT = 1 + QUICK_CHAT_ONLY = 2 +) diff --git a/src/enum/reportoutcome.go b/src/enum/reportoutcome.go new file mode 100644 index 0000000..b9f5b68 --- /dev/null +++ b/src/enum/reportoutcome.go @@ -0,0 +1,9 @@ +package enum + +const ( + REPORTOUTCOME_NOT_REPORTED_UNKNOWN = 0 + REPORTOUTCOME_NOT_REPORTED_NO_ACCOUNT = 1 + REPORTOUTCOME_NOT_REPORTED_NOT_FOUND = 2 + REPORTOUTCOME_NOT_REPORTED_RATE_LIMIT = 3 + REPORTOUTCOME_REPORTED = 4 +) diff --git a/src/enum/reportreason.go b/src/enum/reportreason.go new file mode 100644 index 0000000..d9f126b --- /dev/null +++ b/src/enum/reportreason.go @@ -0,0 +1,8 @@ +package enum + +const ( + REPORTREASON_INAPPROPRIATE_NAME = 0 + REPORTREASON_INAPPROPRIATE_CHAT = 1 + REPORTREASON_CHEATING_HACKING = 2 + REPORTREASON_HARASSMENT_MISCONDUCT = 3 +) diff --git a/src/enum/rpccall.go b/src/enum/rpccall.go new file mode 100644 index 0000000..13c02ef --- /dev/null +++ b/src/enum/rpccall.go @@ -0,0 +1,36 @@ +package enum + +const ( + RPC_PLAY_ANIMATION = 0x00 + RPC_COMPLETE_TASK = 0x01 + RPC_SYNC_SETTINGS = 0x02 + RPC_SET_INFECTED = 0x03 + RPC_EXILED = 0x04 + RPC_CHECK_NAME = 0x05 + RPC_SET_NAME = 0x06 + RPC_CHECK_COLOR = 0x07 + RPC_SET_COLOR = 0x08 + RPC_SET_HAT = 0x09 + RPC_SET_SKIN = 0x0a + RPC_REPORT_DEAD_BODY = 0x0b + RPC_MURDER_PLAYER = 0x0c + RPC_SEND_CHAT = 0x0d + RPC_START_MEETING = 0x0e + RPC_SET_SCANNER = 0x0f + RPC_SEND_CHAT_NOTE = 0x10 + RPC_SET_PET = 0x11 + RPC_SET_START_COUNTER = 0x12 + RPC_ENTER_VENT = 0x13 + RPC_EXIT_VENT = 0x14 + RPC_SNAP_TO = 0x15 + RPC_CLOSE = 0x16 + RPC_VOTING_COMPLETE = 0x17 + RPC_CAST_VOTE = 0x18 + RPC_CLEAR_VOTE = 0x19 + RPC_ADD_VOTE = 0x1a + RPC_CLOSE_DOORS_OF_TYPE = 0x1b + RPC_REPAIR_SYSTEM = 0x1c + RPC_SET_TASKS = 0x1d + RPC_CLIMB_LADDER = 0x1f + RPC_USE_PLATFORM = 0x20 +) diff --git a/src/enum/screenstate.go b/src/enum/screenstate.go new file mode 100644 index 0000000..7df859f --- /dev/null +++ b/src/enum/screenstate.go @@ -0,0 +1,9 @@ +package enum + +const ( + SCREEN_TITLE = 0 + SCREEN_WAITING = 1 + SCREEN_LOBBY = 2 + SCREEN_INGAME = 3 + SCREEN_ERROR = 4 +) diff --git a/src/enum/skin.go b/src/enum/skin.go new file mode 100644 index 0000000..2d4e89f --- /dev/null +++ b/src/enum/skin.go @@ -0,0 +1,23 @@ +package enum + +const ( + SKIN_NONE = 0 + SKIN_ASTRONAUT = 1 + SKIN_CAPTAIN = 2 + SKIN_MECHANIC = 3 + SKIN_MILITARY = 4 + SKIN_POLICE = 5 + SKIN_SCIENTIST = 6 + SKIN_SUIT_BLACK = 7 + SKIN_SUIT_WHITE = 8 + SKIN_WALL_GUARD = 9 + SKIN_HAZMAT = 10 + SKIN_SECURITY_GUARD = 11 + SKIN_TARMAC = 12 + SKIN_MINER = 13 + SKIN_WINTER = 14 + SKIN_ARCHAEOLOGIST = 15 + SKIN_PRISONER = 16 + SKIN_CCC = 17 + SKIN_RIGHT_HAND_MAN_REBORN = 18 +) diff --git a/src/enum/spawnflag.go b/src/enum/spawnflag.go new file mode 100644 index 0000000..0440305 --- /dev/null +++ b/src/enum/spawnflag.go @@ -0,0 +1,6 @@ +package enum + +const ( + SPAWNFLAG_NONE = 0 + SPAWNFLAG_IS_CLIENT_CHARACTER = 1 +) diff --git a/src/enum/spawntype.go b/src/enum/spawntype.go new file mode 100644 index 0000000..50a0d7e --- /dev/null +++ b/src/enum/spawntype.go @@ -0,0 +1,13 @@ +package enum + +const ( + SPAWNTYPE_SKELD_SHIP_STATUS = 0 + SPAWNTYPE_MEETING_HUD = 1 + SPAWNTYPE_LOBBY_BEHAVIOUR = 2 + SPAWNTYPE_GAME_DATA = 3 + SPAWNTYPE_PLAYER_CONTROL = 4 + SPAWNTYPE_MIRA_SHIP_STATUS = 5 + SPAWNTYPE_POLUS_SHIP_STATUS = 6 + SPAWNTYPE_DLEKS_SHIP_STATUS = 7 + SPAWNTYPE_AIRSHIP_STATUS = 8 +) diff --git a/src/enum/systemtype.go b/src/enum/systemtype.go new file mode 100644 index 0000000..839211f --- /dev/null +++ b/src/enum/systemtype.go @@ -0,0 +1,53 @@ +package enum + +const ( + SYSTEMTYPE_HALLWAY = 0 + SYSTEMTYPE_STORAGE = 1 + SYSTEMTYPE_CAFETERIA = 2 + SYSTEMTYPE_REACTOR = 3 + SYSTEMTYPE_UPPER_ENGINE = 4 + SYSTEMTYPE_NAVIGATION = 5 + SYSTEMTYPE_ADMIN = 6 + SYSTEMTYPE_ELECTRICAL = 7 + SYSTEMTYPE_O2 = 8 + SYSTEMTYPE_SHIELDS = 9 + SYSTEMTYPE_MEDBAY = 10 + SYSTEMTYPE_SECURITY = 11 + SYSTEMTYPE_WEAPONS = 12 + SYSTEMTYPE_LOWER_ENGINE = 13 + SYSTEMTYPE_COMMUNICATIONS = 14 + SYSTEMTYPE_SHIP_TASKS = 15 + SYSTEMTYPE_DOORS = 16 + SYSTEMTYPE_SABOTAGE = 17 + SYSTEMTYPE_DECONTAMINATION = 18 + SYSTEMTYPE_LAUNCHPAD = 19 + SYSTEMTYPE_LOCKER_ROOM = 20 + SYSTEMTYPE_LABORATORY = 21 + SYSTEMTYPE_BALCONY = 22 + SYSTEMTYPE_OFFICE = 23 + SYSTEMTYPE_GREENHOUSE = 24 + SYSTEMTYPE_DROPSHIP = 25 + SYSTEMTYPE_DECONTAMINATION_2 = 26 + SYSTEMTYPE_OUTSIDE = 27 + SYSTEMTYPE_SPECIMENS = 28 + SYSTEMTYPE_BOILER_ROOM = 29 + SYSTEMTYPE_VAULT_ROOM = 30 + SYSTEMTYPE_COCKPIT = 31 + SYSTEMTYPE_ARMORY = 32 + SYSTEMTYPE_KITCHEN = 33 + SYSTEMTYPE_VIEWING_DECK = 34 + SYSTEMTYPE_HALL_OF_PORTRAITS = 35 + SYSTEMTYPE_CARGO_BAY = 36 + SYSTEMTYPE_VENTILATION = 37 + SYSTEMTYPE_SHOWERS = 38 + SYSTEMTYPE_ENGINE = 39 + SYSTEMTYPE_BRIG = 40 + SYSTEMTYPE_MEETING_ROOM = 41 + SYSTEMTYPE_RECORDS = 42 + SYSTEMTYPE_LOUNGE = 43 + SYSTEMTYPE_GAP_ROOM = 44 + SYSTEMTYPE_MAIN_HALL = 45 + SYSTEMTYPE_MEDICAL = 46 + + TOTAL_SYSTEMTYPE = 47 +) diff --git a/src/enum/taskbarmode.go b/src/enum/taskbarmode.go new file mode 100644 index 0000000..1bee2ea --- /dev/null +++ b/src/enum/taskbarmode.go @@ -0,0 +1,7 @@ +package enum + +const ( + TASKBARMODE_ALWAYS = 0 + TASKBARMODE_MEETINGS = 1 + TASKBARMODE_NEVER = 2 +) diff --git a/src/enum/tasktypes.go b/src/enum/tasktypes.go new file mode 100644 index 0000000..dc2f67f --- /dev/null +++ b/src/enum/tasktypes.go @@ -0,0 +1,64 @@ +package enum + +const ( + TASKTYPES_SUBMIT_SCAN = 0 + TASKTYPES_PRIME_SHIELDS = 1 + TASKTYPES_FUEL_ENGINES = 2 + TASKTYPES_CHART_COURSE = 3 + TASKTYPES_START_REACTOR = 4 + TASKTYPES_SWIPE_CARD = 5 + TASKTYPES_CLEAR_ASTEROIDS = 6 + TASKTYPES_UPLOAD_DATA = 7 + TASKTYPES_INSPECT_SAMPLE = 8 + TASKTYPES_EMPTY_CHUTE = 9 + TASKTYPES_EMPTY_GARBAGE = 10 + TASKTYPES_ALIGN_ENGINE_OUTPUT = 11 + TASKTYPES_FIX_WIRING = 12 + TASKTYPES_CALIBRATE_DISTRIBUTOR = 13 + TASKTYPES_DIVERT_POWER = 14 + TASKTYPES_UNLOCK_MANIFOLDS = 15 + TASKTYPES_RESET_REACTOR = 16 + TASKTYPES_FIX_LIGHTS = 17 + TASKTYPES_CLEAN_O2_FILTER = 18 + TASKTYPES_FIX_COMMS = 19 + TASKTYPES_RESTORE_O2 = 20 + TASKTYPES_STABILIZE_STEERING = 21 + TASKTYPES_ASSEMBLE_ARTIFACT = 22 + TASKTYPES_SORT_SAMPLES = 23 + TASKTYPES_MEASURE_WEATHER = 24 + TASKTYPES_ENTER_ID_CODE = 25 + TASKTYPES_BUY_BEVERAGE = 26 + TASKTYPES_PROCESS_DATA = 27 + TASKTYPES_RUN_DIAGNOSTICS = 28 + TASKTYPES_WATER_PLANTS = 29 + TASKTYPES_MONITOR_O2 = 30 + TASKTYPES_STORE_ARTIFACTS = 31 + TASKTYPES_FILL_CANISTERS = 32 + TASKTYPES_ACTIVATE_WEATHER_NODES = 33 + TASKTYPES_INSERT_KEYS = 34 + TASKTYPES_RESET_SEISMIC = 35 + TASKTYPES_SCAN_BOARDING_PASS = 36 + TASKTYPES_OPEN_WATERWAYS = 37 + TASKTYPES_REPLACE_WATER_JUG = 38 + TASKTYPES_REPAIR_DRILL = 39 + TASKTYPES_ALIGN_TELESCOPE = 40 + TASKTYPES_RECORD_TEMPERATURE = 41 + TASKTYPES_REBOOT_WIFI = 42 + TASKTYPES_POLISH_RUBY = 43 + TASKTYPES_RESET_BREAKERS = 44 + TASKTYPES_DECONTAMINATE = 45 + TASKTYPES_MAKE_BURGER = 46 + TASKTYPES_UNLOCK_SAFE = 47 + TASKTYPES_SORT_RECORDS = 48 + TASKTYPES_PUT_AWAY_PISTOLS = 49 + TASKTYPES_FIX_SHOWER = 50 + TASKTYPES_CLEAN_TOILET = 51 + TASKTYPES_DRESS_MANNEQUIN = 52 + TASKTYPES_PICK_UP_TOWELS = 53 + TASKTYPES_REWIND_TAPES = 54 + TASKTYPES_START_FANS = 55 + TASKTYPES_DEVELOP_PHOTOS = 56 + TASKTYPES_GET_BIGGOL_SWORD = 57 + TASKTYPES_PUT_AWAY_RIFLES = 58 + TASKTYPES_STOP_CHARLES = 59 +) diff --git a/src/gamedata/01_data.go b/src/gamedata/01_data.go new file mode 100644 index 0000000..fe12d09 --- /dev/null +++ b/src/gamedata/01_data.go @@ -0,0 +1,23 @@ +package gamedata + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type DataH2G struct { + Component *protocol.UpdateComponent +} + +func (d *DataH2G) AsHazel() *protocol.Hazel { + var a protocol.HazelPayload = d + return protocol.NewHazelFromPayload(&a) +} + +func (d *DataH2G) TypeCode() byte { return 0x01 } +func (d *DataH2G) Write(packet *protocol.Packet) { + d.Component.Write(packet) +} +func (d *DataH2G) Read(packet *protocol.Packet) { + d.Component = &protocol.UpdateComponent{} + d.Component.Read(packet) +} diff --git a/src/gamedata/02_rpc.go b/src/gamedata/02_rpc.go new file mode 100644 index 0000000..7c03087 --- /dev/null +++ b/src/gamedata/02_rpc.go @@ -0,0 +1,501 @@ +package gamedata + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/util" +) + +// +// RPC base +// + +type Rpc struct { + SenderNetID uint32 + RPCCallID byte + Sub *RpcSub + UnderlyingHazel *protocol.Hazel +} + +func NewRpcFromHazel(h *protocol.Hazel) *Rpc { + a := &Rpc{} + a.SenderNetID = h.GetUnderlyingPacket().ReadPackedUInt32() + a.RPCCallID = h.GetUnderlyingPacket().Read() + a.UnderlyingHazel = h + return a +} + +func NewRpcFromRpcSub(senderNetID uint32, sub *RpcSub) *Rpc { + return &Rpc{ + SenderNetID: senderNetID, + RPCCallID: (*sub).CallCode(), + Sub: sub, + } +} + +func (d *Rpc) GetUnderlyingHazel() *protocol.Hazel { + return d.UnderlyingHazel +} + +func (d *Rpc) AsHazel() *protocol.Hazel { + var a protocol.HazelPayload = d + return protocol.NewHazelFromPayload(&a) +} + +func (d *Rpc) TypeCode() byte { return 0x02 } +func (d *Rpc) Write(packet *protocol.Packet) { + packet.WritePackedUInt32(d.SenderNetID) + packet.Write(d.RPCCallID) + (*d.Sub).WriteToPacket(packet) +} +func (d *Rpc) Read(packet *protocol.Packet) { + d.SenderNetID = packet.ReadPackedUInt32() + d.RPCCallID = packet.Read() + // TODO: finish this +} + +type RpcSub interface { + CallCode() byte + WriteToPacket(packet *protocol.Packet) + ReadFromPacket(packet *protocol.Packet) +} + +// +// 0x00 RpcPlayAnimation +// + +type RpcPlayAnimation struct{ TaskID byte } + +func (d *RpcPlayAnimation) CallCode() byte { return enum.RPC_PLAY_ANIMATION } +func (d *RpcPlayAnimation) WriteToPacket(packet *protocol.Packet) { packet.Write(d.TaskID) } +func (d *RpcPlayAnimation) ReadFromPacket(packet *protocol.Packet) { d.TaskID = packet.Read() } + +// +// 0x01 RpcCompleteTask +// + +type RpcCompleteTask struct{ TaskIndex uint32 } + +func (d *RpcCompleteTask) CallCode() byte { return enum.RPC_COMPLETE_TASK } +func (d *RpcCompleteTask) WriteToPacket(packet *protocol.Packet) { + packet.WritePackedUInt32(d.TaskIndex) +} +func (d *RpcCompleteTask) ReadFromPacket(packet *protocol.Packet) { + d.TaskIndex = packet.ReadPackedUInt32() +} + +// +// 0x02 RpcSyncSettings +// + +type RpcSyncSettings struct{ Options *protocol.GameOptionsData } + +func (d *RpcSyncSettings) CallCode() byte { return enum.RPC_SYNC_SETTINGS } +func (d *RpcSyncSettings) WriteToPacket(packet *protocol.Packet) { d.Options.Write(packet) } +func (d *RpcSyncSettings) ReadFromPacket(packet *protocol.Packet) { + d.Options = protocol.NewGameOptionsDataFromPacket(packet) +} + +// +// 0x03 RpcSetInfected +// + +type RpcSetInfected struct{ Impostors []byte } + +func (d *RpcSetInfected) CallCode() byte { return enum.RPC_SET_INFECTED } +func (d *RpcSetInfected) WriteToPacket(packet *protocol.Packet) { + packet.Write(byte(len(d.Impostors))) + for i := 0; i < len(d.Impostors); i++ { + packet.Write(d.Impostors[i]) + } +} +func (d *RpcSetInfected) ReadFromPacket(packet *protocol.Packet) { + d.Impostors = make([]byte, packet.Read()) + for i := 0; i < len(d.Impostors); i++ { + d.Impostors[i] = packet.Read() + } +} + +// +// 0x04 RpcExiled +// + +type RpcExiled struct{} + +func (d *RpcExiled) CallCode() byte { return enum.RPC_EXILED } +func (d *RpcExiled) WriteToPacket(packet *protocol.Packet) {} +func (d *RpcExiled) ReadFromPacket(packet *protocol.Packet) {} + +// +// 0x05 RpcCheckName +// + +type RpcCheckName struct{ Name string } + +func (d *RpcCheckName) CallCode() byte { return enum.RPC_CHECK_NAME } +func (d *RpcCheckName) WriteToPacket(packet *protocol.Packet) { packet.WriteString(d.Name) } +func (d *RpcCheckName) ReadFromPacket(packet *protocol.Packet) { d.Name = packet.ReadString() } + +// +// 0x06 RpcSetName +// + +type RpcSetName struct{ Name string } + +func (d *RpcSetName) CallCode() byte { return enum.RPC_SET_NAME } +func (d *RpcSetName) WriteToPacket(packet *protocol.Packet) { packet.WriteString(d.Name) } +func (d *RpcSetName) ReadFromPacket(packet *protocol.Packet) { d.Name = packet.ReadString() } + +// +// 0x07 RpcCheckColor +// + +type RpcCheckColor struct{ Color byte } + +func (d *RpcCheckColor) CallCode() byte { return enum.RPC_CHECK_COLOR } +func (d *RpcCheckColor) WriteToPacket(packet *protocol.Packet) { packet.Write(d.Color) } +func (d *RpcCheckColor) ReadFromPacket(packet *protocol.Packet) { d.Color = packet.Read() } + +// +// 0x08 RpcSetColor +// + +type RpcSetColor struct{ Color byte } + +func (d *RpcSetColor) CallCode() byte { return enum.RPC_SET_COLOR } +func (d *RpcSetColor) WriteToPacket(packet *protocol.Packet) { packet.Write(d.Color) } +func (d *RpcSetColor) ReadFromPacket(packet *protocol.Packet) { d.Color = packet.Read() } + +// +// 0x09 RpcSetHat +// + +type RpcSetHat struct{ HatID uint32 } + +func (d *RpcSetHat) CallCode() byte { return enum.RPC_SET_HAT } +func (d *RpcSetHat) WriteToPacket(packet *protocol.Packet) { packet.WritePackedUInt32(d.HatID) } +func (d *RpcSetHat) ReadFromPacket(packet *protocol.Packet) { d.HatID = packet.ReadPackedUInt32() } + +// +// 0x0a RpcSetSkin +// + +type RpcSetSkin struct{ SkinID uint32 } + +func (d *RpcSetSkin) CallCode() byte { return enum.RPC_SET_SKIN } +func (d *RpcSetSkin) WriteToPacket(packet *protocol.Packet) { packet.WritePackedUInt32(d.SkinID) } +func (d *RpcSetSkin) ReadFromPacket(packet *protocol.Packet) { d.SkinID = packet.ReadPackedUInt32() } + +// +// 0x0b RpcReportDeadBody +// + +type RpcReportDeadBody struct{ VictimPlayerID byte } + +func (d *RpcReportDeadBody) CallCode() byte { return enum.RPC_REPORT_DEAD_BODY } +func (d *RpcReportDeadBody) WriteToPacket(packet *protocol.Packet) { packet.Write(d.VictimPlayerID) } +func (d *RpcReportDeadBody) ReadFromPacket(packet *protocol.Packet) { d.VictimPlayerID = packet.Read() } +func (d *RpcReportDeadBody) IsEmergencyMeetingButton() bool { return d.VictimPlayerID == 255 } + +// +// 0x0c RpcMurderPlayer +// + +type RpcMurderPlayer struct{ VictimNetID uint32 } + +func (d *RpcMurderPlayer) CallCode() byte { return enum.RPC_MURDER_PLAYER } +func (d *RpcMurderPlayer) WriteToPacket(packet *protocol.Packet) { + packet.WritePackedUInt32(d.VictimNetID) +} +func (d *RpcMurderPlayer) ReadFromPacket(packet *protocol.Packet) { + d.VictimNetID = packet.ReadPackedUInt32() +} + +// +// 0x0d RpcSendChat +// + +type RpcSendChat struct{ Message string } + +func (d *RpcSendChat) CallCode() byte { return enum.RPC_SEND_CHAT } +func (d *RpcSendChat) WriteToPacket(packet *protocol.Packet) { packet.WriteString(d.Message) } +func (d *RpcSendChat) ReadFromPacket(packet *protocol.Packet) { d.Message = packet.ReadString() } + +// +// 0x0e RpcStartMeeting +// + +type RpcStartMeeting struct{ VictimPlayerID byte } + +func (d *RpcStartMeeting) CallCode() byte { return enum.RPC_START_MEETING } +func (d *RpcStartMeeting) WriteToPacket(packet *protocol.Packet) { packet.Write(d.VictimPlayerID) } +func (d *RpcStartMeeting) ReadFromPacket(packet *protocol.Packet) { d.VictimPlayerID = packet.Read() } +func (d *RpcStartMeeting) IsEmergencyMeetingButton() bool { return d.VictimPlayerID == 255 } + +// +// 0x0f RpcSetScanner +// + +type RpcSetScanner struct { + IsScanning bool + SequenceID byte +} + +func (d *RpcSetScanner) CallCode() byte { return enum.RPC_SET_SCANNER } +func (d *RpcSetScanner) WriteToPacket(packet *protocol.Packet) { + packet.WriteBool(d.IsScanning) + packet.Write(d.SequenceID) +} +func (d *RpcSetScanner) ReadFromPacket(packet *protocol.Packet) { + d.IsScanning = packet.ReadBool() + d.SequenceID = packet.Read() +} + +// +// 0x10 RpcSendChatNote +// + +type RpcSendChatNote struct { + PlayerID byte + ChatNoteType byte +} + +func (d *RpcSendChatNote) CallCode() byte { return enum.RPC_SEND_CHAT_NOTE } +func (d *RpcSendChatNote) WriteToPacket(packet *protocol.Packet) { + packet.Write(d.PlayerID) + packet.Write(d.ChatNoteType) +} +func (d *RpcSendChatNote) ReadFromPacket(packet *protocol.Packet) { + d.PlayerID = packet.Read() + d.ChatNoteType = packet.Read() +} + +// +// 0x11 RpcSetPet +// + +type RpcSetPet struct{ PetID uint32 } + +func (d *RpcSetPet) CallCode() byte { return enum.RPC_SET_PET } +func (d *RpcSetPet) WriteToPacket(packet *protocol.Packet) { packet.WritePackedUInt32(d.PetID) } +func (d *RpcSetPet) ReadFromPacket(packet *protocol.Packet) { d.PetID = packet.ReadPackedUInt32() } + +// +// 0x12 RpcSetStartCounter +// + +type RpcSetStartCounter struct { + SequenceID uint32 + TimeRemaining byte +} + +func (d *RpcSetStartCounter) CallCode() byte { return enum.RPC_SET_START_COUNTER } +func (d *RpcSetStartCounter) WriteToPacket(packet *protocol.Packet) { + packet.WritePackedUInt32(d.SequenceID) + packet.Write(d.TimeRemaining) +} +func (d *RpcSetStartCounter) ReadFromPacket(packet *protocol.Packet) { + d.SequenceID = packet.ReadPackedUInt32() + d.TimeRemaining = packet.Read() +} +func (d *RpcSetStartCounter) ShouldResetTimer() bool { return d.TimeRemaining == 0xff } + +// +// 0x13 RpcEnterVent +// + +type RpcEnterVent struct{ VentID uint32 } + +func (d *RpcEnterVent) CallCode() byte { return enum.RPC_ENTER_VENT } +func (d *RpcEnterVent) WriteToPacket(packet *protocol.Packet) { packet.WritePackedUInt32(d.VentID) } +func (d *RpcEnterVent) ReadFromPacket(packet *protocol.Packet) { d.VentID = packet.ReadPackedUInt32() } + +// +// 0x14 RpcExitVent +// + +type RpcExitVent struct{ VentID uint32 } + +func (d *RpcExitVent) CallCode() byte { return enum.RPC_EXIT_VENT } +func (d *RpcExitVent) WriteToPacket(packet *protocol.Packet) { packet.WritePackedUInt32(d.VentID) } +func (d *RpcExitVent) ReadFromPacket(packet *protocol.Packet) { d.VentID = packet.ReadPackedUInt32() } + +// +// 0x15 RpcSnapTo +// + +type RpcSnapTo struct { + Position util.Vec2 + LastSequenceID uint16 +} + +func (d *RpcSnapTo) CallCode() byte { return enum.RPC_SNAP_TO } +func (d *RpcSnapTo) WriteToPacket(packet *protocol.Packet) { + packet.WriteVector2(d.Position) + packet.WriteUInt16(d.LastSequenceID) +} +func (d *RpcSnapTo) ReadFromPacket(packet *protocol.Packet) { + d.Position = packet.ReadVector2() + d.LastSequenceID = packet.ReadUInt16() +} + +// +// 0x16 RpcClose +// + +type RpcClose struct{} + +func (d *RpcClose) CallCode() byte { return enum.RPC_CLOSE } +func (d *RpcClose) WriteToPacket(packet *protocol.Packet) {} +func (d *RpcClose) ReadFromPacket(packet *protocol.Packet) {} + +// +// 0x17 RpcVotingComplete +// + +type RpcVotingComplete struct { + VoteStates []byte + ExiledPlayerID byte + IsTie bool +} + +// TODO: write serializer for voting complete +func (d *RpcVotingComplete) CallCode() byte { return enum.RPC_VOTING_COMPLETE } +func (d *RpcVotingComplete) WriteToPacket(packet *protocol.Packet) {} +func (d *RpcVotingComplete) ReadFromPacket(packet *protocol.Packet) {} + +// +// 0x18 RpcCastVote +// + +type RpcCastVote struct { + VotingPlayerID byte + SuspectPlayerID byte +} + +func (d *RpcCastVote) CallCode() byte { return enum.RPC_CAST_VOTE } +func (d *RpcCastVote) WriteToPacket(packet *protocol.Packet) { + packet.Write(d.VotingPlayerID) + packet.Write(d.SuspectPlayerID) +} +func (d *RpcCastVote) ReadFromPacket(packet *protocol.Packet) { + d.VotingPlayerID = packet.Read() + d.SuspectPlayerID = packet.Read() +} + +// +// 0x19 RpcClearVote +// + +type RpcClearVote struct{} + +func (d *RpcClearVote) CallCode() byte { return enum.RPC_CLEAR_VOTE } +func (d *RpcClearVote) WriteToPacket(packet *protocol.Packet) {} +func (d *RpcClearVote) ReadFromPacket(packet *protocol.Packet) {} + +// +// 0x1a RpcAddVote +// + +type RpcAddVote struct { + VotingClientID uint32 + TargetClientID uint32 +} + +func (d *RpcAddVote) CallCode() byte { return enum.RPC_ADD_VOTE } +func (d *RpcAddVote) WriteToPacket(packet *protocol.Packet) { + packet.WriteUInt32(d.VotingClientID) + packet.WriteUInt32(d.TargetClientID) +} +func (d *RpcAddVote) ReadFromPacket(packet *protocol.Packet) { + d.VotingClientID = packet.ReadUInt32() + d.TargetClientID = packet.ReadUInt32() +} + +// +// 0x1b RpcCloseDoorsOfType +// + +type RpcCloseDoorsOfType struct { + SystemID byte +} + +func (d *RpcCloseDoorsOfType) CallCode() byte { return enum.RPC_CLOSE_DOORS_OF_TYPE } +func (d *RpcCloseDoorsOfType) WriteToPacket(packet *protocol.Packet) { packet.Write(d.SystemID) } +func (d *RpcCloseDoorsOfType) ReadFromPacket(packet *protocol.Packet) { d.SystemID = packet.Read() } + +// +// 0x1c RpcRepairSystem +// + +type RpcRepairSystem struct { + SystemID byte + PlayerControlNetID uint32 + Amount byte +} + +func (d *RpcRepairSystem) CallCode() byte { return enum.RPC_REPAIR_SYSTEM } +func (d *RpcRepairSystem) WriteToPacket(packet *protocol.Packet) { + packet.Write(d.SystemID) + packet.WritePackedUInt32(d.PlayerControlNetID) + packet.Write(d.Amount) +} +func (d *RpcRepairSystem) ReadFromPacket(packet *protocol.Packet) { + d.SystemID = packet.Read() + d.PlayerControlNetID = packet.ReadPackedUInt32() + d.Amount = packet.Read() +} + +// +// 0x1d RpcSetTasks +// + +type RpcSetTasks struct { + PlayerID byte + Tasks []byte +} + +func (d *RpcSetTasks) CallCode() byte { return enum.RPC_SET_TASKS } +func (d *RpcSetTasks) WriteToPacket(packet *protocol.Packet) { + packet.Write(d.PlayerID) + packet.WritePackedUInt32(uint32(len(d.Tasks))) + for i := 0; i < len(d.Tasks); i++ { + packet.Write(d.Tasks[i]) + } +} +func (d *RpcSetTasks) ReadFromPacket(packet *protocol.Packet) { + d.PlayerID = packet.Read() + d.Tasks = make([]byte, packet.ReadPackedUInt32()) + for i := 0; i < len(d.Tasks); i++ { + d.Tasks[i] = packet.Read() + } +} + +// +// 0x1f RpcClimbLadder +// + +type RpcClimbLadder struct { + LadderID byte + SequenceID byte +} + +func (d *RpcClimbLadder) CallCode() byte { return enum.RPC_CLIMB_LADDER } +func (d *RpcClimbLadder) WriteToPacket(packet *protocol.Packet) { + packet.Write(d.LadderID) + packet.Write(d.SequenceID) +} +func (d *RpcClimbLadder) ReadFromPacket(packet *protocol.Packet) { + d.LadderID = packet.Read() + d.SequenceID = packet.Read() +} + +// +// 0x1f RpcUsePlatform +// + +type RpcUsePlatform struct{} + +func (d *RpcUsePlatform) CallCode() byte { return enum.RPC_CLIMB_LADDER } +func (d *RpcUsePlatform) WriteToPacket(packet *protocol.Packet) {} +func (d *RpcUsePlatform) ReadFromPacket(packet *protocol.Packet) {} diff --git a/src/gamedata/04_spawn.go b/src/gamedata/04_spawn.go new file mode 100644 index 0000000..0edf575 --- /dev/null +++ b/src/gamedata/04_spawn.go @@ -0,0 +1,36 @@ +package gamedata + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type SpawnH2G struct { + SpawnType uint32 + OwnerID uint32 + SpawnFlags byte + Components []*protocol.SpawnComponent +} + +func (d *SpawnH2G) AsHazel() *protocol.Hazel { + var a protocol.HazelPayload = d + return protocol.NewHazelFromPayload(&a) +} + +func (d *SpawnH2G) TypeCode() byte { return 0x04 } + +func (d *SpawnH2G) Write(packet *protocol.Packet) { + packet.WritePackedUInt32(d.SpawnType) + packet.WritePackedUInt32(d.OwnerID) + packet.Write(d.SpawnFlags) + for i := 0; i < len(d.Components); i++ { + d.Components[i].Write(packet) + } +} +func (d *SpawnH2G) Read(packet *protocol.Packet) { + d.SpawnType = packet.ReadPackedUInt32() + d.OwnerID = packet.ReadPackedUInt32() + d.SpawnFlags = packet.Read() + d.Components = make([]*protocol.SpawnComponent, packet.Read()) + for i := 0; i < len(d.Components); i++ { + d.Components[i] = &protocol.SpawnComponent{} + d.Components[i].Read(packet) + } +} diff --git a/src/gamedata/05_despawn.go b/src/gamedata/05_despawn.go new file mode 100644 index 0000000..facb952 --- /dev/null +++ b/src/gamedata/05_despawn.go @@ -0,0 +1,16 @@ +package gamedata + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type DespawnH2G struct { + NetID uint32 +} + +func (d *DespawnH2G) AsHazel() *protocol.Hazel { + var a protocol.HazelPayload = d + return protocol.NewHazelFromPayload(&a) +} + +func (d *DespawnH2G) TypeCode() byte { return 0x05 } +func (d *DespawnH2G) Write(packet *protocol.Packet) { packet.WritePackedUInt32(d.NetID) } +func (d *DespawnH2G) Read(packet *protocol.Packet) { d.NetID = packet.ReadPackedUInt32() } diff --git a/src/gamedata/06_scenechange.go b/src/gamedata/06_scenechange.go new file mode 100644 index 0000000..2c65929 --- /dev/null +++ b/src/gamedata/06_scenechange.go @@ -0,0 +1,23 @@ +package gamedata + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type SceneChangeC2H struct { + ClientID uint32 + SceneName string +} + +func (d *SceneChangeC2H) AsHazel() *protocol.Hazel { + var a protocol.HazelPayload = d + return protocol.NewHazelFromPayload(&a) +} + +func (d *SceneChangeC2H) TypeCode() byte { return 0x06 } +func (d *SceneChangeC2H) Write(packet *protocol.Packet) { + packet.WritePackedUInt32(d.ClientID) + packet.WriteString(d.SceneName) +} +func (d *SceneChangeC2H) Read(packet *protocol.Packet) { + d.ClientID = packet.ReadPackedUInt32() + d.SceneName = packet.ReadString() +} diff --git a/src/gamedata/07_ready.go b/src/gamedata/07_ready.go new file mode 100644 index 0000000..0172b7f --- /dev/null +++ b/src/gamedata/07_ready.go @@ -0,0 +1,16 @@ +package gamedata + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type ReadyC2H struct { + ReadyClientID uint32 +} + +func (d *ReadyC2H) AsHazel() *protocol.Hazel { + var a protocol.HazelPayload = d + return protocol.NewHazelFromPayload(&a) +} + +func (d *ReadyC2H) TypeCode() byte { return 0x07 } +func (d *ReadyC2H) Write(packet *protocol.Packet) { packet.WritePackedUInt32(d.ReadyClientID) } +func (d *ReadyC2H) Read(packet *protocol.Packet) { d.ReadyClientID = packet.ReadPackedUInt32() } diff --git a/src/gamedata/08_changesettings.go b/src/gamedata/08_changesettings.go new file mode 100644 index 0000000..5955085 --- /dev/null +++ b/src/gamedata/08_changesettings.go @@ -0,0 +1,5 @@ +package gamedata + +// ChangeSettingsAll is never used +type ChangeSettingsAll struct { +} diff --git a/src/gamedata/205_clientinfo.go b/src/gamedata/205_clientinfo.go new file mode 100644 index 0000000..66155fb --- /dev/null +++ b/src/gamedata/205_clientinfo.go @@ -0,0 +1,23 @@ +package gamedata + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type ClientInfoC2S struct { + ClientID uint32 + PlatformID uint32 +} + +func (d *ClientInfoC2S) AsHazel() *protocol.Hazel { + var a protocol.HazelPayload = d + return protocol.NewHazelFromPayload(&a) +} + +func (d *ClientInfoC2S) TypeCode() byte { return 0xcd } +func (d *ClientInfoC2S) Write(packet *protocol.Packet) { + packet.WritePackedUInt32(d.ClientID) + packet.WritePackedUInt32(d.PlatformID) +} +func (d *ClientInfoC2S) Read(packet *protocol.Packet) { + d.ClientID = packet.ReadPackedUInt32() + d.PlatformID = packet.ReadPackedUInt32() +} diff --git a/src/gamedata/go.mod b/src/gamedata/go.mod new file mode 100644 index 0000000..750f2df --- /dev/null +++ b/src/gamedata/go.mod @@ -0,0 +1,13 @@ +module codehub.onpointcoding.net/sean/go-susapp/gamedata + +go 1.17 + +replace ( + codehub.onpointcoding.net/sean/go-susapp/protocol => ../protocol + codehub.onpointcoding.net/sean/go-susapp/util => ../util +) + +require ( + codehub.onpointcoding.net/sean/go-susapp/protocol v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/util v1.0.0 +) diff --git a/src/innernetobjects/gamedata.go b/src/innernetobjects/gamedata.go new file mode 100644 index 0000000..a52efd7 --- /dev/null +++ b/src/innernetobjects/gamedata.go @@ -0,0 +1,35 @@ +package innernetobjects + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type GameData struct { + Players []*protocol.PlayerData +} + +func (data *GameData) Write(packet *protocol.Packet, isSpawn bool) { + if isSpawn { + packet.WriteUInt32(uint32(len(data.Players))) + } else { + packet.Write(byte(len(data.Players))) + } + for i := 0; i < len(data.Players); i++ { + data.Players[i].Write(packet) + } +} +func (data *GameData) Read(packet *protocol.Packet, isSpawn bool) { + packet.ReadBytes(4) + if packet.Dump()[0] == 0x0c { + return + } + if isSpawn { + data.Players = make([]*protocol.PlayerData, packet.ReadPackedUInt32()) + } else { + data.Players = make([]*protocol.PlayerData, packet.Read()) + } + for i := 0; i < len(data.Players); i++ { + data.Players[i] = &protocol.PlayerData{} + data.Players[i].Read(packet) + } +} diff --git a/src/innernetobjects/go.mod b/src/innernetobjects/go.mod new file mode 100644 index 0000000..2ed8e79 --- /dev/null +++ b/src/innernetobjects/go.mod @@ -0,0 +1,17 @@ +module codehub.onpointcoding.net/sean/go-susapp/innernetobjects + +go 1.17 + +replace ( + codehub.onpointcoding.net/sean/go-susapp/enum => ../enum + codehub.onpointcoding.net/sean/go-susapp/protocol => ../protocol + codehub.onpointcoding.net/sean/go-susapp/systemtypes => ../systemtypes + codehub.onpointcoding.net/sean/go-susapp/util => ../util +) + +require ( + codehub.onpointcoding.net/sean/go-susapp/enum v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/protocol v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/systemtypes v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/util v1.0.0 // indirect +) diff --git a/src/innernetobjects/innernetobject.go b/src/innernetobjects/innernetobject.go new file mode 100644 index 0000000..3ddc188 --- /dev/null +++ b/src/innernetobjects/innernetobject.go @@ -0,0 +1,8 @@ +package innernetobjects + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type InnerNetObject interface { + Write(packet *protocol.Packet, isSpawn bool) + Read(packet *protocol.Packet, isSpawn bool) +} diff --git a/src/innernetobjects/mirashipstatus.go b/src/innernetobjects/mirashipstatus.go new file mode 100644 index 0000000..cf15558 --- /dev/null +++ b/src/innernetobjects/mirashipstatus.go @@ -0,0 +1,33 @@ +package innernetobjects + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/systemtypes" +) + +type MiraShipStatus struct { + Systems map[int]*systemtypes.SystemType + SystemsMask uint32 +} + +func NewMiraShipStatus() *MiraShipStatus { + a := &MiraShipStatus{} + a.Systems = make(map[int]*systemtypes.SystemType) + a.Systems[enum.SYSTEMTYPE_REACTOR] = systemtypes.SystemTypeCast(&systemtypes.ReactorSystem{}) + a.Systems[enum.SYSTEMTYPE_ELECTRICAL] = systemtypes.SystemTypeCast(&systemtypes.SwitchSystem{}) + a.Systems[enum.SYSTEMTYPE_O2] = systemtypes.SystemTypeCast(&systemtypes.LifeSuppSystem{}) + a.Systems[enum.SYSTEMTYPE_MEDBAY] = systemtypes.SystemTypeCast(&systemtypes.MedScanSystem{}) + a.Systems[enum.SYSTEMTYPE_COMMUNICATIONS] = systemtypes.SystemTypeCast(&systemtypes.HqHudSystem{}) + a.Systems[enum.SYSTEMTYPE_SABOTAGE] = systemtypes.SystemTypeCast(&systemtypes.SabotageSystem{}) + a.Systems[enum.SYSTEMTYPE_DECONTAMINATION] = systemtypes.SystemTypeCast(&systemtypes.DeconSystem{}) + return a +} + +func (mira *MiraShipStatus) Write(packet *protocol.Packet, isSpawn bool) { + WriteShipStatus(mira.Systems, &mira.SystemsMask, packet, isSpawn) +} + +func (mira *MiraShipStatus) Read(packet *protocol.Packet, isSpawn bool) { + ReadShipStatus(mira.Systems, &mira.SystemsMask, packet, isSpawn) +} diff --git a/src/innernetobjects/playercontrol.go b/src/innernetobjects/playercontrol.go new file mode 100644 index 0000000..d80bada --- /dev/null +++ b/src/innernetobjects/playercontrol.go @@ -0,0 +1,32 @@ +package innernetobjects + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type PlayerControl struct { + IsNew bool + PlayerID byte +} + +func (control *PlayerControl) Write(packet *protocol.Packet, isSpawn bool) { + p := protocol.NewEmptyPacket() + if isSpawn { + p.WriteBool(control.IsNew) + p.Write(control.PlayerID) + h := protocol.NewHazelFromRawData(0, p.Dump()) + h.WriteToPacket(packet) + } else { + p.Write(control.PlayerID) + p.Copy(packet) + } +} + +func (control *PlayerControl) Read(packet *protocol.Packet, isSpawn bool) { + p := packet + if isSpawn { + p = packet.ReadHazel().GetUnderlyingPacket() + control.IsNew = p.ReadBool() + } + control.PlayerID = p.Read() +} diff --git a/src/innernetobjects/playernetworktransform.go b/src/innernetobjects/playernetworktransform.go new file mode 100644 index 0000000..1a7f43f --- /dev/null +++ b/src/innernetobjects/playernetworktransform.go @@ -0,0 +1,35 @@ +package innernetobjects + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/util" +) + +type PlayerNetworkTransform struct { + LastSequenceID uint16 + TargetPosition util.Vec2 + TargetVelocity util.Vec2 +} + +func (transform *PlayerNetworkTransform) Write(packet *protocol.Packet, isSpawn bool) { + p := protocol.NewEmptyPacket() + p.WriteUInt16(transform.LastSequenceID) + p.WriteVector2(transform.TargetPosition) + p.WriteVector2(transform.TargetVelocity) + if isSpawn { + h := protocol.NewHazelFromRawData(0, p.Dump()) + h.WriteToPacket(packet) + } else { + p.Copy(packet) + } +} + +func (transform *PlayerNetworkTransform) Read(packet *protocol.Packet, isSpawn bool) { + p := packet + if isSpawn { + p = packet.ReadHazel().GetUnderlyingPacket() + } + transform.LastSequenceID = p.ReadUInt16() + transform.TargetPosition = p.ReadVector2() + transform.TargetVelocity = p.ReadVector2() +} diff --git a/src/innernetobjects/playerphysics.go b/src/innernetobjects/playerphysics.go new file mode 100644 index 0000000..b733ae5 --- /dev/null +++ b/src/innernetobjects/playerphysics.go @@ -0,0 +1,9 @@ +package innernetobjects + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +// PlayerPhysics has no data +type PlayerPhysics struct{} + +func (control *PlayerPhysics) Write(packet *protocol.Packet, isSpawn bool) {} +func (control *PlayerPhysics) Read(packet *protocol.Packet, isSpawn bool) {} diff --git a/src/innernetobjects/polusshipstatus.go b/src/innernetobjects/polusshipstatus.go new file mode 100644 index 0000000..cc0cf47 --- /dev/null +++ b/src/innernetobjects/polusshipstatus.go @@ -0,0 +1,35 @@ +package innernetobjects + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/systemtypes" +) + +type PolusShipStatus struct { + Systems map[int]*systemtypes.SystemType + SystemsMask uint32 +} + +func NewPolusShipStatus() *MiraShipStatus { + a := &MiraShipStatus{} + a.Systems = make(map[int]*systemtypes.SystemType) + a.Systems[enum.SYSTEMTYPE_ELECTRICAL] = systemtypes.SystemTypeCast(&systemtypes.SwitchSystem{}) + a.Systems[enum.SYSTEMTYPE_MEDBAY] = systemtypes.SystemTypeCast(&systemtypes.MedScanSystem{}) + a.Systems[enum.SYSTEMTYPE_SECURITY] = systemtypes.SystemTypeCast(&systemtypes.SecurityCameraSystem{}) + a.Systems[enum.SYSTEMTYPE_COMMUNICATIONS] = systemtypes.SystemTypeCast(&systemtypes.HudOverrideSystem{}) + a.Systems[enum.SYSTEMTYPE_DOORS] = systemtypes.SystemTypeCast(&systemtypes.DoorsSystem{}) + a.Systems[enum.SYSTEMTYPE_DECONTAMINATION] = systemtypes.SystemTypeCast(&systemtypes.DeconSystem{}) + a.Systems[enum.SYSTEMTYPE_DECONTAMINATION_2] = systemtypes.SystemTypeCast(&systemtypes.DeconSystem{}) + a.Systems[enum.SYSTEMTYPE_SABOTAGE] = systemtypes.SystemTypeCast(&systemtypes.SabotageSystem{}) + a.Systems[enum.SYSTEMTYPE_LABORATORY] = systemtypes.SystemTypeCast(&systemtypes.ReactorSystem{}) + return a +} + +func (polus *PolusShipStatus) Write(packet *protocol.Packet, isSpawn bool) { + WriteShipStatus(polus.Systems, &polus.SystemsMask, packet, isSpawn) +} + +func (polus *PolusShipStatus) Read(packet *protocol.Packet, isSpawn bool) { + ReadShipStatus(polus.Systems, &polus.SystemsMask, packet, isSpawn) +} diff --git a/src/innernetobjects/shipstatus.go b/src/innernetobjects/shipstatus.go new file mode 100644 index 0000000..6553e96 --- /dev/null +++ b/src/innernetobjects/shipstatus.go @@ -0,0 +1,50 @@ +package innernetobjects + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/systemtypes" +) + +func WriteShipStatus(Systems map[int]*systemtypes.SystemType, SystemsMask *uint32, packet *protocol.Packet, isSpawn bool) { + p := protocol.NewEmptyPacket() + for i := 0; i < enum.TOTAL_SYSTEMTYPE; i++ { + if isSpawn { + if val, ok := Systems[i]; ok { + (*val).Write(p, isSpawn) + } + } else { + if (*SystemsMask & (1 << i)) != 0 { + (*Systems[i]).Write(p, isSpawn) + } + } + } + if isSpawn { + h := protocol.NewHazelFromRawData(0, p.Dump()) + h.WriteToPacket(packet) + } else { + packet.WritePackedUInt32(*SystemsMask) + p.Copy(packet) + } +} + +func ReadShipStatus(Systems map[int]*systemtypes.SystemType, SystemsMask *uint32, packet *protocol.Packet, isSpawn bool) { + p := packet + if isSpawn { + p = packet.ReadHazel().GetUnderlyingPacket() + } else { + *SystemsMask = p.ReadPackedUInt32() + } + + for i := 0; i < enum.TOTAL_SYSTEMTYPE; i++ { + if isSpawn { + if val, ok := Systems[i]; ok { + (*val).Read(p, isSpawn) + } + } else { + if (*SystemsMask & (1 << i)) != 0 { + (*Systems[i]).Read(p, isSpawn) + } + } + } +} diff --git a/src/innernetobjects/skeldshipstatus.go b/src/innernetobjects/skeldshipstatus.go new file mode 100644 index 0000000..d776a53 --- /dev/null +++ b/src/innernetobjects/skeldshipstatus.go @@ -0,0 +1,34 @@ +package innernetobjects + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/systemtypes" +) + +type SkeldShipStatus struct { + Systems map[int]*systemtypes.SystemType + SystemsMask uint32 +} + +func NewSkeldShipStatus() *SkeldShipStatus { + a := &SkeldShipStatus{} + a.Systems = make(map[int]*systemtypes.SystemType) + a.Systems[enum.SYSTEMTYPE_REACTOR] = systemtypes.SystemTypeCast(&systemtypes.ReactorSystem{}) + a.Systems[enum.SYSTEMTYPE_ELECTRICAL] = systemtypes.SystemTypeCast(&systemtypes.SwitchSystem{}) + a.Systems[enum.SYSTEMTYPE_O2] = systemtypes.SystemTypeCast(&systemtypes.LifeSuppSystem{}) + a.Systems[enum.SYSTEMTYPE_MEDBAY] = systemtypes.SystemTypeCast(&systemtypes.MedScanSystem{}) + a.Systems[enum.SYSTEMTYPE_SECURITY] = systemtypes.SystemTypeCast(&systemtypes.SecurityCameraSystem{}) + a.Systems[enum.SYSTEMTYPE_COMMUNICATIONS] = systemtypes.SystemTypeCast(&systemtypes.HudOverrideSystem{}) + a.Systems[enum.SYSTEMTYPE_DOORS] = systemtypes.SystemTypeCast(&systemtypes.AutoDoorsSystem{}) + a.Systems[enum.SYSTEMTYPE_SABOTAGE] = systemtypes.SystemTypeCast(&systemtypes.SabotageSystem{}) + return a +} + +func (skeld *SkeldShipStatus) Write(packet *protocol.Packet, isSpawn bool) { + WriteShipStatus(skeld.Systems, &skeld.SystemsMask, packet, isSpawn) +} + +func (skeld *SkeldShipStatus) Read(packet *protocol.Packet, isSpawn bool) { + ReadShipStatus(skeld.Systems, &skeld.SystemsMask, packet, isSpawn) +} diff --git a/src/innernetobjects/votebansystem.go b/src/innernetobjects/votebansystem.go new file mode 100644 index 0000000..8986ad0 --- /dev/null +++ b/src/innernetobjects/votebansystem.go @@ -0,0 +1,46 @@ +package innernetobjects + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type VoteBanSystem struct { + VotedPlayers []*VoteBanItem +} + +type VoteBanItem struct { + Player uint32 + Votes [3]uint32 +} + +func (ban *VoteBanSystem) Write(packet *protocol.Packet, isSpawn bool) { + p := protocol.NewEmptyPacket() + p.Write(byte(len(ban.VotedPlayers))) + for i := 0; i < len(ban.VotedPlayers); i++ { + p.WriteUInt32(ban.VotedPlayers[i].Player) + for j := 0; j < 3; j++ { + p.WritePackedUInt32(ban.VotedPlayers[i].Votes[j]) + } + } + if isSpawn { + h := protocol.NewHazelFromRawData(0, p.Dump()) + h.WriteToPacket(packet) + } else { + p.Copy(packet) + } +} + +func (ban *VoteBanSystem) Read(packet *protocol.Packet, isSpawn bool) { + p := packet + if isSpawn { + p = packet.ReadHazel().GetUnderlyingPacket() + } + + ban.VotedPlayers = make([]*VoteBanItem, p.Read()) + for i := 0; i < len(ban.VotedPlayers); i++ { + ban.VotedPlayers[i].Player = p.ReadUInt32() + for j := 0; j < 3; j++ { + ban.VotedPlayers[i].Votes[j] = p.ReadPackedUInt32() + } + } +} diff --git a/src/mapdata/go.mod b/src/mapdata/go.mod new file mode 100644 index 0000000..78e9809 --- /dev/null +++ b/src/mapdata/go.mod @@ -0,0 +1,10 @@ +module codehub.onpointcoding.net/sean/go-susapp/mapdata + +go 1.17 + +replace ( + codehub.onpointcoding.net/sean/go-susapp/protocol => ../protocol + codehub.onpointcoding.net/sean/go-susapp/util => ../util +) + +require codehub.onpointcoding.net/sean/go-susapp/util v1.0.0 diff --git a/src/mapdata/lobby.go b/src/mapdata/lobby.go new file mode 100644 index 0000000..2f6aaeb --- /dev/null +++ b/src/mapdata/lobby.go @@ -0,0 +1,16 @@ +package mapdata + +import "codehub.onpointcoding.net/sean/go-susapp/util" + +var LobbySpawnPositions = []util.Vec2{ + util.NewVec2(-1.6, 2.4), + util.NewVec2(-1.3, 2.5), + util.NewVec2(-1.1, 2.5), + util.NewVec2(-0.8, 2.6), + util.NewVec2(-0.6, 2.7), + util.NewVec2(0.7, 2.8), + util.NewVec2(0.9, 2.6), + util.NewVec2(1.1, 2.6), + util.NewVec2(1.4, 2.6), + util.NewVec2(1.7, 2.4), +} diff --git a/src/packets/00_hostgameC2S.go b/src/packets/00_hostgameC2S.go new file mode 100644 index 0000000..70a00da --- /dev/null +++ b/src/packets/00_hostgameC2S.go @@ -0,0 +1,24 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type HostGameC2S struct { + Options *protocol.GameOptionsData + QuickChatMode byte +} + +func (d *HostGameC2S) TypeCode() byte { + return 0x00 +} + +func (d *HostGameC2S) Write(packet *protocol.Packet) { + d.Options.Write(packet) + packet.Write(d.QuickChatMode) +} + +func (d *HostGameC2S) Read(packet *protocol.Packet) { + d.Options = protocol.NewGameOptionsDataFromPacket(packet) + d.QuickChatMode = packet.Read() +} diff --git a/src/packets/00_hostgameS2C.go b/src/packets/00_hostgameS2C.go new file mode 100644 index 0000000..a076b54 --- /dev/null +++ b/src/packets/00_hostgameS2C.go @@ -0,0 +1,19 @@ +package packets + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type HostGameS2C struct { + GameID int32 +} + +func (d *HostGameS2C) TypeCode() int { + return 0x00 +} + +func (d *HostGameS2C) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) +} + +func (d *HostGameS2C) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() +} diff --git a/src/packets/01_joingameC2S.go b/src/packets/01_joingameC2S.go new file mode 100644 index 0000000..d809bbc --- /dev/null +++ b/src/packets/01_joingameC2S.go @@ -0,0 +1,19 @@ +package packets + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type JoinGameC2S struct { + GameID int32 +} + +func (d *JoinGameC2S) TypeCode() byte { + return 0x01 +} + +func (d *JoinGameC2S) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) +} + +func (d *JoinGameC2S) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() +} diff --git a/src/packets/01_joingameS2C.go b/src/packets/01_joingameS2C.go new file mode 100644 index 0000000..1336928 --- /dev/null +++ b/src/packets/01_joingameS2C.go @@ -0,0 +1,19 @@ +package packets + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type JoinGameS2C struct { + DisconnectReason int32 +} + +func (d *JoinGameS2C) TypeCode() byte { + return 0x01 +} + +func (d *JoinGameS2C) Write(packet *protocol.Packet) { + packet.WriteInt32(d.DisconnectReason) +} + +func (d *JoinGameS2C) Read(packet *protocol.Packet) { + d.DisconnectReason = packet.ReadInt32() +} diff --git a/src/packets/01_joingameS2G.go b/src/packets/01_joingameS2G.go new file mode 100644 index 0000000..bf4b145 --- /dev/null +++ b/src/packets/01_joingameS2G.go @@ -0,0 +1,25 @@ +package packets + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type JoinGameS2G struct { + GameID int32 + JoiningClientID uint32 + HostClientID uint32 +} + +func (d *JoinGameS2G) TypeCode() byte { + return 0x01 +} + +func (d *JoinGameS2G) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.WriteUInt32(d.JoiningClientID) + packet.WriteUInt32(d.HostClientID) +} + +func (d *JoinGameS2G) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.JoiningClientID = packet.ReadUInt32() + d.HostClientID = packet.ReadUInt32() +} diff --git a/src/packets/02_startgameH2G.go b/src/packets/02_startgameH2G.go new file mode 100644 index 0000000..4096f85 --- /dev/null +++ b/src/packets/02_startgameH2G.go @@ -0,0 +1,19 @@ +package packets + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type StartGameH2G struct { + GameID int32 +} + +func (d *StartGameH2G) TypeCode() byte { + return 0x02 +} + +func (d *StartGameH2G) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) +} + +func (d *StartGameH2G) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() +} diff --git a/src/packets/03_removegameS2C.go b/src/packets/03_removegameS2C.go new file mode 100644 index 0000000..8b0c19e --- /dev/null +++ b/src/packets/03_removegameS2C.go @@ -0,0 +1,22 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type RemoveGameS2C struct { + DisconnectReason byte +} + +func (d *RemoveGameS2C) TypeCode() byte { + return 0x03 +} + +func (d *RemoveGameS2C) Write(packet *protocol.Packet) { + packet.Write(d.DisconnectReason) +} + +func (d *RemoveGameS2C) Read(packet *protocol.Packet) { + d.DisconnectReason = packet.ReadDefault(enum.DISCONNECT_SERVER_REQUEST) +} diff --git a/src/packets/04_removeplayerH2C.go b/src/packets/04_removeplayerH2C.go new file mode 100644 index 0000000..a15f0b4 --- /dev/null +++ b/src/packets/04_removeplayerH2C.go @@ -0,0 +1,28 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type RemovePlayerH2C struct { + GameID int32 + DisconnectedClientID uint32 + DisconnectReason byte +} + +func (d *RemovePlayerH2C) TypeCode() byte { + return 0x04 +} + +func (d *RemovePlayerH2C) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.WritePackedUInt32(d.DisconnectedClientID) + packet.Write(d.DisconnectReason) +} + +func (d *RemovePlayerH2C) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.DisconnectedClientID = packet.ReadPackedUInt32() + d.DisconnectReason = packet.ReadDefault(enum.DISCONNECT_SERVER_REQUEST) +} diff --git a/src/packets/04_removeplayerS2G.go b/src/packets/04_removeplayerS2G.go new file mode 100644 index 0000000..806eee7 --- /dev/null +++ b/src/packets/04_removeplayerS2G.go @@ -0,0 +1,31 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type RemovePlayerS2G struct { + GameID int32 + DisconnectedClientID uint32 + HostClientID uint32 + DisconnectReason byte +} + +func (d *RemovePlayerS2G) TypeCode() byte { + return 0x04 +} + +func (d *RemovePlayerS2G) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.WriteUInt32(d.DisconnectedClientID) + packet.WriteUInt32(d.HostClientID) + packet.Write(d.DisconnectReason) +} + +func (d *RemovePlayerS2G) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.DisconnectedClientID = packet.ReadUInt32() + d.HostClientID = packet.ReadUInt32() + d.DisconnectReason = packet.ReadDefault(enum.DISCONNECT_SERVER_REQUEST) +} diff --git a/src/packets/05_gamedataall.go b/src/packets/05_gamedataall.go new file mode 100644 index 0000000..16ec01f --- /dev/null +++ b/src/packets/05_gamedataall.go @@ -0,0 +1,36 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type GameDataAll struct { + GameID int32 + Subpackets []*protocol.Hazel +} + +func NewGameDataSingleSubpacket(GameID int32, h *GameDataSubpacket) *GameDataAll { + return &GameDataAll{ + GameID, + []*protocol.Hazel{(*h).AsHazel()}, + } +} + +func (d *GameDataAll) TypeCode() byte { return 0x05 } +func (d *GameDataAll) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + for i := 0; i < len(d.Subpackets); i++ { + d.Subpackets[i].WriteToPacket(packet) + } +} +func (d *GameDataAll) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + for packet.Remaining() > 0 { + a := protocol.NewHazel(packet) + d.Subpackets = append(d.Subpackets, a) + } +} + +type GameDataSubpacket interface { + AsHazel() *protocol.Hazel +} diff --git a/src/packets/06_gamedatato.go b/src/packets/06_gamedatato.go new file mode 100644 index 0000000..ef2509c --- /dev/null +++ b/src/packets/06_gamedatato.go @@ -0,0 +1,38 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type GameDataTo struct { + GameID int32 + TargetClientID uint32 + Subpackets []*protocol.Hazel +} + +func NewGameDataToSingleSubpacket(GameID int32, TargetClientID uint32, h *GameDataSubpacket) *GameDataTo { + return &GameDataTo{ + GameID, + TargetClientID, + []*protocol.Hazel{(*h).AsHazel()}, + } +} + +func (d *GameDataTo) TypeCode() byte { return 0x06 } + +func (d *GameDataTo) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.WritePackedUInt32(d.TargetClientID) + for i := 0; i < len(d.Subpackets); i++ { + d.Subpackets[i].WriteToPacket(packet) + } +} + +func (d *GameDataTo) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.TargetClientID = packet.ReadPackedUInt32() + for packet.Remaining() > 0 { + a := protocol.NewHazel(packet) + d.Subpackets = append(d.Subpackets, a) + } +} diff --git a/src/packets/07_joinedgameH2C.go b/src/packets/07_joinedgameH2C.go new file mode 100644 index 0000000..2c4236a --- /dev/null +++ b/src/packets/07_joinedgameH2C.go @@ -0,0 +1,37 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type JoinedGameH2C struct { + GameID int32 + JoinedClientID uint32 + HostClientID uint32 + OtherClientIDs []uint32 +} + +func (d *JoinedGameH2C) TypeCode() byte { + return 0x07 +} + +func (d *JoinedGameH2C) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.WriteUInt32(d.JoinedClientID) + packet.WriteUInt32(d.HostClientID) + packet.WritePackedUInt32(uint32(len(d.OtherClientIDs))) + for i := 0; i < len(d.OtherClientIDs); i++ { + packet.WritePackedUInt32(d.OtherClientIDs[i]) + } +} + +func (d *JoinedGameH2C) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.JoinedClientID = packet.ReadUInt32() + d.HostClientID = packet.ReadUInt32() + l := int(packet.ReadUInt32()) + d.OtherClientIDs = make([]uint32, l) + for i := 0; i < l; i++ { + d.OtherClientIDs[i] = packet.ReadUInt32() + } +} diff --git a/src/packets/08_endgameH2G.go b/src/packets/08_endgameH2G.go new file mode 100644 index 0000000..6ccb21d --- /dev/null +++ b/src/packets/08_endgameH2G.go @@ -0,0 +1,27 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type EndGameH2G struct { + GameID int32 + GameOverReason byte + ShowAd bool +} + +func (d *EndGameH2G) TypeCode() byte { + return 0x08 +} + +func (d *EndGameH2G) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.Write(d.GameOverReason) + packet.WriteBool(d.ShowAd) +} + +func (d *EndGameH2G) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.GameOverReason = packet.Read() + d.ShowAd = packet.ReadBool() +} diff --git a/src/packets/09_getgamelist.go b/src/packets/09_getgamelist.go new file mode 100644 index 0000000..669e813 --- /dev/null +++ b/src/packets/09_getgamelist.go @@ -0,0 +1,5 @@ +package packets + +// GetGameListAll is a legacy packet +type GetGameListAll struct { +} diff --git a/src/packets/10_altergame.go b/src/packets/10_altergame.go new file mode 100644 index 0000000..b1c42e8 --- /dev/null +++ b/src/packets/10_altergame.go @@ -0,0 +1,27 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type AlterGameH2G struct { + GameID int32 + TagID byte + TagValue byte +} + +func (d *AlterGameH2G) TypeCode() byte { + return 0x0a +} + +func (d *AlterGameH2G) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.Write(d.TagID) + packet.Write(d.TagValue) +} + +func (d *AlterGameH2G) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.TagID = packet.Read() + d.TagValue = packet.Read() +} diff --git a/src/packets/11_kickplayer.go b/src/packets/11_kickplayer.go new file mode 100644 index 0000000..21389d2 --- /dev/null +++ b/src/packets/11_kickplayer.go @@ -0,0 +1,30 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type KickPlayerH2G struct { + GameID int32 + KickedClientID uint32 + IsBanned bool + DisconnectReason byte +} + +func (d *KickPlayerH2G) TypeCode() byte { + return 0x0b +} + +func (d *KickPlayerH2G) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.WritePackedUInt32(d.KickedClientID) + packet.WriteBool(d.IsBanned) + packet.Write(d.DisconnectReason) +} + +func (d *KickPlayerH2G) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.KickedClientID = packet.ReadPackedUInt32() + d.IsBanned = packet.ReadBool() + d.DisconnectReason = packet.Read() +} diff --git a/src/packets/12_waitforhostC2H.go b/src/packets/12_waitforhostC2H.go new file mode 100644 index 0000000..6c50a3c --- /dev/null +++ b/src/packets/12_waitforhostC2H.go @@ -0,0 +1,24 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type WaitForHostC2H struct { + GameID int32 + RejoiningClientID uint32 +} + +func (d *WaitForHostC2H) TypeCode() byte { + return 0x0c +} + +func (d *WaitForHostC2H) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.WriteUInt32(d.RejoiningClientID) +} + +func (d *WaitForHostC2H) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.RejoiningClientID = packet.ReadUInt32() +} diff --git a/src/packets/13_redirect.go b/src/packets/13_redirect.go new file mode 100644 index 0000000..d32f5ac --- /dev/null +++ b/src/packets/13_redirect.go @@ -0,0 +1,26 @@ +package packets + +import ( + "net" + + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type RedirectS2C struct { + IPAddress net.IP + Port uint16 +} + +func (d *RedirectS2C) TypeCode() byte { + return 0x0d +} + +func (d *RedirectS2C) Write(packet *protocol.Packet) { + packet.WriteIP(d.IPAddress) + packet.WriteUInt16(d.Port) +} + +func (d *RedirectS2C) Read(packet *protocol.Packet) { + d.IPAddress = packet.ReadIP() + d.Port = packet.ReadUInt16() +} diff --git a/src/packets/14_reselectserverS2C.go b/src/packets/14_reselectserverS2C.go new file mode 100644 index 0000000..0b1e392 --- /dev/null +++ b/src/packets/14_reselectserverS2C.go @@ -0,0 +1,42 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/util" +) + +type ReselectServerS2C struct { + Version byte + MasterServers []*util.MasterServer +} + +var currentReselectServerVerion byte = 1 + +func (d *ReselectServerS2C) TypeCode() byte { + return 0x0e +} + +func (d *ReselectServerS2C) Write(packet *protocol.Packet) { + packet.Write(currentReselectServerVerion) + packet.WritePackedUInt32(uint32(len(d.MasterServers))) + for i := 0; i < len(d.MasterServers); i++ { + packet.WriteString(d.MasterServers[i].Name) + packet.WriteIP(d.MasterServers[i].IPAddress) + packet.WriteUInt16(d.MasterServers[i].Port) + packet.WritePackedUInt32(d.MasterServers[i].NumberOfConnections) + } +} + +func (d *ReselectServerS2C) Read(packet *protocol.Packet) { + d.Version = packet.Read() + l := int(packet.ReadPackedUInt32()) + d.MasterServers = make([]*util.MasterServer, l) + for i := 0; i < l; i++ { + d.MasterServers[i] = &util.MasterServer{ + Name: packet.ReadString(), + IPAddress: packet.ReadIP(), + Port: packet.ReadUInt16(), + NumberOfConnections: packet.ReadPackedUInt32(), + } + } +} diff --git a/src/packets/16_GetGameListV2C2S.go b/src/packets/16_GetGameListV2C2S.go new file mode 100644 index 0000000..341a40e --- /dev/null +++ b/src/packets/16_GetGameListV2C2S.go @@ -0,0 +1,29 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type GetGameListV2C2S struct { + Unknown02 int32 + Options *protocol.GameOptionsData + QuickChatMode byte +} + +var currentUnknown02Value int32 = 0x02 + +func (d *GetGameListV2C2S) TypeCode() byte { + return 0x10 +} + +func (d *GetGameListV2C2S) Write(packet *protocol.Packet) { + packet.WritePackedInt32(currentUnknown02Value) + d.Options.Write(packet) + packet.Write(d.QuickChatMode) +} + +func (d *GetGameListV2C2S) Read(packet *protocol.Packet) { + d.Unknown02 = packet.ReadInt32() + d.Options = protocol.NewGameOptionsDataFromPacket(packet) + d.QuickChatMode = packet.Read() +} diff --git a/src/packets/16_GetGameListV2S2C.go b/src/packets/16_GetGameListV2S2C.go new file mode 100644 index 0000000..58261cf --- /dev/null +++ b/src/packets/16_GetGameListV2S2C.go @@ -0,0 +1,30 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type GetGameListV2S2C struct { + GameCounts *protocol.Hazel // tag = 0x01 + GameList *protocol.Hazel // tag = 0x00 +} + +func (d *GetGameListV2S2C) TypeCode() byte { + return 0x10 +} + +func (d *GetGameListV2S2C) Write(packet *protocol.Packet) { + d.GameCounts.WriteToPacket(packet) + d.GameList.WriteToPacket(packet) +} + +func (d *GetGameListV2S2C) Read(packet *protocol.Packet) { + for packet.Remaining() > 0 { + h := packet.ReadHazel() + if h.GetTag() == 0x01 { + d.GameCounts = h + } else if h.GetTag() == 0x00 { + d.GameList = h + } + } +} diff --git a/src/packets/17_reportplayerC2S.go b/src/packets/17_reportplayerC2S.go new file mode 100644 index 0000000..455a845 --- /dev/null +++ b/src/packets/17_reportplayerC2S.go @@ -0,0 +1,27 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type ReportPlayerC2S struct { + GameID int32 + ReportedClientID uint32 + ReportReason byte +} + +func (d *ReportPlayerC2S) TypeCode() byte { + return 0x11 +} + +func (d *ReportPlayerC2S) Write(packet *protocol.Packet) { + packet.WriteInt32(d.GameID) + packet.WritePackedUInt32(d.ReportedClientID) + packet.Write(d.ReportReason) +} + +func (d *ReportPlayerC2S) Read(packet *protocol.Packet) { + d.GameID = packet.ReadInt32() + d.ReportedClientID = packet.ReadPackedUInt32() + d.ReportReason = packet.Read() +} diff --git a/src/packets/17_reportplayerS2C.go b/src/packets/17_reportplayerS2C.go new file mode 100644 index 0000000..cbdc804 --- /dev/null +++ b/src/packets/17_reportplayerS2C.go @@ -0,0 +1,30 @@ +package packets + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type ReportPlayerS2C struct { + ReportedClientID uint32 + ReportReason uint32 + ReportOutcome byte + ReportedPlayerName string +} + +func (d *ReportPlayerS2C) TypeCode() byte { + return 0x11 +} + +func (d *ReportPlayerS2C) Write(packet *protocol.Packet) { + packet.WritePackedUInt32(d.ReportedClientID) + packet.WriteUInt32(d.ReportReason) + packet.Write(d.ReportOutcome) + packet.WriteString(d.ReportedPlayerName) +} + +func (d *ReportPlayerS2C) Read(packet *protocol.Packet) { + d.ReportedClientID = packet.ReadPackedUInt32() + d.ReportReason = packet.ReadUInt32() + d.ReportOutcome = packet.Read() + d.ReportedPlayerName = packet.ReadString() +} diff --git a/src/packets/gamepacket.go b/src/packets/gamepacket.go new file mode 100644 index 0000000..edd7f9b --- /dev/null +++ b/src/packets/gamepacket.go @@ -0,0 +1,16 @@ +package packets + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +// PacketBase PacketType + +// C2S = client to server +// S2C = server to client +// S2G = server to game (broadcast to all clients from the server) +// H2G = host to game (broadcast to all clients from the host) + +type GamePacket interface { + Write(packet *protocol.Packet) + Read(packet *protocol.Packet) + TypeCode() byte +} diff --git a/src/packets/go.mod b/src/packets/go.mod new file mode 100644 index 0000000..15600b2 --- /dev/null +++ b/src/packets/go.mod @@ -0,0 +1,15 @@ +module codehub.onpointcoding.net/sean/go-susapp/packets + +go 1.17 + +replace ( + codehub.onpointcoding.net/sean/go-susapp/enum => ../enum + codehub.onpointcoding.net/sean/go-susapp/protocol => ../protocol + codehub.onpointcoding.net/sean/go-susapp/util => ../util +) + +require ( + codehub.onpointcoding.net/sean/go-susapp/enum v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/protocol v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/util v1.0.0 +) diff --git a/src/protocol/gamelist.go b/src/protocol/gamelist.go new file mode 100644 index 0000000..864f453 --- /dev/null +++ b/src/protocol/gamelist.go @@ -0,0 +1,60 @@ +package protocol + +import ( + "net" +) + +type GameList struct { + Games []GameListItem +} + +func NewGameList(packet *Packet) *GameList { + games := make([]GameListItem, 0) + for packet.Remaining() > 0 { + games = append(games, *NewGameListItem(packet)) + } + return &GameList{games} +} + +func (list *GameList) WriteToPacket(packet *Packet) { + for i := 0; i < len(list.Games); i++ { + list.Games[i].WriteToPacket(packet) + } +} + +type GameListItem struct { + IPAddress net.IP + Port uint16 + GameID int32 + HostPlayer string + NumberOfPlayers byte + Age uint32 + MapID byte + NumberOfImpostors byte + MaxNumberOfPlayers byte +} + +func NewGameListItem(packet *Packet) *GameListItem { + ip := packet.ReadIP() + port := packet.ReadUInt16() + gameId := packet.ReadInt32() + hostPlayer := packet.ReadString() + numberOfPlayers := packet.Read() + age := packet.ReadPackedUInt32() + mapId := packet.Read() + numberOfImpostors := packet.Read() + maxNumberOfPlayers := packet.Read() + return &GameListItem{ip, port, gameId, hostPlayer, numberOfPlayers, age, mapId, numberOfImpostors, maxNumberOfPlayers} +} + +func (item *GameListItem) WriteToPacket(packet *Packet) { + packet.WriteIP(item.IPAddress) + packet.WriteUInt16(item.Port) + packet.WriteInt32(item.GameID) + packet.WriteString(item.HostPlayer) + packet.Write(item.NumberOfPlayers) + packet.WritePackedUInt32(item.Age) + packet.Write(item.MapID) + packet.Write(item.NumberOfImpostors) + packet.Write(item.MaxNumberOfPlayers) +} diff --git a/src/protocol/gameoptionsdata.go b/src/protocol/gameoptionsdata.go new file mode 100644 index 0000000..7e4675f --- /dev/null +++ b/src/protocol/gameoptionsdata.go @@ -0,0 +1,96 @@ +package protocol + +type GameOptionsData struct { + Version byte + MaxNumberOfPlayers byte + Keywords uint32 + Maps byte + PlayerSpeedModifier float32 + CrewmateLightModifier float32 + ImpostorLightModifier float32 + KillCooldown float32 + NumberOfCommonTasks byte + NumberOfLongTasks byte + NumberOfShortTasks byte + NumberOfEmergencyMeetings uint32 + NumberOfImpostors byte + KillDistance byte + DiscussionTime uint32 + VotingTime uint32 + IsDefaults bool + EmergencyCooldown byte + ConfirmEjects bool + VisualTasks bool + AnonymousVotes bool + TaskBarUpdates byte +} + +var currentGameOptionsVersion byte = 4 + +// Create new default options +func NewDefaultGameOptionsData() *GameOptionsData { + return &GameOptionsData{currentGameOptionsVersion, 10, 1, 1, 1, 1, 1.5, 45, 1, 1, 2, 1, 1, 1, 15, 120, true, 15, true, true, false, 0} +} + +// Create new options from packet data +func NewGameOptionsDataFromPacket(packet *Packet) *GameOptionsData { + a := NewDefaultGameOptionsData() + a.Read(packet) + return a +} + +func (data *GameOptionsData) Write(packet *Packet) { + p := NewEmptyPacket() + p.Write(data.Version) + p.Write(data.MaxNumberOfPlayers) + p.WriteUInt32(data.Keywords) + p.Write(data.Maps) + p.WriteFloat(data.PlayerSpeedModifier) + p.WriteFloat(data.CrewmateLightModifier) + p.WriteFloat(data.ImpostorLightModifier) + p.WriteFloat(data.KillCooldown) + p.Write(data.NumberOfCommonTasks) + p.Write(data.NumberOfLongTasks) + p.Write(data.NumberOfShortTasks) + p.WritePackedUInt32(data.NumberOfEmergencyMeetings) + p.Write(data.NumberOfImpostors) + p.Write(data.KillDistance) + p.WriteUInt32(data.DiscussionTime) + p.WriteUInt32(data.VotingTime) + p.WriteBool(data.IsDefaults) + p.Write(data.EmergencyCooldown) + p.WriteBool(data.ConfirmEjects) + p.WriteBool(data.VisualTasks) + p.WriteBool(data.AnonymousVotes) + p.Write(data.TaskBarUpdates) + packet.WritePackedUInt32(uint32(p.Size())) + p.Copy(packet) +} + +func (data *GameOptionsData) Read(packet *Packet) { + l := packet.ReadPackedUInt32() + p := NewPacket(packet.ReadBytes(int(l))) + + data.Version = p.Read() + data.MaxNumberOfPlayers = p.Read() + data.Keywords = p.ReadUInt32() + data.Maps = p.Read() + data.PlayerSpeedModifier = p.ReadFloat() + data.CrewmateLightModifier = p.ReadFloat() + data.ImpostorLightModifier = p.ReadFloat() + data.KillCooldown = p.ReadFloat() + data.NumberOfCommonTasks = p.Read() + data.NumberOfLongTasks = p.Read() + data.NumberOfShortTasks = p.Read() + data.NumberOfEmergencyMeetings = p.ReadPackedUInt32() + data.NumberOfImpostors = p.Read() + data.KillDistance = p.Read() + data.DiscussionTime = p.ReadUInt32() + data.VotingTime = p.ReadUInt32() + data.IsDefaults = p.ReadBool() + data.EmergencyCooldown = p.ReadDefault(15) + data.ConfirmEjects = p.ReadBoolDefault(true) + data.VisualTasks = p.ReadBoolDefault(true) + data.AnonymousVotes = p.ReadBoolDefault(false) + data.TaskBarUpdates = p.ReadDefault(0) +} diff --git a/src/protocol/go.mod b/src/protocol/go.mod new file mode 100644 index 0000000..00c75a3 --- /dev/null +++ b/src/protocol/go.mod @@ -0,0 +1,6 @@ +module codehub.onpointcoding.net/sean/go-susapp/protocol + +go 1.17 + +replace codehub.onpointcoding.net/sean/go-susapp/util => ../util +require codehub.onpointcoding.net/sean/go-susapp/util v1.0.0 diff --git a/src/protocol/hazelmessage.go b/src/protocol/hazelmessage.go new file mode 100644 index 0000000..7fd191a --- /dev/null +++ b/src/protocol/hazelmessage.go @@ -0,0 +1,59 @@ +package protocol + +type Hazel struct { + p *Packet + l uint16 + t byte + payload *HazelPayload + readPacket bool +} + +func NewHazel(packet *Packet) *Hazel { + l := packet.ReadUInt16() + t := packet.Read() + p := NewPacket(packet.ReadBytes(int(l))) + return &Hazel{p, l, t, nil, true} +} + +func NewHazelFromPayload(payload *HazelPayload) *Hazel { + p := NewEmptyPacket() + return &Hazel{p, 0, (*payload).TypeCode(), payload, false} +} + +func NewHazelFromRawData(tag byte, b []byte) *Hazel { + p := NewEmptyPacket() + p.WriteBytes(b) + return &Hazel{p, 0, tag, nil, true} +} + +func (hazel *Hazel) WriteToPacket(packet *Packet) { + if hazel.readPacket { + packet.WriteUInt16(hazel.l) + packet.Write(hazel.t) + hazel.p.Copy(packet) + } else { + p := NewEmptyPacket() + (*hazel.payload).Write(p) + packet.WriteUInt16(uint16(p.Size())) + packet.Write((*hazel.payload).TypeCode()) + p.Copy(packet) + } +} + +func (hazel *Hazel) GetUnderlyingPacket() *Packet { + return hazel.p +} + +func (hazel *Hazel) Length() uint16 { + return hazel.l +} + +func (hazel *Hazel) GetTag() byte { + return hazel.t +} + +type HazelPayload interface { + Write(packet *Packet) + Read(packet *Packet) + TypeCode() byte +} diff --git a/src/protocol/netcomponent.go b/src/protocol/netcomponent.go new file mode 100644 index 0000000..9de5ef4 --- /dev/null +++ b/src/protocol/netcomponent.go @@ -0,0 +1,29 @@ +package protocol + +type SpawnComponent struct { + NetID uint32 + ComponentData *Hazel +} + +func (d *SpawnComponent) Write(packet *Packet) { + packet.WritePackedUInt32(d.NetID) + d.ComponentData.WriteToPacket(packet) +} +func (d *SpawnComponent) Read(packet *Packet) { + d.NetID = packet.ReadPackedUInt32() + d.ComponentData = NewHazel(packet) +} + +type UpdateComponent struct { + NetID uint32 + RawData *Packet +} + +func (d *UpdateComponent) Write(packet *Packet) { + packet.WritePackedUInt32(d.NetID) + d.RawData.Copy(packet) +} +func (d *UpdateComponent) Read(packet *Packet) { + d.NetID = packet.ReadPackedUInt32() + d.RawData = packet +} diff --git a/src/protocol/packet.go b/src/protocol/packet.go new file mode 100644 index 0000000..b43ecd9 --- /dev/null +++ b/src/protocol/packet.go @@ -0,0 +1,241 @@ +package protocol + +import ( + "encoding/binary" + "math" + "net" + + "codehub.onpointcoding.net/sean/go-susapp/util" +) + +type Packet struct { + data []byte + idx int + l int +} + +func NewPacket(d []byte) *Packet { + return &Packet{ + data: d, + l: len(d), + } +} + +func NewEmptyPacket() *Packet { + return &Packet{ + data: []byte{}, + l: 0, + } +} + +func (packet *Packet) Seek(v int) { + packet.idx = v +} + +func (packet *Packet) Size() int { + return len(packet.data) +} + +func (packet *Packet) Dump() []byte { + return packet.data +} + +func (packet *Packet) Remaining() int { + return packet.Size() - packet.idx +} + +func (packet *Packet) Copy(p *Packet) { + p.WriteBytes(packet.data) +} + +func (packet *Packet) Read() byte { + return packet.ReadDefault(0) +} + +func (packet *Packet) ReadDefault(d byte) byte { + if packet.idx >= len(packet.data) { + return d + } + v := packet.data[packet.idx] + packet.idx++ + return v +} + +func (packet *Packet) Write(v byte) { + packet.data = append(packet.data, v) +} + +func (packet *Packet) ReadBytes(n int) []byte { + b := make([]byte, n) + for i := 0; i < n; i++ { + b[i] = packet.Read() + } + return b +} + +func (packet *Packet) WriteBytes(v []byte) { + packet.data = append(packet.data, v...) +} + +func (packet *Packet) ReadInt16() int16 { + return int16(packet.ReadUInt16()) +} + +func (packet *Packet) WriteInt16(v int16) { + packet.WriteUInt16(uint16(v)) +} + +func (packet *Packet) ReadUInt16() uint16 { + return binary.LittleEndian.Uint16(packet.ReadBytes(2)) +} + +func (packet *Packet) WriteUInt16(v uint16) { + var b [2]byte + binary.LittleEndian.PutUint16(b[:], v) + packet.WriteBytes(b[:]) +} + +func (packet *Packet) ReadUInt16BigEndian() uint16 { + return binary.BigEndian.Uint16(packet.ReadBytes(2)) +} + +func (packet *Packet) WriteUInt16BigEndian(v uint16) { + var b [2]byte + binary.BigEndian.PutUint16(b[:], v) + packet.WriteBytes(b[:]) +} + +func (packet *Packet) ReadInt32() int32 { + return int32(packet.ReadUInt32()) +} + +func (packet *Packet) WriteInt32(v int32) { + packet.WriteUInt32(uint32(v)) +} + +func (packet *Packet) ReadUInt32() uint32 { + return binary.LittleEndian.Uint32(packet.ReadBytes(4)) +} + +func (packet *Packet) WriteUInt32(v uint32) { + var b [4]byte + binary.LittleEndian.PutUint32(b[:], v) + packet.WriteBytes(b[:]) +} + +func (packet *Packet) ReadFloat() float32 { + return math.Float32frombits(packet.ReadUInt32()) +} + +func (packet *Packet) WriteFloat(v float32) { + var b [4]byte + binary.LittleEndian.PutUint32(b[:], math.Float32bits(v)) + packet.WriteBytes(b[:]) +} + +func (packet *Packet) ReadBool() bool { + return packet.Read() == 1 +} + +func (packet *Packet) ReadBoolDefault(v bool) bool { + if v { + return packet.ReadDefault(1) == 1 + } else { + return packet.ReadDefault(0) == 1 + } +} + +func (packet *Packet) WriteBool(v bool) { + if v { + packet.Write(1) + } else { + packet.Write(0) + } +} + +func (packet *Packet) ParsePackedInt32() int32 { + return int32(packet.ReadPackedUInt32()) +} + +func (packet *Packet) WritePackedInt32(v int32) { + packet.WritePackedUInt32(uint32(v)) +} + +func (packet *Packet) ReadPackedUInt32() uint32 { + readMore := true + shift := 0 + var output uint32 = 0 + + for readMore { + b := packet.Read() + if b > 0x80 { + readMore = true + b ^= 0x80 + } else { + readMore = false + } + + output |= uint32(b) << shift + shift += 7 + } + + return output +} + +func (packet *Packet) WritePackedUInt32(v uint32) { + writeMore := true + for writeMore { + b := byte(v & 0xff) + if v >= 0x80 { + b |= 0x80 + } + packet.Write(b) + v >>= 7 + writeMore = v > 0 + } +} + +func (packet *Packet) ReadString() string { + l := int(packet.ReadPackedUInt32()) + return string(packet.ReadBytes(l)) +} + +func (packet *Packet) WriteString(v string) { + l := len(v) + packet.WritePackedUInt32(uint32(l)) + packet.WriteBytes([]byte(v)) +} + +func (packet *Packet) ReadHazel() *Hazel { + return NewHazel(packet) +} + +func (packet *Packet) WriteHazel(hazel *Hazel) { + hazel.WriteToPacket(packet) +} + +func (packet *Packet) ReadIP() net.IP { + b := packet.ReadBytes(4) + return net.IPv4(b[0], b[1], b[2], b[3]) +} + +func (packet *Packet) WriteIP(v net.IP) { + packet.Write(v[0]) + packet.Write(v[1]) + packet.Write(v[2]) + packet.Write(v[3]) +} + +func (packet *Packet) ReadVector2() util.Vec2 { + x := float32(packet.ReadUInt16()) / 65535 + y := float32(packet.ReadUInt16()) / 65535 + + return util.NewVec2(util.Lerp(-50, 50, x), util.Lerp(-50, 50, y)) +} + +func (packet *Packet) WriteVector2(v util.Vec2) { + x := uint16(util.LerpReverse(-50, 50, v.X) * 65535) + y := uint16(util.LerpReverse(-50, 50, v.Y) * 65535) + packet.WriteUInt16(x) + packet.WriteUInt16(y) +} diff --git a/src/protocol/packethandler.go b/src/protocol/packethandler.go new file mode 100644 index 0000000..756f8a1 --- /dev/null +++ b/src/protocol/packethandler.go @@ -0,0 +1,161 @@ +package protocol + +import ( + "fmt" + "net" + "sync" +) + +const ( + PacketNormal = 0x0 + PacketReliable = 0x1 + PacketHello = 0x8 + PacketDisconnect = 0x9 + PacketAcknowledgement = 0xa + PacketFragment = 0xb + PacketPing = 0xc +) + +type PacketHandler struct { + conn *net.UDPConn + addr *net.UDPAddr + HandleNormalPacket func(net *PacketHandler, h []*Hazel) + HandleReliablePacket func(net *PacketHandler, id uint16, h []*Hazel) + HandleForceDisconnectPacket func(net *PacketHandler, h *Hazel) + HandleNormalDisconnectPacket func(net *PacketHandler) + HandleAcknowledgementPacket func(net *PacketHandler, nonce uint16, missing byte) + HandlePingPacket func(net *PacketHandler, id uint16) + currentNonce uint16 + nonceMutex sync.Mutex + currentSequence uint16 + sequenceMutex sync.Mutex +} + +func NewPacketHandler(conn *net.UDPConn, addr *net.UDPAddr) *PacketHandler { + return &PacketHandler{conn: conn, addr: addr, currentNonce: 0} +} + +func (ph *PacketHandler) GetNonce() uint16 { + ph.nonceMutex.Lock() + a := ph.currentNonce + ph.currentNonce++ + ph.nonceMutex.Unlock() + return a +} + +func (ph *PacketHandler) GetSequence() uint16 { + ph.sequenceMutex.Lock() + a := ph.currentSequence + ph.currentSequence++ + ph.sequenceMutex.Unlock() + return a +} + +func (ph *PacketHandler) ReadPacket(data []byte) { + p := NewPacket(data) + b := p.Read() + if b == PacketNormal { + var h []*Hazel + for p.Remaining() > 0 { + h = append(h, p.ReadHazel()) + } + ph.HandleNormalPacket(ph, h) + } else if b == PacketReliable { + id := p.ReadUInt16BigEndian() + var h []*Hazel + for p.Remaining() > 0 { + h = append(h, p.ReadHazel()) + } + if len(h) > 0 { + ph.HandleReliablePacket(ph, id, h) + } + } else if b == PacketHello { + // Client doesn't need to read this + } else if b == PacketDisconnect { + isForced := p.ReadBool() + if isForced { + ph.HandleForceDisconnectPacket(ph, p.ReadHazel()) + } else { + ph.HandleNormalDisconnectPacket(ph) + } + } else if b == PacketAcknowledgement { + nonce := p.ReadUInt16BigEndian() + missing := p.Read() + ph.HandleAcknowledgementPacket(ph, nonce, missing) + } else if b == PacketFragment { + // Currently not used + } else if b == PacketPing { + id := p.ReadUInt16BigEndian() + ph.HandlePingPacket(ph, id) + } +} + +func (ph *PacketHandler) NewPacketFromType(packetType byte) *Packet { + return NewPacket([]byte{packetType}) +} + +func (ph *PacketHandler) SendPacket(packet *Packet) { + _, err := ph.conn.Write(packet.data) + if err != nil { + fmt.Printf("Failed to send packet to server %v\n", err) + } +} + +func (ph *PacketHandler) SendNormalPacket(h []*Hazel) { + p := ph.NewPacketFromType(0x00) + for i := 0; i < len(h); i++ { + p.WriteHazel(h[i]) + } + ph.SendPacket(p) +} + +func (ph *PacketHandler) SendReliablePacket(nonce uint16, h []*Hazel) { + p := ph.NewPacketFromType(0x01) + p.WriteUInt16BigEndian(nonce) + for i := 0; i < len(h); i++ { + p.WriteHazel(h[i]) + } + ph.SendPacket(p) +} + +func (ph *PacketHandler) SendHelloPacket(nonce uint16, hazelVersion byte, clientVersion int32, username string, authNonce uint32, keyword uint32, quickChatMode byte) { + p := ph.NewPacketFromType(0x08) + p.WriteUInt16BigEndian(nonce) + p.Write(hazelVersion) + p.WriteInt32(clientVersion) + p.WriteString(username) + p.WriteUInt32(authNonce) + p.WriteUInt32(keyword) + p.Write(quickChatMode) + ph.SendPacket(p) +} + +func (ph *PacketHandler) SendForceDisconnectPacket(h *Hazel) { + p := ph.NewPacketFromType(0x09) + p.Write(0x01) + p.WriteHazel(h) + ph.SendPacket(p) +} + +func (ph *PacketHandler) SendNormalDisconnectPacket() { + p := ph.NewPacketFromType(0x09) + p.Write(0x00) + ph.SendPacket(p) +} + +func (ph *PacketHandler) SendAcknowledgementPacket(nonce uint16, missing byte) { + p := ph.NewPacketFromType(0x0a) + p.WriteUInt16BigEndian(nonce) + p.Write(missing) + ph.SendPacket(p) +} + +func (ph *PacketHandler) SendPingPacket(id uint16) { + p := ph.NewPacketFromType(0x0c) + p.WriteUInt16(id) + ph.conn.WriteToUDP(p.data, ph.addr) +} + +func (ph *PacketHandler) Close() { + ph.conn.Close() +} diff --git a/src/protocol/playerdata.go b/src/protocol/playerdata.go new file mode 100644 index 0000000..d59c28a --- /dev/null +++ b/src/protocol/playerdata.go @@ -0,0 +1,53 @@ +package protocol + +type PlayerData struct { + PlayerID byte + Name string + ColorID uint32 + HatID uint32 + PetID uint32 + SkinID uint32 + Flags byte + Tasks []*TaskInfo +} + +func (player *PlayerData) Write(packet *Packet) { + packet.Write(player.PlayerID) + packet.WriteString(player.Name) + packet.WritePackedUInt32(player.ColorID) + packet.WritePackedUInt32(player.HatID) + packet.WritePackedUInt32(player.PetID) + packet.WritePackedUInt32(player.SkinID) + packet.Write(player.Flags) + packet.Write(byte(len(player.Tasks))) + for i := 0; i < len(player.Tasks); i++ { + player.Tasks[i].Write(packet) + } +} +func (player *PlayerData) Read(packet *Packet) { + player.PlayerID = packet.Read() + player.Name = packet.ReadString() + player.ColorID = packet.ReadPackedUInt32() + player.HatID = packet.ReadPackedUInt32() + player.PetID = packet.ReadPackedUInt32() + player.SkinID = packet.ReadPackedUInt32() + player.Flags = packet.Read() + player.Tasks = make([]*TaskInfo, packet.Read()) + for i := 0; i < len(player.Tasks); i++ { + player.Tasks[i].Read(packet) + } +} + +type TaskInfo struct { + TaskID uint32 + IsCompleted bool +} + +func (task *TaskInfo) Write(packet *Packet) { + packet.WritePackedUInt32(task.TaskID) + packet.WriteBool(task.IsCompleted) +} +func (task *TaskInfo) Read(packet *Packet) { + task.TaskID = packet.ReadPackedUInt32() + task.IsCompleted = packet.ReadBool() +} diff --git a/src/states/go.mod b/src/states/go.mod new file mode 100644 index 0000000..f9f5d58 --- /dev/null +++ b/src/states/go.mod @@ -0,0 +1,3 @@ +module codehub.onpointcoding.net/sean/go-susapp/states + +go 1.17 diff --git a/src/states/skeld.go b/src/states/skeld.go new file mode 100644 index 0000000..78239e4 --- /dev/null +++ b/src/states/skeld.go @@ -0,0 +1,5 @@ +package states + +type SkeldState struct { + Doors [13]bool +} diff --git a/src/systemtypes/autodoors.go b/src/systemtypes/autodoors.go new file mode 100644 index 0000000..96d5bee --- /dev/null +++ b/src/systemtypes/autodoors.go @@ -0,0 +1,42 @@ +package systemtypes + +import ( + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/util" +) + +type AutoDoorsSystem struct { + DoorsMask uint32 + States []bool +} + +func (system *AutoDoorsSystem) Write(packet *protocol.Packet, isSpawn bool) { + if isSpawn { + if len(system.States) == 13 { + for i := 0; i < 13; i++ { + packet.WriteBool(system.States[i]) + } + } else { + return + } + } else { + packet.WritePackedUInt32(uint32(system.DoorsMask)) + for i := 0; i < 13; i++ { + if (system.DoorsMask & (1 << i)) != 0 { + packet.WriteBool(system.States[i]) + } + } + } +} + +func (system *AutoDoorsSystem) Read(packet *protocol.Packet, isSpawn bool) { + if !isSpawn { + system.DoorsMask = packet.ReadPackedUInt32() + } + + l := util.CountSetBits(system.DoorsMask) + system.States = make([]bool, l) + for i := 0; i < l; i++ { + system.States[i] = packet.ReadBool() + } +} diff --git a/src/systemtypes/decon.go b/src/systemtypes/decon.go new file mode 100644 index 0000000..d923937 --- /dev/null +++ b/src/systemtypes/decon.go @@ -0,0 +1,28 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type DeconSystem struct { + Timer byte + State byte +} + +func (system *DeconSystem) Write(packet *protocol.Packet, isSpawn bool) { + if !isSpawn { + packet.Write(system.Timer) + packet.Write(system.State) + } +} + +func (system *DeconSystem) Read(packet *protocol.Packet, isSpawn bool) { + if !isSpawn { + system.Timer = packet.Read() + system.State = packet.Read() + } +} + +func (system *DeconSystem) IsIdle() bool { return system.State == 0 } +func (system *DeconSystem) IsEnter() bool { return system.State&1 != 0 } +func (system *DeconSystem) IsClosed() bool { return system.State&2 != 0 } +func (system *DeconSystem) IsExit() bool { return system.State&4 != 0 } +func (system *DeconSystem) IsHeadingUp() bool { return system.State&8 != 0 } diff --git a/src/systemtypes/doors.go b/src/systemtypes/doors.go new file mode 100644 index 0000000..310c635 --- /dev/null +++ b/src/systemtypes/doors.go @@ -0,0 +1,44 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type DoorsSystem struct { + DoorCooldowns []*DoorCooldown + Doors [12]bool +} + +func (system *DoorsSystem) Write(packet *protocol.Packet, isSpawn bool) { + packet.WritePackedUInt32(uint32(len(system.DoorCooldowns))) + for i := 0; i < len(system.DoorCooldowns); i++ { + system.DoorCooldowns[i].Write(packet) + } + for i := 0; i < 12; i++ { + packet.WriteBool(system.Doors[i]) + } +} + +func (system *DoorsSystem) Read(packet *protocol.Packet, isSpawn bool) { + system.DoorCooldowns = make([]*DoorCooldown, packet.ReadPackedUInt32()) + for i := 0; i < len(system.DoorCooldowns); i++ { + system.DoorCooldowns[i] = &DoorCooldown{} + system.DoorCooldowns[i].Read(packet) + } + for i := 0; i < 12; i++ { + system.Doors[i] = packet.ReadBool() + } +} + +type DoorCooldown struct { + DoorID byte + Cooldown float32 +} + +func (cooldown *DoorCooldown) Write(packet *protocol.Packet) { + packet.Write(cooldown.DoorID) + packet.WriteFloat(cooldown.Cooldown) +} + +func (cooldown *DoorCooldown) Read(packet *protocol.Packet) { + cooldown.DoorID = packet.Read() + cooldown.Cooldown = packet.ReadFloat() +} diff --git a/src/systemtypes/go.mod b/src/systemtypes/go.mod new file mode 100644 index 0000000..4289442 --- /dev/null +++ b/src/systemtypes/go.mod @@ -0,0 +1,13 @@ +module codehub.onpointcoding.net/sean/go-susapp/systemtypes + +go 1.17 + +replace ( + codehub.onpointcoding.net/sean/go-susapp/protocol => ../protocol + codehub.onpointcoding.net/sean/go-susapp/util => ../util +) + +require ( + codehub.onpointcoding.net/sean/go-susapp/protocol v1.0.0 + codehub.onpointcoding.net/sean/go-susapp/util v1.0.0 // indirect +) diff --git a/src/systemtypes/hqhud.go b/src/systemtypes/hqhud.go new file mode 100644 index 0000000..be228d2 --- /dev/null +++ b/src/systemtypes/hqhud.go @@ -0,0 +1,31 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type HqHudSystem struct { + ActiveConsoles []*UserConsolePair + CompletedConsoles []byte +} + +func (system *HqHudSystem) Write(packet *protocol.Packet, isSpawn bool) { + packet.WritePackedUInt32(uint32(len(system.ActiveConsoles))) + for i := 0; i < len(system.ActiveConsoles); i++ { + system.ActiveConsoles[i].Write(packet) + } + packet.WritePackedUInt32(uint32(len(system.CompletedConsoles))) + for i := 0; i < len(system.CompletedConsoles); i++ { + packet.Write(system.CompletedConsoles[i]) + } +} + +func (system *HqHudSystem) Read(packet *protocol.Packet, isSpawn bool) { + system.ActiveConsoles = make([]*UserConsolePair, packet.ReadPackedUInt32()) + for i := 0; i < len(system.ActiveConsoles); i++ { + system.ActiveConsoles[i] = &UserConsolePair{} + system.ActiveConsoles[i].Read(packet) + } + system.CompletedConsoles = make([]byte, packet.ReadPackedUInt32()) + for i := 0; i < len(system.CompletedConsoles); i++ { + system.CompletedConsoles[i] = packet.Read() + } +} diff --git a/src/systemtypes/hudoverride.go b/src/systemtypes/hudoverride.go new file mode 100644 index 0000000..f284eaa --- /dev/null +++ b/src/systemtypes/hudoverride.go @@ -0,0 +1,15 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type HudOverrideSystem struct { + IsSabotaged bool +} + +func (system *HudOverrideSystem) Write(packet *protocol.Packet, isSpawn bool) { + packet.WriteBool(system.IsSabotaged) +} + +func (system *HudOverrideSystem) Read(packet *protocol.Packet, isSpawn bool) { + system.IsSabotaged = packet.ReadBool() +} diff --git a/src/systemtypes/lifesupp.go b/src/systemtypes/lifesupp.go new file mode 100644 index 0000000..244e61f --- /dev/null +++ b/src/systemtypes/lifesupp.go @@ -0,0 +1,24 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type LifeSuppSystem struct { + Timer float32 + Consoles []uint32 +} + +func (system *LifeSuppSystem) Write(packet *protocol.Packet, isSpawn bool) { + packet.WriteFloat(system.Timer) + packet.WritePackedUInt32(uint32(len(system.Consoles))) + for i := 0; i < len(system.Consoles); i++ { + packet.WritePackedUInt32(system.Consoles[i]) + } +} + +func (system *LifeSuppSystem) Read(packet *protocol.Packet, isSpawn bool) { + system.Timer = packet.ReadFloat() + system.Consoles = make([]uint32, packet.ReadPackedUInt32()) + for i := 0; i < len(system.Consoles); i++ { + system.Consoles[i] = packet.ReadPackedUInt32() + } +} diff --git a/src/systemtypes/medscan.go b/src/systemtypes/medscan.go new file mode 100644 index 0000000..8b44834 --- /dev/null +++ b/src/systemtypes/medscan.go @@ -0,0 +1,21 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type MedScanSystem struct { + Players []byte +} + +func (system *MedScanSystem) Write(packet *protocol.Packet, isSpawn bool) { + packet.WritePackedUInt32(uint32(len(system.Players))) + for i := 0; i < len(system.Players); i++ { + packet.Write(system.Players[i]) + } +} + +func (system *MedScanSystem) Read(packet *protocol.Packet, isSpawn bool) { + system.Players = make([]byte, packet.ReadPackedUInt32()) + for i := 0; i < len(system.Players); i++ { + system.Players[i] = packet.Read() + } +} diff --git a/src/systemtypes/reactor.go b/src/systemtypes/reactor.go new file mode 100644 index 0000000..453fe32 --- /dev/null +++ b/src/systemtypes/reactor.go @@ -0,0 +1,25 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type ReactorSystem struct { + Timer float32 + Players []*UserConsolePair +} + +func (system *ReactorSystem) Write(packet *protocol.Packet, isSpawn bool) { + packet.WriteFloat(system.Timer) + packet.WritePackedUInt32(uint32(len(system.Players))) + for i := 0; i < len(system.Players); i++ { + system.Players[i].Write(packet) + } +} + +func (system *ReactorSystem) Read(packet *protocol.Packet, isSpawn bool) { + system.Timer = packet.ReadFloat() + system.Players = make([]*UserConsolePair, packet.ReadPackedUInt32()) + for i := 0; i < len(system.Players); i++ { + system.Players[i] = &UserConsolePair{} + system.Players[i].Read(packet) + } +} diff --git a/src/systemtypes/sabotage.go b/src/systemtypes/sabotage.go new file mode 100644 index 0000000..52ba3a1 --- /dev/null +++ b/src/systemtypes/sabotage.go @@ -0,0 +1,15 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type SabotageSystem struct { + Cooldown float32 +} + +func (system *SabotageSystem) Write(packet *protocol.Packet, isSpawn bool) { + packet.WriteFloat(system.Cooldown) +} + +func (system *SabotageSystem) Read(packet *protocol.Packet, isSpawn bool) { + system.Cooldown = packet.ReadFloat() +} diff --git a/src/systemtypes/securitycamera.go b/src/systemtypes/securitycamera.go new file mode 100644 index 0000000..c607415 --- /dev/null +++ b/src/systemtypes/securitycamera.go @@ -0,0 +1,21 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type SecurityCameraSystem struct { + Players []byte +} + +func (system *SecurityCameraSystem) Write(packet *protocol.Packet, isSpawn bool) { + packet.WritePackedUInt32(uint32(len(system.Players))) + for i := 0; i < len(system.Players); i++ { + packet.Write(system.Players[i]) + } +} + +func (system *SecurityCameraSystem) Read(packet *protocol.Packet, isSpawn bool) { + system.Players = make([]byte, packet.ReadPackedUInt32()) + for i := 0; i < len(system.Players); i++ { + system.Players[i] = packet.Read() + } +} diff --git a/src/systemtypes/switch.go b/src/systemtypes/switch.go new file mode 100644 index 0000000..d39b585 --- /dev/null +++ b/src/systemtypes/switch.go @@ -0,0 +1,21 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type SwitchSystem struct { + ExpectedSwitches byte + ActualSwitches byte + Value byte +} + +func (system *SwitchSystem) Write(packet *protocol.Packet, isSpawn bool) { + packet.Write(system.ExpectedSwitches) + packet.Write(system.ActualSwitches) + packet.Write(system.Value) +} + +func (system *SwitchSystem) Read(packet *protocol.Packet, isSpawn bool) { + system.ExpectedSwitches = packet.Read() + system.ActualSwitches = packet.Read() + system.Value = packet.Read() +} diff --git a/src/systemtypes/systemtype.go b/src/systemtypes/systemtype.go new file mode 100644 index 0000000..276be4a --- /dev/null +++ b/src/systemtypes/systemtype.go @@ -0,0 +1,16 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type SystemType interface { + Write(packet *protocol.Packet, isSpawn bool) + Read(packet *protocol.Packet, isSpawn bool) +} + +func SystemTypeCast(baz interface{}) *SystemType { + f, ok := baz.(*SystemType) + if !ok { + return nil + } + return f +} diff --git a/src/systemtypes/userconsolepair.go b/src/systemtypes/userconsolepair.go new file mode 100644 index 0000000..edda55e --- /dev/null +++ b/src/systemtypes/userconsolepair.go @@ -0,0 +1,18 @@ +package systemtypes + +import "codehub.onpointcoding.net/sean/go-susapp/protocol" + +type UserConsolePair struct { + PlayerID byte + ConsoleID byte +} + +func (pair *UserConsolePair) Write(packet *protocol.Packet) { + packet.Write(pair.PlayerID) + packet.Write(pair.ConsoleID) +} + +func (pair *UserConsolePair) Read(packet *protocol.Packet) { + pair.PlayerID = packet.Read() + pair.ConsoleID = packet.Read() +} diff --git a/src/util/collision.go b/src/util/collision.go new file mode 100644 index 0000000..dc27d59 --- /dev/null +++ b/src/util/collision.go @@ -0,0 +1,11 @@ +package util + +func CollisionIntersect(x float64, y float64, ax float64, ay float64, bx float64, by float64) bool { + return x > ax && y > ay && x < bx && y < by +} + +func CollisionIntersectCenter(x float64, y float64, ax float64, ay float64, w float64, h float64) bool { + hw := w / 2 + hh := h / 2 + return CollisionIntersect(x, y, ax-hw, ay-hh, ax+hw, ay+hh) +} diff --git a/src/util/countsetbits.go b/src/util/countsetbits.go new file mode 100644 index 0000000..fd2d874 --- /dev/null +++ b/src/util/countsetbits.go @@ -0,0 +1,13 @@ +package util + +func CountSetBits(n uint32) int { + a := 0 + var b uint32 = 1 + for i := 0; i < 32; i++ { + if (b & n) == b { + a++ + } + b = b << 1 + } + return a +} diff --git a/src/util/gamecode.go b/src/util/gamecode.go new file mode 100644 index 0000000..16b3d3f --- /dev/null +++ b/src/util/gamecode.go @@ -0,0 +1,58 @@ +package util + +import ( + "encoding/binary" + "errors" + "strings" +) + +var codeCharSet = []byte("QWXRTYLPESDFGHUJKZOCVBINMA") +var codeInverseSet = []byte{25, 21, 19, 10, 8, 11, 12, 13, 22, 15, 16, 6, 24, 23, 18, 7, 0, 3, 9, 4, 14, 20, 1, 2, 5, 17} + +var ErrGameCodeLettersOnly = errors.New("invalid code, expected letters only") +var ErrGameCodeLength = errors.New("invalid code length, expected 4 or 6 characters") + +func CodeFromGameID(gameID int32) string { + if gameID < 1 { + firstTwo := gameID & 0x3ff + lastFour := (gameID >> 10) & 0xfffff + + var a [6]byte + a[0] = codeCharSet[firstTwo%26] + a[1] = codeCharSet[firstTwo/26] + a[2] = codeCharSet[lastFour%26] + a[3] = codeCharSet[(lastFour/26)%26] + a[4] = codeCharSet[(lastFour/676)%26] + a[5] = codeCharSet[(lastFour/17576)%26] + + return string(a[:]) + } else { + var b [4]byte + binary.LittleEndian.PutUint32(b[:], uint32(gameID)) + return string(b[:]) + } +} + +func CodeToGameID(gameCode string) (int32, error) { + gameCode = strings.ToUpper(gameCode) + if !OnlyLetters(gameCode) { + return 0, ErrGameCodeLettersOnly + } + if len(gameCode) == 4 { + b := []byte(gameCode) + return int32(binary.LittleEndian.Uint32(b)), nil + } else if len(gameCode) != 6 { + return 0, ErrGameCodeLength + } else { + c1 := uint32(codeInverseSet[gameCode[0]-65]) + c2 := uint32(codeInverseSet[gameCode[1]-65]) + c3 := uint32(codeInverseSet[gameCode[2]-65]) + c4 := uint32(codeInverseSet[gameCode[3]-65]) + c5 := uint32(codeInverseSet[gameCode[4]-65]) + c6 := uint32(codeInverseSet[gameCode[5]-65]) + + firstTwo := (c1 + 26*c2) & 0x3ff + lastFour := (c3 + 26*(c4+26*(c5+26*c6))) + return int32(firstTwo | ((lastFour << 10) & 0x3ffffc00) | 0x80000000), nil + } +} diff --git a/src/util/go.mod b/src/util/go.mod new file mode 100644 index 0000000..8ade5f2 --- /dev/null +++ b/src/util/go.mod @@ -0,0 +1,7 @@ +module codehub.onpointcoding.net/sean/go-susapp/util + +go 1.17 + +replace codehub.onpointcoding.net/sean/go-susapp/protocol => ../protocol + +require codehub.onpointcoding.net/sean/go-susapp/protocol v1.0.0 diff --git a/src/util/lerp.go b/src/util/lerp.go new file mode 100644 index 0000000..c350971 --- /dev/null +++ b/src/util/lerp.go @@ -0,0 +1,21 @@ +package util + +func Lerp(a float32, b float32, t float32) float32 { + if t < 0 { + return 0 + } else if t > 1 { + return 1 + } + return a + ((b - a) * t) +} + +func LerpReverse(a float32, b float32, t float32) float32 { + v := (t - a) / (b - a) + + if v < 0 { + return 0 + } else if v > 1 { + return 1 + } + return v +} diff --git a/src/util/masterserver.go b/src/util/masterserver.go new file mode 100644 index 0000000..c57d3c4 --- /dev/null +++ b/src/util/masterserver.go @@ -0,0 +1,10 @@ +package util + +import "net" + +type MasterServer struct { + Name string + IPAddress net.IP + Port uint16 + NumberOfConnections uint32 +} diff --git a/src/util/onlyletters.go b/src/util/onlyletters.go new file mode 100644 index 0000000..660f128 --- /dev/null +++ b/src/util/onlyletters.go @@ -0,0 +1,10 @@ +package util + +func OnlyLetters(a string) bool { + for _, i := range a { + if i < 'A' || i > 'Z' { + return false + } + } + return true +} diff --git a/src/util/vec2.go b/src/util/vec2.go new file mode 100644 index 0000000..5288bf9 --- /dev/null +++ b/src/util/vec2.go @@ -0,0 +1,10 @@ +package util + +type Vec2 struct { + X float32 + Y float32 +} + +func NewVec2(x float32, y float32) Vec2 { + return Vec2{x, y} +} diff --git a/state.go b/state.go new file mode 100644 index 0000000..64219a4 --- /dev/null +++ b/state.go @@ -0,0 +1,111 @@ +package main + +import ( + "fmt" + "os" + + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/innernetobjects" + "codehub.onpointcoding.net/sean/go-susapp/protocol" +) + +type State struct { + Screen int + Version int32 + PosX float32 + PosY float32 + Settings *SettingsJSON + CurrentGame *CurrentGameData + TypedGameID string + IsTypingReady bool + IsLobbyMenuOpen bool + DisconnectReason byte + DisconnectStringReason string + HoldingUp bool + HoldingLeft bool + HoldingDown bool + HoldingRight bool + MovementX float32 + MovementY float32 +} + +type CurrentGameData struct { + GameID int32 + ClientID uint32 + ClientPlayerNetID uint32 + HostClientID uint32 + OtherClientIDs []uint32 + GamePrivacy byte + GameOptions *protocol.GameOptionsData + NetObjects map[uint32]*innernetobjects.InnerNetObject + PlayerNetObjects *PlayerNetObjectMaps + MapNetObjectNetID uint32 + GameDataNetID uint32 + StartGameTimer byte + CheckNameNonce uint16 + CheckColorNonce uint16 + CheckPetHatAndSkinNonce uint16 + CheckedNameAndColor byte + HasSetStartCounter bool +} + +type PlayerNetObjectMaps struct { + ByPlayerClientID map[uint32]*PlayerObject + ByPlayerNetID map[uint32]*PlayerObject +} + +type PlayerObject struct { + PlayerClientNetID uint32 + PlayerControllerNetID uint32 + PlayerPhysicsNetID uint32 + PlayerNetworkTransform uint32 + PlayerName string + PlayerColor byte + PlayerPet uint32 + PlayerHat uint32 + PlayerSkin uint32 +} + +var settingsFilename string = "SusSettings.json" + +func CreateAppState(version int32) *State { + forceSave := false + settings, err := readJson(settingsFilename) + if err != nil { + if os.IsNotExist(err) { + forceSave = true + } else { + fmt.Printf("Error loading settings: %v\n", err) + return nil + } + } + s := &State{ + Screen: enum.SCREEN_TITLE, + Version: version, + Settings: &settings, + } + if forceSave { + s.SaveSettings() + } + return s +} + +func (state *State) SaveSettings() error { + return writeJson(settingsFilename, *state.Settings) +} + +func readJson(filename string) (SettingsJSON, error) { + b, err := os.ReadFile(filename) + if err != nil { + return NewDefaultSettingsJSON(), err + } + return UnmarshalSettings(b) +} + +func writeJson(filename string, settings SettingsJSON) error { + b, err := settings.Marshal() + if err != nil { + return err + } + return os.WriteFile(filename, b, 0644) +} diff --git a/sus.go b/sus.go new file mode 100644 index 0000000..8fd3aad --- /dev/null +++ b/sus.go @@ -0,0 +1,295 @@ +package main + +import ( + "fmt" + "log" + "net" + "os" + "time" + + "github.com/gotk3/gotk3/cairo" + "github.com/gotk3/gotk3/gdk" + "github.com/gotk3/gotk3/glib" + "github.com/gotk3/gotk3/gtk" + + "codehub.onpointcoding.net/sean/go-susapp/enum" + "codehub.onpointcoding.net/sean/go-susapp/gamedata" + "codehub.onpointcoding.net/sean/go-susapp/packets" + "codehub.onpointcoding.net/sean/go-susapp/protocol" + "codehub.onpointcoding.net/sean/go-susapp/util" +) + +const appID = "net.onpointcoding.susapp" + +func main() { + fmt.Println("Booting Sus App") + sus := &SusApp{} + sus.startApp() + fmt.Println("Goodbye :(") +} + +type SusApp struct { + state *State + application *gtk.Application + appWindow *gtk.ApplicationWindow + renderer *Renderer + keyInput *KeyInputHandler + mouseInput *MouseInputHandler + packetProcessor *PacketProcessor + movementProcessor *MovementProcessor + networkHandler *protocol.PacketHandler +} + +func (sus *SusApp) startApp() { + // Create Gtk Application, change appID to your application domain name reversed. + var err error + sus.application, err = gtk.ApplicationNew(appID, glib.APPLICATION_NON_UNIQUE) + // Check to make sure no errors when creating Gtk Application + if err != nil { + log.Fatal("Could not create application.", err) + } + + renderTicker := time.NewTicker(50 * time.Millisecond) + renderTickerDone := make(chan bool) + defer func() { + fmt.Println("Stopping render ticks") + renderTicker.Stop() + renderTickerDone <- true + }() + + movementTicker := time.NewTicker(50 * time.Millisecond) + movementTickerDone := make(chan bool) + defer func() { + fmt.Println("Stopping movement ticks") + movementTicker.Stop() + movementTickerDone <- true + }() + + sus.state = CreateAppState(GenerateVersionNumber(2021, 6, 30, 0)) + if sus.state == nil { + fmt.Println("Error loading new app state") + return + } + sus.renderer = NewRenderer(sus.state) + sus.keyInput = NewKeyInputHandler(sus.state) + sus.mouseInput = NewMouseInputHandler(sus.state, sus.renderer, sus.playGame, sus.openSettings, sus.disconnectFromGame) + sus.packetProcessor = NewPacketProcessor(sus) + sus.movementProcessor = &MovementProcessor{sus: sus} + + fmt.Printf("Starting Sus App %s\n", GetVersionFormatted(sus.state.Version)) + + sus.application.Connect("activate", func() { + w := 800 + h := 450 + + // Create ApplicationWindow + sus.appWindow, err = gtk.ApplicationWindowNew(sus.application) + if err != nil { + log.Fatal("Could not create application window.", err) + } + // Set ApplicationWindow Properties + sus.appWindow.SetTitle("Sus App") + sus.appWindow.SetDefaultSize(w, h) + + layout, _ := gtk.LayoutNew(nil, nil) + sus.appWindow.Add(layout) + + da, _ := gtk.DrawingAreaNew() + da.SetHAlign(gtk.ALIGN_FILL) + da.SetVAlign(gtk.ALIGN_FILL) + da.SetHExpand(true) + da.SetVExpand(true) + da.SetSizeRequest(w, h) + layout.Add(da) + + // Listen for ticks from renderTicker + go func() { + for { + select { + case <-renderTickerDone: + return + case <-renderTicker.C: + da.QueueDraw() + } + } + }() + + // Listen for ticks from movementTicker + go func() { + z := time.Now().UnixMilli() + for { + select { + case <-movementTickerDone: + return + case a := <-movementTicker.C: + y := a.UnixMilli() - z + z = a.UnixMilli() + sus.movementProcessor.Tick(y) + } + } + }() + + r := sus.renderer + da.Connect("draw", func(da *gtk.DrawingArea, cr *cairo.Context) { + r.Draw(da, cr) + }) + + da.AddEvents(int(gdk.KEY_RELEASE_MASK) | int(gdk.KEY_PRESS_MASK) | int(gdk.EVENT_CONFIGURE) | int(gdk.EVENT_BUTTON_PRESS)) + da.Connect("event", sus.mouseInput.motionEvent) + sus.appWindow.Connect("button-press-event", sus.mouseInput.buttonPressEvent) + sus.appWindow.Connect("key-press-event", sus.keyInput.keyPressEvent) + sus.appWindow.Connect("key-release-event", sus.keyInput.keyReleaseEvent) + sus.appWindow.Connect("configure-event", func(window *gtk.ApplicationWindow, ev *gdk.Event) { + e := gdk.EventConfigureNewFromEvent(ev) + da.SetSizeRequest(e.Width(), e.Height()) + }) + sus.appWindow.Connect("destroy", func(window *gtk.ApplicationWindow) bool { + fmt.Println("Closing Sus App") + sus.disconnectFromGame() + sus.application.Quit() + return true + }) + + sus.appWindow.ShowAll() + }) + sus.application.Run(os.Args) +} + +func (sus *SusApp) setupPacketHandler(ip *net.IP, port int, firstPacket func(ph *protocol.PacketHandler)) { + addr := net.UDPAddr{ + Port: port, + IP: *ip, + } + ser, err := net.DialUDP("udp", nil, &addr) + if err != nil { + fmt.Printf("Some error %v\n", err) + return + } + defer ser.Close() + fmt.Printf("Starting connection: %v => %v\n", ser.LocalAddr().String(), ser.RemoteAddr().String()) + + nethandler := protocol.NewPacketHandler(ser, &addr) + nethandler.HandleNormalPacket = sus.handleNormalPacket + nethandler.HandleReliablePacket = sus.handleReliablePacket + nethandler.HandleForceDisconnectPacket = sus.handleForceDisconnectPacket + nethandler.HandleNormalDisconnectPacket = sus.handleNormalDisconnectPacket + nethandler.HandleAcknowledgementPacket = sus.handleAcknowledgementPacket + nethandler.HandlePingPacket = sus.handlePingPacket + + sus.networkHandler = nethandler + + p := make([]byte, 4096) + + firstPacket(nethandler) + for { + n, _, err := ser.ReadFromUDP(p) + if n != 0 { + if err != nil { + fmt.Printf("Some error %v\n", err) + continue + } + + go nethandler.ReadPacket(p[:n]) + } + } +} + +func (sus *SusApp) playGame() { + sus.state.Screen = enum.SCREEN_WAITING + sus.state.IsTypingReady = false + + addrstr := fmt.Sprintf("%v:%v", sus.state.Settings.ServerAddress, sus.state.Settings.ServerPort) + raddr, err := net.ResolveUDPAddr("udp", addrstr) + if err != nil { + fmt.Printf("Failed to resolve address: %v\n", addrstr) + return + } + go sus.setupPacketHandler(&raddr.IP, raddr.Port, func(ph *protocol.PacketHandler) { + fmt.Println("Sending first packet") + ph.SendHelloPacket(ph.GetNonce(), 0, sus.state.Version, sus.state.Settings.PlayerName, 0, sus.state.Settings.Language, sus.state.Settings.QuickChatMode) + y, _ := util.CodeToGameID(sus.state.TypedGameID) + var z protocol.HazelPayload = &packets.JoinGameC2S{GameID: y} + ph.SendReliablePacket(ph.GetNonce(), []*protocol.Hazel{ + protocol.NewHazelFromPayload(&z), + }) + }) +} + +func (sus *SusApp) openSettings() { + openSettingsWindow(sus.application, nil, sus.state) +} + +func (sus *SusApp) disconnectFromGame() { + sus.state.IsLobbyMenuOpen = false + if sus.networkHandler != nil { + sus.networkHandler.SendNormalDisconnectPacket() + } +} + +// TODO: implement all these functions +func (sus *SusApp) handleNormalPacket(net *protocol.PacketHandler, h []*protocol.Hazel) { + sus.packetProcessor.receiveNormalPacket(net, h) +} + +func (sus *SusApp) handleReliablePacket(net *protocol.PacketHandler, nonce uint16, h []*protocol.Hazel) { + sus.packetProcessor.receiveReliablePacket(net, nonce, h) +} + +func (sus *SusApp) handleForceDisconnectPacket(net *protocol.PacketHandler, h *protocol.Hazel) { + net.SendNormalDisconnectPacket() + p := h.GetUnderlyingPacket() + sus.state.DisconnectReason = p.Read() + if p.Remaining() > 0 { + sus.state.DisconnectStringReason = p.ReadString() + sus.state.DisconnectReason = 0xfe + } + sus.state.Screen = enum.SCREEN_ERROR + sus.state.IsLobbyMenuOpen = false + sus.state.DisconnectReason = 0xfe + sus.state.DisconnectStringReason = "This is a very long piece of text that must be completely read and understood thank you for reading :)" +} + +func (sus *SusApp) handleNormalDisconnectPacket(net *protocol.PacketHandler) { + net.SendNormalDisconnectPacket() + sus.state.DisconnectReason = 0xff + sus.state.Screen = enum.SCREEN_TITLE + sus.state.IsLobbyMenuOpen = false +} + +func (sus *SusApp) handleAcknowledgementPacket(net *protocol.PacketHandler, nonce uint16, missing byte) { + if sus.state.CurrentGame != nil && nonce != 0 { + if nonce == sus.state.CurrentGame.CheckNameNonce && sus.state.CurrentGame.CheckedNameAndColor&0b01 == 0 { + sus.state.CurrentGame.CheckedNameAndColor |= 0b01 + if sus.state.CurrentGame.CheckedNameAndColor == 0b11 && sus.state.CurrentGame.CheckedNameAndColor&0b10 == 0 { + sus.sendPetHatAndSkin(net) + } + } else if nonce == sus.state.CurrentGame.CheckColorNonce { + sus.state.CurrentGame.CheckedNameAndColor |= 0b10 + if sus.state.CurrentGame.CheckedNameAndColor == 0b11 { + sus.sendPetHatAndSkin(net) + } + } + } +} + +func (sus *SusApp) handlePingPacket(net *protocol.PacketHandler, nonce uint16) { + // TODO: setup acknowledgement tracking and remove hard coded 0xff + net.SendAcknowledgementPacket(nonce, 0xff) +} + +func (sus *SusApp) sendPetHatAndSkin(net *protocol.PacketHandler) { + // Send RPC SetPet, SetHat, SetSkin + sus.state.CurrentGame.CheckPetHatAndSkinNonce = net.GetNonce() + var f1a gamedata.RpcSub = &gamedata.RpcSetPet{PetID: 1} + var f2a packets.GameDataSubpacket = gamedata.NewRpcFromRpcSub(sus.state.CurrentGame.ClientPlayerNetID, &f1a) + var f1b gamedata.RpcSub = &gamedata.RpcSetHat{HatID: 1} + var f2b packets.GameDataSubpacket = gamedata.NewRpcFromRpcSub(sus.state.CurrentGame.ClientPlayerNetID, &f1b) + var f1c gamedata.RpcSub = &gamedata.RpcSetSkin{SkinID: 1} + var f2c packets.GameDataSubpacket = gamedata.NewRpcFromRpcSub(sus.state.CurrentGame.ClientPlayerNetID, &f1c) + var f3 protocol.HazelPayload = &packets.GameDataAll{GameID: sus.state.CurrentGame.GameID, Subpackets: []*protocol.Hazel{ + f2a.AsHazel(), + f2b.AsHazel(), + f2c.AsHazel(), + }} + net.SendReliablePacket(sus.state.CurrentGame.CheckPetHatAndSkinNonce, []*protocol.Hazel{protocol.NewHazelFromPayload(&f3)}) +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..c986238 --- /dev/null +++ b/version.go @@ -0,0 +1,27 @@ +package main + +import "fmt" + +func GenerateVersionNumber(year int, month int, day int, revision int) int32 { + return int32(year*25000 + month*1800 + day*50 + revision) +} + +func GetVersionYear(version int32) int { + return int(version / 25000) +} + +func GetVersionMonth(version int32) int { + return int((version % 25000) / 1800) +} + +func GetVersionDay(version int32) int { + return int((version % 25000 % 1800) / 50) +} + +func GetVersionRevision(version int32) int { + return int((version % 50)) +} + +func GetVersionFormatted(v int32) string { + return fmt.Sprintf("v%v.%v.%v", GetVersionYear(v), GetVersionMonth(v), GetVersionDay(v)) +}