From c390c735b4eb305a4750040e2dd4bc3823481d06 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 7 Feb 2026 21:33:45 -0800 Subject: [PATCH] Add actor editor with animated 3D character preview Browse and customize all 66 game actors with a Three.js rendered preview featuring skeletal walk cycle animations. Click interaction matches the game's character-dependent behavior (Pepper=hat, Nick=color, etc.). - Parse WDB global parts and global textures for character assembly - Parse and serialize character data (66 entries x 16 bytes) in save files - AnimationParser for .ani binary format with hierarchical keyframe evaluation - Full g_cycles animation table (11 types x 17 animations) driven by move/sound - Per-mesh texture support for hats, torso, and face textures --- public/animations/CNs001Bd.ani | Bin 0 -> 657 bytes public/animations/CNs001Br.ani | Bin 0 -> 1617 bytes public/animations/CNs001La.ani | Bin 0 -> 1335 bytes public/animations/CNs001Ma.ani | Bin 0 -> 1545 bytes public/animations/CNs001Ni.ani | Bin 0 -> 1233 bytes public/animations/CNs001Pa.ani | Bin 0 -> 1277 bytes public/animations/CNs001Pe.ani | Bin 0 -> 1209 bytes public/animations/CNs001Pg.ani | Bin 0 -> 657 bytes public/animations/CNs001Rd.ani | Bin 0 -> 657 bytes public/animations/CNs001Sk.ani | Bin 0 -> 1590 bytes public/animations/CNs001Sy.ani | Bin 0 -> 657 bytes public/animations/CNs001xx.ani | Bin 0 -> 1101 bytes public/animations/CNs002Br.ani | Bin 0 -> 1393 bytes public/animations/CNs002La.ani | Bin 0 -> 1447 bytes public/animations/CNs002Ma.ani | Bin 0 -> 1461 bytes public/animations/CNs002Ni.ani | Bin 0 -> 1201 bytes public/animations/CNs002Pa.ani | Bin 0 -> 1149 bytes public/animations/CNs002Pe.ani | Bin 0 -> 1065 bytes public/animations/CNs002xx.ani | Bin 0 -> 1472 bytes public/animations/CNs003Br.ani | Bin 0 -> 1497 bytes public/animations/CNs003La.ani | Bin 0 -> 1675 bytes public/animations/CNs003Ma.ani | Bin 0 -> 1749 bytes public/animations/CNs003Ni.ani | Bin 0 -> 1369 bytes public/animations/CNs003Pa.ani | Bin 0 -> 1369 bytes public/animations/CNs003Pe.ani | Bin 0 -> 1373 bytes public/animations/CNs003xx.ani | Bin 0 -> 1377 bytes public/animations/CNs004Br.ani | Bin 0 -> 1741 bytes public/animations/CNs004La.ani | Bin 0 -> 2139 bytes public/animations/CNs004Ma.ani | Bin 0 -> 2597 bytes public/animations/CNs004Ni.ani | Bin 0 -> 1853 bytes public/animations/CNs004Pa.ani | Bin 0 -> 1961 bytes public/animations/CNs004Pe.ani | Bin 0 -> 1577 bytes public/animations/CNs004xx.ani | Bin 0 -> 1581 bytes public/animations/CNs005Br.ani | Bin 0 -> 1373 bytes public/animations/CNs005La.ani | Bin 0 -> 1427 bytes public/animations/CNs005Ma.ani | Bin 0 -> 2117 bytes public/animations/CNs005Ni.ani | Bin 0 -> 1245 bytes public/animations/CNs005Pa.ani | Bin 0 -> 1545 bytes public/animations/CNs005Pe.ani | Bin 0 -> 1249 bytes public/animations/CNs005xx.ani | Bin 0 -> 1253 bytes public/animations/CNs006Br.ani | Bin 0 -> 1269 bytes public/animations/CNs006La.ani | Bin 0 -> 1587 bytes public/animations/CNs006Ma.ani | Bin 0 -> 1445 bytes public/animations/CNs006Ni.ani | Bin 0 -> 1365 bytes public/animations/CNs006Pa.ani | Bin 0 -> 1665 bytes public/animations/CNs006Pe.ani | Bin 0 -> 1369 bytes public/animations/CNs006xx.ani | Bin 0 -> 1373 bytes public/animations/CNs007Br.ani | Bin 0 -> 1353 bytes public/animations/CNs007La.ani | Bin 0 -> 1567 bytes public/animations/CNs007Ma.ani | Bin 0 -> 1501 bytes public/animations/CNs007Ni.ani | Bin 0 -> 1205 bytes public/animations/CNs007Pa.ani | Bin 0 -> 1293 bytes public/animations/CNs007Pe.ani | Bin 0 -> 1249 bytes public/animations/CNs007xx.ani | Bin 0 -> 2144 bytes public/animations/CNs008Br.ani | Bin 0 -> 1113 bytes public/animations/CNs008La.ani | Bin 0 -> 1047 bytes public/animations/CNs008Ma.ani | Bin 0 -> 1045 bytes public/animations/CNs008Ni.ani | Bin 0 -> 1005 bytes public/animations/CNs008Pa.ani | Bin 0 -> 1305 bytes public/animations/CNs008Pe.ani | Bin 0 -> 1009 bytes public/animations/CNs008xx.ani | Bin 0 -> 1013 bytes public/animations/CNs009Br.ani | Bin 0 -> 1005 bytes public/animations/CNs009La.ani | Bin 0 -> 1179 bytes public/animations/CNs009Ma.ani | Bin 0 -> 917 bytes public/animations/CNs009Ni.ani | Bin 0 -> 1269 bytes public/animations/CNs009Pa.ani | Bin 0 -> 1757 bytes public/animations/CNs009Pe.ani | Bin 0 -> 941 bytes public/animations/CNs009xx.ani | Bin 0 -> 945 bytes public/animations/CNs010Br.ani | Bin 0 -> 1469 bytes public/animations/CNs010La.ani | Bin 0 -> 1543 bytes public/animations/CNs010Ma.ani | Bin 0 -> 1493 bytes public/animations/CNs010Ni.ani | Bin 0 -> 1541 bytes public/animations/CNs010Pa.ani | Bin 0 -> 1637 bytes public/animations/CNs010Pe.ani | Bin 0 -> 1465 bytes public/animations/CNs010xx.ani | Bin 0 -> 1469 bytes public/animations/CNs011Br.ani | Bin 0 -> 1161 bytes public/animations/CNs011La.ani | Bin 0 -> 798 bytes public/animations/CNs011Ma.ani | Bin 0 -> 1561 bytes public/animations/CNs011Ni.ani | Bin 0 -> 699 bytes public/animations/CNs011Pa.ani | Bin 0 -> 3747 bytes public/animations/CNs011xx.ani | Bin 0 -> 990 bytes public/animations/CNs012Br.ani | Bin 0 -> 2245 bytes public/animations/CNs012Ma.ani | Bin 0 -> 1593 bytes public/animations/CNs012Pa.ani | Bin 0 -> 6900 bytes public/animations/CNs012xx.ani | Bin 0 -> 1830 bytes public/animations/CNs013Br.ani | Bin 0 -> 3417 bytes public/animations/CNs013Ma.ani | Bin 0 -> 3169 bytes public/animations/CNs013Pa.ani | Bin 0 -> 3647 bytes public/animations/CNs014Br.ani | Bin 0 -> 3174 bytes public/animations/CNs0x4Ma.ani | Bin 0 -> 2657 bytes public/animations/CNs0x4Pa.ani | Bin 0 -> 2005 bytes public/animations/CNs900Br.ani | Bin 0 -> 3617 bytes public/animations/CNs901BR.ani | Bin 0 -> 3917 bytes public/animations/CNsx11La.ani | Bin 0 -> 881 bytes public/animations/CNsx11Ni.ani | Bin 0 -> 879 bytes public/animations/manifest.json | 16 + src/core/formats/AnimationParser.js | 217 ++++++ src/core/formats/SaveGameParser.js | 29 +- src/core/formats/SaveGameSerializer.js | 36 +- src/core/formats/WdbParser.js | 63 +- src/core/formats/index.js | 5 +- src/core/rendering/ActorRenderer.js | 881 +++++++++++++++++++++++++ src/core/savegame/actorConstants.js | 669 +++++++++++++++++++ src/core/savegame/constants.js | 6 + src/core/savegame/index.js | 17 + src/lib/SaveEditorPage.svelte | 13 +- src/lib/save-editor/ActorEditor.svelte | 299 +++++++++ 107 files changed, 2238 insertions(+), 13 deletions(-) create mode 100644 public/animations/CNs001Bd.ani create mode 100644 public/animations/CNs001Br.ani create mode 100644 public/animations/CNs001La.ani create mode 100644 public/animations/CNs001Ma.ani create mode 100644 public/animations/CNs001Ni.ani create mode 100644 public/animations/CNs001Pa.ani create mode 100644 public/animations/CNs001Pe.ani create mode 100644 public/animations/CNs001Pg.ani create mode 100644 public/animations/CNs001Rd.ani create mode 100644 public/animations/CNs001Sk.ani create mode 100644 public/animations/CNs001Sy.ani create mode 100644 public/animations/CNs001xx.ani create mode 100644 public/animations/CNs002Br.ani create mode 100644 public/animations/CNs002La.ani create mode 100644 public/animations/CNs002Ma.ani create mode 100644 public/animations/CNs002Ni.ani create mode 100644 public/animations/CNs002Pa.ani create mode 100644 public/animations/CNs002Pe.ani create mode 100644 public/animations/CNs002xx.ani create mode 100644 public/animations/CNs003Br.ani create mode 100644 public/animations/CNs003La.ani create mode 100644 public/animations/CNs003Ma.ani create mode 100644 public/animations/CNs003Ni.ani create mode 100644 public/animations/CNs003Pa.ani create mode 100644 public/animations/CNs003Pe.ani create mode 100644 public/animations/CNs003xx.ani create mode 100644 public/animations/CNs004Br.ani create mode 100644 public/animations/CNs004La.ani create mode 100644 public/animations/CNs004Ma.ani create mode 100644 public/animations/CNs004Ni.ani create mode 100644 public/animations/CNs004Pa.ani create mode 100644 public/animations/CNs004Pe.ani create mode 100644 public/animations/CNs004xx.ani create mode 100644 public/animations/CNs005Br.ani create mode 100644 public/animations/CNs005La.ani create mode 100644 public/animations/CNs005Ma.ani create mode 100644 public/animations/CNs005Ni.ani create mode 100644 public/animations/CNs005Pa.ani create mode 100644 public/animations/CNs005Pe.ani create mode 100644 public/animations/CNs005xx.ani create mode 100644 public/animations/CNs006Br.ani create mode 100644 public/animations/CNs006La.ani create mode 100644 public/animations/CNs006Ma.ani create mode 100644 public/animations/CNs006Ni.ani create mode 100644 public/animations/CNs006Pa.ani create mode 100644 public/animations/CNs006Pe.ani create mode 100644 public/animations/CNs006xx.ani create mode 100644 public/animations/CNs007Br.ani create mode 100644 public/animations/CNs007La.ani create mode 100644 public/animations/CNs007Ma.ani create mode 100644 public/animations/CNs007Ni.ani create mode 100644 public/animations/CNs007Pa.ani create mode 100644 public/animations/CNs007Pe.ani create mode 100644 public/animations/CNs007xx.ani create mode 100644 public/animations/CNs008Br.ani create mode 100644 public/animations/CNs008La.ani create mode 100644 public/animations/CNs008Ma.ani create mode 100644 public/animations/CNs008Ni.ani create mode 100644 public/animations/CNs008Pa.ani create mode 100644 public/animations/CNs008Pe.ani create mode 100644 public/animations/CNs008xx.ani create mode 100644 public/animations/CNs009Br.ani create mode 100644 public/animations/CNs009La.ani create mode 100644 public/animations/CNs009Ma.ani create mode 100644 public/animations/CNs009Ni.ani create mode 100644 public/animations/CNs009Pa.ani create mode 100644 public/animations/CNs009Pe.ani create mode 100644 public/animations/CNs009xx.ani create mode 100644 public/animations/CNs010Br.ani create mode 100644 public/animations/CNs010La.ani create mode 100644 public/animations/CNs010Ma.ani create mode 100644 public/animations/CNs010Ni.ani create mode 100644 public/animations/CNs010Pa.ani create mode 100644 public/animations/CNs010Pe.ani create mode 100644 public/animations/CNs010xx.ani create mode 100644 public/animations/CNs011Br.ani create mode 100644 public/animations/CNs011La.ani create mode 100644 public/animations/CNs011Ma.ani create mode 100644 public/animations/CNs011Ni.ani create mode 100644 public/animations/CNs011Pa.ani create mode 100644 public/animations/CNs011xx.ani create mode 100644 public/animations/CNs012Br.ani create mode 100644 public/animations/CNs012Ma.ani create mode 100644 public/animations/CNs012Pa.ani create mode 100644 public/animations/CNs012xx.ani create mode 100644 public/animations/CNs013Br.ani create mode 100644 public/animations/CNs013Ma.ani create mode 100644 public/animations/CNs013Pa.ani create mode 100644 public/animations/CNs014Br.ani create mode 100644 public/animations/CNs0x4Ma.ani create mode 100644 public/animations/CNs0x4Pa.ani create mode 100644 public/animations/CNs900Br.ani create mode 100644 public/animations/CNs901BR.ani create mode 100644 public/animations/CNsx11La.ani create mode 100644 public/animations/CNsx11Ni.ani create mode 100644 public/animations/manifest.json create mode 100644 src/core/formats/AnimationParser.js create mode 100644 src/core/rendering/ActorRenderer.js create mode 100644 src/core/savegame/actorConstants.js create mode 100644 src/lib/save-editor/ActorEditor.svelte diff --git a/public/animations/CNs001Bd.ani b/public/animations/CNs001Bd.ani new file mode 100644 index 0000000000000000000000000000000000000000..b1a5d31081ec58dce79e9f03180a04162a144e08 GIT binary patch literal 657 zcmWe+zz!IJ!c0)?T3F8yL%w11*+u6qyzq5vj{GCx}&nkS)7OV+MFav28Aa?S1 ziG+!R#6WIx4D!_t3W4aob|lrV{|B4>v;`(M5Ru3bNG44Q8ue z+P)*t*z5(>nxVEjE!DEmX|>&ddufa9ocnoTzkmz{`5$a6T<@jJJ$tvg_3c~kHUnzy z+eJ?H?`C+~9CuQ)Uv;_G3aAAPK>qh|bp(e#1LKOL>O1#^MB8hLZ`lozhxrN=z8;QH zr}3^_WR)P7VJGxBYdb_4hy(?jkE=V}?;RVZ?RG}Z-@#tJ#scE^wj1U47Y!Ts0Tnht sF_41z9i$WP{`b1_`;6x6?hG_x-i2Q)YWjhs5aYEX)(b=fLDs+k0PI|X;s5{u literal 0 HcmV?d00001 diff --git a/public/animations/CNs001Br.ani b/public/animations/CNs001Br.ani new file mode 100644 index 0000000000000000000000000000000000000000..0daf18395c5d9db531051b342b2fbcfcb3b2e9f0 GIT binary patch literal 1617 zcmZvcdq@;f6vk(LtoAaMBFtWYrAY~9h}!O4)^sa0H8FZgSz)Xuu4z!Aq@_erB=nRD zv>;Za)LggHoGHQr`=`(>LrF<1DczndLG(D^&dhdQx-iQtsASVWpz zYl>O&{ASN&@BzFcg@;yQ&|lDfB;JI=bLRpBlruh8RfFeVwSj`pPP`Qj_|H8v{}?hKU;kQX%0hR|l!vym*j0 z?o|h=4&tric#t~bRfmaliPz7~r%CmrO;SekW-Zokv?*(y=SIKi$`fmT=-K)D6$Y^Q zSW&;_nw2$|G>IQ#z8gk6Y!cKD4dk#(`akmJL(8SMvlk_(-%NE)ROh6+w^Rr9*WYKd zL38MR$HO$~RorO_>Nlx8)QwOb)W0TPE%7c8ua$UEuhqHKbD}x2q%a0ikTtlx~Sja z?Cdc$7~!O>)~%qrgH#76Q-dw6uc}vUn>tH6TzJ(8CpBK2%7c>{Zz}P6i3cY&-g1=( zCpDgfct?l_Lo{Bj%7aYpRRWkfWODkh1ZXS<-k5A}zPn9jU z0(#|VHZQPm@)2}prB)WCtFs%z9AffQzgY6j-P@|Kj4Uht)LIaeYJ+F!o$HE<#OExO zdJD<3a_XH4jNtW}jzfo_XhXA@%=5Ll{j1 zcJk=i(?`AF$19&X0f?y%yhaK&_CnY&-T9+kd4(Zguw6;|vD&L zsx#jrk*D?%KvCXc445GZ%oKrr_iVqc-gG}K6c8Su2Zri~5IK>xsf4BRo`z6Y)mVlHzbDbx1yQ~eJb z7+WYeh{L3c$Z~U{qvV{PO?$Xj=L;(Kow`8Ir)3MLwQ1~7dZ-SS{c`KDuFTtfdqrM7 zGV#C-$P+Q>-6Q$Fag{Wdf4xPrerrLZ+Q;$XDI)lowGbFd3Y08?l70Qpp168y>@X7g zJCytlvbG&4VNWayv4d2kkfu%_N2xi8wG>>jg9LgUaFc)w2b{4-V~46TuCdccw{GiQ zmRs?x0V8^#7wh^WWU*= zODwh7cZ?Dq>+ZIgNOh&b(LL(N1r{6@QEd%yxi?1L{F!0BHTDs-U+y4fSYpTPypr= z02c|k3y=@nn*9jK%00dHye<mS9pTEcVw_@e^N+!*G^ zgS(ax7aglGYrBJ5q|`$sIMC6CU8#K;?zs*8FeOC0|4x2&_alz1%n`K1Cz_C^_7$4^ F{0EoPpa1{> literal 0 HcmV?d00001 diff --git a/public/animations/CNs001Ma.ani b/public/animations/CNs001Ma.ani new file mode 100644 index 0000000000000000000000000000000000000000..410cc631610550b1eebe37665822f8c3b9a33f36 GIT binary patch literal 1545 zcmZuxe@qj16hD5nqKRY`bsAtUi^#G_^{&LI^zOSdtP(p~8FPb)(4rtaSk+7kVIup!yDK})Uh+-v^ZMSq z&*!~&UmfxP7K7bz;_=zOar?$t{M$X{KRgxV$+j>?N+4|HCi)cT6dx&g|Fuv@Wz|#B zA1nVKtUO2`nNJ1o#mSgB?omtD?BB#d!W8*`{hBiR z(`n%VW&;EX{hE8m(TfiQG8=Fu;FFtYwyw;#<^gXa=1$(0_SP-8n1;$o7UZch1Q0|V zK3aS%D^_OY7sy$AxMapUUVdJCrucf^O zcgf~ZiU;}taS!afnNwZwc`kHR@Sq)hLpb-3&m7d$&^8|QKXF#0l%yUywP=7Uw$}2{ z1FpUTtxk0@UAl9gwEds+z~oPyJBV{{Tsh+I>p~=cw`9Ny6j;oPg!>uu#G&#pd}33? z2Z?GtNdlu(Afj{?S&|z@rx|9^t0NA7LgRGO;cX#7f(KK}#ngmwBNw0S!ju-oSCrm;0^&Tz>3k(c#~-sk)@iWO+?84P3_ev54BSE^}xO z$IYtxK!Eoi$Mxc0vSnyu?c}2GC>m-sHw5MxwU_@==89|-{hOO z%r>0p$5EPReNY|@%7gHUo$1k+ik$;mq+4K@m8-Cj-1)@bGlC~ zcn7`mytf0|s>X1ofYnI5s28#ff1R#N$DcbcG1({W=Mxs0TyOCG-JOQQ#V;G-``J3i zqMJKx`~l}h!>{!^BlO2G%A(@$Q}~?^T?R^1Brf5xx`j!;EJJ51e-=E^ZGp>gl}Wh*rneqjzTt&3W=8+kA3&BG ILy}_u0%hI#i2wiq literal 0 HcmV?d00001 diff --git a/public/animations/CNs001Ni.ani b/public/animations/CNs001Ni.ani new file mode 100644 index 0000000000000000000000000000000000000000..8f4ba366a01f202155039bb976c706a09d9665e9 GIT binary patch literal 1233 zcmZuxe@GK?6u+Bp3DLq8L`fM16`?{uES=tcS(~TP+GGqu@(*EcxdK=CM_S-Uff$8G zWsD%U`ll8IMW#-7C#Z$}7ez}@5;82v%+l6Ka=mxAJHAN|-tFGo=kwnCyzh6*XZ~+2 zq|tN1oBW>BI{19G>fabrl)<-1U}neoUB5@Z4=G<^Di8dyLvU8RoDQtRupb@%ftY`a z?{1!}Kpq_SC8$VRbwy;GLO3zhV4k8M5ejHImqc+)@ILy+i2NScd&_ z<6VdmtxULdUQlPM#+(X55IAZEFt0t{V>${en?cbDtqM2R+hC z4>`SF&#=?L?WMRakXsJ7kvpa&r*bFt_!c6Nf2!v4K!xi}E4f(b)B2*aAJF3`)kDR@ z0mvJ%3)z)?D=w5~16SfwbS{oWI>z;(g^2R0Jd0o@*s`5!2`gdeq2({CB}kRIi@BxR znm~e#OHnxJ%49M@So^bmbRT@j%X7$St5fspp;}x=wL?vgdik!jf!vDjuS5lOEN_gB zeX1qz1rzBVwsY?eTo-ZLsfl_LHe{qo+g`En=4}xV=4`!{1io%36X(3#rbLH`(>?_5 zq+l9K4BN#?;9>{0UVpY8PKS2_mut0Kj%lOqiQVAd&UZ;tLxe*j9V<7oeIZC5r8*PQ zcwu>M%9sF@eq|2JqhWazuAN=7#3zl;7)bt(ONw$TR+Nh+K4R{kkjfu#G#!=1_S8a2(*wSsW@K0o)0|rL#B`fc1F5RRFFP>fu=G4^&pJ&G!1u zRk2s-lLN(G$e_?Dv@AX6ZKY=h%h?b_hBPm#8QFv``ncGnNd(1& zqOhPgy(lfbXp3}n=PY7I(M6FsjiQLYq(UuA0@3yU-?OvcjUIT<%m3l|_&v`#TcPg% zrl)S4W%P8kcC_ZuyQkovHc*{uzw|6Y=V)o)aJ{Wvnaa)7Z}L>VyQ$$;|M4Hni~LMY ztD~WA_P4#{w4orTR)>qW)$Oa7tY(|F9vnLT$NIL&K-)X<%8BVJm+4zRftA+ z;(+d^!9zyeL78f1AcQaI4hotNl<-BkIFP~@;^MFiT9#}U%;j1x?p%66V5v2i+8R~b z^Y_+@)}(9LF!{6+w7h1t`mfSSGVSeBXksT0Xm4b71TB2S92saQLMo>ZR8E;p*hgKIKMb2aTiE^0IxbAbntw zdo8A>G{)9}%fk33ei`THJY;a*7Y&4qcQ(u$qbQ&4LwOj=W61P-N|YT| z;-3wqu*9oQ$!T@US5Z4T<twDR_`)KhD@7{ii+{AW#za_Jv}_#-gVNK}%_ zg7UC3CBzJ~wsv<#7W7w9BqPd%G@`AQ6e|j?_r33Soze&IcF%jy^WO7(_gymc|D}UA zmI2D+@_1aeF>v)fPTN@yoy99tCyST|zEabr(O&Vpf_dYhsk~`H^vBTq(-lF}>Gj#t z$5@JaGs|#;Oofad3VPc^$udS#!%i*M|_EDq7 z{-@@iA@DR0Ra&e^k?s>$Pr|JyMYukJg_)I}1+cs@r-mo@Fx%}e4=G$dqVn(w$eW|{ z6gB;Wt(TqIIK|qRtTZuHbH*$3VJ`}`c#$=Ayv7>Zl^0rDY6GnW|HYDd_yp{MHFcgc zf>{O^JM4uOQ5@Y!t7Esik--UY(P@@`9BmYSL805@z^egX0C=guL$@4nB94$%YV)-TBLTO7Pl)oK0WSe~ z=njttZe)SYx>vQfd8mhd^|ebBEBja!YB4S_oi*#6G`*~OTV;-<8k45 z46eSX$xVmEi4O*noZd>4YcNedFJ+VQ*&OL+Uo-nUw}Hjv)tL_BuT3Jiw(Jykt?FSh zxie%X1-wam7;v+feQhizzcZUdP(PE~AdGCaJ{lpi)r+j-z|h&NX%z_eR);$iI_mYw1@L?;W;QNvKCV1;NuLh=-f{>t{l6M kuC!RDt8W88)Do+5tP_U%w^gpGvZTB}d>3U}Ol9)#Uv8XQi~s-t literal 0 HcmV?d00001 diff --git a/public/animations/CNs001Pg.ani b/public/animations/CNs001Pg.ani new file mode 100644 index 0000000000000000000000000000000000000000..b50aeaef6361b847bb5d3ed9bd8f15a1bbbfe984 GIT binary patch literal 657 zcmWe+zz!IJ!c0&c;0|K30kM;(w`+hqNNxg<0Et567(hY{46lN%7KjGg0p;NQK*68; z!E#{L96+Y0pPRpXkiQ>T3F8yL%w1RG?CqnA-&sRM{?4egXBEC?3(*B3nSm4w5Igz1 zM8d>DVjwp;2Knj+g+TOPJCbVG|AWnb+5!_Bh{$g*9s6iDoBgwAHt$`oTeugb8zjRH z#LhmB;c%^nD<|7t{m{Q}U&4G_h}IXauJ-*K8TX_dQ`)~le9|6}RuBMb1zGCj2D4Q! zZQqe+Z1#d`%}`sNmTKAOwA${!y|l%4&iy>FUqFU}{13JjuJ_XAp1s@L`t~h%n*p`< z?II`pcQZU~jytK@uew}o1=IotApd)~I=Vm{x8kV!&V3=#_FCdwc0=T0z5<1>ha=Q! zyek)3CCFvi2|do*4p9aoLBZzZ>JImN$3|(pol*05uvf3KfEeF)qulLURQpfQNQlaKx5`z__d;@A7~2s5WH)VXduWM7ytm%M1rgU literal 0 HcmV?d00001 diff --git a/public/animations/CNs001Rd.ani b/public/animations/CNs001Rd.ani new file mode 100644 index 0000000000000000000000000000000000000000..6957838d8a467fb542cbe1c3c293e56ebd5c2134 GIT binary patch literal 657 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 CB9pWL literal 0 HcmV?d00001 diff --git a/public/animations/CNs002La.ani b/public/animations/CNs002La.ani new file mode 100644 index 0000000000000000000000000000000000000000..ee3e7eb0ca2ccb6edb18f9f7bc4269b8f9f439da GIT binary patch literal 1447 zcmZuxe@snr6u+;$mzcMCi~NiUO-r~haPvfsU~h4^nPcXYY@RKM)7G2NnLxR?CTyQzpvDiVk=)`_U4O~O z;+$Rp_ZD#90{1C!b0OxmnCUTH8yV~@T1ZTK9JCE*3 zO6PX!lGvW);1E>y)up4$(fbN!~-`jO~hn$jpW1ZDdO9#qYYx|`vxQ`eHr0jIlvHNJR>*Yx1%lv;?pvI}Y)< z0v`c40Ju=V<+iGMs4C%Vo4i71EV#((C)ZSA1kW(AC2>(n@}4rP%bEi0g~{plU~9}g z!MYgZ+G{FP`WUv(%nLYTjAaDcsu6b|a99^(&t}=Gua5KN%K(RJ%OIBH8)kiw*v$Io z^8|AbElxyyjC*m~n$x%wgBp6aG$b5(N?*DaRmH27wA-kowT&k7WWgx`JL~C8p%#r( zEHSe}-I)tgXFF0NY1TV`+PNoz|@_oFR1 zU2N{v(9tsw$jTMItXv3Ar-tHAaZ%-Xk~{H=bSI7XqG;N}Y4oRVqhPQdA$TWtz!AV1 zTsTzT1h`Fr8+74N0OsQW7Y4XI$cJqWeFP-s?%rA-9TRGe$Hi+#izHp?%hEl57Et4F zZ8*7861wG=78R)fjbVN~xNFf%Bcf#H^}7KLV#0bF=xeoMS4v-kyKe(OOwkdpztgCS UGDE807Ebp(qYi0GU#3aVKeUU%bN~PV literal 0 HcmV?d00001 diff --git a/public/animations/CNs002Ma.ani b/public/animations/CNs002Ma.ani new file mode 100644 index 0000000000000000000000000000000000000000..8bf4b55c44b222d75425e58e10f17ee654fc21b3 GIT binary patch literal 1461 zcmZuxeM}Q)7{4MdXd+ovrVB8WMJ!ncdUwpB^zPj;sG^K^$lO32sDmJCz+rU$Ay=(q z61B`Jq?vA_P6PzudYxM6U7fN&jL{GYYzE`Uu)46W(~J=^KiK=+U0F1Hlec%z^ZwrF ze!u6VM&kdM4!dDT;=Ot8=Cu*{*%$SnJr&yufJjQ@H%f zcRrVW5$bt=^&#fld6fURDXRT;6o=1i{Im3`oj~nn5;vam-_q=f(KPne+p<48N;v!yUGvI+Q*|!4ZTV zrAX-_Hteu*bS(8nALs=i-H|R|xuluXA7OsS+VC;0xlr zzkcDQrVeZpAvaH5H7I`OAd^t|fLdd%6(K)DeHB`f(8*pmo^vOD^`!_o`5oUK!FMa} z9jEr0>Oi5gfE6&#v1pY4Or9$TEBATjrjYjq%1SxmPMDr1LkRWgA85V|Yi0pK=)t9H`jJQQvfkhz**1y+e*3@UQCBE0b;y5G@MafFz~y2e zkB;8Upz3CRN>H@!irYWX@<~4IhSlH@FrCax81ORE*n!aiDC)E9Gdw(a(CO&60L) zI>A7W!|@hr@qh*>gCSLvO>()>zh63MamuGHE=^j*6CPQf$C#P!V`=5jV$2}zWgolq$|J6Gr8wNz zOBXCmGDE*tJmb;LZ8q_s>oR?<-e?B>^glc*J`*o)f80q^`a&O!s0rgt+hmOtKavnV4YQrDbGA>`0F}?M~l9JcU N*%`wX3J8FB{R_Wx!*l=u literal 0 HcmV?d00001 diff --git a/public/animations/CNs002Ni.ani b/public/animations/CNs002Ni.ani new file mode 100644 index 0000000000000000000000000000000000000000..00f04620fc9504dad5520a024d50b8846409a6bf GIT binary patch literal 1201 zcmZuwYe*DP6yEW%YSD&U5G7?<6hsP1mb-fQvZ;=ka~#-cZf|jRQBX z09YlCykb9izX|%g4}tx^Hp~JvY4g8G#r;%qg_9F#+OmHbGpjd5w;$VEt7b{#k!Weo zpY7j!bp4stHPY@twx7Q+LiELtB#*M6J5(0OO^H%)5c_Wk=_G~QK1t9GHmDg7i6GO1 z-zC-!x`JVyv+}lNyUi*?xQ|vxpVKjIYAIicV>o|!X<=ECk|XUI4At;F%w*dveW{%o ziQSGcZaE-^!ou8jxNZTbFR52)NnV0U@o9i3e$w)t`@8WA4Y__Oyh2~kW*|sYMjXKOgY>!WDCHOX4Yv=?081cq}K45he{4cyd)W}{|MXzNQ zsC7}NMX2%9r-l~~1CTRk(i<+ZW5I>;pw&_KX3rBK0hXxuDS@M2+EN0j z$+0T|F{&&$QpBzLw1S5UYxE8VsW|kY;o&=LOU#lt@bC`JY)?~%Ha3)RYF^XQdmX3Z zWkopW=X5&7>uJw`g^ZuREns)klR30iyD7HUL<_xl+XNK;a!vv*{dSm6R^*d??gjz7 zdkV20UN5-^EP@AOQ9N(HF32|uKI{f(j*TZ+%;(isPCD<9cT3HpnA=Y9zG%4mGQ$fbGgEsReS1^r z;hV(Ae5*puZ@rfC8@hPX*B)E+TNjViw)!w18qP5#$(+cp*^^htTw+ExJ-(;AU;eGC aiI+CTtQ^H1&Q4?Qfg+QB=29<%$HYW0y8S# zhXuml631gV>%+BuBCHU8OGV3aqvyi=IoKimV@sAT*9P)|wt*bL(0sk!_Rn17{UA2P zN)90H>F4I}9^~%_R>JtiFLT$*%=>mzi{DwZfz>fEG}!CywcZTmG=MP3b@SNj?RVF0 z+6z^{0CFKnY*XDPTbLNgRUollNa{c?1Bq=$QU?kPkR9nyShm9Sf@o$S2D!k;)m=9z z1me1mjna1h|MPdS-8g9hb{*p@w{rV;%`N+Y3L8Kes7!cRNv> z2e$&`Jdm|OogfSbAm@EXvlbS3$N>rqJmdfcMJ*@@zBaeO?SPpJN^nm8E|D-kEI=KD zeBl9l?MSL!{|`3%X$wqjKW%=_B>>d{UBZek1~w2#fC9?d$1xmkt>MbawpTy&@7tF!-xib~ z7#Q0=*W0iC*0*QDPI=3KZ+v?|#-WRW6oLTAIFKjc;eJgoZQqe+Z1#d`%}`(5+!JkI zkHgL8>uhhESJ{HX z^9EG!ZlK=3K)oO36-o+dn=V+gK;iA->gWPV+AEH#@7xy>ZLcN1Wj9zFY$wR8pbYBa2u)(VD;HTM$Yt0G LJ#WY`$bqG`~@Zdtd+8t?!N=&zlxHy6S=o< zKL1OMfV8Kdo4`(1067KST98v-BfJ4t2XYEX4CoZNc~B!5Sbz*Cf0sxY9~MxK zLB8;Sx^^VhuKx#{{j>!pHW1ep$2i;Py;ZiqTb^nwy{BU@$aV8VOzdZdnC$yDprvClJCviIUkwyoLG zxfkRbP$Yo@%Gt*;9PSjum6L6+e(2w~FJZndD3vlWwtcR*U;C|Z&w`!umI2@R_5h7z zMHd4p1Obq7AdkVr{hD6dz9Y}r>;=`Dp+5V&-^hO3Q`7yc=hxZZGOw})`7Bc>$bL_e zfc*wzuf5(NTd{_4aQ6_-t%<&9}(-%Vz`f z8oC(BJP-iIqlc@b3nXc;II6yLUr4mQmiU(4U}<=0f-;JSBQ)9Zu3Ti5AeUh$^f+rf IL>ZU_0F|;GUH||9 literal 0 HcmV?d00001 diff --git a/public/animations/CNs002xx.ani b/public/animations/CNs002xx.ani new file mode 100644 index 0000000000000000000000000000000000000000..8f2082a42e03919610fd0a462c6a010826fb94a6 GIT binary patch literal 1472 zcmZuxeN0nV6z}7sHZFAQqS3evFvJrA)vnE96~@(Y^w^nmCb=R@50G;!b#8xr%D7RqL|E07fBR7R3$34y zO%-*1`I9z$$rt98>H%7D;&Q01^t5p+s12%rli9;sTA*l8<5L3~he;zR<14tzfE6>AaK@$rqO5fce{I550V;Ud@Gi z=S%&>4p4Iu4~&eoW~(!I7uGQF(6W(TT(qIsrLdTa9S}~=YsuVUwkj;XUROg+`cC1c z+DU6syi1f-4!%N-A5s6#^l34eHz@+~z~=`kx!G2cbN7h1IQQbyu^x`&eBd zUTVf1dv=Km*>vF({>Pb9l2qQ#Didui10j&L+ZNg0-uSK* zd!Qx5t4-EaL~yR!MtU~XAlQ3@eQwi59&E?&?xjxcf)w{<@^?;uik{-q${Xl$catzv zaiwpdI>>|BJ*SasJX!q(ms|LG^C2EA-eTva3^xc4c3w3*PcFh9=pUJdisiYHlVYK* zRr=L-c9BNX6F=FW>8*Tk`{v50dMk2axX1i$*|`eYVBFX<=-NZ|zeJ+)wyF~PGe3Kg zYTAX4H}ldf1s2||gyD(nw{Oc+wcIkaMkw5u5X_(bZUICMmwvB`hUgX=EP%m2JiJIZ zr#EAQ6+|i+@zGdeOl`UhCQ{bQ1}e$;SZ&TwiiS&_^;7YDt9mE+9X#6ZrJDUY|7h%W z3-3|#(QY*tV%EhD(b#J@RUc}@aIavYYALMBkbbizgxkjxi{Ik$>Gx@GM=4SR82D?I z#1B)fz5`|9lW7bLyQ$m}S&GvDa?5BVjhF&zSq7O|fZmztk^cu+jWZg(yM46$&O=oK RK%O{wgnoG7Qgab2+h69>cAEeI literal 0 HcmV?d00001 diff --git a/public/animations/CNs003Br.ani b/public/animations/CNs003Br.ani new file mode 100644 index 0000000000000000000000000000000000000000..35bb8b0d0bce826ae87491ea0e74664138d72426 GIT binary patch literal 1497 zcmZvcT}TvB6vyw?b}d3pEBB#V3BfHRaWlwL_p+(0k@=OB8I={)u1c62NhqygghA*- zA4EuMP-ZBLu3u~J*g;93Sd@SQ^2hPlJ<~Qe_^FQ}a2>HKJ z2&1nCKT~QsTV%al>Icsy8s8X%Wd;O(NE+DHZFw8=`-N9ht^*mS%+#FB3?8A=8J6B= z%Ll_H1{Gfz>F_ke-)pc)d#$Vu)W!#iX;J(SY(Ex!CWz{J3JH@fDm19WV4xx&=>Gri4LmZjp0=?R6xe@ zVdxfYC5N3UIq1TN!-|z0QfOEeR-A4UOMbQ^oU6;Kb}r4$I{A_TSh*|?Qkf1)_Tg|q zN)Af);ZS%bhZ|P8E=JH+f$z^uJ|=8+LLaJatn*sC3&NG6dxv4nIpiJ2r2Cwq_LrL7 zYuKG248t8+&Ke{1T>fHNpLNjg!p_`VFR;foZ@J<3VXoqVMi&kPxjNu-MGiYtaySf$ zJ0fz}nZ%8V`EVE#C#?%r^QRB1N=i2#^_Hw}zMVKRU(0a?^@(_n3B9pkW37udeGSg= zy;+MveUMJ2j3J1I=}-XgY$v17bM8-S77|${DlqJCW<{spaWVZ5bYYjK+E9TLn=9DZ zsGeKeGaa+tGv0;@Aa@ZsqpnJ)0S*;7DRLxkIwlG@RA5Bp^14fS4iz{B+-u0E5;;`h zmB>|oF622ZSNhrT3V3TCdvnXxWvbztO!q|z{C?Y*>2FX%VsCw%`A<*+N}!BkJ~Z4b zX(`5J;W$1W&^fu><19+k@m$5fm-bk+D?WCv^`;K{$6gO|=B7gEH`uOqV6Q_Ei_lsf zpp{{rT^oyiVNBir(qREE{7Q=h>LAO@HdgKk?$AH^xoa?gya^YV0-^Zx*I+=DXSV+2?uh zd!Fz6HjexM!4r&WqFGj4RaqQC59Nu|0KyD|UPHpUr0T^lClsC6UICgxGuL6yUsagz zKqHR7=h)WMw(X9sY3HvmKxeEq25TsnRxIqWfsR2e({v1YBuz8Typ^k-DO|5<%j5;N z#O}3coa?S;qPwx41rh(SbFDOTBmNvc*JA#`bNrW!c1d%`nw3ibu=vixDmH@FT&)z` z+@F5*q+gEr$Z3O79RC+-?~=A|_==n}GgbD@)4su$gfs9XvxnI ze}0=qCNv4ISdM>0+B>9Ga^GT>?pC=mj^q8Lo%pj^$(`&K`IaZyfw`J?cPz_D%DK;+ zu|J7$F-F?^q;+)ePg^<=!LT}vD4KAeXAjwLwswB9eV$BqBv3lT~9RB1IzXnKva8 zMv=h2s@>r4NcR2i5xs~diSRad02i(9U_m(x4=)p?)({{6Ebd~sdIb^{A+u(1B;F1eRcKWw+od(gN* z9(}b>%>dY(t`ydvINE`J$Ux_f1~WQNiGwnZ0I(*Xg*nGFW%R=l(0TWMh>!!5U%QbD zuyP#& z&m$fZ8X#wEV@{rIq!b*r&K)hYVgHdSTBwo>rpC>0! zKP+GWu&N1#J$&P@^PaZXE1%d8s}UJ%Eux5jY9#w)OqR8$C9`xQ22M@WG4#j4-CDge zJ4aKVxSZ@!)^CW`go}={_gf}$c(inQ-A*h Dyy!eE literal 0 HcmV?d00001 diff --git a/public/animations/CNs003Ma.ani b/public/animations/CNs003Ma.ani new file mode 100644 index 0000000000000000000000000000000000000000..e25df2b3f9ebd494b0d0e77e814f008b526be096 GIT binary patch literal 1749 zcmZvde@q)?7{_}s3e3O)!&L=c7L__C&=#gBZO=MZwgmYlnYb~9Kc;1F215oq7wO!b zh^9^4rm|FFW=vU1#$AEZ*&e9HA4L}gsr!RSmNna$*)*9}2a))EkGr-bzR4T7_dd^a z@AG+oG)?jUjiF)m71Z}O>}`mnzfG#!pJ^zj(OU#4y4lwrs?FFwy7US|Nz}KNn=4AI zE6PQL36(dV>usDh9&f%;oC1*H?M>{ad(Rs2fc7V&U=p{ruv2k|je~(<{g;hS!`1jG zm&LBvF3#rZW)h;@Kw#KA+UWF6Bt|)A)oJau_)OlPtYDI)d~V7&LivX8P%tj(yOSA% z8pr-Z&Y2SBhA7`xl&^bLlyp8kv zDIZM9od41h?$lO0kDBADE2)=lF3qj3Su(YU%QULoRl|;_zb{$|h`H&iVy{l8kI?^C zbeQ_9sF}^G?-cs@pnj_OjE_v=6x)Y=~mNIh@JoI?iih@>xa6z9?b6-FU1M?KSl49SY*dWD{ z0Wp^Gw{E<)-^JfrmtTNt7b$jzVq3$xu8(iIb(kGmB6U<-W|qo5vtg~^sBHJlU;oV` zmP>$@1u8def*1wjFg8Jq0xu~rP`T713bdqv$AZTw(2)Xh!DAG7Nr8dNja8r{1w0lv zMuCnLa9G?J1zu8Mz#)M?Nr8?Oxa7IxFg;mO@~r$UFSx1tsmOu{{kx3zf0XP8-GzFw z1je8JI(zbdt*|}Z>TUk+EEbpXuX~a0vHcwk^57iA)kqwwUCv=4Wp10q zp)#2pm-1oJWKRArW{pjWS#2saJrkL-w)d!E`VYGx)OQ(hPbiopH{000u+7`;S^HRJ zIMqw(5|)nvL_<0Vi0px2-V1{NvXV{9J|r0k#9P>x8ZHQ$(G%)5ugq{TuruobJ2{~h z<{u4d*5CM)r6>yaEQ|q zXBkV%#`yua>z)F>YC2n*aa+ literal 0 HcmV?d00001 diff --git a/public/animations/CNs003Ni.ani b/public/animations/CNs003Ni.ani new file mode 100644 index 0000000000000000000000000000000000000000..cde9468de43bfb971b510da7efd2b28a2ccbca83 GIT binary patch literal 1369 zcmZvcTSyd97{}*mu0?2QULL}vmo|zbqX#Q@PMbO!=z2-$4oYO+2*n}<$v_DoYJJec zWWi0W+$dsoQ`g+dN>i(cE_i`#BZ`nrGYpCD-+yLZ#<361nLYpc?RUQKKW8RP_b&X$}3{<%!4L1>_a$hvu^I(-r)^PnR1U5za zW;I;*N`ZCIzLJK!6(X?D#C_Fp_EiFN5tpLj8o~rNt>bD}3+%3rtBe%bjE;j-ojMLZ z2huRmNJ~!HoSmaORpMMDMt5a(+x88|2Lobr9a(&GB>X=8K8HcB;KDI41RT(CicH;7 zEauReo}(_7aHv?%fkL1vRGcRHO1`-%R<>oTPK9po>TAPVnp2F?q-vVfg#t`OHsTn};R;vsR@d|WYcF#Zj3jl{M1ICSxV zxK`pGQ$BDo9(&lVW8$DU$^;En<8Yv=#5D7Mf5~xj4GRO!h!LQ zqnxVYmU>s5cixJ=?s5eEkbNvj|(b}GggLHSTF9KeS~all{m%U2gXosw0)AvO9e2>4mWr9Ytw zrk>{bg5RMDut2XN9|GqhHQAh`zItr~Mz@@Em~V`B-BWJ+UB|~P)$BX&A&!=vxoZo= M@b^VsDyGc;0OWL3)Bpeg literal 0 HcmV?d00001 diff --git a/public/animations/CNs003Pa.ani b/public/animations/CNs003Pa.ani new file mode 100644 index 0000000000000000000000000000000000000000..52d2a4fa13c934e98d9284ddafc135c54442acf4 GIT binary patch literal 1369 zcmZvcT}V_x6vyxQu@>Z_LN1hL5x8U@xDaIJ?y#v>1M{n;%&_#KQj^&5!G{Eejp!lV zhbSThHHb0@^CN9__vXV2r9PCU^$_)tL}hM28hf!tf?t&5}Qwv5$t3Ym;n@dv4@CTdR}s23zTT#8j@naEKWYdBOaa-d+S3Kf^gddUxWWh$NJ*8~LAZNp(VlA-uctXiWII@L8AtmxbTz-j}BdAI`eWdv-XK>XnvY z^YgwCx(IJS&+m1-RVF{AhngmOLJ%HYCvhH~Ll+_k;SKJ%&Y=r~o6_?^c!M+cMb)?* zs4Bn2eIiz}qxXh$cFC_OwOvlUuM)eu+{PcRbOgHtnFq>OtU*ebD8>N7kPZUG&T($< zIVJqr!c((+x&o8&_54EJJ0)lQjy?U-`+g`;lhMj&UOJTM{`s7pkvIKN0Nh35+;*Sc zMjRA4t#eZDd`>oTP+&^usz$sj2L;X$_loi*>Kqi9*17htjVgz7jSq{eK&(5=q}J+y$2KafZ{pDvu#6T>T*nV ae9Vg7`gKq4y?d~BYrQM&Q{#w+spCH(tW3ZF literal 0 HcmV?d00001 diff --git a/public/animations/CNs003Pe.ani b/public/animations/CNs003Pe.ani new file mode 100644 index 0000000000000000000000000000000000000000..3cbd327778c2e1cf38e1c06ff92024ccc567c10d GIT binary patch literal 1373 zcmZvcT}TvB6vyYfsYPh4mHW_#MBtWDtPo`7?qyR)1M_2B%8be$EH#NOA0!eKjPN1! zp${Si*C5I$#58ryoqSlKvYg~?r3$2pa z4Aq-6as8_$)=lfmChk#!#J&;t!^HX5NUVprToZT0DzRCCYg#L@rvg{EUSbh}gHyc% zhn_7o;CX&d?oN+abE>*)gUi-e+~0cmWkx(8Hs4*$rzeu0(|rR5xq=^#s}XR(#HnfO zmSQP~&P0y7SjIuI$bmvY6%^;oy5xsDQk2%BroiItOhA=Pz|v*0kzzTLyo|#Ji5y8@ z#=&@z!*LbYMFz2g#(~1@W9kE44CCFNLs8==L*=fPUvd1Fe|M&4d?@uYZ{M1(lrGc` z*1YHpqKnX$v;1E3J7wfUa1JqY8$wG!vmIdma%Fy7#f=p4E*xKX_xj5j!AU8u(4 zfU2x~_tB{2^p5M!iMbX$8l?Fqwc_nrGlS3{a$el-LWTMnCgN9CpOmc@v(GeaZlK>J@U2-4uCsPoZH@L zw-E;iPU@VL5q6{z2M0!VuB6YWa&X`jaj&Uftj@uKS31}5rAp->*Z8og4n#GNzquCd zm7A6O@@?IMnBVnW{+pQT9B9j^_>-6k3&b4iA#g78a@^V4tM_2o9#Fi;d77=~nK~VH b9Urqiw|?9cI_~Z(+g#yF`c&1YVe0iCD~?z{ literal 0 HcmV?d00001 diff --git a/public/animations/CNs003xx.ani b/public/animations/CNs003xx.ani new file mode 100644 index 0000000000000000000000000000000000000000..e59cf0daccaba4d472fda596d97c53139c78cb78 GIT binary patch literal 1377 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 G{@-_*BIC~h literal 0 HcmV?d00001 diff --git a/public/animations/CNs004xx.ani b/public/animations/CNs004xx.ani new file mode 100644 index 0000000000000000000000000000000000000000..1939a7c86c7661c5b4cb1a513e178092485811e9 GIT binary patch literal 1581 zcmZvcZA=tb6o&6$83hG_5UsVqp9VIjkn&L?urt?HSR^8g8yc#$$R7>b8i=G7jX^BU zR-@TXtXR`xTn)i6;L)I4I*>0@Jji#A4m?UTO%eoy+IfmO^d*@P-hsd$LLFusRao3d{CQEF#>n z^_nQx<|R#>xA|g23AeYuMLc>nF6nknr|$=zaG!jn-J@bdwLb?7ge-!|!z|3pPqvl>RYyBliWYL6hfAdL`o zi@Ir@?qUSt+`wC>fTfi;b%TAS3hht`mnud{*H@Ls)6wKk*UsXe^`i}jY=t?IRUkS6P9?I4Hx zc8ZQ?JU#8qFb3Uj`>~R??`snurp_A2dtDN`9Ub0IzvExA#@aN=bLv|O-EIY48|eI? z`vY|7w&ky4I%1CTbzdoz?q;5p(Cvo`4|QXpL$~*UcLsRh0Iv&p=vLLuDf^;Zn*TUeGgR$~+0)AHrXPY@pTj%UvXHP;_ z@m!=AaG?upD8zv{3`%=0hEjI1zCU>yomj6VC8g}5*$u-it7DZ=v--S%N!{OBMUCrQ zME(Q6_m-(iz@(U>%K+U0&|y-WqAKWM{eb8RkCl#mdO^UXR9>dS!=zMRIPeC5he@ft zG=+yrsl0CBeE~d7O68?1JhY|0^ns)_Z?^_cNI#qH%w;~KL>2PlFg3r7Gzz`v)>ix% zX~d`&){ut-pRJCvwc1_=Ji^k#CyOBelIZWCLe?o-p$K zVS`?5+7$483@3WE{k}!nyv)CxM|6Edt2jAN;(yI(ktp2S9w_lgMv&(_mC@G0V-0>x z1fZM(=S8t1aMDj`g6v!JRq7m#Iv?0M+|hi!+opdjJ6|)I33+Ty93`=J==ng3j8vif z<;{=5vv555c;;p2Bx(vt?(wb|nR?dXe7$tI_5&7_aJxth}&#FUc zys-&Zgct|*qYnG}{`okt2i>MBq*!HVlhO4�RONfnO3$mZxISz9z;~^a;^`=<$f@}-$O_o|&9Mr*$lxwzyHZss( zE;Ie)qYLH@5~#)2gmzbl0oki>%f6&cOFp%cxI_Q48T?CZk530!Xs`+yFP5NxC(Prf6BmSQ_69r|XXsM4!aNMvhr z3YvcyQRhsH$LxyFD3}p=u!}c82IXNud1yTJo&re~RR0(s2@@Jg=PXS+f8o)h!v?O; zwSJ>?_Q_=_-FbNx%E-DYi!7S-1mspJbp8_i4q~63@j>TrurCYyVzF-s`=Ehfy*rE# zu!0{C-Fb-Z+2Cn!ud0Qzs4mf&Y)x7Z`(;F`KLUx=eKlS8TL2yK=EtBu3^)h5*>WYx zwB8fDs6s7Tv!bUDsMs1lGZ^&yoyW-;?pG^cuUJ rwQI-PoIUm>i);`Ho&xxh;GaQYKQpbCIY#tsiY&h-B_KXf<-e)SuH+1hcct8$Hib|8D)uf$d5tJAhM^IkO!So1;TLw zN;I;Dbs>ZpLcKg`t~Q+O{iNtzYs)u+;t@5bGV47<{LXIiWbXS55rHsBU@8m*@2H5Qyxc8;Ee`YK#? zx6jCm^|PXIR?U@LN#=|eMe5xSYx#Yd43(4t*ACp>H^*8^uHI{g-tMvEqs_8 zO1;lN;GoO{4kU7O#zH?x8vtqS4m^~LwyrR?=rSJh(y4_cBdnaNjT2aJ_cTe-f(4{$ zKw5lJrgUAwkOk$Utv29xO%9Tc9n@XK<2AxaGGHVt_V}A^Z3R|T@(%ibg1&<<6QxNH zeU?D~QSh)#qg>70Ngn@{SWoa>;TwU>R%(@_z6~GsB+byjVDAK>Wyo%IqLwI&;(WDOI z^Oh1hgbS5Df0|8d#D3~*smx_NTH``xh^vM81h{^{p|WQz?i`E54mB+9E8rkpbQAc& zb4pYvtQwXkZ9F3H`B+I&IrVZRa&zY9nm+4E(_n?u>(ne-#I~_iT!m9`RSGs-=bpIrTS7iI)4H< z9pG93_Zx8N+y*#5;Q9d93OF=?`|SXn6L7q{C*&wA@WIp0hqZNeA9S^2%@PtT8oV52>st~R)XW3`LlWd|MtE2z5oB; z`tsf{d^`=q6OYv8smlfU+3Y=_i?BXeE(}(A!FC&CgcoUTy>2>hY><7CfL%v; zz0XG3EiS!Cm>Ad>cG5o4W8nSEQ!d*7ap^^))r54nz~S%3t~d826SMsy9N)V1{>C6> z+NQaVHkU3IdsH53O?Dg+vi1(`NTS!a%`wE=6@UIdWuak*1JeO8FBh*@7w&MdI3D;h z1dq8owO$je*Fhb4{4U)ZbJd!5(z)@m0^W1iy}$xX77DQ{X!rx82-C~5Gtd{?MI_c4 z0ir7g3hh7orInilD!;oKXhn?2TRW&b`133|X5#GN$?eZ9ebi#c#C?$m5n*Cp=St#v z&k+T#FC%wuSBDxpDnfEx^<3LHglKQQY_5f^#=@<0^}KGMMEK==qGYpRZQz%S$Z$%z zd5G$NgF?5|YTa3gp7(%4KmMwqBjZYHof}&% zYzC}dq|cI|?9RW>LKY1z6!JZzpi{4PgqGbMLvZz2ZBGdsmE6;dcK;P9~xMvuwGnMNxR+_{;I48nffp-hSO z+)u?;$acxm^@vWZCLf0ND!a_bt&mv&hI z$ZuMbQ2Khl{nT_BA^5{U0Qqff7lrM{VY}jr#|`Es0pr#YEktXIgw|BY5c&E(ol)LJl3 z5p~H)+-y9l#2ty(kNy3e5>KlwDKc?Wem%|@OBQM_ZVPK{(a35(ERn+ucY>e~%~83w zctll2q-Mfu5+l|Ds=GeudJbws-RdnUccF}aygkR3*5peA)-jw$MCCA3(Wb-(L-)fP zz>3jqMzT?>OsVI0lM}3Cq;NeUUHPNorf)*(W{k_28v@Lqa{00IbPrH$4MoYI&66K(r7?WFhFcBYfM>jS&>ZYRwMh(ZndVmsp# z|G>8dy3Q@!ZMJ5VoFR2PL*aXpyo^FgZ3R~GqP#O*yJMhxlP;D?K-0KeI zQ1N_tKyLGt8=a>naXCRJ0#vROjlVFK3|P@l$_)Q8r0)9rN%6Vl|L}%FTVIKk<@y!V zKUEs5;aoOGyKOR=6YNDlDiBM76bQ~?f>^x=ePM($ z49+D~WGb$RNgg6ebZ(XaX$`JOBCg2ui?@t_#&=0(Vp5;#7&LNJ0BZg!iuQ|`C*?>B TmrG3%_ZL*wmJU76k@Efr*^81gQs*SGg zp}^+^t~d5S>aw(~4zAI0-PqJ-+-A!QPDQlAkkH*3Di0kgo37Q)?na%cIZ?i@j=h-3Gy%sVN*A9) zK7m3{VJj%iaF+7g^Y>Ftj&RM0{?(IQbQiS_MVd3I7B+g>5TO5!2<*)l>v6W&d=^hc zPQrdP5;wfWN!Ww?uA|JpzF%~vsV8oXb1021dO@l)n>Au>E+b8usk!J?JhNU{6E(N? zm2Av%8)_R^J5BBVLVH*nARA1u0c-Ibo`8OHLcgdt9Hc!C%fYCQZlu|{FSe1P@k1up zPd&Tt$<9MBQFFJqO0(H*iuogTGKC**-@Yklo%*nHuRhB^(AMFHTQD6IbHsfk4sOA@ ziR&e9kT^V{J<@N1xF5v99gYw-GT&z2AG4O(b5(x%-K8iMZW)E5=BJ=#zJc{Ew)#BY zw4Ei3$v&cV@j0}|LVFAzdrzS)mCE=hJxfV%=93GWPyX6h#!l2|0^Y4Ta`BMt+g-$~-eh?AbR%di#wc+|;b z-kwih@()>Cl`iW|wNli4bnUMOX8n_#Cy%;wt9~bA0$qF#{jqQkiVDmYUfKQ7=v7L~ q*rv6EV{j#E6_33Qe9%Sa$gy70b7xn@hAPvl_lJj3CTd=${{96bvRgp_ literal 0 HcmV?d00001 diff --git a/public/animations/CNs005Pa.ani b/public/animations/CNs005Pa.ani new file mode 100644 index 0000000000000000000000000000000000000000..00dfdde5f23b07e69e43b08ae9726f481c348108 GIT binary patch literal 1545 zcmZvcYe}B)g9t7AAPP~LkzKdljd3Ufkz>okPzWG6D$ryNAd4hnTGvc=OOQ)QMUqQ5VNmvwOWM66_eDIir4l50sZ5+t}~{jp9S%Ldw@K8JTnP@;T{VSFDsfR@tTPzr7fBqUNME zu_o5tnBb?vT1=3p*441}^*YdFO(uO~-p;<%?@~?#q{-)PY|GjLB`rvWekfIe^v5wP zn-UXLd?0n%t627Qr_v9kKgp+x3I0Wje%E+DxnKA}FP}_mF^5n;&WZQEuiWqUwG6p| zDr%1UmJo&n1fT*TXKjJ9B`$R*ZRr)Z1S$|>g=j!+CxmnXA&fdr@By`&`gw$YK*bSn z;72SIAu*14RLB=b#g8s6*R&?A!xwGLTdrJm@Or6a|E6U}S{CThOVp7lR2QDBWX?^O zq<1MJ`WM};UXVV0SjNs0BGT+p0+z*EF_RI2^%%)fMcL-*;$Gi2QdA{W0(!WMvplywY(a$?eN8VzZ`RxrS7tmhEyT92frl cvW%gh$%MAEOG{>yOOv0k??joXd71qD4bt76hX4Qo literal 0 HcmV?d00001 diff --git a/public/animations/CNs005Pe.ani b/public/animations/CNs005Pe.ani new file mode 100644 index 0000000000000000000000000000000000000000..4d083ca0be0be75d10a7a223b9079f3359cd5dd8 GIT binary patch literal 1249 zcmZvbT}V@57{@nl(=IZspbIU#7!D#Uqe5#thv}RK%gHeM&`j|)qgVbOKdf`2s|8xG&bAHcr-jl2P z|58&JTT9#RaJwDN8FV+1#YdJ>d) zGkQf|9zPRfGzewW-c@a}RoZRUAXlkps-2+$=NsYDp~Oz)@tQg6^IOEH6U74TSme^A z#}Ln?jk#DWDvRv3Ecf(ao?u&ajcT7gDnWH#YmlTqo@W+A=XFuKZ^OoHK3a~7bL}Vb zM#NN@M+^DJ{`ypy!I0N2mahr$WOBVQ^C300=o86gR*Qo6rKmufbDa%JzC;jcyk=;? zNH7e!4A6k9V8}6`0i%M$09v{aZ$Q0oT)iaQ61pj~nfE7ka*!tb?$jXT;|{_5Ln~hP zZ8e}4uNm!uK9?A1z9W1po;SP?^+!NE{jy%{)-FoDC$>a}ySgKwg>Ojl&5#eYZ^?I^ ze0}5_B_G~UN4`PweWrS|m`fcEZrWyCe0^+Ha+~hZ+^B3ehOum5PL+7u_S4O^k@=cKs4J7N`Q+&Wm{ycK$DYhq* z=OV+FPO0B=RnFu!Ydzya4yr0lW|rx{uMbMKb>jNf ncgH}*YuTRq8u+2BOvA1$I<1PM{bn&|w5?WLOX+ua?%Vvi}Q||DbH! zGP?_t_U~bW$e(D+RPQ&tw*Lh)gs-sv*m9RP;R7P95Pn0`6mCd=(Yu6$`J$ z9N=Jw$Va&FuejXabO7igkV6>)c1%}yzSeaB;u@ISJ^kGL-Glu7KyGJXeBzh6Yh~tr zyQ#(RtU>ya`4AN`GBx&BE*RQDOac3s6)Ds_X}{8$cKoHm}^u?cX%FfNTVbF@VCO2B>Z~LLCDr3^w%_ z*gMOvM|K(5ygd+gAgyo!j3ltjeB7W;d#@|M?@fO9&V38s?F2aunGbOq$Qkb-&Hx(& zavI3FP-lR}Ku!ZW!xhOHAg7_L1K9vF?-P=FFt@P)8BYE#kuW|iARU8z;Q@K=NUB}` z4>tR03ruW4W+C$-u8mrnZ@<21o!x80FE&qiE#C`r?StDf_Osaj?^{@Bw0GN-S$jdQ z1*zKvRrds_4&>U0Ky|Z$>Xri4?EtC+*$@p>w-KoBEzrDYKy@J3fszm?Af0_2!{JUd zTshhH>WBV)`x55cf;1xYA*rD4bG`lAZ+&|f?3A|*_{O&fsEQR`3}ieAfQ$!u93D^C z^wRbndB$chsMZYeIT9b@^P793?JM#b_wUp!IpEKEDT4=LA$I15|evs1D=-n0Z^F>OjtdWgf84;cmNhxo7V-x4wPL z-DZGwBJ&|$XZl@l@Ai++#&*|yi;TZ~ARn-zi-F7s0Z=;daCLNn=v;ABedoTAXnQU3 qExSRkLgs^&!(#)Kp*>D?!nm5}+ec5w;!xdSQPGNt`ouyKNxx-m(_xEG(3spg8$LR(;Vgy8a zc|+G_na*=|mW_*hc8(@jo1dG*K=>bX%B?-+)A-fug>>kK$1mPiL%f(w#@Q&QKVNSj z+zHSUp`}x6=%u<1}3v5{? z45$D-t}NpNDo|GKQ5L9p<3QriDBrS2J{K1u+GOOaid<)xP|Ez>ZlRo`rMU5jh`81+ zSHM8=_^*G86;Z-EI;_@0qH{eAO?S&Hd^GAXT{_l5j=$SV7NSAvR>#P1iR$RiQx?)1 z*JWt}##MLlqAmkHYc-MIlUHnag*^UB2qtgkaDi)f3v|(saD^Oh72#lTnv8P!O%4~b z^vnXCb)mWsxVjHfU4|g~fXJCT5ggsfDe$z>f$KKu>4u}7m}NsK1`22mVjHNkrPhK??AKaB z0dE{A;Ee+XQZsJjGo_WZLutk~?paWP0xFa*66Fg;`Q}hQPyle#9ym}S5#c0U-9?0h z7F-XAQNZ2DbH@d8P}4%0G&eX2eAem+&2O;+v$mz_e~A?k3kGwMrB@W36R|5b5 literal 0 HcmV?d00001 diff --git a/public/animations/CNs006La.ani b/public/animations/CNs006La.ani new file mode 100644 index 0000000000000000000000000000000000000000..d26ccb40a35ec61f25870e99b3158ea131f34037 GIT binary patch literal 1587 zcmZux4NO~A6mEfbKXIKP8%z8F975D#`bdKt>${giX{V`lcBm{)Wibpe(@u=Cu`*bT zINd@QQ8yFj3UN@3jP-4@(Y>F}z$}Y#Xr(j83=)i@7}$VxF6i{0_a5Zgc$3?_@4R!) z{l4$qzGT(^FEt6n){wn>=bN=V6Uf8JGDTno3H*0$P>UrhpCdhcmnTb@m+|;p{at9} zeSuAo-iO^aIo|xJFqtUfjK0g==9YUxsTv74xj)F!+l--~WSo_i=nrSWa>Dy^zEPLd(g1qSRuyI_#xDT&4cGv?kb7Glo8VV|p{xDY2+T zz2iO?{(gi)kPZvf3})}g?dvI26{|Bgh5Kkw6kF%QuYE(Io1gv`EbM%U1yzN!^|DN7xm z^E9I|Ra#&sJ3S9nP06+Tx(Aum&yVRrRhYPRcs<3J5|<8!`#4Z_*1HEg?(ah%J9W%? zcYhu*fpcTz+*_OcffWT;DRQ>BfE6gPS!4+_x#|2L&cg$|Z+ZXV~Ynk9-Cr^ipEDuqySBw{^zMq(&jFf#C=i2h-Jh4Gm^i36(<~+-0JooT%z5dD!rY zwo&Ljz;MA0r$J+r;Q%H;pO>?lcGgREZxYKxklU@Yh`@{ z>6^C3bS$)wk3oA_&>l=%x(m_AcJYJ1XmN73M{-SpCD*QcBFJ`kwcy^}Ni!=`X}Hc- zr;Y_D*EBHnl^3l+a4kL$T#L^G*Yb-#L*tcugow#c-}TJ25nMBqei@`+D(N>#`hjbZ zH>SvgnTBO92ML$cgbPSo{2+<0E$R9$a)j%*wDR4Sgg<~c%T}k POuTX)%9L0t6Mz2&J3R(N literal 0 HcmV?d00001 diff --git a/public/animations/CNs006Ma.ani b/public/animations/CNs006Ma.ani new file mode 100644 index 0000000000000000000000000000000000000000..19d6c63d97ba12e7c8b2c1e7d89cc2ce37de52af GIT binary patch literal 1445 zcmZuxdq`7Z6yJPI3tgp_fAmm8k|M*65n9{5P3LB?oJrCo2_-a*Vo~@Yvgu|Pl^gW_ zqlXFk2%7Gauet3mP%|r9St~*+GD<2eN>(Oi=iGa>+vtOD+wa_S&iDKMzO%bX@c$)* zFzyi8`-=7z1;MvB>@S`N_}{fw$P@?~bK_Q(m=ozq4!^GGY9_p=X=f1oe8^OmWL@v0 z$AeiM-#*x)s*m=>gtEBCbT%nPEBB0YI59D}^_s816CTDs*Q*w^jvO<2fDIB9k-Xh@{tcMWdzsy6lrdG!30 zt8#2iLW;Li=-0TqlKg3|Bl5ac)V(FCQuL@6YCEB}%4??TWjpQYQ$eATj2pGME)O?J z4;fdcpjS5Qep0`@oyfUAL^V8;N|8wo)OJH{-JJ;g;oA>va5l4$6)MoGISHSWy%|~Z!ojv6gGWiJSeuZ=WZ!#pBXQTGn z^KmfKb*lVXJbN+9+%0n#d+{+2i9q;bN-R$ZG19TslnXF(G0m1| zM1R6=x_IE4Yw+a~mmX9#XRelA5gF;GBb3Xr_BvGus;C1tBLBL9e(z{;eV0r+J3<7) z$tZ>FGM5)Q?V(Vq9rE;Co*(j1X`NLjyTs*1eSL&VLjz+BLHI!bGqlR>bFw6DXB1Dr zSl#Zzs|u9G$LvJbe>0Ld{T63qZ(LfAU+l#8fiY~43)`bjE~te=JO9P1;d*F literal 0 HcmV?d00001 diff --git a/public/animations/CNs006Ni.ani b/public/animations/CNs006Ni.ani new file mode 100644 index 0000000000000000000000000000000000000000..19caa5ef1d5d2b23ee6e75c554499756392b2462 GIT binary patch literal 1365 zcmZuxZ%7ki7{95T7P)d+e&~+|;wUr{hFI$dw5Nl-rV-Q|HX)V`1z>As&*Ab z_^CvUv-(E5^)0JI>2i#l99PxbOxn;}5oc#7dfFq^p_F9&yi&i?6F%n%AvGY7Dj&R| zE{P^Zn}>^B>!w;`Nh4=uToe6v?K>RqWh@Jv_Q^f(71uHk@Lk&SrQc z9674}+Kax>O$N*{6i1&@)!5g683?3^k3vS>b6`A!_i^)YJb(Y<6BO^n#jO&Mj zz3$d%p%;l^Qz_Y-pGxhfBs-tc$ORB?f0c%t{F|h9q(nTI&J(XUw7bpe$0PAiIT-swC1+ROI-+YFjYU|Rf@cM#Djy^ z?9ym%i9FTpa}KGKOLKxibb(DMHtF`o$I9=yLXUrS1%owhG>k=Je1cl$?CibyR=?Yu zxxIW|>4)~x9JGgk_F!6kk0V>_f{+6wD`DPelK zGza}5;QW=A==8YKGpzOoEB3QnG9Eq#RU{VZf+nyU{zyu7;^&mw)3KvwV?CpKfA|rU JkrQoLbuokh2}aS7YP4CZ4V3fFota)X+~m&W z+oujRW?0A>a8nWxjk5WdAxl1;((_+KUt8= zyY3Zhqf?_zN@&W?KI?V5P)m&Q0Z6v1|2bSMa#U?eM;Pw(jqdNcqKvNCp*3AN9=~|~ ziFiGc_E$dZ{KBqkeS5?4=&BRpE|9)?QrU8%P5V7M5WibG89S9D*(UR3=TSqqa5@1oo{8@e>x~qytxY>(D4)58@)#(Ordeku_5Ak4=x=tmXl0OhO^A8deh7{$zQ>!j zKx>~Io?js2%v7~GV(8@JjXee2sRArk2c%Hq7Mgs z|EQdTI)nVUgt@ux?uUx?Cu_ALB88$PWQBhSmqq=5 z$xs=#0&R!Q;jp!)psTrbva$|U{$0f~p^5TZ^NX6?Dt<-83#Y~hn7J!_Fa_VAb+xEH zWifs+72|@DnL&Nm=17hl;||v~^&Yb}@8-3Apt_bo2CaIc< zo9fEgg$Q;7r3|zWR2wZOtEC#8Q?mEfErFqydFJ}b_ilDiY0 z2sH}wXhXcozdQ;u;5-hta{CDBOwbEr9<(MGdPRaWvr)A6xg7CvYN2o%LWc$k>#P@qAaX zH@q4xXptCRDkXdUL#ZuHbMm?ETnOO~ICKNuFPW*rW4vv;H#GBhYp4z#v;o6T&0U3) z?>obV0>%AYCrr>f%+&YW`Hz9Y(6`c+;Ak2pTgj$1cSK(1T7-kC#t^Sg^s#v(C3L0w;+?ETg5px2+j zw`Nrt1bJx<KYg~pBD;X&gqAvO}@*izP2~Cczbn1-jq~qJ}?`DxLT_We)Z2YuwSyWWPK95ZXwL H+l1dgevOHi literal 0 HcmV?d00001 diff --git a/public/animations/CNs006xx.ani b/public/animations/CNs006xx.ani new file mode 100644 index 0000000000000000000000000000000000000000..2f184c3f7fc7abcfef494f37ea8cff87289fdd4c GIT binary patch literal 1373 zcmZuxZAcSg9KWfX7P-7x%9QQb*Q#>70dWW!fVejYI3W%xPTWWN!wL-b-8yGeN8UuCvg?6xZKPRKq~wFHJ*=BXQ}V*8*^j4`8rTO0jiPD5c8eqOn> z2KQ3YR-kP)SxW3Rxc9x9&Ly>`Hx3H28^ebHf$deJLC0&mk>E-q~3lY8Dw;-k@Ead z&^NtW9cYO$97sa;=BH9unC9ei+nEr;9dPIdx?fRKg~zzY>E6)HyRD&GbkGJ&JGJ)| zPOk4P6ABdfGo3KO$ziIl-@$zf42HgI;=$20LKe>^wRZ(x=4zOMsm2hmR^Y859vr+* zk5+p};AJmAV~{(!w8k4mKkyO^R{f#mSel`0!xPTb)t`C*5{Crn#G|E zLXunB8S=>yO8`=n@*p)S4^k_uxKAxKwsH}@kKAxDri0XsNUs3tFVS2f= z2Kunz{8gItM%?L}R{PnS!}PA)hm+t+jCnfm38IESl1jbsIb{y@?QPi6Ol7}6{s`K{ J7`O4ie*s1Ui1Yve literal 0 HcmV?d00001 diff --git a/public/animations/CNs007Br.ani b/public/animations/CNs007Br.ani new file mode 100644 index 0000000000000000000000000000000000000000..e922568d66be47f0b91df8b582a9d93019a62b42 GIT binary patch literal 1353 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 gS-J)aWoGn{=}x&;C#wU!xv<49M21s@7ofT zKgDy5ysd3XAYIF`>)3ojnZ*!z6T#zqmKg)V%0N;g&sWT*8AhC^1EdB5g;(qMiI(8m z0G_#|B${`WOV(nWwH&$y&1kv9)8lw&yx15nK%E+cAPDgkItoisagNQ-tJ?2n7_EQm z2XvvQ`KVH3Es8YL33@h_pqJ_S6at<vCsqt(Zyi z5=*;q4ec?|9>!zuDY)T)f9Ru*rLJk^&Q+K@pOZJS{xQ9LyZfa0I%%9eMDaBiP4F|#X69t_opS3OP<&L58H8R|##ntXj!4+A&o)F|ZlHO*p}0How;uPqg-P=%2GP@e=#POvijpEzA+Nmk wWUbq8-^Zv!7{i79felY6djt6EuWO#5)~5z5q4bl}^W|KVJI=giC|2>&+= zb>l?QQ(IYE89}eFV*YA+%L0V=)A16-c8i`Mv|e80X)ArMrbIIR+u!8buzEzbM={-Z zbfwffnyV#6Go4~9@@$#8t;NPLeJw53vzcnz4FE#pYa=f^~0d1SLp7`r}6{>XuF?_vsy`td*C47Ti z97y5wa&g!VdTbL5GL2kZBk2KwJ-;Bmtenwa>RB&2f;9uysb|@s<+V<0nUT+uSy!V< z6B}?qdoQdbXyF^+$Ur;kJwk%Pp#Dr~t^nGf-cs^)JTbIG{YmmY5M~+%0j*sQ;m)Ne ztKVy`_|*1UA8>j753bn~xWJ&5aC8Iid>DqYTz=uka`}a$>o>ZRIaj0E!>EfFbiX6- zu%V5G+sY1_3!fM|F17r!l3VSWxv0f!G;WB-IcXfN79H->3(_Af@_^lT$i&#$bweEg(W0tOw+IYgvp1t@ zzMgb1GzZ#!$$Lu{;sPii?n8MP%410LIZb4oP=`KSNy1hao08LP%GZGka<0ar1$JbK z%P;PVn9?3!MP9nbr0x|-%7xRNBBor_c9H(+EbXDYPJFZHybe1R%JtLSC7KHae4qFj z^(jq`(Xp$_Be_}i$anNiUb8VH7VC+;9G5E&y0fd6;y@c6?n8YT$P4prc`Wn(aH>x& suOMmb`utcSuk~=939G>$M4>J83a{$w+~?d_B_)15)sHk@vo!tp4_Inf&Hw-a literal 0 HcmV?d00001 diff --git a/public/animations/CNs007Pe.ani b/public/animations/CNs007Pe.ani new file mode 100644 index 0000000000000000000000000000000000000000..7437aa75ed7348513b2900961d5a73a7f0f7fe3c GIT binary patch literal 1249 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 n3yHSZ65p~LEDaA4P)6}^geDW-m5ZzrgyYr literal 0 HcmV?d00001 diff --git a/public/animations/CNs008Ni.ani b/public/animations/CNs008Ni.ani new file mode 100644 index 0000000000000000000000000000000000000000..2f59fe3d9f7e6ed21e5624f991527e0cc52512d8 GIT binary patch literal 1005 zcmWe+zz!IJ!XV57#Ce&?*-SwC0y~1w4CJGNFN|Of3>T)V+orh9+xvnU!k=({!V(vg zU3)+#fk1=3-d^j?AO;A7bj@R{w_jhk$ri{5gFCDYjB|lvd+Rpsg^7Xe2I;k0-38JM z2Ou|c0I{c^o4;?Is4TybQ-F1UPAhvdFl(zH#pTC3c#z_m@w#EQ$xQJu}$X1Zp)eDCEKsJH^ z$kzY=>+K;tR1y>rAX|Ohpw_;wY>K58go!Pv% z0vG_1OY`lw7p>d($neXad%KqJ1qJ|E@7ZT-w{X-s7|no26DXiT0pRT87!J4AaOGs% zs~`IJ?Ms+%3yF!g&-M0ezxC}|uv6YL;2Yl_n4>{jK@Nt8-8H?meMg?L*$b*QLtU)M zQg6RAk8h7*g1ePa?Iv50iw~V}u}?Nvvi~ZSyw`enr!6ST)F0&8@0!19-yE)AHvERm zZ9y&u>6Oms+w(8p-AWLs7i2BSL7)T*b}`)EOP720ZgcC~x7=+8)X7Z0>+RkC@!8n! zns1Tum(K>I7X(1z=;7+<0`b_2qv|{Ng+$wHiEr5rmIj*-@)jrqcsN3n1nL& zqMV54-%K}4r@MDA>%ks!DNchTGtnMGO-;hc^__d~)+~GLz<0lU&i6a_{(k42FPPZc zNF0VG#;YW^Bv*v@?x5ekANDufD#;}f&iY8|vZj&w?8p0cqjfE6`H(F@fE!T^lVHJ) z5f)@MN(|^(g_#ln#Jg<-`54ET3Nz!0Ik&@`5h=RvKrUyF0HC)yRvk!?tKv9R5vI1R zh~w-BUe8%Fb`a!O@$=XiW;hn(?Q!yh{oy+aav7grjVzOY!+Ryh&*B)I=T{l!i{iNc zC?-J;KY}{=EILOY`T7^L;r!Uzp{`Zfe5y(wGI2|0Bzaf(b*gK!%pbvg3r z+V2wdph>Re7;|GQetXCgF(2Lc5V!ceh#>vXUPCU?ExPj6pt3>V&6MGSX0{fi-qId+ zMz(638ERv2K}i!H@8-NgOTfO+zW!W4P#ZSQP6$5&@R%2_jMw~dkCbfCQK`IFI_QDmD&pPHL zDEP=tLD_z>0a=TrHd#)xBILT6f=jw<9xKxu=v0T{BLDyZ literal 0 HcmV?d00001 diff --git a/public/animations/CNs008Pe.ani b/public/animations/CNs008Pe.ani new file mode 100644 index 0000000000000000000000000000000000000000..453905550d93644e816174677994de42714b9ce5 GIT binary patch literal 1009 zcmWe+zz!IJ!XV5B#09AZ1*t_$K-L9zWD#bd2rBr(2-d`KVXC@qirc)sFPI_x3Fjv) zaWUDohXtyLW5-KM=TF_7&*<@Q#q zyFhy30OU>%Aolcg^LG#O_XF`67@zoM?pm37-)?I0J8S&*?nbf$WN)lYjs2wyhISxh zKmcZMa+4N{y&(UC!pFzeT{kEMVr$1nX*>V_`8(KdoV37gYYfnai%2$rYz2v3yQ3?P%8{9Php z;;;a44Dy8sz_lZ(cKtut?58a-u>rdT9JMSrRW{2%*WEY^7}OC=42&FMW%k#YTI@Pb z?Xa1Vxn^$#FaV;L=G$*5TDR|!;g>y+b}iov3;@RYz$CCJb;o8-1t}wtUXab8fCdGC zvyWpq+*-qxlWnhl=-;<5VZJRSCfYvN+pqoBw`ajldCP!re0xAzK>(x`P|@c=7iZSnc^=ucC+PedTTql~Jjk=( zK7Z4``CPwj1Pzzlf?N#Jy9%hc2C5fiCCEXb1PXRB+}=x)W^7Z3fiIOuy^x z-Tv{}*zTHdk@1($2Ba4RK;h`&>gWRT*ovd-JNJb|+iQt$*$tM4hX^PGcsN3n1ngfOg literal 0 HcmV?d00001 diff --git a/public/animations/CNs008xx.ani b/public/animations/CNs008xx.ani new file mode 100644 index 0000000000000000000000000000000000000000..18499abe00af1c3ce1a2193c5bd8a85b89f5003e GIT binary patch literal 1013 zcmWe+zz!IJ!XV56#EHoz`9<*thD<>A1$I<1W}p}<_`(R*$Z%n*x^0TvyuB}&A^ZvF zCoFL>*|moSs=UEoZ?E-cAg2L@LAvI#)!VPH+hhw>z;K6^fpIQSY;WDBy)ZG5{XpgR zR;#-}df@=(R!=`SfA=7NKM^P2F3*k>+OL;4ImYEtBh=Q7$IT}5AG=Wvo#yCuQJ;E1#EAF z{gXY`n^}zPG%POKZ;D`os8j2=-kjWdWTQ1uZwoU6E66-#u?;LxJF@IH?`b(<=m0bi z7${(Sg`FHX|7tr1)(f&9=2^!eU$|$l9Z9w8|G{QIZGnjm$cqe&^VsU`S#GLqmVd6h zaaP@?y+9YUqKg4t%*YW|W`B*T#jfMj4x1U7YxeE|x)`KR<|X46jTgEb7XbAFUCj8( zt=xWD2E&$?1A3d@HMi^ox!Ab1*uJ-+$!@clh3$!h+WSB*o(t4l@cQ#+;ZM$+?*sLM z9Rnmlu5$Kq42L_}aOGs%s~`IJ?Ms+%3yHC|&-M0ezxC}|uv6YL;2Yl_kX8@?X$5%} z9%t9|()JyB#%3?5)(rKnB1^sf&OE+7h6(OgLbaQ~@v~RI!ai{Z-=0fP)plKeR%HkB z?d%mL_OU-}>~`vE?kx$Dvjh1Sq&E(z*B+?X5~vpxKj>m0-`clV+s}&CuoGOLy~l%R zgDuFnAa#8Ce0$ad^%?{9f}9KT3@CwveG7NdV8&n z->&Uc2l;hVe}TPgw2iup?0UPC*$l=nfU$UEbFBUR11_8Y|Np;FcdzwkP{sr4y)LkM s^V^$w#`6yxF#?Hz>;VNkDC2oJhCr<4UAf3AK`z5i=yBF|kRm7m0E~qr4FCWD literal 0 HcmV?d00001 diff --git a/public/animations/CNs009La.ani b/public/animations/CNs009La.ani new file mode 100644 index 0000000000000000000000000000000000000000..42d188e245b519c89723f6913d489cb96d18e4c0 GIT binary patch literal 1179 zcmZvce@IhN6vyA2ZW-7>iwq2+9?Jsnn5|D|OFhLGitG7S|mLhGD29VHz&>G@~VCGao|BZmNEF|f)7GCEcl7nu+ji(`uZ9GlhCm^H$VpPSx?0!2AN${=GR zA{7aGdsR-mC+pE2|Kvg9t00`iC?x*YeI|UsoTn*Tf6a&3T*G^lJ*%1JFpf!*tP0Gqra0FcEsxP`_p1Junx9UzNq z{Hpe_okZL zZmcgPAJ-q;6>uEIs*YFrERx>#5Lf}`!;9VI2&df0WT8c#voN#Oh}RgS=o47`|d}jAf(l5?YFjc zjw3A#6}-{n9eij(&Z89rt=O#N{tWqN2+zi3#BE3MO1ImbIbt~@=ap%Z8ux+@@qDa% zFOu|XIY01aN6+BcB^JBA-*BF!{=C9k6uYeF6TDdL7QM&7OTZfpUg%c3R>N+|jmzX- zsxRx-dYX@NZPQ}6SSQbw^R3`jgBNkJ8GLcYZXL`6gEy^aU4>AutJdN>PtlTJ9MLVy z8EsE(Q09`H0T$_64GS^wK1M}^gxfc&;rULBpg%(VJUX9>(~cd+k4037-d?t$u}+ib OmC3q~W_R1m#NR)9Ghz1t literal 0 HcmV?d00001 diff --git a/public/animations/CNs009Ma.ani b/public/animations/CNs009Ma.ani new file mode 100644 index 0000000000000000000000000000000000000000..8d6447ad968f8d9b32aeb84552d14364f17361b7 GIT binary patch literal 917 zcmWe+zz!IJ!XV57#JP#NiA+HH4l9BWv=; z>pa)Mqv+ZmV1O_#1lsG+4^+p>ywQ23ku69k%(IR`zHrZ8JCbVG|AWnb+5!_Bh>Pd3 z)!Vb&RM{;5TzBKFx=nk5E@nj+1G<=zBdpB+8dHm1$Eh7QGcwog-2-$nNS(|}#w{8z zbT=*l>TO|WV0`6PZoe#pVN1&ay-n|$TlRrmY}{IG-`mh+w^_`>_QXN$eIOUl1?nw$ z{du$SC+E%gfqFqM1_cbrRn9(+;czD#uAFRp^+W%@eF^hzAu-nWx!!*5x4u0KcFJ1@ zeB;{#(h33~tsu|BnxVc`WU05`na8)sFu~nQsCE-Le)j5D*eA~5 z+jHru+OEsbs_a0%oxP&OKK5sg-A-N2y(K|%b|Bw^^u_`8+5`1k0`&rY%Ze@r@~wS) zwf(GE4Lia0*?T;AHrRrE3sT3I&$nkiP_HpiFUYwd&wvsw*tc*uU%K40cbi+^zU6K+ zY(dGLfsyHVy}jE%J{#Lz^DQ#|^4S30!ip{iQV0T|jN;+y=mH6-HAmHV9t?@LFMYas gH&_}T!k~=e;RsFIyek)3CCFvi2|do*4p9as0l9Dd0RR91 literal 0 HcmV?d00001 diff --git a/public/animations/CNs009Ni.ani b/public/animations/CNs009Ni.ani new file mode 100644 index 0000000000000000000000000000000000000000..8f5c7bb598d97646d76b0bc76ada317190370579 GIT binary patch literal 1269 zcmaKseMl2=7{_n6t%zD_qDAEhBxg2Fw?enO`>|Zp$Yw1mBMK`DtrJVw5G}#57wE-lVbO~$BI$W8LT`2YM zkiaqyDUAHQwHrg=-X;4z9|`lJF;gN6s98cdW8d;P`s=W{JH-E-62fVUI;JchF*H9z zI&_Y^{T;|O3dVf1DKpKIX~sDvd1KCNsLFfEo+#|o%W(z5PU3mJ6Mx`9y0dZ{Ss>Rt z9m|bLH>@0wgb4~OdDQA)#`XC0?X3D-TQs}}a5M!-OVjCpBlOO~H*lVdU)S+}_U^pg9@|E5EdtUb}>))=EF zdvZBFkZPl|pd*Vm|T zbMOJWQs%hE)HTWRbB(DM=`ir*kS@bCi1$&g6h0IjatW1vuE4SWw|E>Bj|mj76SjTf z_358LQ2-CZPAn7SYHoMMCQUu)(Ru-S*VP1~ovriqe=F0PRW>amp8*?THjbb3EI|{I zEocMC536-Inx4ldO#vkqwrG!6J9uUB@2v$;rt|EiJkbk5Sqg z?NoNF9Tjlg&;l|%+CSIO#i*rQNk>RAd*DGmU7mM^$414xDLs-Jl~?Vk1T7Z6*R+jX z$fatEZ^mKq;CHo>@oydg1OnISHMD zsg1bu74F2GG+o$kj=_fK6SkApb)}@Z+i!4zrx<&0O z&=Sz5fEL`!hOePpvVSM3SgB$|t2esS$6UvkK-4M<6z*!!YCwy)Xa-+W(QS~qt^F`x zYbp~in#vjcoKTXGA2wndP|!@nx#ZlR3L02s!`Cno1A8ESp26rZ)TO2vmyorB%!sUM f6dwmGj30({!(eNVtvavEiOOZeUT>)Lr-|2J+o@`X literal 0 HcmV?d00001 diff --git a/public/animations/CNs009Pa.ani b/public/animations/CNs009Pa.ani new file mode 100644 index 0000000000000000000000000000000000000000..d312ca957a1337faa5f78106180f3f3ebf19b554 GIT binary patch literal 1757 zcmZvd4NOy46vtaWI>xjcYS4u*H4J44*!F3F_PrZEENo*iP$y0>)Wiv5fFYTNNoj;R zmm!#JEC@{4mQ0wNwXdao^}Wsw46?}>Yz*Wx0TrC9Mi#wj~&U1dAOLV{=~zk$}}bo&0aYHo3`KNs;ER3gzu zpCZSV@v%-T=Y!5G7jSuI<(zV{$8T-$AiBuSFQq=a^0|G6HMqcdB~Be5ofsd7bCGyF zF1@)feS|4HX;VIj9xxpV;*>_b!GYCWmK2cJBub;>cZ#kxs zOidq?;B7M=;51Yh`0#z-!g}t(o^rUSpfYi?=NZ!9C1cLU9WmY>JWaKN&rq68)LweV zVW`bG6rMq0e-;)Bh?`;$;;4`y1F-3D%nfbW;?#@woASy86ij08G>zzIzsf7km;{SW&oGZJ{9K1FPWMNIFp z_+3{`#Eb+|fIxSbjQ?e;OMSz&NHY>(fk32}o}ZXISD*KDm>CIRC#PS$xnpnfL)o25 zwHZBVC?6yct6oI#DA0$0nDtQ;Ut?=}I)YUqU_k`*k_h^t`NI=4%3qtob zOyFlp7~zSpFAJl$^(a$1BO#yGP%43nP=!;!uhHjzprHb)0V>W|+5de=#pTU+Nv}f@ zbH1ZdRi6G0hX)?xJSWAb?Gu598puVlU}ayWiA+V`1+ma%9!V+QKS?g=&N4>#5W4+F z01u?ZpFwW+A5+HlPa=&Ykc*_+wj3juOMYi;!|rrV+vgmLrAo5Ej0_}O|NNwa>(k0P z6wB~HGO3m<@Z&I_1V0Ev*h_V6JL?=d#ty;zkOCeC6h>rh_<>~vvbvAy-yWCpd}=jq zKP~5`;6zTBSZbPlLf@E{S}+w2QxV>>rh0?k=3DPch?`&GBE2n61*9ocYLTTzhpo$J zY19-Ke41#=rIzlye$74i@))F7S({5z{yxJr1^RQjiQWv-!`#D=OCUECa)F-nY9G=g zAG}8@QnoX0`V33Vn(O$n5ptzjQi~pP6_AVc%BE9Dg8wYj*b&V2y;VsgJypcl%*fGf zt4~BNy?eh+h4lI@hsdccu~o8j7wz%WB^v4Bx;J1i*2B?Za(Ew>k8tqgD$rYI(A`ta znbjry6?Hj{e;XpP!)yUj&r4Nwb9G4CoKyw1Ij{C%AR1~kW>0vO9ei!uwkCexA@cG@ iQ;YC;xnuZ2j|pEq%4_Ss2ut-&W7drwZ?K06zyAWp4<~>C literal 0 HcmV?d00001 diff --git a/public/animations/CNs009Pe.ani b/public/animations/CNs009Pe.ani new file mode 100644 index 0000000000000000000000000000000000000000..47b68618c256b4547ccd608d0b779f0dd7fea116 GIT binary patch literal 941 zcmWe+zz!IJ!XV5B#09AZ1*t_$K-L{rWD%eM7=YAq0I{c^o4T^jkTZ6cT$7^_ay*_#G$vh7f--2Lp2p=}E@1LH!VdG)QF zTav%#7%f?8wD$#2OyX>v{o&>N_uk$6e%F*+NB3-Cfv7tz`rhQs!otm~RvOs?gNYTS z7g-GCE?59K2KmAR;M$Q?yZ#?+_R|)a*nqsvz&MYs-k#;A%4YfJx*KQJZQ2X;1uME3 z$QK-8W%k#YTI@Pb?Xa1Vxn}Pkpf5n`WL`3E(RiV|aRE>-$QQ5N%I%kBFl=c#pttE= zbIU%Ei;Y{0?Ry)V>^6&8*q%73y$|SO#<@Vf1+PDE7XIYC`94rD*fBr?G>B~-f!9A|s=E9?_z@a?(uRBhMgXH|9}-_Bl9Vjugn#%`ys z=H8MZIXjSVL3-nWdhLOFErEK0zGX!h1Nqjzz1n_OtcIQ7`s_U(JR3mq1J=uz&$nki zP_HpiFUYyDgbemA+|8FR_w3!~*0*oD+YDP!CSYJ>`dx4D_K(lTcGrB1jK6#~K)0}> zi-8n^04R-mxH`H(0&2xk^_}}dqV2WBx9kQ>gY5(b6Dad|I6{*<@5)71333^BLXWey ILzIC@0E`0^Gynhq literal 0 HcmV?d00001 diff --git a/public/animations/CNs009xx.ani b/public/animations/CNs009xx.ani new file mode 100644 index 0000000000000000000000000000000000000000..9284109201e1ed0ae9e794584b89563656e4752d GIT binary patch literal 945 zcmWe+zz!IJ!XV56#EHoz`9<*thD<>A9adB^pb!|q)Oz~4`MU@C`+@ijj8FVBcdg94 zZ#T90oi#)qGmv5fVjov`-JlSNs+^6|cH-LucgzVsX#r6MBH5v;eB7YQ-s{Tmdz0V2 zbKin@I}yrQfJ`TUmq>`S7#J5EthWcrCbKmg+O0CO)nSB+U3jGQfz!pvc9qfIFF-M~ zA9eN%N;cWv`uTpBM*eD>2qp%`suOkgroo$RJJc$7Kl@{7+rrGixDaSweJkgdQ0NkH#xJgaPz8_Mz+9!Vg>0%76Z8p z766VxzVHCJb|lrV{|B4>v;`(MAa64;&SR^$XSu1eS^l~1##wcn_5yvuiY^B71xHw! z{WYc*yN**kY-VJx*}Dhm3y?aQmyBC9Ug&OI0MrZe#VfaR`(+snTUrk2ZF<+-vJd29 zLg5>(85oKRIu{57Y~G43Gf1%Gt*;9PVVpm6L6+ ze(2w~FJZndBo^B~*W0iC*0*QDPI=3KZ+v?|T0sD$735iXJYLgF+jry{o4ue~Gt{?= zEcNy~^Z51{Cb(M()oudE*y{eGccm=RD6j_r_3v zHyXR)MB;O%rsC}R+Pa!B{5%r1)~Ch3wKfyJ7QfR<7g6;?E;QNOCA{4oAW*VU+5 z5#I=fCMr(lgJmCy9T1Dr*gqF;qO zP-SV5#aJ@jAzi%9g~kI0)%Q$8(V8U=ZCrNyl{o0UUB{uihJIf0LH8$c?jn)f8cEUP zUOUoJ6jI_qr`1&sz4o?2FRFn!IF7R9no|l2Wme67@vLptIiw4Yr2{Jx|C?1+X;4tE z23B_5*<`?=`heB3iJ=Sfg<0DTwpbYS>XwbN)2E|V}%t={zqT{ ztSqJwt@)G$Vb*!wCv?pDfEJ1WA6gnr>yRy{NTI3>&{%L(9pLmx8QZd~Lw7pTvVbXx zVc7B-Y2GG#6TYSN-NWkyYuQ$qhVJ7tU+rRMt6e+{u+#m9eVKSpw2waEM?Y-lVKBAD z*~s}LNeo;nnSI1Z|M@TxA%gc*__B^&OBRu*0(7)-XscwBAQ&~uBK zT4)i35)T83B$hYw+D{HWUUK_8dUSM!Nh@sAXI<;IF`Km)PyP9M* z2rR3SsNs1RNV!7uyKintF257hI$o(2Zgm}r_{Ezu5r~z?DLU98)4oL05HA-=fou|zi(YOY*s(e%=HPn8`CvNc1 zO>Uvf)GB3?Se7H~7l%y0^;yiYC*8542UgnkKDZwY_9ZXZm?LL)kM8yfg(uOTEdwv0 mD-uiLQcoZueCYCwp;u>9PiIESb_W~#sd5m~NGzvGzkdLdrGb$E literal 0 HcmV?d00001 diff --git a/public/animations/CNs010La.ani b/public/animations/CNs010La.ani new file mode 100644 index 0000000000000000000000000000000000000000..820d174ac8d19bff2205f693c6657f750028c7d2 GIT binary patch literal 1543 zcmds1|4S2L7{AkPexZhnRA6N0Fenr*j7+_ITk2-CHWeXECHI3iMKiLfWO;@VLNSRb zEYg`ICfFC9q_f@KFB;e{4ZpRZFF)89iKRj!LYTeJ-R?NWzn~Z1ZJ%eK=Xsyc=Y8&C ziLDoj!?3k@*V->%w1?pzV^OLA{IhY*hi}9mwPFdPam0zH`RVKt*PF3STyU@wyd`8M2v zYU}|9V{zdQ$+F!}H0h_5*h>vTYF1dZZPn$IdC+;MjzyQ6201wf-Jij^%c|V=NP?X7 zlp`HMAejdyt*^7_ou>nOQ5Kkkd4wg;oLW>YscIcaU~KF4Bf7vu2B=cw=uqa?s%q^4 zUM>x)%5i7YeoBdfs=^D`P)A9XqVy_ORf>5(RXk9-d>Mf>_{KeEnK3IRt%Xck!LB1OQfATe0{&8Dw ziEN2H5XPcY%0SGHRWfa>I&{A)JsTWRV;CN3EHxhtp2Q!i0}t_d!kBjyr=v%BFV;Ef zV4ahLf#`Ow`47G5qZ?S`!YT zz>!9--`zxm8&fS)j3ve)u(jRvi|BY7+z52qQ1Y&L;h@zb&_MJ6n#^5ucRrw_)x}o$tIdz+RWpeSorgkw?jU_w6{;U|#a`jJZ942F7^Z#!izKC7<$CE_jPAh>~S!0b2Y<| Krp8j5`1ucYA(5K^ literal 0 HcmV?d00001 diff --git a/public/animations/CNs010Ma.ani b/public/animations/CNs010Ma.ani new file mode 100644 index 0000000000000000000000000000000000000000..55d626840d8fccc540ba69c3138f76243db76323 GIT binary patch literal 1493 zcmdT^T}TvB6rR!DRihRasldogDJT@Sj7*)mZ0fGjwre7UspUSXtGF2$L}5-Mgix$R zKUk!@sbPZs=t{P>GxMT>y|ifB2K_wrCt+$NB81sJGdtr|qW2Emo&C<7bMN`SbMC}Y ze>WPtVQKJk+MV_={2Y#2!4Vj*wyE&dI8G~VK>a9gWIflT#J3?FySJZpc2A;G_|7KNV4+_fGz^W8i_9|ddeZcDI>2s*9s6u55tWMxKfnWfvK#^}mF0h)w zth5v*DrW#I8Y`?|@(GgytSqJwt=W_WVY>FRPw>q6fEJ1WA6jrR4%%{x6sk%CjRi;4 z22Ku_vn`7{bkmcT1x!f{&6dYV^CsDw@HM6HHXbJ!%eKNabO*0lXFW6RtmmPD9j@2x z^TbNAbmT5S@_r)^jj1ciMz!CQ#K75dKHlHVLt~V<5*#;*`9@V+2FhlaaWW%k{ z$ig*(#$;QJ$JBBSJ=d6txfVevb2E@gVtFDj{p8St1()CBOE{3fp!ee_?OGr30)y>A z47Kl*sTIQT7afY((Il%uU|Ees4TpTE^nOE`IV1K$KLctEe%^@={iHb~ z8lM5BDjyX|4Yl5}@hkijlS}9}wMv;JmgNZh#UaygeHJt1PIoNnft7Zx5B3LxdCAK) z=E#}%M|S#z!eX>*)4(&Rio{a5R1-)DAG$na=<00f>BuPC>R@9(Ru4iNiRCou_YdOe Bg{lAm literal 0 HcmV?d00001 diff --git a/public/animations/CNs010Ni.ani b/public/animations/CNs010Ni.ani new file mode 100644 index 0000000000000000000000000000000000000000..c588f980c5d295f69778e59da7b9a3f2db65bb3b GIT binary patch literal 1541 zcmds1`%4s26rR!D^+7EvQh||~i$S3)qDg7m28e7gix$R z6c*{4BqrEHSJJhenO`)pUs`;%L60BoA+f2Dh!AG?%4lIM8%|v)~Jp@Z-2=z1JfG0!ts3pC`pn#ISjd1pH3l}UW|YEZxZw}%Ws)G@gxXPc zxu3yUT)0EBWVZuN`Y0v#LPL<66&7tnwWf?tosD1?FHLVaYKk7ZggWS_k47+j{+o&Oea`s?<0#n7Os8nsPrc zmj+d(xU*>=rNls0{>7`Pqo`6*dIhU0#=IXY9w=RM=cB+(j3C!LrX$Fnq%>rhp_CY? z2j}KgxuAX)tH;U|9;k;mA=gqr!!Q8#7E@6EVsbnmsK4pqyB0hkUyc7i`EZer+p>#f zOXPtt7MxNBVs@;8XLz$1+luCZ=n+`=MwMcHLu;fO9hDDw_y_YI8r^j3yq`{5huZPi^a0vO2 zG;;l}CK}wBYMEjzQFei?>877Y#?s)1zte^icf|?^tv32&-fbG(0N%+UZwT{1)Lwi> zIX+|GS#x?6J_A~nKT@I_O23m+x44%kC*Nyo7cIEoD9#-|yiIi_w9dL$9DKH5Tb&PhcT@ICG7`E4IC_JH2d=or#&N9)>hE ImeR!Ezeiz^xc~qF literal 0 HcmV?d00001 diff --git a/public/animations/CNs010Pa.ani b/public/animations/CNs010Pa.ani new file mode 100644 index 0000000000000000000000000000000000000000..ebd143ee865bc1c86695dfbe5dd57d50ca3f3faa GIT binary patch literal 1637 zcmc&!Ye*DP6rR;}eN>VrS!^Vx6ckDuN~X?S*0x|KP9C=nwc-7~W@jpqLj+}-`YJ?GB(zI*QY5G#ia z)3D@t6&Z>QF8I55)nBZ@cxilfU5a13aT^e)drfG#t(3L9^Z1Uky}8VDoV9uKxS_V1 z0gQKt2NKs(jWl3>H=sQ5+8?jg>k{?x&<7#=7@ybInm0yO7f(h(ofLaQ298n`N=J3W z4_MPOLiWrx7v&oq6vkZOj%DeaOlZ)q61fgJA(PLT(dyYstB?muZYj*@L`fGdaG)j? z*G@}nYdr|rV0Q+>-XI|lt0Wc{&FGzWCQRUP$MWSI{09btuW|cyA&E)6(uz(crC&7m zDjWk5pj3+Sad5G+(gK5naV8L;Gy``#Y*&dKC_Pkk3e~0Li0VOUCeC9dsz52YG$E-C z!%8>4*P$Ih0-WbW5TM>S#0!mj>uEUr;`F`9I1$9! zPiE6fYXuFeguHZ|H-Pg%Rr~RFDy_-Lj`cOubE;`8bW)G6&EuwHJD;{(=(z5@K5(U> ztG#jRWKn{u^(y(|`xS=CswbgPius#x{-+t;o2j%mSOWGW%y`50Zp%5)3IW@LLE_Ae zgyt}|&kE$TwVZcIf_aAmTOkJ=^}m4bdliD!%yL8p528UyHC??^(7=YG*muB z#jVX{%>|9*eJ?)}9CF;!qo56b?2dRnIhs&Kf5>>~ELx`p&P zJjr#k1wU7VpKHt1MXtloh4zJ)9_Ns#VQ}aY{XkpEG;3=)i=>#h$mO>gX&1bs$-erC z+<9-<-)%Vu`$NE7#qLvU_{x_4Z5Ade4Q==Dc?9-LF_+Hu1Wv(=I9BbvifdXMBeOQ= QQobMZA3~WF^JU!UAEDl~{r~^~ literal 0 HcmV?d00001 diff --git a/public/animations/CNs010Pe.ani b/public/animations/CNs010Pe.ani new file mode 100644 index 0000000000000000000000000000000000000000..0d90583c4ed92f0d31eac253e01da067905aeb36 GIT binary patch literal 1465 zcmdT^ZAepL6uwutsZm2kDljrr3W^B}BUA5vS?XrAHWMMNS^8r(MRQ~j$?_UP2*o6# zut;Z8n4pzT(%J64e>AW^4Zm8@$B(`wOoc>*FniCvyO&pq{&(Qr?sGWjJ?DAOxi^9O zyV2MUM~9Ew>2^CWMB%5=IMSoVp7k~xy&k{QN|#U#!yYu%+a!i!Wc)DsN%z&bIT7Cn zMJNT!+2D%B=cvtWw-noLP?@4X+p0!-sy?!}>*sSIkHioS)`T+n96vn3S)wWWRkRCL zSA<;1wTqe+uU=5xH%#6g}ZD zLs%!I#DPj{YaDv*Z--t~193o4Yk|d3Tq3h-=}%_uYjwlA@OT!mBJsaj)l`H9>j9y2J^-{x{QuC>U|L7)`K1a~Wq_vQh@RK9f-?jDFRjggwo&8U&Wr zNY!vCa9$rYmRqtDuM9At#?Y68XjMFb!iO68USA^vYD_jyvetN)$klW)Pd6nophmdE zj?%U#iTiDK=6%sk2GoGKW0AN)90#O!;TjdV#_qG0?08%QT2($Ok{W8i6O%Xi=Vq_a zZElk?Ni53|^@~I1-}+o;#Fyn-)dMT-MjzY{2K!Q2V9J*>ACK(~2qmY{zHNgqpeqtf j;Zjc^A$;fxO_5h;OHWsJ`A!#`_^Emb(nu_)Nxy#p6uy7m literal 0 HcmV?d00001 diff --git a/public/animations/CNs010xx.ani b/public/animations/CNs010xx.ani new file mode 100644 index 0000000000000000000000000000000000000000..71c095a029cd37a356e4900ed349744afa340311 GIT binary patch literal 1469 zcmdT^e@GK?6u;ALYSd7X3XIH@fFT{Xf>5sEl`^vKU=DYebpbCTlI6fkf+AbDOfYgkBGByHQ=XgowIzGoVO)GV=ReZhx<83?7gf3Ku>?8p zw<8@vAUO_nT3uz)Ykvpyq8NyS;|O!1DWjxRV%6H0%ve`lL%Q%-Hn39Tf3vD}gn9XD zU}eXhO@=7N2ds{tzl1uNJkyRSbX?DDqvE3#_IvD?DLAt^uq_tgwvL|1lT< zE3+{|Yd#}|pLJae@Lh8OprywD4=oL*b=X=^CR3FMXec?R3~+j+l4)Dkq1#)ad+u-o&7d6{}vu#ev7Mn7!kU@-Nz zeB}C(CWJ0la>>C~4hAE~*>Kz#j)SURys8T`QB?ZFbm~rIW#Kh~!Q`6_CzN(6-%WaI zp^fJ&yfh@Lv9ys_f3oQDk|)>|NI6uzqz~dK?Rp>Z0)y*83}x=qnN|GAR~<^&)gq~( z!jc-P8Wsi4>4Q{-DJSuAKMiUOemQ_vg#9Rdu#xNaHqoHQM9TzYj&}-dZ8!aNLlO;Y zggdP$ZEKRS*J7pL7vG>k4Tw7$i5tLiKxzlB;lMR^mYZ_oaSdoy{-}s*DE*F4T<4w} zJ$#R`UCdNtNsg#rEHeJq=h4I7Z0Cv|SZUY$;C?XJm!d*Lft2}Zba#L+J%#pc8F&F* lsj(O?_5>2bhpxyFd384Vx^pVFJDJ2!HG`0*#!{O2`v;HtfO`M{ literal 0 HcmV?d00001 diff --git a/public/animations/CNs011Br.ani b/public/animations/CNs011Br.ani new file mode 100644 index 0000000000000000000000000000000000000000..088ce7acd3ed8b069857f934ecfca109d2e4a41c GIT binary patch literal 1161 zcmWe+U|?`FK4cH17`&dO+cSL9um-UiftVGDIe^$H$kW+7I3$P($Yuv(7gw+dNC}7= z5FP~NdxN-aKC#*Ue?>67IEHueO19m zj>2m;AR7&q)!QfLxokct^3KpME^IHzA0Rs__q%OAFxhsUh$`16kO;`DAQ$L@LM908 zFfhR#HPP;J)KKW%}D4MgPsek1!WPfhnH&9B?r zVP3Ttq#GmyiaTc?$8fk-t(B8)T|e~io0l-(7NYf-p`(2u!^tLj->$RsJ~RQu$kf>1zhJlzsJsD+K{~-n45Sqn*_J1y>@Z`8znd+NV{69m|JaE=cSfaX~SM!Dh@c83>kv^PATupiTwF^X>Y){Xpd)T?`LC3)sB9di4Mchyw%-_Ii7*H-i`; z4AOPDwcfrn*?RNw4$e&+d{wqDm>C$~3Dw)b-Yd7ox7gk&g=^hjkmVq~{d;zAo+KZ# z%4GF^Q;-PA3U(mY1v@$dY$KT9j+$uqYfYaCND%`A$Z`%K_Vjb}cMtOSgQ$Drm$_?Y z=6$=V#qX>kBBAGN?N_c~wFBva0gwzxzmva9BuotCeNgB)2Knj+g+TOPJCbVG|AWnb z+5!_Bh{*r_M)q5tn(j}UU$?izylO8qh| zb##F^Zp~5kod-jr?Mt6--VK(9`wA4k9*$6_@vdBCl^~a4C-gXLJ46|X1o^N)Wm&z>c4^kRY^_>1@hNrveU84leH8etC-jR3@2Tivj7olOgntr zzbtWk__nUbt&roeVwkuBW+}|4+nYNAp^kQ)eYJi6+|>TO^h@-24=|}YE<+j&h1UvC z8vnuhowkL$u^l!-8wdKi$M(1cIojxR5Qh~z=_GXZS#bO93Gjp1*oCu;Xphi@mwI8YNOOgYq66YA6z z`R)83De;e!H%vBoQ4w=ojwCwnjC1%Zv1_(1X9lOmF-oX!ViSLENmhsEhD7@EUV(}< zMH~4-Rh=3s#6)RW5gS$zdXrc3?(Hq=%8!S|?$A!L2-qiw-TZ~+8udUdD(I27M9NO+ zxz|_mNOeeUs2CL^^(_L_q-Pek@>}W_^;F{_@osWZpqic!<|R=ZR(DsP5KpAm2vp8z zth*Q1ErWH>-$UrX#=0(8w-?q0HL2go3qzg@@@@cD)+=-6$w$?{GW$ey*O17#2u(u2 ziN2nO)T;IM*e{+iE2aqpe@b9BwaQ($<=i|q%KSG*a^o*18Omvn9U5z#**2W7r&Pl(<5`(YR2FW1NYYW(plV^6~jev;MUOS)@H-$ zx$1E4!Zjyv-*i}W+8k>Ki#}|R^XrlFtg0=G4jyq`H#wZ$t&B{*_)$mY$CwW zX>+|AsE6%$g3}pry1()Si=@^tZ2!`$N4=*DgX&^znoah8>jnoMRt!bo2z^XIA2Fj3 zlmqVrz6Y9Hw>5G7kPt1+M|0PkKh*3p$GTk)ycy@8{^#(L*@Ge#eb9?`SuxzNdqQqC8&2bb5Kk}J=_}(ysJUK?9*@MsjsVMzv}GfZ!DzI zWNMD}j6<)*Is2j_T^$<|_MaVTJPK9}>tf${6!0Ak%EnQ+cWTldShr7tvNKrT93x2| zNNe>6?wp{W)f;kSfxL9`TwZ%U&2f>Wzc5HEzA!(1Qz2FK<|WV69~#RX-(ive0V^5V AV*mgE literal 0 HcmV?d00001 diff --git a/public/animations/CNs011Ni.ani b/public/animations/CNs011Ni.ani new file mode 100644 index 0000000000000000000000000000000000000000..0e16e10046a2ef78b53beaa74ca9c225b21523f1 GIT binary patch literal 699 zcmWe+U|?`FK4cH17`&dO+cSL9um-UiftU%1S%BEj)7cxuW&>hh{}6vaPmt^#R#aKA z7N9r-!_4|Gwm?~sB8Gs>2Aj86uO0xgA*O;%(FGY90agGexT7Z8{aVv!0#*dJlLN@~ z^mFrf5AyeesC(jf}@2nvrq33JuSFT{SgXn^gAQ_OGo%~%QVdBg{2FO;& zAYa{}5QyGuM^f$jf3Vq4TVP@X5&6I0$bQRH)BQ>F>-Kh-SM3Gq2Fb7kv9ph3I9#jN z%E`8_ANu#rOPFs9(R$3#(LRvj%kJZk)%WM^oxBI66$C(9L6-Ws!EDt_+jry{o4ue~ zGt}0ox4QQ8nyvN=bT`>fDKEAKX#*Jw@;}&CxZX>bd-iT~>)W^7Z3fiZ&x@SwJ!g8_ z9B@*zcfC?;1=0%wApd)~I=Vm{x8|t&&VwP*_N7lZ?*>c5eFX|%4@aoecvmj6N|4L2 z6MCGr9ij|Gf_&iP>W=V%ow&ljGj8|oIy>(}VN%!rZ%=$=#PDDC`q$A#_iTn1{e*wn|*mwp80B#tOXaE2J literal 0 HcmV?d00001 diff --git a/public/animations/CNs011Pa.ani b/public/animations/CNs011Pa.ani new file mode 100644 index 0000000000000000000000000000000000000000..43ab4d39fb5dad38ca4eea750978adb2c1a43fd8 GIT binary patch literal 3747 zcmZ9P30PBC7RTQMg6stB^0|U6(JCTKuo6fvAe%}7EzoIk!v(<&1V&JSM6F9(hEAoU zU<-@9K-z2Kk~J<7MLNQEA-*4Fn2lRde^D~a>8o0C3YjZi==rz`GL`|&D6r| zjJbdMa7JE`yBcy&cYhRE;^v^s5F2tWhZ>|ciG6`HKi#c`nen+oA4XZiZ3Fk5_~?X~ z1qt!^fGK+U{LGH}%pOH`)<6g{5!%ie0hfvZk3jqfZ+!9{9~6`Em+s14H+xC0%eML> zlZv)2r0)wEdvnVUjo9`)GNJu6O0sToiu~u!i{9Iw5BP;yQ?zT0lE}FVMxTFLy)k_n za-usyZw%-ipZ6}Whn)6<-hI%ETwbbi1wG(I`-5I4=xzV;ly@=cjd!5v(V!Owdbz{b z)SEyLI|IkJ5A-HZog7RZy`f_(hLY%25B7ZIr1zmIk+~1i*-ncfiCK?3!@4A#+p;n}Hf(UWKe^MfSKsJ0i}I=;UJn6>=5ZV0E$JpqV?{nAJzR3VCIySdbA9dM=J6U-l6uVKFkLbo4^<|?HLZq<6 z3%@T1Td0o+h5CTcL?1g=n2#uh;}^pTApAZLY@t3P73u>{6MgJzVLoCM>hotHU69~F zEHiJhg}>X9Ej9O9pd>Q)A*tR6y)1dhk*k^2$Ih~}=a3G>aaNhP6p+hps=2Q)tW`0W z)zzzTUunaw?!T)*Di~*DjN1rtNTvt$!a+|BdNV-}Y2?FL?2Kc{gRvlyDr+&x>*{4e zkF4sy%OVAgvxm6NFlMOBB8l6e7iQE80zI5P#yJ|}3Lp-fYsnN3vdZ0xThn-2k(2mu z7OCVx9E^`G2E9UvGwf-ljw&+Xpd#u&lmw%MHtvCN!AgWZ7xq~G!sU*eVqCOwSM2af zXTQH=P2#<`^Gz(Y9WjNTWThmDYn=3RQe$KjinMZE+D*6Z$WU)G*Qfo5yl8!d9O-{8 z*+R~H`YXmh&evup^pj!J0r;rzLWk27<9dR9hSo*~V# z{*WwYVuk|3k7Jt9Hl*Pv9*uEPF|YRg_V30j{Fe17x;`7`Ur2;D|F#41{0oT=|G^u( z>JcSkXG|<#kQrOi?{FhLyPw8iRTWar-Zp$|fA?)zpQ!`C?-*P-6kq2z`1; zat~@?W88-t{ZM1x#D5+>2sPl;e$`(;y}J`shKXc`~bh<`LL}*zx^o@6tJ~>YvlS-dsLdhuF=1 zh`kPK+*PJ@E;=*8>&&FVCjOj&8k?a8YuPMOK@G(I3~Kl`A7n0Ut5fHA2DTt}3#gIS zajy03`72)IYS#1E&3%Zy0cyit&5d;2WfOXc5|PRI{${lAHRP7coI|ep1$AY&-P`JOFNS|e1#I) zxcv@PmMflTen`TzeYhuJ-;Mb}UY?OliiXp*Z z;qN=450(ALMUpa6BD3Rd4>HfIOO7vQ$s3}GIB1DH+0%o>4_Pr-;j}D@e5{Yuhqro= vAu3u9zaME1^fqFh#33Xq=C&e6xlPbwL@r7d71bTLG`>*x!pCY65BvK5HBM{>uRRBwv! ztM(4bg{n{oAPoij-I20d&vo|QT)iNax>yn9!Vey|Ejay;dp2gW3o1XPLO?127Z}6D z>Vrc6v;xN^)k?&hqIo=XSmmE9F=fTow6hI_n49%+Z%mcF+3{}n=%|b(bn=xNxjPDS zD@ksdlnW&R2znVJj?^7_dpXc$bP7$z+boO#qGZ2$tX@(vn|&^w{f~+nL`c3;1D5%# z#aLFCPiM?fy~+Ut1GiT-auRQ2)y$&K&YjO0q7$PKHS+QZE_~KRJM>J4+5$(ph zY8>OAGW%A zoTOmA^uAr}``2Hzq-H2Ljm+_*VMS-j&@~Sz;R%3mAWtu~G1l`it8_ydxAtR2H<-kL rHx%TWB6E^CxSmVdALQm6ztdrlI1}KC^oC1F6o!mJuC6Y)%!d8}qK^oO literal 0 HcmV?d00001 diff --git a/public/animations/CNs012Br.ani b/public/animations/CNs012Br.ani new file mode 100644 index 0000000000000000000000000000000000000000..d0e47578d6d1e9da9450e1bf866050a473b44dd1 GIT binary patch literal 2245 zcmai#dq`7J9LMi!t_QSA0#UFBSXybMO(?hB(@bZ@(h#GUk@P}KDl79%t{Vdbz% zok(hWa7|9mS=P2^D36ODhuLI`J!T%iJsmeUt*Tdf7Q^E=6nD1u%BL(m?z4y4{Ub|R z^>7|PeR*5HM{&sr9`B6LYAxRIOU4Q@BI_B!L&?Wx1ntcf_;AoF&2);&VWoZMOxADC zY}1M)T5XJ&i>#RHb&ggY!)yNKuyU$xGtNh=$9z#az5^fSE@6H87)n?0zaZaTkXKlc zkA6s|uKcpr`m&JRy{*SS8W|eV`&dWRaf_EV?)E$RecqK5*S#$EOhz!0OI0WIvGO~s z_3!N`2dJ$w)CSOa`i{Gk|8e9%^=HhDqt1!7Pd0j%wxa#B~yfcP7H664y;RSdMUEjU&rC zH&f$aS>(uQ6!(VaBcrBc^kiHu?QEW6yWU(!4pcGpf#%KgxR&br((9|7k3Jt#l#&`J z7rM94%Nhm_22Qk0n6r4m8bB!qN+I6k*33Viu|c`{+QP<8snpC*GJ9FeW@{k5Brhv= zm){BVDK9p&D>Z)Q*vKD_G3m8Vm=CSr%Iw)^WNArH;GwI|3G>MsSEF$-FLE$1axgD) zFfVd2FLE$1axgD)FfVd2FLE$HksNf8gO8d6i3Y^t(O1pu{vLK52wrv{RBGMTn))F> zz0CdF>TtH!%q|UE9jL{?doVxOmBSlUcPqI?DO$;<#a+Cn-d5*Fd+_@+-=(!@RQ<`U zlJTW-Z0E)+9K$DGA5U(c-v2r63G&o6A^GdkFLw3yq5Fe>r2L1rvh2i088cv@iSOJy z*yqYU3eQLqng}k}z@dpEhbD>~nkaH;qR63%B8Mi59GWO{XrjoWi6Y1UmVAuz>fdB! ze>g)j?CCv}<0dNi+#z}2zR-X4%hs-SOGWjRaubVFAPeNCj#$Uz^tcmoH0A_sjU k2Yn(3eIf^aA_sjU2Yn(3eIf^afBtycs>%<3qdN8aFKmKMssI20 literal 0 HcmV?d00001 diff --git a/public/animations/CNs012Ma.ani b/public/animations/CNs012Ma.ani new file mode 100644 index 0000000000000000000000000000000000000000..51e44661931d04f6292627fe264e83e2b2e99844 GIT binary patch literal 1593 zcmZ8h4Qx|Y6u!6H>#BuCWP(nI3^P&EEx~p!w0-Z|{j_Cvt*KRn5q=b^EXFW9(@nOu zI^#@MP$$BOu+3!wYt*c~t+=t*N<;%8l8I6Ln=?_s;V_8FoVXCrd2gMsJ;{CVednEX z&pF>Y_bwuY)SchU?km*`Z(o0y`QPu5Kt<@>%7%B1t;VK-_gxDJNnyo;(7OF4O#5TO zP>$Kc)~qWl#s4T_mRi4MrDIn%p#}9j+1WeZWcbeG(*F~J4mwzC-FA<|>v3ajLVtDd z7>Vwf5H1D&us|khSdSJUVOAnpMEt;YN=`Rj_k@PfvX*9M(cCY9jVzEMm#Kp+LBg#3 z>>;CKMF?a{*Pd%-QuRrhBh>e62|aUf1G{_r!2Et)IzqeRyV%S-=6T(gn+TVwyLvP3 z5N6j-S?6OwWe_=ChgR4ROsU*cxgIeJ@}z}j^VSr5eF{szpWH4)X0*)IXR;zoYMg^k zv|Ag3-jHzcqUM2k>rv?qN@!(o5qoUPmTVtsm&9+MmXM{2KpDHRa&@w9HYn-*VQG05 zq1V}+toG6B;5->*(wVBzbE5*rYg8_+lyeuJ0|Ma%7=JKz3PsMH4ZLh>6 z%%lh6j^&z6AJs25Vq7^Hcwkl)F59~K5>`du;;XK{p6K4v!=s}bD@7jDh1k}Xy`!D0 zKjdl8)N!!lGIh8;3T}@Cx$Hi9(CCQ4pE!L{%iKk6R$UHB!()J#Tpmx5RU8xknzCXi z)%KW<8pDAAMhmk;Y_cwQG`zuKUe&#Z zd4oU9BcmZr0ef}YnWXPU_%)|%d1TZzXV)=jhm02Iwle<97+-XG=J@njjEA}7GIf~! zlksx4>F0&X(%yfiLif7@R*;Hu<4A-zK@6Bhde~=Tojz02@LV&$0T#?6owVP|`Yt_^ zoPDs9U;L>{CVvIgwi{}@1!^mR+T?=af#*~18t|$#MSm_LRonL@Chc9ary463OI`}G zBR4oJ8E%pW0!wi|xlA4IkAk!Atg=-qo*#U+a`=R=mc4L6#AZ`9mJbnqac79}v3$`G zY?rcs;lypwm-u&0>{{M=(-*gI77h$KjRxq;F0d0+cEBx{slzkvjr!SucB^=1=}*E} zo_-_p`1pwc`~DkIBn<}>e}5bo@l0pxBEVz)P$MfDSTxvi_<6zIHg5Fk39agGWLf&$ zL1$q@g6!)Nv5F?hW!UtP(E>U5HVwwGZan8WvUvA7ZFAHvjP{uw%bpRS>|^Mt#^|I7 zy1A+xIOz&&8}pVY_Bkem{!b?48&i!_PRb2}ZmAn&{PTPv|EUSV68%&5Qse)<$bZMn B%_9H+ literal 0 HcmV?d00001 diff --git a/public/animations/CNs012Pa.ani b/public/animations/CNs012Pa.ani new file mode 100644 index 0000000000000000000000000000000000000000..b80a9c3686a780b9653e3079305a064e9f5b3434 GIT binary patch literal 6900 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 O%_<8zdeq_oqy7aBy)eiC literal 0 HcmV?d00001 diff --git a/public/animations/CNs013Br.ani b/public/animations/CNs013Br.ani new file mode 100644 index 0000000000000000000000000000000000000000..027e4f86aebac4b471bafb40df08b4504252d6c3 GIT binary patch literal 3417 zcmbuBdr(wW9LMh>i=rYAAL}rp{ow;cQMeaUkUhNQF>R6zh_5C_vP4lFpm4B(ZG4RQ zES+IYu}nqM5S3k2gx$49t?Z%cNZDwVSQ;ckB9WT(``&xca(DmepU%wPv%lYd&pE&I zJD>Br>n{jGNyk-29ktzRF947NkMMTio&7-5Bh#Cz|}X3d`dGd>k;v73WibR zcDGK@btL#RZZ~-dy1?>$)_nbfF=wQ|c*0Y$H;;YF*6hp?*XRYEU)L}eVKBtzMK+4D zUUDD;w+9VW?0b7Ju;1JKq&(G5@d;p8wna$NAm#TjpCqxhJtLr(qw*n_fcZMqVu!H}wa$S^E<0I(a+BMwKn_-Ck z`q3*=(J*B`ZvUX#Jf2y;%I`cLPe0dqru)0b)2rG?gD*DCP4_>E3~TeJnu3{y+M+G= ziy04PS=a3u!E(-dS{_ng(m6q*OzYi!$TsO&Utgbn0QJwPr;O{}eaL#l(A_8g^o(#6 z4%sI4P*0iFyZf#v*=EFoqnGCLq#hZ6L@~sFRbi#|9fz$8tdwQFyAPS}?&~vp(-;a0 z52I}B-F?Wo?}QokG~Q8{`J)B%P*(NszWtQx`!pZz%i%?H4WtLzen5J2e8u#q)R+2f zktpMW2j%a2>K4nq*TzO9B(AqomOb5lU>(g&w?KJ_H5Xf9&CCw1wi%%zwp@NE7G%4x zuS}jpVZqD}s(M7i=SHubb^m&TKzotzTc$5hjVm*JH%-$r8!#;zos)2pqHS%P(x`X@;mi<0cXekOTQM&$hWl_UdLNdt zYtJg1xDUHMO=ExRjgRI&&^&L<+ic@^VU!`IVV63JW$8!3k=%zKU{a7md|KiH*l5^4 zouv?${?gR+3}yH2=a(9{_IfetfHVuc*V-d7NcVwP8S}g`-ZJj))RTKk>uk6k3#;wZV}gg zuj>4$R&m`eRfqb8SlA?QDX$|sd@sdy%)M%W@uV*pXc^*E@IE?b#p3V4&7k z4u2y-cdTc7)Bc8WGhgl*)a&b4t?Ez{x>h?KXIK*j@Irchenz4 z)R)%9fSkG=t65}Gjh!VH#+agqqpr%n@S+-HY+|8pzu|8YFrW?0&d!4k-M=Ux^odvhR*9O zAcyO)w_lUHM3AG^WtsyekfYUY`m0+6Ik+(I@&|IZZCK38OplF;nI86{ z&a4=a1Kp~Hxz>>4!(t=pK+Yl}yNYPPOC)+UR?7f@oHGd()+Z~g#ZO6xte{SyHp}EV z&-UuIt+rE1+wIj!yXBEu8>5tl`jRp`E>VH$;|r4->=}#L^9E}paMRj~9%}(3)Zdgi z6(gS`2cojaE;9zSE0Pn@7>cCwT0nO_7NCogF+KHAeI7j3OQLLKH>pgDYr zLC5W?K;ZDj2OW17RKSM=O48^69}Xx%qvPHD>z!%l7|hmeSA&652MmxF!o`UYFtCzOiyAP%dwNIRF8QB4wgU{@ zb-=(~2Mn-As%8z2L9*t^fdO2MiEPi0^m_@uC5PdRl`paOMDq W30-W^H0R!cfx8d&0`31|Ap8rz4#Xt@ literal 0 HcmV?d00001 diff --git a/public/animations/CNs013Ma.ani b/public/animations/CNs013Ma.ani new file mode 100644 index 0000000000000000000000000000000000000000..9e0779a4d33c7773acfefd24b06a40e3ed4e99e4 GIT binary patch literal 3169 zcmZve3sg*b9LMiXQ;}+2s2u_!B~4N+#wBfI69$XV33 zR2D}sD$Sjn6uSp|B<=Cs&U#xd*+W{bZGXRcOcb0 zeZMW0w1jRTIF9LMK0m&XxxK%S`TZ6-n$Oobn%{3nU-S8vzUKEUcQT)+s2P)_^;7j|UCOiMOmz#VlON#bSNhSdnW4qQli75JTq?*`Bc6Xfz)u2;h zuZnW$+6&Rar6IwaPy3qA8`RqnOMxJkQ+5Jha!HF$ai+IS&j6jDIg9M7=12Es4uma>`cR>U{oSB`{* zHpmwaI%=f`9a5V0Pb?(oHkWcpxaq+r60&~?dp|l-K3rAIA>jxsIiY5I@K-(Kx#B}x zI3)M0aouZJHv-lnIdR-)q~SYzes-RcJN`b4LoH9vzRU7n%lN2)-*eu@131ug*ScA- zt`OFtro%J63HxCZUze21E!HM-plkZ%(-88T!jZpHtKwcw+{dA|J9!B?_t=l$p1YZ= z_TR{1W|)U2N$Xuy_=BVp^1$kt@^}jIjfD8HI;K2+GUkEQO?h;{I(Jxy)J=H=8uP#& zG35~nd3Zt|&?jB%l*T-;S4?@-LLOg09?&mcd0aN;fjwi&BMtI64|%{7#ZV7B>oe(R z>?c$~9{M7|AAAH%Qw7Y2(qi!`dfbUDW=897-O;FA#^9;2QIqJD{n~v#rx`l8r5cyv zg8pIFOLg>Q3I`u%ux&pv)YjEBY1A&{JFXmJo_qT-sHpy+yo;c`h&&~8^?eqDifXywMy_QmdAE`b zW{+$!gNiBxMKyq;?$oLnXa9W+Dk|{9B%;dn=3}*qOlwjqgNhms488&eE=C5ZsB1sl-78ryZ8K4^f1O``u!C+vp7Z{)#%Yngl zU~mf<^aBQ{#+kriHZWKY3|<2RR3kDF1A_|@O6d^LfnFT&2cPA7HFjyr_C_W*OR-D$ zT#=)vC$#miC%Rv%niT#kl2cVlTycVn95pqr=Wk1>9X6fI%?j;`@pfExt`q+Z?8@j@)d_>dq{eL;j}6$A9W3uH zT}niu89V}YN;s=^3MwRT1~~HAc%Qc&;pS9pNy-p6b_3Wc@_KIBg;Qiy%{lfS*txN} zT)0OrsTqEf#Re4SgUu(jvGJIy0_pU)D{NZPrYdYY;e4g7xs8Pr^3Z@SA&)?4 zIw22iLLm=qKp~H>!4~qsW)$*3K?r$Z6AF1)ip_b%fGy;K%_z(d8&POu6AF1?0}AKu zP%&_sf9im0K#k@;R8GSFb)+DB61&R3A2;=6pbpirOYBOb5>%vP$r+y=;YuG=gj?A> zvM$b+xF0U_dD`4pf>mC1(2I?klFX;R9bZx#;wQmM{@AJJ^OHhl{kV*hm)_e-p*o`O zKCF<-k#+onmDaimt~T6+BP&^?K2_|>=On0jajtXu?aJTFko?Qic|0Fy%UA5XTW0yt zsT^tE&GBNx;l5W!dX#r;44DALsFR&)5Euv7E zt06d|u5J`%5T@6Bf@7Wjh>!a}^%*PNk98345Bc9`D~5!vt(u|4x2NG5u*hks?trqH hJyz2!GsX-9etF?C)9d;EC0EvUNi!=u@mjEo`VYKn_Im&T literal 0 HcmV?d00001 diff --git a/public/animations/CNs013Pa.ani b/public/animations/CNs013Pa.ani new file mode 100644 index 0000000000000000000000000000000000000000..c3a187ffc4af50523ce16da47c5b58abacc5171e GIT binary patch literal 3647 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 G*Z%>HH1TQx literal 0 HcmV?d00001 diff --git a/public/animations/CNs014Br.ani b/public/animations/CNs014Br.ani new file mode 100644 index 0000000000000000000000000000000000000000..ed7a0bd6141feea23bfd7088a5bb17ec6ffc1691 GIT binary patch literal 3174 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 F{0jhQ(Eb1b literal 0 HcmV?d00001 diff --git a/public/animations/CNs0x4Pa.ani b/public/animations/CNs0x4Pa.ani new file mode 100644 index 0000000000000000000000000000000000000000..3ad2f7f76a36f3381b69fdf883f149c873c9fe79 GIT binary patch literal 2005 zcmZuyX=oEs5Z*+SdKL_b;8j7Ypw82-M4@8E+NhxZkTzHq!4pLh z?S-H{#3QXo(`I)?6cj;H#o8ZQgNoLxUbPCMalYMbd_H|JNoMlRn{U3EY=%kyKNbqZ z`jh!r`&V1({ltJj{vh)IK2t1ug5)bKNj+IyqV@{@xPHd>bC%|vt>5@b?G}7*f8+Z= z_NLS$sROm`Hp3s?&g!OF-P#5d-zSHNLxG?0`h#d$^mux%FdN%qr zgB?OdTUwao$<5Bs%|RcMG&z(K%wLI9xh) zUn$@)uUM>{JvwvvvRMP~{mATO-mL7j`~pLfy}?ng+*oOgz4Oj2D8l=?B9=FvZLFta zmr98N7!&~uzAGq#SPRDtiacsAWU*MxMS1#xBA=TVv+jqE_?(ChRpn~vI4C04Fcslz zKoPM96cKA+N5mR&N>UPifFfQ`T#^rIDeAk@eId2BCj@f5|Cc0*wjc>;&`mJ?K$0&) ztLBpMi(@Vczc{G+mSq1>u`5vnl5|oHTI@s(NYYC+UUPCnk{TLCkXsx`f(28!@tBhI zay3lu*wMMU^Ou{xxj63pMTy1fxLM>n^xe*m*QPtcm-mMt(QN#NM#_y)ZVTmJA|_P@>5NQ!RkB0*NJh%p#2A#j80Ytd`28S$14y}h z#BVzByGH!liC=_bCax=vgK{PgecUFT-Y~sjndW)jOGzgEp=6oVa8%Cvcu1}G)yOs- zwdYGYJKuLOTpLQs$m>fElLR&~7Pd;3qzi?ta{TnL^I1&oTeCZi)<)Wvu&q_q;R7#* zvX^hiWIm>)K<;(A?`t3>V+GxZA~>LefhG33W9nM<=1V&pHlf0p80Q-kJL;&FSIK4C zw@*R2%Ms1Q#AeTJbVZB&T8Fz{mX`E5F|j=yu7hwiv43!w7?0~D9402-hqgoO`6LFZBh{ME093(RTB=m_Tbh`gwzjD=cP_6Tvkg?gkZ#*=uKgyZsSF%%Xt(mp% zXR?#-w}~;_kAvURf-LuZBeVY2#E@FBl1&%p6wgE R&gFQ%{uJ6e*?xBOOF5m literal 0 HcmV?d00001 diff --git a/public/animations/CNs900Br.ani b/public/animations/CNs900Br.ani new file mode 100644 index 0000000000000000000000000000000000000000..5214c4191e8f63507a5ccb18f27f013f5a20fece GIT binary patch literal 3617 zcma)9e^gWF8NQJig8~C2!X`>KM42ajx ziIig9*E2)cTmq%2Rwjq=UuY;r)0%C)BED%7r6^U*57luU(ik7Sa4@s|Gz%9FJpL0w zDLzu!nO7RqSlOT$GnY-0AyQ?|VkTf|tV?8LXy=-Sru6UEp zDml@+BhTBO18OV3wKKI>7PHIm#ppBC2ijl-aEn0S;U(-hBSX5ce)mXwR3fDe&*4Jw zcB|k>D_m@~6yYAq1J^Zr@C9wv(i&setO@K|+;d6bZrJWwGS|+t8y|AfY|VH|(b(Uf zc{FmVakf2`#r2k4i`Vu)HmENd9cah3YKCfir%btTcr9zldll9>0j_K6PW3uJuW!e# z$N2xd`yt%jXH_)&&hWJb@#PMOnWMNhc zbx~g%EK(W?W99tt4L%)KUx-hOBzCIge6_fPLCl*%yN2{(Q%FOnmg}nzdj{_Z>*D%s zVUDmrQa*pG*q^~kaQpGA$iE&|FN6nFS{ELTAQ?`{QF48bsPAj|?P9Plj-MATucJL- zMy$ui$m;{54x^7_^|`S^Fj>MwzvB17bZ zy~<)bG_bS|#w81Ea7>8LUqOPbOrafPMjSs$Bd??BN$X(OvOoir*3tZ=0mdh*huitQ zAXsiV(8a_Iq!tgIOxeKByL)L?m0sDVOm_Q(ZVqyj7EX&D`RPvR=n(E|Y-M`lT^B8M zwS(C_Cv&#tv$ZWP>^q&%*#w96*FEohe+fCEyB*B7;>HeBL=tvlBX-XqM)ny9syZ&u7j#b~uTB*y#3&#<}hDM+mnC zxX|SUXF4@w-|PB#_U=g+jh+4g);s5bBz zg*jTDa`NB`L`yonHWV22+>$4qJURp7x(;mZcrj%XccaY7gF6u1a?pI?&{_6sxsykS zK%Av3O6RH6a34YrT!Cno=D-<<=9J=S-C!JX+^?=)ljv?i8zy;SJnw6Ip z=oEmf&Uex19N)nl|Mafb^+3+?_8<@LLB{(pXg*%!;fmJ{^5`Il8;X9nqxh_i`=oV{ z2Nxl@U1Qw7=*R@luy>G0CqZ0e$!eX=WaCQr4Dx6u_ydbSlFvV5$@xVNF(1Xh`lt3~ zFyAmLsv#g`MwN3J06A_d=k_h{ufi8e63B#i>inD+1so=S(|W~A{V1l@lx@wi3e4;; zr}WJ$y2IR`8oTiOIdLwW(|p)9nM?>D_>(KXx(mqhOH}^rmr@z)*Uf!G<_74!1w8tu zX6{hc3>|W@g4|~;x%p39^DP4MqGeUj?p5cEb>$&8DZ*tc3#3 z**@8r|6om*>eQk6GA6$4JAwzCgntOgH0K7X*V0k~SJ(W$JJ2xFEl|G#!M8zhkmf)d z25t}DUUC5+qL6IsqL7di`(HJ>@2D70gHA7u)_SRcIh`Bd_lLil{U@Jz&yQ@Z;M>eo z=IrM~)QsI*4F9<94g_r54Fa|Lrk&}$?dtV)r(`{LJ0|o;1F1M^XbmT3zZTd(tYYG3 zR0d}~@lihWXi`F7h9%!{pf-;Iw1z*_lb-#pu;N z+vnGR8>0sb30!}kDLeRJHhny^J5acUnU%=-gu1vY+|<16;O&jxy<>rG>Pln$h1KL4 I7ogPt0PAb=@c;k- literal 0 HcmV?d00001 diff --git a/public/animations/CNs901BR.ani b/public/animations/CNs901BR.ani new file mode 100644 index 0000000000000000000000000000000000000000..623af202ebd56a0ec57d17e76be2ccd21b782525 GIT binary patch literal 3917 zcma)8X;c(f7OsM(1qA{KLK2k3FwvtTTRQ^P-6aMLPOx!0s01V6ic5~Nd7`7CJHU`O zfo?MjVisW>gDfu4)lfh$z!}GF$cbBsXQHBsL)0iZ1~SHtxwopjr>ZAE=AKi~b?^7R z_q}>`zk2?}uLlWfH@H0DN?bI5Nh&5h;aXL&VfCiMq5>@D1rx+L_#csymYS5T#*{b! zfINHL@8y4BileF1(i78@y&QGt|8wIwTDK!{#D^dU$JvyQ`@x|C1G_IhbN> zO#-#0Pc?Mqoh8ib$G-At&yy{%0(eBA@9c8sm!b2rfBo^5R_|a!;+n&S;NzCU6_LI~ zt;)nb6bG(roNZK>3$AR z_jgi?`Czc5^~++Tf>*-htn0T-pSq^jglZ7Cq20tZwEQW06m|j?y&bxfmIiy6K6^PJ zX7F6J4ODS+guo8g|2A;?xtc2sD3Rd2!W^RT2{~VM1!hFChDQ*2vjP>{G>yj7qAEem zBDoQWVR@ze_OZ#v-8h~gMqKI{E{$Sw6Z2nk7m(p+8Eg<{9OJ`=i6T|P*B$y=V3EQI z^bqr1H~5N6eIC9of;c1<^F19qXve(6x2sbhHia-`o??A9F7MzId)=|V92ZA8;U%7b zx}!hsli>E_PY3_LOFbQ4P+^^aHSA>g7wRq6r}h4C4S(IV*B#@h_=xLhPndDk{d~ps zB42U+vP)eK4I|`31BpW7C)T&#PgExQ>~+WdQU2okUVriU4_)eSgPkJ7!3TR4ML0CD zunxv03T$vpINra-fub^jc8nRv_#r{!I+~ua4t6aHG(ce;%}*F$e4@ImozJy{#fAf2 z%$b4E;-Qm?9I*3F3&lvIN?RnM1~cEyc5cYLseVJxAA*i{;_mvDMg>38Q+!uz>5n60 zW~;v1*4)f|&<>r=;jo@tz0bH5a(s7d>HXRDZOQH-%;`K`J9IdQdrx98hnZJ0C-Zgf ze3xtK@balM_LT-PMTNR{=xk22FbCbuai{%$TI9vEArnkRa)(CtJvadqEr?ffYWjTG>3oX zZZf`+%AyM(j;a5o?WpDk<9j5P1t;Kez3v9{so!)ltC*-=De0+%43x=n#lg zb$H9FOM^_GLk?Vl(=5z^GjN*2vVCM{*&x%qhYV(P2O;+iaKRxs&7*$aGDa0-`b)l^ zLYF|?O(P|{1anm9G_l|moEAN3-q~Dax|-F*qFW%&I;d~EQcRhy8k$&e3=Zc3n#bBn zQ_#mvEV>5bs18cD<7R?Mx4Vf2r{FXTbKn-7=1!el<_);LCka z840-ZG(CmR@eJnphjF8<3v$}``dDxeZoD5s^QDSvQ}&KN799j}=Y4jyWnarNojTIT zf{Spt!)^w%k5`~cajcI;CqZ0&-bPtYa*iqgXdjDaf*)A?NG@BbN_sPiGavH1o+;hY z^e?oOsI&2z5y>1HK-!-qbGldel;J^=3uMBXIxXof9*4<49Jym8{x7CFIZ>UY=9!82 zCU?)xd`Lf;;x~`ajE#*c@q)W*r~%2VYa(lKI*j94R!L= zexTm_6)J8IK3*IK!yzP5y~rt~|HN9Q;h~hSu9L|TPmn9_MbMv>CUjr_u*ACb-9jrK z>&UVnmBVvZb?}!E(JzR6}Z6Q^oYI;Z03zoL~e%<@N`^W;{ z0`kq_mX3p?>7huk(8k7^8ykAm?~3IY=D$R`>W{FcrYPn%6etM_ZvAs;Gx8S04X+XjsO4v literal 0 HcmV?d00001 diff --git a/public/animations/CNsx11La.ani b/public/animations/CNsx11La.ani new file mode 100644 index 0000000000000000000000000000000000000000..19cbb2c53e3a1769183fa6b11bf5e7ab5292f14e GIT binary patch literal 881 zcmWe+U|?`FK4cH17`&dO+cSL9um-UiftU%1S%KKcF*L{##9;$sU;hw)KTnYK9iSYF zG*}Z*f`MUX{TExHG)NUgKxTu@+pAX(fY=aoLB{C%c{+PXfE9oV?x=}&zt;4bfE6*Y z04WY2_Vjb}cMtOSgQ$Drm$_?Y=6$=V#qX>kBBAGN?N_c~wS(w_kRTb5ekXsINSHV? zkO8vQG00apChh{}6vaPmt^#R#aKA z7N9r-!_4|Gwm?~sB8Gs>2Aj86uO0xgA*O;%(FGY90agGexT7Z8{aVv!0#*dJlLN@~ z^mFrf5AyeesC(jf}@2nvrq33JuSFT{SgXn^gAQ_OGo%~%QVdBg{2FO;& zAYa{}5QyGuM^f$jf3Vq4TVP_t4pPYYf4`CamZzrsljhg$?J%#}tHa2^iY_*Rk%4i4 zk$`=MPSAc?smXgYjlK4M0g8ob{ z2h^Je)GGoErGG$sw*u`=0ovORv={6cAOUifvyWpq+{s!iC)>Jy=-)RlVZJTMiwum% z3?1zQ8NTd3{#bo~-rmW3fKFjW7gJznV7%>q!OrY)iq)!fnfv?J&ff!c9$0S>RBtX& zFUUX;09gwPC?7XiNa>~RJMxUpUQn&s1`>8rZ*}eGHCyc$=x(x|QeJEe(gu^*0C+8l6Fvv<8xYX#B^0w520xH`H(RINFxzVl#6 zw0-H*&AY+UVDmvv1EmQMN2t?yS1z(jkjtduCVWn+kLyv z&il}m6C+b&fB%BvKA_45Ckr0g`3?%O??^^pjjh`a$w`@B&T_wA|w L0*+Cz@eB+A2M*s~ literal 0 HcmV?d00001 diff --git a/public/animations/manifest.json b/public/animations/manifest.json new file mode 100644 index 0000000..8f2c4a8 --- /dev/null +++ b/public/animations/manifest.json @@ -0,0 +1,16 @@ +{ + "walkAnimations": { + "xx": "CNs001xx.ani", + "Pe": "CNs001Pe.ani", + "Ma": "CNs001Ma.ani", + "Pa": "CNs001Pa.ani", + "Ni": "CNs001Ni.ani", + "La": "CNs001La.ani", + "Br": "CNs001Br.ani", + "Bd": "CNs001Bd.ani", + "Pg": "CNs001Pg.ani", + "Rd": "CNs001Rd.ani", + "Sy": "CNs001Sy.ani", + "Sk": "CNs001Sk.ani" + } +} diff --git a/src/core/formats/AnimationParser.js b/src/core/formats/AnimationParser.js new file mode 100644 index 0000000..7140556 --- /dev/null +++ b/src/core/formats/AnimationParser.js @@ -0,0 +1,217 @@ +/** + * Parser for LEGO Island .ani animation files. + * Format spec: isle/docs/animation.ksy + * + * Binary layout: + * S32 magic (0x11) + * F32 boundingRadius, F32 centerX, F32 centerY, F32 centerZ + * S32 hasCameraAnim, S32 unused + * U32 numActors + * For each actor: U32 nameLen, char[nameLen] name, U32 actorType (if nameLen > 0) + * S32 duration (ms) + * [optional camera_anim if hasCameraAnim != 0] + * tree_node root + * + * tree_node: + * node_data data + * U32 numChildren + * tree_node[numChildren] children + * + * node_data: + * U32 nameLen, char[nameLen] name (if nameLen > 0) + * U16 numTranslationKeys, translation_key[...] + * U16 numRotationKeys, rotation_key[...] + * U16 numScaleKeys, scale_key[...] + * U16 numMorphKeys, morph_key[...] + * + * Keys share packed time_and_flags (S32): bits 0-23 = time ms, bits 24-31 = flags + * translation_key: anim_key + F32 x,y,z + * rotation_key: anim_key + F32 angle(w), F32 x, F32 y, F32 z (quaternion) + * scale_key: anim_key + F32 x,y,z + * morph_key: anim_key + U8 visible + */ + +import { BinaryReader } from './BinaryReader.js'; + +// Keyframe flags +const KEY_ACTIVE = 0x01; +const KEY_NEGATE_ROTATION = 0x02; +const KEY_SKIP_INTERPOLATION = 0x04; + +export class AnimationParser { + /** + * @param {ArrayBuffer} buffer + */ + constructor(buffer) { + this.reader = new BinaryReader(buffer); + } + + parse() { + const magic = this.reader.readS32(); + if (magic !== 0x11) { + throw new Error(`Invalid animation magic: 0x${magic.toString(16)}, expected 0x11`); + } + + const boundingRadius = this.reader.readF32(); + const centerX = this.reader.readF32(); + const centerY = this.reader.readF32(); + const centerZ = this.reader.readF32(); + const hasCameraAnim = this.reader.readS32(); + const unused = this.reader.readS32(); + + const numActors = this.reader.readU32(); + const actors = []; + for (let i = 0; i < numActors; i++) { + actors.push(this.parseActorEntry()); + } + + const duration = this.reader.readS32(); + + let cameraAnim = null; + if (hasCameraAnim !== 0) { + cameraAnim = this.parseCameraAnim(); + } + + const rootNode = this.parseTreeNode(); + + return { + boundingRadius, + center: { x: centerX, y: centerY, z: centerZ }, + actors, + duration, + cameraAnim, + rootNode + }; + } + + parseActorEntry() { + const nameLen = this.reader.readU32(); + if (nameLen === 0) { + return { name: '', actorType: 0 }; + } + const name = this.reader.readString(nameLen); + const actorType = this.reader.readU32(); + return { name, actorType }; + } + + parseCameraAnim() { + const numTranslationKeys = this.reader.readU16(); + const translationKeys = []; + for (let i = 0; i < numTranslationKeys; i++) { + translationKeys.push(this.parseTranslationKey()); + } + + const numTargetKeys = this.reader.readU16(); + const targetKeys = []; + for (let i = 0; i < numTargetKeys; i++) { + targetKeys.push(this.parseTranslationKey()); + } + + const numRotationKeys = this.reader.readU16(); + const rotationKeys = []; + for (let i = 0; i < numRotationKeys; i++) { + rotationKeys.push(this.parseRotationZKey()); + } + + return { translationKeys, targetKeys, rotationKeys }; + } + + parseTreeNode() { + const data = this.parseNodeData(); + const numChildren = this.reader.readU32(); + const children = []; + for (let i = 0; i < numChildren; i++) { + children.push(this.parseTreeNode()); + } + return { data, children }; + } + + parseNodeData() { + const nameLen = this.reader.readU32(); + let name = ''; + if (nameLen > 0) { + name = this.reader.readString(nameLen); + } + + const numTranslationKeys = this.reader.readU16(); + const translationKeys = []; + for (let i = 0; i < numTranslationKeys; i++) { + translationKeys.push(this.parseTranslationKey()); + } + + const numRotationKeys = this.reader.readU16(); + const rotationKeys = []; + for (let i = 0; i < numRotationKeys; i++) { + rotationKeys.push(this.parseRotationKey()); + } + + const numScaleKeys = this.reader.readU16(); + const scaleKeys = []; + for (let i = 0; i < numScaleKeys; i++) { + scaleKeys.push(this.parseScaleKey()); + } + + const numMorphKeys = this.reader.readU16(); + const morphKeys = []; + for (let i = 0; i < numMorphKeys; i++) { + morphKeys.push(this.parseMorphKey()); + } + + return { name, translationKeys, rotationKeys, scaleKeys, morphKeys }; + } + + parseAnimKey() { + const timeAndFlags = this.reader.readS32(); + return { + time: timeAndFlags & 0xFFFFFF, + flags: (timeAndFlags >>> 24) & 0xFF + }; + } + + parseTranslationKey() { + const key = this.parseAnimKey(); + const x = this.reader.readF32(); + const y = this.reader.readF32(); + const z = this.reader.readF32(); + return { ...key, x, y, z }; + } + + parseRotationKey() { + const key = this.parseAnimKey(); + const w = this.reader.readF32(); // angle/scalar component + const x = this.reader.readF32(); + const y = this.reader.readF32(); + const z = this.reader.readF32(); + return { ...key, w, x, y, z }; + } + + parseScaleKey() { + const key = this.parseAnimKey(); + const x = this.reader.readF32(); + const y = this.reader.readF32(); + const z = this.reader.readF32(); + return { ...key, x, y, z }; + } + + parseMorphKey() { + const key = this.parseAnimKey(); + const visible = this.reader.readU8(); + return { ...key, visible: visible !== 0 }; + } + + parseRotationZKey() { + const key = this.parseAnimKey(); + const z = this.reader.readF32(); + return { ...key, z }; + } +} + +/** + * Parse an animation buffer. + * @param {ArrayBuffer} buffer + * @returns {Object} Parsed animation data + */ +export function parseAnimation(buffer) { + const parser = new AnimationParser(buffer); + return parser.parse(); +} diff --git a/src/core/formats/SaveGameParser.js b/src/core/formats/SaveGameParser.js index f3e71fd..494f219 100644 --- a/src/core/formats/SaveGameParser.js +++ b/src/core/formats/SaveGameParser.js @@ -100,10 +100,31 @@ export class SaveGameParser { } /** - * Skip character manager data (66 characters * 16 bytes = 1056 bytes) + * Parse character manager data (66 characters * 16 bytes = 1056 bytes) + * Each character: sound(S32) + move(S32) + mood(U8) + * + hatPartNameIndex(U8) + hatNameIndex(U8) + infogronNameIndex(U8) + * + armlftNameIndex(U8) + armrtNameIndex(U8) + leglftNameIndex(U8) + legrtNameIndex(U8) */ - skipCharacters() { - this.reader.skip(66 * 16); + parseCharacters() { + this.parsed.charactersOffset = this.reader.tell(); + const characters = []; + + for (let i = 0; i < 66; i++) { + characters.push({ + sound: this.reader.readS32(), + move: this.reader.readS32(), + mood: this.reader.readU8(), + hatPartNameIndex: this.reader.readU8(), + hatNameIndex: this.reader.readU8(), + infogronNameIndex: this.reader.readU8(), + armlftNameIndex: this.reader.readU8(), + armrtNameIndex: this.reader.readU8(), + leglftNameIndex: this.reader.readU8(), + legrtNameIndex: this.reader.readU8() + }); + } + + this.parsed.characters = characters; } /** @@ -403,7 +424,7 @@ export class SaveGameParser { parse() { this.parseHeader(); this.parseVariables(); - this.skipCharacters(); + this.parseCharacters(); this.skipPlants(); this.skipBuildings(); this.parseGameStates(); diff --git a/src/core/formats/SaveGameSerializer.js b/src/core/formats/SaveGameSerializer.js index c39110e..d96f24b 100644 --- a/src/core/formats/SaveGameSerializer.js +++ b/src/core/formats/SaveGameSerializer.js @@ -4,7 +4,7 @@ */ import { SaveGameParser } from './SaveGameParser.js'; import { BinaryWriter } from './BinaryWriter.js'; -import { GameStateTypes, GameStateSizes, Actor, Act1TextureOrder } from '../savegame/constants.js'; +import { GameStateTypes, GameStateSizes, Actor, Act1TextureOrder, CharacterFieldOffsets, CHARACTER_RECORD_SIZE } from '../savegame/constants.js'; /** * Offsets for header fields @@ -461,6 +461,40 @@ export class SaveGameSerializer { return newBuffer; } + /** + * Update a character field in the save file + * @param {number} characterIndex - Character index (0-65) + * @param {string} field - Field name from CharacterFieldOffsets + * @param {number} value - New value + * @param {ArrayBuffer} [buffer] - Optional buffer to use + * @returns {ArrayBuffer|null} - Modified buffer or null on error + */ + updateCharacter(characterIndex, field, value, buffer = null) { + if (characterIndex < 0 || characterIndex > 65) { + console.error(`Invalid character index: ${characterIndex}`); + return null; + } + + const fieldOffset = CharacterFieldOffsets[field]; + if (fieldOffset === undefined) { + console.error(`Unknown character field: ${field}`); + return null; + } + + const workingBuffer = buffer || this.createCopy(); + const view = new DataView(workingBuffer); + + const offset = this.parsed.charactersOffset + (characterIndex * CHARACTER_RECORD_SIZE) + fieldOffset; + + if (field === 'sound' || field === 'move') { + view.setInt32(offset, value, true); + } else { + view.setUint8(offset, value); + } + + return workingBuffer; + } + /** * Get the byte offset for a mission score * @param {string} missionType diff --git a/src/core/formats/WdbParser.js b/src/core/formats/WdbParser.js index 3e08deb..c4b3e1c 100644 --- a/src/core/formats/WdbParser.js +++ b/src/core/formats/WdbParser.js @@ -23,14 +23,18 @@ export class WdbParser { } const globalTexturesSize = this.reader.readU32(); - // Skip global textures for now - BIGCUBE.GIF is in model_data - this.reader.skip(globalTexturesSize); + let globalTextures = []; + if (globalTexturesSize > 0) { + globalTextures = this.parseTextureInfo(); + } const globalPartsSize = this.reader.readU32(); - // Skip global parts - this.reader.skip(globalPartsSize); + let globalParts = null; + if (globalPartsSize > 0) { + globalParts = this.parseGlobalParts(globalPartsSize); + } - return { worlds, globalTexturesSize, globalPartsSize }; + return { worlds, globalTexturesSize, globalPartsSize, globalParts, globalTextures }; } parseWorldEntry() { @@ -125,6 +129,40 @@ export class WdbParser { return { parts, textures }; } + /** + * Parse global parts block (same structure as parsePartData but inline) + * @param {number} size - Size of global parts block + * @returns {{ parts: Array, textures: Array }} + */ + parseGlobalParts(size) { + const startOffset = this.reader.tell(); + const textureInfoOffset = this.reader.readU32(); + const numRois = this.reader.readU32(); + const parts = []; + + for (let i = 0; i < numRois; i++) { + const nameLen = this.reader.readU32(); + const name = this.readCleanString(nameLen); + const numLods = this.reader.readU32(); + const roiInfoOffset = this.reader.readU32(); + + const lods = []; + for (let j = 0; j < numLods; j++) { + lods.push(this.parseLod()); + } + + parts.push({ name, lods }); + } + + this.reader.seek(startOffset + textureInfoOffset); + const textures = this.parseTextureInfo(); + + // Ensure we've consumed the full block + this.reader.seek(startOffset + size); + + return { parts, textures }; + } + /** * Parse model_data blob at specified offset * @param {number} offset - Absolute file offset @@ -510,6 +548,21 @@ export function resolveLods(roi, partsMap) { return []; } +/** + * Build a parts lookup map from global parts + * @param {{ parts: Array }} globalParts - Parsed global parts from WdbParser + * @returns {Map} - Map of part name (lowercase) -> part data + */ +export function buildGlobalPartsMap(globalParts) { + const partsMap = new Map(); + if (!globalParts || !globalParts.parts) return partsMap; + + for (const part of globalParts.parts) { + partsMap.set(part.name.toLowerCase(), part); + } + return partsMap; +} + /** * Build a parts lookup map from a world's parts array * @param {WdbParser} parser - Parser instance for reading part data diff --git a/src/core/formats/index.js b/src/core/formats/index.js index a6d3ca4..aaddc97 100644 --- a/src/core/formats/index.js +++ b/src/core/formats/index.js @@ -7,7 +7,7 @@ export { BinaryReader } from './BinaryReader.js'; export { BinaryWriter } from './BinaryWriter.js'; // WDB format -export { WdbParser, findRoi } from './WdbParser.js'; +export { WdbParser, findRoi, buildGlobalPartsMap } from './WdbParser.js'; // Save game format export { SaveGameParser, parseSaveGame } from './SaveGameParser.js'; @@ -19,3 +19,6 @@ export { PlayersSerializer, createPlayersSerializer } from './PlayersSerializer. // Texture format export { parseTex } from './TexParser.js'; + +// Animation format +export { AnimationParser, parseAnimation } from './AnimationParser.js'; diff --git a/src/core/rendering/ActorRenderer.js b/src/core/rendering/ActorRenderer.js new file mode 100644 index 0000000..d96e890 --- /dev/null +++ b/src/core/rendering/ActorRenderer.js @@ -0,0 +1,881 @@ +import * as THREE from 'three'; +import { ActorLODs, ActorLODFlags, ActorInfoInit, partToLODIndex } from '../savegame/actorConstants.js'; +import { parseAnimation } from '../formats/AnimationParser.js'; + +// Extended LEGO colors (includes brown and lt grey not in the vehicle editor) +const ExtendedLegoColors = Object.freeze({ + 'lego black': { r: 0x21, g: 0x21, b: 0x21 }, + 'lego blue': { r: 0x00, g: 0x54, b: 0x8c }, + 'lego green': { r: 0x00, g: 0x78, b: 0x2d }, + 'lego red': { r: 0xcb, g: 0x12, b: 0x20 }, + 'lego white': { r: 0xfa, g: 0xfa, b: 0xfa }, + 'lego yellow': { r: 0xff, g: 0xb9, b: 0x00 }, + 'lego brown': { r: 0x4a, g: 0x23, b: 0x00 }, + 'lego lt grey': { r: 0xc0, g: 0xc0, b: 0xc0 } +}); + +/** + * Map actor index to animation suffix index (from g_characters[].m_unk0x16). + * This maps the 66 ActorInfoInit indices to the g_cycles row index. + */ +const ACTOR_SUFFIX_INDEX = (() => { + // Default all to 0 (xx) + const map = new Array(66).fill(0); + map[0] = 1; // pepper → Pe + map[1] = 2; // mama → Ma + map[2] = 3; // papa → Pa + map[3] = 4; // nick → Ni + map[4] = 5; // laura → La + map[5] = 0; // infoman → xx (not in g_characters, uses default) + map[6] = 6; // brickstr → Br + // 7-35: all 0 (xx) — generic NPCs + // Note: g_characters indices don't perfectly align with ActorInfoInit indices + // for the NPCs after brickstr. The g_characters array has 47 entries + // matching by name. We map the special ones: + map[37] = 9; // rd → Rd (g_characters index 36) + map[38] = 8; // pg → Pg (g_characters index 37) + map[39] = 7; // bd → Bd (g_characters index 38) + map[40] = 10; // sy → Sy (g_characters index 39) + map[56] = 1; // pep → Pe (same as pepper) + return map; +})(); + +/** + * Suffix names indexed by g_cycles row index. + */ +const SUFFIX_NAMES = ['xx', 'Pe', 'Ma', 'Pa', 'Ni', 'La', 'Br', 'Bd', 'Pg', 'Rd', 'Sy']; + +/** + * g_cycles[11][17] — animation name table from legoanimationmanager.cpp. + * Rows = character type suffix index, columns = sound + 4 * move (0-16). + */ +const G_CYCLES = [ + // 0: xx + ['CNs001xx','CNs002xx','CNs003xx','CNs004xx','CNs005xx','CNs007xx','CNs006xx','CNs008xx','CNs009xx','CNs010xx','CNs011xx','CNs012xx',null,null,null,null,null], + // 1: Pe + ['CNs001Pe','CNs002Pe','CNs003Pe','CNs004Pe','CNs005Pe','CNs007Pe','CNs006Pe','CNs008Pe','CNs009Pe','CNs010Pe','CNs001Sk',null,null,null,null,null,null], // CNs001Sk = skateboard + // 2: Ma + ['CNs001Ma','CNs002Ma','CNs003Ma','CNs004Ma','CNs005Ma','CNs007Ma','CNs006Ma','CNs008Ma','CNs009Ma','CNs010Ma','CNs0x4Ma',null,null,'CNs011Ma','CNs012Ma','CNs013Ma',null], + // 3: Pa + ['CNs001Pa','CNs002Pa','CNs003Pa','CNs004Pa','CNs005Pa','CNs007Pa','CNs006Pa','CNs008Pa','CNs009Pa','CNs010Pa','CNs0x4Pa',null,null,'CNs011Pa','CNs012Pa','CNs013Pa',null], + // 4: Ni + ['CNs001Ni','CNs002Ni','CNs003Ni','CNs004Ni','CNs005Ni','CNs007Ni','CNs006Ni','CNs008Ni','CNs009Ni','CNs010Ni','CNs011Ni','CNsx11Ni',null,null,null,null,null], + // 5: La + ['CNs001La','CNs002La','CNs003La','CNs004La','CNs005La','CNs007La','CNs006La','CNs008La','CNs009La','CNs010La','CNs011La','CNsx11La',null,null,null,null,null], + // 6: Br + ['CNs001Br','CNs002Br','CNs003Br','CNs004Br','CNs005Br','CNs007Br','CNs006Br','CNs008Br','CNs009Br','CNs010Br','CNs011Br','CNs900Br','CNs901BR','CNs011Br','CNs012Br','CNs013Br','CNs014Br'], + // 7: Bd + ['CNs001xx','CNs002xx','CNs003xx','CNs004xx','CNs005xx','CNs007xx','CNs006xx','CNs008xx','CNs009xx','CNs010xx','CNs001Bd','CNs012xx',null,null,null,null,null], + // 8: Pg + ['CNs001xx','CNs002xx','CNs003xx','CNs004xx','CNs005xx','CNs007xx','CNs006xx','CNs008xx','CNs009xx','CNs010xx','CNs001Pg','CNs012xx',null,null,null,null,null], + // 9: Rd + ['CNs001xx','CNs002xx','CNs003xx','CNs004xx','CNs005xx','CNs007xx','CNs006xx','CNs008xx','CNs009xx','CNs010xx','CNs001Rd','CNs012xx',null,null,null,null,null], + // 10: Sy + ['CNs001xx','CNs002xx','CNs003xx','CNs004xx','CNs005xx','CNs007xx','CNs006xx','CNs008xx','CNs009xx','CNs010xx','CNs001Sy','CNs012xx',null,null,null,null,null], +]; + +/** + * Map ActorLOD names (used in our part hierarchy) to animation node names. + * Animation files use uppercase names like "BODY", "HEAD", "LEG-RT", etc. + */ +const PART_NAME_TO_ANIM_NODE = { + 'body': 'BODY', + 'infohat': 'INFOHAT', + 'infogron': 'INFOGRON', + 'head': 'HEAD', + 'arm-lft': 'ARM-LFT', + 'arm-rt': 'ARM-RT', + 'claw-lft': 'CLAW-LFT', + 'claw-rt': 'CLAW-RT', + 'leg-lft': 'LEG-LFT', + 'leg-rt': 'LEG-RT' +}; + +/** + * Renderer for full LEGO characters assembled from WDB global parts. + * Mirrors the game's LegoCharacterManager::CreateActorROI logic. + */ +export class ActorRenderer { + constructor(canvas) { + this.canvas = canvas; + this.animating = false; + this.modelGroup = null; + this.partGroups = []; // 10 part groups for click targeting + this.textures = new Map(); + this.clock = new THREE.Clock(); + this.mixer = null; + this.currentAction = null; + this.animationCache = new Map(); // suffix → parsed animation data + + this.scene = new THREE.Scene(); + + this.camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100); + this.camera.position.set(2, 0.8, 3.5); + this.camera.lookAt(0, 0.2, 0); + + this.renderer = new THREE.WebGLRenderer({ + canvas, + antialias: true, + alpha: true + }); + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + this.renderer.setClearColor(0x000000, 0); + + this.raycaster = new THREE.Raycaster(); + + this.setupLighting(); + } + + setupLighting() { + const ambient = new THREE.AmbientLight(0xffffff, 0.8); + this.scene.add(ambient); + + const sunLight = new THREE.DirectionalLight(0xffffff, 0.6); + sunLight.position.set(1, 2, 3); + this.scene.add(sunLight); + } + + /** + * Create a Three.js texture from parsed texture data + */ + createTexture(textureData) { + const canvas = document.createElement('canvas'); + canvas.width = textureData.width; + canvas.height = textureData.height; + const ctx = canvas.getContext('2d'); + + const imageData = ctx.createImageData(textureData.width, textureData.height); + for (let i = 0; i < textureData.pixels.length; i++) { + const colorIdx = textureData.pixels[i]; + const color = textureData.palette[colorIdx] || { r: 0, g: 0, b: 0 }; + imageData.data[i * 4 + 0] = color.r; + imageData.data[i * 4 + 1] = color.g; + imageData.data[i * 4 + 2] = color.b; + imageData.data[i * 4 + 3] = 255; + } + ctx.putImageData(imageData, 0, 0); + + const texture = new THREE.CanvasTexture(canvas); + texture.minFilter = THREE.NearestFilter; + texture.magFilter = THREE.NearestFilter; + texture.wrapS = THREE.RepeatWrapping; + texture.wrapT = THREE.RepeatWrapping; + return texture; + } + + /** + * Load a full actor from global parts. + * @param {number} actorIndex - Index into ActorInfoInit (0-65) + * @param {Array} characters - Parsed character state from save file (66 entries) + * @param {Map} globalPartsMap - Name→part lookup for global parts + * @param {Array} globalTextures - Global texture list from WDB + */ + loadActor(actorIndex, characters, globalPartsMap, globalTextures) { + this.clearModel(); + + const actorInfo = ActorInfoInit[actorIndex]; + if (!actorInfo) return; + + const charState = characters ? characters[actorIndex] : null; + + // Build texture lookup + this.textures.clear(); + if (globalTextures) { + for (const tex of globalTextures) { + if (tex.name) { + this.textures.set(tex.name.toLowerCase(), this.createTexture(tex)); + } + } + } + + this.modelGroup = new THREE.Group(); + this.partGroups = []; + + // Assemble 10 body parts (matching CreateActorROI loop: i=0..9 maps to ActorLODs[1..10]) + for (let i = 0; i < 10; i++) { + const lodIdx = partToLODIndex[i]; + const actorLOD = ActorLODs[lodIdx]; + const part = actorInfo.parts[i]; + + // Resolve part name for body (i=0) and hat (i=1) + let partName; + if (i === 0 || i === 1) { + partName = this.resolvePartName(part, charState, i); + } else { + partName = actorLOD.parentName; + } + + if (!partName) continue; + + // Find the part's LOD data in global parts + const partData = globalPartsMap.get(partName.toLowerCase()); + if (!partData) continue; + + const partGroup = new THREE.Group(); + partGroup.userData.partIndex = i; + partGroup.userData.partName = partName; + partGroup.userData.lodName = actorLOD.name; // for animation matching + // Name used by Three.js PropertyBinding to match animation tracks + partGroup.name = `part_${actorLOD.name}`; + + // Resolve color/texture for this part + const resolvedName = this.resolveNameValue(part, charState, i); + + // Create meshes from LODs + const lods = partData.lods || []; + if (lods.length > 0) { + const lod = lods[lods.length - 1]; // Highest quality + this.createPartMeshes(lod, actorLOD, part, resolvedName, i, partGroup); + } + + // Position the part using ActorLOD transform + this.applyPartTransform(partGroup, actorLOD); + + this.modelGroup.add(partGroup); + this.partGroups[i] = partGroup; + } + + this.centerAndScaleModel(); + // Rotate 180° around Y so actor faces the camera (negating X for + // left-to-right-handed conversion flips the facing direction) + this.modelGroup.rotation.y = Math.PI; + this.scene.add(this.modelGroup); + + // Load and start animation based on move/sound + const move = charState?.move ?? 0; + const sound = charState?.sound ?? 0; + this.loadAnimationForActor(actorIndex, move, sound); + + this.renderer.render(this.scene, this.camera); + } + + /** + * Resolve which part geometry to use (body variant or hat type). + * For body (i=0): partNameIndices[charState.hatPartNameIndex or default] → bodyPartNames index + * For hat (i=1): partNameIndices[charState.hatPartNameIndex or default] → hatPartNames + * The save file stores the index into the partNameIndices array. + */ + resolvePartName(part, charState, partIdx) { + if (!part.partNameIndices || !part.partNames) return null; + + let nameIdx = part.partNameIndex; // default from actorInfoInit + + // For body (part 0): the partNameIndex selects from bodyPartNames + // For hat (part 1): the partNameIndex selects from hatPartNames + // The save file overrides hatPartNameIndex for part 1 only + if (partIdx === 1 && charState) { + nameIdx = charState.hatPartNameIndex; + } + + if (nameIdx >= part.partNameIndices.length) { + nameIdx = part.partNameIndex; + } + + const resolvedIdx = part.partNameIndices[nameIdx]; + if (resolvedIdx === undefined || resolvedIdx >= part.partNames.length) { + return part.partNames[part.partNameIndices[part.partNameIndex]]; + } + + return part.partNames[resolvedIdx]; + } + + /** + * Resolve the color or texture name for a part. + * Uses nameIndices[nameIndex] → names[] (colorAliases or faceTextures/chestTextures) + */ + resolveNameValue(part, charState, partIdx) { + if (!part.nameIndices || !part.names) return null; + + let nameIdx = part.nameIndex; // default + + // Save file overrides specific fields + if (charState) { + switch (partIdx) { + case 1: nameIdx = charState.hatNameIndex; break; // hat color + case 2: nameIdx = charState.infogronNameIndex; break; // infogron color + case 4: nameIdx = charState.armlftNameIndex; break; // arm left + case 5: nameIdx = charState.armrtNameIndex; break; // arm right + case 8: nameIdx = charState.leglftNameIndex; break; // leg left + case 9: nameIdx = charState.legrtNameIndex; break; // leg right + } + } + + if (nameIdx >= part.nameIndices.length) { + nameIdx = part.nameIndex; + } + + const resolvedIdx = part.nameIndices[nameIdx]; + if (resolvedIdx === undefined || resolvedIdx >= part.names.length) { + return part.names[part.nameIndices[part.nameIndex]]; + } + + return part.names[resolvedIdx]; + } + + /** + * Create meshes for a single body part. + */ + createPartMeshes(lod, actorLOD, part, resolvedName, partIdx, group) { + const useTexture = (actorLOD.flags & ActorLODFlags.USE_TEXTURE) !== 0; + const useColor = (actorLOD.flags & ActorLODFlags.USE_COLOR) !== 0; + + // Special case: body part (i=0) with partNameIndex 0 uses color instead of texture + // (matches the C++ condition: i != 0 || part.m_partNameIndices[part.m_partNameIndex] != 0) + const bodyUsesDefaultGeom = partIdx === 0 && part.partNameIndices && + part.partNameIndices[part.partNameIndex] === 0; + + let partColor = null; + let partTexture = null; + + if (useTexture && !bodyUsesDefaultGeom) { + // Look up texture by resolved name + const texName = resolvedName?.toLowerCase(); + if (texName && this.textures.has(texName)) { + partTexture = this.textures.get(texName); + } + } + + if ((useColor || bodyUsesDefaultGeom) && !partTexture) { + // Resolve LEGO color + const colorEntry = ExtendedLegoColors[resolvedName] || ExtendedLegoColors['lego white']; + partColor = new THREE.Color(colorEntry.r / 255, colorEntry.g / 255, colorEntry.b / 255); + } + + for (const mesh of lod.meshes) { + const geometry = this.createGeometry(mesh, lod); + if (!geometry) continue; + + // Check for per-mesh texture from the WDB geometry + let meshTexture = null; + const meshTexName = mesh.properties?.textureName?.toLowerCase(); + if (meshTexName && this.textures.has(meshTexName)) { + meshTexture = this.textures.get(meshTexName); + } + + let material; + if (partTexture) { + material = new THREE.MeshLambertMaterial({ + map: partTexture, + side: THREE.DoubleSide, + color: 0xffffff + }); + } else if (meshTexture) { + material = new THREE.MeshLambertMaterial({ + map: meshTexture, + side: THREE.DoubleSide, + color: 0xffffff + }); + } else if (partColor) { + material = new THREE.MeshLambertMaterial({ + color: partColor, + side: THREE.DoubleSide + }); + } else { + const meshColor = mesh.properties?.color || { r: 128, g: 128, b: 128 }; + material = new THREE.MeshLambertMaterial({ + color: new THREE.Color(meshColor.r / 255, meshColor.g / 255, meshColor.b / 255), + side: THREE.DoubleSide + }); + } + + const threeMesh = new THREE.Mesh(geometry, material); + group.add(threeMesh); + } + } + + /** + * Create geometry from mesh data (same approach as VehiclePartRenderer) + */ + createGeometry(mesh, lod) { + if (!mesh.polygonIndices || mesh.polygonIndices.length === 0) return null; + + const hasTexture = mesh.textureIndices && mesh.textureIndices.length > 0; + + const vertexIndicesPacked = []; + for (const poly of mesh.polygonIndices) { + vertexIndicesPacked.push(poly.a, poly.b, poly.c); + } + + const textureIndicesFlat = []; + if (hasTexture) { + for (const texPoly of mesh.textureIndices) { + textureIndicesFlat.push(texPoly.a, texPoly.b, texPoly.c); + } + } + + const meshVertices = []; + const meshNormals = []; + const meshUvs = []; + const indices = []; + + for (let i = 0; i < vertexIndicesPacked.length; i++) { + const packed = vertexIndicesPacked[i]; + + if ((packed & 0x80000000) !== 0) { + indices.push(meshVertices.length); + + const gv = packed & 0xFFFF; + const v = lod.vertices[gv] || { x: 0, y: 0, z: 0 }; + meshVertices.push([-v.x, v.y, v.z]); + + const gn = (packed >>> 16) & 0x7fff; + const n = lod.normals[gn] || { x: 0, y: 1, z: 0 }; + meshNormals.push([-n.x, n.y, n.z]); + + if (hasTexture && lod.textureVertices && lod.textureVertices.length > 0) { + const tex = textureIndicesFlat[i]; + const uv = lod.textureVertices[tex] || { u: 0, v: 0 }; + meshUvs.push([uv.u, 1 - uv.v]); + } + } else { + indices.push(packed & 0xFFFF); + } + } + + // Reverse face winding + for (let i = 0; i < indices.length; i += 3) { + const temp = indices[i]; + indices[i] = indices[i + 2]; + indices[i + 2] = temp; + } + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.Float32BufferAttribute(meshVertices.flat(), 3)); + geometry.setAttribute('normal', new THREE.Float32BufferAttribute(meshNormals.flat(), 3)); + geometry.setIndex(indices); + + if (hasTexture && meshUvs.length > 0) { + geometry.setAttribute('uv', new THREE.Float32BufferAttribute(meshUvs.flat(), 2)); + } + + return geometry; + } + + /** + * Apply position/direction/up transform from ActorLOD data. + * The game uses CalcLocalTransform with direction/up vectors. + */ + applyPartTransform(group, actorLOD) { + const pos = actorLOD.position; + const dir = actorLOD.direction; + const up = actorLOD.up; + + // Build right vector = cross(dir, up) + const right = new THREE.Vector3( + dir[1] * up[2] - dir[2] * up[1], + dir[2] * up[0] - dir[0] * up[2], + dir[0] * up[1] - dir[1] * up[0] + ); + + // Negate X for our coordinate system (matching VehiclePartRenderer's -v.x) + group.position.set(-pos[0], pos[1], pos[2]); + } + + /** + * Update a single part's color without full reload + */ + updatePartColor(partIndex, colorName) { + const partGroup = this.partGroups[partIndex]; + if (!partGroup) return; + + const colorEntry = ExtendedLegoColors[colorName] || ExtendedLegoColors['lego white']; + const threeColor = new THREE.Color(colorEntry.r / 255, colorEntry.g / 255, colorEntry.b / 255); + + partGroup.traverse((child) => { + if (child instanceof THREE.Mesh && child.material && !child.material.map) { + child.material.color = threeColor; + } + }); + + this.renderer.render(this.scene, this.camera); + } + + /** + * Get which body part was clicked + * @returns {number} Part index (0-9) or -1 if nothing hit + */ + getClickedPart(mouseEvent) { + if (!this.modelGroup) return -1; + + const rect = this.canvas.getBoundingClientRect(); + const mouse = new THREE.Vector2( + ((mouseEvent.clientX - rect.left) / rect.width) * 2 - 1, + -((mouseEvent.clientY - rect.top) / rect.height) * 2 + 1 + ); + + this.raycaster.setFromCamera(mouse, this.camera); + + for (let i = 0; i < this.partGroups.length; i++) { + const partGroup = this.partGroups[i]; + if (!partGroup) continue; + + const meshes = []; + partGroup.traverse((child) => { + if (child instanceof THREE.Mesh) meshes.push(child); + }); + + const intersects = this.raycaster.intersectObjects(meshes); + if (intersects.length > 0) return i; + } + + return -1; + } + + // ─── Animation System ──────────────────────────────────────────── + + /** + * Load and start animation for the given actor using g_cycles table. + * Animation index = sound + 4 * move. Pre-computes world-space transforms + * by evaluating the animation tree hierarchically, then plays via AnimationMixer. + * Falls back to Y-axis rotation if unavailable. + */ + async loadAnimationForActor(actorIndex, move = 0, sound = 0) { + if (!this.modelGroup) return; + + this.stopAnimation(); + + const suffixIdx = ACTOR_SUFFIX_INDEX[actorIndex] ?? 0; + const animIdx = sound + 4 * move; + const animName = G_CYCLES[suffixIdx]?.[animIdx]; + + if (!animName) return; // null entry in g_cycles — no animation for this combo + + try { + const animData = await this.fetchAnimationByName(animName); + if (!animData || !this.modelGroup) return; + + // Build animation node name → part group lookup + const nodeToPartGroup = new Map(); + for (let i = 0; i < this.partGroups.length; i++) { + const pg = this.partGroups[i]; + if (!pg) continue; + const lodName = pg.userData.lodName; + const animName = PART_NAME_TO_ANIM_NODE[lodName]; + if (animName) { + nodeToPartGroup.set(animName.toLowerCase(), pg); + } + } + + const tracks = this.buildHierarchicalTracks(animData, nodeToPartGroup); + if (tracks.length === 0) return; + + const clip = new THREE.AnimationClip('walk', -1, tracks); + this.mixer = new THREE.AnimationMixer(this.modelGroup); + this.currentAction = this.mixer.clipAction(clip); + this.currentAction.play(); + } catch (e) { + // Animation unavailable — fall back to rotation (handled in animate()) + } + } + + /** + * Fetch and parse an animation file by name (e.g. "CNs001xx"), with caching. + */ + async fetchAnimationByName(animName) { + if (this.animationCache.has(animName)) { + return this.animationCache.get(animName); + } + + const response = await fetch(`/animations/${animName}.ani`); + if (!response.ok) return null; + + const buffer = await response.arrayBuffer(); + const animData = parseAnimation(buffer); + this.animationCache.set(animName, animData); + return animData; + } + + /** + * Build world-space keyframe tracks by evaluating the animation tree + * hierarchically. At each unique keyframe time, walks the tree composing + * parent * child transforms via matrix multiplication, then decomposes + * to world-space position/quaternion for each part. + */ + buildHierarchicalTracks(animData, nodeToPartGroup) { + const duration = animData.duration; + + // Collect all unique keyframe times from the tree + const timesSet = new Set([0]); + this.collectKeyframeTimes(animData.rootNode, timesSet); + const times = [...timesSet].filter(t => t <= duration).sort((a, b) => a - b); + + // For each time, evaluate the full tree and store world-space transforms + const valueMap = new Map(); + const identity = new THREE.Matrix4(); + + for (const time of times) { + this.evaluateNode(animData.rootNode, time, identity, nodeToPartGroup, valueMap, true); + } + + // Convert to Three.js KeyframeTracks + const timesSec = times.map(t => t / 1000); + const tracks = []; + for (const [name, values] of valueMap) { + if (name.endsWith('.position')) { + tracks.push(new THREE.VectorKeyframeTrack(name, timesSec, values)); + } else if (name.endsWith('.quaternion')) { + tracks.push(new THREE.QuaternionKeyframeTrack(name, timesSec, values)); + } + } + return tracks; + } + + /** + * Recursively collect all unique keyframe times from the animation tree. + */ + collectKeyframeTimes(node, timesSet) { + const data = node.data; + for (const key of data.translationKeys) timesSet.add(key.time); + for (const key of data.rotationKeys) timesSet.add(key.time); + for (const key of data.scaleKeys) timesSet.add(key.time); + for (const child of node.children) { + this.collectKeyframeTimes(child, timesSet); + } + } + + /** + * Evaluate a single animation node at a given time, composing its local + * transform with the parent's world matrix. If the node maps to a part + * group, stores the decomposed world-space position and quaternion. + * Recurses into children with the composed matrix. + */ + evaluateNode(node, time, parentMatrix, nodeToPartGroup, valueMap, isRoot = false) { + const data = node.data; + let mat = new THREE.Matrix4(); + + // 1. Scale (applied first) + if (data.scaleKeys.length > 0) { + const scale = this.interpolateVertex(data.scaleKeys, time, false); + if (scale) { + mat.scale(scale); + } + if (data.rotationKeys.length > 0) { + mat = this.evaluateRotation(data.rotationKeys, time).multiply(mat); + } + } else if (data.rotationKeys.length > 0) { + mat = this.evaluateRotation(data.rotationKeys, time); + } + + // 2. Translation (skip on root node so the actor walks in place) + if (!isRoot && data.translationKeys.length > 0) { + const vertex = this.interpolateVertex(data.translationKeys, time, true); + if (vertex) { + mat.elements[12] += vertex.x; + mat.elements[13] += vertex.y; + mat.elements[14] += vertex.z; + } + } + + // 3. Compose with parent: world = parent * local + mat = parentMatrix.clone().multiply(mat); + + // 4. If this node maps to a part group, decompose and store + const nodeName = data.name?.toLowerCase(); + if (nodeName) { + const partGroup = nodeToPartGroup.get(nodeName); + if (partGroup) { + const position = new THREE.Vector3(); + const quaternion = new THREE.Quaternion(); + const scale = new THREE.Vector3(); + mat.decompose(position, quaternion, scale); + + if (Math.abs(scale.x) < 1e-8 || Math.abs(scale.y) < 1e-8 || Math.abs(scale.z) < 1e-8) { + quaternion.identity(); + } + + const trackName = partGroup.name; + this.pushValues(valueMap, `${trackName}.position`, position.toArray()); + this.pushValues(valueMap, `${trackName}.quaternion`, [quaternion.x, quaternion.y, quaternion.z, quaternion.w]); + } + } + + // 5. Recurse into children + for (const child of node.children) { + this.evaluateNode(child, time, mat, nodeToPartGroup, valueMap); + } + } + + /** + * Evaluate rotation keyframes at a given time. + * Handles slerp interpolation between keyframes with flag-based control. + * Coordinate conversion: game (w,x,y,z) → Three.js with X negated. + */ + evaluateRotation(keys, time) { + const { before, after } = this.getBeforeAndAfter(keys, time); + + const toQuat = (key) => new THREE.Quaternion(-key.x, key.y, key.z, key.w); + + if (!after) { + if (before.flags & 0x01) { + return new THREE.Matrix4().makeRotationFromQuaternion(toQuat(before)); + } + return new THREE.Matrix4(); + } + + if ((before.flags & 0x01) || (after.flags & 0x01)) { + const beforeQ = toQuat(before); + + // Flag 0x04: skip interpolation, use before value + if (after.flags & 0x04) { + return new THREE.Matrix4().makeRotationFromQuaternion(beforeQ); + } + + let afterQ = toQuat(after); + // Flag 0x02: negate the after quaternion before slerp + if (after.flags & 0x02) { + afterQ.set(-afterQ.x, -afterQ.y, -afterQ.z, -afterQ.w); + } + + const t = (time - before.time) / (after.time - before.time); + const result = new THREE.Quaternion().slerpQuaternions(beforeQ, afterQ, t); + return new THREE.Matrix4().makeRotationFromQuaternion(result); + } + + return new THREE.Matrix4(); + } + + /** + * Interpolate translation or scale keyframes at a given time. + * For translation: negates X for coordinate system conversion. + * For scale: no negation. + */ + interpolateVertex(keys, time, isTranslation) { + const { before, after } = this.getBeforeAndAfter(keys, time); + + const toVec = (key) => isTranslation + ? new THREE.Vector3(-key.x, key.y, key.z) + : new THREE.Vector3(key.x, key.y, key.z); + + if (!after) { + if (isTranslation && !(before.flags & 0x01)) { + // Check if vertex is non-zero (matching reference behavior) + if (Math.abs(before.x) < 1e-5 && Math.abs(before.y) < 1e-5 && Math.abs(before.z) < 1e-5) { + return null; + } + } + return toVec(before); + } + + if (isTranslation && !(before.flags & 0x01) && !(after.flags & 0x01)) { + // Both inactive — check if vertices are non-zero + const bNonZero = Math.abs(before.x) > 1e-5 || Math.abs(before.y) > 1e-5 || Math.abs(before.z) > 1e-5; + const aNonZero = Math.abs(after.x) > 1e-5 || Math.abs(after.y) > 1e-5 || Math.abs(after.z) > 1e-5; + if (!bNonZero && !aNonZero) return null; + } + + const t = (time - before.time) / (after.time - before.time); + return new THREE.Vector3().lerpVectors(toVec(before), toVec(after), t); + } + + /** + * Find the keyframes immediately before and after the given time. + */ + getBeforeAndAfter(keys, time) { + let idx = keys.findIndex(k => k.time > time); + if (idx < 0) idx = keys.length; + const before = keys[Math.max(0, idx - 1)]; + return { before, after: keys[idx] || null }; + } + + /** + * Append values to a named entry in the value map. + */ + pushValues(map, key, values) { + const existing = map.get(key); + if (!existing) { + map.set(key, [...values]); + } else { + existing.push(...values); + } + } + + stopAnimation() { + if (this.currentAction) { + this.currentAction.stop(); + this.currentAction = null; + } + if (this.mixer) { + this.mixer.stopAllAction(); + this.mixer = null; + } + } + + // ─── Scene Management ──────────────────────────────────────────── + + centerAndScaleModel() { + if (!this.modelGroup) return; + + const box = new THREE.Box3().setFromObject(this.modelGroup); + const center = box.getCenter(new THREE.Vector3()); + const size = box.getSize(new THREE.Vector3()); + + this.modelGroup.position.sub(center); + + const maxDim = Math.max(size.x, size.y, size.z); + if (maxDim > 0) { + const scale = 1.8 / maxDim; + this.modelGroup.scale.setScalar(scale); + } + } + + clearModel() { + this.stopAnimation(); + + if (this.modelGroup) { + this.modelGroup.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.geometry?.dispose(); + child.material?.dispose(); + } + }); + this.scene.remove(this.modelGroup); + this.modelGroup = null; + } + this.partGroups = []; + + for (const texture of this.textures.values()) { + texture.dispose(); + } + this.textures.clear(); + } + + start() { + this.animating = true; + this.clock.start(); + this.animate(); + } + + stop() { + this.animating = false; + } + + animate = () => { + if (!this.animating) return; + requestAnimationFrame(this.animate); + + const delta = this.clock.getDelta(); + + if (this.mixer) { + this.mixer.update(delta); + } else if (this.modelGroup) { + // Fallback: rotate if no animation loaded + this.modelGroup.rotation.y += 0.01; + } + + this.renderer.render(this.scene, this.camera); + } + + resize(width, height) { + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(width, height, false); + } + + dispose() { + this.animating = false; + this.stopAnimation(); + this.clearModel(); + this.renderer?.dispose(); + this.animationCache.clear(); + } +} diff --git a/src/core/savegame/actorConstants.js b/src/core/savegame/actorConstants.js new file mode 100644 index 0000000..008a1a3 --- /dev/null +++ b/src/core/savegame/actorConstants.js @@ -0,0 +1,669 @@ +/** + * Actor data constants ported from LEGO1 source: + * isle/LEGO1/lego/legoomni/src/common/legoactors.cpp + * isle/LEGO1/lego/legoomni/include/legoactors.h + */ + +// LegoActorLOD flags +export const ActorLODFlags = Object.freeze({ + USE_TEXTURE: 0x01, + USE_COLOR: 0x02 +}); + +// LegoActorLODs enum — indices into ActorLODs[] +export const ActorLODIndex = Object.freeze({ + TOP: 0, + BODY: 1, + INFOHAT: 2, + INFOGRON: 3, + HEAD: 4, + ARMLFT: 5, + ARMRT: 6, + CLAWLFT: 7, + CLAWRT: 8, + LEGLFT: 9, + LEGRT: 10 +}); + +// LegoActorParts enum — indices into the 10-part array on each actor +export const ActorPart = Object.freeze({ + BODY: 0, + INFOHAT: 1, + INFOGRON: 2, + HEAD: 3, + ARMLFT: 4, + ARMRT: 5, + CLAWLFT: 6, + CLAWRT: 7, + LEGLFT: 8, + LEGRT: 9 +}); + +// Mapping from ActorPart index to the ActorLOD that provides its transform. +// ActorLODs[0] ("top") is the root and not directly a part. +// Parts 0..9 map to ActorLODs[1..10] respectively. +export const partToLODIndex = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + +/** + * g_actorLODs[11] — transform/bounding data for each body part position. + * Fields: name, parentName, flags, boundingSphere[4], boundingBox[6], + * position[3], direction[3], up[3] + */ +export const ActorLODs = Object.freeze([ + { name: 'top', parentName: 'top', flags: 0, + boundingSphere: [0.000267, 0.780808, -0.01906, 0.951612], + boundingBox: [-0.461166, -0.002794, -0.299442, 0.4617, 1.56441, 0.261321], + position: [0, 0, 0], direction: [0, 0, 1], up: [0, 1, 0] }, + { name: 'body', parentName: 'body', flags: ActorLODFlags.USE_TEXTURE, + boundingSphere: [0.00158332, 0.401828, -0.00048697, 0.408071], + boundingBox: [-0.287507, 0.150419, -0.147452, 0.289219, 0.649774, 0.14258], + position: [-0.00089, 0.436353, 0.007277], direction: [0, 0, 1], up: [0, 1, 0] }, + { name: 'infohat', parentName: 'infohat', flags: ActorLODFlags.USE_COLOR, + boundingSphere: [0, -0.00938, -0.01955, 0.35], + boundingBox: [-0.231822, -0.140237, -0.320954, 0.234149, 0.076968, 0.249083], + position: [0.000191, 1.519793, 0.001767], direction: [0, 0, 1], up: [0, 1, 0] }, + { name: 'infogron', parentName: 'infogron', flags: ActorLODFlags.USE_COLOR, + boundingSphere: [0, 0.11477, 0.00042, 0.26], + boundingBox: [-0.285558, -0.134391, -0.142231, 0.285507, 0.152986, 0.143071], + position: [-0.00089, 0.436353, 0.007277], direction: [0, 0, 1], up: [0, 1, 0] }, + { name: 'head', parentName: 'head', flags: ActorLODFlags.USE_TEXTURE, + boundingSphere: [0, -0.03006, 0, 0.3], + boundingBox: [-0.189506, -0.209665, -0.189824, 0.189532, 0.228822, 0.194945], + position: [-0.00105, 1.293115, 0.001781], direction: [0, 0, 1], up: [0, 1, 0] }, + { name: 'arm-lft', parentName: 'arm-lft', flags: ActorLODFlags.USE_COLOR, + boundingSphere: [-0.06815, -0.0973747, 0.0154655, 0.237], + boundingBox: [-0.137931, -0.282775, -0.105316, 0.000989, 0.100221, 0.140759], + position: [-0.225678, 0.963312, 0.023286], + direction: [-0.003031, -0.017187, 0.999848], up: [0.173622, 0.984658, 0.017453] }, + { name: 'arm-rt', parentName: 'arm-rt', flags: ActorLODFlags.USE_COLOR, + boundingSphere: [0.0680946, -0.097152, 0.0152722, 0.237], + boundingBox: [0.00141, -0.289604, -0.100831, 0.138786, 0.09291, 0.145437], + position: [0.223494, 0.963583, 0.018302], + direction: [0, 0, 1], up: [-0.173648, 0.984808, 0] }, + { name: 'claw-lft', parentName: 'claw-lft', flags: ActorLODFlags.USE_COLOR, + boundingSphere: [0.000773381, -0.101422, -0.0237761, 0.15], + boundingBox: [-0.089838, -0.246208, -0.117735, 0.091275, 0.000263, 0.07215], + position: [-0.341869, 0.700355, 0.092779], + direction: [0.000001, 0.000003, 1], up: [0.190812, 0.981627, -0.000003] }, + { name: 'claw-rt', parentName: 'claw-lft', flags: ActorLODFlags.USE_COLOR, + boundingSphere: [0.000773381, -0.101422, -0.0237761, 0.15], + boundingBox: [-0.095016, -0.245349, -0.117979, 0.086528, 0.00067, 0.069743], + position: [0.343317, 0.69924, 0.096123], + direction: [0.00606, -0.034369, 0.999391], up: [-0.190704, 0.981027, 0.034894] }, + { name: 'leg-lft', parentName: 'leg', flags: ActorLODFlags.USE_COLOR, + boundingSphere: [0.00433584, -0.177404, -0.0313928, 0.33], + boundingBox: [-0.129782, -0.440428, -0.184207, 0.13817, 0.118415, 0.122607], + position: [-0.156339, 0.436087, 0.006822], direction: [0, 0, 1], up: [0, 1, 0] }, + { name: 'leg-rt', parentName: 'leg', flags: ActorLODFlags.USE_COLOR, + boundingSphere: [0.00433584, -0.177404, -0.0313928, 0.33], + boundingBox: [-0.132864, -0.437138, -0.183944, 0.134614, 0.12043, 0.121888], + position: [0.151154, 0.436296, 0.007373], direction: [0, 0, 1], up: [0, 1, 0] } +]); + +// Index arrays — 0xff marks end-of-list sentinel +export const hatPartIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; +export const pepperHatPartIndices = [21, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; +export const infomanHatPartIndices = [22]; +export const ghostHatPartIndices = [20]; +export const bodyPartIndices = [0, 1, 2, 3, 4, 5, 6, 7]; +export const hatColorIndices = [0, 1, 2, 3, 4, 5, 6, 7]; +export const faceTextureIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; +export const chestTextureIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27]; +export const armColorIndices = [0, 1, 2, 3, 4, 5, 6, 7]; +export const clawRightColorIndices = [0, 1, 2, 3, 4, 5, 6, 7]; +export const clawLeftColorIndices = [0, 1, 2, 3, 4, 5, 6, 7]; +export const gronColorIndices = [0, 1, 2, 3, 4, 5, 6, 7]; +export const legColorIndices = [0, 1, 2, 3, 4, 5, 6, 7]; + +// Name arrays +export const hatPartNames = [ + 'baseball', 'chef', 'cap', 'cophat', 'helmet', 'ponytail', 'pageboy', 'shrthair', + 'bald', 'flower', 'cboyhat', 'cuphat', 'cathat', 'backbcap', 'pizhat', 'caprc', + 'capch', 'capdb', 'capjs', 'capmd', 'sheet', 'phat', 'icap' +]; + +export const bodyPartNames = [ + 'body', 'bodyred', 'bodyblck', 'bodywhte', 'bodyyllw', 'bodyblue', 'bodygren', 'bodybrwn' +]; + +export const chestTextures = [ + 'peprchst.gif', 'mamachst.gif', 'papachst.gif', 'nickchst.gif', 'norachst.gif', + 'infochst.gif', 'shftchst.gif', 'rac1chst.gif', 'rac2chst.gif', 'bth1chst.gif', + 'bth2chst.gif', 'mech.gif', 'polkadot.gif', 'bowtie.gif', 'postchst.gif', + 'vest.gif', 'doctor.gif', 'copchest.gif', 'l.gif', 'e.gif', + 'g.gif', 'o.gif', 'fruit.gif', 'flowers.gif', 'construct.gif', + 'paint.gif', 'l6.gif', 'unkchst.gif' +]; + +export const faceTextures = [ + 'peprface.gif', 'mamaface.gif', 'papaface.gif', 'nickface.gif', 'noraface.gif', + 'infoface.gif', 'shftface.gif', 'dogface.gif', 'womanshd.gif', 'smileshd.gif', + 'woman.gif', 'smile.gif', 'mustache.gif', 'black.gif' +]; + +export const colorAliases = [ + 'lego white', 'lego black', 'lego yellow', 'lego red', 'lego blue', 'lego brown', 'lego lt grey', 'lego green' +]; + +// Character type classification +export const CharacterType = Object.freeze({ + STANDARD: 0, + PEPPER: 1, + INFOMAN: 2, + GHOST: 3 +}); + +/** + * Determine character type from actor index + */ +export function getCharacterType(actorIndex) { + if (actorIndex === 0 || actorIndex === 56) return CharacterType.PEPPER; // pepper, pep + if (actorIndex === 5) return CharacterType.INFOMAN; + if (actorIndex >= 48 && actorIndex <= 53) return CharacterType.GHOST; // ghost, ghost01..05 + return CharacterType.STANDARD; +} + +// Reference names for the index arrays (used to build part configs) +const HP = 'hatPartIndices'; +const PHP = 'pepperHatPartIndices'; +const IHP = 'infomanHatPartIndices'; +const GHP = 'ghostHatPartIndices'; +const BP = 'bodyPartIndices'; +const HC = 'hatColorIndices'; +const FT = 'faceTextureIndices'; +const CT = 'chestTextureIndices'; +const AC = 'armColorIndices'; +const CRC = 'clawRightColorIndices'; +const CLC = 'clawLeftColorIndices'; +const GC = 'gronColorIndices'; +const LC = 'legColorIndices'; + +// Lookup tables for the index array references +const indexArrays = { + [HP]: hatPartIndices, + [PHP]: pepperHatPartIndices, + [IHP]: infomanHatPartIndices, + [GHP]: ghostHatPartIndices, + [BP]: bodyPartIndices, + [HC]: hatColorIndices, + [FT]: faceTextureIndices, + [CT]: chestTextureIndices, + [AC]: armColorIndices, + [CRC]: clawRightColorIndices, + [CLC]: clawLeftColorIndices, + [GC]: gronColorIndices, + [LC]: legColorIndices +}; + +const nameArrays = { + hatPartNames, + bodyPartNames, + chestTextures, + faceTextures, + colorAliases +}; + +/** + * Helper to build a Part entry from compact form. + * C++ Part has: { partNameIndices, partName, partNameIndex, nameIndices, names, nameIndex } + * We store references to our JS arrays. + */ +function P(partNameIndicesRef, partNamesRef, partNameIndex, nameIndicesRef, namesRef, nameIndex) { + return { + partNameIndices: partNameIndicesRef ? indexArrays[partNameIndicesRef] : null, + partNames: partNamesRef ? nameArrays[partNamesRef] : null, + partNameIndex, + nameIndices: indexArrays[nameIndicesRef], + names: nameArrays[namesRef], + nameIndex + }; +} + +// Short aliases for name array refs +const HPN = 'hatPartNames'; +const BPN = 'bodyPartNames'; +const CTN = 'chestTextures'; +const FTN = 'faceTextures'; +const CA = 'colorAliases'; + +/** + * g_actorInfoInit[66] — All 66 game actors. + * Each entry: { name, sound, move, mood, parts[10] } + * Parts order: body, infohat, infogron, head, armlft, armrt, clawlft, clawrt, leglft, legrt + */ +export const ActorInfoInit = Object.freeze([ + /* 0 */ { name: 'pepper', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 0), P(PHP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 0), + P(null, null, 0, AC, CA, 3), P(null, null, 0, AC, CA, 3), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 1 */ { name: 'mama', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 1), P(HP, HPN, 1, HC, CA, 0), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 1), + P(null, null, 0, AC, CA, 3), P(null, null, 0, AC, CA, 3), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 2 */ { name: 'papa', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 2, CT, CTN, 2), P(HP, HPN, 1, HC, CA, 0), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 2), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 3 */ { name: 'nick', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 2, CT, CTN, 3), P(HP, HPN, 3, HC, CA, 1), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 3), + P(null, null, 0, AC, CA, 1), P(null, null, 0, AC, CA, 1), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 4 */ { name: 'laura', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 2, CT, CTN, 4), P(HP, HPN, 3, HC, CA, 1), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 4), + P(null, null, 0, AC, CA, 1), P(null, null, 0, AC, CA, 1), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 5 */ { name: 'infoman', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 5), P(IHP, HPN, 0, HC, CA, 3), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 5), + P(null, null, 0, AC, CA, 1), P(null, null, 0, AC, CA, 1), + P(null, null, 0, CLC, CA, 0), P(null, null, 0, CRC, CA, 0), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 6 */ { name: 'brickstr', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 6), P(HP, HPN, 13, HC, CA, 1), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 6), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 0), P(null, null, 0, CRC, CA, 4), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 7 */ { name: 'studs', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 7), P(HP, HPN, 4, HC, CA, 1), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 7), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 8 */ { name: 'rhoda', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 5, CT, CTN, 8), P(HP, HPN, 4, HC, CA, 3), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 8), + P(null, null, 0, AC, CA, 3), P(null, null, 0, AC, CA, 3), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 9 */ { name: 'valerie', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 9), P(HP, HPN, 5, HC, CA, 3), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 8), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 2), P(null, null, 0, LC, CA, 2)] }, + /* 10 */ { name: 'snap', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 10), P(HP, HPN, 0, HC, CA, 4), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 9), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 2), P(null, null, 0, LC, CA, 2)] }, + /* 11 */ { name: 'pt', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 5, CT, CTN, 11), P(HP, HPN, 6, HC, CA, 1), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 8), + P(null, null, 0, AC, CA, 4), P(null, null, 0, AC, CA, 4), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 12 */ { name: 'mg', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 12), P(HP, HPN, 6, HC, CA, 5), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 10), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 13 */ { name: 'bu', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 13), P(HP, HPN, 7, HC, CA, 5), + P(null, null, 0, GC, CA, 5), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 5), P(null, null, 0, LC, CA, 5)] }, + /* 14 */ { name: 'ml', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 5, CT, CTN, 14), P(HP, HPN, 2, HC, CA, 4), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 12), + P(null, null, 0, AC, CA, 4), P(null, null, 0, AC, CA, 4), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 15 */ { name: 'nu', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 5, CT, CTN, 11), P(HP, HPN, 7, HC, CA, 1), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 7), + P(null, null, 0, AC, CA, 4), P(null, null, 0, AC, CA, 4), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 16 */ { name: 'na', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 15), P(HP, HPN, 10, HC, CA, 3), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 8), + P(null, null, 0, AC, CA, 4), P(null, null, 0, AC, CA, 4), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 3), P(null, null, 0, LC, CA, 3)] }, + /* 17 */ { name: 'cl', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 16), P(HP, HPN, 19, HC, CA, 0), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 12), + P(null, null, 0, AC, CA, 4), P(null, null, 0, AC, CA, 4), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 18 */ { name: 'en', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 16), P(HP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 19 */ { name: 're', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 16), P(HP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 20 */ { name: 'ro', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 2, CT, CTN, 17), P(HP, HPN, 3, HC, CA, 1), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 9), + P(null, null, 0, AC, CA, 1), P(null, null, 0, AC, CA, 1), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 21 */ { name: 'd1', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 5, CT, CTN, 11), P(HP, HPN, 15, HC, CA, 0), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 22 */ { name: 'd2', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 5, CT, CTN, 11), P(HP, HPN, 16, HC, CA, 0), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 23 */ { name: 'd3', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 5, CT, CTN, 11), P(HP, HPN, 17, HC, CA, 0), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 24 */ { name: 'd4', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 5, CT, CTN, 11), P(HP, HPN, 18, HC, CA, 0), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 25 */ { name: 'l1', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 18), P(HP, HPN, 5, HC, CA, 1), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 26 */ { name: 'l2', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 19), P(HP, HPN, 6, HC, CA, 1), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 12), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 27 */ { name: 'l3', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 20), P(HP, HPN, 7, HC, CA, 1), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 28 */ { name: 'l4', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 21), P(HP, HPN, 7, HC, CA, 1), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 29 */ { name: 'l5', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 26), P(HP, HPN, 7, HC, CA, 1), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 12), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 30 */ { name: 'l6', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 26), P(HP, HPN, 0, HC, CA, 1), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 31 */ { name: 'b1', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 1), P(HP, HPN, 7, HC, CA, 1), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 12), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 32 */ { name: 'b2', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 1), P(HP, HPN, 5, HC, CA, 1), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 10), + P(null, null, 0, AC, CA, 3), P(null, null, 0, AC, CA, 3), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 33 */ { name: 'b3', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 4), P(HP, HPN, 7, HC, CA, 5), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 34 */ { name: 'b4', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 1), P(HP, HPN, 7, HC, CA, 1), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 9), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 35 */ { name: 'cm', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 4, CT, CTN, 22), P(HP, HPN, 9, HC, CA, 2), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 8), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 3), P(null, null, 0, LC, CA, 3)] }, + /* 36 */ { name: 'gd', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 1), P(HP, HPN, 7, HC, CA, 1), + P(null, null, 0, GC, CA, 6), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 6), P(null, null, 0, AC, CA, 6), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 6), P(null, null, 0, LC, CA, 6)] }, + /* 37 */ { name: 'rd', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 3), P(HP, HPN, 0, HC, CA, 3), + P(null, null, 0, GC, CA, 7), P(null, null, 0, FT, FTN, 9), + P(null, null, 0, AC, CA, 3), P(null, null, 0, AC, CA, 3), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 7), P(null, null, 0, LC, CA, 7)] }, + /* 38 */ { name: 'pg', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 3), P(HP, HPN, 5, HC, CA, 3), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 3), P(null, null, 0, LC, CA, 3)] }, + /* 39 */ { name: 'bd', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 6), P(HP, HPN, 0, HC, CA, 6), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 12), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 40 */ { name: 'sy', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 4), P(HP, HPN, 5, HC, CA, 6), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 10), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 41 */ { name: 'gn', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 6, CT, CTN, 13), P(HP, HPN, 7, HC, CA, 5), + P(null, null, 0, GC, CA, 5), P(null, null, 0, FT, FTN, 9), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 5), P(null, null, 0, LC, CA, 5)] }, + /* 42 */ { name: 'df', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 5, CT, CTN, 23), P(HP, HPN, 6, HC, CA, 5), + P(null, null, 0, GC, CA, 6), P(null, null, 0, FT, FTN, 8), + P(null, null, 0, AC, CA, 4), P(null, null, 0, AC, CA, 4), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 6), P(null, null, 0, LC, CA, 6)] }, + /* 43 */ { name: 'bs', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 10), P(HP, HPN, 7, HC, CA, 3), + P(null, null, 0, GC, CA, 7), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 2), P(null, null, 0, LC, CA, 2)] }, + /* 44 */ { name: 'lt', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 10), P(HP, HPN, 7, HC, CA, 1), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 2), P(null, null, 0, LC, CA, 2)] }, + /* 45 */ { name: 'st', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 9), P(HP, HPN, 5, HC, CA, 5), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 10), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 2), P(null, null, 0, LC, CA, 2)] }, + /* 46 */ { name: 'bm', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 24), P(HP, HPN, 0, HC, CA, 4), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 7), + P(null, null, 0, AC, CA, 6), P(null, null, 0, AC, CA, 6), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 47 */ { name: 'jk', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 24), P(HP, HPN, 0, HC, CA, 4), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 9), + P(null, null, 0, AC, CA, 6), P(null, null, 0, AC, CA, 6), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 48 */ { name: 'ghost', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 0), P(GHP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 13), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 0), P(null, null, 0, CRC, CA, 0), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 49 */ { name: 'ghost01', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 0), P(GHP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 13), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 0), P(null, null, 0, CRC, CA, 0), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 50 */ { name: 'ghost02', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 0), P(GHP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 13), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 0), P(null, null, 0, CRC, CA, 0), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 51 */ { name: 'ghost03', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 0), P(GHP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 13), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 0), P(null, null, 0, CRC, CA, 0), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 52 */ { name: 'ghost04', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 0), P(GHP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 13), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 0), P(null, null, 0, CRC, CA, 0), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 53 */ { name: 'ghost05', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 0), P(GHP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 0), P(null, null, 0, FT, FTN, 13), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 0), P(null, null, 0, CRC, CA, 0), + P(null, null, 0, LC, CA, 0), P(null, null, 0, LC, CA, 0)] }, + /* 54 */ { name: 'hg', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 3), P(HP, HPN, 8, HC, CA, 3), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 8), + P(null, null, 0, AC, CA, 3), P(null, null, 0, AC, CA, 3), + P(null, null, 0, CLC, CA, 3), P(null, null, 0, CRC, CA, 3), + P(null, null, 0, LC, CA, 3), P(null, null, 0, LC, CA, 3)] }, + /* 55 */ { name: 'pntgy', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 3), P(HP, HPN, 0, HC, CA, 3), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 7), + P(null, null, 0, AC, CA, 3), P(null, null, 0, AC, CA, 3), + P(null, null, 0, CLC, CA, 3), P(null, null, 0, CRC, CA, 3), + P(null, null, 0, LC, CA, 3), P(null, null, 0, LC, CA, 3)] }, + /* 56 */ { name: 'pep', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 0), P(PHP, HPN, 0, HC, CA, 0), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 0), + P(null, null, 0, AC, CA, 3), P(null, null, 0, AC, CA, 3), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 57 */ { name: 'cop01', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 2, CT, CTN, 17), P(HP, HPN, 3, HC, CA, 1), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 9), + P(null, null, 0, AC, CA, 1), P(null, null, 0, AC, CA, 1), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 58 */ { name: 'actor_01', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 4), P(HP, HPN, 5, HC, CA, 6), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 10), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 59 */ { name: 'actor_02', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 6), P(HP, HPN, 0, HC, CA, 6), + P(null, null, 0, GC, CA, 1), P(null, null, 0, FT, FTN, 12), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 1), P(null, null, 0, LC, CA, 1)] }, + /* 60 */ { name: 'actor_03', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 1), P(HP, HPN, 7, HC, CA, 1), + P(null, null, 0, GC, CA, 6), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 6), P(null, null, 0, AC, CA, 6), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 6), P(null, null, 0, LC, CA, 6)] }, + /* 61 */ { name: 'actor_04', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 1, CT, CTN, 12), P(HP, HPN, 6, HC, CA, 5), + P(null, null, 0, GC, CA, 4), P(null, null, 0, FT, FTN, 10), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 4), P(null, null, 0, LC, CA, 4)] }, + /* 62 */ { name: 'actor_05', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 4, CT, CTN, 22), P(HP, HPN, 9, HC, CA, 2), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 8), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 3), P(null, null, 0, LC, CA, 3)] }, + /* 63 */ { name: 'btmncycl', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 3), P(HP, HPN, 5, HC, CA, 3), + P(null, null, 0, GC, CA, 3), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 0), P(null, null, 0, AC, CA, 0), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 3), P(null, null, 0, LC, CA, 3)] }, + /* 64 */ { name: 'cboycycl', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 3, CT, CTN, 10), P(HP, HPN, 7, HC, CA, 3), + P(null, null, 0, GC, CA, 7), P(null, null, 0, FT, FTN, 11), + P(null, null, 0, AC, CA, 2), P(null, null, 0, AC, CA, 2), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 2), P(null, null, 0, LC, CA, 2)] }, + /* 65 */ { name: 'boatman', sound: 0, move: 0, mood: 0, parts: [ + P(BP, BPN, 0, LC, CA, 3), P(HP, HPN, 0, HC, CA, 3), + P(null, null, 0, GC, CA, 7), P(null, null, 0, FT, FTN, 9), + P(null, null, 0, AC, CA, 3), P(null, null, 0, AC, CA, 3), + P(null, null, 0, CLC, CA, 2), P(null, null, 0, CRC, CA, 2), + P(null, null, 0, LC, CA, 7), P(null, null, 0, LC, CA, 7)] } +]); + +/** + * Save file field offsets within the 16-byte character record. + * The save file stores these per-character values that override defaults: + * sound(S32) + move(S32) + mood(U8) + hatPartNameIndex(U8) + hatNameIndex(U8) + * + infogronNameIndex(U8) + armlftNameIndex(U8) + armrtNameIndex(U8) + * + leglftNameIndex(U8) + legrtNameIndex(U8) + */ +export const CharacterFieldOffsets = Object.freeze({ + sound: 0, // S32 + move: 4, // S32 + mood: 8, // U8 + hatPartNameIndex: 9, // U8 + hatNameIndex: 10, // U8 + infogronNameIndex: 11, // U8 + armlftNameIndex: 12, // U8 + armrtNameIndex: 13, // U8 + leglftNameIndex: 14, // U8 + legrtNameIndex: 15 // U8 +}); + +export const CHARACTER_RECORD_SIZE = 16; + +/** + * Part labels for display + */ +export const ActorPartLabels = Object.freeze({ + 0: 'Body', + 1: 'Hat', + 2: 'Torso Emblem', + 3: 'Head', + 4: 'Left Arm', + 5: 'Right Arm', + 6: 'Left Claw', + 7: 'Right Claw', + 8: 'Left Leg', + 9: 'Right Leg' +}); diff --git a/src/core/savegame/constants.js b/src/core/savegame/constants.js index eb6c8cf..7d82085 100644 --- a/src/core/savegame/constants.js +++ b/src/core/savegame/constants.js @@ -2,6 +2,12 @@ * Constants and enums from KSY save file specifications */ +// Re-export actor constants +export { ActorInfoInit, ActorLODs, ActorPart, ActorLODFlags, ActorPartLabels, + CharacterType, getCharacterType, CharacterFieldOffsets, CHARACTER_RECORD_SIZE, + hatPartNames, bodyPartNames, chestTextures, faceTextures, colorAliases +} from './actorConstants.js'; + // Save game file version (must match for valid saves) export const SAVEGAME_VERSION = 0x1000c; // 65548 diff --git a/src/core/savegame/index.js b/src/core/savegame/index.js index 14e2e7c..9619ac3 100644 --- a/src/core/savegame/index.js +++ b/src/core/savegame/index.js @@ -91,6 +91,8 @@ export async function listSaveSlots() { missions: null, variables: null, act1State: null, + characters: null, + charactersOffset: null, playerName: null, buffer: null }; @@ -104,6 +106,8 @@ export async function listSaveSlots() { slot.missions = parsed.missions; slot.variables = parsed.variables; slot.act1State = parsed.act1State || null; + slot.characters = parsed.characters || null; + slot.charactersOffset = parsed.charactersOffset || null; slot.buffer = buffer; // Try to get player name @@ -168,6 +172,8 @@ export async function loadSaveSlot(slotNumber) { missions: parsed.missions, variables: parsed.variables, act1State: parsed.act1State || null, + characters: parsed.characters || null, + charactersOffset: parsed.charactersOffset || null, playerName, buffer }; @@ -238,6 +244,17 @@ export async function updateSaveSlot(slotNumber, updates) { } } + // Apply character update + if (updates.character) { + const { characterIndex, field, value } = updates.character; + const charSerializer = createSerializer(newBuffer); + const result = charSerializer.updateCharacter(characterIndex, field, value); + if (result) { + newBuffer = result; + modified = true; + } + } + // Apply texture update if (updates.texture) { const { textureName, textureData } = updates.texture; diff --git a/src/lib/SaveEditorPage.svelte b/src/lib/SaveEditorPage.svelte index 8970c86..2d6b206 100644 --- a/src/lib/SaveEditorPage.svelte +++ b/src/lib/SaveEditorPage.svelte @@ -6,6 +6,7 @@ import SkyColorEditor from './save-editor/SkyColorEditor.svelte'; import LightPositionEditor from './save-editor/LightPositionEditor.svelte'; import VehicleEditor from './save-editor/VehicleEditor.svelte'; + import ActorEditor from './save-editor/ActorEditor.svelte'; import { saveEditorState, currentPage } from '../stores.js'; import { listSaveSlots, updateSaveSlot, updatePlayerName } from '../core/savegame/index.js'; import { Actor, ActorNames } from '../core/savegame/constants.js'; @@ -23,7 +24,8 @@ { id: 'player', label: 'Player', firstSection: 'name' }, { id: 'scores', label: 'Scores', firstSection: null }, { id: 'island', label: 'Island', firstSection: 'skycolor' }, - { id: 'vehicles', label: 'Vehicles', firstSection: null } + { id: 'vehicles', label: 'Vehicles', firstSection: null }, + { id: 'actors', label: 'Actors', firstSection: null } ]; // Reset state when navigating to this page @@ -120,7 +122,7 @@ if (updated) { slots = slots.map(s => s.slotNumber === selectedSlot - ? { ...s, variables: updated.variables, act1State: updated.act1State } + ? { ...s, variables: updated.variables, act1State: updated.act1State, characters: updated.characters } : s ); } @@ -405,6 +407,13 @@ {/if} + + +
+ {#if $currentPage === 'save-editor'} + + {/if} +
{/if} diff --git a/src/lib/save-editor/ActorEditor.svelte b/src/lib/save-editor/ActorEditor.svelte new file mode 100644 index 0000000..76b8449 --- /dev/null +++ b/src/lib/save-editor/ActorEditor.svelte @@ -0,0 +1,299 @@ + + + +
+ + + {#if loading} +
+
+
+ {:else if error} +
{error}
+ {/if} +
+ +
+
+ +
+ {actorIndex + 1} / {ActorInfoInit.length} + {actorName} +
+ +
+
+
+ +