From e2cf12c9f73f1e9497245b7ce29ca0f95542321e Mon Sep 17 00:00:00 2001 From: Auri Date: Tue, 5 Jan 2021 21:56:20 -0800 Subject: [PATCH] Fix chunk rendering, improve load times, standardize editor units. --- app/res/tileset/hole.png | Bin 0 -> 5152 bytes app/res/tileset/new_wall_stone.png | Bin 1826 -> 4862 bytes app/res/ui/icon/detail.png | Bin 0 -> 1429 bytes app/res/ui/icon/{ground.png => floor.png} | Bin app/res/ui/icon/virtual_dungeon.png | Bin 767 -> 0 bytes app/src/components/App.tsx | 8 +- app/src/components/route/AssetRoute.tsx | 57 ++++ app/src/components/route/AssetsRoute.tsx | 31 ++- app/src/components/route/CollectionRoute.tsx | 59 +++++ app/src/components/route/Routes.ts | 2 + .../components/view/AssetCollectionList.sass | 87 ++++++ .../components/view/AssetCollectionList.tsx | 40 +++ .../{MyAssetsList.sass => AssetList.sass} | 19 +- app/src/components/view/AssetList.tsx | 43 +++ app/src/components/view/MyAssetsList.tsx | 37 --- app/src/components/view/NewAssetForm.sass | 7 + app/src/components/view/NewAssetForm.tsx | 16 +- app/src/editor/ArchitectMode.ts | 43 ++- app/src/editor/MapChunk.ts | 96 ------- app/src/editor/MapData.ts | 198 -------------- app/src/editor/SmartTiler.ts | 68 ----- app/src/editor/TilesetManager.ts | 39 --- app/src/editor/TilesetPatcher.ts | 210 ++++++++------- app/src/editor/Token.ts | 76 +++--- app/src/editor/TokenMode.ts | 43 ++- app/src/editor/WorldView.ts | 25 +- app/src/editor/history/HistoryElement.ts | 6 +- .../interface/components/UITileSidebar.ts | 45 ++-- .../interface/components/UITokenSidebar.ts | 2 +- app/src/editor/lighting/LightSource.ts | 3 +- app/src/editor/lighting/Lighting.ts | 6 +- app/src/editor/map/Map.ts | 99 +++++++ app/src/editor/map/MapChunk.ts | 114 ++++++++ app/src/editor/map/MapLayer.ts | 250 ++++++++++++++++++ app/src/editor/map/TileStore.ts | 49 ++++ app/src/editor/scene/LoadScene.ts | 29 +- app/src/editor/scene/MapScene.ts | 14 +- app/src/editor/util/Asset.ts | 2 +- app/src/editor/util/Layer.ts | 8 +- ...g => 1212b3f83385f518d41fd5d60924617f.png} | Bin assets/129e90e0cc9499733a12c1604429fa62.png | Bin 1030 -> 0 bytes assets/19f80922c8ec85b838aecbb58e83ebb5.png | Bin 1358 -> 0 bytes assets/1d47e7bf3a7e92475630090704215c3d.png | Bin 0 -> 5152 bytes assets/1d8c662852f88f8b0eab68764b6075a9.png | Bin 0 -> 4862 bytes assets/2506d31b947fda4295475c191f2dd0db.png | Bin 4878 -> 0 bytes assets/4d382cb3f4956f5b3acacb84cc75b242.png | Bin 407 -> 0 bytes assets/51bb14491a3ec884269416faac03cafa.png | Bin 5665 -> 0 bytes assets/595347ff1a6fe6a52fbde2255860fdd7.png | Bin 1569 -> 0 bytes assets/5cafce367161af38becdcfbd4e965baf.png | Bin 0 -> 1045 bytes assets/65172fd93b3361a53f63e6a964f47152.png | Bin 18170 -> 0 bytes assets/6900ec9f541e1d7d010aee1f57c7f1a9.png | Bin 1046 -> 0 bytes assets/8a344775bc45eaae8c7a399985f336e9.png | Bin 1358 -> 0 bytes ...g => 8aad9d92a9a622d664c3362c69154aca.png} | Bin assets/a532af9feb981f9302ecf865b5d9c7d0.png | Bin 5665 -> 0 bytes assets/auri_16x_fantasy_cadin_1.png | Bin 1286 -> 0 bytes assets/auri_16x_fantasy_wall_dungeon.png | Bin 4821 -> 0 bytes assets/c57c07c340da5af1c066def99abf7a25.png | Bin 958 -> 0 bytes assets/c58cb184401388ad65c9e3509e83b706.png | Bin 3090 -> 0 bytes assets/cc88be196a49a85b30d8f075cface18e.png | Bin 3233 -> 0 bytes assets/d365e29f01067ade7b15566e97656999.png | Bin 5665 -> 0 bytes assets/d38c5fc89edfc8feb26af435c84bda95.png | Bin 843 -> 0 bytes ...g => df059aba8e6c8d0afc77acf3a9713c27.png} | Bin assets/e8e61062f0889dffca9a1439d9678201.png | Bin 3233 -> 0 bytes assets/f975160fe390339d57d077a6ff0085aa.png | Bin 1004 -> 0 bytes assets/fcb05842c62423b61ca44e39e68380d9.png | Bin 4878 -> 0 bytes common/AppData.ts | 3 +- common/DBStructs.ts | 2 +- server/src/Database.ts | 61 ++++- server/src/routers/DataRouter.ts | 42 ++- 69 files changed, 1204 insertions(+), 735 deletions(-) create mode 100644 app/res/tileset/hole.png create mode 100644 app/res/ui/icon/detail.png rename app/res/ui/icon/{ground.png => floor.png} (100%) delete mode 100644 app/res/ui/icon/virtual_dungeon.png create mode 100644 app/src/components/route/AssetRoute.tsx create mode 100644 app/src/components/route/CollectionRoute.tsx create mode 100644 app/src/components/view/AssetCollectionList.sass create mode 100644 app/src/components/view/AssetCollectionList.tsx rename app/src/components/view/{MyAssetsList.sass => AssetList.sass} (86%) create mode 100644 app/src/components/view/AssetList.tsx delete mode 100644 app/src/components/view/MyAssetsList.tsx delete mode 100755 app/src/editor/MapChunk.ts delete mode 100755 app/src/editor/MapData.ts delete mode 100755 app/src/editor/SmartTiler.ts delete mode 100755 app/src/editor/TilesetManager.ts create mode 100755 app/src/editor/map/Map.ts create mode 100755 app/src/editor/map/MapChunk.ts create mode 100644 app/src/editor/map/MapLayer.ts create mode 100755 app/src/editor/map/TileStore.ts rename assets/{auri_16x_fantasy_floor_rock.png => 1212b3f83385f518d41fd5d60924617f.png} (100%) delete mode 100644 assets/129e90e0cc9499733a12c1604429fa62.png delete mode 100644 assets/19f80922c8ec85b838aecbb58e83ebb5.png create mode 100644 assets/1d47e7bf3a7e92475630090704215c3d.png create mode 100644 assets/1d8c662852f88f8b0eab68764b6075a9.png delete mode 100644 assets/2506d31b947fda4295475c191f2dd0db.png delete mode 100644 assets/4d382cb3f4956f5b3acacb84cc75b242.png delete mode 100644 assets/51bb14491a3ec884269416faac03cafa.png delete mode 100644 assets/595347ff1a6fe6a52fbde2255860fdd7.png create mode 100644 assets/5cafce367161af38becdcfbd4e965baf.png delete mode 100644 assets/65172fd93b3361a53f63e6a964f47152.png delete mode 100644 assets/6900ec9f541e1d7d010aee1f57c7f1a9.png delete mode 100644 assets/8a344775bc45eaae8c7a399985f336e9.png rename assets/{99f8ad5dbf19c983e8decd61a1ab0efc.png => 8aad9d92a9a622d664c3362c69154aca.png} (100%) delete mode 100644 assets/a532af9feb981f9302ecf865b5d9c7d0.png delete mode 100644 assets/auri_16x_fantasy_cadin_1.png delete mode 100644 assets/auri_16x_fantasy_wall_dungeon.png delete mode 100644 assets/c57c07c340da5af1c066def99abf7a25.png delete mode 100644 assets/c58cb184401388ad65c9e3509e83b706.png delete mode 100644 assets/cc88be196a49a85b30d8f075cface18e.png delete mode 100644 assets/d365e29f01067ade7b15566e97656999.png delete mode 100644 assets/d38c5fc89edfc8feb26af435c84bda95.png rename assets/{7ff3add8ef026bdf6e0dad0d5ba85642.png => df059aba8e6c8d0afc77acf3a9713c27.png} (100%) delete mode 100644 assets/e8e61062f0889dffca9a1439d9678201.png delete mode 100644 assets/f975160fe390339d57d077a6ff0085aa.png delete mode 100644 assets/fcb05842c62423b61ca44e39e68380d9.png diff --git a/app/res/tileset/hole.png b/app/res/tileset/hole.png new file mode 100644 index 0000000000000000000000000000000000000000..80d4bf4d05412eed1a1b0b5b8223c5d654f37712 GIT binary patch literal 5152 zcmV+*6yNKKP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+NGFlaw9nog#Ystdj!4!kjLROVsEg=-w!3(ZFl#~ z_QYCYsZ=TzK_HQtP&ohj_YMEzuT;}e#hP~2$Y1TX=fOdXufOp;r&yoo=g;0B@&4QS zkUu`ioGQGQ*W0G=^M~_Xl-?J%^}f&GynMM)KkpCb_YcPVM!CHgc%T1RsHE2daldag z-Zv`wy>flMpYN5mH~YGU{jr1gKGRdY0@*^>xg1F8#@G43sQ;DN=vu_QcO@e#_0r>tFnZ zKiSqB4&TcCdGv-s`1qjq-@`!9`-<{Y_4M=gIUj!hAj$8`{nYd;Fzfp$zs`T&h?J~v zn{Ge;@7?X*^V;30%9i!LsgEtD;743I9R$zUl!x&z9NY8gJR0q?DD`H!$z!FSG9|vL zrkzH*A)V)7rA3T2pKQG5yr{aLYt*9WRbEBsk#4rq%Px)5FQ8I5o9_+pSkkj@f7V;6 zvGPt_niDrmeC2;W!k=99caM8LwJE(?q(2EGuI#WU46mF&a~Ty$_nlOE68!P_4#;<5 z3ni2%&5aF?IA6Dzl73*TJcTY?C%nFTEZKg2-)~e|TL>lOV71g^6NY-NhPFcF>2GxpjfpE__5l+(2yckL{v`=IP#@#zp2`G z*Fbb=Te#CWC7((-8-cPo&XSpK+h%FVy0P6@ z_JpvaEVfr;p8W)~vMo;2CF2}2edpQB$d>eK2ZdQvE6J8rMF)IHu<6`fW@%GcaR;&$hPpK|Xc|Rypq7;%)27vB9-E3is?GgXdC~WgDX9 zp;lJUM81K?ma~RT>v@w>D@*d|?Yiv2=9xV8b#08IVUL1A%F+`TP8Afg$4Pjamfd|t zmc$K$VDj6WLfTaGj!pEEX1k=I%W8p!gKF$9q?_!IXlxri~Sf+`s zb+IboG*KX%ecWQZ1_;e_Hb*;APy*6(;15Cu-dmxznluPE)sBEp;iYA8DopG>&X(eNxxDi!GY9YjGS!0WEVx%2Gl@p3uyxovgn~AN^ zZVNluRw623z0M>@sI*JlB#gr*y?n!_?0RYg9&zWsI)LxwCek}ILNQ=0O6Mcbsk>G{Tz2c9~|8&NhniXEPJf}n8p)#97<4ODnno~^Ciy~`3c%B2A* zHiT30T`B!P==gZ!3{>99Yxd$zG0}h~$?{sxudvxF?GTZ(T|s7=4c&_`oxE-mC#?u& zv=%PA!cHer!xsv0j0GHRI&5NdQ*P}-DuBM<3_ZJNL?!Stg?<{)H}N4Y;c!nYcsJHB z3SzUfQ=v8s+t_G+@bF}Q&|~z$EMU+$R@ABy-ejc8IdsoxQ*Q5}Tp5i>I5*@L>U)b5 z$H|`0d}+&g)|%Kt@FeqG6WRz#tDzBDU$)sV=jbKUAPH6KdebV14)+-Zjwzp8-$bDWW!EpKa4+KZ-o}o zhk9-#Ni`{75ru?G9})+{3%g69JA2VL9U%VZxS49+Hk(Nn(Yqwih^m~BBC+F^U_;fZ#9Nh1T)N$pO%y|@Z?fD-`rq!Y^l zx-R2$r@6A|5y*|c?zo#4+h*TqO@W|y11^AfAbKE=8gJ7bNbSiRV(XG~otlmhBB*qv zr6D0I1SQUGS;mwh+gp}0(SP)hxEj!fx>iFH9pc!t+g1|MZ-Xs&{~VHV%e!Mdw_JCj5NKHJjvMLJxc5 z*CG`y?x}LbXyH(~6+-B>pSs(FJAr~fyM#QDcYI8uY`vnGi9a`Pu^wD3H5qp#W%c#TmL}W7kNV12r_PzEpQoBKXbtY9`PUsiC-O1~}~`+6U8) zs?n7U8Bq~{4QB&Sx+Yf4XjLNL)WRmdl;Xe=V;N`lRRZkQtD4jzE0eUwfr%$<+1DB~ z0p2Q*2bvk#Vu#ENJ|QJx!UxpD(~TA6gj-pf#M4JOJG%!+z{AJ(vA~N3=M8g5NQ8)P z!aAV1hM!VnHc&i-E7d6*c+wKtg&M2jjqx-`P1RomP8t?LQh_pSLj*KF$sndV3t${% z?eg}LOe_;tlXWpwWH@0GND9Tm)+koex#{ZKK3go(1K5ZXG*EO@5#Ki#>^hBM4C=vJ z&gQa;i-Pu{Fxn|}@_-d$^5`8+x))VX(+LXlmt8Q0YRgctb@Oii;|A^yyU$um3Ge{j zJ!!y!ycH>Nf80@ahfmKoEIt+soYA^OQ1ps1L`NF$895X zpIc`>iPv7OHvsUvzrVi*Ke&zA(=~*?I5c!!e2GC>F%K7O2sRmX`f3i_?4pFGGNVxs z3a{8Nl~9V01(1fz9EOC^Kp3|MbXahLa3C~=fj9uJVnL~k8~Ql?Ju-Q@&zZ%-D)a*r z4t+xxVk`v6W~W<9i@7TR8FGXwAOWUpI~4Dgn}BI*~?MU)D@%;zN;y zFqmUI(!{bS`j4EIH`!4^1&|TUvvQb~kuCt5pF0y|<3%Cvj1jqf7d?f<7tVC!*kLsz zc-Cev2eq}K6g^LFc!B0>UrY;&DI2K;0NjU`WZdT-r=hsR>+A#T z3=A1KlZw#zTt3(a9VQHo#14R*K(%h4XB$Kz4LABCU#Z4i4=hbduab^{aN6l+E^alg!Nm3m`B2WrjVTr`q%3My2&>v8wvGQah(Tc>f3Q10=v)>4Kv?;YSKSn)xpbo7ApTRGC-ZP)-yDHG}yXUI?KE zGy~Ifa8`&2`MvJYu?XvsPQC$E;wtEYih8GvMftr7gkR*~*H?3BhyMbB(H9cP4g_le z00D(*LqkwWLqi~Na&Km7Y-Iodc$|HaJxIe)6opS)OGPRURuB;hSe-10ia2T&iclfc z3avVryz~#6G$bi5j)H5!!JoydgNw7S4z7YA_ygkR=A`H%CEk}5TEuwa@jlMG=kVTr zfWKa5s@X9PsG4P@5^*7uT@^yF@F9pk3}IMeramW%NqE-RJ#|yv#dwx?-=Ed3A_T~&qJ%Om#Aw$@F_EJ4 zxQBnh@r&e=$yEX)#{w!)Avu2VKlnXcGe0@uCWYfb@Wr-2Mu5;R(5Tt=_pxm^PJqBO zaHX~Ul{zr(i|qi@Ory|+Nunmf1V zIZhvdG|eh`0~{OzqXo)d_jq?_``rHRY0mElQb=-|ydk2s00006VoOIv0RI600RN!9 zr;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNlirueSad^g zZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00ZbrL_t(&-sM}dj^Z#7oglsdtVB^L zsF1i54bnEyT(N&}CB=OLpMdxTK4A;W{evyGVVjj`&>a%h7K>1#z%S&A({O{6ICc`- z;cg^^FtKMm&u={TfCB)qn9q{U`xe6Z47)=FJ_9(#ASH|WEY z?0YRY6rj#~somVTA~rp4hyXncv30DPPbA&k%9 zu`Y!1IrTkl=b!Od7hc}>4s{X&Oo0&f$L~e@ajd}ipx+CGtk2S4(7zcW6j$o*5Gf6S zPWyS--?X!i3-KkoGMm0VASu40m&`D|5B9rqO zO&!cTj{+joU!*_2M8G<(aDF2FGX1TF&>!EwC1|`V1M9G%N(fgGpZ#H`$GR|m7+*u7 ztp1$-RzqkDlM4)?wnZ0svSW*$(P>+00BP)}A<2}Wem*4XlK2uq7@uMDzP&jr$VQ*t zA!<}8ZDYD^(mtRu#O8p z6Yqbbqlj?^_IrWwV69th=+D!4%gQJPd=_)9GY}2OLvhyX(=Dd;BLn%XGHgR8(ihs- zB=-H6dmsXlCCk;?$-7Fc`*M6Va8t=qX#g=S=sbfJF~Wp?pZ&e8;2B6k#Oye$y)N)* zIL44(2E=yWL~WjVbn1^U5qPXym^|qUlN7wHMWhO1Yq?s()AK95yzQ?}(+S>a;KJi% zh;>AV?mevIYQH2v9mIJ_#h3B|D)XHZ18uWP#{#k)RjBa$eU%)bW!iXLWgnl%RP7Dx zk*4+AOx~&8VM#JP;(DscAWnz^Qo>2zoF`(#oGIfVh+YF@kRE;=on-jp~1yg#{w?Vcq^6QY6 z`LJ=Dyr67%zl{7E0Bx8)jN$3|)wy>78?y(>)Z^+1;Z3XO-4z4zwxvuxt}gJJR?oXj z2CCHKsna0SwjP%^7C#9u5cr-{8ZV*0Dlo}C1Na-K(?OwgQCT_w O0000aB^>EX>4U6ba`-PAZ2)IW&i+q+NGFlk|Vhd zg#YstJ_27L$m0-vMEC|izCVvgy_EvZYTDiWEd6w+& z3hyhA$*1|LZ+{FxmK};T-rAn{UCm!=^Y!{4e!}0`)=P(9;QoI1(ja_&P{;3S(9U~C z`2%|G`}H{<{`f(X-^2Z2`ZF=>`!0Wue_v>wm&ZTg{rAppkr@&v2UkUjFTYylWGIusOqkS%6l74WjJU|!D z6J9?(mVb1AzV8>R+*?>oLg`X$sYq%mYR;Mxh3o({Q|!6aY#@+C@@A2_meRn6Cg9eA z?`V3?&5}RIQAv=H(u#u3C=;-175JmI!=Wifs)#gE=`v)t(rTc?TGQroJGE$4(Wa_h zht6Jl?b2J<-uvitg@4KUlOvX`ST(U`YTbs-1$|asvf9$s z*I0AsT{eBZ+t%Ip*z=?Vl}9d~}C_M-Zy$Nzv@dQr;{ zQhuI(qsCFy&ue)2!bKc25X&`zxH z&IzsGhTmHo^+Z;UM9*9Jt$mA085!_TwYDZWN!r|K4Y$JEw+`EFNL+J-wotFhzqNL? zJylLEZJc(;>Svp!zU}OBb?zoxtzOuXdVgc9>GX0}^BUwZsT^&#gbo~6#7b*Yj#OLe zqKBtQJ7wzudGs@li)BsJInC23Z5f+tqDOX1$ZpwPsz}ecNT;wFeM~7B`$LceGy1z#rpDFdzxCuY2_YmXG4!EqS$Mo%%u*D1UO( zYRC*Fh}uJO*dvHFNj4yA-N0N{d{))rUA@KfW%auh$dp%|4L+~C6nwnqLJ9~bkV@g! zlr#EUy+rY4omKqnY3V$>dz5RGsJxYfHd`Fab#fj`KGG4tzwM<%8ewWT!C>fvbn0R~ zT-8s4D3E8U2+V{&8%-NFRtq)Kaeq{dKPVNQwK_a9)}W~B9E)C{ro@ZBr|e--!7$Pn z^r)GY+%(Y=VP-YZa2y(Cmoz-Pt>oe1t~>`zRdxKfmfH%-zh{9*I40*HSC0-v#qV--nr+HUFXtFeH4{Av$9RIoSSFJizeV4&403t3XvgG z%EjQEr!Ct+D#!u+D}5|6QmauYY#ckmyOEi7OAs2G4FS=9FhVW{;zLCzETIkiBUSn7 zL*!`PO%?7u2MH~DuI>X@DK@7F2T=eTUhag61M=(zfm-w~1o$044H^IR%2#)~C(`@%8laFcuvl@2m}%{iZ~ zPPH+hW50Pk`!=kA3IyM3{FK69&+^k-MtE4(w1d1%bRsKMfTe=i)3N};@Om_XKiO3diAk78(m`gQeQ5iaxzOkfhw zB6XW2gt)JEKG`6i@uMfddy^Ar+ES?zzHvRP4;C9Mf`4B4GH%wmERIa)qnfUeRR;2o zwSZtP2}bydbhax(7`u|z#Mh3TKb2Ng0_Nj6*eT|N{)b650nT&)_rFw_0KyllO*XUgJ zo;ddoCx2_I=Jy&{4NcQ5lJtCVtAsJiY8Q-A_}B3kF^vM++O7E)apHLc-&*6_00d{*rqPddkuSvIscK8`b@MnojE z1?$492+`uzz$F**ACO);t>nkM+_;#cnp;qU^MAj%(4*ux5>M-NBSFAWzlL~G6b%O< zb*%)^PQ=G=y9h}bmB^%892vJ z1%KwW;Q@gpI{+lFf}BCIdqNQchYpYGn?j?TNP7x$dG6a^qw$M5_!m?N% z@sZMAdi(4E*%BM^on-*dZaoZAU`7sv^Jia~V2G(JG&yr2N#KvMaubWBP7Dk^Vk@}o z!ssc-sm^v`%&2NuSRw$jV}{}(C?q?8@PF5%I2oP=6JrFYPZT$JaOkV>#L!kR#3j+? zBYvYn{HKS(r7uRjWTMzv0%&f7yA#;)4~QYL2UyGsJVEo6V`fq!Br!$}&KijTdDPQ~ zLkxr3l4I-IPf+5#3@D*HN60H}L3K#!-3$!0b@SLld2l{*GCH{2wBaF^%ST9EQ-AM? zrevZL&i?oXOF&1HqfY_?SG)it!?Wm=mWOb*bn@+axsweH-=n{I5E+9P}YIJ9u5Q#Y#l|AyT z$vc_GM}3mxU(x6{;8wOZ@v}?MJ(}YA3Ka;O!5o1?R4p&2{P|1_xGB}mZhy=e6~D|C zhanP^YM$aQGn$dP=VId!uUc>cM?Ci??-m+j;CSTX$~SOrM6?xn=>HF;xp*NLBf~(k zftcZ2{XduUXeB)H?NX_8h=j&euoxC~_;Gy-o@ZkiJQFc$*eqcQDWTTHf~sq~l*Khd zfiV>}!)4O?RXA3*H_C$Q*MBD>WC9oR`Gl3t#dwM=GElIEeGsyP#w{VRqo^bUG&&ic zxVQ<1WI6 z-Ovd&RB{Wn#JV^Sadl75M2ggX0>3@PNs=aJ0zDa_KH$ekw6+hTc|?+^9e-_q(I3-W zEX>4Tx0C=2zkv&MmP!xqvTT4YM z4ptBm30RR!K!3kpW~$jS4yc-Cq!MujDw_ypovrW+RV2J!T!rE}gVjATK;?XDu>ms& z6)YDZ>~`Uk>j5Z#Qb|NXRA}DqSwU~pNEm&Ap2&`4ZPHi`kzyf>R;9gwRHSfPap0C8 z5b+22Mf`!)-uALL4hSwq+en;RRVi32BSIpVW|hnoq#oLE2;XRuy4iRp%_Iw-R4Mg% z{65c{8GmnHrnI>;@Ph!%$>GKC&yhAtn>zpi#^PzUF#cD0QLk@_)~ z9T@gcjbB(TB6{BhfC1e@<8bo%cw+y~$B$UQcaJ8*;1qUa1pxewE$s&!?Y%NepjxZr z?CcDM)gl0YXzc=N;>puz@_hyG4@q)z@He&)H4pIi#h+94kD3P%ypQ%_!zzI{umDk5 zEyCZ}x_0u^38GF~`aKPPQvZb@FlruHOM+(fydnuJ4Zh8>O58%D<^iIA-dRgx-uUBK zY4L52RpJU1ybsY6){>YvzUT@0{nNo8?{AgB?n#V)C=9WE|ACbRY~XL-e}E_q(LIT^ z@R`eGf9nK_o`4?&_Ew)Id_M?=D@g<2zjIgixBd-eE;G`BreO=8IXN(wLG%QelY`)W ztXCf+U-0D4K%rhE(!6p7Q5YKKP}LGZqI5mT+zC2_@48D6=9 zYOSuX1LM{QeC0iB1kM|uj=@xX`nDanTZmhKA7p`*FL)5VKSH=^u2C4mD_2H}pl;kT zJc+R^PHhSKz1J^Ch$QmHSC`sGt53HIWh^V7MH7Y`5N#bum6k0XI8S{4&RwbF7|Uj8 z_9tzL>NR;^+yTxn03f4PxOr>xS%+``MihpZEUT%v(4#lQ892{V^(RlC4Zkg6D0SF> zyrqO61c<^A?ZbwNpQqKg8+U-4!_7~KFJLaC39&p?D06b~gTM-TD!z?4+It0@Ux2af z&~Id}wiaBm@d8w;?>BZw{zm53d84J**ID>c?FN{<4OZE!5PHTPDf!zg&T) ze%!3_e-fB7O#Qf7;{PPjK!@r2akIpKU;1z0`orIl_tqw!KqQ@#&`E-BIjrNyCE?S_ z#x(iQ+Rs}E0`uP5l|nkv54&Ozb#$1PA2)`dI{z8@c?+|J(#w(OmE?!115bamIz5@j zkDG))ZT{Eg=UFFE@cyNfka0h3{IrDpFq25*$4$bYHh&ez)X!Tu9k}1vl`Da7Yz`{7 zll(B*u4#do`f)0L-bEX>4Tx04R}tkv&MmP!xqvQ$>*$2MdZg zWN4Gl3_yR-s4&gy8V5ApHq*(3n9Z$t6(RCjTn<=^*b zh1I;pfPhFm%M8;d-XNadv<=St#1U4KRpN8vF_SJx{K$31<2TMlmj#{~F*E6T;s~)= z>|mvXS;^Fhr--Afrc=I<^;qS+#aXM=SnHnrg`t1EzOu}9S|dne5lfIDLO~5>RA3`c zyH1LQG@U1W{KKwaB9}t0G8j1)P=yBB^@IPx@7Y>~$q6qhlmNP49Ookj^y~u7y5oEw zJ5KWi2tET>dfQ)Z05hMY*V|h32|JM*x85dC)YCx6SBtIPaYCXRg{) z0Qw31li;VW{QI8g;lYFX8~w?Sj~mc54W8$v&g->46k3{+Cu?HojA|F#s1wPLu}nXq ze{B3*z5wU%8UXa^8S01AR`W*x#?cX09zCLg&_9P-O9Ozpx-R{KgYDOe0w`CixVX4L zE?)qD0I5|Vjy-$+f`6~?bq7h(W?`R3K_vLSq{GHl9e6Nd>cRNZG_#Gy|a`^(x z)%CBQJaK@CgO;wN;D`B7L;{`KzOo=_M32kDu;SpW?5ofv)T!;mIs8jm5aY%VV#UQ* z*;k=6(D%CVJHE0Y#*OcHe7^t5;0OJcCa`^f>cX*YY&?FVBmfoo8;_sBv2CcFI?3ZH4SU^;`}@xioN_+A$) z<)4tv8GL0R6Yo*djM5?;+fEciacvWfWfGNQ>1@tWN{LnAi*+F3@9k_Mn=^(6Q~T6^ zy><`1OH6Az3dIsk%aX1G%atmBf4Ne{+uwh^yLF&SGfIm*Sya%|3>Axfb9EgXn@<4% zg<|Q}N(sVG#6p`Vb)}uC0$;?W>uF8rGn81S?{#^y=(*-e9lqCmNA5%H|CCUUw+rNn>?v8%Akyhy=CW zM#7UA#l@+rLVo+rtD!_v@`AHVY@^vH%R(8;@OROGAq7Ob4kV_fstz0{zPbF6*Kv$x zQZ)L*mPGL$f6i?Hy-NT{Nf9?=p1__DQNT1E1Rs_*S=4gFSu zpEc%rd2n1i0088d7BIJ*$Jt?j3$6D@$S*Ab0BV2iB3ml_pTq=~R7X{sz`I~)hb?~H zdVe(7yRMQqNv328O{&L@6Te+=PV_)iG6R$9abF96Dlp?Fb=E1Lfl2i^75Mq31-=)4 z^9f9<$En1hv?Wy;i4p5CReE3?{7EG;>6IUTMAn$U@dP65l!Q(aWa+Sfj2;(;PbV8u z;t$jF7|Tf4p=Y1Y)h94(%#ZwlP(5r!C(6`eQhHnfKX&{9J&)qp*nGC<>wJuVDCPW(YVkLfHWqsNIEdY)?g4`2Md5f5Na Qxc~qF07*qoM6N<$f zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K7vfC;QhTmC5mw+UM#B#8KnY)87zdx9`$?2Ki z)9F)ZVq(CuEP+1BNkaer-w5AuaFiI5n&*;p#F0uWTru%@9d-2-)9Uv{d`w>C;l9BT z2}ZdtJ#Tr3oT1NKer`Jza(yyX`o3|z-l*FU`*!f~xE%>y4r1Ge>NX_2J*VsX*q(E7 zd)`8O?7+9guQSXpiE-9Ume41TX1QSQ9Frk#Z7x)7`M(`~ybcdlM4q_|NnE{OtCV?? zRE$@zJZA}WUcCsd_{IQaSOP4U9oru7XuibertI4llUx*-nG-%Z(3dko%U) z2YUG)mppvDLHarF#q?=KmTt%VvLch)M|{RQ&oP>h(XZXrl(FT!H}%*Fg*k)bF_U0D zmeCBSVs5JyG)K9dK;7aQ544jCF%S<*)Tq)3)Ys&s6KhP|*)TGnpgv^|7KKl_prg@X zlOjzV32-7+1X3O&?6uJHTb93*9G)9Nl?lcOkNj^7Us3v98)McIQP?#@7hs-gk7Z19 zvxgdp(AYuM72rpEO~?&afKXSM9UH9ixJ(>MKXXeKXn~$$ylRZBUC&QL3BDDJApwGz zNt7~0V>TiL(gCPabQBjKAV8|zut-iK8EjAla2@cB(b8q*OOCfBNT_5nuqjdjD;V_5kDBa<13aUu}6NdO7W zlUa01i6^ zObNO+$IL4i0004nX+uL$Nkc;*aB^>EX>4Tx0C=2zkv&MmP!xqvTT4YM4ptBm30R#h zh>AFB6^c+H)C#RSn7s54nlvOSE{=k0!NH%!s)LKOt`4q(Aov5~=H{g6A|>9J6k5c1 z;qgAsyXWxUeSp7SW~$jS4yc-Cq!MujDw_ypovrW+RV2J!T!rE}gVj6D*P1)G<~dFufHci2c>^3A0;2`WUiWx+XZzg#?P<>M2U19Kn!F*RwEzGB24YJ` zL;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ju|;78oCwTeE!t z000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0000?Nkl5Rr2+gYq8G_FRtNug4Dzcmp(+p$KBn)sm9UGgZKtuwe#2AFr6@&tjiI^}T j*$Y@~3?(ZnhXw!u0ApONO}}-w00000NkvXXu0mjfr;diy literal 0 HcmV?d00001 diff --git a/app/res/ui/icon/ground.png b/app/res/ui/icon/floor.png similarity index 100% rename from app/res/ui/icon/ground.png rename to app/res/ui/icon/floor.png diff --git a/app/res/ui/icon/virtual_dungeon.png b/app/res/ui/icon/virtual_dungeon.png deleted file mode 100644 index 3f865db213a9d0cceb47d9854ec2e5e4319dbc87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 767 zcmVEX>4Tx04R}tkv&MmKpe$iTcsiuhjtKA$xvOiAS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRL(V-$lDGxa%9Ou}<~-NVP%yBN>%KKJM7QF0~&d;;+-(+!JwgLrz= z(mC%FM_5r(h|h_~47wokBiCh@-#8Z>_Vdh$kxtDMM~H<&8_R9XiiS!&MI2RBjq?2& zmle)ioYiubHSft^7|LlY%Uq{9fCLt?1Q7ycR8c}17Gkt&q?kz2e%!;~@AyS>$>b`5 zkz)ZBsE`~#_#gc4*33^%xJjWn(D`E9AHzU!7iiRM`}^3o8z(^E8Mx9~{z@H~`6Rv8 z(xOK|?>2C8-O}Ve;Bp7(d(tICa-;xFe?AYqpV2pEf$%NRz2^4T+{ftykfyGZH^9Lm zFjAoGb&q#eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{007rXL_t(I%cWDX5rZ%cd@}@h(9ot$!x*pui%>H_R&bTp z8N*dN=aPzsCIh5&8YyRl?G*kypKRGkFTGG`YLq}}|M?xJwmUq~+nT+v$a(-id@SH; zB?B-_B)iF$fTo7tRs&#Q-_&*?b0P_=G@3P1RwGK zPDv~WiS=NNb)Il0yCj<^Sh%3}M@+ty3e4gZ_@1d5I0O(Z0N|Y|E6PABauIj)-vGqo x%G4>lAxXLWb~i{Vklwv;?*>Vg{*4t1@daPnL7hUUl3xG-002ovPDHLkV1iQ=NSpuw diff --git a/app/src/components/App.tsx b/app/src/components/App.tsx index 4e4c49a..6bab52d 100644 --- a/app/src/components/App.tsx +++ b/app/src/components/App.tsx @@ -49,12 +49,16 @@ export default function App() {
+ + + + - + - +
diff --git a/app/src/components/route/AssetRoute.tsx b/app/src/components/route/AssetRoute.tsx new file mode 100644 index 0000000..6271108 --- /dev/null +++ b/app/src/components/route/AssetRoute.tsx @@ -0,0 +1,57 @@ +import * as Preact from 'preact'; +import { useAppData } from '../../Hooks'; +import { Switch, Route, Redirect, useParams, useHistory } from 'react-router-dom'; + +import Button from '../Button'; + +export default function AssetRoute() { + const [ { assets } ] = useAppData('assets'); + if (!assets) return null; + + const history = useHistory(); + const { id } = useParams<{ id: string }>(); + const currentAsset = (assets ?? []).filter(a => a.identifier === id)[0]; + + if (!currentAsset) return ; + + const handleDeleteAsset = () => { + fetch('/data/asset/delete', { + method: 'POST', cache: 'no-cache', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ identifier: id }) + }); + + history.push('/assets'); + }; + + return ( +
+ +
+ + + +
+
+ ); +} diff --git a/app/src/components/route/AssetsRoute.tsx b/app/src/components/route/AssetsRoute.tsx index 4a5c339..1915ce3 100644 --- a/app/src/components/route/AssetsRoute.tsx +++ b/app/src/components/route/AssetsRoute.tsx @@ -1,30 +1,37 @@ import * as Preact from 'preact'; -import { Switch, Route, Redirect, NavLink as Link } from 'react-router-dom'; +import { useAppData } from '../../Hooks'; +import { Switch, Route, NavLink as Link, useHistory } from 'react-router-dom'; +import AssetList from '../view/AssetList'; import NewAssetForm from '../view/NewAssetForm'; -import MyAssetsList from '../view/MyAssetsList'; +import AssetCollectionList from '../view/AssetCollectionList'; export default function AssetsRoute() { + const history = useHistory(); + const [ { assets, collections, user } ] = useAppData([ 'assets', 'collections', 'user' ]); return (
- {/* -

Storefront

+ + a.user === user!.user)} + onClick={(user, identifier) => history.push(`/asset/${user}/${identifier}`)} + onNew={() => history.push('/assets/new')} /> - -

Subscribed

-
*/} - + + + history.push(`/assets/collection/${user}/${identifier}`)} /> + + - + {/* */}
diff --git a/app/src/components/route/CollectionRoute.tsx b/app/src/components/route/CollectionRoute.tsx new file mode 100644 index 0000000..55b38f1 --- /dev/null +++ b/app/src/components/route/CollectionRoute.tsx @@ -0,0 +1,59 @@ +import * as Preact from 'preact'; +import { useState } from 'preact/hooks'; +import { useAppData } from '../../Hooks'; +import { Redirect, useParams } from 'react-router-dom'; + +import AssetList from '../view/AssetList'; + +export default function CollectionRoute() { + const [ { collections, assets },, mergeData ] = useAppData([ 'collections', 'assets' ]); + if (!collections) return null; + + const [ adding, setAdding ] = useState(false); + + const { id } = useParams<{ id: string }>(); + const currentCollection = (collections ?? []).filter(c => c.identifier === id)[0]; + if (!currentCollection) return ; + + const collectionAssets = (assets || []).filter(({ user, identifier }) => currentCollection.items.includes(user + ':' + identifier)); + + const handleAddingAsset = () => { + setAdding(true); + }; + + const handleAddAsset = async (user: string, identifier: string) => { + const res = await fetch('/data/collection/add', { + method: 'POST', cache: 'no-cache', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ collection: currentCollection.identifier, asset: user + ':' + identifier }) + }); + + if (res.status !== 200) console.error(await res.text()); + else { + const data = await res.json(); + await mergeData(data); + } + + setAdding(false); + }; + + return ( +
+ +
+ {!adding && {/**/}} onNew={handleAddingAsset} newText='Add Asset' />} + {adding && +

Add Asset

+ +
} +
+
+ ); +} diff --git a/app/src/components/route/Routes.ts b/app/src/components/route/Routes.ts index 2d3e7e6..46f12d4 100644 --- a/app/src/components/route/Routes.ts +++ b/app/src/components/route/Routes.ts @@ -8,6 +8,8 @@ export { default as Map } from './MapRoute'; export { default as Campaign } from './CampaignRoute'; export { default as Campaigns } from './CampaignsRoute'; +export { default as Asset } from './AssetRoute'; export { default as Assets } from './AssetsRoute'; +export { default as Collection } from './CollectionRoute'; export { default as Editor } from './EditorRoute'; diff --git a/app/src/components/view/AssetCollectionList.sass b/app/src/components/view/AssetCollectionList.sass new file mode 100644 index 0000000..32254a2 --- /dev/null +++ b/app/src/components/view/AssetCollectionList.sass @@ -0,0 +1,87 @@ +@use '../../style/text' +@use '../../style/grid' +@use '../../style/def' as * + +.AssetCollectionList + max-width: 1000px + display: block + margin: 0 auto + margin-top: 56px + + .AssetCollectionList-Grid + @include grid.auto_width(160px, 16px) + + .AssetCollectionList-CollectionWrap + .AssetCollectionList-Collection + width: 100% + height: 0 + border: none + display: grid + user-select: none + position: relative + padding-bottom: 100% + + overflow: hidden + border-radius: 4px + text-decoration: none + background-color: $neutral-200 + box-shadow: 0px 2px 12px 0px transparentize($neutral-000, 0.9) + + .AssetCollectionList-CollectionInner + position: absolute + top: 0 + left: 0 + width: 100% + height: 100% + display: grid + grid-template-rows: 1fr auto + + .AssetCollectionList-CollectionPreview + position: relative + padding: 8px + overflow: hidden + background-color: $neutral-100 + + img + width: 100% + height: 100% + + user-select: none + object-fit: contain + pointer-events: none + image-rendering: pixelated + + .AssetCollectionList-CollectionTitle + @include text.line_clamp + + margin: 0 + font-size: 18px + padding: 12px + + .AssetCollectionList-NewCollection + width: 100% + display: flex + user-select: none + flex-direction: column + justify-content: center + align-items: center + + overflow: hidden + border-radius: 4px + background-color: $neutral-100 + box-shadow: 0px 2px 12px 0px transparentize($neutral-000, 0.9) + + border: 3px dotted $neutral-300 + + min-height: 180px + height: 100% + + img + width: 80px + height: 80px + image-rendering: pixelated + + p + font-size: 18px + margin: 0 + margin-top: 4px diff --git a/app/src/components/view/AssetCollectionList.tsx b/app/src/components/view/AssetCollectionList.tsx new file mode 100644 index 0000000..b0da310 --- /dev/null +++ b/app/src/components/view/AssetCollectionList.tsx @@ -0,0 +1,40 @@ +import * as Preact from 'preact'; + +import './AssetCollectionList.sass'; + +import { AssetCollection } from '../../../../common/DBStructs'; + +interface Props { + collections: AssetCollection[]; + onNew?: () => void; + onClick: (user: string, identifier: string) => void; +} + +export default function AssetCollectionList({ collections, onNew, onClick }: Props) { + return ( +
+ {collections === undefined &&

Loading Collections...

} + {collections !== undefined && + +
    + {collections.map(c =>
  • + +
  • )} +
  • + {onNew && } +
  • +
+
+ } +
+ ); +} diff --git a/app/src/components/view/MyAssetsList.sass b/app/src/components/view/AssetList.sass similarity index 86% rename from app/src/components/view/MyAssetsList.sass rename to app/src/components/view/AssetList.sass index e1ddb42..0554905 100644 --- a/app/src/components/view/MyAssetsList.sass +++ b/app/src/components/view/AssetList.sass @@ -2,18 +2,20 @@ @use '../../style/grid' @use '../../style/def' as * -.AssetsList +.AssetList max-width: 1000px display: block margin: 0 auto margin-top: 56px - .AssetsList-Grid + .AssetList-Grid @include grid.auto_width(160px, 16px) - .AssetsList-AssetWrap - .AssetsList-Asset + .AssetList-AssetWrap + .AssetList-Asset + width: 100% height: 0 + border: none display: grid user-select: none position: relative @@ -25,7 +27,7 @@ background-color: $neutral-200 box-shadow: 0px 2px 12px 0px transparentize($neutral-000, 0.9) - .AssetsList-AssetInner + .AssetList-AssetInner position: absolute top: 0 left: 0 @@ -34,7 +36,7 @@ display: grid grid-template-rows: 1fr auto - .AssetsList-AssetPreview + .AssetList-AssetPreview position: relative padding: 8px overflow: hidden @@ -49,14 +51,15 @@ pointer-events: none image-rendering: pixelated - .AssetsList-AssetTitle + .AssetList-AssetTitle @include text.line_clamp margin: 0 font-size: 18px padding: 12px - .AssetsList-NewAsset + .AssetList-NewAsset + width: 100% display: flex user-select: none flex-direction: column diff --git a/app/src/components/view/AssetList.tsx b/app/src/components/view/AssetList.tsx new file mode 100644 index 0000000..ee72f9b --- /dev/null +++ b/app/src/components/view/AssetList.tsx @@ -0,0 +1,43 @@ +import * as Preact from 'preact'; + +import './AssetList.sass'; + +import { Asset } from '../../../../common/DBStructs'; + +interface Props { + assets: Asset[]; + + newText?: string; + onNew?: () => void; + onClick: (user: string, identifier: string) => void; +} + +export default function AssetList({ assets, newText, onNew, onClick }: Props) { + return ( +
+ {assets === undefined &&

Loading Assets...

} + {assets !== undefined && + +
    + {assets.map(a =>
  • + +
  • )} +
  • + {onNew && } +
  • +
+
+ } +
+ ); +} diff --git a/app/src/components/view/MyAssetsList.tsx b/app/src/components/view/MyAssetsList.tsx deleted file mode 100644 index bbdfbe3..0000000 --- a/app/src/components/view/MyAssetsList.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as Preact from 'preact'; -import { useAppData } from '../../Hooks'; -import { NavLink as Link } from 'react-router-dom'; - -import './MyAssetsList.sass'; - -export default function MyAssetsList() { - const [ { assets } ] = useAppData('assets'); - - return ( -
- {assets === undefined &&

Loading Assets...

} - {assets !== undefined && - -
    - {assets.map(a =>
  • - -
    -
    - -
    -

    {a.name || 'Untitled'}

    -
    - -
  • )} -
  • - - -

    Upload Asset

    - -
  • -
-
- } -
- ); -} diff --git a/app/src/components/view/NewAssetForm.sass b/app/src/components/view/NewAssetForm.sass index ef1e1a3..aa9dfda 100644 --- a/app/src/components/view/NewAssetForm.sass +++ b/app/src/components/view/NewAssetForm.sass @@ -25,6 +25,13 @@ display: grid grid-gap: 16px grid-template-columns: 1fr 1fr + + .NewAssetForm-Col2-60 + @extend .NewAssetForm-Col2 + grid-template-columns: 5fr 4fr + + & > div:nth-child(2) + text-align: right .NewAssetForm-UploadWrap @extend %material_button diff --git a/app/src/components/view/NewAssetForm.tsx b/app/src/components/view/NewAssetForm.tsx index 9dc0cb4..09b6581 100644 --- a/app/src/components/view/NewAssetForm.tsx +++ b/app/src/components/view/NewAssetForm.tsx @@ -16,7 +16,7 @@ export default function NewAssetForm() { const [ queryState, setQueryState ] = useState<'idle' | 'querying'>('idle'); - const [ type, setType ] = useState<'wall' | 'ground' | 'token'>('token'); + const [ type, setType ] = useState<'wall' | 'floor' | 'detail' | 'token'>('token'); const [ tokenType, setTokenType ] = useState<1 | 4 | 8>(4); const [ file, setFile ] = useState(null); @@ -25,7 +25,7 @@ export default function NewAssetForm() { const [ name, setName ] = useState(''); const [ identifier, setIdentifier ] = useState(''); - const handleSetType = (type: 'wall' | 'ground' | 'token') => { + const handleSetType = (type: 'wall' | 'floor' | 'detail' | 'token') => { setType(type); }; @@ -69,12 +69,9 @@ export default function NewAssetForm() { body: data }); - if (res.status === 202) { - console.log('hellyea!'); - history.push('/assets'); - } + if (res.status === 200) history.push('/assets'); else { - console.log('hellnah', await res.text()); + console.error(await res.text()); setQueryState('idle'); } }; @@ -83,12 +80,13 @@ export default function NewAssetForm() {

New Asset

-
+
diff --git a/app/src/editor/ArchitectMode.ts b/app/src/editor/ArchitectMode.ts index 944fc89..426c9c5 100755 --- a/app/src/editor/ArchitectMode.ts +++ b/app/src/editor/ArchitectMode.ts @@ -1,7 +1,7 @@ import type MapScene from './scene/MapScene'; -import Layer from './util/Layer'; import { Vec2 } from './util/Vec'; +import { Layer } from './util/Layer'; export default class ArchitectMode { scene: MapScene; @@ -16,7 +16,7 @@ export default class ArchitectMode { pointerPrimaryDown: boolean = false; activeTileset: number = 0; - activeLayer: Layer = Layer.wall; + activeLayer: Layer = 'wall'; manipulated: {pos: Vec2; layer: Layer; lastTile: number; tile: number}[] = []; @@ -25,19 +25,18 @@ export default class ArchitectMode { } init() { - // Create cursor hover sprite this.cursor = this.scene.add.sprite(0, 0, 'cursor'); - this.cursor.setScale(4, 4); this.cursor.setDepth(1000); this.cursor.setOrigin(0, 0); + this.cursor.setScale(1 / 16); } update() { this.active = true; this.cursor!.setVisible(true); - let selectedTilePos = new Vec2(Math.floor(this.scene.view.cursorWorld.x / 64), Math.floor(this.scene.view.cursorWorld.y / 64)); - this.cursor!.setPosition(selectedTilePos.x * 64, selectedTilePos.y * 64); + let selectedTilePos = new Vec2(Math.floor(this.scene.view.cursorWorld.x), Math.floor(this.scene.view.cursorWorld.y)); + this.cursor!.setPosition(selectedTilePos.x, selectedTilePos.y); this.cursor!.setVisible((selectedTilePos.x >= 0 && selectedTilePos.y >= 0 && selectedTilePos.x < this.scene.map.size.x && selectedTilePos.y < this.scene.map.size.y)); @@ -91,7 +90,7 @@ export default class ArchitectMode { if (Math.abs(b.x - a.x) > Math.abs(b.y - a.y)) b.y = a.y; else b.x = a.x; - this.cursor!.setPosition(b.x * 64, b.y * 64); + this.cursor!.setPosition(b.x, b.y); this.primitives.forEach((v) => v.destroy()); this.primitives = []; @@ -100,21 +99,19 @@ export default class ArchitectMode { this.primitives.forEach((v) => { v.setOrigin(0, 0); - v.setScale(64, 64); - v.setLineWidth(0.03); v.setDepth(300); + v.setLineWidth(0.03); }); - this.primitives.push(this.scene.add.sprite(this.startTilePos.x * 64, this.startTilePos.y * 64, - 'cursor') as any as Phaser.GameObjects.Line); + this.primitives.push(this.scene.add.sprite(this.startTilePos.x, this.startTilePos.y, 'cursor') as any); this.primitives[1].setOrigin(0, 0); - this.primitives[1].setScale(4, 4); + this.primitives[1].setScale(1 / 16); this.primitives[1].setAlpha(0.5); } else if (!this.scene.i.mouseLeftDown() && !this.scene.i.mouseRightDown() && this.pointerDown) { - let a = new Vec2(this.startTilePos.x * 64, this.startTilePos.y * 64); - let b = new Vec2(selectedTilePos.x * 64, selectedTilePos.y * 64); + let a = new Vec2(this.startTilePos.x, this.startTilePos.y); + let b = new Vec2(selectedTilePos.x, selectedTilePos.y); if (Math.abs(b.x - a.x) > Math.abs(b.y - a.y)) b.y = a.y; else b.x = a.x; @@ -125,12 +122,12 @@ export default class ArchitectMode { change.y /= normalizeFactor; while (Math.abs(b.x - a.x) >= 1 || Math.abs(b.y - a.y) >= 1) { - this.placeTileAndPushManip(new Vec2(Math.floor(a.x / 64), Math.floor(a.y / 64)), this.pointerPrimaryDown); + this.placeTileAndPushManip(new Vec2(Math.floor(a.x), Math.floor(a.y)), this.pointerPrimaryDown); a.x += change.x; a.y += change.y; } - this.placeTileAndPushManip(new Vec2(b.x / 64, b.y / 64), this.pointerPrimaryDown); + this.placeTileAndPushManip(new Vec2(b.x, b.y), this.pointerPrimaryDown); this.primitives.forEach((v) => v.destroy()); this.primitives = []; } @@ -154,7 +151,6 @@ export default class ArchitectMode { this.primitives.forEach((v) => { v.setOrigin(0, 0); - v.setScale(64, 64); v.setLineWidth(0.03); v.setDepth(300); }); @@ -180,14 +176,14 @@ export default class ArchitectMode { let change = new Vec2(this.scene.view.cursorWorld.x - this.scene.view.lastCursorWorld.x, this.scene.view.cursorWorld.y - this.scene.view.lastCursorWorld.y); - let normalizeFactor = Math.sqrt(change.x * change.x + change.y * change.y); + let normalizeFactor = Math.sqrt(change.x * change.x + change.y * change.y) * 10; change.x /= normalizeFactor; change.y /= normalizeFactor; let place = new Vec2(this.scene.view.lastCursorWorld.x, this.scene.view.lastCursorWorld.y); - while (Math.abs(this.scene.view.cursorWorld.x - place.x) >= 1 || Math.abs(this.scene.view.cursorWorld.y - place.y) >= 1) { - this.placeTileAndPushManip(new Vec2(Math.floor(place.x / 64), Math.floor(place.y / 64)), this.scene.i.mouseLeftDown()); + while (Math.abs(this.scene.view.cursorWorld.x - place.x) >= 0.1 || Math.abs(this.scene.view.cursorWorld.y - place.y) >= 0.1) { + this.placeTileAndPushManip(new Vec2(Math.floor(place.x), Math.floor(place.y)), this.scene.i.mouseLeftDown()); place.x += change.x; place.y += change.y; } @@ -198,12 +194,12 @@ export default class ArchitectMode { placeTileAndPushManip(manipPos: Vec2, solid: boolean) { let tile = solid ? this.activeTileset : -1; - let layer = (tile === -1 && this.activeLayer === Layer.floor) ? Layer.wall : this.activeLayer; + let layer = (tile === -1 && this.activeLayer === 'floor') ? 'wall' : this.activeLayer; - let lastTile = this.scene.map.getTileset(layer, manipPos.x, manipPos.y); + let lastTile = this.scene.map.activeLayer.getTile(layer, manipPos.x, manipPos.y); if (tile === lastTile) return; - this.scene.map.setTile(layer, tile, manipPos.x, manipPos.y); + this.scene.map.activeLayer.setTile(layer, tile, manipPos.x, manipPos.y); this.manipulated.push({ pos: manipPos, @@ -211,7 +207,6 @@ export default class ArchitectMode { lastTile: lastTile, tile: tile }); - } cleanup() { diff --git a/app/src/editor/MapChunk.ts b/app/src/editor/MapChunk.ts deleted file mode 100755 index fc5c305..0000000 --- a/app/src/editor/MapChunk.ts +++ /dev/null @@ -1,96 +0,0 @@ -import MapData from './MapData'; - -import Layer from './util/Layer'; -import { Vec2 } from './util/Vec'; - -export default class MapChunk { - static CHUNK_SIZE = 16; - static TILE_SIZE = 16; - static DIRTY_LIMIT = 32; - - private pos: Vec2; - private map: MapData; - private canvas: Phaser.GameObjects.RenderTexture; - - private dirtyList: Vec2[] = []; - private fullyDirty: boolean = false; - - constructor(map: MapData, x: number, y: number) { - this.pos = new Vec2(x, y); - this.canvas = map.scene.add.renderTexture( - x * MapChunk.CHUNK_SIZE * MapChunk.TILE_SIZE * 4, y * MapChunk.CHUNK_SIZE * MapChunk.TILE_SIZE * 4, - MapChunk.CHUNK_SIZE * MapChunk.TILE_SIZE, MapChunk.CHUNK_SIZE * MapChunk.TILE_SIZE); - - this.map = map; - this.canvas.setScale(4); - this.canvas.setOrigin(0, 0); - - for (let i = 0; i < MapChunk.CHUNK_SIZE * MapChunk.CHUNK_SIZE; i++) { - let x = i % MapChunk.CHUNK_SIZE; - let y = Math.floor(i / MapChunk.CHUNK_SIZE); - - let mX = x + this.pos.x * MapChunk.CHUNK_SIZE; - let mY = y + this.pos.y * MapChunk.CHUNK_SIZE; - if (mX >= this.map.size.x || mY >= this.map.size.y) continue; - - this.drawTile(x, y); - } - } - - dirty(pos: Vec2): void { - if (!this.fullyDirty) { - for (let v of this.dirtyList) if (v.equals(pos)) return; - this.dirtyList.push(pos); - - if (this.dirtyList.length > MapChunk.DIRTY_LIMIT) { - this.fullyDirty = true; - this.dirtyList = []; - } - } - } - - rebuild(): boolean { - if (this.fullyDirty) { - for (let i = 0; i < MapChunk.CHUNK_SIZE * MapChunk.CHUNK_SIZE; i++) { - let x = i % MapChunk.CHUNK_SIZE; - let y = Math.floor(i / MapChunk.CHUNK_SIZE); - - let mX = x + this.pos.x * MapChunk.CHUNK_SIZE; - let mY = y + this.pos.y * MapChunk.CHUNK_SIZE; - if (mX >= this.map.size.x || mY >= this.map.size.y) continue; - - this.drawTile(x, y); - } - this.fullyDirty = false; - return true; - } - - if (this.dirtyList.length === 0) return false; - - for (let elem of this.dirtyList) this.drawTile(elem.x, elem.y); - this.dirtyList = []; - return true; - } - - private drawTile(x: number, y: number): void { - let mX = x + this.pos.x * MapChunk.CHUNK_SIZE; - let mY = y + this.pos.y * MapChunk.CHUNK_SIZE; - - let wallTile = this.map.getTile(Layer.wall, mX, mY); - if (this.map.getTileset(Layer.wall, mX, mY) === -1 || (wallTile < 54 || wallTile > 60)) { - this.canvas.drawFrame(this.map.manager.groundLocations[this.map.getTileset(Layer.floor, mX, mY)].identifier, - this.map.getTile(Layer.floor, mX, mY), x * MapChunk.TILE_SIZE, y * MapChunk.TILE_SIZE); - - if (this.map.getTileset(Layer.overlay, mX, mY) !== -1) - this.canvas.drawFrame(this.map.manager.overlayLocations[this.map.getTileset(Layer.overlay, mX, mY)].identifier, - this.map.getTile(Layer.overlay, mX, mY), x * MapChunk.TILE_SIZE, y * MapChunk.TILE_SIZE); - } - - if (this.map.getTileset(Layer.wall, mX, mY) !== -1) - this.canvas.drawFrame(this.map.manager.wallLocations[this.map.getTileset(Layer.wall, mX, mY)].identifier, - this.map.getTile(Layer.wall, mX, mY), x * MapChunk.TILE_SIZE, y * MapChunk.TILE_SIZE); - - if ((x % 2 === 0 && y % 2 === 0) || (x % 2 !== 0 && y % 2 !== 0)) - this.canvas.drawFrame('grid_tile', 0, x * MapChunk.TILE_SIZE, y * MapChunk.TILE_SIZE); - } -} diff --git a/app/src/editor/MapData.ts b/app/src/editor/MapData.ts deleted file mode 100755 index f3d0cae..0000000 --- a/app/src/editor/MapData.ts +++ /dev/null @@ -1,198 +0,0 @@ -import MapChunk from './MapChunk'; -import * as SmartTiler from './SmartTiler'; -import type MapScene from './scene/MapScene'; -import TilesetManager from './TilesetManager'; - -import Layer from './util/Layer'; -import { Vec2 } from './util/Vec'; -import { Asset } from './util/Asset'; -import { clamp } from './util/Helpers'; - -export default class MapData { - scene: MapScene; - size: Vec2 = new Vec2(0, 0); - - savedMapData: number[][] = []; - - manager: TilesetManager; - private layers: {[key: number]: { tiles: number[][]; tilesets: number[][] }} = {}; - private chunks: MapChunk[][] = []; - - constructor(scene: MapScene) { - this.scene = scene; - this.manager = new TilesetManager(scene); - } - - init(size: Vec2, assets: Asset[]) { - this.size = size; - this.manager.init(assets); - - this.registerLayer(Layer.floor, () => Math.floor(Math.random() * 6) + 54, 0); - this.registerLayer(Layer.wall, 0, -1); - this.registerLayer(Layer.overlay, 0, -1); - - for (let i = 0; i < Math.ceil(size.y / MapChunk.CHUNK_SIZE); i++) { - this.chunks[i] = []; - for (let j = 0; j < Math.ceil(size.x / MapChunk.CHUNK_SIZE); j++) { - this.chunks[i][j] = new MapChunk(this, j, i); - } - } - } - - update(): void { - let start = Date.now(); - for (let arr of this.chunks) for (let chunk of arr) { - chunk.rebuild(); - if (Date.now() - start > 10) break; - } - - if (this.scene.i.keyPressed('S')) this.saveMap(); - if (this.scene.i.keyPressed('L')) this.loadMap(this.savedMapData); - } - - setTile(layer: Layer, tileset: number, xx: number | Vec2, yy?: number): boolean { - let x: number, y: number; - if (xx instanceof Vec2) { x = xx.x; y = xx.y; } - else { x = xx; y = yy!; } - - if (x < 0 || y < 0 || x >= this.size.x || y >= this.size.y) return false; - - let oldTileset = this.getTileset(layer, x, y); - if (oldTileset === tileset) return false; - this.setTileset(layer, x, y, tileset); - this.smartTile(x, y); - - return true; - } - - setTileset(key: number, x: number | Vec2, a?: number, b?: number): void { - if (x instanceof Vec2) this.layers[key].tilesets[x.y][x.x] = a!; - else this.layers[key].tilesets[a!][x] = b!; - } - - getTile(key: number, xx: number | Vec2, yy?: number): number { - let x: number, y: number; - if (xx instanceof Vec2) { x = xx.x; y = xx.y; } - else { x = xx; y = yy!; } - - return this.layers[key].tiles[clamp(y, 0, this.size.y - 1)][clamp(x, 0, this.size.x - 1)]; - } - - getTileset(key: number, xx: number | Vec2, yy?: number): number { - let x: number, y: number; - if (xx instanceof Vec2) { x = xx.x; y = xx.y; } - else { x = xx; y = yy!; } - - return this.layers[key].tilesets[clamp(y, 0, this.size.y - 1)][clamp(x, 0, this.size.x - 1)]; - } - - private smartTile(x: number, y: number): void { - for (let i = clamp(x - 1, this.size.x - 1, 0); i <= clamp(x + 1, this.size.x - 1, 0); i++) { - for (let j = clamp(y - 1, this.size.y - 1, 0); j <= clamp(y + 1, this.size.y - 1, 0); j++) { - const solids = this.getTilesetsAt(Layer.wall, i, j).map(i => i !== -1); - - // const bits = SmartTiler.bitsToIndices(solids); - // if (i === x && j === y) console.log(bits, Math.floor(bits / 16), bits % 16); - - const wall = SmartTiler.wall(solids, this.getTile(Layer.wall, i, j)); - if (wall !== -1) this.setTileRaw(Layer.wall, i, j, wall); - - const floor = SmartTiler.floor(solids, this.getTile(Layer.floor, i, j)); - if (floor !== -1) this.setTileRaw(Layer.floor, i, j, floor); - - const overlay = SmartTiler.overlay(this.getTilesetsAt(Layer.overlay, i, j) - .map(t => t === this.getTileset(Layer.overlay, i, j)), this.getTileset(Layer.overlay, i, j)); - if (overlay !== -1) this.setTileRaw(Layer.overlay, i, j, overlay); - } - } - - // this.scene.lighting.tileUpdatedAt(x, y); - } - - private setTileRaw(key: number, x: number | Vec2, a?: number, b?: number, c?: number): void { - if (x instanceof Vec2) { - this.layers[key].tiles[x.y][x.x] = a!; - if (b !== undefined) this.setTileset(key, x, b); - - this.chunks[Math.floor(x.y / MapChunk.CHUNK_SIZE)][Math.floor(x.x / MapChunk.CHUNK_SIZE)] - .dirty(new Vec2(x.x % MapChunk.CHUNK_SIZE, x.y % MapChunk.CHUNK_SIZE)); - } - else { - this.layers[key].tiles[a!][x] = b!; - if (c !== undefined) this.setTileset(key, x, a, c); - - this.chunks[Math.floor(a! / MapChunk.CHUNK_SIZE)][Math.floor(x / MapChunk.CHUNK_SIZE)] - .dirty(new Vec2(x % MapChunk.CHUNK_SIZE, a! % MapChunk.CHUNK_SIZE)); - } - } - - private getTilesetsAt(layer: Layer, x: number, y: number): number[] { - let tilesets: number[] = []; - for (let i = -1; i <= 1; i++) - for (let j = -1; j <= 1; j++) - tilesets.push(this.getTileset(layer, clamp(x + j, 0, this.size.x - 1), clamp(y + i, 0, this.size.y - 1))); - return tilesets; - } - - private registerLayer(key: number, startTile: any = 0, startTileset: number = -1): void { - let layer: {tiles: number[][]; tilesets: number[][]} = { tiles: [], tilesets: [] }; - - for (let i = 0; i < this.size.y; i++) { - layer.tiles[i] = []; - layer.tilesets[i] = []; - for (let j = 0; j < this.size.x; j++) { - let tile = typeof(startTile) === 'number' ? startTile : startTile(); - layer.tiles[i][j] = tile; - layer.tilesets[i][j] = startTileset; - } - } - - this.layers[key] = layer; - } - - private saveMap() { - - let mapData: number[][] = []; - - for (let k = 0; k < 3; k++) { - let tile = 0; - let count = 0; - mapData[k] = []; - - for (let i = 0; i < this.size.x * this.size.y; i++) { - let x = i % this.size.x; - let y = Math.floor(i / this.size.x); - - if (this.getTileset(k, x, y) === tile) count++; - else { - if (i !== 0) { - mapData[k].push(tile); - mapData[k].push(count); - } - tile = this.getTileset(k, x, y); - count = 1; - } - } - } - - this.savedMapData = mapData; - } - - private loadMap(dat: number[][]) { - for (let k = 0; k < 3; k++) { - let offset = 0; - for (let i = 0; i < dat[k].length / 2; i++) { - let tile = dat[k][i * 2]; - let count = dat[k][i * 2 + 1]; - - for (let t = 0; t < count; t++) { - let x = (offset + t) % this.size.x; - let y = Math.floor((offset + t) / this.size.x); - - this.setTile(k, tile, x, y); - } - offset += count; - } - } - } -} diff --git a/app/src/editor/SmartTiler.ts b/app/src/editor/SmartTiler.ts deleted file mode 100755 index 6b0d847..0000000 --- a/app/src/editor/SmartTiler.ts +++ /dev/null @@ -1,68 +0,0 @@ -const wallField = [ - 33, 33, 6, 6, 33, 33, 6, 6, 5, 5, 10, 17, 5, 5, 10, 17, 15, 15, 9, 9, 15, 15, 16, 16, 2, 2, 4, 50, 2, 2, 49, 32, - 33, 33, 6, 6, 33, 33, 6, 6, 5, 5, 10, 17, 5, 5, 10, 17, 15, 15, 9, 9, 15, 15, 16, 16, 2, 2, 4, 50, 2, 2, 49, 32, - 14, 14, 11, 11, 14, 14, 11, 11, 1, 1, 13, 48, 1, 1, 13, 48, 0, 0, 12, 12, 0, 0, 47, 47, 3, 3, 25, 46, 3, 3, 45, 20, - 14, 14, 11, 11, 14, 14, 11, 11, 8, 8, 39, 23, 8, 8, 39, 23, 0, 0, 12, 12, 0, 0, 47, 47, 41, 41, 37, 29, 41, 41, 51, 18, - 33, 33, 6, 6, 33, 33, 6, 6, 5, 5, 10, 17, 5, 5, 10, 17, 15, 15, 9, 9, 15, 15, 16, 16, 2, 2, 4, 50, 2, 2, 49, 32, - 33, 33, 6, 6, 33, 33, 6, 6, 5, 5, 10, 17, 5, 5, 10, 17, 15, 15, 9, 9, 15, 15, 16, 16, 2, 2, 4, 50, 2, 2, 49, 32, - 14, 14, 11, 11, 14, 14, 11, 11, 1, 1, 13, 48, 1, 1, 13, 48, 7, 7, 38, 38, 7, 7, 22, 22, 40, 40, 36, 42, 40, 40, 30, 19, - 14, 14, 11, 11, 14, 14, 11, 11, 8, 8, 39, 23, 8, 8, 39, 23, 7, 7, 38, 38, 7, 7, 22, 22, 31, 31, 21, 27, 31, 31, 28, 54 -]; - -const floorField = [ - 54, 20, 19, 19, 18, 4, 19, 19, 11, 11, 3, 3, 51, 51, 3, 3, 9, 52, 5, 5, 9, 52, 5, 5, 39, 39, 30, 30, 39, 39, 30, 30, - 2, 12, 32, 32, 34, 6, 32, 32, 11, 11, 3, 3, 51, 51, 3, 3, 43, 38, 29, 29, 43, 38, 29, 29, 39, 39, 30, 30, 39, 39, 30, 30, - 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, - 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, - 0, 33, 31, 31, 14, 7, 31, 31, 42, 42, 27, 27, 36, 36, 27, 27, 9, 52, 5, 5, 9, 52, 5, 5, 39, 39, 30, 30, 39, 39, 30, 30, - 22, 15, 28, 28, 16, 37, 28, 28, 42, 42, 27, 27, 36, 36, 27, 27, 43, 38, 29, 29, 43, 38, 29, 29, 39, 39, 30, 30, 39, 39, 30, 30, - 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, - 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49 -]; - -export function bitsToIndices(walls: boolean[]) { - return ( - (+walls[0] << 0) + - (+walls[1] << 1) + - (+walls[2] << 2) + - (+walls[3] << 3) + - (+walls[5] << 4) + - (+walls[6] << 5) + - (+walls[7] << 6) + - (+walls[8] << 7)); -} - -export function wall(walls: boolean[], current: number): number { - if (current === -1) return -1; - const ind = wallField[bitsToIndices(walls)]; - if (ind < 54) return ind; - return 54 + Math.floor(Math.random() * 6); -} - -export function overlay(overlays: boolean[], current: number): number { - if (current === -1) return -1; - const ind = floorField[bitsToIndices(overlays)]; - if (ind < 54) return ind; - return 54 + Math.floor(Math.random() * 6); -} - -export function floor(walls: boolean[], current: number): number { - if (current === -1) return -1; - const ind = floorField[bitsToIndices(walls)]; - if (ind < 54) return ind; - return 54 + Math.floor(Math.random() * 6); -} - -// const l = []; -// for (let i = 0; i < (1 << 8); ++i ) { -// let arr = [ i & 0x001, i & 0x002, i & 0x004, i & 0x008, 0, -// i & 0x010, i & 0x020, i & 0x040, i & 0x080 ].map(a => !!a); -// l.push(overlay(arr, 1)); -// } - -// let lines = []; -// for (let i = 0; i < 16; i++) { -// lines.push(l.slice(i * 16, (i + 1) * 16).map(n => n < 10 ? n + ', ' : n + ',').join(' ')); -// } - -// console.log(lines.join('\n')); diff --git a/app/src/editor/TilesetManager.ts b/app/src/editor/TilesetManager.ts deleted file mode 100755 index 458705c..0000000 --- a/app/src/editor/TilesetManager.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type MapScene from './scene/MapScene'; - -import Layer from './util/Layer'; -import { Asset } from './util/Asset'; - -export default class TilesetManager { - scene: MapScene; - - wallLocations: {[index: number]: { res: number; ind: number; identifier: string }} = {}; - groundLocations: {[index: number]: { res: number; ind: number; identifier: string }} = {}; - overlayLocations: {[index: number]: { res: number; ind: number; identifier: string }} = {}; - indexes: {[tileset_key: string]: number} = {}; - - private currentWallInd: number = 0; - private currentGroundInd: number = 0; - private currentOverlayInd: number = 0; - - constructor(scene: MapScene) { - this.scene = scene; - } - - init(assets: Asset[]) { - for (let tileset of assets.filter(a => a.type === 'wall' )) this.addTileset(tileset.identifier, Layer.wall); - for (let tileset of assets.filter(a => a.type === 'ground' )) this.addTileset(tileset.identifier, Layer.floor); - // for (let tileset of assets.filter(a => a.type === 'overlay')) this.addTileset(tileset.key, Layer.overlay); - } - - private addTileset(identifier: string, layer: Layer): void { - let res = this.scene.textures.get(identifier).getSourceImage(0).width / 9; - let ind = (layer === Layer.wall ? this.currentWallInd : layer === Layer.floor ? this.currentGroundInd : this.currentOverlayInd); - this[layer === Layer.wall ? 'wallLocations' : layer === Layer.floor ? - 'groundLocations' : 'overlayLocations'][ind] = { res, ind, identifier }; - this.indexes[identifier] = ind; - - if (layer === Layer.wall) this.currentWallInd++; - else if (layer === Layer.floor) this.currentGroundInd++; - else this.currentOverlayInd++; - } -} diff --git a/app/src/editor/TilesetPatcher.ts b/app/src/editor/TilesetPatcher.ts index add76fa..b882805 100644 --- a/app/src/editor/TilesetPatcher.ts +++ b/app/src/editor/TilesetPatcher.ts @@ -5,154 +5,160 @@ import { Vec2, Vec4 } from './util/Vec'; export default class TilesetPatcher { constructor(private scene: Phaser.Scene) {} - patch(tileset_key: string, tile_size: number): void { - const s = Date.now(); + async patch(tileset_key: string, tile_size: number): Promise { + return new Promise(resolve => { + const s = Date.now(); - const canvas = new Phaser.GameObjects.RenderTexture(this.scene, 0, 0, 10 * tile_size, 5 * tile_size); - canvas.draw(tileset_key); + const canvas = new Phaser.GameObjects.RenderTexture(this.scene, 0, 0, 10 * tile_size, 5 * tile_size); + canvas.draw(tileset_key); - let part: Phaser.GameObjects.Sprite | Phaser.GameObjects.RenderTexture - = new Phaser.GameObjects.Sprite(this.scene, 0, 0, tileset_key); - part.setOrigin(0, 0); + let part: Phaser.GameObjects.Sprite | Phaser.GameObjects.RenderTexture + = new Phaser.GameObjects.Sprite(this.scene, 0, 0, tileset_key, '__BASE'); + part.setOrigin(0, 0); - function draw(source: Vec4, dest: Vec2) { - part.setCrop(source.x * tile_size, source.y * tile_size, (source.z - source.x) * tile_size, (source.w - source.y) * tile_size); - part.setPosition((dest.x - source.x) * tile_size, (dest.y - source.y) * tile_size); - canvas.draw(part); - } + function draw(source: Vec4, dest: Vec2) { + part.setCrop(source.x * tile_size, source.y * tile_size, (source.z - source.x) * tile_size, (source.w - source.y) * tile_size); + part.setPosition((dest.x - source.x) * tile_size, (dest.y - source.y) * tile_size); + canvas.draw(part); + } - // End Pieces and Walls + // End Pieces and Walls - draw(new Vec4(2, 0, 3, 0.5), new Vec2(7, 0)); - draw(new Vec4(2, 1.5, 3, 2), new Vec2(7, 0.5)); + draw(new Vec4(2, 0, 3, 0.5), new Vec2(7, 0)); + draw(new Vec4(2, 1.5, 3, 2), new Vec2(7, 0.5)); - draw(new Vec4(2, 0, 2.5, 1), new Vec2(8, 0)); - draw(new Vec4(3.5, 0, 4, 1), new Vec2(8.5, 0)); + draw(new Vec4(2, 0, 2.5, 1), new Vec2(8, 0)); + draw(new Vec4(3.5, 0, 4, 1), new Vec2(8.5, 0)); - draw(new Vec4(1, 0, 2, 0.5), new Vec2(9, 0)); - draw(new Vec4(0, 1.5, 1, 2), new Vec2(9, 0.5)); + draw(new Vec4(1, 0, 2, 0.5), new Vec2(9, 0)); + draw(new Vec4(0, 1.5, 1, 2), new Vec2(9, 0.5)); - draw(new Vec4(2, 1, 2.5, 2), new Vec2(7, 1)); - draw(new Vec4(3.5, 1, 4, 2), new Vec2(7.5, 1)); + draw(new Vec4(2, 1, 2.5, 2), new Vec2(7, 1)); + draw(new Vec4(3.5, 1, 4, 2), new Vec2(7.5, 1)); - draw(new Vec4(3, 0, 4, 0.5), new Vec2(8, 1)); - draw(new Vec4(3, 1.5, 4, 2), new Vec2(8, 1.5)); + draw(new Vec4(3, 0, 4, 0.5), new Vec2(8, 1)); + draw(new Vec4(3, 1.5, 4, 2), new Vec2(8, 1.5)); - draw(new Vec4(0, 0, 0.5, 1), new Vec2(9, 1)); - draw(new Vec4(1.5, 1, 2, 2), new Vec2(9.5, 1)); + draw(new Vec4(0, 0, 0.5, 1), new Vec2(9, 1)); + draw(new Vec4(1.5, 1, 2, 2), new Vec2(9.5, 1)); - // Advanced Corners (Orange) + // Advanced Corners (Orange) - draw(new Vec4(6, 1, 7, 1.5), new Vec2(0, 2)); - draw(new Vec4(6, 0.5, 7, 1), new Vec2(0, 2.5)); + draw(new Vec4(6, 1, 7, 1.5), new Vec2(0, 2)); + draw(new Vec4(6, 0.5, 7, 1), new Vec2(0, 2.5)); - draw(new Vec4(6, 1, 6.5, 2), new Vec2(1, 2)); - draw(new Vec4(5.5, 1, 6, 2), new Vec2(1.5, 2)); + draw(new Vec4(6, 1, 6.5, 2), new Vec2(1, 2)); + draw(new Vec4(5.5, 1, 6, 2), new Vec2(1.5, 2)); - draw(new Vec4(5, 0, 6, 1), new Vec2(2, 2)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(2, 2)); + draw(new Vec4(5, 0, 6, 1), new Vec2(2, 2)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(2, 2)); - draw(new Vec4(6, 0, 6.5, 1), new Vec2(0, 3)); - draw(new Vec4(5.5, 0, 6, 1), new Vec2(0.5, 3)); + draw(new Vec4(6, 0, 6.5, 1), new Vec2(0, 3)); + draw(new Vec4(5.5, 0, 6, 1), new Vec2(0.5, 3)); - draw(new Vec4(5, 1, 6, 1.5), new Vec2(1, 3)); - draw(new Vec4(5, 0.5, 6, 1), new Vec2(1, 3.5)); + draw(new Vec4(5, 1, 6, 1.5), new Vec2(1, 3)); + draw(new Vec4(5, 0.5, 6, 1), new Vec2(1, 3.5)); - draw(new Vec4(6, 0, 7, 1), new Vec2(2, 3)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(2.5, 3)); + draw(new Vec4(6, 0, 7, 1), new Vec2(2, 3)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(2.5, 3)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(0.5, 4)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(0, 4)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(0, 4.5)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(0.5, 4.5)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(0.5, 4)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(0, 4)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(0, 4.5)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(0.5, 4.5)); - /** - * So here's why this is horrible: - * - Phaser doesn't let you copy a region of a RenderTexture to the same RenderTexture. - * - Creating a new RenderTexture and drawing the original directly onto it flips it upside down for some reason? - * - Scaling that upside-down RenderTexture to upside-right fucks with the draw() function's positioning. - * - * In other words, yeah, it's fucked man. The janky solution below is the only way I've found to make it work, - * so if future-Auri is looking at this and making a snarky comment to her friends about how she could do it - * *so much better*, please, just don't. Don't do it. - */ + /** + * So here's why this is horrible: + * - Phaser doesn't let you copy a region of a RenderTexture to the same RenderTexture. + * - Creating a new RenderTexture and drawing the original directly onto it flips it upside down for some reason? + * - Scaling that upside-down RenderTexture to upside-right fucks with the draw() function's positioning. + * + * In other words, yeah, it's fucked man. The janky solution below is the only way I've found to make it work, + * so if future-Auri is looking at this and making a snarky comment to her friends about how she could do it + * *so much better*, please, just don't. Don't do it. + */ - part.setCrop(); - part = new Phaser.GameObjects.RenderTexture(this.scene, 0, 0, canvas.width, canvas.height); - part.setOrigin(0, 0); - const temp = new Phaser.GameObjects.Sprite(this.scene, 0, 0, canvas.texture); - temp.setOrigin(0, 0); - part.draw(temp); + part.setCrop(); + part = new Phaser.GameObjects.RenderTexture(this.scene, 0, 0, canvas.width, canvas.height); + part.setOrigin(0, 0); + const temp = new Phaser.GameObjects.Sprite(this.scene, 0, 0, canvas.texture); + temp.setOrigin(0, 0); + part.draw(temp); - // Derived Forms (Pink) + // Derived Forms (Pink) - draw(new Vec4(2, 0, 3, 1), new Vec2(3, 2)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(3.5, 2.5)); + draw(new Vec4(2, 0, 3, 1), new Vec2(3, 2)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(3.5, 2.5)); - draw(new Vec4(3, 0, 4, 1), new Vec2(4, 2)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(4, 2.5)); + draw(new Vec4(3, 0, 4, 1), new Vec2(4, 2)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(4, 2.5)); - draw(new Vec4(1, 0, 2, 1), new Vec2(5, 2)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(5.5, 2.5)); + draw(new Vec4(1, 0, 2, 1), new Vec2(5, 2)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(5.5, 2.5)); - draw(new Vec4(1, 0, 2, 1), new Vec2(6, 2)); - draw(new Vec4(0, 3.5, 1, 4), new Vec2(6, 2.5)); + draw(new Vec4(1, 0, 2, 1), new Vec2(6, 2)); + draw(new Vec4(0, 3.5, 1, 4), new Vec2(6, 2.5)); - draw(new Vec4(1, 0, 2, 1), new Vec2(7, 2)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(7, 2.5)); + draw(new Vec4(1, 0, 2, 1), new Vec2(7, 2)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(7, 2.5)); - draw(new Vec4(0, 0, 1, 1), new Vec2(8, 2)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(8.5, 2.5)); + draw(new Vec4(0, 0, 1, 1), new Vec2(8, 2)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(8.5, 2.5)); - draw(new Vec4(1, 1, 2, 2), new Vec2(9, 2)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(9, 2.5)); + draw(new Vec4(1, 1, 2, 2), new Vec2(9, 2)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(9, 2.5)); - draw(new Vec4(2, 1, 3, 2), new Vec2(3, 3)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(3.5, 3)); + draw(new Vec4(2, 1, 3, 2), new Vec2(3, 3)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(3.5, 3)); - draw(new Vec4(3, 1, 4, 2), new Vec2(4, 3)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(4, 3)); + draw(new Vec4(3, 1, 4, 2), new Vec2(4, 3)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(4, 3)); - draw(new Vec4(0, 1, 1, 2), new Vec2(5, 3)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(5.5, 3)); + draw(new Vec4(0, 1, 1, 2), new Vec2(5, 3)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(5.5, 3)); - draw(new Vec4(0, 1, 1, 2), new Vec2(6, 3)); - draw(new Vec4(1, 2, 2, 2.5), new Vec2(6, 3)); + draw(new Vec4(0, 1, 1, 2), new Vec2(6, 3)); + draw(new Vec4(1, 2, 2, 2.5), new Vec2(6, 3)); - draw(new Vec4(0, 1, 1, 2), new Vec2(7, 3)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(7, 3)); + draw(new Vec4(0, 1, 1, 2), new Vec2(7, 3)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(7, 3)); - draw(new Vec4(0, 0, 1, 1), new Vec2(8, 3)); - draw(new Vec4(1.5, 3, 2, 4), new Vec2(8.5, 3)); + draw(new Vec4(0, 0, 1, 1), new Vec2(8, 3)); + draw(new Vec4(1.5, 3, 2, 4), new Vec2(8.5, 3)); - draw(new Vec4(1, 1, 2, 2), new Vec2(9, 3)); - draw(new Vec4(0, 2, 0.5, 3), new Vec2(9, 3)); + draw(new Vec4(1, 1, 2, 2), new Vec2(9, 3)); + draw(new Vec4(0, 2, 0.5, 3), new Vec2(9, 3)); - draw(new Vec4(0, 2, 1, 3), new Vec2(4, 4)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(4.5, 4)); + draw(new Vec4(0, 2, 1, 3), new Vec2(4, 4)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(4.5, 4)); - draw(new Vec4(1, 3, 2, 4), new Vec2(5, 4)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(5, 4)); + draw(new Vec4(1, 3, 2, 4), new Vec2(5, 4)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(5, 4)); - draw(new Vec4(0, 2, 1, 3), new Vec2(6, 4)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(6.5, 4.5)); + draw(new Vec4(0, 2, 1, 3), new Vec2(6, 4)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(6.5, 4.5)); - draw(new Vec4(1, 3, 2, 4), new Vec2(7, 4)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(7, 4.5)); + draw(new Vec4(1, 3, 2, 4), new Vec2(7, 4)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(7, 4.5)); - draw(new Vec4(0, 0, 1, 1), new Vec2(8, 4)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(8.5, 4)); + draw(new Vec4(0, 0, 1, 1), new Vec2(8, 4)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(8.5, 4)); - draw(new Vec4(1, 1, 2, 2), new Vec2(9, 4)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(9, 4)); + draw(new Vec4(1, 1, 2, 2), new Vec2(9, 4)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(9, 4)); - this.scene.textures.removeKey(tileset_key); - canvas.saveTexture(tileset_key); + canvas.snapshot((img: any) => { + this.scene.textures.removeKey(tileset_key); + this.scene.textures.addSpriteSheet(tileset_key, img, { frameWidth: tile_size, frameHeight: tile_size }); - console.log(`Patched '${tileset_key}' in ${Date.now() - s} ms.`); + console.log(`Patched '${tileset_key}' in ${Date.now() - s} ms.`); + + resolve(); + }); + }); } } diff --git a/app/src/editor/Token.ts b/app/src/editor/Token.ts index 8f0ecb2..495747d 100755 --- a/app/src/editor/Token.ts +++ b/app/src/editor/Token.ts @@ -1,4 +1,3 @@ -import { Vec2 } from './util/Vec'; import { generateId } from './util/Helpers'; export interface SerializedToken { @@ -10,8 +9,8 @@ export interface SerializedToken { } export default class Token extends Phaser.GameObjects.Container { - sprite: Phaser.GameObjects.Sprite | null = null; - shadow: Phaser.GameObjects.Sprite | null = null; + sprite: Phaser.GameObjects.Sprite; + shadow: Phaser.GameObjects.Sprite; currentFrame: number = 0; @@ -25,6 +24,20 @@ export default class Token extends Phaser.GameObjects.Container { constructor(scene: Phaser.Scene, x: number, y: number, tex: string) { super(scene, x, y); + + this.shadow = new Phaser.GameObjects.Sprite(this.scene, 0, 0, ''); + this.shadow.setOrigin(0, 0); + this.shadow.setScale(1 / this.shadow.width, 0.25 / this.shadow.height); + this.shadow.setTint(0x000000); + this.shadow.setAlpha(0.1, 0.1, 0.3, 0.3); + this.list.push(this.shadow); + + this.sprite = new Phaser.GameObjects.Sprite(this.scene, 0, 0, ''); + this.sprite.setOrigin(0, 0); + this.sprite.setScale(1 / this.sprite.width, 1 / this.sprite.height); + this.setPosition(this.x, this.y); + this.list.push(this.sprite); + this.setTexture(tex); this.uuid = generateId(32); @@ -37,28 +50,16 @@ export default class Token extends Phaser.GameObjects.Container { } setTexture(tex: string) { - if (this.shadow != null) this.shadow.setTexture(tex); - else { - this.shadow = new Phaser.GameObjects.Sprite(this.scene, -4, -4, tex); - this.shadow.setOrigin(0, 0); - this.shadow.setScale(4, 1); - this.shadow.setTint(0x000000); - this.shadow.setAlpha(0.1, 0.1, 0.3, 0.3); - this.list.push(this.shadow); - } + this.shadow.setTexture(tex); + this.sprite.setTexture(tex); - this.width = this.shadow.width * 4; - this.height = this.shadow.height * 4; - this.shadow.y = this.height - 26; + this.shadow.setScale(1 / this.shadow.width, 0.25 / this.shadow.height); + this.sprite.setScale(1 / this.sprite.width, 1 / this.sprite.height); - if (this.sprite != null) this.sprite.setTexture(tex); - else { - this.sprite = new Phaser.GameObjects.Sprite(this.scene, -4, -4, tex); - this.sprite.setOrigin(0, 0); - this.sprite.setScale(4, 4); - this.setPosition(this.x / 4, this.y / 4); - this.list.push(this.sprite); - } + this.shadow.y = this.sprite.displayHeight - this.shadow.displayHeight - 0.025; + + this.width = this.sprite.displayWidth; + this.height = this.sprite.displayHeight; } setFrame(frame: number): void { @@ -83,11 +84,14 @@ export default class Token extends Phaser.GameObjects.Container { this.hovered = hovered; if (!hovered && !this.selected) { - this.sprite.resetPipeline(); + // this.sprite.resetPipeline(); + this.sprite.setTint(0xffffff); return; } - if (!this.selected) this.sprite.setPipeline('brighten'); + if (!this.selected) this.sprite.setTint(0x999999); + + // if (!this.selected) this.sprite.setPipeline('brighten'); } setSelected(selected: boolean) { @@ -96,33 +100,27 @@ export default class Token extends Phaser.GameObjects.Container { this.selected = selected; if (!selected) { - if (!this.hovered) this.sprite.resetPipeline(); - else this.sprite.setPipeline('brighten'); + // if (!this.hovered) this.sprite.resetPipeline(); + // else this.sprite.setPipeline('brighten'); + if (!this.hovered) this.sprite.setTint(0xffffff); + else this.sprite.setTint(0x999999); } else { - this.sprite.setPipeline('outline'); + // this.sprite.setPipeline('outline'); + this.sprite.setTint(0x000000); // @ts-ignore // this.sprite.pipeline.setFloat1('tex_size', this.sprite.texture.source[0].width); } } - setPosition(x?: number, y?: number, z?: number, w?: number): this { - Phaser.GameObjects.Container.prototype.setPosition.call(this, (x || 0) * 4, (y || 0) * 4, z, w); - return this; - } - - getPosition(): Vec2 { - return new Vec2(this.x / 4, this.y / 4); - } - // Serialization Methods serialize(): string { return JSON.stringify(({ uuid: this.uuid, sprite: this.sprite ? this.sprite.texture.key : '', frame: this.currentFrame, - x: this.x / 4, - y: this.y / 4 + x: this.x, + y: this.y } as SerializedToken)); } diff --git a/app/src/editor/TokenMode.ts b/app/src/editor/TokenMode.ts index 69f645d..00655cc 100755 --- a/app/src/editor/TokenMode.ts +++ b/app/src/editor/TokenMode.ts @@ -31,7 +31,7 @@ export default class TokenMode { init() { // Create cursor hover sprite this.cursor = this.scene.add.sprite(0, 0, 'cursor'); - this.cursor.setScale(4, 4); + this.cursor.setScale(1 / 16, 1 / 16); this.cursor.setDepth(1000); this.cursor.setOrigin(0, 0); this.cursor.setVisible(false); @@ -58,15 +58,15 @@ export default class TokenMode { update() { this.active = true; - let selectedTilePos = new Vec2(Math.floor(this.scene.view.cursorWorld.x / 64), Math.floor(this.scene.view.cursorWorld.y / 64)); + let selectedTilePos = new Vec2(Math.floor(this.scene.view.cursorWorld.x), Math.floor(this.scene.view.cursorWorld.y)); if (this.movingTokens) this.moving(); if (!this.movingTokens) this.selecting(); if (this.selectedTokens.length > 0 && !this.movingTokens) this.tokenMoveControls(); - this.tokenPreview!.setPosition(selectedTilePos.x * 16, selectedTilePos.y * 16); - this.cursor!.setPosition(selectedTilePos.x * 64, selectedTilePos.y * 64); + this.tokenPreview!.setPosition(selectedTilePos.x, selectedTilePos.y); + this.cursor!.setPosition(selectedTilePos.x, selectedTilePos.y); if (this.selectedTokenType === '') this.tokenPreview!.setVisible(false); if (this.selectedTokenType !== '') this.cursor!.setVisible(false); @@ -80,7 +80,7 @@ export default class TokenMode { updateRectangleSelect() { const cursor = this.scene.view.cursorWorld; - let selectedTilePos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + let selectedTilePos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); this.primitives.forEach((v) => v.destroy()); this.primitives = []; @@ -97,7 +97,6 @@ export default class TokenMode { this.primitives.forEach((v) => { v.setOrigin(0, 0); - v.setScale(64, 64); v.setLineWidth(0.03); v.setDepth(300); }); @@ -109,14 +108,14 @@ export default class TokenMode { this.movingTokens = true; const cursor = this.scene.view.cursorWorld; - this.tileGrabPos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + this.tileGrabPos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); this.prevSerialized = []; this.selectedTokens.forEach(t => this.prevSerialized.push(t.serialize())); } createToken(): Token { - let token = new Token(this.scene, Math.floor(this.scene.view.cursorWorld.x / 4 / 16) * 16, - Math.floor(this.scene.view.cursorWorld.y / 4 / 16) * 16, this.selectedTokenType); + let token = new Token(this.scene, Math.floor(this.scene.view.cursorWorld.x), + Math.floor(this.scene.view.cursorWorld.y), this.selectedTokenType); this.scene.add.existing(token); this.scene.tokens.push(token); @@ -150,16 +149,16 @@ export default class TokenMode { private tokenMoveControls(): void { if (this.scene.i.keyPressed('UP')) { - this.moveToken(0, -16, 2); + this.moveToken(0, -1, 2); } if (this.scene.i.keyPressed('LEFT')) { - this.moveToken(-16, 0, 1); + this.moveToken(-1, 0, 1); } if (this.scene.i.keyPressed('DOWN')) { - this.moveToken(0, 16, 0); + this.moveToken(0, 1, 0); } if (this.scene.i.keyPressed('RIGHT')) { - this.moveToken(16, 0, 3); + this.moveToken(1, 0, 3); } } @@ -175,8 +174,8 @@ export default class TokenMode { let prevSerialized: string[] = []; this.selectedTokens.forEach((token) => { prevSerialized.push(token.serialize()); - token.x += x * 4; - token.y += y * 4; + token.x += x; + token.y += y; token.setFrame(frame); }); @@ -201,7 +200,7 @@ export default class TokenMode { for (let i = this.scene.tokens.length - 1; i >= 0; i--) { let token = this.scene.tokens[i]; - if (cursor.x >= token.x && cursor.y >= token.y && cursor.x <= token.x + token.width - 8 && cursor.y <= token.y + token.height - 8) { + if (cursor.x >= token.x && cursor.y >= token.y && cursor.x <= token.x + token.width && cursor.y <= token.y + token.height) { this.hoveredToken = token; break; } @@ -237,7 +236,7 @@ export default class TokenMode { } // Start a rectangle selection else { - this.startTilePos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + this.startTilePos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); } } // Selecting existing token to move @@ -251,7 +250,7 @@ export default class TokenMode { } else { this.selectedTokens.forEach(t => t.setSelected(false)); - this.selectedTokens = [this.hoveredToken]; + this.selectedTokens = [ this.hoveredToken ]; this.clickedLastFrame = true; clickedAddedThisFrame = true; this.hoveredToken.setSelected(true); @@ -266,7 +265,7 @@ export default class TokenMode { this.primitives.forEach((v) => v.destroy()); this.primitives = []; - let selectedTilePos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + let selectedTilePos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); let a = new Vec2(Math.min(this.startTilePos.x, selectedTilePos.x), Math.min(this.startTilePos.y, selectedTilePos.y)); let b = new Vec2(Math.max(this.startTilePos.x, selectedTilePos.x), Math.max(this.startTilePos.y, selectedTilePos.y)); @@ -278,7 +277,7 @@ export default class TokenMode { } for (let token of this.scene.tokens) { - let tokenTilePos = new Vec2(Math.floor(token.x / 64), Math.floor(token.y / 64)); + let tokenTilePos = new Vec2(Math.floor(token.x), Math.floor(token.y)); if (tokenTilePos.x >= a.x && tokenTilePos.y >= a.y && tokenTilePos.x <= b.x && tokenTilePos.y <= b.y) { let selected = this.scene.i.keyDown('CTRL') ? !this.selectedIncludes(token) : true; @@ -360,13 +359,13 @@ export default class TokenMode { return; } - let newTileGrabPos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + let newTileGrabPos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); let offset = new Vec2(newTileGrabPos.x - this.tileGrabPos.x, newTileGrabPos.y - this.tileGrabPos.y); if (offset.x === 0 && offset.y === 0) return; this.movedTokens = true; this.tileGrabPos = newTileGrabPos; - this.selectedTokens.forEach(tkn => tkn.setPosition(tkn.x / 4 + offset.x * 16, tkn.y / 4 + offset.y * 16)); + this.selectedTokens.forEach(tkn => tkn.setPosition(tkn.x + offset.x, tkn.y + offset.y)); } } } diff --git a/app/src/editor/WorldView.ts b/app/src/editor/WorldView.ts index 623af05..aa007d9 100755 --- a/app/src/editor/WorldView.ts +++ b/app/src/editor/WorldView.ts @@ -12,8 +12,8 @@ export default class WorldView { cursorWorld: Vec2 = new Vec2(); lastCursorWorld: Vec2 = new Vec2(); - zoomLevels: number[] = [10, 17, 25, 33, 40, 50, 60, 67, 75, 80, 90, 100, 110, 125, 150, 175, 200, 250, 300, 400, 500]; - zoomLevel = 11; + zoomLevels: number[] = [ 5, 6, 8, 10, 15, 20, 25, 33, 40, 50, 60, 75, 100, 125, 150, 200, 300 ]; + zoomLevel = 9; constructor(scene: MapScene) { this.scene = scene; @@ -22,11 +22,26 @@ export default class WorldView { init(): void { this.camera = this.scene.cameras.main; this.camera.setBackgroundColor('#090d24'); + this.camera.setZoom(this.zoomLevels[this.zoomLevel]); + this.camera.setScroll(-this.camera.width / 2.2, -this.camera.height / 2.2); this.scene.i.bindScrollEvent((delta: number) => { if (!this.scene.token.movingTokens && !this.scene.ui.uiActive) { + + const lastZoom = this.zoomLevels[this.zoomLevel]; this.zoomLevel = clamp(this.zoomLevel + delta, 0, this.zoomLevels.length - 1); - this.camera!.setZoom(this.zoomLevels[this.zoomLevel] / 100); + const zoom = this.zoomLevels[this.zoomLevel]; + + this.scene.tweens.add({ + targets: this.camera, + zoom: { from: lastZoom, to: zoom }, + ease: 'Cubic', + duration: 150, + repeat: 0 + }); + + // this.scene.tweens.add({ targets: this.camera!, duration: 150, props: { zoom: this.zoomLevels[this.zoomLevel / 100]}}); + // this.camera!.setZoom(this.zoomLevels[this.zoomLevel] / 100); } }); } @@ -47,8 +62,8 @@ export default class WorldView { private pan() { if (this.scene.input.activePointer.middleButtonDown()) { - this.camera!.scrollX += Math.round((this.lastCursorScreen.x - this.cursorScreen.x) / this.camera!.zoom); - this.camera!.scrollY += Math.round((this.lastCursorScreen.y - this.cursorScreen.y) / this.camera!.zoom); + this.camera!.scrollX += (this.lastCursorScreen.x - this.cursorScreen.x) / this.camera!.zoom; + this.camera!.scrollY += (this.lastCursorScreen.y - this.cursorScreen.y) / this.camera!.zoom; } } } diff --git a/app/src/editor/history/HistoryElement.ts b/app/src/editor/history/HistoryElement.ts index 1852064..607c4ff 100755 --- a/app/src/editor/history/HistoryElement.ts +++ b/app/src/editor/history/HistoryElement.ts @@ -1,8 +1,8 @@ import Token from '../Token'; import type MapScene from '../scene/MapScene'; -import Layer from '../util/Layer'; import { Vec2 } from '../util/Vec'; +import { Layer } from '../util/Layer'; export default class HistoryElement { scene: MapScene; @@ -19,7 +19,7 @@ export default class HistoryElement { console.log('Undo', this.type); if (this.type === 'tile') { for (let tile of this.data as {pos: Vec2; layer: Layer; lastTile: number; tile: number}[]) { - this.scene.map.setTile(tile.layer, tile.lastTile, tile.pos.x, tile.pos.y); + this.scene.map.activeLayer.setTile(tile.layer, tile.lastTile, tile.pos.x, tile.pos.y); this.scene.lighting.tileUpdatedAt(tile.pos.x, tile.pos.y); } } @@ -69,7 +69,7 @@ export default class HistoryElement { console.log('Redo', this.type); if (this.type === 'tile') { for (let tile of this.data as {pos: Vec2; layer: Layer; lastTile: number; tile: number}[]) { - this.scene.map.setTile(tile.layer, tile.tile, tile.pos.x, tile.pos.y); + this.scene.map.activeLayer.setTile(tile.layer, tile.tile, tile.pos.x, tile.pos.y); this.scene.lighting.tileUpdatedAt(tile.pos.x, tile.pos.y); } } diff --git a/app/src/editor/interface/components/UITileSidebar.ts b/app/src/editor/interface/components/UITileSidebar.ts index d0f42cb..840c03a 100755 --- a/app/src/editor/interface/components/UITileSidebar.ts +++ b/app/src/editor/interface/components/UITileSidebar.ts @@ -1,14 +1,13 @@ import UISidebar from './UISidebar'; import type MapScene from '../../scene/MapScene'; -import Layer from '../../util/Layer'; import { Asset } from '../../util/Asset'; export default class UITileSidebar extends UISidebar { walls: string[] = []; grounds: string[] = []; - overlays: string[] = []; + details: string[] = []; constructor(scene: MapScene, x: number, y: number, assets: Asset[]) { super(scene, x, y); @@ -30,18 +29,18 @@ export default class UITileSidebar extends UISidebar { this.list.push(add_ground); this.sprites.push(add_ground); - for (let tileset of assets.filter((a) => a.type === 'ground')) + for (let tileset of assets.filter((a) => a.type === 'floor')) this.addGround(tileset.identifier); - let add_overlay = new Phaser.GameObjects.Sprite(this.scene, 9 + x * 21 * 3, 9 + 9 * 21 * 3, 'ui_sidebar_browse'); - add_overlay.setName('add_overlay'); - add_overlay.setScale(3); - add_overlay.setOrigin(0, 0); - this.list.push(add_overlay); - this.sprites.push(add_overlay); + let add_detail = new Phaser.GameObjects.Sprite(this.scene, 9 + x * 21 * 3, 9 + 9 * 21 * 3, 'ui_sidebar_browse'); + add_detail.setName('add_detail'); + add_detail.setScale(3); + add_detail.setOrigin(0, 0); + this.list.push(add_detail); + this.sprites.push(add_detail); - for (let tileset of assets.filter((a) => a.type === 'ground')) - this.addOverlay(tileset.identifier); + for (let tileset of assets.filter((a) => a.type === 'detail')) + this.addDetail(tileset.identifier); for (let i = 0; i < 12; i++) { if (i % 4 !== 0) this.backgrounds[i].setFrame(0); @@ -50,21 +49,21 @@ export default class UITileSidebar extends UISidebar { elemClick(x: number, y: number): void { if (y < 4) { - this.scene.architect.activeTileset = this.scene.map.manager.indexes[this.walls[x + y * 3]]; - this.scene.architect.activeLayer = Layer.wall; + this.scene.architect.activeTileset = this.scene.map.tileStore.indices[this.walls[x + y * 3]]; + this.scene.architect.activeLayer = 'wall'; } else if (y < 8) { - this.scene.architect.activeTileset = this.scene.map.manager.indexes[this.grounds[x + (y - 4) * 3]]; - this.scene.architect.activeLayer = Layer.floor; + this.scene.architect.activeTileset = this.scene.map.tileStore.indices[this.grounds[x + (y - 4) * 3]]; + this.scene.architect.activeLayer = 'floor'; } else { - this.scene.architect.activeTileset = this.scene.map.manager.indexes[this.overlays[x + (y - 8) * 3]]; - this.scene.architect.activeLayer = Layer.overlay; + this.scene.architect.activeTileset = this.scene.map.tileStore.indices[this.details[x + (y - 8) * 3]]; + this.scene.architect.activeLayer = 'detail'; } } private addWall(tileset: string): void { - this.addTilesetSprite(tileset, this.walls.length % 3, Math.floor(this.walls.length / 3) + 1, 17); + this.addTilesetSprite(tileset, this.walls.length % 3, Math.floor(this.walls.length / 3) + 1, 13); (this.getByName('add_wall') as Phaser.GameObjects.Sprite).x = 9 + ((this.walls.length + 1) % 3 * 21 * 3); (this.getByName('add_wall') as Phaser.GameObjects.Sprite).y = 9 + (Math.floor((this.walls.length + 1) / 3 + 1) * 21 * 3); this.walls.push(tileset); @@ -77,11 +76,11 @@ export default class UITileSidebar extends UISidebar { this.grounds.push(tileset); } - private addOverlay(tileset: string): void { - this.addTilesetSprite(tileset, this.overlays.length % 3, Math.floor(this.overlays.length / 3) + 9, 33); - (this.getByName('add_overlay') as Phaser.GameObjects.Sprite).x = 9 + ((this.overlays.length + 1) % 3 * 21 * 3); - (this.getByName('add_overlay') as Phaser.GameObjects.Sprite).y = 9 + (Math.floor((this.overlays.length + 1) / 3 + 9) * 21 * 3); - this.overlays.push(tileset); + private addDetail(tileset: string): void { + this.addTilesetSprite(tileset, this.details.length % 3, Math.floor(this.details.length / 3) + 9, 33); + (this.getByName('add_detail') as Phaser.GameObjects.Sprite).x = 9 + ((this.details.length + 1) % 3 * 21 * 3); + (this.getByName('add_detail') as Phaser.GameObjects.Sprite).y = 9 + (Math.floor((this.details.length + 1) / 3 + 9) * 21 * 3); + this.details.push(tileset); } private addTilesetSprite(key: string, x: number, y: number, frame: number) { diff --git a/app/src/editor/interface/components/UITokenSidebar.ts b/app/src/editor/interface/components/UITokenSidebar.ts index 5148a3c..ee7c8fc 100755 --- a/app/src/editor/interface/components/UITokenSidebar.ts +++ b/app/src/editor/interface/components/UITokenSidebar.ts @@ -74,7 +74,7 @@ export default class UITokenSidebar extends UISidebar { let token = new Token(this.scene, 0, 0, sprite); Phaser.GameObjects.Sprite.prototype.setPosition.call(token, 12 + x * 21 * 3, 12 + y * 21 * 3); - token.setScale(3 / 4); + token.setScale(3); this.sprites.push(token); this.list.push(token); diff --git a/app/src/editor/lighting/LightSource.ts b/app/src/editor/lighting/LightSource.ts index bf78298..6b5bea0 100755 --- a/app/src/editor/lighting/LightSource.ts +++ b/app/src/editor/lighting/LightSource.ts @@ -1,6 +1,5 @@ import type Lighting from './Lighting'; -import Layer from '../util/Layer'; import { Vec2 } from '../util/Vec'; export default class LightSource { @@ -36,7 +35,7 @@ export default class LightSource { let dir = new Vec2(Math.cos(i * 1.25 * (Math.PI / 180)) / 32, Math.sin(i * 1.25 * (Math.PI / 180)) / 32); let dist = 0; - while (this.light.scene.map.getTileset(Layer.wall, Math.floor(start.x + ray.x), Math.floor(start.y + ray.y)) === -1 && + while (this.light.scene.map.activeLayer.getTile('wall', Math.floor(start.x + ray.x), Math.floor(start.y + ray.y)) === -1 && (dist = Math.sqrt(Math.pow(ray.x, 2) + Math.pow(ray.y, 2))) < this.radius / 16) { ray.x += dir.x; diff --git a/app/src/editor/lighting/Lighting.ts b/app/src/editor/lighting/Lighting.ts index f2541c3..8b8a40b 100755 --- a/app/src/editor/lighting/Lighting.ts +++ b/app/src/editor/lighting/Lighting.ts @@ -1,4 +1,4 @@ -import MapChunk from '../MapChunk'; +import { CHUNK_SIZE } from '../map/MapChunk'; import type MapScene from '../scene/MapScene'; import LightChunk from './LightChunk'; @@ -23,9 +23,9 @@ export default class Lighting { init(size: Vec2) { this.size = size; - for (let i = 0; i < Math.ceil(size.y / (MapChunk.CHUNK_SIZE * 2)); i++) { + for (let i = 0; i < Math.ceil(size.y / (CHUNK_SIZE * 2)); i++) { this.chunks[i] = []; - for (let j = 0; j < Math.ceil(size.x / (MapChunk.CHUNK_SIZE * 2)); j++) { + for (let j = 0; j < Math.ceil(size.x / (CHUNK_SIZE * 2)); j++) { this.chunks[i][j] = new LightChunk(this, j, i); } } diff --git a/app/src/editor/map/Map.ts b/app/src/editor/map/Map.ts new file mode 100755 index 0000000..7184cc6 --- /dev/null +++ b/app/src/editor/map/Map.ts @@ -0,0 +1,99 @@ +import * as Phaser from 'phaser'; + +import MapLayer from './MapLayer'; +import TileStore from './TileStore'; +import MapChunk, { CHUNK_SIZE } from './MapChunk'; + +import { Vec2 } from '../util/Vec'; +import { Asset } from '../util/Asset'; + + + +export default class MapData { + tileStore: TileStore = new TileStore(); + size: Vec2 = new Vec2(0, 0); + + activeLayer: MapLayer = {} as MapLayer; + private layers: MapLayer[] = []; + + private chunks: MapChunk[][] = []; + + init(scene: Phaser.Scene, size: Vec2, assets: Asset[]) { + this.size = size; + this.tileStore.init(scene.textures, assets); + + this.layers.push(new MapLayer(size, this.handleDirty)); + this.activeLayer = this.layers[0]; + + for (let i = 0; i < Math.ceil(size.y / CHUNK_SIZE); i++) { + this.chunks[i] = []; + for (let j = 0; j < Math.ceil(size.x / CHUNK_SIZE); j++) { + this.chunks[i][j] = new MapChunk(scene, new Vec2(j, i), this.activeLayer, this.tileStore); + } + } + } + + update(): void { + let start = Date.now(); + + for (let arr of this.chunks) { + for (let chunk of arr) { + chunk.redraw(); + if (Date.now() - start > 10) break; + } + } + + // if (this.scene.i.keyPressed('S')) this.saveMap(); + // if (this.scene.i.keyPressed('L')) this.loadMap(this.savedMapData); + } + + private handleDirty = (x: number, y: number) => { + this.chunks[Math.floor(y / CHUNK_SIZE)][Math.floor(x / CHUNK_SIZE)].setDirty(new Vec2(x % CHUNK_SIZE, y % CHUNK_SIZE)); + }; + + // private saveMap() { + + // let mapData: number[][] = []; + + // for (let k = 0; k < 3; k++) { + // let tile = 0; + // let count = 0; + // mapData[k] = []; + + // for (let i = 0; i < this.size.x * this.size.y; i++) { + // let x = i % this.size.x; + // let y = Math.floor(i / this.size.x); + + // if (this.getTileset(k, x, y) === tile) count++; + // else { + // if (i !== 0) { + // mapData[k].push(tile); + // mapData[k].push(count); + // } + // tile = this.getTileset(k, x, y); + // count = 1; + // } + // } + // } + + // this.savedMapData = mapData; + // } + + // private loadMap(dat: number[][]) { + // for (let k = 0; k < 3; k++) { + // let offset = 0; + // for (let i = 0; i < dat[k].length / 2; i++) { + // let tile = dat[k][i * 2]; + // let count = dat[k][i * 2 + 1]; + + // for (let t = 0; t < count; t++) { + // let x = (offset + t) % this.size.x; + // let y = Math.floor((offset + t) / this.size.x); + + // this.setTile(k, tile, x, y); + // } + // offset += count; + // } + // } + // } +} diff --git a/app/src/editor/map/MapChunk.ts b/app/src/editor/map/MapChunk.ts new file mode 100755 index 0000000..37b4598 --- /dev/null +++ b/app/src/editor/map/MapChunk.ts @@ -0,0 +1,114 @@ +import * as Phaser from 'phaser'; + +import MapLayer from './MapLayer'; +import TileStore from './TileStore'; + +import { Vec2 } from '../util/Vec'; + +export const TILE_SIZE = 16; +export const CHUNK_SIZE = 32; +export const DIRTY_LIMIT = (CHUNK_SIZE * CHUNK_SIZE) / 2; + + +/** + * A visual representation of a chunk of a MapLayer. + */ + +export default class MapChunk extends Phaser.GameObjects.RenderTexture { + private dirtyList: Vec2[] = []; + private fullyDirty: boolean = true; + + constructor(scene: Phaser.Scene, private pos: Vec2, private layer: MapLayer, private tileStore: TileStore) { + super(scene, CHUNK_SIZE * pos.x - 2 / TILE_SIZE, CHUNK_SIZE * pos.y - 2 / TILE_SIZE, + CHUNK_SIZE * TILE_SIZE + 4, CHUNK_SIZE * TILE_SIZE + 4); + this.setScale(1 / TILE_SIZE); + this.setOrigin(0, 0); + + scene.add.existing(this); + } + + + /** + * Indicates that a position on the chunk is dirty so it will be re-rendered. + * + * @param {Vec2} pos - The position that is dirtied. + */ + + setDirty(pos: Vec2): void { + if (!this.fullyDirty) { + for (let v of this.dirtyList) if (v.equals(pos)) return; + this.dirtyList.push(pos); + + if (this.dirtyList.length > DIRTY_LIMIT) { + this.fullyDirty = true; + this.dirtyList = []; + } + } + } + + + /** + * Redraws all dirty tiles on the chunk. + * + * @returns {boolean} - A boolean indicating if tiles have changed since the last render. + */ + + redraw(): boolean { + if (this.fullyDirty) { + for (let i = 0; i < CHUNK_SIZE * CHUNK_SIZE; i++) { + let x = i % CHUNK_SIZE; + let y = Math.floor(i / CHUNK_SIZE); + + if (x + this.pos.x * CHUNK_SIZE >= this.layer.size.x || + y + this.pos.y * CHUNK_SIZE >= this.layer.size.y) continue; + + this.drawTile(x, y); + } + + this.fullyDirty = false; + return true; + } + + if (this.dirtyList.length === 0) return false; + + for (let elem of this.dirtyList) this.drawTile(elem.x, elem.y); + this.dirtyList = []; + + return true; + } + + + /** + * Redraws the tile at the specified position, + * based on the current data on the MapLayer. + * + * @param {number} x - The x position to draw at. + * @param {number} y - The y position to draw at. + */ + + private drawTile(x: number, y: number): void { + let mX = x + this.pos.x * CHUNK_SIZE; + let mY = y + this.pos.y * CHUNK_SIZE; + + let wallTile = this.layer.getTile('wall', mX, mY); + let wallTileIndex = this.layer.getTileIndex('wall', mX, mY); + + let floorTile = this.layer.getTile('floor', mX, mY); + let floorTileIndex = this.layer.getTileIndex('floor', mX, mY); + + let detailTile = this.layer.getTile('detail', mX, mY); + let detailTileIndex = this.layer.getTileIndex('detail', mX, mY); + + if (floorTile !== -1) + this.drawFrame(this.tileStore.floorTiles[floorTile].identifier, floorTileIndex, x * TILE_SIZE + 2, y * TILE_SIZE + 2); + + if (detailTile !== -1) + this.drawFrame(this.tileStore.detailTiles[detailTile].identifier, detailTileIndex, x * TILE_SIZE + 2, y * TILE_SIZE + 2); + + if (wallTile !== -1) + this.drawFrame(this.tileStore.wallTiles[wallTile].identifier, wallTileIndex, x * TILE_SIZE + 2, y * TILE_SIZE + 2); + + if ((x % 2 === 0 && y % 2 === 0) || (x % 2 !== 0 && y % 2 !== 0)) + this.drawFrame('grid_tile', 0, x * TILE_SIZE + 2, y * TILE_SIZE + 2); + } +} diff --git a/app/src/editor/map/MapLayer.ts b/app/src/editor/map/MapLayer.ts new file mode 100644 index 0000000..66d2239 --- /dev/null +++ b/app/src/editor/map/MapLayer.ts @@ -0,0 +1,250 @@ +import { Vec2 } from '../util/Vec'; +import { Layer } from '../util/Layer'; +import { clamp } from '../util/Helpers'; + + +const WALL_FIELD = [ + 4, 4, 17, 17, 4, 4, 17, 17, 18, 18, 34, 13, 18, 18, 34, 13, 7, 7, 33, 33, 7, 7, 12, 12, 9, 9, 36, 35, 9, 9, 37, 10, + 4, 4, 17, 17, 4, 4, 17, 17, 18, 18, 34, 13, 18, 18, 34, 13, 7, 7, 33, 33, 7, 7, 12, 12, 9, 9, 36, 35, 9, 9, 37, 10, + 8, 8, 19, 19, 8, 8, 19, 19, 24, 24, 39, 29, 24, 24, 39, 29, 23, 23, 38, 38, 23, 23, 28, 28, 26, 26, 40, 47, 26, 26, 46, 30, + 8, 8, 19, 19, 8, 8, 19, 19, 3, 3, 49, 11, 3, 3, 49, 11, 23, 23, 38, 38, 23, 23, 28, 28, 25, 25, 45, 31, 25, 25, 22, 5, + 4, 4, 17, 17, 4, 4, 17, 17, 18, 18, 34, 13, 18, 18, 34, 13, 7, 7, 33, 33, 7, 7, 12, 12, 9, 9, 36, 35, 9, 9, 37, 10, + 4, 4, 17, 17, 4, 4, 17, 17, 18, 18, 34, 13, 18, 18, 34, 13, 7, 7, 33, 33, 7, 7, 12, 12, 9, 9, 36, 35, 9, 9, 37, 10, + 8, 8, 19, 19, 8, 8, 19, 19, 24, 24, 39, 29, 24, 24, 39, 29, 2, 2, 48, 48, 2, 2, 0, 0, 27, 27, 44, 32, 27, 27, 20, 6, + 8, 8, 19, 19, 8, 8, 19, 19, 3, 3, 49, 11, 3, 3, 49, 11, 2, 2, 48, 48, 2, 2, 0, 0, 1, 1, 21, 15, 1, 1, 16, 14 +]; + +const FLOOR_FIELD = [ + 54, 20, 19, 19, 18, 4, 19, 19, 11, 11, 3, 3, 51, 51, 3, 3, 9, 52, 5, 5, 9, 52, 5, 5, 39, 39, 30, 30, 39, 39, 30, 30, + 2, 12, 32, 32, 34, 6, 32, 32, 11, 11, 3, 3, 51, 51, 3, 3, 43, 38, 29, 29, 43, 38, 29, 29, 39, 39, 30, 30, 39, 39, 30, 30, + 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, + 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, + 0, 33, 31, 31, 14, 7, 31, 31, 42, 42, 27, 27, 36, 36, 27, 27, 9, 52, 5, 5, 9, 52, 5, 5, 39, 39, 30, 30, 39, 39, 30, 30, + 22, 15, 28, 28, 16, 37, 28, 28, 42, 42, 27, 27, 36, 36, 27, 27, 43, 38, 29, 29, 43, 38, 29, 29, 39, 39, 30, 30, 39, 39, 30, 30, + 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, + 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49 +]; + +type LayerData = { tiles: number[][]; tilesets: number[][] }; + +export default class MapLayer { + private data: { [ key in Layer ]: LayerData } = { + wall: { tiles: [], tilesets: [] }, floor: { tiles: [], tilesets: [] }, detail: { tiles: [], tilesets: [] } }; + + constructor(public size: Vec2, private onDirty: (x: number, y: number) => void) { + const createLayerData = (startTile: number | (() => number), startTileset: number): LayerData => { + let layer: LayerData = { tiles: [], tilesets: [] }; + + for (let i = 0; i < this.size.y; i++) { + layer.tiles[i] = []; + layer.tilesets[i] = []; + for (let j = 0; j < this.size.x; j++) { + let tile = typeof(startTile) === 'number' ? startTile : startTile(); + layer.tiles[i][j] = tile; + layer.tilesets[i][j] = startTileset; + } + } + + return layer; + }; + + this.data.wall = createLayerData(0, -1); + this.data.floor = createLayerData(() => Math.floor(Math.random() * 6) + 54, 0); + this.data.detail = createLayerData(0, -1); + } + + + /** + * Convert a 3x3 array of wall states into a numeric value between 0 and 255. + * + * @param {boolean} walls - The walls + */ + + static bitsToIndices(walls: boolean[]): number { + return ( + (+walls[0] << 0) + + (+walls[1] << 1) + + (+walls[2] << 2) + + (+walls[3] << 3) + + (+walls[5] << 4) + + (+walls[6] << 5) + + (+walls[7] << 6) + + (+walls[8] << 7)); + } + + + /** + * Returns a tile index for a wall based on it's surrounding walls. + * + * @param {boolean[]} walls - Surrounding walls boolean array. + * @param {number} current - The current wall value. + */ + + static wall(walls: boolean[], current: number): number { + if (current === -1) return -1; + const ind = WALL_FIELD[MapLayer.bitsToIndices(walls)]; + if (ind < 54) return ind; + return 54 + Math.floor(Math.random() * 6); + } + + + /** + * Returns a tile index for a floor based on it's surrounding walls. + * + * @param {boolean[]} walls - Surrounding walls boolean array. + * @param {number} current - The current floor value. + */ + + static floor(walls: boolean[], current: number): number { + if (current === -1) return -1; + const ind = FLOOR_FIELD[MapLayer.bitsToIndices(walls)]; + if (ind < 54) return ind; + return 54 + Math.floor(Math.random() * 6); + } + + + /** + * Returns a tile index for a detail based on it's surrounding details. + * + * @param {boolean[]} details - Surrounding details boolean array. + * @param {number} current - The current floor value. + */ + + static detail(details: boolean[], current: number): number { + if (current === -1) return -1; + const ind = WALL_FIELD[MapLayer.bitsToIndices(details)]; + if (ind < 54) return ind; + return 54 + Math.floor(Math.random() * 6); + } + + + /** + * Sets a tile to the tileset provided, automatically smart-tiling as needed. + * + * @param {Layer} layer - The internal layer to set the tile at. + * @param {number} tileset - The tileset to set the tile to. + * @param {number | Vec2} x - Either the x value of the position to set the tile at, or a vector for the full position. + * @param {number} y - The y value of the position if the x value is a number. + * + * @returns {boolean} - True if the tileset was changed, false otherwise. + */ + + setTile(layer: Layer, tileset: number, x: number | Vec2, y?: number): boolean { + if (x instanceof Vec2) { y = x.y; x = x.x; } + if (x < 0 || y! < 0 || x >= this.size.x || y! >= this.size.y) return false; + + if (this.setTileset(layer, x, y!, tileset)) { + // this.setTileIndex(layer, x, y!, 3); + this.autoTile(x, y!); + return true; + } + + return false; + } + + + /** + * Gets the tileset at the specified position. + * + * @param layer - The internal layer to get the tileset at. + * @param {number} x - Either the x value of the position to get the tile set at, or a vector for the full position. + * @param {number} y - The y value of the position if the x value is a number. + */ + + getTile(layer: Layer, x: number | Vec2, y?: number): number { + if (x instanceof Vec2) { y = x.y; x = x.x; } + return this.data[layer].tilesets[clamp(y!, 0, this.size.y - 1)][clamp(x, 0, this.size.x - 1)]; + } + + + /** + * Gets the current tile index at a position. + * + * @param {Layer} layer - The internal layer to get the tile at. + * @param {number | Vec2} x - Either the x value of the position to get the tile at, or a vector for the full position. + * @param {number} y - The y value of the position if the x value is a number. + * + * @returns {number} - The tile index at the position specified. + */ + + getTileIndex(layer: Layer, x: number | Vec2, y?: number): number { + if (x instanceof Vec2) { y = x.y; x = x.x; } + return this.data[layer].tiles[clamp(y!, 0, this.size.y - 1)][clamp(x, 0, this.size.x - 1)]; + } + + + /** + * Sets a tile to the one provided. + * + * @param {Layer} layer - The layer to set the tileset at. + * @param {number | Vec2} x - Either the x value of the position to set the tileset at, or a vector for the full position. + * @param {number} y - Either the y value of the position if x is a number, or the tileset to set. + * @param {number} tile - The tileset to set if the x value is a number. + * + * @returns {boolean} - True if the tileset was changed, false otherwise. + */ + + private setTileset(layer: Layer, x: number | Vec2, y: number, tile?: number): boolean { + if (x instanceof Vec2) { tile = y; y = x.y; x = x.x; }; + + const oldTileset = this.getTile(layer, x, y); + if (oldTileset === tile!) return false; + + this.data[layer].tilesets[y][x] = tile!; + return true; + } + + + /** + * Sets the tile at the specified position to the index provided. + */ + + private setTileIndex(layer: Layer, x: number, y: number, index: number): void { + this.data[layer].tiles[y][x] = index; + this.onDirty(x, y); + } + + + /** + * Automatically updates the tile indexes surrounding a position. + * + * @param {number} x - The x value of the position to center around. + * @param {number} y - The y value of the position to center around. + */ + + private autoTile(x: number, y: number): void { + for (let i = clamp(x - 1, this.size.x - 1, 0); i <= clamp(x + 1, this.size.x - 1, 0); i++) { + for (let j = clamp(y - 1, this.size.y - 1, 0); j <= clamp(y + 1, this.size.y - 1, 0); j++) { + const solids = this.getTilesAround('wall', i, j).map(i => i !== -1); + + const wall = MapLayer.wall(solids, this.getTileIndex('wall', i, j)); + if (wall !== -1) this.setTileIndex('wall', i, j, wall); + + const floor = MapLayer.floor(solids, this.getTileIndex('floor', i, j)); + if (floor !== -1) this.setTileIndex('floor', i, j, floor); + + const detail = MapLayer.detail(this.getTilesAround('detail', i, j).map(i => i !== -1), -1); + if (detail !== -1) this.setTileIndex('detail', i, j, detail); + } + } + } + + + /** + * Gets the 9 tiles in a 3x3 grid around the position specified. + * + * @param {Layer} layer - The internal layer to get the tileset at. + * @param {number} x - The x value of the position to center around. + * @param {number} y - The y value of the position to center around. + * + * @returns {number[]} a nine-element long array of the tiles around. + */ + + private getTilesAround(layer: Layer, x: number, y: number): number[] { + let tilesets: number[] = []; + for (let i = -1; i <= 1; i++) + for (let j = -1; j <= 1; j++) + tilesets.push(this.getTile(layer, clamp(x + j, 0, this.size.x - 1), clamp(y + i, 0, this.size.y - 1))); + return tilesets; + } +} diff --git a/app/src/editor/map/TileStore.ts b/app/src/editor/map/TileStore.ts new file mode 100755 index 0000000..70500db --- /dev/null +++ b/app/src/editor/map/TileStore.ts @@ -0,0 +1,49 @@ +import * as Phaser from 'phaser'; + +import { Layer } from '../util/Layer'; +import { Asset, AssetType } from '../util/Asset'; + +interface TileInfo { + res: number; + ind: number; + identifier: string; +} + +/** + * Stores a map of tileset indexes to tiles. + */ + +export default class TileStore { + indices: { [tileset_key: string]: number } = {}; + wallTiles: { [index: number]: TileInfo } = {}; + floorTiles: { [index: number]: TileInfo } = {}; + detailTiles: { [index: number]: TileInfo } = {}; + + private currentInd: { [layer in Layer]: number } = { wall: 0, floor: 0, detail: 0 }; + + + /** + * Initializes tilesets from a list of assets. + */ + + init(textures: Phaser.Textures.TextureManager, assets: Asset[]) { + for (const tileset of assets.filter(a => a.type !== 'token')) + this.addTileset(textures, tileset.type, tileset.identifier); + } + + + /** + * Adds the specified tileset to the map. + */ + + private addTileset(textures: Phaser.Textures.TextureManager, layer: AssetType, identifier: string): void { + const ind = this.currentInd[layer as Layer]++; + const res = textures.get(identifier).getSourceImage(0).width / 9; + + if (layer === 'wall') this.wallTiles[ind] = { res, ind, identifier }; + else if (layer === 'floor') this.floorTiles[ind] = { res, ind, identifier }; + else if (layer === 'detail') this.detailTiles[ind] = { res, ind, identifier }; + + this.indices[identifier] = ind; + } +} diff --git a/app/src/editor/scene/LoadScene.ts b/app/src/editor/scene/LoadScene.ts index 5ec6474..be9fb06 100755 --- a/app/src/editor/scene/LoadScene.ts +++ b/app/src/editor/scene/LoadScene.ts @@ -1,7 +1,9 @@ import * as Phaser from 'phaser'; -import EditorData from '../EditorData'; +import TilesetPatcher from '../TilesetPatcher'; + import { Asset } from '../util/Asset'; +import EditorData from '../EditorData'; export default class LoadScene extends Phaser.Scene { loaderOutline: Phaser.GameObjects.Sprite | null = null; @@ -24,8 +26,6 @@ export default class LoadScene extends Phaser.Scene { this.load.image('cursor', '/app/static/cursor.png'); this.load.image('grid_tile', '/app/static/grid_tile.png'); - this.load.image('tileset_partial', '/app/static/tileset/water_new.png'); - this.load.image('tileset_template', '/app/static/tileset_template.png'); this.load.image('ui_button_grid', '/app/static/ui/button_grid.png'); this.load.spritesheet('ui_button_side_menu', '/app/static/ui/button_side_menu.png', {frameWidth: 21, frameHeight: 18}); this.load.spritesheet('ui_history_manipulation', '/app/static/ui/history_manipulation.png', {frameWidth: 39, frameHeight: 18}); @@ -43,17 +43,22 @@ export default class LoadScene extends Phaser.Scene { this.assets = JSON.parse(this.cache.text.get('assets')); for (let asset of this.assets) { - if (asset.tileSize) this.load.spritesheet(asset.identifier, '/app/asset/' + asset.path, - { frameWidth: asset.tileSize, frameHeight: asset.tileSize }); - else this.load.image(asset.identifier, asset.path); + if (asset.tileSize && asset.type !== 'wall' && asset.type !== 'detail') + this.load.spritesheet(asset.identifier, '/app/asset/' + asset.path, { frameWidth: asset.tileSize, frameHeight: asset.tileSize }); + else this.load.image(asset.identifier, '/app/asset/' + asset.path); } } create(): void { - this.game.scene.start('MapScene', { ...this.editorData, data: JSON.parse(this.cache.text.get('data')), assets: this.assets }); - this.cache.text.remove('assets'); - this.game.scene.stop('LoadScene'); - this.game.scene.swapPosition('MapScene', 'LoadScene'); + const t = new TilesetPatcher(this); + Promise.all(this.assets.filter(a => a.type === 'wall' || a.type === 'detail') + .map(async (a) => await t.patch(a.identifier, a.tileSize))) + .then(() => { + this.game.scene.start('MapScene', { ...this.editorData, data: JSON.parse(this.cache.text.get('data')), assets: this.assets }); + this.cache.text.remove('assets'); + this.game.scene.stop('LoadScene'); + this.game.scene.swapPosition('MapScene', 'LoadScene'); + }); } private setup(): void { @@ -66,8 +71,8 @@ export default class LoadScene extends Phaser.Scene { this.add.sprite(this.cameras.main.width / 2, this.cameras.main.height - 140, 'logo'); this.load.on('progress', (val: number) => { - this.loaderFilled!.setCrop(0, this.loaderFilled!.height - this.loaderFilled!.height * val, - this.loaderFilled!.width, this.loaderFilled!.height * val); + this.loaderFilled!.setCrop(0, this.loaderFilled!.height - this.loaderFilled!.height * val, + this.loaderFilled!.width, this.loaderFilled!.height * val); }); } } diff --git a/app/src/editor/scene/MapScene.ts b/app/src/editor/scene/MapScene.ts index 229fb9e..33a1dfb 100755 --- a/app/src/editor/scene/MapScene.ts +++ b/app/src/editor/scene/MapScene.ts @@ -9,10 +9,9 @@ import WorldView from '../WorldView'; import TokenMode from '../TokenMode'; import ArchitectMode from '../ArchitectMode'; +import Map from '../map/Map'; import Token from '../Token'; -import MapData from '../MapData'; import Lighting from '../lighting/Lighting'; -import TilesetPatcher from '../TilesetPatcher'; // import OutlinePipeline from '../shader/OutlinePipeline'; // import BrightenPipeline from '../shader/BrightenPipeline'; @@ -34,7 +33,7 @@ export default class MapScene extends Phaser.Scene { size: Vec2 = new Vec2(); - map: MapData = new MapData(this); + map: Map = new Map(); lighting: Lighting = new Lighting(this); mode: number = 0; @@ -48,18 +47,11 @@ export default class MapScene extends Phaser.Scene { // webRenderer.pipelines.add('outline', new OutlinePipeline(this.game)); // webRenderer.pipelines.add('brighten', new BrightenPipeline(this.game)); - const t = new TilesetPatcher(this); - t.patch('tileset_partial', 16); - - const s = this.add.sprite(300, 300, 'tileset_partial'); - s.setOrigin(0, 0); - s.setScale(4); - this.i.init(); this.view.init(); this.size = new Vec2(data.data.size); - this.map.init(this.size, this.assets!); + this.map.init(this, this.size, this.assets!); this.ui.init(this.assets!); this.architect.init(); diff --git a/app/src/editor/util/Asset.ts b/app/src/editor/util/Asset.ts index 1a8ab86..9e43fcb 100755 --- a/app/src/editor/util/Asset.ts +++ b/app/src/editor/util/Asset.ts @@ -1,6 +1,6 @@ import { Vec2 } from './Vec'; -export type AssetType = 'ground' | 'wall' | 'token'; +export type AssetType = 'floor' | 'detail' | 'wall' | 'token'; export interface Asset { type: AssetType; diff --git a/app/src/editor/util/Layer.ts b/app/src/editor/util/Layer.ts index 88d5729..87fd5df 100644 --- a/app/src/editor/util/Layer.ts +++ b/app/src/editor/util/Layer.ts @@ -1,7 +1 @@ -enum Layer { - floor = 0, - wall = 1, - overlay = 2 -}; - -export default Layer; +export type Layer = 'floor' | 'wall' | 'detail'; diff --git a/assets/auri_16x_fantasy_floor_rock.png b/assets/1212b3f83385f518d41fd5d60924617f.png similarity index 100% rename from assets/auri_16x_fantasy_floor_rock.png rename to assets/1212b3f83385f518d41fd5d60924617f.png diff --git a/assets/129e90e0cc9499733a12c1604429fa62.png b/assets/129e90e0cc9499733a12c1604429fa62.png deleted file mode 100644 index c5c8e59e6f259cedf4a23607d317c1243cee4875..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1030 zcmV+h1o``kP)EX>4Tx04R}tkv&MmP!xqvQ>7vm2P=p;WT;LSL`58J6^c+H)C#RSn7s54nlvOS zE{=k0!NH%!s)LKOt`4q(Aov5~=H{g6A|>9J6k5c1;lamw_gwBf4-gs^rdeGRfTr7K zI++l&xm7XriU0%*B8HgEEF&&SDfrgcJ#|yv#rQ1$zCWu^&07ozh{SWuFm2)u;+aj` z;Ji;9VI^55J|`YGX+h#gt}7nDaW1+n@R<=alb$Dz5R1hQRyvrKOpSP&II3zo%@?vB ztDLtuYn2*n?a5yl&g(17T&FXFBo?s*2_h8KP(}qd=bb^8^S!16O+6Uu^)hpQP8@ zTJ#9$-v%zO+nTZmTq1-RIpsow@zn)5`A$i4teSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00G`fL_t(&-tC%8j>8}fhEt2xT&ZW8b*|8?bEV0ZX4@`i zP!oiW!8UOtkh(%8{^AGxCb#F~=Q$~i>(a{}j{{H*fEdGv~fK43Z zY%>8|R+;hW{IVrrh9G;G=&>d^%C;ReWdAtd%|kK*sfFdgr|29rfXjKu1TLFn7@ROP zDZ`uc4D%KliYc6u_f!BFA(#ilJR^kb1{EDqwU2cwDr_LcQ{^1YYIMhD#>}4wTY0lN$zbDPYKjUULWOLsK$P|wLl zSdbVQW7`6byC(MhASvr-hHNP`fm1rYS4Fbtk-~jSsb_D{%X;juU+%2PIkvdgV_u8H zkS(L<%1ULha!iFF?R8RL*-9 zfZ!e~&1EJW6f2wns-zC8%YP0I0R>A>QOdlzS5kckosuhs-&xWppnH^JPNiSGiGgD= zA#4PRyFGjEsK*2!VsiCZDYsBPMpeU%eU}xQFUpMR9$oIwLjV8(07*qoM6N<$f^U7j AYybcN diff --git a/assets/19f80922c8ec85b838aecbb58e83ebb5.png b/assets/19f80922c8ec85b838aecbb58e83ebb5.png deleted file mode 100644 index d4471e9fb402fe021c4556e02130b83dab0c99de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1358 zcmV-U1+n^xP)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00g#4L_t(o!^KxkXd6cueY+8j z3w91cq=bO*(G&}my3p8|Q1?e}@xc%!#mJW;jG;P+-V8Cca*Psk3w!W!Zx*4TfC?tH zAu$va*Iw6BT@&|f@@5CeA07%8^asd2XN(5 z0QSisi;qu&qD9Z|v9kC|qO_4iemI(nO#R4VEmGW@>}npN-V*9Z4*qyq?uHZ-qd|^! zY8ko+G&{4%$%KQ)2=$hbUCnbc;ovc@TngaE^#SHr(5FhX1dvL^ zc;V87XRkj*vC+q`Zu$@aRGrI6W*(t-W@`XYKXOoP^tnoab_0qekrMYZM&4&v^QfKK z7>kbL_48j{KReYUzGML4YIp==(NV0gZ2$m!`0ytz&Hv~cbL-om0RX{k0T^bEV_u*E z2m7UkYVAV0xcdqpe;(2*yLS4A+3)cWK7h4zh)=%y8rIGs{<5BSBm)4IU7KE|-{@~( z?HuCng9jY%@5%v+yRWeI@=uq8ng<{oi;nVlEl;`a7hhw#zWgT-)G}hCX6io5b7;y;wCGmDwT+7Uil}ZRzXn`cli4Y_hFbh&q%d(>Xs_x zicvIDjHi0U)#xHaQ~WNa(bJ;8VP*2mM)VOr8Scu{~0w$41 zxs0*sD8Bjf3(U>k!*9=?`*%|hwWm~iozz|@_1TT`>_&=qt@i$l`Tx893tM7ie-*&` Q0RR9107*qoM6N<$f}kIAn*aa+ diff --git a/assets/1d47e7bf3a7e92475630090704215c3d.png b/assets/1d47e7bf3a7e92475630090704215c3d.png new file mode 100644 index 0000000000000000000000000000000000000000..80d4bf4d05412eed1a1b0b5b8223c5d654f37712 GIT binary patch literal 5152 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 O0000 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 literal 0 HcmV?d00001 diff --git a/assets/2506d31b947fda4295475c191f2dd0db.png b/assets/2506d31b947fda4295475c191f2dd0db.png deleted file mode 100644 index f5e2e29df1eaf687d3c6a62e62ff4f8dc7d48c23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4878 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 Bt{ngX diff --git a/assets/51bb14491a3ec884269416faac03cafa.png b/assets/51bb14491a3ec884269416faac03cafa.png deleted file mode 100644 index f257d861b1446dc9c1d405e01a1c0788d762a8a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5665 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 diff --git a/assets/595347ff1a6fe6a52fbde2255860fdd7.png b/assets/595347ff1a6fe6a52fbde2255860fdd7.png deleted file mode 100644 index 13d9fc7dc1b9aac6fe4012703392f0843f49133f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1569 zcmV++2HyFJP)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00oIjL_t(o!^Kx$Xj@en|J}I8 znvqVDk`l6q>O)wRhAn*=no6s&e<*`Od>K-36aq?1?28Q=#vIkPBGj^}Q1EGmJq@Ov z*p_I=7KbZrmW3MEu{EnLfwZY3mqop8b9}Jh;pG0y^+EjLlAC+a_q)IIo$ve3??~7; zhbKh0C>QQP&#a6wGSSz}Us5?Ry?11Dvs38mjxhmLvPxjzm;N#pIgVq~`)DE=C#R~b zt2@So_omTkZI6nN*#1Nq0FI%DV^fE4`f3JKUwsDvXl;+8wLJ=#*AJK14*)oM>Qihy zu9%W>dHuM3?J|*KgaH69uOAEPWem5i;qx!QHeNCTkdZT4Y@mme1c2@Qis5q6Qn|c- zZ0A=rSO9Qs^e?>po&>p=)8N?j@@hN3!ap1v=;8NP$jr@7@k!7x`H}?(9_fgtYRZgvV|qmzutHB@B826{M2r@PtEU&HYDu_N#_96%~- zXZ?dixOm|_0D$iQMP>0yqU5FAGsBVe$P`MuY@ZbR2ZuOFB%q*B+Qm;l|7wL4qlu8e zQK=jDA(z*W!?q>nX*htEMj4qcCsJ8E03Z@j_%kC?4o?V)WE^9olbR?+0tx^im9*tWe6XLVl&abfb%27_DrBOyCpl~t-xL!F5hbP4J@^5T(lJ~JhGLE3HjN!H-S{h}f zvi6!i%qGl)0lxW#-@#Opd;WNl=Ir&9^MkPyjsHy&+^7fS_04S7pk=b%05>RmQ!g0L%4Ej268cnLrwMQ=nUiG58qUM?CyrclnelPTV4VHjE|qj z$jDhXK7JZseDH47nAJaDK;Ov|nyPz>2JAe^f0$kV?TqrrWAyb$q}+qGGjGS z-QPqg@;a98Z{qjWMN4D=V9&OP+=I0@{b;Feje%Tw={#Nr)R3(?>xyv&tDuDe&smQ*KeWjpqF2wpXt`2 zYIYKdtkruNamP1Qj1Er-PIoipVyDGjqpggS(YEvAiupl zr|Zxg?XoIEnA6>iA3tGePd=>~v%mb3I!c-d1$|{nEfqwoMMj*gT+AUj@JK_1gqqpr z%4gM-8l5;u9g<>fsK}ydV>R{Ac76qsfPz%k&YbRM)YTX6 z)?V{4+1eh(?Q56$()FD@0pGJWO_HmlE$N*WNTiW25=B;_w2Sr1QGO687w*8}32}m| zQ->xBY6WRXNK8gi9g3z>k&>vH)um1Sh$1B_Ml%_!PHN`z`cWwDR*5rFq!miLT+5L9 z6I3Gqm5VvvR1?WKQdv7%8f6T(6%q86A!;DgDpMbICT_U-r5-ajI>`VqQi=V8Ll_&K zWc`Cfd}($RrK)dH)QuXeZXXKz%Dmc0l#7Q&F%lv$sXtmiA&>ZyQS^;YF1inoA3MU- zDp5QVQ-t(-5;ax;xN>O*&(;4@h5gP)pJ8O=EC5LP%uQ>A_C*%!=nP|FK8=pfFs@uo z=}8KsBQ+ECbw_6y-LJihk&&|?%$j;gO-4`Y)lz$A6SJy3B`bhd)!u*M|G(ScGR;KE TgGYhI00000NkvXXu0mjfemdk% diff --git a/assets/5cafce367161af38becdcfbd4e965baf.png b/assets/5cafce367161af38becdcfbd4e965baf.png new file mode 100644 index 0000000000000000000000000000000000000000..f853645c6c74cb3f584532fd20d2c75ea3ed900e GIT binary patch literal 1045 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 diff --git a/assets/6900ec9f541e1d7d010aee1f57c7f1a9.png b/assets/6900ec9f541e1d7d010aee1f57c7f1a9.png deleted file mode 100644 index b82b259e41d2d8da292017b815a19dbc99810cc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1046 zcmV+x1nK*UP)vOtGG#i6B|TpUE(9IS?-sU@jV0*4Ta z0v8d<8iWv9wA_Lhzx%!a^L*dC@4nAhP;}vb_k8!9^S|eud(XYc>>q2#TG`WaZnTQb z&yB||Wy@koE{2(0M8>25#7m9BshsKyVn2It+R9v)4fNm2{>wNE$on7F&?{qmRl0yv zix+vq5k~jWLRFnP*6_H;ivav1u=egzU|pEruZ!TJSFSUf5FSS&L`Uslbt**3Z(YQz$qp}2q_;2bTldhDFrCpwiS++;&$8Lhvdv!H zx5`*1+p(G9?d`6c?GmAU!Zbb9Sj`sQ?wkooO9%H15Fg%@Id_LoR>w#BY;k$r(Df~a z6E(;i1L)VC&-U`e_W+y%^NV5-|8v_Temc-N+6<@R49OS{AD-c%jA5?q!N!oq)1QI? zoE18{v=mP63}Fn4d#h8{*_$aYZ3c`00)=P6LmREO+CH+#H*{&7Bhj7@-4G!rF$ypg z@rF)g4qn;jWu@!qVh-gZq=kGYR4mI1@!A=Mhi;BO*BIpngS^7()i*Zyq8$_RI@%s# zYukzZfmv?rb6Z9Tr7TE{;yz)d0g6m4+|pr$&o)Vy5j}dipUlF0cISEiVr-iBTa#Y` zzEWO`kv*-9k*rdme_bEYWUaprJYLiw46{4y_FrdLp;pFZiEr5b5 zM&{&YnTM5ap;qP&q7fNk=weLaL=6%^K&oAaH|8hwn;hkgf3t9?Zp^(0Y5gR(Rw~>a z@3N;;4_k=_eik7MiI6E|wYik3Jns(AnDv;=ZfN#!YV5>2jZ~yqd6QTbV+c zCqw#LVxZqb<*EPfp#?LWu`B0KFjXyHxX~;l?4jnV)h`0mT$%Jmrx^}By=O&(8qSbF zRMlaGy5^9PkN&lV+C%0vQpx`S;32J29LZnJmjL1@X(mc(-vM}Qw zej6eL3!{n(WC?Tf@DL(S`E5l9Cp;$qv6hRF1eBtTc-Bty+bTS8JRQSrFvu%}FxP5m zyyGA)ZN@$G;M#iT(DTtavV(x>dgr>QPkzk0=XnD>jf`}7DyW5+qeH*Ff3`m7^+ZS0 zUIkS#3n*i$_KK-0FpVrOny415Me$-dQG=wwDC7KG49N36{k!@e>#xwi0178@*d14~ QwEzGB07*qoM6N<$f>ZDP%m4rY diff --git a/assets/8a344775bc45eaae8c7a399985f336e9.png b/assets/8a344775bc45eaae8c7a399985f336e9.png deleted file mode 100644 index d4471e9fb402fe021c4556e02130b83dab0c99de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1358 zcmV-U1+n^xP)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00g#4L_t(o!^KxkXd6cueY+8j z3w91cq=bO*(G&}my3p8|Q1?e}@xc%!#mJW;jG;P+-V8Cca*Psk3w!W!Zx*4TfC?tH zAu$va*Iw6BT@&|f@@5CeA07%8^asd2XN(5 z0QSisi;qu&qD9Z|v9kC|qO_4iemI(nO#R4VEmGW@>}npN-V*9Z4*qyq?uHZ-qd|^! zY8ko+G&{4%$%KQ)2=$hbUCnbc;ovc@TngaE^#SHr(5FhX1dvL^ zc;V87XRkj*vC+q`Zu$@aRGrI6W*(t-W@`XYKXOoP^tnoab_0qekrMYZM&4&v^QfKK z7>kbL_48j{KReYUzGML4YIp==(NV0gZ2$m!`0ytz&Hv~cbL-om0RX{k0T^bEV_u*E z2m7UkYVAV0xcdqpe;(2*yLS4A+3)cWK7h4zh)=%y8rIGs{<5BSBm)4IU7KE|-{@~( z?HuCng9jY%@5%v+yRWeI@=uq8ng<{oi;nVlEl;`a7hhw#zWgT-)G}hCX6io5b7;y;wCGmDwT+7Uil}ZRzXn`cli4Y_hFbh&q%d(>Xs_x zicvIDjHi0U)#xHaQ~WNa(bJ;8VP*2mM)VOr8Scu{~0w$41 zxs0*sD8Bjf3(U>k!*9=?`*%|hwWm~iozz|@_1TT`>_&=qt@i$l`Tx893tM7ie-*&` Q0RR9107*qoM6N<$f}kIAn*aa+ diff --git a/assets/99f8ad5dbf19c983e8decd61a1ab0efc.png b/assets/8aad9d92a9a622d664c3362c69154aca.png similarity index 100% rename from assets/99f8ad5dbf19c983e8decd61a1ab0efc.png rename to assets/8aad9d92a9a622d664c3362c69154aca.png diff --git a/assets/a532af9feb981f9302ecf865b5d9c7d0.png b/assets/a532af9feb981f9302ecf865b5d9c7d0.png deleted file mode 100644 index f257d861b1446dc9c1d405e01a1c0788d762a8a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5665 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 diff --git a/assets/auri_16x_fantasy_cadin_1.png b/assets/auri_16x_fantasy_cadin_1.png deleted file mode 100644 index 2009e551d81396ef191d3a17abe80f63f95e009d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1286 zcmV+h1^N1kP)89)F402y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00eGHL_t(o!^Kz6Ya2%xeY4%f z=&*tsA#fTn=%kAd>OqKL5^Sq(4<$EWLImxlr1}R8MFb~o$TcUQa?{bb(jID*8jNv` zE^;Gi1SUd?6FJy!1zL#GvSM?PzRApJW_Lpf9ayy5o$u}Ye#|#-7^b6`&(bXpn{DVu zoH?c;+J@OP#URkVqp`Id=KzJV-jji>O z_$Zh<5&+;BIvh(4;UB$A5;@hlKk(5TCPK+nOCBoQ2_hvqQ7w7s4}9FXbw`WCG=M?5 z;A1g4(tfZvOrX}Vjp?FPRZAY+x)bQ4kiA~OZ@;v(IF_!iY7H9z;MN`fknW?%D5;vk z0f5_gpBZsx2vO>K!vwk~V_f-sLauu<4owveN|jr80wUKv8B24@1>b0|OBvEh8)rj5 zC>MNCwCMf=Ru*4Lls0lG42PM>OnHV9i`qO+oXI%)HWnJ}zkKFhtLRdF_)ctr$=3;>)BeIwvuvkkZI@IFT5Rx=~y zQXaPr8fbv`|d3e%Ea8g9(NoVpdthk9307k2;b>T>DgX}2~qH~NM(1&Ub8)7k2 zm?=7OihCh<4U6_aQY{ePH_^XHLSxrwu(&#$NzA~jHd z9+d}=_^*gTkt!E_i3|XIMiXAXzPQ&JeX;rUm4(d8P1b$>V)MrzzQf4e%t;P117NVcw#1+9 z?(9o*PFn|YNlArIn7cdsMhvEFYCV5`W(A$L4p^8T(&-%&u`s8Y2~al_O(T(tvX3k1 zcnw`$Nb!&6ue`-xXKrTluYX;V-oJc(0iC@#?|SAx>~-dFbs@zs(JpH1kQ5_rj{Siz z?%GDhSj=Z>l}ah9mOSRx9c-#4;s^Sq6jZA41K$&4L<2X9`K?HTAX5`1mDn=3K z#;rTjZckWl-3dNKeteW-6r)rw`1tzU9NK#glW1dVltLAwZp7`>gme4uvrzRInljQu zqL@;n6X*OT%2-t$l6n+QHj#P=>7>nS4O=2WsY)kp7S(fv69>m3tjFKKfm?Tsk+Rir zQqfJs834RDBsEzrdCWzYH@sp#JF{YFg~@8sLAl^dY9dm?0^o{K)G?%uS{DxO+Dz2U zs>jrisJpB3!z5$XqMlKV;w36ZQ4tC%k^fQz`U9VrLN4WDH8Tp8v36A9l5!*|D$b#P za@cG$7g_Oe$f)L2yh+r}jaA!+RD~x-LOt4uz@$7?C8$%3v5rB8JxwAIb-ohiuURy%v_n&o=BGRFrKab_LC495Gf{l&)Ak0WT wq>|Aoy(qO8=2%?i$>IRURePVp{(m3u063C2MsNu!ZU6uP07*qoM6N<$g1|OI_y7O^ diff --git a/assets/auri_16x_fantasy_wall_dungeon.png b/assets/auri_16x_fantasy_wall_dungeon.png deleted file mode 100644 index ff157c1f8e7c95a513445a4df3e7ba0aa56f25fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4821 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 diff --git a/assets/c57c07c340da5af1c066def99abf7a25.png b/assets/c57c07c340da5af1c066def99abf7a25.png deleted file mode 100644 index 190b845b660ebbf34f85cc0892ebfe34832eca4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 958 zcmV;v13~e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00SmTL_t(o!^KxIj;lZn^`P{* zK>9fZqM+mk+eJd8Nwdw0L!fQ8p#dS$?F~|`r z9{bsTqrh3)T?_yK&-=hWhb(G?_n{by0|Ef!ytmIwpws~d%Mj-jXCE>DnWpYvp7Wvt z05IEKY(ke>?GBn{de=a8ZwmyDOVgf^V*a5=*` zo>*clp~4jMhl!X%f*y|h7f>2Ktuq+M6N~e_4{E!MjcXqX6=o)|*>3_ic~Mz{`bvHr zPX+*#i__FSYAMbD0i8x7N zxSYY|R|tQuw+F;Y3gdXPo0WJJN#)+P5;(P zd8Kuxs56w8lD!g$=`CpqOdz*egAw_LuGSe=mc6cV#>Q z$7mavA`cReBI=Bk*EmUS{|G5vOfTJls}`jwM~pE4)#5fooxlOTG@K(xm>XXta40r) zucT<=rkc7}<9HHrq_uIy+2Oc1c~L2>FgF#`Ny11RK#e2e;DDXpy);@yyK`PtGnql< z0|g6^uU6s`61(3uT>IF#M_%`Iz$r$l(kRa7*Y~U$A{8bc8PeyJSHhxV)H;KY_qUgi z<2#Quf$c7~4KPkp8zt$C#ya%84+=fU)Ld^Vs~AJ)7dpieS`uj+cLH~9IXkv+JMk$# zr@1OJer8yIq8PEmOA+MeVZ?C&KPV_Y6`C`wQfg0>XV|poR3S}aXu2pC+PiJUdzAJ8 gu1(qhwEy$<1Ac#$X`vpIvj6}907*qoM6N<$f<>sIBme*a diff --git a/assets/c58cb184401388ad65c9e3509e83b706.png b/assets/c58cb184401388ad65c9e3509e83b706.png deleted file mode 100644 index 8822e9b9c340cdd5a2077114e79cd6998b402b88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3090 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 diff --git a/assets/cc88be196a49a85b30d8f075cface18e.png b/assets/cc88be196a49a85b30d8f075cface18e.png deleted file mode 100644 index 4d82d6c756716d583730e1e67fb8c415455d9eeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3233 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 diff --git a/assets/d38c5fc89edfc8feb26af435c84bda95.png b/assets/d38c5fc89edfc8feb26af435c84bda95.png deleted file mode 100644 index 8ed64bbed75244741740b683ecbec8c21b49ca30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 843 zcmV-R1GM~!P)1STmKwEu(7litSoG7B-)A1S_Ba+M6?Kq#6lZI zM3mob&hzYi?b{70gbZxv&YgSCoVjnl5^+#g%)s%pm;Ue*Ja5b2t5?CS)d(Sac>i|M zwQ=D}xX2=);(%uvOjVG0Z{56+$RNaCrhqwr?qocD_A*W#`!!nBp?nY#;a^|hnCsr! zn#Rt~c6|DJB))unQD7D96BvsTwwWQsJb3g(ammgF4xMI5jAQ8@o25b!Gk^W=b8K!t zj_+rF$Mvfh({R}Mdf7pY)*kU>N2P~T40Si{%nBlk>_wl7b(0r9Gi8jZsvu@60n{Rs zZ;_}3*qwy1^7XL96rW7M$>j8q6=o)3>TlHys6pYNfEAv}I+nKk3PE&|cGi(ZL57Vm z`kcVYYc?3L{J_E`JT=FOpKpbroD@QSDq0U}kEtD_Ng{?!=DTrDkd!+?Ye2=e`g4De z^XIoWL-=7?N*`^uH)TdL%Z}Tmm@vrLW7A7ywlnFW!hi0z2o&EwEvm(^y>_ZFGk zPM`(sKDe|Fb>;Y(M673wF~lPyc@!oD>J;v=ZoF0E*{~sA(9r#+v+k2Ts zk8AE6(jWkIYs*LNr>y&zv0L2Ys0yO<&fR;%H313Rc&|k~y;o$h?FDKL&iUczMk}u7 zcWxegZ$JEq>8s~+M@ch7H9|kkYW+Q*i%}e6{9?slWC&*tb|I@~C+CIp{n53!eBxTt zr%Y7^Ie0(B!0)`YHPNad&k#U@DZ0;AIqKm|BoS$|VDsiu5$AB*t0S)wqYE;ByX3L7 zHLwP7pYPOJhW=fu;wl!kXNMrNA|cp5fJjt>&0m1xlNf|}W`tE;2vUdFF3SBaTMJZT zv|d!VaQNp0LEynXf;?8Z{N00}2U(9xSn6R7F@V;J{{1CD3!0RRbkU13ecHutzq}w_ZP)P V=Y~!Wq4Hpsz3o-$`0000PbVXQnLvL+uWo~o;Lvm$dbY)~9 zcWHEJAV*0}P*;Ht7XSbZ8%ab#RCwC$T~BLV$q_G21R7mde-1WYVnqjeFFHuf5-}D* zjIjwaERinx0ET=6eSl;ydz;Ii13BrkIaq>)6^o69Fw&sKgMIMH#wgN3YkOnMu%MZc z;2ga5wx-qZz3#4h-TlU|E*ia=dR<*zue-YX_o_y|{QlN^C@~!z*8u=v?&ei^e*Yx7 zKDICp{U_(i|MYwHx9^V5V0EQ#+-7^L0~_m2=uS-~ZP>K061RQd^3%^F!(u?XL3#S- zF)TM)i89_Dox#NHWb*%hcNgZT7vbrf#{dAZvEH<98}Es2($8x9bWo^F-1a&AX=JLu zQ5Yhk%0z{>w{~F}TFH0wum}?XiSewi)L|8xPVM16XuHd^E!gkw>f8V2_Y<>|i6L>@ z-&k+5_?H_kmj03%=+WUl_mx0!`<=Q?zMc6^zQ;1VGe%3v$uI;P$qitoij{|po zrUxPQ&(&WIfAsm%EqHWzFKM6%p|cdlZU55R6vlUu(rrKMbfsnY#GUBq- zM0lu*I&|&7>#Rd}f^h1ueJ{W@cjPCKy>`o_G#jlj4g~0 ze1G!OPl?;$c$6Xr zr~UO;1J|e_Q3D$`a?v*%4@B`SQWQFlgHJ6!-Dp5}H#Y~af6Szt{hR}dl8$ujc%F~h zc=CXFPTP2CJWGsF3^bf-=E z*zj+xH{tqB4;pXZCdOlf2SRej14-gZB=9gCJW+_955-ft^j{KBWHWx$sPHoj6D@%! z;VwNiJxOfKSlTfF^*uQ6#!KLd(2uemKUN%wJ_%eriBxPNB%Vk@)=+pN*YR_X1LbwZ z7EdA-2MLKM$_oY1tQ={0s*rdhPJScytIb?IiF6z(W^jFs`GL6kje_I{l8mL4@x8E^ z@&mby82eKVED*sSq=Lii2vGrME%Mau-gQEQ245-M=9ij0= zHV$x%`GIUV0#yDgKTtqNx(v%!p8R6@fy#9!QmcsPaU+0Mc&hp7#c1*aapfP%Vk^G#6LIh~ zNU`Jxa+SaI21H91q45-%pGXoUC`IMjIFiX~3`|BU?$t+=C-<4wq>_CU_bu z6itcG#RHl0V_P~r4V6Eu7MPCTMSj|bS{}f*|7E8dI)FOki81<&MnIirDu13#I{q@u z047HzAu1Bp&<`jxp2k-GGU0Jzr^JaaE27WEh{}oxEP_Ow=rZN!(cx)q6b4^!_^j(0HcO=Laf#y)7z#R-0-3WzO>~(Y{z5$i@$n zz|&Cvy!<$6`H4*Bm(Fh_Rqw2uuQ(<#&DRIr2-)I-%OXu!LcL9UT-jZ5KnIT;KU^ zC?pm6ZRA(^U3P&S`E@=WEqH()GT4=Qon9_cj3Mu!U7}^V7KU>wF*^`D4n@6C%G6?f=eSzLkM<&{s7~ zw~<_E0_C^ay6Rqw$$M2MuijbcdS5R3cWqO$-rI#Z5WW`_V;m?ezm6_HkWIa_7~X4> z*1wN>Z^n3_7{~+(qWu@~fhs&uSn{oZ$^O?cC{?CX zOKN@{Tlu;9d87|Red}K`KT(AT^0*1eiUax5zce1I!UIJ>KJ_o%|I*|K%ER;6$l`L8 z5euk2uWu&fAX)ECiwDXR&=;P?2G3)PXHxlX^ZTg3;qu$)-vrB(HxAPE-i+};I$?a* zyD9iSF%x-jm92jcLj;(-k1 zSNUnZtmpSn2I^(y{ci?TY77pCsG(M;;z1PZk|N%`aqAFH4F{^{>m4VjBl491mm~lkpt~ zGQ|U>$&$s-$uA^9dHR=#166pSg7840@kA9K$TLaefo%A7^|GXVpb8J!AEl3z7O6Mma| zSrRmKc%UM2EfTmIZYVx9zsu4tdC$V|KrZ#ReB)`->TRXTuNtEXzfHX?={S&NOw9&Q zB@zd6skdc{hr!Nd@jp7e=TvXYRQ{s)CF4Lv`ay-_fmEk11RlnQKhG)k#b3rakT@QQ zQ*R3Zg5ha&*L?GH2f!AOB{dUC;ekqDaIWmc%@7uvYSj={R2fi3;SDI{5k{-I1g+*0 z%v`%%jYwGyDC7~wZs#!hOpR#f+U3N!Dyo*SOyg>Q{nfxVYDm<;DvFDu*@fW@g|ibH z(A~|=f$P;SEG`<2MG)0Mj-S1}U{n=V2E;O|iYgzdqG|^6L%8#L@$Y}Z@w1oJNDb2r z&wY48I1M>Y|ShcmUd>|1A`tIiDg(a*qo_MHqr}5Gi_d-*38eeVgKWh3vx;5q) T1Bjsx00000NkvXXu0mjfpD#PNs6|N+?%Ns`SVTR9z63o)M1dfI24zJE7P=^8 zfsvsb_23Vrpq8{OAwI06{VkYCJ_v4{xqD{k?!CM2+DZgFa5;0&oO9-zbI;6t7s#|s z2L3JsHr2hQ{Z05jO%{S@7qM<*>8BmuDlt%5Vuc()rXp)oNu=YN9fXTRBOQ>lXCnB!k_K@T;Wqtcaa z0%ZVHUsnj!y_Vm4CG-*>J-rC7Y(Q%TN-hp=3WN=)K76Hx;bH6)pQ(R22W~A|3Ixn5 z%M#Gz?hjb}G6&zlZH@_FX(6iKe}c_sL+4cujRB_|JA0{1*h@uBhrp0;HTi1f8vsxw ztyFMQ2|xl;mE5Py5SXnrE8lk$vr4EU^fxykwzwsr3ulTkGI;>!SM!9^!s;*7de_le z>lN=>>pJ?{JLFhd#9qxP&&KuBH}F~7_Z{0O`_!4r>c!<7U7(^@n*^jPAFe(?!P;@m z`Co|I;jzIV^9b%4!YO!Il~B^6UcoD35rExG)mV47$ zd*A{vIUPcobv%Q1b}a2WT-$WUiaSYWAd`XrAp?es zDjCAXi%Nzu(iOt?Bn$HQi`PU`NmNX|n zO6-ms0kmpX_fV`IO|D);{Q-)rll;TpE2wDl8)}&RB_IwK4*-v+A_*kphdVwnl~n&l|%kD03?sKk|nG*_9DrMXw`0XVW;iK%IpiiwG#X>P5Q z9OcTi$Wi9jaFGIvIDn77?>qP0d(U~#J->UO^E~f;-up|rW^H;lFUn_1UWb-1zL1s5X002>`f1MBTyg;586bd!BG8UTW z7n70P?;fCFw(%je*=J49wLrP2G?ca2$#aB0hHdf~JQYHNKP5j8?lYRumH(7Gv!A@h&dpN0t7%!d4AX7};~!a_cKa7!cJ z%1@^NM_&~8(8y2}rpD~1-%&pcMOHw%9;K&wzKrsv4WXJ6znR}1CJ#hYQrfq)V=Vhfj!F5>v& z+P0PV#r0p1Gx0b}gBuD(?}`H|)8i+HBa;JQ5>Gge!-HgQpVtRNan9o6?YkD!*bMQY zD9RmOCJibwfzJQG1*vi@i@GSO@8c1YQ8`;yX1gA{NP8HV8#Z1)3lTUfRLTH#`ycN1 zzwiXttgu~vsftUHgC zFDUvXBd@B-S=>*`t1F&>Qz&?~i3uM`m2&G+$W8wYRSeT)gp9tpme?H#qmqay33gA~ zZO4Q4FTy|EFF4Y;?!on0a+N@Rqv4Z^v?>kBK+onjjAg-256j*zuQs5fsuXdaGXGYF zi?1dZnob#2$@$|Pxx9e7*tpdNsE8ET^sZ9)u;d8%<|#l>%$o4Y250Nv>)~XRCVSKO zs*OOY7n5DctHi=1jkCikdx)u!n6*!%9;;+d#V^V*Vcc?~SKqj5+}_9>lYPZ+8K5nn zF=KUQZ^IpP68Z6_IC$+=sPiWL&}y_)0P6f>pq4G&{gO~;aO{L=tnS-npi?0nw3G6S zzz+OqTe~)9?}iGLRBQ9pKGKbf;%zi_tQ4$`uDpBkC}pTd-1h>$8-ZmUK%{6C-MT!W zGiQ<|2xIY0PDbC_$ukHzBLBTGQ|7^UT!iyxSE~-wn`=)^Th6xk*pR-4q3xy_=Du8; z84mm?34%FPEv%)bZTJ#iW5;LcQm8DH>XZjBJ0>R0x2ug6RZd^o zU-tltW#_FM&d&>I&3P~G`sSt_khm4t@n)+2joH0DdXGakEe)5%_i=8{Fa&5x+5aaJ z9bl_xWw%*6SFzy@5k-B^Z0{V}F=l}iyL+X8#&ap~UkK#!D3zO$yGQ~;p0vGpp0V9s zdvQ5BQWs+ z^z{7Cyj#1-uTr!P(tS|5^cje_D6rlfGI*1I2_VwV|9aXjm5G$b)`>#~9j4=T{rjK| zL9@ZBpF2NbIZe*i0o1(qkl?v^JDQ>KoZF;5*IW=iK^>%%f*?b_-JQ}@ZVXl^ni~16 zgbh9dgfnl0AXjBUru%`%(>#wwN}mUI^#y18@<0O8*$O_n`DV(gvu@v+#_X+q$k$vW z*w`4WX5B10a7v;BUwZak)Qnq?grgt=zQUNkVYYh67@h7($G!bo5-D%bJ^VIjhqOqm zSf8~qGhKN6=;Td18a(P}9+g|t4UQMOnPmD?0AvF9Dpa9aM(u#fTx0CiS&$h3d*y^9 zBppzs`dD3MM`&Q;RjiQjg}#onv}Mnqj&x)#Hw^@Hk;Y~jfJ_7jJ36GEz!ecy$3+M2 z+B39458Kq{NIqP-i)a}MhzQ%5cS84dEnQI32*oPLVy&~A^~L0B8`oJY=}s*m-FsuXpXqvRt0QTvO) z>zv9H4#oC^FxURn6U&(Q3+{AAWj}92N)wWO*?Yf}9vbppsr=0)RUYvdvt<*L?{T4sgq zQ8&1)y()w!Nrc{UZV=Ssgk=BeRT+z3tev&8D(|cJ$2t}lTpl7#1rbK>8ArX@HLygR zCtFGY5Go2H(Sk@qn%awX&RZ-0w~+YfoR#vsI*;vm__}WITFt8s;Ue{CJGLD1)bT|1 zzEqU^I{Z{EAkn_1MQtgZQj;Y66=Cz zW56@rYA3+fxRPB2?+)wuC2mL4J@H?0UW^3l7xY|o8Vi@P3#Y-gxE|YK^sKxhBDq5m zwjc~Ye?3VlSq@!$yzBB4n3s3By~)xbjmHFuPjl|VYveTuH5*^) z*ZL;cUwC(r-X60NJmgbZkC=}(Olb8z{l?I^SiPQHC6z+&EM4*F{V`rr-8&useGgj8 zJZSV7*IiydlizVpvbl846st*{di;4T@W&b9gt4|Stk+CfOQQqi+PBD((Kt_)`2!DL z&b^98s%(zvf8$7gt=WG?T?d)vU(w`XU3^-?9$i}Eelq!9Z{zTI-+7v}KhgyEXY0v~ zTuIYgm0#7gJ3jr9UKeSHz$C0sYeQHA>34^StO6v<|E6=I`YH8m?J9|;%V7m?+NDm; z#P^_xJ35hy(>#GUx;`VKZpVAtzK&4qSnHgn*eHz8tTrcxlRxLNHJ8H4Xi^R$n$YyNgr*%e ze8KtCno%fy*0E&CKN(oEpacA&_l!8M=A-l%bCn9TQS)t=;#6t)eQ48LgUw=k)n z^fH*?E#u%Mp*s@p{rhU+rsItEQds2vxTs@Hg-XR5sr zBNRaJaOUO=pDZaM!d@^wF=8?fj=oSX7|@Phr?FXldi`CUC&rRdH_Pe{8KY&x=fLH{!rdP?SC+`}1` zM3q6jlJ%LwK|U{D>9-e@sucQsDC{WqNV$38pHcJC{=O_NNJb@ehGIS{+AldA?V$?; zvimIpPeUS=W8_k5t-Mjn?lFdDj4+Fr_?q_!F<6^r&~1a$}Tn_&LsV7`&J^SB#8V=Z~d#;#vM!-o@3{+Wa-vnt;!Vu(q5?cxn74?wnX6%o+g# zECPQ|*RMDM_CkYP>SUeoX>T?e0PX0Ed4njk@W~gfb+u!Hoir7%>S@F-|5n6}ckKAX z@-kyp1}VremF;qq!b0`$WD7N41s4MnaE9_z8fGI4L)h%`EQi29W|~7tZY5Wd{hA7n<;TIi4Dy{;JDT2R;nZ>dY3PhURtxe@t3=ll5C2% z>O0cWcS5CN=h$Ws(W^Erfm7R2KTZ^Y*Dr5)YjU*2IJK}Kt`YM+50IKzq7mI-`!Da^ z5Pei=X78k+ZF^@AeXfgCM7XtIvpX~6IK7K;2wil0S+j|x0g(pCkC{U>@%#RemVv)9 zj)leS_zSFgd?N5xApKDn5K}w-Fx9tAHlA>-#>@fa<#L4*KZ|sXr#jXI$`AHB)eig? zpCJ7O{zg)kR=h{I-%i~B+Ka{B&OT*4<1H&-FPoZMGLFkb`Ox?LC;vg&A@NY1@fQVz z+M{uhQZsZ>$#}I7F2^tWJNG^3BYQM#`=4+Ctl;+xd%&7EFQ?^08Xnp5gf8s`}Q9xB~-NBfbvz<)j+P z;MzS%R0C1~Xxsz??z+kE8Syx{f4$-!W*Qy*D~u!X5%cdgXO@FBu{WDGCF;W%48MD# z=a!Ck?LnT%zU*O+YF!`Zz<6wvq$Q#Y!V5{S4?zy11nhfr=?AQW;A&WxhUU#Wco!y1 z=pP?lDannh5tjAT4j>pFS(1Gsm_`FN$W&DY(j?#;j2h}Tn8Wug?Wj)`j z@sE!|oPLtftT&hFpGM#cUmlkFne$DlnXFe083@WaR&!$vpRO&YFB{^ApC3l!Ygtv# zJvy|%tk5Q*Tye0Hdgx}2cyH}tl}*_t=8G{y7KCgUy1&)G zehWn$8tAXSE&$6+=T_`Y=|-Fp-e;#hzP(J{VnEae!BFV$!_Y5(iDfCuk(Mmd8MLxJ zo3rlXne|sh`H1M@ z-Lqq5Nv@t};=h8cNBe;Ay+Hk<#(~3fPh^7`yC>)n=|4`!FfslBh(rZf7!l^cJ^XpL z8CBopyuG@jSrxP}Ky!^)9jtO%D}2Rp-U*qj!!1Ni^TkjcTiSuEwZ;swxaQ#H-sHT%8oZ9=lm>@l;9Uv5@V|R`4n@tIe|YtV63V z2x4~#QsVP>cC}dw#bBD?)dn9ubSujCOc*W$z%s>189WnoXg&2L+J(4xsWRH7b`Ki0 z)IZ{j7s?d^EkOuEzolz4S_P{pQ1><0F1l-yF`zVb?ltbToHi zhSFO%X$RVQC9R$`6W=GZG~pEAXDjq7N2)w0;qL#|r;R7khwa(6jaq`I=3eRuo@#n) z+b3+-m)m{Gm_A-k7kyeYq?r { - return await this.db!.collection('assets').find({ user: user }).toArray(); + return await this.db!.collection('assets').find({ user }).toArray(); + } + + + /** + * Gets a user's collections. + */ + + + async getUserCollections(user: string): Promise { + return await this.db!.collection('collections').find({ user }).toArray(); + } + + + /** + * Adds an asset to a user's collection + * + * @param user - The user that owns the collection. + * @param collection - The collection to insert an item into. + * @param asset - An Asset string to add to the collection. + */ + + + async addCollectionAsset(user: string, collection: string, asset: string) { + const res = await this.db!.collection('assets').find({ + user: asset.slice(0, asset.indexOf(':')), + identifier: asset.slice(asset.indexOf(':') + 1) + }); + if (!res) throw 'Asset doesn\'t exist.'; + + const matched = await this.db!.collection('collections') + .updateOne({ user: user, identifier: collection }, { $addToSet: { items: asset }}); + if (!matched.matchedCount) throw 'Collection doesn\'t exist.'; } @@ -342,7 +375,7 @@ export default class Database { let assetName = '', assetPath = ''; while (true) { assetName = crypto.createHash('md5').update(data.identifier + await crypto.randomBytes(8)).digest('hex') + '.png'; - assetPath = path.join(path.dirname(path.dirname(__dirname)), 'assets', assetName); + assetPath = path.join(ASSET_PATH, assetName); try { await fs.access(assetPath, fsc.R_OK | fsc.W_OK); } catch (e) { if (e.code === 'ENOENT') break; } } @@ -378,6 +411,26 @@ export default class Database { } + /** + * Deletes a user's asset, removing it from the filesystem. + * + * @param {string} user - The user identifier. + * @param {string} identifier - The asset identifier. + */ + + async deleteAsset(user: string, identifier: string): Promise { + const asset: DB.Asset | null = await this.db!.collection('assets').findOne({ user, identifier }); + if (!asset) return; + + try { await fs.unlink(path.join(ASSET_PATH, asset.path)) } catch {} + await this.db!.collection('assets').remove({ user, identifier }); + + const query = user + ':' + identifier; + await this.db!.collection('collections').updateMany( + { items: { $all: [ query ] } }, { $pull: { items: query } }); + } + + /** * Creates and returns an authentication token for a user using a username / password pair. * Throws if the username and password do not refer to a valid user. diff --git a/server/src/routers/DataRouter.ts b/server/src/routers/DataRouter.ts index eac0d4b..e75a8fe 100755 --- a/server/src/routers/DataRouter.ts +++ b/server/src/routers/DataRouter.ts @@ -35,12 +35,17 @@ export default class DataRouter extends Router { case 'assets': data.assets = await this.db.getUserAssets(user); break; + + case 'collections': + data.collections = await this.db.getUserCollections(user); + break; } })); return data; }; + /** * Attempts to authenticate a user from a username and password. */ @@ -97,7 +102,7 @@ export default class DataRouter extends Router { /** - * Creates a new campaign. + * Creates a new map within a campaign. */ this.router.post('/map/new', this.authRoute(async (user, req, res) => { @@ -118,16 +123,16 @@ export default class DataRouter extends Router { })); this.router.post('/asset/upload/', this.authRoute(async (user, req, res) => { - const type: 'ground' | 'token' | 'wall' = req.body.type; + const type: 'floor' | 'token' | 'detail' | 'wall' = req.body.type; const tokenType: '1' | '4' | '8' = req.body.tokenType; const name = req.body.name; const identifier = req.body.identifier; - if (typeof name != 'string' || typeof identifier != 'string' || - (type != 'token' && type != 'ground' && type != 'wall') || + if (typeof name !== 'string' || typeof identifier !== 'string' || + (type !== 'token' && type !== 'floor' && type !== 'wall' && type !== 'detail') || (type === 'token' && tokenType !== '1' && tokenType !== '4' && tokenType !== '8')) - return res.status(400) + return res.sendStatus(400); const file = req.files?.file; if (!file || Array.isArray(file)) return res.sendStatus(400); @@ -144,6 +149,33 @@ export default class DataRouter extends Router { })); + /** + * Deletes an asset from the database & filesystem. + */ + + this.router.post('/asset/delete/', this.authRoute(async (user, req, res) => { + const identifier = req.body.identifier; + + if (typeof identifier !== 'string') res.sendStatus(400); + else { + await this.db.deleteAsset(user, identifier); + res.sendStatus(200); + } + })); + + + /** + * Adds an asset to a collection. + */ + + this.router.post('/collection/add', this.authRoute(async (user, req, res) => { + if (typeof req.body.collection !== 'string' || typeof req.body.asset !== 'string') throw 'Invalid parameters.'; + + await this.db.addCollectionAsset(user, req.body.collection, req.body.asset); + res.send(await getAppData(user, 'collections')); + })); + + this.app.use('/data', this.router); } }