From 7f157da4350af0d501e6bd67537cad0241932e20 Mon Sep 17 00:00:00 2001 From: Yuancheng Zhang Date: Thu, 10 Dec 2020 13:24:00 +0800 Subject: [PATCH] Update avatar-ava.smap --- Smap/avatar-ava.smap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Smap/avatar-ava.smap b/Smap/avatar-ava.smap index cb4afeb..9c6cc74 100644 --- a/Smap/avatar-ava.smap +++ b/Smap/avatar-ava.smap @@ -1 +1 @@ -{"sandbox_archive_version":61,"resources":[{"default":[{"guid":[953679908,159793237,2165885860,1104393525],"revisions":[{"type":"kTexture","name":"Dot","revision":-1,"autoGenerated":false,"rawdata":"0WSam051Rl009610096102BZK0Dc7k0EtjeM-2)Z3IG5A4M|8uQUCw|F8}}lF9-$z0046*ldJ#$010qNS#tmY4#WTe4#WYKD-Ig~00MDIL_t(&fz_J5ZWBQiMvupW1}PN$iy%am6d*;Ao;dG7LK>igm!N|lWOx8lVG@)AQadivfeA<;k(Pi2L?Ag94aj^Hvy0bDEIhOJj`v8TwMM(XN8_KFxpPaB$cO=EfVt>81xx~mfWtrw_yK$bwt+Xm2JjmAoh5K7gZxF{2CxVm_jvv*@C0}Syz(-TKZQf=zX1BTy8!q>DiE_(>#yj3|0P$q}GVVV_K3Hh9O^;RLw~`O*M+|C4W%TYW~hWtVSMA@?}Z25VrfUqCTHMKlxB!m3~w;p!`B8!~d|TlxupDngref6JC?XXxRhK13PhtN!@Ut@e~K^buZbG9Qirynq%Cl01fCJ`P)HM-1+MnkNhm~BAAXlvp_Q*`D-CfJh%>aFOp0j5J+crs;x)1fUqIZ=}I6F3ms#DjK8sg+rgFuZboH9TMs0EgBr3Q53&B$^{6AzZ5yN7)V)Wf+rQ3smPy8*uw#>JVt@r$H<16a#l#f>!*>6vuK>f&lmb+r0{1_$#zxwo(3)r;x(u|lFmt5AF=p)6lWkO7x}o&Ycwa|^R6ygY7Z`9YY&!yBOcFx10G{*51Kv#S>$8N*ktYH=sHce>SF>sGsaaP@3B=M4Q$m%CrjWze+IZe`C-a-.NkvXXu0mjfiBL{Q4GJ0x0000DNk~Le0000l0000l2nGNE0COjktN;K232;bRa{vGi!~g&e!~vBn4jTXf0&z)1K~!jg)tbF-6G0S4kH>-rDHQyRAVifEAVrX#IPXA08lZxgpo1P{cmPsi5|jc`J1)|J2}mH3mVg9AAUPHd$b1yDi`PpmJhS$W_ei6)M!UX8XK!1b76z@-mR*+A5Oj8ZPzdeVtrfZaypN*^s5vJdffGRsM>k)`-PpT9OuqAzzkM%}F{=D+1KiBh$bnlV#Nl6gu!md^a?u%_hwS%vp3c0go1b6FHwoOAr*h2C7y4wR@MluVc2)VF1}+WaHK;t2&PiGyvG{rvXCNmR`MAw%G$-Kmt}a<>4=!PA50-!<9?yRR9%E|{nmz(q`T4M(S=I+exefX0}sEUu_qAq`krn4Xo|7Rx1+7KK{-hKIa^tv^8Zn!h)q(%PNJxe*A<$@)TZ2d63tc*vRucEw1jqEZE%*OE^6F0FGPImUSsC1#MYWhO{Z9TrCB0>rRXy=n;F9!qe^me9JFDrnCT&AA1ZRhG3fbI1ZBV@I43vJVn72;fWVu62xSZfc*oVH_cb%a`tKO%k5IQv9=}%HBI9;6JO~y!by^#AVgv4Il?o$%F@=W%)ww`CJfWEwo+A~+ta1m+7!Yeg|Z&aR>tqs@tG=7OKXJv01Y5#BUf*A(L3i1i9(d3vc|D^Wb2Kx%52m>OpnKp9JomG{P|eQa-@wtyS2~LX0fXUx)erjH4|sIF3f{Yy(XC!)GEDDsu(WnVu%T38F+IoE3O*z_5m(t?oDABgn<`CUKlNKL~uedeis~(Ah<_!#pP=EH##L@M54(f)D;Y;mc9scMpjCf)TJv+x)!Le))0(8kz}e++BtSsCJ*KFEW6NZB@1_8E}-rQ{^U(a|(wu_y2$?LOFZRkrk&G^Cg><0+$)L4T&$wkv=^pZ}-+S1nxw(&E*2y0+{solE2Hbhj`2EAf55DkSKOg;3TtK?+%aogD7P^3{LfUccFh>e;%u$BbIphH7!Tc3Hkqb;yN|{$zokCWxLi_8@Z8qQ{3ZD>6P258+KsW_EL@R73xY@9000cQNklFPN3Z7so%lkC(6p3=lB<3=lA2fFNNB8YEzVpg{rz3=lA2(11Y#1Pu@*Xn>#r0tN{ZBw&yLK>`~vNPr;04c@>T_7~2by_em6XXgDg@4x%~JkNceg?H{d^DJM^nK?6a&cfQ-8UtW2a2i+!b^+UGo~;5;fxEyJ;CbVtn^P8gF0LEE5n!pncgDbN;Nv7C66^F;T)zSD)$pD>z~LI+Ber0%?Ew_zEbx+ggaz=y&s@+4xA=f&gvF$v2MzT#c)BjGTfqLvw%N?*1r~Bxoje;){yv8NlYT`-x-6~}z|n|y8787jz_!U}C-5fl2Cx9U5B$zld?AhkpA;Xmn5D~eu9v_lqTT1fw*}to+-%Id7Ye*rY@n-_>lXfheCPTWC?+g`1C#H>STMQA3Ut+S`3B|O;LeJDoC7WrwqUYeCF!E&`YxhPRbhcyuj}|NSO#3Hp|825i`n-!R4)Pcw*I0_KiOaVkJ;L_?QgAiUZ(SSZb4AzYTzRpr40s3}OxSm5g{pa1LAoZcyuplt*Aw^c3))ItCFq)4Y!lzSOOyH%NL*9KZ4Y2*NL(Tr5|>DZ#3hm;afxI|Tp}3~mq>=hC6Xa=iDXDzA{i2wNJf{$wLRPI&t;m22m_RO5tiGdT1uI5{nSiCtJq^ca?~Vhj&3%l;C}!1xjq>2JWP1Y@jBm)s(>o3`=^w&H7&Rjl~cR-6L9#)KHlBh2MKsyMD%khrFl71vgk&_=|&(7%8^Gk;#j3d0u>)kZo|lvHl9JWf=1FyYUB?h;>qZ#VG$xia7;;Z@%ataf9J6&q77#1Y~;RwblyWVy<5n7DAl$H1}3ccu^)SW(m)iQer+;wsMDnA?lxHff79#PAWe;%pR&E1nEEyFY{JaiqyDGL`cr8d=ab%c4s#Hw7f(f&zz-AW>+km$wpEo9dzCygn?iK925}MFY@lELK>fg7;wGmelcMoy97x#d72`?Ugz-m?|N0zHuR*7?~&i(Bqep~Diwr?4-U?&34cVnRpUxCvoB(CO(Dfs?^R|BiDoWxvcNMPU3{Cp9!+G~@~6W~v*g09Rr3ts_@cq)qz~}gDK!$jRKM9Cg0gKpy}*$Pt*t)ecQ%jxOpN-A;hp4Tv4*4s1pF3KG|_iJjXyLEUD0Y-roe_hUr17q~Y27hMw9JmsNp>b^jX`(4FOc)ZT$6C4MC1s!6R^$n6gvGxku7Q(k;cy1O^~ylv66Lw#c{TzbOI)HXzU`$Euv_91Rq&;S$|NpP1@8vD#3icmyYHbg3r@L#e>Av4!@KB9%k#vEsA7FTp}4=5?9-K=tXgM_#SpiTw-8ITp}3~mq>;}5%WQlcQV(JxQ5R{TOHRat|jc&hf6y40=p^lqQ*W%kEkN9Wh_V2GJz#2w$1SnOKSF0g+4`(C^MnIjFoe{hm{@)2@A}sEn~H9UIM?A=vVZNBH}uS3F9o)HdjZ$5iIqgL^fbz-C|6M-ZC7)tl%Nq&vGMbCM2%?z#r68em9RLEVsZ`$4^9$@wvsL*gtwoVa+m_b4vs}x-tpe}yr8q27HB}6VJj)eZfXE4Ih0WDc-RB|GT$^H!*h*Y~M|{s&s&BCfu&29M~uJfyyC5tJ3BnH4nah(ga&wxvm%Sh)qNn8nE(Q{_SbuG}=3G1sG`n`!2Cz1slFtg(F)#1yqTncpv%W(&|fCM&JoVZR0+GRwRK`Y1w%9Z97-N1X|#l^W!dD>*uYq2)+4*vRsqNg~^wJ*>vrn+erE+l@xXn{p++a)i8?!peK`HEr*_eBp_#43O0kx?z|P=vP|>PIxhB6jkjE2Ny;iH2DuztDC@^Bc}?^(Y3wBKZPp>m-BR<3L!%e_Elf_>)$T17MN#Rou()70uLv771Ad?t7k(2=6(jLhiBWEaI<%Fv5;Iw?T&7W3O=$df&XO%_GWkk3GhT%NKcbI3A~u`zp@=r;Q0hrBL#((~2p1<%a8F0Q|bTRqA>ic+9O)IK-S%rN;G1;CZ0UF5tTgeTW`YwSanzdpt!rx4jr3RmHUe{19j}!YZGM;o-e4R#jX8pAzRDzptS$(Je}yL@i28aPETu*^3fi@%JtD%X0;0-m^B`$!^2QO3jL0rm71y}paC!S{+@L?*q=!kKTH?dxFF6w9-^80j-os}FmdIvo;$Y*x9IpXqRlVKM``C^I)^(0}o{fR4?hj46MMq4BhY6?86wPyg%of4&HZ2#}xvu?zHg}1;!Y0xY63Ketbe1+P7r+6+b+q5Zzx)+x3Eg`blXC*5ZOa9AE+}-aUKP{Y;V9vi`<7M<+P7R_muf>NB6P8~9exZvALE%jcfoQwUJ@>Oe2j4M1yK)w0PlL9XV_)vxAGcv!E!l{N3;q3PZIU;HGW|^a)kxm64y<_F|85WdrZwT;ldRe5gw#VZqea8E97_|3sH#riJ6|P0&j#~)FsR1I1tfB)+-JF5Ej;^ZJs>3D6Xf3llm^9q`r0YjIhVZu+{xa%nQ5d79IZ*PC5P)aSH+ACuS;=VWN5`_N864T#isd(G0sgr>xQC*@taNhlB<8`;HbCbX#1mplIIqkeeHvMH}2@xQtCLV(T>pC-58TvRiccGO5h+9CMLcMf!OUix?3ZJVS-tzzlcp(DEw?Cjm#&E!-x3zTIzpTabY$3>x>85Z`y#HG4OCGtm4pS|o^#FeGj=sCbUa5?C!2f@Q1Oz?Yqbog87^Uy`16DVeWDy0=4N+>8phBU%jLMhG+~^qSiOv~#ZWE6g8t08w&EE!IacZ8YB)D|oSKg__04k4@+NjEU_ANAk`=@K&aPr7NozkR*JsPMkq21Bc~)ean@w@Qmk|~dyaIfivcvxa;()H}usI@h.NkvXXu0mjfiBL{Q4GJ0x0000DNk~Le0001&0002M2nGNE00|WFHUIzs32;bRa{vGf6951U69E94oEQKA0)1&zSaeuTOgdw4Z7yMCZ(?OGcx`Y10RUB0MlCo1SWQeiV{dIPVPtP&WiEJaZ~!=sos`{fn=llBU&V57Hf>-`3Mp1u)sb$^1zXid>TVs|Nvr~9wo^!7Z5Ml_y}}F)tnIW`D-y^){>~ph=NzB3HDx!#f~8o?Duus({DeUA6kbPpkk^^m$n!fbuI|1p*xe0FI6V0Pj$6`}btxy5I?Y}7wYkH?Q3xJdH+`B=(wq>Vkhwa?RLv8x5T6h>_|6KA(B%!5w7CYcu0D!ovu7%u8!hzVpFcylc)t{U_90WN0lO<@>>ffqzx7%gx_a6&MC7aWlwxJPrvBD}X_t|EK;}EnNfB;?;J#w(KpPOXKczw=erE@qNE4BZHHshcZm78wbjj`V>T#-Txf=Au}01UWEL_t(|ob8=2n3csB$3MG|m#_v55HS1<5HMhXAYlm_Bw&D`K>`E}5HMiSfI$KT4G<(~fS>^a1_=@*V2}Vo0vj+$fFQvQ-oP977tWo%m)(75=KVA8zx(|>&wZYSckVm$EMLx-1GzzX9*n@SZ!s;Tql}wqUXC0Tkpc@RE9j1@OV-eWz=BpO~Rfmg`qc45_#ce3Wo2Y|OhC3cOcrpsSYa7XE*H=lT{XCMFuBJHbk%bC2Ibt~&We4U11=J_V6t8%>7wQOE}~6UVS!n%>-a5L23)J5ueqd)mg_(A_X;!MQ%n?f@i*{4&+`mhB#44^&2sI+zX|=EdW8jWlCbp>CFq*C&U>DXfinsEyo)Uk@)#l4Sc4+Dcp{=$cz>6W_c`lll@!TvNtv4`66WTp}3~mq>=hC6Xa=iDXDzA{i2wNQT5Ek|A-4WJp{h84{OBMwi63J=`QI+CQwBE{RJF42er5L*f$2khnxLBrcH*iAy9y;u6V_xI{7}E|Cn0OC&?$63LLbL^32Ukqn7TBtzm7$#|{p0SticSc;|HSR#{EEF;hTHV4%OB(8FN1bjg_<@hRA8SOLXu+C$bQYdW){>^#TL<*(HSW(ce$bbu&KrU0PzN2FWD_r$gsnPWiiED~aT#k&m;s`!|0*+UFM=$8ognl*LBq=eXsVXe6vMs%;i3+IIdcdxTcg9*H)F#M#Q_&zkoe6e_qB4!xs_NMmkZHRBo|6PE>a=;m>~V5?_9AH}L)B=O2N4R1+3hh-8hHYj?|XO(|lzGT^ui_ft=%AW)6j$6~#SM-b`;yQ;3<1E!SS4Y4REcKy8Heh1iVoZtNG91CI;33-2awBRcB(D9yAJkKRH;*MOx4>4%PehOLxy7T{KYB`G%`%vCOv1>sT+6_%0`KvqI4n{%RSbwc%N1LI$O&qN&DB!f=ONQvn_`aGN?dz9C#$A;XU8ZlhSP^Q)L8iz$922EayfoeQ+jfJ>CiNar|7TnS&%b7sYLEzs5p>#G|2y@?ekk_8(uv*PmA;mfdG3Uvs}aR<171U6WlxK0P!Wki-iE64@PmF5-QzCFVHTgx@i?IB!0hWfkkZFB`<>R!VanVied@(MGsiSDu3pYQ7!CHgtr^&M>NDDcJiSsq@3G{hFK)P&~`@i8_sU^CVMI?y=`A;;(`*!j3z)L5AF8uW=E2-@L2MBg%4*J;sU47lj`IPZ}6Y9($OB{Gmg{iOc7HM%d|8-;o?4HnFg_wtgVtU;GzSGwf)y5RQGopE=K7DT#L};$?X+5@Hh@#pOHv*8Y(^F*Gw?gKvuzB|J5b_ua1z7$Hw2)oCPay}thiPP7bj3~RlGuxJoLv2yT{#F0y!ZHSI{^Z(dI15DK984uD^&|J<2_bQni0#R*(Nfrzhr~vXx?Ef~%*=PZc2MfU@Fx1Y8fa8DSMT#GF#4#`Q_yd7#ZM;JXTah#ph5fO?F3JViLSy%-=>#kB(b5NI>PDxZnr;k_+ZRa^j{66YSjuc0r|ElQn4ElNyq^#m5d7sJ6j-Kk4lPk}3eHY4Jzp^1Lr4K>vzE`ZMn=lYU+92VZv)U((5>tKWxSr73R-ApfXmca^C`1ew@e6Cr7Qym1Ef?6iuKj^FcZs{gCejfS$$H>)mNqRHzyZQ_wBN(O{1s^l-Fq05a{{Jq%LR5WD0Hq~71P?`DB+d+mR1Yew_ISCYC|U?bg{M_ehfSxoo-@@Ehr}TXgs`sm$^mbCFs_`gsqF7sjS>cN6%BMLX()xFGKAmt%}sz5CRYrYTe{w-_y;u$tMR_WtvI5&8lnvXN}&2r81CUz-cJo(6y6~q0`u3{%iYdw`M5f&4?0(_gY!~X-~fUfJXIU;lb0000Y0AB-N0sDb3fiHl)z;57kU?;Ex*bZz1wgOv{f0G(JNr6%uUBD>dY+xiX0yqm84x9<}FK%c6>;papHUpmkn}ChL$G`?GjHKceDAjZZFb)`-Y@?HHaFK(`U^}or+14i88sM`c2PJ+Alxmm=TmXDK*(P9jBz3kMSOxqq+1>+I7CIt1Aq7e~jRz(H7Xudo7Zy95(s&nG0lWh&2UZt5966Q(r5w6|Dam$8vh^!;MCG?1cq`eKC7YhZ6ik6qoMFIslI>F9ykdt_c^?380{>06EyWI}GbvEoYY_0=WV;MFx6skl*N4Cxz-!62qtMZG5Cuy9`T$oZ+f?j&_jHb?c76x_8gUx_QggL-S&?EyRLVGEksoGa8tcY?BKf%V*eae6!QE*?GFX$U${uFs;tsW~c8stoye}ZvzXGZGFLGsVoIbI_Cp(fZ13<6k#RSJ=*LfT}5^KFuJgZ)y+=e3$X6sBCG}G11|vY7c_>-QlR)u1g-`ebWYknE<_0ov>c-QT8bmrXxf9mLI!;&wfU*#JD7Y-;sC0R>9$MgTVeH)5aj^R|Unr{|kk_op21JgmF8*=ani4cA-)JPka9J=~UVC{Vh08gNsxP09IMJ7{%uzJYalO1N{euH$A0@}Qirw+whH+4kgot;$iL9B&SAGxjxbUJlag++2!1P?hW^U`>S0&g4G5@1Ds*;6K>ITS+KTS|$OvBwJpyo2{YMskt<#YlxXjt5Z3zkE{EDCzEY?PS>au1&Vcg&P}$wz5-r^b!EPUHI+&G49B{bufn>R^RO0}mux$8x<(~*dZ5ezzK1oY#kLK*EpB#VO7WVV%rmj>=GYbh|E7c6`8zdGh6A?(w_)#|#I&AP$K~6R&nF)aZe!~U>zj7|!ap}?KV7V~y@CDu*(Jk|sy)iVm~e!h;lE&tnqN0V)9#PgO`rv%Cs!1uAgMzO4>)j_EssfSUtI-+B?`p*X*0bY%G&Qhv-pqvEUm26`oopTe`E%`K>q^GXNVcpR;VBOL&tOFiSw%Qh5ode|@;O=CLVGpei$z_qwDdjeWR>$;dkKkqaZ8PpV}m);&EZ()ktx|HK~L)=C`%2P^{bfR?uob}lSfgYmRXI>@2Y!Yv&ydzuTAhqqLP4?^LaP%y%lly)fxiI%iZQ5mt7@R!3;Y~=5S!MkSoh-mC?gUvv$3x1D>9s87w~7`fhZ$tqpAf;U*MO(FOvT|jYG6L7S}}@m&lRE(CW}WDdjm10Dl7hnEcFwNF{~?5A91AJ7Smc&`i_+6_z^bY_V2M(5=yJ=cPN8^-(nBB(%40-6R}el$&zyDN2`-N?UxCUVD(ViP$_eu3;=#d`a6>~Soh()Xd@EIZZ6iQl;_X+p`tnuehlgoXYJD%o2>s+k13Q<&`+(Eh@`Z=u6L|+X{-R;7D*V9Y~c@ODdM9id!K$(WEo8w*T;&trvrJB~HQ*6Y3=e-kG_rq!|%ajSJv^!AF!RChWF2c12dpAez^wMcoV88p`B{s%jwU%XaC%1M6%1PMlRo;iNyRf&0^`f72s>N6hct7e-$7(L^G?Z@b43xV_w{ZD`XbJ+6c~~uY-Hp{=TeK@st{~kH^%&{?BZ}0~gQQyv`7W}}!XK<1fie`Ean1YOWC=EHqh?wYG5fKa@GeL<2CKm=>jedAN1)t^tySXj5jLT;=71GB+B&Q@K5`y68>_`!!|MV;WYQxyUO}W&pQ2kJ*YX6QJiNB4O5HHM7-u+-+EmMwdsR3$2Z;AWt|q)CSa!Xn0fdgB#B7x7T|w7eqQ98F0el9zc{jd|RJ)p8~=0_6r^iu0H!fwd8?Eh5KTi`AIN6s(pr$r>o*fg7F2ynz3Q6%n5&v0C%E5v%FcvIffa_!bu01N;Zrm+`tH(z|_Ft?h9fGY+fi)UpK1#1t2Q3Rsxonj#WzAy#u9*JHJvN|r#m4jARwe-r*6PDHYK3ahz15Z=lQsCOh_jA9&t*4H3!5^H@!WnvC-uJ#U~~f6DTvBCtr`|h!rWD`B-g+x(3ID)}BDQ%CRrlYt(uHP^4Ve;-5Aoxb{N>WgKv|V*uOGTqh!BwE*87&Q}xo&R%GsOvAU@*h1iK$NnPHK5yfDr*1fbi}r;CN}te^9pOdi{vy&|-Wr{TWkIh(0_93zs^h|oP@ssE-HZ4pw4RD(L9aps#p7k?{vuKgZ>?qyltK9JPSyY~IrbNk;=F|K9lNPmCiLh;f%08og5!cq@ULDbBE{H@Z;`zTSSIu+Bv3*fL@NM_RLv5sc0;8OlwrVSjtjj4EOqQJBE?&Z)o!TEux#j7Z-Me1;9SRrJcAOE^6=E|xmY%I>qLPPY6rSI5vi)BSPh4A2TB)kspA5#0jnJQi%5B_!fH6wrC3HBr>8)f0-Wc#fM+lwQa+va*5-_{{=pD>@Ona^C4Etp}Nh8?gC|ePdoR(GYAnWFK?h6w+?c5fiel`7kJX$z?+Uih)8+8iPdz7epprLdOZ;a_lQ2<>#sC3$e^N?ASoLz;UwG&Wnpk`Mrg29ps}j$u>1j(tU>dOS6LI6Kk?%Kseuib(Z%YP{P(8J*L`M5KB=HGVidnt{?qc%XcSCQ}lryH!}NhZs%r{IwYxjm4?JiCu-!Q*5viu_Sj~qR4i+e<17`*v^l^$oiAZ&Y*8G{k=?!e1(*A)5ZA>*J5vi)sn(q$`YXCzXC*R~BkBC&5r{;$?fFX_pK5-64M5@hG^FtbhcgO;4b`C~Fs?AgLgB!r0z@0w_KJ|}BL@Ml4toB0;Y5)TRcisZ*OEn}Bsj7X1uX#MM`L%I~t?9-iA{7@}`=>VupZ0NE%E5?8wT0GxR|7aTaA$SMigc#X+CQ}coD#V6j+BEDk!lOA{Zksiw*q(GnQ|~9Qf;BN|E&hlFL3A3uZ~2dUP5cXUjsNfaA!^XDAJiiYyad1@Xf%T_of_-h*Vo>?SHcY^bOqki}@=h)8{e*8c7W;jbop76cQK`UtK4&l`kKp5j@uR7C2bKUVu8b~Xr~-p#Y9x`@=nnS_6ByrY3{^6BCJ@rX!;d1`)p1K;u(XE_HWBGu-p`E3n+3wVui4n{<(%~SJR8~BzO9qAvBh*Vf;&F=%YG`}_uT?GB?R6`PxstT?7Pf5Nu2G|^U&?wX~E7BKUQGuVu60G|XN)CG)o4oXC-X*A(e7kmP(K-uKHh=|mmr`9(?D^NB%4jk*)S467IQ|lX{6(}F)bP*A$E>Eq046Q)fKzPUOI5ZukNZoZ|wH{(SmLZ1&WjC-s@Zd9m39g}uNYzZhYCXhyl4(#H$NWD-ecQ3Gh*XcK#t&!5k%6){r;CY5^>}LhaCW2(lnWgDib(Z%YP{P(Swr}AkQ0Fm9Tyal>bMZA@esSQ>;R4ql+S=wj*DL8*jGfV!&BR9vUq7t2L-iUc?>SC19=OD@zlfCIC0K2TIw~`c4V0C@yUvS?NPT!}`dutD4m&nb09H6oIK{EAh?JkFrjN^t?gHf<=f%%+>@OnabsoMzIW8-@3zX%+{=gG<0hc-kAR^^;Dd8{v?#Hs?DBT6hYTzx$Aim?+S47IoQ_F8*S#gx^0;RdLXAtK)_7{=zITwGR9H*y1S>`zLFyJ!B{vuL7ml3{(G4zlTy#>k#j+1}av9E}fkEez|z!@mJfj6Cl(WFZv)ieQLpu9;BFB10@C;>bl*d%8hC}t34JQhe4;>e|(y_0Il!vEwKO|+KYysYITNOKW0>v}w(llVWV}B7T&TxD;0jtOuC_8|qjth^p%3qk|MbC)s$xW=)+h_v4|SgnRyN~AzJ2rO}27!)WXW#_HYC0G{pDkM;vC)@0}_)K83V}B88pUGH_hT2TvO+{#+tN~tf3}6T_%dx+Rw9hQ8Mnk>C5o_=CCNxk0UUuvYHt4a=V?-ohWAXp753YSrpezDjbnHJGnC;kKMDjNqtIbd^VznQ{6DZA-d&~hwIgb&Me2l_s(!*8r-auIhyzSWkeBfH=HAEyI*J3pp>TRs%gLwmG8?eB6%ysB4MWkq>uv+w3fYp33Z=f_UvevQxMBw@q*AbC;uE%OI)LQ&E52*rWJuu&S%=P%*84!_p#^L{?J|C-fmo$L_@B*;GvHy7B#+26*k!)_nw_foEtj68a2Fm-u^Uh;#Kued3wA&P{_B@`)YTPYtpa49F?=EEoa8risiHP4#SnY+{fYo*?SpsDx@T~KgoAA9uAR_6_#Q#V9S**5G$r32diwE`iCr$%y2Kr~bu88!mKUQm}IgVL_|0dxmSp#J?@U-)oIlwI$uPY+Gy9Iw&?9+6!Ua?2kKmm9L-_oUEKdfFH6v@X}{C~8UVYQq|j6m50JmoxQ5->NywMFE3bFmupcnYiKOkxBI*kD{e|JV<$Ia5U9nuY(5^+NnN1>K4jD0_ha0Q(%r3c1jK>nJHgl0@plk*n!#O7<_8;l)_`?yYmAmo(@!f{iq-PaSmplUn?7`2Y&SOr({$ov#KOB)-n2y!RNzUUQCB;0k9(e}}*bm{IgZ3QYhv<2V$g##?wQ-K~xcT`1xVyC@P__b(0PCE`O#^-m^ou?sk!tFP)r7YZbRAZM-fv#r+7T!KuL2LJIPMPMp2%Ynsj7Rhn(%rUtHIp0D^S3GsE1MnBn*1F+w9?*ivwwe?us;e;tFP{4lZG;cfJ4*VSG8+SM&rO_Ar9lt%rvGYjx$2y8417#QRH~bH20qzBU5qCHurSS{wcid|+R#Us;4kuiZfdaO$&|iTSDUSUm_W4qzPVU2gx4l+iwd7fNFS?=w1>kkyeqf{XxV|VRJo`~mn4dZr@P)ff96{sZt68IAfAWP5%IX4eX6JqDa0`^ZpH1o4hyrQUM|v%*9^x^S+zY^nX}wlrBu5ymu%~gMi;+Gkd483+ozu5dXVFA~E&DrXRS6^skOQg4M&0NMnkw?12InD!(S(ol4VV*vnGhx12>{7=z8UmS%FKrskbVX~P7n5h!4Z{%g`dQvrMP@}KCrinP~sY!28g>v#VH3zO2fL{ueEz#i0Q?Tq~hn{X!0%yA-KcPE>7Lg^#j5B!mmJ~KI1l>!A??e@PQeP`?;Y)ZU`(MKT3#~5tQoV)NZbofTyCHrc)H&qLirhBpO!YuQmyox>K@-Fcq(wo`Xmk8b!{f@K?Yj`VF(^`nC1`60*5kDhaXY5vNt&&IauW%@Gq#@X%<#*y=YxqbTv3GO+6=P8CR^>ne>oUBL>~qa7z`hA;`;>W73)8S~aAsvZ&kF3BNhx0ROQY(60@lR#Q?k#BzZqLZ`f=K@Ad=L=P~bM;R$zF>b1lZYy9=L!Ad5N#3Ru_SPl&UA&m!R8G6`yY88ur}klq+Z5if4JrX12dj$Kdnyg()XBzs8gUc-GOx_##m^5Iq+n%?Tc_esjmLOEy*@1!ui%=-P?uCPM?=L1`60ivL6y}wZRvF{{RakolmM|CU7$_C(`*AV_n;|`x2n8fdbZ@*z8n{VGrKCIZLTi0tKvRpxMbdB;t8jVvR`8qhP73QNXoWqvXVh=iNrD6T2gNn>r~_nr30&1Z8=hygTn>4{ztAubQi3EOysA2RJ|SxtCzy;B-_!Qzr%rSXbk%SeIj#_b$4#9{VL)0K6Uf+!DuR><`N1ea-x0vPto-Nw%59{n*>I0PC_|l*73zq0<8etlP2K`8YPGYb*j@2412A|7sO%IB+#^6>-|+yR(*7XZ6mUu2BgJl$J@fIwAY#bd5FGXX_=v(wwd#W-9iaY#Qljm_Lzyv^uGm=X8xqQJ@@e4%QVpGskNj1YSwDrNF8juhmH=U_o+avh~UFS_`r6=@)XmM&&3_x_26_4#_DwUuy^OTC%+Xe3gHVFGT^((HYn%oEu+;jy(i~uRgMCscO$TF$s5V!iU&^bw59cbwf)41xh;SV-Ig$S!g2(b1_BnrTvaL_H?L`i%8-ua`Zlkd<9*aGnI-|%zVO`D54(Ig+kEOB{C~*wIzGiNAVwS~j?AHBbYHPv!l4GeK@tDKq;s3Sa;~fShwhf#SW)5-o?6v-@&?u)e2uS3Y2P?h;@%PJ4q){u3nBj?8Y9bHamgeLoM*7r$DKuGqA6vo1Lc3&eOp~4l0A~v^sm6oxW?Z?q8`81xkH%VO^+aV_m5uurAf%Sl4P^v(Sawhjr&}#=3PkVcokQW8J)Z{!s%8l+JKE_N~%TtebT(_7(QPY0AB-N0sDb3fiHl)z;57kU?;Ex*bZz1wgOv{f0G(JNr6%uUBD>dY+xiX0yqm84x9<}FK%c6>;papHUpmkn}ChL$G`?GjHKceDAjZZFb)`-Y@?HHaFK(`U^}or+14i88sM`c2PJ+Alxmm=TmXDK*(P9jBz3kMSOxqq+1>+I7CIt1Aq7e~jRz(H7Xudo7Zy95(s&nG0lWh&2UZt5966Q(r5w6|Dam$8vh^!;MCG?1cq`eKC7YhZ6ik6qoMFIslI>F9ykdt_c^?380{>06EyWI}GbvEoYY_0=WV;MFx6skl*N4Cxz-!62qtMZG5Cuy9`T$oZ+f?j&_jHb?c76x_8gUx_QggL-S&?EyRLVGEksoGa8tcY?BKf%V*eae6!QE*?GFX$U${uFs;tsW~c8stoye}ZvzXGZGFLGsVoIbI_Cp(fZ13<6k#RSJ=*LfT}5^KFuJgZ)y+=e3$X6sBCG}G11|vY7c_>-QlR)u1g-`ebWYknE<_0ov>c-QT8bmrXxf9mLI!;&wfU*#JD7Y-;sC0R>9$MgTVeH)5aj^R|Unr{|kk_op21JgmF8*=ani4cA-)JPka9J=~UVC{Vh08gNsxP09IMJ7{%uzJYalO1N{euH$A0@}Qirw+whH+4kgot;$iL9B&SAGxjxbUJlag++2!1P?hW^U`>S0&g4G5@1Ds*;6K>ITS+KTS|$OvBwJpyo2{YMskt<#YlxXjt5Z3zkE{EDCzEY?PS>au1&Vcg&P}$wz5-r^b!EPUHI+&G49B{bufn>R^RO0}mux$8x<(~*dZ5ezzK1oY#kLK*EpB#VO7WVV%rmj>=GYbh|E7c6`8zdGh6A?(w_)#|#I&AP$K~6R&nF)aZe!~U>zj7|!ap}?KV7V~y@CDu*(Jk|sy)iVm~e!h;lE&tnqN0V)9#PgO`rv%Cs!1uAgMzO4>)j_EssfSUtI-+B?`p*X*0bY%G&Qhv-pqvEUm26`oopTe`E%`K>q^GXNVcpR;VBOL&tOFiSw%Qh5ode|@;O=CLVGpei$z_qwDdjeWR>$;dkKkqaZ8PpV}m);&EZ()ktx|HK~L)=C`%2P^{bfR?uob}lSfgYmRXI>@2Y!Yv&ydzuTAhqqLP4?^LaP%y%lly)fxiI%iZQ5mt7@R!3;Y~=5S!MkSoh-mC?gUvv$3x1D>9s87w~7`fhZ$tqpAf;U*MO(FOvT|jYG6L7S}}@m&lRE(CW}WDdjm10Dl7hnEcFwNF{~?5A91AJ7Smc&`i_+6_z^bY_V2M(5=yJ=cPN8^-(nBB(%40-6R}el$&zyDN2`-N?UxCUVD(ViP$_eu3;=#d`a6>~Soh()Xd@EIZZ6iQl;_X+p`tnuehlgoXYJD%o2>s+k13Q<&`+(Eh@`Z=u6L|+X{-R;7D*V9Y~c@ODdM9id!K$(WEo8w*T;&trvrJB~HQ*6Y3=e-kG_rq!|%ajSJv^!AF!RChWF2c12dpAez^wMcoV88p`B{s%jwU%XaC%1M6%1PMlRo;iNyRf&0^`f72s>N6hct7e-$7(L^G?Z@b43xV_w{ZD`XbJ+6c~~uY-Hp{=TeK@st{~kH^%&{?BZ}0~gQQyv`7W}}!XK<1fie`Ean1YOWC=EHqh?wYG5fKa@GeL<2CKm=>jedAN1)t^tySXj5jLT;=71GB+B&Q@K5`y68>_`!!|MV;WYQxyUO}W&pQ2kJ*YX6QJiNB4O5HHM7-u+-+EmMwdsR3$2Z;AWt|q)CSa!Xn0fdgB#B7x7T|w7eqQ98F0el9zc{jd|RJ)p8~=0_6r^iu0H!fwd8?Eh5KTi`AIN6s(pr$r>o*fg7F2ynz3Q6%n5&v0C%E5v%FcvIffa_!bu01N;Zrm+`tH(z|_Ft?h9fGY+fi)UpK1#1t2Q3Rsxonj#WzAy#u9*JHJvN|r#m4jARwe-r*6PDHYK3ahz15Z=lQsCOh_jA9&t*4H3!5^H@!WnvC-uJ#U~~f6DTvBCtr`|h!rWD`B-g+x(3ID)}BDQ%CRrlYt(uHP^4Ve;-5Aoxb{N>WgKv|V*uOGTqh!BwE*87&Q}xo&R%GsOvAU@*h1iK$NnPHK5yfDr*1fbi}r;CN}te^9pOdi{vy&|-Wr{TWkIh(0_93zs^h|oP@ssE-HZ4pw4RD(L9aps#p7k?{vuKgZ>?qyltK9JPSyY~IrbNk;=F|K9lNPmCiLh;f%08og5!cq@ULDbBE{H@Z;`zTSSIu+Bv3*fL@NM_RLv5sc0;8OlwrVSjtjj4EOqQJBE?&Z)o!TEux#j7Z-Me1;9SRrJcAOE^6=E|xmY%I>qLPPY6rSI5vi)BSPh4A2TB)kspA5#0jnJQi%5B_!fH6wrC3HBr>8)f0-Wc#fM+lwQa+va*5-_{{=pD>@Ona^C4Etp}Nh8?gC|ePdoR(GYAnWFK?h6w+?c5fiel`7kJX$z?+Uih)8+8iPdz7epprLdOZ;a_lQ2<>#sC3$e^N?ASoLz;UwG&Wnpk`Mrg29ps}j$u>1j(tU>dOS6LI6Kk?%Kseuib(Z%YP{P(8J*L`M5KB=HGVidnt{?qc%XcSCQ}lryH!}NhZs%r{IwYxjm4?JiCu-!Q*5viu_Sj~qR4i+e<17`*v^l^$oiAZ&Y*8G{k=?!e1(*A)5ZA>*J5vi)sn(q$`YXCzXC*R~BkBC&5r{;$?fFX_pK5-64M5@hG^FtbhcgO;4b`C~Fs?AgLgB!r0z@0w_KJ|}BL@Ml4toB0;Y5)TRcisZ*OEn}Bsj7X1uX#MM`L%I~t?9-iA{7@}`=>VupZ0NE%E5?8wT0GxR|7aTaA$SMigc#X+CQ}coD#V6j+BEDk!lOA{Zksiw*q(GnQ|~9Qf;BN|E&hlFL3A3uZ~2dUP5cXUjsNfaA!^XDAJiiYyad1@Xf%T_of_-h*Vo>?SHcY^bOqki}@=h)8{e*8c7W;jbop76cQK`UtK4&l`kKp5j@uR7C2bKUVu8b~Xr~-p#Y9x`@=nnS_6ByrY3{^6BCJ@rX!;d1`)p1K;u(XE_HWBGu-p`E3n+3wVui4n{<(%~SJR8~BzO9qAvBh*Vf;&F=%YG`}_uT?GB?R6`PxstT?7Pf5Nu2G|^U&?wX~E7BKUQGuVu60G|XN)CG)o4oXC-X*A(e7kmP(K-uKHh=|mmr`9(?D^NB%4jk*)S467IQ|lX{6(}F)bP*A$E>Eq046Q)fKzPUOI5ZukNZoZ|wH{(SmLZ1&WjC-s@Zd9m39g}uNYzZhYCXhyl4(#H$NWD-ecQ3Gh*XcK#t&!5k%6){r;CY5^>}LhaCW2(lnWgDib(Z%YP{P(Swr}AkQ0Fm9Tyal>bMZA@esSQ>;R4ql+S=wj*DL8*jGfV!&BR9vUq7t2L-iUc?>SC19=OD@zlfCIC0K2TIw~`c4V0C@yUvS?NPT!}`dutD4m&nb09H6oIK{EAh?JkFrjN^t?gHf<=f%%+>@OnabsoMzIW8-@3zX%+{=gG<0hc-kAR^^;Dd8{v?#Hs?DBT6hYTzx$Aim?+S47IoQ_F8*S#gx^0;RdLXAtK)_7{=zITwGR9H*y1S>`zLFyJ!B{vuL7ml3{(G4zlTy#>k#j+1}av9E}fkEez|z!@mJfj6Cl(WFZv)ieQLpu9;BFB10@C;>bl*d%8hC}t34JQhe4;>e|(y_0Il!vEwKO|+KYysYITNOKW0>v}w(llVWV}B7T&TxD;0jtOuC_8|qjth^p%3qk|MbC)s$xW=)+h_v4|SgnRyN~AzJ2rO}27!)WXW#_HYC0G{pDkM;vC)@0}_)K83V}B88pUGH_hT2TvO+{#+tN~tf3}6T_%dx+Rw9hQ8Mnk>C5o_=CCNxk0UUuvYHt4a=V?-ohWAXp753YSrpezDjbnHJGnC;kKMDjNqtIbd^VznQ{6DZA-d&~hwIgb&Me2l_s(!*8r-auIhyzSWkeBfH=HAEyI*J3pp>TRs%gLwmG8?eB6%ysB4MWkq>uv+w3fYp33Z=f_UvevQxMBw@q*AbC;uE%OI)LQ&E52*rWJuu&S%=P%*84!_p#^L{?J|C-fmo$L_@B*;GvHy7B#+26*k!)_nw_foEtj68a2Fm-u^Uh;#Kued3wA&P{_B@`)YTPYtpa49F?=EEoa8risiHP4#SnY+{fYo*?SpsDx@T~KgoAA9uAR_6_#Q#V9S**5G$r32diwE`iCr$%y2Kr~bu88!mKUQm}IgVL_|0dxmSp#J?@U-)oIlwI$uPY+Gy9Iw&?9+6!Ua?2kKmm9L-_oUEKdfFH6v@X}{C~8UVYQq|j6m50JmoxQ5->NywMFE3bFmupcnYiKOkxBI*kD{e|JV<$Ia5U9nuY(5^+NnN1>K4jD0_ha0Q(%r3c1jK>nJHgl0@plk*n!#O7<_8;l)_`?yYmAmo(@!f{iq-PaSmplUn?7`2Y&SOr({$ov#KOB)-n2y!RNzUUQCB;0k9(e}}*bm{IgZ3QYhv<2V$g##?wQ-K~xcT`1xVyC@P__b(0PCE`O#^-m^ou?sk!tFP)r7YZbRAZM-fv#r+7T!KuL2LJIPMPMp2%Ynsj7Rhn(%rUtHIp0D^S3GsE1MnBn*1F+w9?*ivwwe?us;e;tFP{4lZG;cfJ4*VSG8+SM&rO_Ar9lt%rvGYjx$2y8417#QRH~bH20qzBU5qCHurSS{wcid|+R#Us;4kuiZfdaO$&|iTSDUSUm_W4qzPVU2gx4l+iwd7fNFS?=w1>kkyeqf{XxV|VRJo`~mn4dZr@P)ff96{sZt68IAfAWP5%IX4eX6JqDa0`^ZpH1o4hyrQUM|v%*9^x^S+zY^nX}wlrBu5ymu%~gMi;+Gkd483+ozu5dXVFA~E&DrXRS6^skOQg4M&0NMnkw?12InD!(S(ol4VV*vnGhx12>{7=z8UmS%FKrskbVX~P7n5h!4Z{%g`dQvrMP@}KCrinP~sY!28g>v#VH3zO2fL{ueEz#i0Q?Tq~hn{X!0%yA-KcPE>7Lg^#j5B!mmJ~KI1l>!A??e@PQeP`?;Y)ZU`(MKT3#~5tQoV)NZbofTyCHrc)H&qLirhBpO!YuQmyox>K@-Fcq(wo`Xmk8b!{f@K?Yj`VF(^`nC1`60*5kDhaXY5vNt&&IauW%@Gq#@X%<#*y=YxqbTv3GO+6=P8CR^>ne>oUBL>~qa7z`hA;`;>W73)8S~aAsvZ&kF3BNhx0ROQY(60@lR#Q?k#BzZqLZ`f=K@Ad=L=P~bM;R$zF>b1lZYy9=L!Ad5N#3Ru_SPl&UA&m!R8G6`yY88ur}klq+Z5if4JrX12dj$Kdnyg()XBzs8gUc-GOx_##m^5Iq+n%?Tc_esjmLOEy*@1!ui%=-P?uCPM?=L1`60ivL6y}wZRvF{{RakolmM|CU7$_C(`*AV_n;|`x2n8fdbZ@*z8n{VGrKCIZLTi0tKvRpxMbdB;t8jVvR`8qhP73QNXoWqvXVh=iNrD6T2gNn>r~_nr30&1Z8=hygTn>4{ztAubQi3EOysA2RJ|SxtCzy;B-_!Qzr%rSXbk%SeIj#_b$4#9{VL)0K6Uf+!DuR><`N1ea-x0vPto-Nw%59{n*>I0PC_|l*73zq0<8etlP2K`8YPGYb*j@2412A|7sO%IB+#^6>-|+yR(*7XZ6mUu2BgJl$J@fIwAY#bd5FGXX_=v(wwd#W-9iaY#Qljm_Lzyv^uGm=X8xqQJ@@e4%QVpGskNj1YSwDrNF8juhmH=U_o+avh~UFS_`r6=@)XmM&&3_x_26_4#_DwUuy^OTC%+Xe3gHVFGT^((HYn%oEu+;jy(i~uRgMCscO$TF$s5V!iU&^bw59cbwf)41xh;SV-Ig$S!g2(b1_BnrTvaL_H?L`i%8-ua`Zlkd<9*aGnI-|%zVO`D54(Ig+kEOB{C~*wIzGiNAVwS~j?AHBbYHPv!l4GeK@tDKq;s3Sa;~fShwhf#SW)5-o?6v-@&?u)e2uS3Y2P?h;@%PJ4q){u3nBj?8Y9bHamgeLoM*7r$DKuGqA6vo1Lc3&eOp~4l0A~v^sm6oxW?Z?q8`81xkH%VO^+aV_m5uurAf%Sl4P^v(Sawhjr&}#=3PkVcokQW8J)Z{!s%8l+JKE_N~%TtebT(_7(QP= 0) or mode == Enum.CameraMode.Orbital or\r\n mode == Enum.CameraMode.Custom\r\nend\r\n\r\n--获取按键盘时的移动方向最终取值\r\nfunction GetKeyValue()\r\n moveForwardAxis = Input.GetPressKeyData(FORWARD_KEY) > 0 and 1 or 0\r\n moveBackAxis = Input.GetPressKeyData(BACK_KEY) > 0 and -1 or 0\r\n moveLeftAxis = Input.GetPressKeyData(LEFT_KEY) > 0 and 1 or 0\r\n moveRightAxis = Input.GetPressKeyData(RIGHT_KEY) > 0 and -1 or 0\r\n if player.State == Enum.CharacterState.Died then\r\n moveForwardAxis, moveBackAxis, moveLeftAxis, moveRightAxis = 0, 0, 0, 0\r\n end\r\nend\r\n\r\n-- 获取移动方向\r\nfunction GetMoveDir()\r\n forwardDir = IsFreeMode() and camera.Forward or player.Forward\r\n forwardDir.y = 0\r\n rightDir = Vector3(0, 1, 0):Cross(forwardDir)\r\n horizontal = joystick.Horizontal\r\n vertical = joystick.Vertical\r\n if horizontal ~= 0 or vertical ~= 0 then\r\n finalDir = rightDir * horizontal + forwardDir * vertical\r\n else\r\n GetKeyValue()\r\n finalDir = forwardDir * (moveForwardAxis + moveBackAxis) - rightDir * (moveLeftAxis + moveRightAxis)\r\n end\r\nend\r\n\r\n-- 移动逻辑\r\nfunction PlayerMove(dir)\r\n dir.y = 0\r\n if player.State == Enum.CharacterState.Died then\r\n dir = Vector3.Zero\r\n end\r\n if dir.Magnitude > 0 then\r\n if IsFreeMode then\r\n player:FaceToDir(dir, 4 * math.pi)\r\n end\r\n player:MoveTowards(Vector2(dir.x, dir.z).Normalized)\r\n else\r\n player:MoveTowards(Vector2.Zero)\r\n end\r\nend\r\n\r\n-- 跳跃逻辑\r\nfunction PlayerJump()\r\n if (player.IsOnGround or player.State == Enum.CharacterState.Seated) and not isDead then\r\n player:Jump()\r\n return\r\n end\r\nend\r\njumpButton.OnDown:Connect(PlayerJump)\r\nInput.OnKeyDown:Connect(\r\n function()\r\n if Input.GetPressKeyData(JUMP_KEY) == 1 then\r\n PlayerJump()\r\n end\r\n end\r\n)\r\n\r\n-- 死亡逻辑\r\nfunction PlayerDie()\r\n isDead = true\r\n wait(player.RespawnTime)\r\n player:Reset()\r\n isDead = false\r\nend\r\nplayer.OnDead:Connect(PlayerDie)\r\n\r\n-- 生命值检测\r\nfunction HealthCheck(oldHealth, newHealth)\r\n if newHealth <= 0 then\r\n player:Die()\r\n end\r\nend\r\nplayer.OnHealthChange:Connect(HealthCheck)\r\n\r\n-- 每个渲染帧处理操控逻辑\r\nfunction MainControl()\r\n camera = world.CurrentCamera\r\n mode = camera.CameraMode\r\n GetMoveDir()\r\n PlayerMove(finalDir)\r\nend\r\nworld.OnRenderStepped:Connect(MainControl)\r\n\r\n-- 检测触屏的手指数\r\nlocal touchNumber = 0\r\nfunction countTouch(container)\r\n touchNumber = #container\r\nend\r\ntouchScreen.OnTouched:Connect(countTouch)\r\n\r\n-- 滑屏转向\r\nfunction cameraMove(pos, dis, deltapos, speed)\r\n if touchNumber == 1 then\r\n if IsFreeMode() then\r\n camera:CameraMove(deltapos)\r\n else\r\n player:RotateAround(player.Position, Vector3.Up, deltapos.x)\r\n camera:CameraMove(Vector2(0, deltapos.y))\r\n end\r\n end\r\nend\r\ntouchScreen.OnPanStay:Connect(cameraMove)\r\n\r\n-- 双指缩放摄像机距离\r\nfunction cameraZoom(pos1, pos2, dis, speed)\r\n if mode == Enum.CameraMode.Social then\r\n camera.Distance = camera.Distance - dis / 50\r\n end\r\nend\r\ntouchScreen.OnPinchStay:Connect(cameraZoom)\r\n"}}]},{"class":"cScriptObject","name":"PlayerGuiScript","guid":[2621639870,238436836,3036077570,4237246209],"parentGuid":[3505660041,1377324444,2607505656,798530734],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerGuiScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--[[\r\nAuthor: your name\r\nDate: 2020-06-04 23:23:59\r\nLastEditTime: 2020-08-11 17:52:25\r\nLastEditors: Please set LastEditors\r\nDescription: In User Settings Edit\r\nFilePath: \\Luas\\['Archetypes']['Player']['Local']['C_Code']['PlayerGuiScript'].Script.lua\r\n--]]\r\n--- 玩家默认UI\r\n-- @script Player Default GUI\r\n-- @copyright Lilith Games, Avatar Team\r\n\r\n-- 获取本地玩家\r\nlocal player = localPlayer\r\n\r\n-- 姓名板\r\nlocal nameGUI = player.NameGui\r\nnameGUI.NameBarTxt1.Text = localPlayer.Name\r\nnameGUI.NameBarTxt2.Text = localPlayer.Name\r\n\r\n-- 姓名板的显示逻辑\r\nfunction NameBarLogic()\r\n\tnameGUI.Visible = player.DisplayName\r\n\tif player.DisplayName then\r\n\t\tlocal addedHeight = (healthGUI and healthGUI.ActiveSelf) and 1.1 or 1\r\n\t\tnameGUI.LocalPosition = Vector3(0, addedHeight + player.Avatar.Height, 0)\r\n\tend\r\nend\r\n\r\n-- 血条\r\nlocal healthGUI = player.HealthGui\r\nlocal background = healthGUI.BackgroundImg\r\nlocal healthBar = background.HealthBarImg\r\nlocal RED_BAR = ResourceManager.GetTexture('Internal/Blood_Red')\r\nlocal GREEN_BAR = ResourceManager.GetTexture('Internal/Blood_Green')\r\nlocal ORANGE_BAR = ResourceManager.GetTexture('Internal/Blood_Orange')\r\nlocal HIT_LAST_TIME = 2\r\nlocal healthBarShowTime = 0\r\n\r\n-- 血条随生命值颜色改变而改变\r\nfunction healthChange(oldHealth, newHealth)\r\n\tif oldHealth > newHealth then\r\n\t\thealthBarShowTime = 2\r\n\tend\r\n\tlocal percent = player.Health / player.MaxHealth\r\n\tif percent >= 0.7 then\r\n\t\thealthBar.Texture = GREEN_BAR\r\n\telseif percent >= 0.3 then\r\n\t\thealthBar.Texture = ORANGE_BAR\r\n\telse\r\n\t\thealthBar.Texture = RED_BAR\r\n\tend\r\n\thealthBar.AnchorsX = Vector2(0.05, 0.9 * percent + 0.05)\r\nend\r\nplayer.OnHealthChange:Connect(healthChange)\r\n\r\n-- 血条在各显示模式下的显示逻辑\r\nfunction HealthBarLogic(delta)\r\n\thealthBarShowTime = healthBarShowTime - delta\r\n\tif player.HealthDisplayMode == Enum.HealthDisplayMode.Always then\r\n\t\thealthGUI.Visible = true\r\n\telseif player.HealthDisplayMode == Enum.HealthDisplayMode.Never then\r\n\t\thealthGUI.Visible = false\r\n\telseif player.HealthDisplayMode == Enum.HealthDisplayMode.OnHit then\r\n\t\thealthGUI.Visible = player.Health ~= player.MaxHealth\r\n\telse\r\n\t\thealthGUI.Visible = healthBarShowTime > 0\r\n\tend\r\nend\r\n\r\n-- 每个渲染帧更新姓名板和血条的显示逻辑\r\nfunction MainGUI(delta)\r\n\tNameBarLogic()\r\n\tHealthBarLogic(delta)\r\nend\r\nworld.OnRenderStepped:Connect(MainGUI)\r\n"}}]},{"class":"cScriptObject","name":"ClientMainScript","guid":[3286834691,760497087,3178050546,2718823107],"parentGuid":[3505660041,1377324444,2607505656,798530734],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ClientMainScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 客户端代码入口\r\n-- @script Client Main Function\r\n-- @copyright Lilith Games, Avatar Team\r\nClient:Run()\r\n"}}]},{"class":"cIndependentObject","name":"Independent","guid":[3627004305,3866512638,2620321223,2908859036],"parentGuid":[417325223,2932100337,2430726746,1418825083],"components":[{"id":0,"class":"sIndependentTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Independent"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cUiScreenUiObject","name":"ControlGui","guid":[3152002301,500581670,2834863041,2890838613],"parentGuid":[417325223,2932100337,2430726746,1418825083],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ControlGui"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIScreenRenderComponent","data":{}},{"id":26,"class":"sUiScreenUiComponent","data":{"m_widgetSize":[1920.0,1080.0],"m_order":1,"m_pivot":[0.0,0.0],"m_anchorsX":[0.0,0.0],"m_anchorsY":[0.0,0.0],"m_anchor":"kAlignBottomLeft","m_anchorPercent":[0.0,0.0],"m_alignment":"kAlignBottomLeft","m_alignmentPercent":[0.0,0.0]}}]},{"class":"cUiFigureObject","name":"TouchFig","guid":[2166888536,686704193,2615741217,2673510721],"parentGuid":[3152002301,500581670,2834863041,2890838613],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"TouchFig"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":24,"class":"sUiFigureComponent","data":{"m_widgetSize":[2016.2951,889.9224],"m_pivot":[0.0,0.0],"m_anchorsX":[0.0,1.0],"m_anchorsY":[0.0,1.0],"m_anchor":"kAlignBottomLeft","m_anchorPercent":[0.0,0.0],"m_alignment":"kAlignBottomLeft","m_alignmentPercent":[0.0,0.0],"m_prePivot":[0.0,0.0],"m_preAnchorsX":[0.0,1.0],"m_preAnchorsY":[0.0,1.0],"m_finalSize":[2000.0001,1551.923],"m_autoScale":true,"m_autoScaleType":"ByParentScale","m_bgColor":[1.0,1.0,1.0,0.0],"m_borderColor":[0.0,0.0,0.0,0.0]}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiButtonObject","name":"JumpBtn","guid":[269643794,976504742,2395271818,3878267745],"parentGuid":[3152002301,500581670,2834863041,2890838613],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"JumpBtn"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":26,"class":"sUiButtonComponent","data":{"m_addedSize":[200.0,200.0],"m_widgetSize":[194.1089,194.1089],"m_order":1,"m_pivot":[0.0,0.0],"m_anchorsX":[0.85,0.85],"m_anchorsY":[0.1,0.1],"m_anchor":"kAlignBottomLeft","m_anchorPercent":[0.0,0.0],"m_alignment":"kAlignBottomRight","m_alignmentPercent":[1.0,0.0],"m_acceptRaycastWhenEdit":true,"m_prePivot":[0.0,0.0],"m_preAnchorsX":[0.85,0.85],"m_preAnchorsY":[0.1,0.1],"m_finalSize":[200.0,200.0],"m_autoScale":true,"m_autoScaleType":"ByParentDiagonal","m_image":{"m_guid":[1381378012,3623568531,2182762728,2959803675],"m_revision":0,"m_type":"kTexture","m_autoGenerated":false},"m_fillAmount":0.0,"m_imageColor":[1.0196,1.0196,1.0196,1.0],"m_averageColor":[0.0,0.0,0.0,1.0],"m_averageColorReciprocal":[5.0,5.0,5.0,1.6776]}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiJoystickObject","name":"Joystick","guid":[2678133478,3590734283,2993206887,719544226],"parentGuid":[3152002301,500581670,2834863041,2890838613],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Joystick"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":30,"class":"sUiJoystickComponent","data":{"m_widgetSize":[291.1633,291.1633],"m_order":2,"m_pivot":[0.0,0.0],"m_anchorsX":[0.1,0.1],"m_anchorsY":[0.1,0.1],"m_anchor":"kAlignTopLeft","m_anchorPercent":[0.0,0.0],"m_alignment":"kAlignBottomLeft","m_alignmentPercent":[0.0,0.0],"m_prePivot":[0.0,0.0],"m_preAnchorsX":[0.1,0.1],"m_preAnchorsY":[0.1,0.1],"m_finalSize":[300.0,300.0],"m_autoScale":true,"m_autoScaleType":"ByParentDiagonal","m_threshold":0.1,"m_workArea":[230.6141,230.6141],"m_backGround":{"m_guid":[1676341669,384255279,2994375601,539764901],"m_revision":0,"m_type":"kTexture","m_autoGenerated":false},"m_handle":{"m_guid":[1309922238,2547074866,2257169120,2540834883],"m_revision":0,"m_type":"kTexture","m_autoGenerated":false},"m_arrow":{"m_guid":[3072216954,3382394894,3198588415,2980353886],"m_revision":0,"m_type":"kTexture","m_autoGenerated":false},"m_normalized":true}}]},{"class":"cUiSurfaceUiObject","name":"NameGui","guid":[4146740280,3784462226,2760327012,2765239408],"parentGuid":[821939657,2231651804,3205518288,3133023722],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[0.0,2.0,0.0]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"NameGui"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUISurfaceRenderComponent","data":{}},{"id":26,"class":"sUiSurfaceUiComponent","data":{"m_pivot":[0.0,0.0],"m_anchorsX":[0.0,0.0],"m_anchorsY":[0.0,0.0],"m_anchor":"kAlignBottomLeft","m_anchorPercent":[0.0,0.0],"m_alignment":"kAlignBottomLeft","m_alignmentPercent":[0.0,0.0],"m_billboard":true,"m_size":[300.0,80.0],"m_disappearDistance":15,"m_originSize":[300.0,80.0]}}]},{"class":"cUiTextObject","name":"NameBarTxt1","guid":[4182480961,1243630445,2505638190,3021140383],"parentGuid":[4146740280,3784462226,2760327012,2765239408],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"NameBarTxt1"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":23,"class":"sUiTextComponent","data":{"m_addedSize":[300.0,80.0],"m_widgetSize":[300.0,80.0],"m_anchor":"kAlignBottomLeft","m_oldTreeLevelVersion":true,"m_finalSize":[300.0,80.0],"m_offset":[1.0,6.0],"m_autoScale":true,"m_autoScaleType":"ByParentScale","m_text":"","m_textColor":[0.1294,0.0,0.0,1.0],"m_fontSize":32,"m_wrap":true}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiTextObject","name":"NameBarTxt2","guid":[3408334953,2456570346,2781213355,3640939331],"parentGuid":[4146740280,3784462226,2760327012,2765239408],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"NameBarTxt2"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":23,"class":"sUiTextComponent","data":{"m_addedSize":[300.0,80.0],"m_widgetSize":[300.0,80.0],"m_order":1,"m_anchor":"kAlignBottomLeft","m_finalSize":[300.0,80.0],"m_offset":[-2.0,10.0],"m_autoScale":true,"m_autoScaleType":"ByParentScale","m_text":"","m_textColor":[1.0,1.0,1.0,1.0],"m_fontSize":32,"m_wrap":true}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiSurfaceUiObject","name":"HealthGui","guid":[369634605,1008748531,2745303685,1844355597],"parentGuid":[821939657,2231651804,3205518288,3133023722],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[0.0,1.9199,0.0]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"HealthGui"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUISurfaceRenderComponent","data":{}},{"id":26,"class":"sUiSurfaceUiComponent","data":{"m_pivot":[0.0,0.0],"m_anchorsX":[0.0,0.0],"m_anchorsY":[0.0,0.0],"m_anchor":"kAlignBottomLeft","m_anchorPercent":[0.0,0.0],"m_alignment":"kAlignBottomLeft","m_alignmentPercent":[0.0,0.0],"m_billboard":true,"m_size":[160.0,80.0],"m_disappearDistance":50000,"m_originSize":[160.0,80.0]}}]},{"class":"cUiImageObject","name":"BackgroundImg","guid":[568360428,1629308004,2824114936,747566214],"parentGuid":[369634605,1008748531,2745303685,1844355597],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"BackgroundImg"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":22,"class":"sUiImageComponent","data":{"m_addedSize":[160.0,31.0],"m_widgetSize":[160.0,31.0],"m_pivot":[0.0,0.0],"m_anchorsX":[0.0,0.0],"m_anchorsY":[0.0,0.0],"m_anchor":"kAlignBottomLeft","m_anchorPercent":[0.0,0.0],"m_alignment":"kAlignBottomLeft","m_alignmentPercent":[0.0,0.0],"m_acceptRaycastWhenEdit":true,"m_prePivot":[0.0,0.0],"m_preAnchorsX":[0.0,0.0],"m_preAnchorsY":[0.0,0.0],"m_finalSize":[160.0,31.0],"m_offset":[-0.0,6.0],"m_imagePriSize":[136.0,28.0],"m_textureRef":{"m_guid":[2748549193,222842754,2941154731,726271496],"m_revision":0,"m_type":"kTexture","m_autoGenerated":false},"m_imageType":"kSliced","m_fillAmount":0.0,"m_imageColor":[1.0,1.0,1.0,0.9555],"m_averageColor":[0.0,0.0,0.0,1.0],"m_averageColorReciprocal":[1.0,1.0,1.0,5.6666]}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiImageObject","name":"HealthBarImg","guid":[1838961149,3644411417,3140159949,3227657251],"parentGuid":[568360428,1629308004,2824114936,747566214],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"HealthBarImg"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":22,"class":"sUiImageComponent","data":{"m_widgetSize":[144.0,12.3999],"m_pivot":[0.0,0.0],"m_anchorsX":[0.05,0.9499],"m_anchorsY":[0.3,0.6999],"m_anchor":"kAlignBottomLeft","m_anchorPercent":[0.0,0.0],"m_alignment":"kAlignBottomLeft","m_alignmentPercent":[0.0,0.0],"m_acceptRaycastWhenEdit":true,"m_prePivot":[0.0,0.0],"m_preAnchorsX":[0.05,0.9499],"m_preAnchorsY":[0.3,0.6999],"m_finalSize":[144.0,12.3999],"m_imagePriSize":[124.0,16.0],"m_textureRef":{"m_guid":[364921788,1323712570,2497125887,1146669227],"m_revision":0,"m_type":"kTexture","m_autoGenerated":false},"m_imageType":"kSliced","m_fillAmount":0.0,"m_imageColor":[4.5625,1.0,3.1612,1.0],"m_averageColor":[0.0,0.0,0.0,1.0],"m_averageColorReciprocal":[15.9374,1.0,8.2258,1.0]}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cIndependentObject","name":"Independent","guid":[2873573351,1645366652,2298632684,4233248422],"parentGuid":[821939657,2231651804,3205518288,3133023722],"components":[{"id":0,"class":"sIndependentTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Independent"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"GuideArchetype","guid":[561473259,162807994,2433453766,3209124006],"parentGuid":[2301495294,1274365006,3003438828,1864040556],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GuideArchetype"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cUiScreenUiObject","name":"ClickGuide","guid":[2365122082,899434690,2179617276,3710057178],"parentGuid":[561473259,162807994,2433453766,3209124006],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ClickGuide"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIScreenRenderComponent","data":{}},{"id":26,"class":"sUiScreenUiComponent","data":{"m_order":100}}]},{"class":"cUiFigureObject","name":"FigBackground","guid":[193533327,97273373,2447791134,1407769823],"parentGuid":[2365122082,899434690,2179617276,3710057178],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"FigBackground"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":24,"class":"sUiFigureComponent","data":{"m_widgetSize":[2041.6667,1000.0],"m_anchorsX":[0.0,1.0],"m_anchorsY":[0.0,1.0],"m_acceptRaycastWhenEdit":true,"m_preAnchorsX":[0.0,1.0],"m_preAnchorsY":[0.0,1.0],"m_finalSize":[2000.0001,1551.923],"m_bgColor":[0.0,0.0,0.0,0.3921],"m_borderColor":[0.0,0.0,0.0,0.3921]}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiImageObject","name":"ImgDot","guid":[775533922,2914337731,2824470072,1287739025],"parentGuid":[2365122082,899434690,2179617276,3710057178],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ImgDot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":22,"class":"sUiImageComponent","data":{"m_addedSize":[47.0,47.0],"m_widgetSize":[47.0,47.0],"m_order":1,"m_acceptRaycastWhenEdit":true,"m_finalSize":[47.0,47.0],"m_imagePriSize":[47.0,47.0],"m_textureRef":{"m_guid":[953679908,159793237,2165885860,1104393525],"m_revision":-1,"m_type":"kTexture","m_autoGenerated":false},"m_imageType":"kSliced","m_fillAmount":0.0,"m_averageColor":[0.0,0.0,0.0,1.0],"m_averageColorReciprocal":[1.0,1.0,1.0,1.0431]}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiImageObject","name":"ImgHand","guid":[3206132332,3936635091,2669444799,1004457278],"parentGuid":[775533922,2914337731,2824470072,1287739025],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ImgHand"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":22,"class":"sUiImageComponent","data":{"m_addedSize":[151.0,192.0],"m_widgetSize":[151.0,192.0],"m_pivot":[0.3499,1.5],"m_anchorPercent":[0.3499,1.5],"m_acceptRaycastWhenEdit":true,"m_prePivot":[0.3499,1.5],"m_finalSize":[151.0,192.0],"m_imagePriSize":[151.0,192.0],"m_textureRef":{"m_guid":[1260272136,125914301,2612360310,1584440831],"m_revision":-1,"m_type":"kTexture","m_autoGenerated":false},"m_imageType":"kSliced","m_fillAmount":0.0,"m_averageColor":[0.0,0.0,0.0,1.0],"m_averageColorReciprocal":[1.0,1.0,1.0,1.0673]}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiImageObject","name":"ImgCircle","guid":[1289910241,2183086102,2544630924,1952969503],"parentGuid":[775533922,2914337731,2824470072,1287739025],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ImgCircle"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":22,"class":"sUiImageComponent","data":{"m_addedSize":[276.0,276.0],"m_widgetSize":[276.0,276.0],"m_order":1,"m_acceptRaycastWhenEdit":true,"m_finalSize":[276.0,276.0],"m_imagePriSize":[276.0,276.0],"m_textureRef":{"m_guid":[3420273076,1898989905,2838226766,2988281017],"m_revision":-1,"m_type":"kTexture","m_autoGenerated":false},"m_imageType":"kSliced","m_fillAmount":0.0,"m_averageColor":[0.0,0.0,0.0,1.0],"m_averageColorReciprocal":[1.0,1.0,1.0,1.1137]}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiFigureObject","name":"FigTextBox","guid":[2708136990,3530313139,3204269919,1596665144],"parentGuid":[775533922,2914337731,2824470072,1287739025],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"FigTextBox"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":24,"class":"sUiFigureComponent","data":{"m_addedSize":[400.0,100.0],"m_widgetSize":[400.0,100.0],"m_order":2,"m_anchorsY":[3.0,3.0],"m_acceptRaycastWhenEdit":true,"m_preAnchorsY":[3.0,3.0],"m_finalSize":[400.0,100.0],"m_bgColor":[0.0,0.0,0.0,0.3921],"m_borderColor":[0.0,0.0,0.0,0.3921]}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiTextObject","name":"TxtContent","guid":[3313636831,2756263958,2171545255,2669991234],"parentGuid":[2708136990,3530313139,3204269919,1596665144],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"TxtContent"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":23,"class":"sUiTextComponent","data":{"m_addedSize":[200.0,60.0],"m_widgetSize":[200.0,60.0],"m_order":1,"m_acceptRaycastWhenEdit":true,"m_finalSize":[200.0,60.0],"m_text":"","m_textColor":[1.0,1.0,1.0,1.0],"m_fontSize":24}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cUiButtonObject","name":"BtnClose","guid":[3801986601,3327740245,2585213971,1154776719],"parentGuid":[775533922,2914337731,2824470072,1287739025],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"BtnClose"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":20,"class":"sUIRenderComponent","data":{}},{"id":26,"class":"sUiButtonComponent","data":{"m_addedSize":[140.0,140.0],"m_widgetSize":[140.0,140.0],"m_order":3,"m_acceptRaycastWhenEdit":true,"m_finalSize":[140.0,140.0],"m_alpha":0.0,"m_image":{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},"m_fillAmount":0.0}},{"id":27,"class":"sUiTouchEventComponent","data":{}}]},{"class":"cScriptObject","name":"GuideEffectScript","guid":[2410717176,3615114731,2482557484,3761060926],"parentGuid":[2365122082,899434690,2179617276,3710057178],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GuideEffectScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---引导系统-客户端表现\r\n---@script Guide effect\r\n---@copyright Lilith Games, Avatar Team\r\n---@author Sid Zhang\r\n\r\nlocal root = script.Parent\r\nlocal tweenerHand = Tween:TweenProperty(root.ImgDot.ImgHand, {Pivot = Vector2(0.35, 1)}, 0.5, Enum.EaseCurve.Linear)\r\nlocal tweenerCircle =\r\n Tween:TweenProperty(root.ImgDot.ImgCircle, {Size = Vector2(276, 276), Alpha = 0}, 0.5, Enum.EaseCurve.Linear)\r\n\r\n::Repeat::\r\n--重置UI位置与状态\r\nroot.ImgDot.ImgHand.Pivot = Vector2(0.35, 1.5)\r\nroot.ImgDot.ImgCircle.Size = Vector2(48, 48)\r\nroot.ImgDot.ImgCircle.Alpha = 1\r\ntweenerHand:Play()\r\ntweenerHand:WaitForComplete()\r\ntweenerCircle:Play()\r\ntweenerCircle:WaitForComplete()\r\ngoto Repeat\r\n"}}]}]},{"ObjectsLinker":[{"class":"cWorkspace","name":"World","guid":[1981988479,3555894,2597800867,69741929],"spaceId":"kDefaultSpace","objectType":0},{"class":"cWorkspace","name":"Global","guid":[344576033,1668630255,2683907297,2226597768],"spaceId":"kDefaultSpace","objectType":0},{"class":"cDataStoreObject","name":"DataStore","guid":[3161908908,3361359298,2468494744,1705162253],"spaceId":"kDefaultSpace","objectType":0},{"class":"cScriptObject","name":"LuaFunctionScript","guid":[2783201338,2636205455,2564339011,1843429949],"spaceId":"kDefaultSpace","objectType":0},{"class":"cScriptObject","name":"ModuleRequireScript","guid":[789428111,3886039363,2156446074,3559120475],"spaceId":"kDefaultSpace","objectType":0},{"class":"cScriptObject","name":"AutoAssignTeamScript","guid":[811022213,2158838612,2670341129,820752393],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"Utility","guid":[626626282,3227665360,2471910562,2840544587],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ModuleUtilModule","guid":[1931854124,2576109163,2694171240,1407146350],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"NetUtilModule","guid":[877830752,869352727,3042692887,2948948613],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"CsvUtilModule","guid":[974164082,3633467084,2781912883,1451329930],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"XlsUtilModule","guid":[2760847323,3965272240,2950769927,3980104073],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"EventUtilModule","guid":[2566228644,2776910007,2667900515,194242277],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"TimeUtilModule","guid":[3212895662,3876605484,2451409624,1852532118],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"LogUtilModule","guid":[1448450812,1700152430,3083838744,455591299],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"CamUtilModule","guid":[1786588942,1014908186,2189044832,747029913],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"UuidModule","guid":[4157188643,3705227052,3158561090,4053995093],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"LuaJsonUtilModule","guid":[1171767027,1956007823,2562650671,2698147667],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ObjPoolUtilModule","guid":[2729864637,1304447132,2322341388,2859273176],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"SoundUtilModule","guid":[375943343,233195050,2377557310,1976802289],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"LinkedListModule","guid":[572803863,1537884583,2902871108,3023603150],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"GlobalFuncModule","guid":[397078907,1337606489,2918347355,1376574677],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"TweenControllerModule","guid":[3340226311,3486273272,3143424947,180252541],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ValueChangeUtilModule","guid":[506887186,2798406641,2460390889,2104928690],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"Framework","guid":[756373066,1240223530,2766704094,915472147],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"FrameworkConfigModule","guid":[3532304481,3064676706,3106629846,811992798],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ServerHeartbeatModule","guid":[4201358790,153109338,3063225923,2088318747],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ClientHeartbeatModule","guid":[2448379167,2886812809,2770381150,2704067643],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ServerModule","guid":[2630836918,4157751681,2512528126,1629800954],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ClientModule","guid":[2395017761,2395361208,3179134946,4246078414],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ServerBaseModule","guid":[2864628079,2098938141,2169623745,1280720590],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ClientBaseModule","guid":[1957111763,3183362511,3175239265,4168800515],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"Plugin","guid":[2555489138,626346364,2851147263,1364204280],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"FUNC_Guide","guid":[684900468,2789166335,2666626870,2188212943],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"GuideSystemModule","guid":[2070445638,524698677,3104412276,3887386575],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"Define","guid":[2737473538,3344386760,2691081356,600893088],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"GlobalDataModule","guid":[1468398307,3695920924,2815319038,3596233864],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ConstModule","guid":[2512024877,1669350945,3212507937,4057137757],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"EventsModule","guid":[3985164048,3143978161,3122137539,183444988],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"ConfigModule","guid":[2110201861,617827622,2622314683,2453149769],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"Module","guid":[1574945571,3461236255,2335300488,1410656948],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"S_Module","guid":[760758812,3011002765,2609222735,3236427035],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"C_Module","guid":[3651921500,3839968525,2765586875,2228388046],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"LanguageUtilModule","guid":[1327910891,3901181274,2716120486,2266956693],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"Cls_Module","guid":[522079265,1772703138,3001358313,3723987968],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"Editor_Module","guid":[1822279021,412438229,2868384553,280931416],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"Xls","guid":[3561931272,2228374286,3075622864,2620868908],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"Example1XlsModule","guid":[1154807438,2590788022,3089941547,2078280727],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"Example2XlsModule","guid":[3966878609,3696577721,2175744138,2596481351],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"GlobalSettingXlsModule","guid":[915862330,741691007,3064296356,1451895238],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"LanguagePackXlsModule","guid":[700825784,2334608193,2463848717,1647971739],"spaceId":"kDefaultSpace","objectType":0},{"class":"cModuleScriptObject","name":"SoundXlsModule","guid":[3708642180,138366140,2150857251,245324002],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"Csv","guid":[294313154,1450328249,2442680246,272497625],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"S_Code","guid":[630163054,4187112824,2753371896,1919329423],"spaceId":"kDefaultSpace","objectType":0},{"class":"cScriptObject","name":"ServerMainScript","guid":[344478647,1240680084,3003146376,138016020],"spaceId":"kDefaultSpace","objectType":0},{"class":"cFolderObject","name":"SpawnLocations","guid":[3060414528,2729002180,2754306229,2661207025],"spaceId":"kDefaultSpace","objectType":0},{"class":"cStartPortal","name":"StartPortal00","guid":[2311234828,1873955616,2655392405,2525362369],"spaceId":"kDefaultSpace","objectType":0},{"class":"cStartPortal","name":"StartPortal01","guid":[2880873389,3573762209,2456263445,1546252123],"spaceId":"kDefaultSpace","objectType":0},{"class":"cStartPortal","name":"StartPortal02","guid":[3258803195,2884980247,2417960272,149547500],"spaceId":"kDefaultSpace","objectType":0},{"class":"cStartPortal","name":"StartPortal03","guid":[4114460255,208751620,2876153555,2633735762],"spaceId":"kDefaultSpace","objectType":0},{"class":"cAudioSource","name":"BGM","guid":[1596674502,2319271992,2900844459,2801180766],"spaceId":"kDefaultSpace","objectType":-842150451},{"class":"cSkydome","name":"Sky","guid":[3956874685,1991789763,3220541584,1456680764],"spaceId":"kDefaultSpace","objectType":-842150451},{"class":"cFolderObject","name":"Players","guid":[3397398191,850542637,3138524115,1333674022],"spaceId":"kDefaultSpace","objectType":0},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[718760084,1257915969,2962466066,2150526306],"spaceId":"kDefaultSpace","objectType":0},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[438081159,4019275113,2394302102,997050187],"spaceId":"kDefaultSpace","objectType":0},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[154289415,229392516,2254790969,287930189],"spaceId":"kDefaultSpace","objectType":0},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[1151332762,2713075851,2532352188,4124889855],"spaceId":"kDefaultSpace","objectType":0},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[784260868,2609333952,2774312238,2440002730],"spaceId":"kDefaultSpace","objectType":0},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[36220398,4069804567,3087648968,1513344952],"spaceId":"kDefaultSpace","objectType":0},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[301805571,3183757047,2970218906,1035586513],"spaceId":"kDefaultSpace","objectType":0},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[329052330,2108966125,2246354006,3727422990],"spaceId":"kDefaultSpace","objectType":0},{"class":"cTerrainObject","name":"Terrain","guid":[980033814,3447341367,2913170761,3546742988],"spaceId":"kDefaultSpace","objectType":0},{"class":"cStaticSpaceFolderObject","name":"StaticSpace","guid":[2324326883,3665710638,3070246021,2579386889],"spaceId":"kDefaultSpace","objectType":0},{"class":"cPrimitiveObject","name":"BaseFloor","guid":[2469667971,3052095412,2449968994,2749907796],"spaceId":"kDefaultSpace","objectType":0}],"ObjectsData":[{"class":"cWorkspace","name":"World","guid":[1981988479,3555894,2597800867,69741929],"parentGuid":[0,0,0,0],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"World"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sWorkspaceComponent","data":{}},{"id":11,"class":"sConfigPhysicsComponent","data":{"m_collisionGroupMaskFlags":[0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}}]},{"class":"cWorkspace","name":"Global","guid":[344576033,1668630255,2683907297,2226597768],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Global"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sWorkspaceComponent","data":{}},{"id":11,"class":"sConfigPhysicsComponent","data":{"m_collisionGroupMaskFlags":[]}}]},{"class":"cDataStoreObject","name":"DataStore","guid":[3161908908,3361359298,2468494744,1705162253],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"DataStore"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":200,"class":"sDataStoreComponent","data":{"m_dataSheet":[],"m_archive":[]}}]},{"class":"cScriptObject","name":"LuaFunctionScript","guid":[2783201338,2636205455,2564339011,1843429949],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LuaFunctionScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 提供一组常用函数,以及对 Lua 标准库的扩展\r\n-- @script Lua function extension libraries\r\n-- @author Lilith Games, Avatar Team\r\n-- @see https://wiki.lilithgames.com/x/tSkMAg\r\n\r\n--- 检查并尝试转换为数值,如果无法转换则返回 0\r\n-- @param mixed value 要检查的值\r\n-- @param [integer base] 进制,默认为十进制\r\n-- @return number\r\nfunction checknumber(value, base)\r\n return tonumber(value, base) or 0\r\nend\r\n\r\n--- 检查是否是有效的number类型\r\n-- @param number\r\nfunction isValidNumber(num)\r\n return num ~= nil and num > 0\r\nend\r\n\r\n--- 检查并尝试转换为整数,如果无法转换则返回 0\r\n-- @param mixed value 要检查的值\r\n-- @return integer\r\nfunction checkint(value)\r\n return math.round(checknumber(value))\r\nend\r\n\r\n--- 检查并尝试转换为布尔值,除了 nil 和 false,其他任何值都会返回 true\r\n-- @param mixed value 要检查的值\r\n-- @return boolean\r\nfunction checkbool(value)\r\n return (value ~= nil and value ~= false)\r\nend\r\n\r\n--- 检查值是否是一个表格,如果不是则返回一个空表格\r\n-- @param mixed value 要检查的值\r\n-- @return table\r\nfunction checktable(value)\r\n if type(value) ~= 'table' then\r\n value = {}\r\n end\r\n return value\r\nend\r\n\r\n--- 处理对象\r\n-- @param mixed obj Lua 对象\r\n-- @param function method 对象方法\r\n-- @return function\r\nfunction handler(obj, method)\r\n return function(...)\r\n return method(obj, ...)\r\n end\r\nend\r\n\r\n--- 计算表格包含的字段数量\r\n-- Lua table 的 \"#\" 操作只对依次排序的数值下标数组有效,table.nums() 则计算 table 中所有不为 nil 的值的个数。\r\n-- @param table\r\nfunction table.nums(t)\r\n if t == nil then\r\n return 0\r\n end\r\n local count = 0\r\n for _ in pairs(t) do\r\n count = count + 1\r\n end\r\n return count\r\nend\r\n\r\n--- 返回指定表格中的所有键\r\n-- @param k-v table\r\n-- @return keys' table\r\n-- @usage example\r\n-- local hashtable = {a = 1, b = 2, c = 3}\r\n-- local keys = table.keys(hashtable)\r\n-- >> keys = {\"a\", \"b\", \"c\"}\r\nfunction table.keys(hashtable)\r\n local keys = {}\r\n for k, _ in pairs(hashtable) do\r\n table.insert(keys, k)\r\n end\r\n return keys\r\nend\r\n\r\n--- 返回指定表格中的所有值\r\n-- @param k-v table\r\n-- @return values' table\r\n-- @usage example\r\n-- local hashtable = {a = 1, b = 2, c = 3}\r\n-- local values = table.values(hashtable)\r\n-- >> values = {1, 2, 3}\r\nfunction table.values(hashtable)\r\n local values = {}\r\n local i = 1\r\n for k, v in pairs(hashtable) do\r\n values[i] = v\r\n i = i + 1\r\n end\r\n return values\r\nend\r\n\r\n--- 将来源表格中所有键及其值复制到目标表格对象中,如果存在同名键,则覆盖其值\r\n-- @param target table\r\n-- @param source table\r\n-- @usage example\r\n-- local dest = {a = 1, b = 2}\r\n-- local src = {c = 3, d = 4}\r\n-- table.merge(dest, src)\r\n-- >> dest = {a = 1, b = 2, c = 3, d = 4}\r\nfunction table.merge(dest, src)\r\n for k, v in pairs(src) do\r\n dest[k] = v\r\n end\r\nend\r\n\r\n--- 深度将来源表格中所有键及其值复制到目标表格对象中,如果存在同名键,则覆盖其值,如果存在子表,则遍历子表进行复制\r\nfunction table.deepMerge(dest, src)\r\n for k, v in pairs(src) do\r\n if type(v) == 'table' then\r\n if dest[k] == nil then\r\n dest[k] = {}\r\n end\r\n table.deepMerge(dest[k], v)\r\n else\r\n dest[k] = v\r\n end\r\n end\r\nend\r\n\r\n--- 将来源表格中所有键及其值复制到目标表格对象中,如果存在同名键,则覆盖其值\r\n-- @param ... 多个表,第一个是目标表格\r\n-- @return 返回一个新表\r\n---@author Sharif Ma\r\nfunction table.MergeTables(...)\r\n local tabs = {...}\r\n if not tabs or #tabs == 0 then\r\n return {}\r\n end\r\n local origin = {}\r\n for k, v in pairs(tabs[1]) do\r\n origin[k] = v\r\n end\r\n for i = 2, #tabs do\r\n if origin then\r\n if tabs[i] then\r\n for _, v in pairs(tabs[i]) do\r\n table.insert(origin, v)\r\n end\r\n end\r\n else\r\n origin = tabs[i]\r\n end\r\n end\r\n return origin\r\nend\r\n\r\n--- 在目标表格的指定位置插入来源表格,如果没有指定位置则连接两个表格\r\n-- @param target table\r\n-- @param source table\r\n-- @param start index\r\n-- @usage example #1\r\n-- local dest = {1, 2, 3}\r\n-- local src = {4, 5, 6}\r\n-- table.insertto(dest, src)\r\n-- >> dest = {1, 2, 3, 4, 5, 6}\r\n-- @usage example #2\r\n-- local dest = {1, 2, 3}\r\n-- local src = {4, 5, 6}\r\n-- table.insertto(dest, src, 5)\r\n-- >> dest = {1, 2, 3, nil, 4, 5, 6}\r\nfunction table.insertto(dest, src, begin)\r\n if begin == nil then\r\n begin = #dest + 1\r\n else\r\n begin = checkint(begin)\r\n if begin <= 0 then\r\n begin = #dest + 1\r\n end\r\n end\r\n\r\n local len = #src\r\n for i = 0, len - 1 do\r\n dest[i + begin] = src[i + 1]\r\n end\r\nend\r\n\r\n--- 从表格中查找指定值,返回其索引,如果没找到返回 false\r\n-- @param array table\r\n-- @param target value\r\n-- @param start index\r\n-- @return index or false\r\n-- @usage example\r\n-- local array = {\"a\", \"b\", \"c\"}\r\n-- print(table.indexof(array, \"b\"))\r\n-- >> 2\r\nfunction table.indexof(array, value, begin)\r\n if array ~= nil then\r\n for i = begin or 1, #array do\r\n if array[i] == value then\r\n return i\r\n end\r\n end\r\n end\r\n return 0\r\nend\r\n\r\n--- 检查表格中是否存在指定值\r\n-- @param array table\r\n-- @param target value\r\n-- @return @boolean\r\nfunction table.exists(array, value)\r\n return table.indexof(array, value) > 0\r\nend\r\n\r\n--- 清空数组表格\r\n-- @param array table\r\nfunction table.cleararray(array)\r\n if array ~= nil then\r\n local count = #array\r\n while count > 0 do\r\n table.remove(array, count)\r\n count = #array\r\n end\r\n end\r\nend\r\n\r\n--- 清空k-v表格\r\n-- @param k-v table\r\nfunction table.clearhashtable(hashtable)\r\n if hashtable ~= nil then\r\n for k, v in pairs(hashtable) do\r\n hashtable[k] = nil\r\n end\r\n end\r\nend\r\n\r\n--- 清空表格\r\n-- @param table\r\n-- @see table.clearhashtable\r\nfunction table.cleartable(t)\r\n table.clearhashtable(t)\r\nend\r\n\r\n--- 截取Array其中一段,startIndex从1开始 return截取后的新数组\r\n-- @param table array table\r\n-- @param @number start index\r\n-- @param @number length\r\n-- @return @table array table\r\n-- @usage example\r\n-- local array = {\"a\", \"b\", \"c\", \"d\"}\r\n-- print(table.subArray(array, 2, 2))\r\n-- >> {\"b\", \"c\"}\r\nfunction table.subArray(array, startIndex, length)\r\n if array ~= nil then\r\n local count = table.nums(array)\r\n local tempArray = array\r\n array = {}\r\n if startIndex <= count then\r\n local maxlength = count - startIndex + 1\r\n length = length > maxlength and maxlength or length\r\n local endIndex = startIndex + length - 1\r\n for i = startIndex, endIndex do\r\n table.insert(array, tempArray[i])\r\n end\r\n end\r\n end\r\n return array\r\nend\r\n\r\n--- 截取Array的后半段,startIndex从1开始 return截取后的新数组\r\n-- @param table array table\r\n-- @param @number start index\r\n-- @return @table array table\r\nfunction table.subArrayByStartIndex(array, startIndex)\r\n if array ~= nil then\r\n local count = table.nums(array)\r\n local length = count - startIndex + 1\r\n return table.subArray(array, startIndex, length)\r\n end\r\n return array\r\nend\r\n\r\n--- 从表格中查找指定值,返回其 key,如果没找到返回 nil\r\n-- @param table hash table\r\n-- @param any value\r\n-- @return key of value\r\n-- @usage\r\n-- local hashtable = {name = \"dualface\", comp = \"chukong\"}\r\n-- print(table.keyof(hashtable, \"chukong\"))\r\n-- >> comp\r\nfunction table.keyof(hashtable, value)\r\n for k, v in pairs(hashtable) do\r\n if v == value then\r\n return k\r\n end\r\n end\r\n return nil\r\nend\r\n\r\n--- 从表格中删除指定值,返回删除的值的个数\r\n-- @usage\r\n-- local array = {\"a\", \"b\", \"c\", \"c\"}\r\n-- print(table.removebyvalue(array, \"c\", true))\r\n-- >> 输出 2\r\nfunction table.removebyvalue(array, value, removeall)\r\n local c, i, max = 0, 1, #array\r\n while i <= max do\r\n if array[i] == value then\r\n table.remove(array, i)\r\n c = c + 1\r\n i = i - 1\r\n max = max - 1\r\n if not removeall then\r\n break\r\n end\r\n end\r\n i = i + 1\r\n end\r\n return c\r\nend\r\n\r\n--- 数组混淆\r\nfunction table.shuffle(_tbl)\r\n local j\r\n for i = #_tbl, 2, -1 do\r\n j = math.random(i)\r\n _tbl[i], _tbl[j] = _tbl[j], _tbl[i]\r\n end\r\n return _tbl\r\nend\r\n\r\n--- 对表格中每一个值执行一次指定的函数,并用函数返回值更新表格内容\r\n-- @param table\r\n-- @param function fn 参数指定的函数具有两个参数,并且返回一个值。原型如下:\r\n-- function map_function(value, key)\r\n-- return value\r\n-- end\r\n-- @usage\r\n-- local t = {name = \"dualface\", comp = \"chukong\"}\r\n-- table.map(t, function(v, k)\r\n-- -- 在每一个值前后添加括号\r\n-- return \"[\" .. v .. \"]\"\r\n-- end)\r\n-- 输出修改后的表格内容\r\n-- for k, v in pairs(t) do\r\n-- print(k, v)\r\n-- end\r\n-- >> 输出\r\n-- name [dualface]\r\n-- comp [chukong]\r\nfunction table.map(t, fn)\r\n for k, v in pairs(t) do\r\n t[k] = fn(v, k)\r\n end\r\nend\r\n\r\n--- 对表格中每一个值执行一次指定的函数,但不改变表格内容\r\n-- @param table\r\n-- @param function fn 参数指定的函数具有两个参数,没有返回值。原型如下:\r\n-- function map_function(value, key)\r\n-- -- no return here\r\n-- end\r\n-- @usage\r\n-- local t = {name = \"dualface\", comp = \"chukong\"}\r\n-- table.walk(t, function(v, k)\r\n-- -- 输出每一个值\r\n-- print(v)\r\n-- end)\r\nfunction table.walk(t, fn)\r\n for k, v in pairs(t) do\r\n fn(v, k)\r\n end\r\nend\r\n\r\n--- 对表格中每一个值执行一次指定的函数,如果该函数返回 false,则对应的值会从表格中删除\r\n-- @param table\r\n-- @param function fn 参数指定的函数具有两个参数,并且返回一个 boolean 值。原型如下:\r\n-- !!!!该方法有局限性,执行后会修改原表格t中值\r\n-- function map_function(value, key)\r\n-- return true or false\r\n-- end\r\n-- @usage\r\n-- local t = {name = \"dualface\", comp = \"chukong\"}\r\n-- table.filter(t, function(v, k)\r\n-- return v ~= \"dualface\" -- 当值等于 dualface 时过滤掉该值\r\n-- end)\r\n-- 输出修改后的表格内容\r\n-- for k, v in pairs(t) do\r\n-- print(k, v)\r\n-- end\r\n-- >> 输出 comp chukong\r\nfunction table.filter(t, fn)\r\n for k, v in pairs(t) do\r\n if not fn(v, k) then\r\n t[k] = nil\r\n end\r\n end\r\nend\r\n\r\n--- 找到表格中每个符合matchFunc的条目\r\n-- @param array table\r\n-- @param match function, return T/F\r\n-- @return all elements matched, default is {}\r\nfunction table.findAll(array, matchFunc)\r\n local ret, idx = {}, 1\r\n for i = 1, #array do\r\n if matchFunc(array[i]) then\r\n ret[idx] = array[i]\r\n idx = idx + 1\r\n end\r\n end\r\n return ret\r\nend\r\n\r\n--- 找到表格中每个符合matchFunc的条目,并执行walkFunc\r\n-- @param array table\r\n-- @param match function, return T/F\r\n-- @param walk function\r\nfunction table.findAllAndWalk(array, matchFunc, walkFunc)\r\n for i = 1, #array do\r\n if matchFunc(array[i]) then\r\n walkFunc(array[i])\r\n end\r\n end\r\nend\r\n\r\n--- 在表格中插入一个新值\r\n-- @param array table\r\n-- @param new element\r\nfunction table.insert_once(T, elem)\r\n for _, v in ipairs(T) do\r\n if v == elem then\r\n return\r\n end\r\n end\r\n table.insert(T, elem)\r\nend\r\n\r\n--- 遍历表格,确保其中的值唯一\r\n-- @function [parent=#table] unique\r\n-- @param table t 表格\r\n-- @param boolean bArray t是否是数组,是数组,t中重复的项被移除后,后续的项会前移\r\n-- @return table#table 包含所有唯一值的新表格\r\n-- @usage\r\n-- 遍历表格,确保其中的值唯一\r\n-- local t = {\"a\", \"a\", \"b\", \"c\"} -- 重复的 a 会被过滤掉\r\n-- local n = table.unique(t)\r\n-- for k, v in pairs(n) do\r\n-- print(v)\r\n-- end\r\n-- >> 输出 a b c\r\nfunction table.unique(t, bArray)\r\n local check = {}\r\n local n = {}\r\n local idx = 1\r\n for k, v in pairs(t) do\r\n if not check[v] then\r\n if bArray then\r\n n[idx] = v\r\n idx = idx + 1\r\n else\r\n n[k] = v\r\n end\r\n check[v] = true\r\n end\r\n end\r\n return n\r\nend\r\n\r\n--- table 深度复制\r\n-- @param table\r\n-- @return a net table with same data\r\nfunction table.deepcopy(object)\r\n local lookup_table = {}\r\n local function _copy(object)\r\n if type(object) ~= 'table' then\r\n return object\r\n elseif lookup_table[object] then\r\n return lookup_table[object]\r\n end\r\n local new_table = {}\r\n lookup_table[object] = new_table\r\n for key, value in pairs(object) do\r\n new_table[_copy(key)] = _copy(value)\r\n end\r\n return setmetatable(new_table, getmetatable(object))\r\n end\r\n return _copy(object)\r\nend\r\n\r\n--- table 浅度复制(不处理metatable)\r\nfunction table.shallowcopy(orig)\r\n local orig_type = type(orig)\r\n local copy\r\n if orig_type == 'table' then\r\n copy = {}\r\n for orig_key, orig_value in next, orig, nil do\r\n copy[table.shallowcopy(orig_key)] = table.shallowcopy(orig_value)\r\n end\r\n else\r\n copy = orig\r\n end\r\n return copy\r\nend\r\n\r\n--- 获取or创建一个子表\r\nfunction table.need(tb, key)\r\n if type(tb) == 'table' then\r\n local subTb = tb[key]\r\n if subTb == nil then\r\n subTb = {}\r\n tb[key] = subTb\r\n end\r\n return subTb\r\n end\r\n return\r\nend\r\n\r\n--- 打印table中的所有内容\r\n-- @param data table\r\n-- @param @boolean showMetatable 是否显示元表\r\nfunction table.dump(data, showMetatable)\r\n local result, tab = {}, ' '\r\n local function _dump(data, showMetatable, lastCount)\r\n if type(data) ~= 'table' then\r\n if type(data) == 'string' then\r\n table.insert(result, '\"')\r\n table.insert(result, data)\r\n table.insert(result, '\"')\r\n else\r\n table.insert(result, tostring(data))\r\n end\r\n else\r\n --Format\r\n local count = lastCount or 0\r\n count = count + 1\r\n table.insert(result, '{\\n')\r\n --Metatable\r\n if showMetatable then\r\n for i = 1, count do\r\n table.insert(result, tab)\r\n end\r\n local mt = getmetatable(data)\r\n table.insert(result, '\"__metatable\" = ')\r\n _dump(mt, showMetatable, count)\r\n table.insert(result, ',\\n')\r\n end\r\n --Key\r\n for key, value in pairs(data) do\r\n for i = 1, count do\r\n table.insert(result, tab)\r\n end\r\n if type(key) == 'string' then\r\n table.insert(result, '\"')\r\n table.insert(result, key)\r\n table.insert(result, '\" = ')\r\n elseif type(key) == 'number' then\r\n table.insert(result, '[')\r\n table.insert(result, key)\r\n table.insert(result, '] = ')\r\n else\r\n table.insert(result, tostring(key))\r\n end\r\n _dump(value, showMetatable, count)\r\n table.insert(result, ',\\n')\r\n end\r\n --Format\r\n for i = 1, lastCount or 0 do\r\n table.insert(result, tab)\r\n end\r\n table.insert(result, '}')\r\n end\r\n --Format\r\n if not lastCount then\r\n table.insert(result, '\\n')\r\n end\r\n end\r\n _dump(data, showMetatable, 0)\r\n\r\n -- print('dump: \\n' .. table.concat(result))\r\n return 'dump: \\n' .. table.concat(result)\r\nend\r\n\r\n--- 用指定字符或字符串分割输入字符串,返回包含分割结果的数组\r\n-- @param @string input 输入的字符串\r\n-- @param @string delimiter 分隔符\r\n-- @return array\r\n-- @usage example #1\r\n-- local input = \"Hello,World\"\r\n-- local res = string.split(input, \",\")\r\n-- >> res = {\"Hello\", \"World\"}\r\n-- @usage example #2\r\n-- local input = \"Hello-+-World-+-Quick\"\r\n-- local res = string.split(input, \"-+-\")\r\n-- >> res = {\"Hello\", \"World\", \"Quick\"}\r\nfunction string.split(input, delimiter)\r\n input = tostring(input)\r\n delimiter = tostring(delimiter)\r\n if (delimiter == '') then\r\n return false\r\n end\r\n local pos, arr = 0, {}\r\n -- for each divider found\r\n for st, sp in function()\r\n return string.find(input, delimiter, pos, true)\r\n end do\r\n table.insert(arr, string.sub(input, pos, st - 1))\r\n pos = sp + 1\r\n end\r\n table.insert(arr, string.sub(input, pos))\r\n return arr\r\nend\r\n\r\n--- 判断字符串是否为空或者长度为零\r\n-- @param @string 输入的字符串\r\nfunction string.isnilorempty(inputStr)\r\n return inputStr == nil or inputStr == ''\r\nend\r\n\r\n--- 去除输入字符串头部的空白字符,返回结果\r\n-- @param @string input\r\n-- @return @string\r\n-- @usage example\r\n-- local input = \" ABC\"\r\n-- print(string.ltrim(input))\r\n-- >> 输出 ABC,输入字符串前面的两个空格被去掉了\r\n-- 空白字符包括:\r\n-- - 空格\r\n-- - 制表符 \\t\r\n-- - 换行符 \\n\r\n-- - 回到行首符 \\r\r\nfunction string.ltrim(input)\r\n return string.gsub(input, '^[ \\t\\n\\r]+', '')\r\nend\r\n\r\n--- 去除输入字符串尾部的空白字符,返回结果\r\n-- @param @string input\r\n-- @return @string\r\n-- @usage example\r\n-- local input = \"ABC \"\r\n-- print(string.rtrim(input))\r\n-- >> 输出 ABC,输入字符串最后的两个空格被去掉了\r\nfunction string.rtrim(input)\r\n return string.gsub(input, '[ \\t\\n\\r]+$', '')\r\nend\r\n\r\n--- 去掉字符串首尾的空白字符,返回结果\r\n-- @param @string input\r\n-- @return @string\r\nfunction string.trim(input)\r\n input = string.gsub(input, '^[ \\t\\n\\r]+', '')\r\n return string.gsub(input, '[ \\t\\n\\r]+$', '')\r\nend\r\n\r\n--- 将字符串的第一个字符转为大写,返回结果\r\n-- @param @string input\r\n-- @return @string\r\n-- @usage example\r\n-- local input = \"hello\"\r\n-- print(string.ucfirst(input))\r\n-- >> 输出 Hello\r\nfunction string.ucfirst(input)\r\n return string.upper(string.sub(input, 1, 1)) .. string.sub(input, 2)\r\nend\r\n\r\nfunction string.firstToUpper(str)\r\n return (str:gsub('^%l', string.upper))\r\nend\r\n\r\n--- 计算 UTF8 字符串的长度,每一个中文算一个字符\r\n-- @param @string input\r\n-- @return @number cnt\r\n-- @usage example\r\n-- local input = \"你好World\"\r\n-- print(string.utf8len(input))\r\n-- >> 输出 7\r\nfunction string.utf8len(input)\r\n local len = string.len(input)\r\n local left = len\r\n local cnt = 0\r\n local arr = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}\r\n while left ~= 0 do\r\n local tmp = string.byte(input, -left)\r\n local i = #arr\r\n while arr[i] do\r\n if tmp >= arr[i] then\r\n left = left - i\r\n break\r\n end\r\n i = i - 1\r\n end\r\n cnt = cnt + 1\r\n end\r\n return cnt\r\nend\r\n\r\n--- 替换字符串内容\r\n-- @param @string input\r\n-- @param @number start index\r\n-- @param new context\r\n-- @return a new string\r\nfunction string.replace(str, index, char)\r\n return table.concat {str:sub(1, index - 1), char, str:sub(index + 1)}\r\nend\r\n\r\n--- 检查字符串是否为指定字符串开头\r\n-- @param @string target\r\n-- @param @string start\r\n-- @return @boolean\r\nfunction string.startswith(str, start)\r\n return str:sub(1, #start) == start\r\nend\r\n\r\n--- 检查字符串是否以指定字符串结尾\r\n-- @param @string target\r\n-- @param @string start\r\n-- @return @boolean\r\nfunction string.endswith(str, ending)\r\n return ending == '' or str:sub(-(#ending)) == ending\r\nend\r\n\r\n--- 四舍五入\r\n-- @param a number\r\n-- @return a round number\r\nfunction math.round(value)\r\n return math.floor(value + 0.5)\r\nend\r\n\r\n--- [0, 1]区间限定函数\r\n-- @param a number\r\n-- @return a clamped number\r\nfunction math.clamp01(value)\r\n return math.min(1, math.max(0, value))\r\nend\r\n\r\n---最小数值和最大数值指定返回值的范围\r\n-- @param a number\r\n-- @param min threshold\r\n-- @param max threshold\r\n-- @return a clamped number\r\nfunction math.Clamp(value, min, max)\r\n if value < min then\r\n return min\r\n end\r\n if value > max then\r\n return max\r\n end\r\n return value\r\nend\r\n\r\n--- 高斯岁间变量\r\nfunction math.GaussRandom()\r\n local u = math.random()\r\n local v = math.random()\r\n local z = math.sqrt(-2 * math.log(u)) * math.cos(2 * math.pi * v)\r\n z = (z + 3) / 6\r\n z = 2 * z - 1\r\n if (math.abs(z) > 1) then\r\n return math.GaussRandom()\r\n end\r\n return z\r\nend\r\n\r\n--- 数据结构 队列\r\n-- @usage queue example\r\n-- local myQueue = Queue:New()\r\n-- myQueue:Enqueue('a')\r\n-- myQueue:Enqueue('b')\r\n-- myQueue:Enqueue('c')\r\n-- myQueue:PrintElement()\r\n-- print(myQueue:Dequeue())\r\n-- myQueue:PrintElement()\r\n-- myQueue:Clear()\r\n-- myQueue:PrintElement()\r\nQueue = {}\r\nfunction Queue:New()\r\n local inst = {\r\n _first = -1,\r\n _last = -1,\r\n _size = 0,\r\n _queue = {}\r\n }\r\n setmetatable(inst, {__index = self})\r\n return inst\r\nend\r\n\r\nfunction Queue:IsEmpty()\r\n if self._size == 0 then\r\n return true\r\n end\r\n return false\r\nend\r\n\r\nfunction Queue:Enqueue(inElement)\r\n if self._size == 0 then\r\n self._first = 0\r\n self._last = 1\r\n self._size = 1\r\n self._queue[self._last] = inElement\r\n else\r\n self._last = self._last + 1\r\n self._queue[self._last] = inElement\r\n self._size = self._size + 1\r\n end\r\nend\r\n\r\nfunction Queue:Dequeue()\r\n if self:IsEmpty() then\r\n print('Error: the queue is empty')\r\n return\r\n end\r\n self._size = self._size - 1\r\n self._first = self._first + 1\r\n local value = self._queue[self._first]\r\n return value\r\nend\r\n\r\nfunction Queue:Clear()\r\n self._queue = nil\r\n self._queue = {}\r\n self._size = 0\r\n self._first = -1\r\n self._last = -1\r\nend\r\n\r\nfunction Queue:Size()\r\n return self._size or 0\r\nend\r\n\r\nfunction Queue:PrintElement()\r\n if self._size == 0 then\r\n print('{}')\r\n else\r\n local f = self._first + 1\r\n local l = self._last\r\n local str\r\n local flag = true\r\n while f ~= l do\r\n if flag == true then\r\n str = '{' .. tostring(self._queue[f])\r\n f = f + 1\r\n flag = false\r\n else\r\n str = str .. ',' .. tostring(self._queue[f])\r\n f = f + 1\r\n end\r\n end\r\n str = str .. ',' .. tostring(self._queue[l]) .. '}'\r\n print(str)\r\n end\r\nend\r\n\r\nfunction Queue:GetValue(index)\r\n if self:IsEmpty() or index == nil or index == 0 then\r\n print('Error: Get Value Failure!')\r\n return\r\n end\r\n if index > 0 then\r\n return self._queue[self._first + index]\r\n else\r\n return self._queue[self._last + index + 1]\r\n end\r\nend\r\n\r\nfunction Queue:GetValues()\r\n if self:IsEmpty() then\r\n return\r\n end\r\n local data = {}\r\n for i = self._first + 1, self._last, 1 do\r\n data[#data + 1] = self._queue[i]\r\n end\r\n return data\r\nend\r\n\r\n--- 数据结构 栈\r\n-- @usage example\r\n-- local myStack = Stack:New()\r\n-- myStack:Push(\"a\")\r\n-- myStack:Push(\"b\")\r\n-- myStack:Push(\"c\")\r\n-- myStack:PrintElement()\r\n-- print(myStack:Pop())\r\n-- myStack:PrintElement()\r\n-- myStack:Clear()\r\n-- myStack:PrintElement()\r\nStack = {}\r\nfunction Stack:New()\r\n local inst = {\r\n _last = 0,\r\n _stack = {}\r\n }\r\n setmetatable(inst, {__index = self})\r\n\r\n return inst\r\nend\r\n\r\nfunction Stack:IsEmpty()\r\n if self._last == 0 then\r\n return true\r\n end\r\n return false\r\nend\r\n\r\nfunction Stack:Push(inElement)\r\n self._last = self._last + 1\r\n self._stack[self._last] = inElement\r\nend\r\n\r\nfunction Stack:Pop()\r\n if self:IsEmpty() then\r\n --print(\"Error: the stack is empty\")\r\n return\r\n end\r\n local value = self._stack[self._last]\r\n self._stack[self._last] = nil\r\n self._last = self._last - 1\r\n return value\r\nend\r\n\r\nfunction Stack:Exists(element, compairFunc)\r\n if compairFunc == nil then\r\n compairFunc = function(a, b)\r\n return a == b\r\n end\r\n end\r\n for i = self._last, 1, -1 do\r\n if compairFunc(element, self._stack[i]) then\r\n return i\r\n end\r\n end\r\n return -1\r\nend\r\n\r\nfunction Stack:RemoveAt(index)\r\n if index < 1 or index > self._last then\r\n return\r\n end\r\n table.remove(self._stack, index)\r\n self._last = self._last - 1\r\nend\r\n\r\nfunction Stack:Clear()\r\n self._stack = nil\r\n self._stack = {}\r\n self._last = 0\r\nend\r\n\r\nfunction Stack:Size()\r\n return self._last\r\nend\r\n\r\nfunction Stack:PrintElement()\r\n local str = '{'\r\n for i = self._last, 1, -1 do\r\n str = str .. tostring(self._stack[i]) .. ','\r\n end\r\n str = str .. '}'\r\n print(str)\r\nend\r\n"}}]},{"class":"cScriptObject","name":"ModuleRequireScript","guid":[789428111,3886039363,2156446074,3559120475],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ModuleRequireScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 将Global.Module目录下每一个用到模块提前require,定义为全局变量\r\n-- @script Module Defines\r\n-- @copyright Lilith Games, Avatar Team\r\n\r\n-- Utilities\r\nModuleUtil = require(Utility.ModuleUtilModule)\r\nLuaJsonUtil = require(Utility.LuaJsonUtilModule)\r\nNetUtil = require(Utility.NetUtilModule)\r\nCsvUtil = require(Utility.CsvUtilModule)\r\nXlsUtil = require(Utility.XlsUtilModule)\r\nEventUtil = require(Utility.EventUtilModule)\r\nUUID = require(Utility.UuidModule)\r\nTweenController = require(Utility.TweenControllerModule)\r\nGlobalFunc = require(Utility.GlobalFuncModule)\r\nLinkedList = Utility.LinkedListModule\r\nValueChangeUtil = require(Utility.ValueChangeUtilModule)\r\nTimeUtil = require(Utility.TimeUtilModule)\r\nTimeUtil.Init()\r\n\r\n-- Framework\r\nModuleUtil.LoadModules(Framework)\r\n\r\n-- Globle Defines, Server and Clinet Modules\r\nModuleUtil.LoadModules(Define)\r\nModuleUtil.LoadXlsModules(Xls, Config)\r\nModuleUtil.LoadModules(Module.S_Module)\r\nModuleUtil.LoadModules(Module.C_Module)\r\nModuleUtil.LoadModules(Module.Cls_Module)\r\n\r\n-- Plugin Modules\r\nGuideSystem = require(world.Global.Plugin.FUNC_Guide.GuideSystemModule)\r\n"}}]},{"class":"cScriptObject","name":"AutoAssignTeamScript","guid":[811022213,2158838612,2670341129,820752393],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"AutoAssignTeamScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 玩家加入\r\n-- @script Auto assign\r\n-- @copyright Lilith Games, Avatar Team\r\n\r\n--- 编辑器默认方法\r\n-- run once when script init\r\nfunction autoAssign()\r\n local container = world:FindTeams()\r\n local min = 1\r\n local teamTojoin = {}\r\n local playerfolder = world.Players\r\n for i = 1, #container, 1 do\r\n if container[i].CurrentMaxMemberNum > 0 then\r\n temp = container[i].CurrentMemberNum / (container[i].CurrentMaxMemberNum)\r\n if (temp < min and temp ~= 1) then\r\n teamTojoin = {}\r\n min = temp\r\n table.insert(teamTojoin, container[i])\r\n elseif temp == min and temp ~= 1 then\r\n table.insert(teamTojoin, container[i])\r\n end\r\n end\r\n end\r\n local a = 1\r\n if #teamTojoin > 0 then\r\n a = math.random(1, #teamTojoin)\r\n return teamTojoin[a]\r\n else\r\n return nil\r\n end\r\nend\r\n"}}]},{"class":"cFolderObject","name":"Utility","guid":[626626282,3227665360,2471910562,2840544587],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Utility"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"ModuleUtilModule","guid":[1931854124,2576109163,2694171240,1407146350],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ModuleUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 模块工具\r\n-- @module Module utilities\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\n\r\nlocal ModuleUtil = {}\r\n\r\n--- 加载模块目录\r\n-- @param _root 模块目录的节点\r\n-- @param _scope 载入后脚本的作用域\r\nfunction ModuleUtil.LoadModules(_root, _scope)\r\n _scope = _scope or _G\r\n assert(_root, '[ModuleUtil] Node does NOT exist!')\r\n local tmp = _root:GetChildren()\r\n for _, v in pairs(tmp) do\r\n name = (v.Name):gsub('Module', '')\r\n -- print('[ModuleUtil] Load: ' .. name)\r\n _scope[name] = require(v)\r\n end\r\nend\r\n\r\n--- 加载XLS表格目录\r\n-- @param _root 模块目录的节点\r\nfunction ModuleUtil.LoadXlsModules(_root, _config)\r\n assert(_root, '[ModuleUtil] Node does NOT exist!')\r\n assert(_config, '[ModuleUtil] Config does NOT exist!')\r\n local tmp = _root:GetChildren()\r\n for _, v in pairs(tmp) do\r\n name = (v.Name):gsub('XlsModule', '')\r\n print('[ModuleUtil] Load: ' .. name)\r\n _config[name] = require(v)\r\n end\r\nend\r\n\r\n--- 加载多个模块目录\r\nfunction ModuleUtil.LoadAllModules(...)\r\n local args = table.pack(...)\r\n for i = 1, args.n do\r\n if args[i] then\r\n ModuleUtil.LoadModules(args[i])\r\n end\r\n end\r\nend\r\n\r\n--- 将有包含特定方法的模块筛选出来,并放在一个table中\r\n-- @param _root 模块目录的节点\r\n-- @param @string _fn 方法名 function_name\r\n-- @param @table _list 存放的table\r\nfunction ModuleUtil.GetModuleListWithFunc(_root, _fn, _list)\r\n assert(_root, '[ModuleUtil] Node does NOT exist!')\r\n assert(not string.isnilorempty(_fn), '[ModuleUtil] Function name is nil or empty!')\r\n assert(_list, '[ModuleUtil] List is NOT initialized!')\r\n local tmp, name = _root:GetChildren()\r\n for _, v in pairs(tmp) do\r\n name = (v.Name):gsub('Module', '')\r\n if _G[name] and _G[name][_fn] and type(_G[name][_fn]) == 'function' then\r\n table.insert(_list, _G[name])\r\n end\r\n end\r\nend\r\n\r\n--- 新建一个模块实例(ServerBase or ClientBase)\r\nfunction ModuleUtil.New(_name, _baseClass)\r\n local t = class(_name, _baseClass)\r\n return t, t:GetSelf()\r\nend\r\n\r\nreturn ModuleUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"NetUtilModule","guid":[877830752,869352727,3042692887,2948948613],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"NetUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 网路工具/事件工具\r\n-- @module Network utilities\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Sharif Ma, Yuancheng Zhang, Yen Yuan\r\nlocal NetUtil = {}\r\n\r\n-- 格式化事件参数, Table=>JSON\r\nlocal FormatArgs\r\n\r\n--! 事件参数校验, true:开启校验\r\nlocal valid, ValidateArgs = true\r\n\r\n--! 打印事件日志, true:开启打印\r\nlocal showLog, PrintEventLog = false\r\n\r\nlocal FireEnum = {\r\n SERVER = 1,\r\n CLIENT = 2,\r\n BROADCAST = 3\r\n}\r\n\r\n--! 外部接口\r\n\r\n--- 向服务器发送消息\r\n-- @param @string _eventName 事件的名字(严格对应)\r\n-- @param ... 事件参数\r\nfunction NetUtil.Fire_S(_eventName, ...)\r\n ValidateArgs(FireEnum.SERVER, _eventName)\r\n local args = {...}\r\n world.S_Event[_eventName]:Fire(table.unpack(args))\r\n PrintEventLog(FireEnum.SERVER, _eventName, nil, args)\r\nend\r\n\r\n--- 向指定的玩家发送消息\r\n-- @param @string _eventName 事件的名字\r\n-- @param _player 玩家对象\r\n-- @param ... 事件参数\r\nfunction NetUtil.Fire_C(_eventName, _player, ...)\r\n\tif(_player == nil) then\r\n\t\treturn\r\n\tend\r\n ValidateArgs(FireEnum.CLIENT, _eventName, _player)\r\n local args = {...}\r\n _player.C_Event[_eventName]:Fire(table.unpack(args))\r\n PrintEventLog(FireEnum.CLIENT, _eventName, _player, args)\r\nend\r\n\r\n--- 客户端广播\r\n-- @param @string _eventName 事件的名字(严格对应)\r\n-- @param ... 事件参数\r\nfunction NetUtil.Broadcast(_eventName, ...)\r\n ValidateArgs(FireEnum.BROADCAST, _eventName, ...)\r\n local args = {...}\r\n world.Players:BroadcastEvent(_eventName, table.unpack(args))\r\n PrintEventLog(FireEnum.BROADCAST, _eventName, nil, args)\r\nend\r\n\r\n--! 私有函数\r\n\r\n--- 格式化事件参数\r\nFormatArgs = function(...)\r\n local args = {...}\r\n for k, v in pairs(args) do\r\n if type(v) == 'table' then\r\n args[k] = string.format('JSON%sJSON', LuaJsonUtil:encode(v))\r\n end\r\n end\r\n return args\r\nend\r\n\r\n--! 辅助功能\r\n\r\n--- 事件参数校验\r\nValidateArgs =\r\n valid and\r\n function(_fireEnum, _eventName, _player)\r\n if _fireEnum == FireEnum.SERVER then\r\n --! Fire_S 检查参数\r\n assert(not string.isnilorempty(_eventName), '[NetUtil][Fire_S] 事件名为空')\r\n assert(world.S_Event[_eventName], string.format('[NetUtil][Fire_S] 服务器不存在事件: %s', _eventName))\r\n elseif _fireEnum == FireEnum.CLIENT then\r\n --! Fire_C 检查参数\r\n assert(not string.isnilorempty(_eventName), '[NetUtil] 事件名为空')\r\n assert(\r\n _player and _player.ClassName == 'PlayerInstance',\r\n string.format('[NetUtil][Fire_C]第2个参数需要是玩家对象, 错误事件: %s', _eventName)\r\n )\r\n assert(\r\n _player.C_Event[_eventName],\r\n string.format('[NetUtil][Fire_C] 客户端玩家不存在事件: %s, 玩家: %s', _player.Name, _eventName)\r\n )\r\n elseif _fireEnum == FireEnum.BROADCAST then\r\n --! Broadcase 检查参数\r\n assert(not string.isnilorempty(_eventName), '[NetUtil][Broadcast] 事件名为空')\r\n end\r\n end or\r\n function()\r\n end\r\n\r\n--- 打印事件日志\r\nPrintEventLog = showLog and function(_fireEnum, _eventName, _player, _args)\r\n if _fireEnum == FireEnum.SERVER then\r\n --* Fire_S 参数打印\r\n print(string.format('[NetUtil][服务器] %s, 参数 = %s, %s', _eventName, #_args, table.dump(_args)))\r\n elseif _fireEnum == FireEnum.CLIENT then\r\n --* Fire_C 参数打印\r\n print(\r\n string.format(\r\n '[NetUtil][客户端] %s, 玩家=%s, 参数 = %s, %s',\r\n _eventName,\r\n _player.Name,\r\n #_args,\r\n table.dump(_args)\r\n )\r\n )\r\n elseif _fireEnum == FireEnum.BROADCAST then\r\n --* Broadcase 参数打印\r\n print(string.format('[NetUtil][客户端][广播] %s, 参数 = %s, %s', _eventName, #_args, table.dump(_args)))\r\n end\r\n end or function()\r\n end\r\n\r\nreturn NetUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"CsvUtilModule","guid":[974164082,3633467084,2781912883,1451329930],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"CsvUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 读表工具: 将CSV导入成Lua Table,支持单一主键和多主键\r\n-- @module CSV Utility\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\n-- @see https://wiki.lilithgames.com/x/RGEMAg\r\nlocal CsvUtil = {}\r\n\r\n--! 打印事件日志, true:开启打印\r\nlocal showLog, PrintGlobalKV, PrintLog = true\r\n\r\n--- 将表中的字符串改为数字\r\n-- @param _t input table\r\nlocal function StrToNum(_t)\r\n for k, v in pairs(_t) do\r\n _t[k] = tonumber(v)\r\n end\r\n return _t\r\nend\r\n\r\n--- 类型解析配置表\r\nlocal parser = {\r\n int = function(_raw)\r\n return tonumber(_raw)\r\n end,\r\n float = function(_raw)\r\n return tonumber(_raw)\r\n end,\r\n string = function(_raw)\r\n return _raw\r\n end,\r\n boolean = function(_raw)\r\n return string.lower(_raw) == 'true'\r\n end,\r\n vector2 = function(_raw)\r\n return Vector2(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n vector3 = function(_raw)\r\n return Vector3(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n euler = function(_raw)\r\n return EulerDegree(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n color = function(_raw)\r\n return Color(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end\r\n}\r\n\r\n--- 读取配置表,会根据id生成lua表\r\n-- @param _type String 数据类型\r\n-- @parm _stringValue String 数据\r\n-- @return value 解析出来的数值\r\nlocal function GetValue(_type, _stringValue)\r\n _type = string.lower(_type)\r\n assert(parser[_type], string.format('[CsvUtil][GlobalSetting] \"%s\" Type字段的值不是目前所支持的数据类型', _type))\r\n return parser[_type](_stringValue)\r\nend\r\n\r\n--- 读取配置表,会根据id生成lua表\r\n-- @param _csv 表格\r\n-- @parma ... 表格的主键ids,可以为单一主键或多主键(多主键的id顺序决定lua table的结构)\r\n-- @usage exmaple #1 如果, 单一键值为主键\r\n-- Level.csv 表格内容为:\r\n-- ----------------------------------\r\n-- | String | String | Int |\r\n-- | level_id | level_name | reward |\r\n-- | easy_01 | Level 01 | 100 |\r\n-- | easy_02 | Level 02 | 140 |\r\n-- | hard_01 | Level 03 | 280 |\r\n-- | hard_02 | Level 04 | 320 |\r\n-- ----------------------------------\r\n-- 调用函数 local levelCsv = CsvUtil.GetCsvInfo(Level, 'level_id') 导入的lua表格结果为:\r\n-- levelCsv = {\r\n-- easy_01 = {\r\n-- level_id = 'easy_01',\r\n-- level_name = 'Level 01',\r\n-- reward = 100\r\n-- },\r\n-- easy_02 = {\r\n-- level_id = 'easy_02',\r\n-- level_name = 'Level 02',\r\n-- reward = 140\r\n-- },\r\n-- hard_01 = {\r\n-- level_id = 'hard_01',\r\n-- level_name = 'Level 03',\r\n-- reward = 280\r\n-- },\r\n-- hard_02 = {\r\n-- level_id = 'hard_02',\r\n-- level_name = 'Level 04',\r\n-- reward = 320\r\n-- }\r\n-- }\r\n-- @usage exmaple #2 如果, 多键值为主键\r\n-- Enemy.csv 表格内容为:\r\n-- ----------------------------------\r\n-- | String | String | Int |\r\n-- | enemy_id | difficulty | hp |\r\n-- | foe_01 | easy | 100 |\r\n-- | foe_01 | hard | 150 |\r\n-- | foe_02 | easy | 300 |\r\n-- | foe_02 | hard | 400 |\r\n-- ----------------------------------\r\n-- 调用函数 local enemyCsv = CsvUtil.GetCsvInfo(Enemy, 'enemy_id', 'difficulty') 导入的lua表格结果为:\r\n-- enemyCsv = {\r\n-- foe_01 = {\r\n-- easy = {\r\n-- enemy_id = 'foe_01',\r\n-- difficulty = 'easy',\r\n-- hp = 100\r\n-- },\r\n-- hard = {\r\n-- enemy_id = 'foe_02',\r\n-- difficulty = 'hard',\r\n-- hp = 150\r\n-- }\r\n-- },\r\n-- foe_02 = {\r\n-- esay = {\r\n-- enemy_id = 'foe_02',\r\n-- difficulty = 'easy',\r\n-- hp = 300\r\n-- },\r\n-- hard = {\r\n-- enemy_id = 'foe_02',\r\n-- difficulty = 'hard',\r\n-- hp = 400\r\n-- }\r\n-- }\r\n-- }\r\n-- 使用lua table中的数据方法:\r\n-- health = enemyCsv.foe_01.hard.hp 或 health = enemyCsv['foe_01']['hard']['hp']\r\n-- health的值为150\r\nfunction CsvUtil.GetCsvInfo(_csv, ...)\r\n local rawTable = _csv:GetRows()\r\n local ids = {...}\r\n if #ids < 1 or (#ids == 1 and ids[1] == 'Type') then\r\n -- 默认用Type索引,直接返回\r\n return rawTable\r\n end\r\n local result = {}\r\n local tmp, key, id, idstr -- 临时变量\r\n for _, v in pairs(rawTable) do\r\n tmp = result\r\n idstr = {}\r\n for i = 1, #ids do\r\n id = ids[i]\r\n key = v[id]\r\n idstr[i] = tostring(id) .. ','\r\n assert(not string.isnilorempty(key), string.format('[CsvUtil] CSV表格没有找到此id, CSV:%s, id: %s', _csv.Name, id))\r\n if i == #ids then\r\n -- 最后的键,确定唯一性\r\n assert(\r\n not tmp[key],\r\n string.format('[CsvUtil] CSV数据重复, ids不是唯一的, CSV: %s, ids: %s', _csv.Name, table.concat(idstr))\r\n )\r\n tmp[key] = v\r\n else\r\n -- 多键,之后还有\r\n if tmp[key] == nil then\r\n tmp[key] = {}\r\n end\r\n tmp = tmp[key]\r\n end\r\n end\r\n end\r\n return result\r\nend\r\n\r\n--- 读取Config全局配置表\r\n-- GlobleSetting.csv 表格内容为:\r\n-- ---------------------------------------------------------\r\n-- | String | String | String | String |\r\n-- | Key | Type | Value | Des |\r\n-- ---------------------------------------------------------\r\n-- | CubeMax | Int | 200 | 最大Cube数 |\r\n-- | BattleTime | Float | 5.45 | 战斗时间 |\r\n-- | GameTitle | String | Boom Party | 游戏标题 |\r\n-- | IsFree | Boolean | true | 是否免费 |\r\n-- | UiMapOrigin | Vector2 | 3,4 | UI地图原点位置 |\r\n-- | TreePos | Vector3 | 12,3,-3 | 树的位置 |\r\n-- | TreeRot | Euler | 45,90,0 | 树的旋转 |\r\n-- | TreeColor | Color | 255,255,255,0 | 树的颜色 |\r\nfunction CsvUtil.GetGlobalCsvInfo(_csv)\r\n local rawTable = _csv:GetRows()\r\n if table.nums(rawTable) == 0 then\r\n return\r\n end\r\n assert(rawTable['1'].Key, '[CsvUtil] 全局配置表的没有\"Key\"')\r\n assert(rawTable['1'].Type, '[CsvUtil] 全局配置表的没有\"Type\"')\r\n assert(rawTable['1'].Value, '[CsvUtil] 全局配置表的没有\"Value\"')\r\n local result = {}\r\n for _, v in pairs(rawTable) do\r\n result[v.Key] = GetValue(v['Type'], v['Value'])\r\n PrintGlobalKV(v.Key, v.Type, result[v.Key]) -- * 输出KV键值对\r\n end\r\n return result\r\nend\r\n\r\n--- 表格预加载,预加载配置模块:World.Global.Define.ConfigModule\r\nfunction CsvUtil.PreloadCsv(_preloadList, _csvRoot, _config)\r\n assert(_preloadList and #_preloadList > 0, '[CsvUtil] ConfigModule中没有预加载表格')\r\n for _, pl in pairs(_preloadList) do\r\n if not string.isnilorempty(pl.csv) then\r\n pl.name = string.isnilorempty(pl.name) and pl.csv or pl.name\r\n PrintLog(string.format('[CsvUtil] Load: %s.csv', pl.csv))\r\n if pl.csv == 'GlobalSetting' and _csvRoot[pl.csv] then\r\n _config[pl.name] = CsvUtil.GetGlobalCsvInfo(_csvRoot[pl.csv])\r\n elseif not string.isnilorempty(pl.csv) and _csvRoot[pl.csv] then\r\n pl.ids = pl.ids or {}\r\n _config[pl.name] = CsvUtil.GetCsvInfo(_csvRoot[pl.csv], table.unpack(pl.ids))\r\n end\r\n end\r\n end\r\nend\r\n\r\n--! 辅助功能\r\n\r\n--- 输出全局变量键值对\r\nPrintGlobalKV =\r\n showLog and\r\n function(_key, _type, _value)\r\n _type = string.lower(_type)\r\n local showTypes = {\r\n vector2 = 'Vector2',\r\n vector3 = 'Vector3',\r\n euler = 'EulerDegree',\r\n color = 'Color'\r\n }\r\n if showTypes[_type] then\r\n print(string.format('[CsvUtil][GlobalSetting] %s = %s%s ', _key, showTypes[_type], _value))\r\n else\r\n print(string.format('[CsvUtil][GlobalSetting] %s = %s ', _key, _value))\r\n end\r\n end or\r\n function()\r\n end\r\n\r\nPrintLog = showLog and function(...)\r\n print(...)\r\n end or function()\r\n end\r\n\r\nreturn CsvUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"XlsUtilModule","guid":[2760847323,3965272240,2950769927,3980104073],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"XlsUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 读表工具: 将导入成Lua Table,支持单一主键和多主键\r\n-- @module XLS Utility\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\n-- @see https://wiki.lilithgames.com/x/RGEMAg\r\nlocal XlsUtil = {}\r\n\r\n--! 打印事件日志, true:开启打印\r\nlocal showLog, PrintGlobalKV, PrintLog = true\r\n\r\n--- 将表中的字符串改为数字\r\n-- @param _t input table\r\nlocal function StrToNum(_t)\r\n for k, v in pairs(_t) do\r\n _t[k] = tonumber(v)\r\n end\r\n return _t\r\nend\r\n\r\n--- 类型解析配置表\r\nlocal parser = {\r\n int = function(_raw)\r\n return math.floor(tonumber(_raw))\r\n end,\r\n float = function(_raw)\r\n return tonumber(_raw)\r\n end,\r\n string = function(_raw)\r\n return _raw\r\n end,\r\n boolean = function(_raw)\r\n return string.lower(_raw) == 'true'\r\n end,\r\n vector2 = function(_raw)\r\n return Vector2(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n vector3 = function(_raw)\r\n return Vector3(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n euler = function(_raw)\r\n return EulerDegree(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n color = function(_raw)\r\n return Color(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end\r\n}\r\n\r\n--- 读取配置表,会根据id生成lua表\r\n-- @param _type String 数据类型\r\n-- @parm _stringValue String 数据\r\n-- @return value 解析出来的数值\r\nlocal function GetValue(_type, _stringValue)\r\n _type = string.lower(_type)\r\n assert(parser[_type], string.format('[XlsUtil][GlobalSetting] \"%s\" Type字段的值不是目前所支持的数据类型', _type))\r\n return parser[_type](_stringValue)\r\nend\r\n\r\n--- 根据id转换lua table\r\nfunction XlsUtil.GetXlsInfo(_xls, ...)\r\n local ids = {...}\r\n if #ids < 1 or (#ids == 1 and ids[1] == 'Type') then\r\n -- 默认用Type索引,直接返回\r\n return _xls\r\n end\r\n local rawTable = _xls\r\n local result = {}\r\n local tmp, key, id, idstr -- 临时变量\r\n for _, v in pairs(rawTable) do\r\n tmp = result\r\n idstr = {}\r\n for i = 1, #ids do\r\n id = ids[i]\r\n key = v[id]\r\n idstr[i] = tostring(id) .. ','\r\n assert(\r\n not string.isnilorempty(key),\r\n string.format('[XlsUtil] Excel表格没有找到此id, Excel:%s, id: %s', _xls.Name, id)\r\n )\r\n if i == #ids then\r\n -- 最后的键,确定唯一性\r\n assert(\r\n not tmp[key],\r\n string.format('[XlsUtil] Excel数据重复, ids不是唯一的, Excel: %s, ids: %s', _xls.Name, table.concat(idstr))\r\n )\r\n tmp[key] = v\r\n else\r\n -- 多键,之后还有\r\n if tmp[key] == nil then\r\n tmp[key] = {}\r\n end\r\n tmp = tmp[key]\r\n end\r\n end\r\n end\r\n return result\r\nend\r\n\r\n--- 读取Config全局配置表\r\nfunction XlsUtil.GetGlobalXlsInfo(_xls)\r\n local rawTable = _xls\r\n if table.nums(rawTable) == 0 then\r\n return\r\n end\r\n assert(rawTable[1].Key, '[XlsUtil] 全局配置表的没有\"Key\"')\r\n assert(rawTable[1].Type, '[XlsUtil] 全局配置表的没有\"Type\"')\r\n assert(rawTable[1].Value, '[XlsUtil] 全局配置表的没有\"Value\"')\r\n local result = {}\r\n for _, v in pairs(rawTable) do\r\n result[v.Key] = GetValue(v['Type'], v['Value'])\r\n PrintGlobalKV(v.Key, v.Type, result[v.Key]) -- * 输出KV键值对\r\n end\r\n return result\r\nend\r\n\r\n--- 表格预加载,预加载配置模块:World.Global.Define.ConfigModule\r\nfunction XlsUtil.PreloadXls(_preloadList, _xlsRoot, _config)\r\n -- todo: load xls lua talbe\r\n assert(_preloadList and #_preloadList > 0, 'ConfigModule中没有预加载表格')\r\n\r\n for _, pl in pairs(_preloadList) do\r\n if not string.isnilorempty(pl.xls) then\r\n pl.name = string.isnilorempty(pl.name) and pl.xls or pl.name\r\n pl.module = string.isnilorempty(pl.module) and pl.xls .. 'Xls' or pl.module\r\n PrintLog(string.format('[XlsUtil] Load: %s', pl.module))\r\n if pl.xls == 'GlobalSetting' and _xlsRoot[pl.module .. 'Module'] then\r\n _config[pl.name] = XlsUtil.GetGlobalXlsInfo(_G[pl.module])\r\n elseif not string.isnilorempty(pl.xls) and _G[pl.module] then\r\n pl.ids = pl.ids or {}\r\n _config[pl.name] = XlsUtil.GetXlsInfo(_G[pl.module], table.unpack(pl.ids))\r\n end\r\n end\r\n end\r\nend\r\n\r\n--! 辅助功能\r\n\r\n--- 输出全局变量键值对\r\nPrintGlobalKV =\r\n showLog and\r\n function(_key, _type, _value)\r\n _type = string.lower(_type)\r\n local showTypes = {\r\n vector2 = 'Vector2',\r\n vector3 = 'Vector3',\r\n euler = 'EulerDegree',\r\n color = 'Color'\r\n }\r\n if showTypes[_type] then\r\n print(string.format('[XlsUtil][GlobalSetting] %s = %s%s ', _key, showTypes[_type], _value))\r\n else\r\n print(string.format('[XlsUtil][GlobalSetting] %s = %s ', _key, _value))\r\n end\r\n end or\r\n function()\r\n end\r\n\r\nPrintLog = showLog and function(...)\r\n print(...)\r\n end or function()\r\n end\r\n\r\nreturn XlsUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"EventUtilModule","guid":[2566228644,2776910007,2667900515,194242277],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"EventUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 事件绑定工具\r\n-- @module Event Connects Handler\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang, Yen Yuan\r\nlocal EventUtil = {}\r\n\r\n--- 检查是否为Json化的字符串\r\n-- @param _str @string 输入的字符串\r\n-- @return @boolean true: json table string\r\nlocal function IsJsonTable(_str)\r\n return type(_str) == 'string' and string.endswith(_str, 'JSON') and string.startswith(_str, 'JSON')\r\nend\r\n\r\n--- 处理Handler的传入参数\r\n--@param variable args\r\n--@return variable args\r\nlocal function ArgsAux(...)\r\n local _s = {...}\r\n for k, v in pairs(_s) do\r\n if IsJsonTable(v) then\r\n local json = string.sub(v, 5, -5)\r\n _s[k] = LuaJsonUtil:decode(json)\r\n end\r\n end\r\n return table.unpack(_s)\r\nend\r\n\r\n--- 遍历所有的events,找到module中对应名称的handler,建立Connect\r\n-- @param _eventFolder 事件所在的节点folder\r\n-- @param _module 模块\r\n-- @param _this module的self指针,用于闭包\r\nfunction EventUtil.LinkConnects(_eventFolder, _module, _this)\r\n assert(\r\n _eventFolder and _module and _this,\r\n string.format('[EventUtil] 参数有空值: %s, %s, %s', _eventFolder, _module, _this)\r\n )\r\n local events = _eventFolder:GetChildren()\r\n for _, evt in pairs(events) do\r\n if string.endswith(evt.Name, 'Event') then\r\n local handler = _module[evt.Name .. 'Handler']\r\n if handler ~= nil then\r\n -- print('[EventUtil]', _eventFolder, _module, evt)\r\n evt:Connect(\r\n function(...)\r\n handler(_this, ArgsAux(...))\r\n end\r\n )\r\n end\r\n end\r\n end\r\nend\r\n\r\nreturn EventUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"TimeUtilModule","guid":[3212895662,3876605484,2451409624,1852532118],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"TimeUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 时间管理器模块\r\n-- @module Module Time Manager\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Bingyun Chen, Yuancheng Zhang\r\n-- @see the functions defined by JavaScript syntax\r\n\r\nlocal TimeUtil = {}\r\n\r\n-- All registered events\r\nlocal eventList = {}\r\n\r\n-- Current active event list\r\nlocal activeEvents = {}\r\n\r\nlocal running = false\r\n\r\n-- Set update delta time\r\nlocal DELTA_TIME = .05\r\n\r\n--- Find all registered events to trigger\r\nlocal function CheckEvents()\r\n -- now = os.time()\r\n local now = Timer.GetTimeMillisecond()\r\n local i, event = 1\r\n while i <= #eventList do\r\n event = eventList[i]\r\n if event.triggerTime <= now then\r\n table.insert(activeEvents, event)\r\n if event.loop then\r\n event.triggerTime = event.triggerTime + event.delay\r\n i = i + 1\r\n else\r\n table.remove(eventList, i)\r\n end\r\n else\r\n i = i + 1\r\n end\r\n end\r\nend\r\n\r\n--- Trigger events\r\nlocal function TriggerEvents()\r\n local i = 1\r\n while i <= #activeEvents do\r\n event = activeEvents[i]\r\n invoke(event.func)\r\n table.remove(activeEvents, i)\r\n end\r\nend\r\n\r\n--- Update\r\nlocal function StartUpdate()\r\n while running do\r\n -- print(os.time())\r\n CheckEvents()\r\n TriggerEvents()\r\n wait(DELTA_TIME)\r\n end\r\nend\r\n\r\n--- Initialization\r\nfunction TimeUtil.Init()\r\n TimeUtil.Start()\r\nend\r\n\r\n--- Run Update()\r\nfunction TimeUtil.Start()\r\n running = true\r\n invoke(StartUpdate)\r\nend\r\n\r\n--- Stop Update()\r\nfunction TimeUtil.Stop()\r\n running = false\r\nend\r\n\r\n--- Call a function after a specified number of milliseconds,\r\n-- use ClearTimeout() method to prevent the function from running\r\n-- @param _func execution function to call\r\n-- @param _delayTime\r\n-- @return timer id\r\n-- @see https://www.w3schools.com/jsref/met_win_settimeout.asp\r\nfunction TimeUtil.SetTimeout(_func, _seconds)\r\n assert(_func, '[TimeUtil] TimeUtil.SetTimeout() _func 不能为空')\r\n assert(_seconds >= 0, '[TimeUtil] TimeUtil.SetTimeout() 延迟时间需大于等于0')\r\n if _seconds == 0 then\r\n print('[TimeUtil] TimeUtil.SetTimeout() 事件立即执行')\r\n invoke(_func)\r\n return\r\n end\r\n local id = #eventList + 1\r\n -- convert to milliseconds\r\n local ms = math.floor(_seconds * 1000)\r\n local timestamp = ms + Timer.GetTimeMillisecond()\r\n table.insert(\r\n eventList,\r\n {\r\n id = id,\r\n func = _func,\r\n delay = ms,\r\n triggerTime = timestamp\r\n }\r\n )\r\n return id\r\nend\r\n\r\n--- Call a function or evaluates an expression at specified intervals (in milliseconds),\r\n-- the method will continue calling the function until ClearInterval() is called, or the game is over.\r\n-- @param _func execution function to call\r\n-- @param _delayTime\r\n-- @return timer id\r\n-- @see https://www.w3schools.com/jsref/met_win_setinterval.asp\r\nfunction TimeUtil.SetInterval(_func, _seconds)\r\n assert(_func, '[TimeUtil] TimeUtil.SetInterval() _func 不能为空')\r\n assert(_seconds > 0, '[TimeUtil] TimeUtil.SetInterval() 延迟时间需大于0')\r\n local id = #eventList + 1\r\n -- convert to milliseconds\r\n local ms = math.floor(_seconds * 1000)\r\n local timestamp = ms + Timer.GetTimeMillisecond()\r\n table.insert(\r\n eventList,\r\n {\r\n id = id,\r\n func = _func,\r\n delay = ms,\r\n triggerTime = timestamp,\r\n loop = true\r\n }\r\n )\r\n return id\r\nend\r\n\r\n--- Clear a timer set with the SetTimeout() method\r\n-- @param _id timmer id\r\n-- @see https://www.w3schools.com/jsref/met_win_cleartimeout.asp\r\nfunction TimeUtil.ClearTimeout(_id)\r\n for k, e in pairs(eventList) do\r\n if e.id == _id then\r\n table.remove(eventList, k)\r\n break\r\n end\r\n end\r\nend\r\n\r\n--- Clear a timer set with the SetInterval() method, used as ClearTimeout()\r\n-- @see https://www.w3schools.com/jsref/met_win_clearinterval.asp\r\nTimeUtil.ClearInterval = TimeUtil.ClearTimeout\r\n\r\nreturn TimeUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"LogUtilModule","guid":[1448450812,1700152430,3083838744,455591299],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LogUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- Debug工具\r\n-- @module Debug utilities\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nlocal LogUtil = {}\r\n\r\n--- 日志级别枚举\r\nLogUtil.LevelEnum = {\r\n -- 指出细粒度信息事件对调试应用程序是非常有帮助的 主要用于开发过程中打印一些运行信息\r\n DEBUG = 1,\r\n -- 消息在粗粒度级别上突出强调应用程序的运行过程\r\n -- 打印一些你感兴趣的或者重要的信息 这个可以用于生产环境中输出程序运行的一些重要信息\r\n -- 但是不能滥用 避免打印过多的日志\r\n INFO = 2,\r\n -- 表明会出现潜在错误的情形 有些信息不是错误信息 但是也要给程序员的一些提示\r\n -- 指出虽然发生错误事件 但仍然不影响系统的继续运行\r\n -- 打印错误和异常信息 如果不想输出太多的日志 可以使用这个级别\r\n ERROR = 3\r\n}\r\n\r\n--- 日志级别\r\nLogUtil.level = LogUtil.LevelEnum.DEBUG\r\n\r\n--- 开关\r\nLogUtil.debugMode = true\r\n\r\nfunction LogUtil.Test(...)\r\n if LogUtil.debugMode and LogUtil.level <= LogUtil.LevelEnum.DEBUG then\r\n print('[TEST]', ...)\r\n end\r\nend\r\n\r\nfunction LogUtil.Debug(...)\r\n if LogUtil.debugMode and LogUtil.level <= LogUtil.LevelEnum.DEBUG then\r\n print('[DEBUG]', ...)\r\n end\r\nend\r\n\r\nfunction LogUtil.Info(...)\r\n if LogUtil.debugMode and LogUtil.level <= LogUtil.LevelEnum.INFO then\r\n print('[INFO]', ...)\r\n end\r\nend\r\n\r\nreturn LogUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"CamUtilModule","guid":[1786588942,1014908186,2189044832,747029913],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"CamUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---摄像机工具类\r\n---@module Cam Utility\r\n---@copyright Lilith Games, Avatar Team\r\n---@author Sharif Ma\r\n---@class CamUtil\r\nlocal CamUtil = {}\r\n\r\n---将摄像机在水平面上转动到和角色朝向一致的角度\r\n---@param _player PlayerInstance 摄像机看向的物体\r\n---@param _cam Camera 转动的摄像机\r\n---@param _time number 转动过程的事件,不填则瞬间转动\r\nfunction CamUtil.ToRoleForward(_player, _cam, _time)\r\n _time = _time or 0\r\n local dir = _player.Position - _cam.Position\r\n local forward = _player.Forward\r\n local alpha = Vector2.Angle(Vector2(dir.x, dir.z), Vector2(forward.x, forward.z))\r\n local left = _player.Left\r\n if Vector3.Angle(left, dir) > 90 then\r\n alpha = 360 - alpha\r\n end\r\n if _time == 0 then\r\n _cam:CameraMoveInDegree(Vector2(alpha, 0))\r\n return\r\n end\r\n invoke(\r\n function()\r\n local curTime = 0\r\n while true do\r\n local dt = wait()\r\n local dtDe = alpha * dt / _time\r\n _cam:CameraMoveInDegree(Vector2(dtDe, 0))\r\n curTime = curTime + dt\r\n if curTime >= _time then\r\n return\r\n end\r\n end\r\n end\r\n )\r\nend\r\n\r\nreturn CamUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"UuidModule","guid":[4157188643,3705227052,3158561090,4053995093],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"UuidModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---------------------------------------------------------------------------------------\r\n-- Copyright 2012 Rackspace (original), 2013 Thijs Schreijer (modifications)\r\n--\r\n-- Licensed under the Apache License, Version 2.0 (the \"License\");\r\n-- you may not use this file except in compliance with the License.\r\n-- You may obtain a copy of the License at\r\n--\r\n-- http://www.apache.org/licenses/LICENSE-2.0\r\n--\r\n-- Unless required by applicable law or agreed to in writing, software\r\n-- distributed under the License is distributed on an \"AS-IS\" BASIS,\r\n-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n-- See the License for the specific language governing permissions and\r\n-- limitations under the License.\r\n--\r\n-- see http://www.ietf.org/rfc/rfc4122.txt\r\n--\r\n-- Note that this is not a true version 4 (random) UUID. Since `os.time()` precision is only 1 second, it would be hard\r\n-- to guarantee spacial uniqueness when two hosts generate a uuid after being seeded during the same second. This\r\n-- is solved by using the node field from a version 1 UUID. It represents the mac address.\r\n--\r\n-- 28-apr-2013 modified by Thijs Schreijer from the original [Rackspace code](https://github.com/kans/zirgo/blob/807250b1af6725bad4776c931c89a784c1e34db2/util/uuid.lua) as a generic Lua module.\r\n-- Regarding the above mention on `os.time()`; the modifications use the `socket.gettime()` function from LuaSocket\r\n-- if available and hence reduce that problem (provided LuaSocket has been loaded before uuid).\r\n--\r\n-- **6-nov-2015 Please take note of this issue**; [https://github.com/Mashape/kong/issues/478](https://github.com/Mashape/kong/issues/478)\r\n-- It demonstrates the problem of using time as a random seed. Specifically when used from multiple processes.\r\n-- So make sure to seed only once, application wide. And to not have multiple processes do that\r\n-- simultaneously (like nginx does for example).\r\n\r\nlocal M = {}\r\n\r\nlocal bitsize = 32 -- bitsize assumed for Lua VM. See randomseed function below.\r\nlocal lua_version = tonumber(_VERSION:match('%d%.*%d*')) -- grab Lua version used\r\n\r\nlocal MATRIX_AND = {{0, 0}, {0, 1}}\r\nlocal MATRIX_OR = {{0, 1}, {1, 1}}\r\nlocal HEXES = '0123456789abcdef'\r\n\r\nlocal math_floor = math.floor\r\nlocal math_random = math.random\r\nlocal math_abs = math.abs\r\nlocal string_sub = string.sub\r\nlocal to_number = tonumber\r\nlocal assert = assert\r\nlocal type = type\r\n\r\n-- performs the bitwise operation specified by truth matrix on two numbers.\r\nlocal function BITWISE(x, y, matrix)\r\n local z = 0\r\n local pow = 1\r\n while x > 0 or y > 0 do\r\n z = z + (matrix[x % 2 + 1][y % 2 + 1] * pow)\r\n pow = pow * 2\r\n x = math_floor(x / 2)\r\n y = math_floor(y / 2)\r\n end\r\n return z\r\nend\r\n\r\nlocal function INT2HEX(x)\r\n local s, base = '', 16\r\n local d\r\n while x > 0 do\r\n d = x % base + 1\r\n x = math_floor(x / base)\r\n s = string_sub(HEXES, d, d) .. s\r\n end\r\n while #s < 2 do\r\n s = '0' .. s\r\n end\r\n return s\r\nend\r\n\r\n----------------------------------------------------------------------------\r\n-- Creates a new uuid. Either provide a unique hex string, or make sure the\r\n-- random seed is properly set. The module table itself is a shortcut to this\r\n-- function, so `my_uuid = uuid.new()` equals `my_uuid = uuid()`.\r\n--\r\n-- For proper use there are 3 options;\r\n--\r\n-- 1. first require `luasocket`, then call `uuid.seed()`, and request a uuid using no\r\n-- parameter, eg. `my_uuid = uuid()`\r\n-- 2. use `uuid` without `luasocket`, set a random seed using `uuid.randomseed(some_good_seed)`,\r\n-- and request a uuid using no parameter, eg. `my_uuid = uuid()`\r\n-- 3. use `uuid` without `luasocket`, and request a uuid using an unique hex string,\r\n-- eg. `my_uuid = uuid(my_networkcard_macaddress)`\r\n--\r\n-- @return a properly formatted uuid string\r\n-- @param hwaddr (optional) string containing a unique hex value (e.g.: `00:0c:29:69:41:c6`), to be used to compensate for the lesser `math_random()` function. Use a mac address for solid results. If omitted, a fully randomized uuid will be generated, but then you must ensure that the random seed is set properly!\r\n-- @usage\r\n-- local uuid = require(\"uuid\")\r\n-- print(\"here's a new uuid: \",uuid())\r\nfunction M.new(hwaddr)\r\n -- bytes are treated as 8bit unsigned bytes.\r\n local bytes = {\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255)\r\n }\r\n\r\n if hwaddr then\r\n assert(type(hwaddr) == 'string', 'Expected hex string, got ' .. type(hwaddr))\r\n -- Cleanup provided string, assume mac address, so start from back and cleanup until we've got 12 characters\r\n local i, str = #hwaddr, hwaddr\r\n hwaddr = ''\r\n while i > 0 and #hwaddr < 12 do\r\n local c = str:sub(i, i):lower()\r\n if HEXES:find(c, 1, true) then\r\n -- valid HEX character, so append it\r\n hwaddr = c .. hwaddr\r\n end\r\n i = i - 1\r\n end\r\n assert(\r\n #hwaddr == 12,\r\n \"Provided string did not contain at least 12 hex characters, retrieved '\" ..\r\n hwaddr .. \"' from '\" .. str .. \"'\"\r\n )\r\n\r\n -- no split() in lua. :(\r\n bytes[11] = to_number(hwaddr:sub(1, 2), 16)\r\n bytes[12] = to_number(hwaddr:sub(3, 4), 16)\r\n bytes[13] = to_number(hwaddr:sub(5, 6), 16)\r\n bytes[14] = to_number(hwaddr:sub(7, 8), 16)\r\n bytes[15] = to_number(hwaddr:sub(9, 10), 16)\r\n bytes[16] = to_number(hwaddr:sub(11, 12), 16)\r\n end\r\n\r\n -- set the version\r\n bytes[7] = BITWISE(bytes[7], 0x0f, MATRIX_AND)\r\n bytes[7] = BITWISE(bytes[7], 0x40, MATRIX_OR)\r\n -- set the variant\r\n bytes[9] = BITWISE(bytes[7], 0x3f, MATRIX_AND)\r\n bytes[9] = BITWISE(bytes[7], 0x80, MATRIX_OR)\r\n return INT2HEX(bytes[1]) ..\r\n INT2HEX(bytes[2]) ..\r\n INT2HEX(bytes[3]) ..\r\n INT2HEX(bytes[4]) ..\r\n '-' ..\r\n INT2HEX(bytes[5]) ..\r\n INT2HEX(bytes[6]) ..\r\n '-' ..\r\n INT2HEX(bytes[7]) ..\r\n INT2HEX(bytes[8]) ..\r\n '-' ..\r\n INT2HEX(bytes[9]) ..\r\n INT2HEX(bytes[10]) ..\r\n '-' ..\r\n INT2HEX(bytes[11]) ..\r\n INT2HEX(bytes[12]) ..\r\n INT2HEX(bytes[13]) ..\r\n INT2HEX(bytes[14]) ..\r\n INT2HEX(bytes[15]) .. INT2HEX(bytes[16])\r\nend\r\n\r\n----------------------------------------------------------------------------\r\n-- Improved randomseed function.\r\n-- Lua 5.1 and 5.2 both truncate the seed given if it exceeds the integer\r\n-- range. If this happens, the seed will be 0 or 1 and all randomness will\r\n-- be gone (each application run will generate the same sequence of random\r\n-- numbers in that case). This improved version drops the most significant\r\n-- bits in those cases to get the seed within the proper range again.\r\n-- @param seed the random seed to set (integer from 0 - 2^32, negative values will be made positive)\r\n-- @return the (potentially modified) seed used\r\n-- @usage\r\n-- local socket = require(\"socket\") -- gettime() has higher precision than os.time()\r\n-- local uuid = require(\"uuid\")\r\n-- -- see also example at uuid.seed()\r\n-- uuid.randomseed(socket.gettime()*10000)\r\n-- print(\"here's a new uuid: \",uuid())\r\nfunction M.randomseed(seed)\r\n seed = math_floor(math_abs(seed))\r\n if seed >= (2 ^ bitsize) then\r\n -- integer overflow, so reduce to prevent a bad seed\r\n seed = seed - math_floor(seed / 2 ^ bitsize) * (2 ^ bitsize)\r\n end\r\n if lua_version < 5.2 then\r\n -- 5.1 uses (incorrect) signed int\r\n math.randomseed(seed - 2 ^ (bitsize - 1))\r\n else\r\n -- 5.2 uses (correct) unsigned int\r\n math.randomseed(seed)\r\n end\r\n return seed\r\nend\r\n\r\n----------------------------------------------------------------------------\r\n-- Seeds the random generator.\r\n-- It does so in 2 possible ways;\r\n--\r\n-- 1. use `os.time()`: this only offers resolution to one second (used when\r\n-- LuaSocket hasn't been loaded yet\r\n-- 2. use luasocket `gettime()` function, but it only does so when LuaSocket\r\n-- has been required already.\r\n-- @usage\r\n-- local socket = require(\"socket\") -- gettime() has higher precision than os.time()\r\n-- -- LuaSocket loaded, so below line does the same as the example from randomseed()\r\n-- uuid.seed()\r\n-- print(\"here's a new uuid: \",uuid())\r\nfunction M.seed()\r\n -- if package.loaded['socket'] and package.loaded['socket'].gettime then\r\n -- return M.randomseed(package.loaded['socket'].gettime() * 10000)\r\n -- else\r\n return M.randomseed(os.time())\r\n -- end\r\nend\r\n\r\nreturn setmetatable(\r\n M,\r\n {\r\n __call = function(self, hwaddr)\r\n return self.new(hwaddr)\r\n end\r\n }\r\n)\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"LuaJsonUtilModule","guid":[1171767027,1956007823,2562650671,2698147667],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LuaJsonUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- -*- coding: utf-8 -*-\r\n---\r\n--- Simple JSON encoding and decoding in pure Lua.\r\n---\r\n--- Copyright 2010-2014 Jeffrey Friedl\r\n--- http://regex.info/blog/\r\n---\r\n--- Latest version: http://regex.info/blog/lua/json\r\n---\r\n--- This code is released under a Creative Commons CC-BY \"Attribution\" License:\r\n--- http://creativecommons.org/licenses/by/3.0/deed.en_US\r\n---\r\n--- It can be used for any purpose so long as the copyright notice above,\r\n--- the web-page links above, and the 'AUTHOR_NOTE' string below are\r\n--- maintained. Enjoy.\r\n---\r\nlocal VERSION = 20141223.14 --- version history at end of file\r\nlocal AUTHOR_NOTE = '-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20141223.14 ]-'\r\n\r\n---\r\n--- The 'AUTHOR_NOTE' variable exists so that information about the source\r\n--- of the package is maintained even in compiled versions. It's also\r\n--- included in OBJDEF below mostly to quiet warnings about unused variables.\r\n---\r\n---@module LuaJson\r\nlocal OBJDEF = {\r\n VERSION = VERSION,\r\n AUTHOR_NOTE = AUTHOR_NOTE\r\n}\r\n\r\n---\r\n--- Simple JSON encoding and decoding in pure Lua.\r\n--- http://www.json.org/\r\n---\r\n--\r\n-- JSON = assert(loadfile \"JSON.lua\")() -- one-time load of the routines\r\n--\r\n-- local lua_value = JSON:decode(raw_json_text)\r\n--\r\n-- local raw_json_text = JSON:encode(lua_table_or_value)\r\n-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- \"pretty printed\" version for human readability\r\n--\r\n--\r\n--\r\n-- DECODING (from a JSON string to a Lua table)\r\n--\r\n--\r\n-- JSON = assert(loadfile \"JSON.lua\")() -- one-time load of the routines\r\n--\r\n-- local lua_value = JSON:decode(raw_json_text)\r\n--\r\n-- If the JSON text is for an object or an array, e.g.\r\n-- { \"what\": \"books\", \"count\": 3 }\r\n-- or\r\n-- [ \"Larry\", \"Curly\", \"Moe\" ]\r\n--\r\n-- the result is a Lua table, e.g.\r\n-- { what = \"books\", count = 3 }\r\n-- or\r\n-- { \"Larry\", \"Curly\", \"Moe\" }\r\n--\r\n--\r\n-- The encode and decode routines accept an optional second argument,\r\n-- \"etc\", which is not used during encoding or decoding, but upon error\r\n-- is passed along to error handlers. It can be of any type (including nil).\r\n--\r\n--\r\n--\r\n-- ERROR HANDLING\r\n--\r\n-- With most errors during decoding, this code calls\r\n--\r\n-- JSON:onDecodeError(message, text, location, etc)\r\n--\r\n-- with a message about the error, and if known, the JSON text being\r\n-- parsed and the byte count where the problem was discovered. You can\r\n-- replace the default JSON:onDecodeError() with your own function.\r\n--\r\n-- The default onDecodeError() merely augments the message with data\r\n-- about the text and the location if known (and if a second 'etc'\r\n-- argument had been provided to decode(), its value is tacked onto the\r\n-- message as well), and then calls JSON.assert(), which itself defaults\r\n-- to Lua's built-in assert(), and can also be overridden.\r\n--\r\n-- For example, in an Adobe Lightroom plugin, you might use something like\r\n--\r\n-- function JSON:onDecodeError(message, text, location, etc)\r\n-- LrErrors.throwUserError(\"Internal Error: invalid JSON data\")\r\n-- end\r\n--\r\n-- or even just\r\n--\r\n-- function JSON.assert(message)\r\n-- LrErrors.throwUserError(\"Internal Error: \" .. message)\r\n-- end\r\n--\r\n-- If JSON:decode() is passed a nil, this is called instead:\r\n--\r\n-- JSON:onDecodeOfNilError(message, nil, nil, etc)\r\n--\r\n-- and if JSON:decode() is passed HTML instead of JSON, this is called:\r\n--\r\n-- JSON:onDecodeOfHTMLError(message, text, nil, etc)\r\n--\r\n-- The use of the fourth 'etc' argument allows stronger coordination\r\n-- between decoding and error reporting, especially when you provide your\r\n-- own error-handling routines. Continuing with the the Adobe Lightroom\r\n-- plugin example:\r\n--\r\n-- function JSON:onDecodeError(message, text, location, etc)\r\n-- local note = \"Internal Error: invalid JSON data\"\r\n-- if type(etc) = 'table' and etc.photo then\r\n-- note = note .. \" while processing for \" .. etc.photo:getFormattedMetadata('fileName')\r\n-- end\r\n-- LrErrors.throwUserError(note)\r\n-- end\r\n--\r\n-- :\r\n-- :\r\n--\r\n-- for i, photo in ipairs(photosToProcess) do\r\n-- :\r\n-- :\r\n-- local data = JSON:decode(someJsonText, { photo = photo })\r\n-- :\r\n-- :\r\n-- end\r\n--\r\n--\r\n--\r\n--\r\n--\r\n-- DECODING AND STRICT TYPES\r\n--\r\n-- Because both JSON objects and JSON arrays are converted to Lua tables,\r\n-- it's not normally possible to tell which original JSON type a\r\n-- particular Lua table was derived from, or guarantee decode-encode\r\n-- round-trip equivalency.\r\n--\r\n-- However, if you enable strictTypes, e.g.\r\n--\r\n-- JSON = assert(loadfile \"JSON.lua\")() --load the routines\r\n-- JSON.strictTypes = true\r\n--\r\n-- then the Lua table resulting from the decoding of a JSON object or\r\n-- JSON array is marked via Lua metatable, so that when re-encoded with\r\n-- JSON:encode() it ends up as the appropriate JSON type.\r\n--\r\n-- (This is not the default because other routines may not work well with\r\n-- tables that have a metatable set, for example, Lightroom API calls.)\r\n--\r\n--\r\n-- ENCODING (from a lua table to a JSON string)\r\n--\r\n-- JSON = assert(loadfile \"JSON.lua\")() -- one-time load of the routines\r\n--\r\n-- local raw_json_text = JSON:encode(lua_table_or_value)\r\n-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- \"pretty printed\" version for human readability\r\n-- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = \"| \", align_keys = false })\r\n--\r\n-- On error during encoding, this code calls:\r\n--\r\n-- JSON:onEncodeError(message, etc)\r\n--\r\n-- which you can override in your local JSON object.\r\n--\r\n-- The 'etc' in the error call is the second argument to encode()\r\n-- and encode_pretty(), or nil if it wasn't provided.\r\n--\r\n--\r\n-- PRETTY-PRINTING\r\n--\r\n-- An optional third argument, a table of options, allows a bit of\r\n-- configuration about how the encoding takes place:\r\n--\r\n-- pretty = JSON:encode(val, etc, {\r\n-- pretty = true, -- if false, no other options matter\r\n-- indent = \" \", -- this provides for a three-space indent per nesting level\r\n-- align_keys = false, -- see below\r\n-- })\r\n--\r\n-- encode() and encode_pretty() are identical except that encode_pretty()\r\n-- provides a default options table if none given in the call:\r\n--\r\n-- { pretty = true, align_keys = false, indent = \" \" }\r\n--\r\n-- For example, if\r\n--\r\n-- JSON:encode(data)\r\n--\r\n-- produces:\r\n--\r\n-- {\"city\":\"Kyoto\",\"climate\":{\"avg_temp\":16,\"humidity\":\"high\",\"snowfall\":\"minimal\"},\"country\":\"Japan\",\"wards\":11}\r\n--\r\n-- then\r\n--\r\n-- JSON:encode_pretty(data)\r\n--\r\n-- produces:\r\n--\r\n-- {\r\n-- \"city\": \"Kyoto\",\r\n-- \"climate\": {\r\n-- \"avg_temp\": 16,\r\n-- \"humidity\": \"high\",\r\n-- \"snowfall\": \"minimal\"\r\n-- },\r\n-- \"country\": \"Japan\",\r\n-- \"wards\": 11\r\n-- }\r\n--\r\n-- The following three lines return identical results:\r\n-- JSON:encode_pretty(data)\r\n-- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = \" \" })\r\n-- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = \" \" })\r\n--\r\n-- An example of setting your own indent string:\r\n--\r\n-- JSON:encode_pretty(data, nil, { pretty = true, indent = \"| \" })\r\n--\r\n-- produces:\r\n--\r\n-- {\r\n-- | \"city\": \"Kyoto\",\r\n-- | \"climate\": {\r\n-- | | \"avg_temp\": 16,\r\n-- | | \"humidity\": \"high\",\r\n-- | | \"snowfall\": \"minimal\"\r\n-- | },\r\n-- | \"country\": \"Japan\",\r\n-- | \"wards\": 11\r\n-- }\r\n--\r\n-- An example of setting align_keys to true:\r\n--\r\n-- JSON:encode_pretty(data, nil, { pretty = true, indent = \" \", align_keys = true })\r\n--\r\n-- produces:\r\n--\r\n-- {\r\n-- \"city\": \"Kyoto\",\r\n-- \"climate\": {\r\n-- \"avg_temp\": 16,\r\n-- \"humidity\": \"high\",\r\n-- \"snowfall\": \"minimal\"\r\n-- },\r\n-- \"country\": \"Japan\",\r\n-- \"wards\": 11\r\n-- }\r\n--\r\n-- which I must admit is kinda ugly, sorry. This was the default for\r\n-- encode_pretty() prior to version 20141223.14.\r\n--\r\n--\r\n-- AMBIGUOUS SITUATIONS DURING THE ENCODING\r\n--\r\n-- During the encode, if a Lua table being encoded contains both string\r\n-- and numeric keys, it fits neither JSON's idea of an object, nor its\r\n-- idea of an array. To get around this, when any string key exists (or\r\n-- when non-positive numeric keys exist), numeric keys are converted to\r\n-- strings.\r\n--\r\n-- For example,\r\n-- JSON:encode({ \"one\", \"two\", \"three\", SOMESTRING = \"some string\" }))\r\n-- produces the JSON object\r\n-- {\"1\":\"one\",\"2\":\"two\",\"3\":\"three\",\"SOMESTRING\":\"some string\"}\r\n--\r\n-- To prohibit this conversion and instead make it an error condition, set\r\n-- JSON.noKeyConversion = true\r\n--\r\n\r\n--\r\n-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT\r\n--\r\n-- assert\r\n-- onDecodeError\r\n-- onDecodeOfNilError\r\n-- onDecodeOfHTMLError\r\n-- onEncodeError\r\n--\r\n-- If you want to create a separate Lua JSON object with its own error handlers,\r\n-- you can reload JSON.lua or use the :new() method.\r\n--\r\n---------------------------------------------------------------------------\r\n\r\nlocal default_pretty_indent = ' '\r\nlocal default_pretty_options = {pretty = true, align_keys = false, indent = default_pretty_indent}\r\n\r\nlocal isArray = {\r\n __tostring = function()\r\n return 'JSON array'\r\n end\r\n}\r\nisArray.__index = isArray\r\nlocal isObject = {\r\n __tostring = function()\r\n return 'JSON object'\r\n end\r\n}\r\nisObject.__index = isObject\r\n\r\nfunction OBJDEF:newArray(tbl)\r\n return setmetatable(tbl or {}, isArray)\r\nend\r\n\r\nfunction OBJDEF:newObject(tbl)\r\n return setmetatable(tbl or {}, isObject)\r\nend\r\n\r\nlocal function unicode_codepoint_as_utf8(codepoint)\r\n --\r\n -- codepoint is a number\r\n --\r\n if codepoint <= 127 then\r\n return string.char(codepoint)\r\n elseif codepoint <= 2047 then\r\n --\r\n -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8\r\n --\r\n local highpart = math.floor(codepoint / 0x40)\r\n local lowpart = codepoint - (0x40 * highpart)\r\n return string.char(0xC0 + highpart, 0x80 + lowpart)\r\n elseif codepoint <= 65535 then\r\n --\r\n -- 1110yyyy 10yyyyxx 10xxxxxx\r\n --\r\n local highpart = math.floor(codepoint / 0x1000)\r\n local remainder = codepoint - 0x1000 * highpart\r\n local midpart = math.floor(remainder / 0x40)\r\n local lowpart = remainder - 0x40 * midpart\r\n\r\n highpart = 0xE0 + highpart\r\n midpart = 0x80 + midpart\r\n lowpart = 0x80 + lowpart\r\n\r\n --\r\n -- Check for an invalid character (thanks Andy R. at Adobe).\r\n -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070\r\n --\r\n if\r\n (highpart == 0xE0 and midpart < 0xA0) or (highpart == 0xED and midpart > 0x9F) or\r\n (highpart == 0xF0 and midpart < 0x90) or\r\n (highpart == 0xF4 and midpart > 0x8F)\r\n then\r\n return '?'\r\n else\r\n return string.char(highpart, midpart, lowpart)\r\n end\r\n else\r\n --\r\n -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx\r\n --\r\n local highpart = math.floor(codepoint / 0x40000)\r\n local remainder = codepoint - 0x40000 * highpart\r\n local midA = math.floor(remainder / 0x1000)\r\n remainder = remainder - 0x1000 * midA\r\n local midB = math.floor(remainder / 0x40)\r\n local lowpart = remainder - 0x40 * midB\r\n\r\n return string.char(0xF0 + highpart, 0x80 + midA, 0x80 + midB, 0x80 + lowpart)\r\n end\r\nend\r\n\r\nfunction OBJDEF:onDecodeError(message, text, location, etc)\r\n if text then\r\n if location then\r\n message = string.format('%s at char %d of: %s', message, location, text)\r\n else\r\n message = string.format('%s: %s', message, text)\r\n end\r\n end\r\n\r\n if etc ~= nil then\r\n message = message .. ' (' .. OBJDEF:encode(etc) .. ')'\r\n end\r\n\r\n if self.assert then\r\n self.assert(false, message)\r\n else\r\n assert(false, message)\r\n end\r\nend\r\n\r\nOBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError\r\nOBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError\r\n\r\nfunction OBJDEF:onEncodeError(message, etc)\r\n if etc ~= nil then\r\n message = message .. ' (' .. OBJDEF:encode(etc) .. ')'\r\n end\r\n\r\n if self.assert then\r\n self.assert(false, message)\r\n else\r\n assert(false, message)\r\n end\r\nend\r\n\r\nlocal function grok_number(self, text, start, etc)\r\n --\r\n -- Grab the integer part\r\n --\r\n local integer_part = text:match('^-?[1-9]%d*', start) or text:match('^-?0', start)\r\n\r\n if not integer_part then\r\n self:onDecodeError('expected number', text, start, etc)\r\n end\r\n\r\n local i = start + integer_part:len()\r\n\r\n --\r\n -- Grab an optional decimal part\r\n --\r\n local decimal_part = text:match('^%.%d+', i) or ''\r\n\r\n i = i + decimal_part:len()\r\n\r\n --\r\n -- Grab an optional exponential part\r\n --\r\n local exponent_part = text:match('^[eE][-+]?%d+', i) or ''\r\n\r\n i = i + exponent_part:len()\r\n\r\n local full_number_text = integer_part .. decimal_part .. exponent_part\r\n local as_number = tonumber(full_number_text)\r\n\r\n if not as_number then\r\n self:onDecodeError('bad number', text, start, etc)\r\n end\r\n\r\n return as_number, i\r\nend\r\n\r\nlocal function grok_string(self, text, start, etc)\r\n if text:sub(start, start) ~= '\"' then\r\n self:onDecodeError(\"expected string's opening quote\", text, start, etc)\r\n end\r\n\r\n local i = start + 1 -- +1 to bypass the initial quote\r\n local text_len = text:len()\r\n local VALUE = ''\r\n while i <= text_len do\r\n local c = text:sub(i, i)\r\n if c == '\"' then\r\n return VALUE, i + 1\r\n end\r\n if c ~= '\\\\' then\r\n VALUE = VALUE .. c\r\n i = i + 1\r\n elseif text:match('^\\\\b', i) then\r\n VALUE = VALUE .. '\\b'\r\n i = i + 2\r\n elseif text:match('^\\\\f', i) then\r\n VALUE = VALUE .. '\\f'\r\n i = i + 2\r\n elseif text:match('^\\\\n', i) then\r\n VALUE = VALUE .. '\\n'\r\n i = i + 2\r\n elseif text:match('^\\\\r', i) then\r\n VALUE = VALUE .. '\\r'\r\n i = i + 2\r\n elseif text:match('^\\\\t', i) then\r\n VALUE = VALUE .. '\\t'\r\n i = i + 2\r\n else\r\n local hex =\r\n text:match(\r\n '^\\\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])',\r\n i\r\n )\r\n if hex then\r\n i = i + 6 -- bypass what we just read\r\n\r\n -- We have a Unicode codepoint. It could be standalone, or if in the proper range and\r\n -- followed by another in a specific range, it'll be a two-code surrogate pair.\r\n local codepoint = tonumber(hex, 16)\r\n if codepoint >= 0xD800 and codepoint <= 0xDBFF then\r\n -- it's a hi surrogate... see whether we have a following low\r\n local lo_surrogate =\r\n text:match('^\\\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)\r\n if lo_surrogate then\r\n i = i + 6 -- bypass the low surrogate we just read\r\n codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16)\r\n else\r\n -- not a proper low, so we'll just leave the first codepoint as is and spit it out.\r\n end\r\n end\r\n VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint)\r\n else\r\n -- just pass through what's escaped\r\n VALUE = VALUE .. text:match('^\\\\(.)', i)\r\n i = i + 2\r\n end\r\n end\r\n end\r\n\r\n self:onDecodeError('unclosed string', text, start, etc)\r\nend\r\n\r\nlocal function skip_whitespace(text, start)\r\n local _, match_end = text:find('^[ \\n\\r\\t]+', start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2\r\n if match_end then\r\n return match_end + 1\r\n else\r\n return start\r\n end\r\nend\r\n\r\nlocal grok_one -- assigned later\r\n\r\nlocal function grok_object(self, text, start, etc)\r\n if text:sub(start, start) ~= '{' then\r\n self:onDecodeError(\"expected '{'\", text, start, etc)\r\n end\r\n\r\n local i = skip_whitespace(text, start + 1) -- +1 to skip the '{'\r\n\r\n local VALUE = self.strictTypes and self:newObject {} or {}\r\n\r\n if text:sub(i, i) == '}' then\r\n return VALUE, i + 1\r\n end\r\n local text_len = text:len()\r\n while i <= text_len do\r\n local key, new_i = grok_string(self, text, i, etc)\r\n\r\n i = skip_whitespace(text, new_i)\r\n\r\n if text:sub(i, i) ~= ':' then\r\n self:onDecodeError('expected colon', text, i, etc)\r\n end\r\n\r\n i = skip_whitespace(text, i + 1)\r\n\r\n local new_val, new_i = grok_one(self, text, i)\r\n\r\n VALUE[key] = new_val\r\n\r\n --\r\n -- Expect now either '}' to end things, or a ',' to allow us to continue.\r\n --\r\n i = skip_whitespace(text, new_i)\r\n\r\n local c = text:sub(i, i)\r\n\r\n if c == '}' then\r\n return VALUE, i + 1\r\n end\r\n\r\n if text:sub(i, i) ~= ',' then\r\n self:onDecodeError(\"expected comma or '}'\", text, i, etc)\r\n end\r\n\r\n i = skip_whitespace(text, i + 1)\r\n end\r\n\r\n self:onDecodeError(\"unclosed '{'\", text, start, etc)\r\nend\r\n\r\nlocal function grok_array(self, text, start, etc)\r\n if text:sub(start, start) ~= '[' then\r\n self:onDecodeError(\"expected '['\", text, start, etc)\r\n end\r\n\r\n local i = skip_whitespace(text, start + 1) -- +1 to skip the '['\r\n local VALUE = self.strictTypes and self:newArray {} or {}\r\n if text:sub(i, i) == ']' then\r\n return VALUE, i + 1\r\n end\r\n\r\n local VALUE_INDEX = 1\r\n\r\n local text_len = text:len()\r\n while i <= text_len do\r\n local val, new_i = grok_one(self, text, i)\r\n\r\n -- can't table.insert(VALUE, val) here because it's a no-op if val is nil\r\n VALUE[VALUE_INDEX] = val\r\n VALUE_INDEX = VALUE_INDEX + 1\r\n\r\n i = skip_whitespace(text, new_i)\r\n\r\n --\r\n -- Expect now either ']' to end things, or a ',' to allow us to continue.\r\n --\r\n local c = text:sub(i, i)\r\n if c == ']' then\r\n return VALUE, i + 1\r\n end\r\n if text:sub(i, i) ~= ',' then\r\n self:onDecodeError(\"expected comma or '['\", text, i, etc)\r\n end\r\n i = skip_whitespace(text, i + 1)\r\n end\r\n self:onDecodeError(\"unclosed '['\", text, start, etc)\r\nend\r\n\r\ngrok_one = function(self, text, start, etc)\r\n -- Skip any whitespace\r\n start = skip_whitespace(text, start)\r\n\r\n if start > text:len() then\r\n self:onDecodeError('unexpected end of string', text, nil, etc)\r\n end\r\n\r\n if text:find('^\"', start) then\r\n return grok_string(self, text, start, etc)\r\n elseif text:find('^[-0123456789 ]', start) then\r\n return grok_number(self, text, start, etc)\r\n elseif text:find('^%{', start) then\r\n return grok_object(self, text, start, etc)\r\n elseif text:find('^%[', start) then\r\n return grok_array(self, text, start, etc)\r\n elseif text:find('^true', start) then\r\n return true, start + 4\r\n elseif text:find('^false', start) then\r\n return false, start + 5\r\n elseif text:find('^null', start) then\r\n return nil, start + 4\r\n else\r\n self:onDecodeError(\"can't parse JSON\", text, start, etc)\r\n end\r\nend\r\n\r\n---@param text string\r\nfunction OBJDEF:decode(text, etc)\r\n if type(self) ~= 'table' or self.__index ~= OBJDEF then\r\n OBJDEF:onDecodeError('JSON:decode must be called in method format', nil, nil, etc)\r\n end\r\n\r\n if text == nil then\r\n self:onDecodeOfNilError(string.format('nil passed to JSON:decode()'), nil, nil, etc)\r\n elseif type(text) ~= 'string' then\r\n self:onDecodeError(\r\n string.format('expected string argument to JSON:decode(), got %s', type(text)),\r\n nil,\r\n nil,\r\n etc\r\n )\r\n end\r\n\r\n if text:match('^%s*$') then\r\n return nil\r\n end\r\n\r\n if text:match('^%s*<') then\r\n -- Can't be JSON... we'll assume it's HTML\r\n self:onDecodeOfHTMLError(string.format('html passed to JSON:decode()'), text, nil, etc)\r\n end\r\n\r\n --\r\n -- Ensure that it's not UTF-32 or UTF-16.\r\n -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3),\r\n -- but this package can't handle them.\r\n --\r\n if text:sub(1, 1):byte() == 0 or (text:len() >= 2 and text:sub(2, 2):byte() == 0) then\r\n self:onDecodeError('JSON package groks only UTF-8, sorry', text, nil, etc)\r\n end\r\n\r\n local success, value = pcall(grok_one, self, text, 1, etc)\r\n\r\n if success then\r\n return value\r\n else\r\n -- if JSON:onDecodeError() didn't abort out of the pcall, we'll have received the error message here as \"value\", so pass it along as an assert.\r\n if self.assert then\r\n self.assert(false, value)\r\n else\r\n assert(false, value)\r\n end\r\n -- and if we're still here, return a nil and throw the error message on as a second arg\r\n return nil, value\r\n end\r\nend\r\n\r\nlocal function backslash_replacement_function(c)\r\n if c == '\\n' then\r\n return '\\\\n'\r\n elseif c == '\\r' then\r\n return '\\\\r'\r\n elseif c == '\\t' then\r\n return '\\\\t'\r\n elseif c == '\\b' then\r\n return '\\\\b'\r\n elseif c == '\\f' then\r\n return '\\\\f'\r\n elseif c == '\"' then\r\n return '\\\\\"'\r\n elseif c == '\\\\' then\r\n return '\\\\\\\\'\r\n else\r\n return string.format('\\\\u%04x', c:byte())\r\n end\r\nend\r\n\r\nlocal chars_to_be_escaped_in_JSON_string =\r\n '[' ..\r\n '\"' .. -- class sub-pattern to match a double quote\r\n '%\\\\' .. -- class sub-pattern to match a backslash\r\n '%z' .. -- class sub-pattern to match a null\r\n '\\001' ..\r\n '-' ..\r\n '\\031' .. -- class sub-pattern to match control characters\r\n ']'\r\n\r\nlocal function json_string_literal(value)\r\n local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function)\r\n return '\"' .. newval .. '\"'\r\nend\r\n\r\nlocal function object_or_array(self, T, etc)\r\n --\r\n -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON\r\n -- object. If there are only numbers, it's a JSON array.\r\n --\r\n -- If we'll be converting to a JSON object, we'll want to sort the keys so that the\r\n -- end result is deterministic.\r\n --\r\n local string_keys = {}\r\n local number_keys = {}\r\n local number_keys_must_be_strings = false\r\n local maximum_number_key\r\n\r\n for key in pairs(T) do\r\n if type(key) == 'string' then\r\n table.insert(string_keys, key)\r\n elseif type(key) == 'number' then\r\n table.insert(number_keys, key)\r\n if key <= 0 or key >= math.huge then\r\n number_keys_must_be_strings = true\r\n elseif not maximum_number_key or key > maximum_number_key then\r\n maximum_number_key = key\r\n end\r\n else\r\n self:onEncodeError(\"can't encode table with a key of type \" .. type(key), etc)\r\n end\r\n end\r\n\r\n if #string_keys == 0 and not number_keys_must_be_strings then\r\n --\r\n -- An empty table, or a numeric-only array\r\n --\r\n if #number_keys > 0 then\r\n return nil, maximum_number_key -- an array\r\n elseif tostring(T) == 'JSON array' then\r\n return nil\r\n elseif tostring(T) == 'JSON object' then\r\n return {}\r\n else\r\n -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects\r\n return nil\r\n end\r\n end\r\n\r\n table.sort(string_keys)\r\n\r\n local map\r\n if #number_keys > 0 then\r\n --\r\n -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array\r\n -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object.\r\n --\r\n\r\n if self.noKeyConversion then\r\n self:onEncodeError('a table with both numeric and string keys could be an object or array; aborting', etc)\r\n end\r\n\r\n --\r\n -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings\r\n --\r\n map = {}\r\n for key, val in pairs(T) do\r\n map[key] = val\r\n end\r\n\r\n table.sort(number_keys)\r\n\r\n --\r\n -- Throw numeric keys in there as strings\r\n --\r\n for _, number_key in ipairs(number_keys) do\r\n local string_key = tostring(number_key)\r\n if map[string_key] == nil then\r\n table.insert(string_keys, string_key)\r\n map[string_key] = T[number_key]\r\n else\r\n self:onEncodeError(\r\n 'conflict converting table with mixed-type keys into a JSON object: key ' ..\r\n number_key .. ' exists both as a string and a number.',\r\n etc\r\n )\r\n end\r\n end\r\n end\r\n\r\n return string_keys, nil, map\r\nend\r\n\r\n--\r\n-- Encode\r\n--\r\n-- 'options' is nil, or a table with possible keys:\r\n-- pretty -- if true, return a pretty-printed version\r\n-- indent -- a string (usually of spaces) used to indent each nested level\r\n-- align_keys -- if true, align all the keys when formatting a table\r\n--\r\nlocal encode_value -- must predeclare because it calls itself\r\nfunction encode_value(self, value, parents, etc, options, indent)\r\n if value == nil then\r\n return 'null'\r\n elseif type(value) == 'string' then\r\n return json_string_literal(value)\r\n elseif type(value) == 'number' then\r\n if value ~= value then\r\n --\r\n -- NaN (Not a Number).\r\n -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option.\r\n --\r\n return 'null'\r\n elseif value >= math.huge then\r\n --\r\n -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should\r\n -- really be a package option. Note: at least with some implementations, positive infinity\r\n -- is both \">= math.huge\" and \"<= -math.huge\", which makes no sense but that's how it is.\r\n -- Negative infinity is properly \"<= -math.huge\". So, we must be sure to check the \">=\"\r\n -- case first.\r\n --\r\n return '1e+9999'\r\n elseif value <= -math.huge then\r\n --\r\n -- Negative infinity.\r\n -- JSON has no INF, so we have to fudge the best we can. This should really be a package option.\r\n --\r\n return '-1e+9999'\r\n else\r\n return tostring(value)\r\n end\r\n elseif type(value) == 'boolean' then\r\n return tostring(value)\r\n elseif type(value) ~= 'table' then\r\n self:onEncodeError(\"can't convert \" .. type(value) .. ' to JSON', etc)\r\n else\r\n --\r\n -- A table to be converted to either a JSON object or array.\r\n --\r\n local T = value\r\n\r\n if type(options) ~= 'table' then\r\n options = {}\r\n end\r\n if type(indent) ~= 'string' then\r\n indent = ''\r\n end\r\n\r\n if parents[T] then\r\n self:onEncodeError('table ' .. tostring(T) .. ' is a child of itself', etc)\r\n else\r\n parents[T] = true\r\n end\r\n\r\n local result_value\r\n\r\n local object_keys, maximum_number_key, map = object_or_array(self, T, etc)\r\n if maximum_number_key then\r\n --\r\n -- An array...\r\n --\r\n local ITEMS = {}\r\n for i = 1, maximum_number_key do\r\n table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent))\r\n end\r\n\r\n if options.pretty then\r\n result_value = '[ ' .. table.concat(ITEMS, ', ') .. ' ]'\r\n else\r\n result_value = '[' .. table.concat(ITEMS, ',') .. ']'\r\n end\r\n elseif object_keys then\r\n --\r\n -- An object\r\n --\r\n local TT = map or T\r\n\r\n if options.pretty then\r\n local KEYS = {}\r\n local max_key_length = 0\r\n for _, key in ipairs(object_keys) do\r\n local encoded = encode_value(self, tostring(key), parents, etc, options, indent)\r\n if options.align_keys then\r\n max_key_length = math.max(max_key_length, #encoded)\r\n end\r\n table.insert(KEYS, encoded)\r\n end\r\n local key_indent = indent .. tostring(options.indent or '')\r\n local subtable_indent =\r\n key_indent .. string.rep(' ', max_key_length) .. (options.align_keys and ' ' or '')\r\n local FORMAT = '%s%' .. string.format('%d', max_key_length) .. 's: %s'\r\n\r\n local COMBINED_PARTS = {}\r\n for i, key in ipairs(object_keys) do\r\n local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent)\r\n table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))\r\n end\r\n result_value = '{\\n' .. table.concat(COMBINED_PARTS, ',\\n') .. '\\n' .. indent .. '}'\r\n else\r\n local PARTS = {}\r\n for _, key in ipairs(object_keys) do\r\n local encoded_val = encode_value(self, TT[key], parents, etc, options, indent)\r\n local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent)\r\n table.insert(PARTS, string.format('%s:%s', encoded_key, encoded_val))\r\n end\r\n result_value = '{' .. table.concat(PARTS, ',') .. '}'\r\n end\r\n else\r\n --\r\n -- An empty array/object... we'll treat it as an array, though it should really be an option\r\n --\r\n result_value = '[]'\r\n end\r\n\r\n parents[T] = false\r\n return result_value\r\n end\r\nend\r\n\r\nfunction OBJDEF:encode(value, etc, options)\r\n if type(self) ~= 'table' or self.__index ~= OBJDEF then\r\n OBJDEF:onEncodeError('JSON:encode must be called in method format', etc)\r\n end\r\n return encode_value(self, value, {}, etc, options or nil)\r\nend\r\n\r\nfunction OBJDEF:encode_pretty(value, etc, options)\r\n if type(self) ~= 'table' or self.__index ~= OBJDEF then\r\n OBJDEF:onEncodeError('JSON:encode_pretty must be called in method format', etc)\r\n end\r\n return encode_value(self, value, {}, etc, options or default_pretty_options)\r\nend\r\n\r\nfunction OBJDEF.__tostring()\r\n return 'JSON encode/decode package'\r\nend\r\n\r\nOBJDEF.__index = OBJDEF\r\n\r\nfunction OBJDEF:new(args)\r\n local new = {}\r\n\r\n if args then\r\n for key, val in pairs(args) do\r\n new[key] = val\r\n end\r\n end\r\n\r\n return setmetatable(new, OBJDEF)\r\nend\r\n\r\nreturn OBJDEF:new()\r\n\r\n--\r\n-- Version history:\r\n--\r\n-- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really\r\n-- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines\r\n-- more flexible, and changed the default encode_pretty() to be more generally useful.\r\n--\r\n-- Added a third 'options' argument to the encode() and encode_pretty() routines, to control\r\n-- how the encoding takes place.\r\n--\r\n-- Updated docs to add assert() call to the loadfile() line, just as good practice so that\r\n-- if there is a problem loading JSON.lua, the appropriate error message will percolate up.\r\n--\r\n-- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string,\r\n-- so that the source of the package, and its version number, are visible in compiled copies.\r\n--\r\n-- 20140911.12 Minor lua cleanup.\r\n-- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'.\r\n-- (Thanks to SmugMug's David Parry for these.)\r\n--\r\n-- 20140418.11 JSON nulls embedded within an array were being ignored, such that\r\n-- [\"1\",null,null,null,null,null,\"seven\"],\r\n-- would return\r\n-- {1,\"seven\"}\r\n-- It's now fixed to properly return\r\n-- {1, nil, nil, nil, nil, nil, \"seven\"}\r\n-- Thanks to \"haddock\" for catching the error.\r\n--\r\n-- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to \"blue\" for the heads up.\r\n--\r\n-- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces \"2.0\" instead of \"2\",\r\n-- and this caused some problems.\r\n--\r\n-- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate,\r\n-- and had of course diverged (encode_pretty didn't get the fixes that encode got, so\r\n-- sometimes produced incorrect results; thanks to Mattie for the heads up).\r\n--\r\n-- Handle encoding tables with non-positive numeric keys (unlikely, but possible).\r\n--\r\n-- If a table has both numeric and string keys, or its numeric keys are inappropriate\r\n-- (such as being non-positive or infinite), the numeric keys are turned into\r\n-- string keys appropriate for a JSON object. So, as before,\r\n-- JSON:encode({ \"one\", \"two\", \"three\" })\r\n-- produces the array\r\n-- [\"one\",\"two\",\"three\"]\r\n-- but now something with mixed key types like\r\n-- JSON:encode({ \"one\", \"two\", \"three\", SOMESTRING = \"some string\" }))\r\n-- instead of throwing an error produces an object:\r\n-- {\"1\":\"one\",\"2\":\"two\",\"3\":\"three\",\"SOMESTRING\":\"some string\"}\r\n--\r\n-- To maintain the prior throw-an-error semantics, set\r\n-- JSON.noKeyConversion = true\r\n--\r\n-- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry.\r\n--\r\n-- 20130120.6 Comment update: added a link to the specific page on my blog where this code can\r\n-- be found, so that folks who come across the code outside of my blog can find updates\r\n-- more easily.\r\n--\r\n-- 20111207.5 Added support for the 'etc' arguments, for better error reporting.\r\n--\r\n-- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent.\r\n--\r\n-- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules:\r\n--\r\n-- * When encoding lua for JSON, Sparse numeric arrays are now handled by\r\n-- spitting out full arrays, such that\r\n-- JSON:encode({\"one\", \"two\", [10] = \"ten\"})\r\n-- returns\r\n-- [\"one\",\"two\",null,null,null,null,null,null,null,\"ten\"]\r\n--\r\n-- In 20100810.2 and earlier, only up to the first non-null value would have been retained.\r\n--\r\n-- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as \"1+e9999\".\r\n-- Version 20100810.2 and earlier created invalid JSON in both cases.\r\n--\r\n-- * Unicode surrogate pairs are now detected when decoding JSON.\r\n--\r\n-- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding\r\n--\r\n-- 20100731.1 initial public release\r\n--\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ObjPoolUtilModule","guid":[2729864637,1304447132,2322341388,2859273176],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ObjPoolUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---对象池工具模块\r\n---@module ObjPoolUtil\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yen Yuan\r\n---@class ObjPoolUtil\r\nlocal ObjPoolUtil = class('ObjPoolUtil')\r\n\r\n---创建某一个对象的对象池\r\n---@param _folderName Object 管理的目录\r\n---@param _objName string 对象的Archetype名\r\n---@param _maxCount number 对象池最大上限,不填则为100\r\n---@return ObjPoolUtil\r\nfunction ObjPoolUtil.static.Newpool(_folderName, _objName, _maxCount)\r\n if _folderName == nil or _objName == nil then\r\n error('[ObjPoolUtil] 管理目录或管理对象为空')\r\n end\r\n if _maxCount == nil then\r\n _maxCount = 100\r\n end\r\n local realPool = class(_objName .. 'Pool', ObjPoolUtil)\r\n realPool.static.obj = _objName\r\n realPool.static.folder = _folderName\r\n realPool.static.maxCount = _maxCount\r\n realPool.pool = {}\r\n print(string.format('[ObjPoolUtil] 创建了一个%s的对象池,目录为%s', _objName, _folderName))\r\n return realPool\r\nend\r\n\r\n---从池中创建对象到世界下\r\n---@param _position Vector3\r\n---@param _rotation EulerDegree\r\nfunction ObjPoolUtil:Spawn(_position, _rotation)\r\n local realObj = nil\r\n if #self.pool == 0 then\r\n realObj = world:CreateInstance(self.obj, self.obj, self.folder, _position, _rotation)\r\n if realObj == nil then\r\n error(string.format('[ObjPoolUtil] Archetype下没有名为%s的对象', self.obj))\r\n return\r\n end\r\n return realObj\r\n else\r\n realObj = self.pool[1]\r\n self.pool[1].Position = _position\r\n self.pool[1].Rotation = _rotation\r\n self.pool[1]:SetActive(true)\r\n table.remove(self.pool, 1)\r\n return realObj\r\n end\r\nend\r\n\r\n---从世界中销毁对象到池中\r\n---@param _obj Object\r\nfunction ObjPoolUtil:Despawn(_obj)\r\n if _obj == nil then\r\n error('[ObjPoolUtil] 传入对象为空')\r\n elseif #self.pool > self.maxCount then\r\n error(string.format('[ObjPoolUtil] %s对象池已满,该对象会永久销毁', self.obj))\r\n _obj:Destroy()\r\n else\r\n table.insert(self.pool, _obj)\r\n _obj:SetActive(false)\r\n end\r\nend\r\n\r\nreturn ObjPoolUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"SoundUtilModule","guid":[375943343,233195050,2377557310,1976802289],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"SoundUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 音效播放模块\r\n---@module SoundUtil\r\n---@copyright Lilith Games, Avatar Team\r\n---@author Sharif Ma\r\n---@class SoundUtil\r\nlocal SoundUtil = {}\r\n\r\nfunction SoundUtil:Init()\r\n print('[SoundUtil] Init()')\r\n self.SoundPlaying = {}\r\n self.Table_Sound = Config.Sound\r\nend\r\n\r\n---创建一个新音效并播放\r\n---@param _ID number 音效的ID\r\n---@param _SoundSourceObj Object 音效的挂载物体,不填则为2D音效,挂载在主摄像机上\r\nfunction SoundUtil:PlaySound(_ID, _SoundSourceObj)\r\n local Info, _Duration\r\n _SoundSourceObj = _SoundSourceObj or world.CurrentCamera\r\n Info = self.Table_Sound[_ID]\r\n assert(Info, '[SoundUtil] 表中不存在该ID的音效')\r\n _Duration = Info.Duration\r\n local sameSoundPlayingNum = 0\r\n for k, v in pairs(self.SoundPlaying) do\r\n if v == _ID then\r\n sameSoundPlayingNum = sameSoundPlayingNum + 1\r\n end\r\n end\r\n if sameSoundPlayingNum > 0 and not Info.CoverPlay then\r\n print(string.format('[SoundUtil] %s音效CoverPlay字段为false,不能覆盖播放', _ID))\r\n return\r\n end\r\n\r\n local Audio = world:CreateObject('AudioSource', 'Audio_' .. Info.FileName, _SoundSourceObj)\r\n Audio.LocalPosition = Vector3.Zero\r\n Audio.SoundClip = ResourceManager.GetSoundClip('Audio/' .. Info.FileName)\r\n print('[SoundUtil] Audio.SoundClip', Audio.SoundClip)\r\n Audio.Volume = Info.Volume\r\n Audio.MaxDistance = 10\r\n Audio.MinDistance = 10\r\n Audio.Loop = Info.IsLoop\r\n Audio:Play()\r\n table.insert(self.SoundPlaying, _ID)\r\n _Duration = _Duration or 1\r\n invoke(\r\n function()\r\n if Audio then\r\n Audio:Destroy()\r\n end\r\n end,\r\n _Duration\r\n )\r\n invoke(\r\n function()\r\n for k, v in pairs(self.SoundPlaying) do\r\n if v == _ID then\r\n table.remove(self.SoundPlaying, k)\r\n end\r\n end\r\n end,\r\n _Duration\r\n )\r\nend\r\n\r\nreturn SoundUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"LinkedListModule","guid":[572803863,1537884583,2902871108,3023603150],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LinkedListModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- C# 双向链表\r\n-- @module C# doubly linked list implemented with lua\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Bruce Chen\r\n-- @see https://wiki.lilithgames.com/x/7yRZAg\r\n-- @see https://github.com/BruceCheng1995/LuaLinkedList\r\n\r\nlocal LinkedList = {}\r\nlocal LinkedNode = {}\r\nLinkedNode.__index = LinkedNode\r\n\r\nlocal NativePrint = print\r\nlocal EmptuFunc = function()\r\nend\r\n--是否开放内部日志\r\nfunction LinkedList:EnableLog(_enable)\r\n if _enable then\r\n print = NativePrint\r\n else\r\n print = EmptuFunc\r\n end\r\nend\r\nLinkedList:EnableLog(false)\r\n--新建节点\r\nfunction LinkedNode:new(value, list)\r\n local o = {}\r\n setmetatable(o, self)\r\n o.List = list\r\n o.Next = nil\r\n o.Prev = nil\r\n o.Value = value\r\n return o\r\nend\r\n--克隆这个节点\r\nfunction LinkedNode:Clone()\r\n return LinkedNode:new(self.Value, nil)\r\nend\r\n--节点失效\r\nfunction LinkedNode:Invalidate()\r\n self.Next = nil\r\n self.Prev = nil\r\n self.List = nil\r\nend\r\n--打印\r\nfunction LinkedNode:tostring()\r\n return tostring(self.Value)\r\nend\r\nLinkedNode.__tostring = LinkedNode.tostring\r\n\r\n--验证新节点是否是自由节点\r\nfunction LinkedList:ValidateNewNode(node)\r\n if not node then\r\n return false\r\n end\r\n --assert(LinkedNode:include(node),\"instance of LinkedNode needed.\")\r\n if node.List ~= nil then\r\n return false\r\n end\r\n return true\r\nend\r\n\r\n--验证该节点是否是属于该表\r\nfunction LinkedList:ValidateNode(node)\r\n if not node then\r\n return false\r\n end\r\n --assert(LinkedNode:include(node),\"instance of LinkedNode needed.\")\r\n if node.List ~= self then\r\n return false\r\n end\r\n return true\r\nend\r\n\r\n--将节点插入到node节点之前(list:链表,node:插在这个节点前面,newnode:被插入的节点)\r\nlocal function InternalInsertNodeBefore(list, node, newnode)\r\n newnode.Next = node\r\n newnode.Prev = node.Prev\r\n node.Prev.Next = newnode\r\n node.Prev = newnode\r\n list.Count = list.Count + 1\r\nend\r\n\r\n--将节点插入到一个空链表之前(list:链表,newnode:被插入的节点)\r\nlocal function InternalInsertNodeToEmptyList(list, newnode)\r\n newnode.Next = newnode\r\n newnode.Prev = newnode\r\n list.First = newnode\r\n list.Count = list.Count + 1\r\nend\r\n\r\n--移除链表中的节点(list:链表,node:被删除的节点)\r\nlocal function InternalRemoveNode(list, node)\r\n if node.Next == node then\r\n list.First = nil\r\n else\r\n node.Next.Prev = node.Prev\r\n node.Prev.Next = node.Next\r\n if list.First == node then\r\n list.First = node.Next\r\n end\r\n end\r\n node:Invalidate()\r\n list.Count = list.Count - 1\r\nend\r\n\r\n--新建双向链表\r\nfunction LinkedList:new(tab)\r\n local o = {}\r\n setmetatable(o, self)\r\n o.Count = 0\r\n o.First = nil\r\n if type(tab) == 'table' then\r\n for _, v in pairs(tab) do\r\n o:AddLast(v)\r\n end\r\n end\r\n return o\r\nend\r\n\r\n--Add Value\r\n--在尾部添加值(若传入值是表,则遍历表,并将所有值添加到尾部)\r\nfunction LinkedList:Add(value)\r\n if type(value) == 'table' then\r\n for _, v in pairs(value) do\r\n self:AddLast(v)\r\n end\r\n else\r\n self:AddLast(value)\r\n end\r\nend\r\n\r\n--在尾部添加值\r\nfunction LinkedList:AddLast(value)\r\n local newnode = LinkedNode:new(value, self)\r\n if not self.First then\r\n InternalInsertNodeToEmptyList(self, newnode)\r\n else\r\n InternalInsertNodeBefore(self, self.First, newnode)\r\n end\r\n return newnode\r\nend\r\n\r\n--在头部添加值\r\nfunction LinkedList:AddFirst(value)\r\n local newnode = LinkedNode:new(value, self)\r\n if not self.First then\r\n InternalInsertNodeToEmptyList(self, newnode)\r\n else\r\n InternalInsertNodeBefore(self, self.First, newnode)\r\n self.First = newnode\r\n end\r\n return newnode\r\nend\r\n\r\n--在指定节点后面添加值(node:插入在这个节点后,value:被插入的值)\r\nfunction LinkedList:AddAfter(node, value)\r\n if not self:ValidateNewNode(node) then\r\n return\r\n end\r\n local newnode = LinkedNode:new(value, self)\r\n InternalInsertNodeBefore(self, node.Next, newnode)\r\n return newnode\r\nend\r\n\r\n--在指定节点前面添加值(node:插入在这个节点前,value:被插入的值)\r\nfunction LinkedList:AddBefore(node, value)\r\n if not self:ValidateNode(node) then\r\n return\r\n end\r\n local newnode = LinkedNode:new(value, self)\r\n InternalInsertNodeBefore(self, node, newnode)\r\n if node == self.First then\r\n self.First = newnode\r\n end\r\n return newnode\r\nend\r\n\r\n--Add Node\r\n--在头部添加节点\r\nfunction LinkedList:AddNodeFirst(node)\r\n if not self:ValidateNewNode(node) then\r\n return\r\n end\r\n if not self.First then\r\n InternalInsertNodeToEmptyList(self, node)\r\n else\r\n InternalInsertNodeBefore(self, self.First, node)\r\n self.First = node\r\n end\r\n node.List = self\r\nend\r\n\r\n--在尾部添加节点\r\nfunction LinkedList:AddNodeLast(node)\r\n if not self:ValidateNewNode(node) then\r\n return\r\n end\r\n if not self.First then\r\n InternalInsertNodeToEmptyList(self, node)\r\n else\r\n InternalInsertNodeBefore(self, self.First, node)\r\n end\r\n node.List = self\r\nend\r\n\r\n--在指定节点后面添加值(node:插入在这个节点后,newnode:被插入的节点)\r\nfunction LinkedList:AddNodeAfter(node, newnode)\r\n if not self:ValidateNode(node) and not self:ValidateNewNode(newnode) then\r\n return\r\n end\r\n InternalInsertNodeBefore(self, node.Next, newnode)\r\n newnode.List = self\r\nend\r\n\r\n--在指定节点后面添加值(node:插入在这个节点前,newnode:被插入的节点)\r\nfunction LinkedList:AddNodeBefore(node, newnode)\r\n if not self:ValidateNode(node) and not self:ValidateNewNode(newnode) then\r\n return\r\n end\r\n InternalInsertNodeBefore(self, node, newnode)\r\n newnode.List = self\r\n if node ~= self.First then\r\n return\r\n end\r\n self.First = newnode\r\nend\r\n\r\n--Remove\r\n--找到表中的第一个指定值,并删除,返回是否命中\r\nfunction LinkedList:Remove(value)\r\n local node = self:Find(value)\r\n if not node then\r\n return false\r\n end\r\n InternalRemoveNode(self, node)\r\n return true\r\nend\r\n\r\n--找到表中的第一个指定节点,并删除,返回是否命中\r\nfunction LinkedList:RemoveNode(node)\r\n if not self:ValidateNode(node) then\r\n return\r\n end\r\n InternalRemoveNode(self, node)\r\nend\r\n\r\n--移除头部节点\r\nfunction LinkedList:RemoveFirst()\r\n if self.First == nil then\r\n print('[LinkedList] list is empty.')\r\n else\r\n InternalRemoveNode(self, self.First)\r\n end\r\nend\r\n\r\n--移除尾部节点\r\nfunction LinkedList:RemoveLast()\r\n if self.First == nil then\r\n print('[LinkedList] list is empty.')\r\n else\r\n InternalRemoveNode(self, self.First.Prev)\r\n end\r\nend\r\n\r\n--Find\r\n--尝试找到表中的第一个指定值,若有则返回这个节点\r\nfunction LinkedList:Find(value)\r\n local ptrnode = self.First\r\n if value ~= nil then\r\n while ptrnode.Value ~= value do\r\n ptrnode = ptrnode.Next\r\n if ptrnode == self.First then\r\n goto close1\r\n end\r\n end\r\n return ptrnode\r\n else\r\n while ptrnode.Value ~= nil do\r\n ptrnode = ptrnode.Next\r\n if ptrnode == self.First then\r\n goto close1\r\n end\r\n end\r\n return ptrnode\r\n end\r\n ::close1::\r\n return\r\nend\r\n\r\n--尝试反向找到表中第一个指定值,若有则返回这个节点\r\nfunction LinkedList:FindLast(value)\r\n if self.First == nil then\r\n return\r\n end\r\n local prev = self.First.Prev\r\n local ptrnode = prev\r\n if value ~= nil then\r\n while ptrnode.Value ~= value do\r\n ptrnode = ptrnode.Prev\r\n if ptrnode == Prev then\r\n goto close2\r\n end\r\n end\r\n return ptrnode\r\n else\r\n while ptrnode.Value ~= nil do\r\n ptrnode = ptrnode.Prev\r\n if ptrnode == prev then\r\n goto close2\r\n end\r\n end\r\n return ptrnode\r\n end\r\n ::close2::\r\n return\r\nend\r\n\r\n--Other\r\n--清空链表\r\nfunction LinkedList:Clear()\r\n local ptrnode = self.First\r\n while ptrnode ~= nil do\r\n local lastnode = ptrnode\r\n ptrnode = ptrnode.Next\r\n lastnode:Invalidate()\r\n end\r\n self.First = nil\r\n self.Count = 0\r\nend\r\n\r\n--向给定table的指定位置插入数值(tab:被插入表,index:序号)\r\nfunction LinkedList:CopyTo(tab, index)\r\n assert(type(tab) == 'table', '[LinkedList] bad argument \"table\"')\r\n assert(index >= 1, '[LinkedList] Index out of range')\r\n local ptrnode = self.First\r\n if ptrnode == nil then\r\n return\r\n end\r\n repeat\r\n table.insert(tab, index, ptrnode.Value)\r\n ptrnode = ptrnode.Next\r\n index = index + 1\r\n until (ptrnode == self.First)\r\nend\r\n\r\n--将链表中的数据拷贝到新表中,并将这个表输出\r\nfunction LinkedList:ToTable()\r\n local tab = {}\r\n self:CopyTo(tab, 1)\r\n return tab\r\nend\r\n\r\n--克隆当前链表,并返回\r\nfunction LinkedList:Clone()\r\n local newlist = LinkedList:new()\r\n local ptrnode = self.First\r\n repeat\r\n local clnode = ptrnode:Clone()\r\n newlist:AddNodeLast(clnode)\r\n ptrnode = ptrnode.Next\r\n until (ptrnode == self.First)\r\n return newlist\r\nend\r\n\r\n--检查链表中是否包含指定值\r\nfunction LinkedList:Contains(value)\r\n return self:Find(value) and true or false\r\nend\r\n\r\n--将链表反向\r\nfunction LinkedList:Reverse()\r\n local tmp\r\n if not self.First then\r\n print('[LinkedList] list is empty')\r\n return\r\n end\r\n self.First = self.First.Prev\r\n for item in self:ipairer() do\r\n tmp = item.Next\r\n item.Next = item.Prev\r\n item.Prev = tmp\r\n end\r\nend\r\n\r\n--返回头部节点\r\nfunction LinkedList:GetFirst()\r\n return self.First\r\nend\r\n\r\n--返回尾部节点\r\nfunction LinkedList:GetLast()\r\n return self.First ~= nil and self.First.Prev or nil\r\nend\r\n\r\n--返回第index个节点\r\nfunction LinkedList:GetNode(index)\r\n if index < 1 or index > self.Count then\r\n print('[LinkedList] Index out of range')\r\n return\r\n end\r\n local ptrnode = self.First.Prev\r\n while index > 0 do\r\n ptrnode = ptrnode.Next\r\n index = index - 1\r\n end\r\n return ptrnode\r\nend\r\n\r\n--返回链表长度\r\nfunction LinkedList:Len()\r\n return self.Count\r\nend\r\n\r\n--返回迭代器\r\nfunction LinkedList:ipairer()\r\n local ptrnode = self:GetLast()\r\n local passFirst = false\r\n return function()\r\n if ptrnode then\r\n if ptrnode ~= self:GetLast() or not passFirst then\r\n passFirst = true\r\n ptrnode = ptrnode.Next\r\n return ptrnode\r\n end\r\n end\r\n end\r\nend\r\n\r\n--以文本方式表示此表\r\nfunction LinkedList:tostring()\r\n local t = {}\r\n for item in self:ipairer() do\r\n table.insert(t, tostring(item))\r\n end\r\n return 'LinkedList:{' .. table.concat(t, ',') .. '}'\r\nend\r\n\r\nLinkedList.__index = LinkedList\r\nLinkedList.__tostring = LinkedList.tostring\r\n\r\nreturn {\r\n list = setmetatable(LinkedList, {__call = LinkedList.new}),\r\n node = setmetatable(LinkedNode, {__call = LinkedNode.new})\r\n}\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"GlobalFuncModule","guid":[397078907,1337606489,2918347355,1376574677],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GlobalFuncModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 全局函数的定义\r\n--- @module GlobalFunc Defines\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Sid Zhang\r\nlocal GlobalFunc = {}\r\n\r\n--- 埋点上传日志\r\n--- @param _tableName string 表名\r\nfunction GlobalFunc.UploadLogs(_tableName, ...)\r\n local args = {...}\r\n if localPlayer then\r\n pcall(\r\n function()\r\n TrackService.CloudLogFromClient({_tableName, table.unpack(args)})\r\n end\r\n )\r\n else\r\n pcall(\r\n function()\r\n TrackService.CloudLogFromServer({_tableName, table.unpack(args)})\r\n end\r\n )\r\n end\r\nend\r\n\r\nreturn GlobalFunc\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"TweenControllerModule","guid":[3340226311,3486273272,3143424947,180252541],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"TweenControllerModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---控制某个变量随时间变化的协程类\r\n---@module TweenController\r\n---@copyright Lilith Games, Avatar Team\r\n---@author An Dai\r\nlocal TweenController = class('TweenController')\r\n\r\n---_name:类名,_sender:使用它的类,_getTotalTime:获得总时间的方法,_update _callback:回调函数 _isFix:是否在fixupdate中执行, _start: 开始函数\r\nfunction TweenController:initialize(_name, _sender, _getTotalTime, _update, _callback, _isFix, _start)\r\n _start = _start or function()\r\n return\r\n end\r\n\r\n local updateStr = (_isFix and 'Fix' or '') .. 'Update'\r\n\r\n self.Start = function(self)\r\n _start()\r\n self.totalTime = _getTotalTime()\r\n self.time = 0\r\n _sender[updateStr .. 'Table'][_name] = self\r\n end\r\n\r\n self[updateStr] = function(self, _dt)\r\n self.time = self.time + _dt\r\n if (self.time > self.totalTime) then\r\n self:Stop()\r\n goto UpdateReturn\r\n end\r\n _update(self.time, self.totalTime, _dt)\r\n ::UpdateReturn::\r\n end\r\n\r\n self.Stop = function(self)\r\n _sender[updateStr .. 'Table'][_name] = nil\r\n _callback()\r\n end\r\nend\r\n\r\nreturn TweenController\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ValueChangeUtilModule","guid":[506887186,2798406641,2460390889,2104928690],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ValueChangeUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 值改变及值改变事件\r\n--- @module ValueChangeUtil Module\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Xin Tan\r\nlocal ValueChangeUtil = {}\r\n\r\n--- 数据变化事件\r\n--- @param _table table 事件表\r\n--- @param _index string 索引\r\n--- @param _oldValue mixed 旧值\r\n--- @param _newValue mixed 新值\r\n--- @param _targetPlayer PlayerInstance 这条数据对应的玩家实例\r\nfunction ValueChangeUtil.DataChangeEvent(_table, _index, _oldValue, _newValue)\r\n\tif not _table[_index] or type(_table[_index]) ~= \"function\" then\r\n\t\treturn\r\n\tend\r\n\t_table[_index](_oldValue, _newValue)\r\nend\r\n\r\n--- 将目标表(或其中某个值)改为新值\r\n--- @param _table table 目标表\r\n--- @param _index string 目标索引(改整个目标表时填nil)\r\n--- @param _value mixed 新值\r\n--- @param _eventTable table 数值改变事件表(不响应时不传)\r\nfunction ValueChangeUtil.ChangeValue(_table, _index, _value, _eventTable)\r\n\tif type(_table) ~= \"table\" then\r\n\t\tprint(\"[error]传入的目标表类型错误\")\r\n\t\treturn\r\n\tend\r\n\t\r\n local tmp = _table\r\n local eventtmp = _eventTable or false\r\n\t\r\n\t-- 参数含索引时\r\n\tif _index then\r\n\t\tlocal idx = {}\r\n\t\tif type(_index) == \"string\" then\r\n\t\t\t-- 将索引通过'.'拆开\r\n\t\t\tidx = string.split(_index, '.')\r\n\t\telseif type(_index) == \"table\" then\r\n\t\t\tidx = _index\r\n\t\tend\r\n\t\t-- 一层层向下索引\r\n\t\tfor i = 1, #idx - 1 do\r\n\t\t\t-- 若目标表没有对应的索引则建立空表\r\n\t\t\tif type(tmp[idx[i]]) ~= \"table\" then\r\n\t\t\t\ttmp[idx[i]] = {}\r\n\t\t\tend\r\n tmp = tmp[idx[i]]\r\n if eventtmp then\r\n if type(eventtmp[idx[i]]) ~= \"table\" then\r\n eventtmp[idx[i]] = {}\r\n end\r\n eventtmp = eventtmp[idx[i]]\r\n end\r\n\t\tend\r\n\t\t\r\n\t\t-- 若目标值不是table,则直接赋值\r\n\t\tif type(_value) ~= \"table\" then\r\n local oldValue = table.shallowcopy(tmp[idx[#idx]])\r\n tmp[idx[#idx]] = _value\r\n if eventtmp then\r\n ValueChangeUtil.DataChangeEvent(eventtmp, idx[#idx], oldValue, _value)\r\n end\r\n\t\t\treturn\r\n\t\telse\r\n\t\t\t-- 目标值是table\r\n\t\t\t-- 若目标索引不是table,则创建table\r\n\t\t\tif type(tmp[idx[#idx]]) ~= \"table\" then\r\n tmp[idx[#idx]] = {}\r\n if eventtmp and type( eventtmp[idx[#idx]]) ~= \"table\" then\r\n eventtmp[idx[#idx]] = {}\r\n end\r\n\t\t\tend\r\n tmp = tmp[idx[#idx]]\r\n if eventtmp then\r\n eventtmp = eventtmp[idx[#idx]]\r\n end\r\n\t\tend\r\n\telse\r\n\t\t-- 参数无索引时,从目标表根目录开始同步\r\n\t\tif type(_value) ~= \"table\" then\r\n\t\t\tprint(\"[error]传入的新值类型错误\")\r\n\t\t\treturn\r\n\t\tend\r\n\tend\r\n\t\r\n\t-- 清除目标索引表与新值的差集\r\n\tfor k, v in pairs(tmp) do\r\n if not _value[k] then \r\n local oldValue = tmp[k]\r\n tmp[k] = nil\r\n if eventtmp then\r\n ValueChangeUtil.DataChangeEvent(eventtmp, k, oldValue, nil)\r\n end\r\n end\r\n\tend\r\n\t\r\n\t-- 逐层覆盖数据\r\n\tfor k, v in pairs(_value) do\r\n\t\t-- 如果值为table则向下递归\r\n\t\tif type(v) == \"table\" then\r\n ValueChangeUtil.ChangeValue(tmp, k, v, eventtmp)\r\n\t\telse\r\n\t\t\t-- 若值不是table,则直接赋值\r\n local oldValue = tmp[k]\r\n tmp[k] = v\r\n if eventtmp then\r\n ValueChangeUtil.DataChangeEvent(eventtmp, k, oldValue, v)\r\n end\r\n\t\tend\r\n\tend\r\n\tif eventtmp then\r\n\t\tValueChangeUtil.DataChangeEvent(eventtmp, \"parentTableEvent\")\r\n\tend\r\nend\r\n\r\n--- 进行数据验证,将对照表中存在而目标表中不存在键补充至目标表中\r\n--- @param _table table 目标表\r\n--- @param _contrast table 对照表\r\nfunction ValueChangeUtil.VerifyTable(_table, _contrast)\r\n\tfor k, v in pairs(_contrast) do\r\n\t\t-- 如果值为table则向下递归\r\n\t\tif type(v) == \"table\" then\r\n if not _table[k] then\r\n\t\t\t\t_table[k] = table.shallowcopy(v)\r\n\t\t\telse\r\n\t\t\t\tValueChangeUtil.VerifyTable(_table[k], v)\r\n\t\t\tend\r\n\t\telse\r\n\t\t\t-- 若值不是table,则直接校对\r\n if not _table[k] then _table[k] = v end\r\n\t\tend\r\n\tend\r\nend\r\n\r\nreturn ValueChangeUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Framework","guid":[756373066,1240223530,2766704094,915472147],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Framework"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"FrameworkConfigModule","guid":[3532304481,3064676706,3106629846,811992798],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"FrameworkConfigModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 框架配置\r\n--- @module Framework Global FrameworkConfig\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Yuancheng Zhang\r\nlocal FrameworkConfig = {\r\n -- 启动心跳\r\n HeartbeatStart = true,\r\n Server = {\r\n -- 心跳包间隔时间,单位:秒\r\n HeartbeatDelta = 1,\r\n -- 心跳阈值,单位:秒,范围定义如下:\r\n -- 0s -> threshold_1 : connected\r\n -- threshold_1 -> threshold_2 : disconnected, but player can reconnect\r\n -- threshold_2 -> longer : disconnected, remove player\r\n HeartbeatThreshold1 = 9,\r\n HeartbeatThreshold2 = 10,\r\n -- 显示心跳日志\r\n ShowHeartbeatLog = false,\r\n -- 插件中需要使用声明周期的服务器模块目录\r\n PluginModules = {},\r\n -- 插件中服务器需要生成的CustomEvent, 模块中必须得有ServerEvents\r\n PluginEvents = {}\r\n },\r\n Client = {\r\n -- 心跳包间隔时间,单位:秒\r\n HeartbeatDelta = 1,\r\n -- 心跳阈值,单位:秒,范围定义如下:\r\n -- 0s -> threshold_1 : connected\r\n -- threshold_1 -> threshold_2 : disconnected, weak network, can reconnect\r\n -- threshold_2 -> longer : disconnected, quit server\r\n HeartbeatThreshold1 = 9,\r\n HeartbeatThreshold2 = 10,\r\n -- 显示心跳日志\r\n ShowHeartbeatLog = false,\r\n -- 插件中需要使用声明周期的客户端模块目录\r\n PluginModules = {},\r\n -- 插件中客户端需要生成的CustomEvent,模块中必须得有ClientEvents\r\n PluginEvents = {}\r\n }\r\n}\r\n\r\nreturn FrameworkConfig\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ServerHeartbeatModule","guid":[4201358790,153109338,3063225923,2088318747],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ServerHeartbeatModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 游戏服务器心跳\r\n--- @module Server Heartbeat, Server-side\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Yuancheng Zhang\r\nlocal ServerHeartbeat = {}\r\n\r\n-- Localize global vars\r\nlocal Setting = FrameworkConfig.Server\r\n\r\n-- 心跳包间隔时间,单位:秒\r\nlocal HEARTBEAT_DELTA = Setting.HeartbeatDelta\r\n\r\n-- 心跳阈值,单位:秒,范围定义如下:\r\n-- 0s -> threshold_1 : connected\r\n-- threshold_1 -> threshold_2 : disconnected, but player can rejoin\r\n-- threshold_2 -> longer : disconnected, remove player\r\nlocal HEARTBEAT_THRESHOLD_1 = Setting.HeartbeatThreshold1 * 1000 -- second => ms\r\nlocal HEARTBEAT_THRESHOLD_2 = Setting.HeartbeatThreshold2 * 1000 -- second => ms\r\n\r\n-- 玩家心跳连接状态\r\nlocal HeartbeatEnum = {\r\n CONNECT = 1, -- 在线\r\n DISCONNECT = 2 -- 离线\r\n}\r\n\r\n-- 正在运行\r\nlocal running = false\r\n\r\n-- 上一次客户端发来的心跳时间戳缓存\r\nlocal cache = {}\r\n\r\n-- 临时变量\r\nlocal diff -- 时间戳插值\r\nlocal sTmpTs, cTmpTs -- 时间戳缓存\r\n\r\n--- 打印心跳日志\r\nlocal PrintHb = Setting.ShowHeartbeatLog and function(...)\r\n print('[Heartbeat][Server]', ...)\r\n end or function()\r\n end\r\n\r\n--! 外部接口\r\n\r\n--- 初始化心跳包\r\nfunction ServerHeartbeat.Init()\r\n print('[Heartbeat][Server] Init()')\r\n CheckSetting()\r\n InitEventsAndListeners()\r\nend\r\n\r\n--- 开始发出心跳\r\nfunction ServerHeartbeat.Start()\r\n print('[Heartbeat][Server] Start()')\r\n running = true\r\n while (running) do\r\n Update()\r\n wait(HEARTBEAT_DELTA)\r\n end\r\nend\r\n\r\n--- 停止心跳\r\nfunction ServerHeartbeat.Stop()\r\n print('[Heartbeat][Server] Stop()')\r\n running = false\r\nend\r\n\r\n--! 私有函数\r\n\r\n--- 校验心跳参数\r\nfunction CheckSetting()\r\n assert(HEARTBEAT_DELTA >= 1, '[Heartbeat][Server] HEARTBEAT_DELTA 必须大于1秒')\r\n assert(HEARTBEAT_THRESHOLD_1 >= HEARTBEAT_DELTA, '[Heartbeat][Server] HEARTBEAT_THRESHOLD_1 >= HEARTBEAT_DELTA')\r\n assert(\r\n HEARTBEAT_THRESHOLD_2 >= HEARTBEAT_THRESHOLD_1,\r\n '[Heartbeat][Server] HEARTBEAT_THRESHOLD_2 >= HEARTBEAT_THRESHOLD_1'\r\n )\r\nend\r\n\r\n--- 初始化事件和绑定Handler\r\nfunction InitEventsAndListeners()\r\n if world.S_Event == nil then\r\n world:CreateObject('FolderObject', 'S_Event', world)\r\n end\r\n world:CreateObject('CustomEvent', 'HeartbeatC2SEvent', world.S_Event)\r\n world.S_Event.HeartbeatC2SEvent:Connect(HeartbeatC2SEventHandler)\r\n\r\n -- OnAwakeEvent(玩家加入前初始化)\r\n -- OnPlayerJoinEvent(玩家第一次加入,类似现在的OnPlayerAdded)\r\n -- OnPlayerRejoinEvent(玩家离开房间后重新进入同一个房间)\r\n -- OnPlayerDisconnectEvent(未接收到玩家心跳等待重连,在服务器第二个阶段)\r\n -- OnPlayerReconnectEvent(玩家断线后重连)\r\n -- OnPlayerLeaveEvent(玩家彻底离开,退出房间)\r\n world:CreateObject('CustomEvent', 'OnAwakeEvent', world.S_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerJoinEvent', world.S_Event)\r\n -- world:CreateObject('CustomEvent', 'OnPlayerRejoinEvent', world.S_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerDisconnectEvent', world.S_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerReconnectEvent', world.S_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerLeaveEvent', world.S_Event)\r\n\r\n -- 玩家退出,发出OnPlayerLeaveEvent\r\n world.OnPlayerRemoved:Connect(\r\n function(_player)\r\n if cache[_player] then\r\n print('[Heartbeat][Server] OnPlayerLeaveEvent, 玩家主动离开游戏,', _player)\r\n NetUtil.Fire_S('OnPlayerLeaveEvent', _player)\r\n end\r\n end\r\n )\r\nend\r\n\r\n--- Update心跳\r\nfunction Update()\r\n for p, v in pairs(cache) do\r\n if p and not p:IsNull() then\r\n sTmpTs = Timer.GetTimeMillisecond()\r\n cTmpTs = v.cTimestamp\r\n PrintHb(string.format('=> S = %s, C = %s, %s', sTmpTs, cTmpTs, p))\r\n CheckPlayerStates(p, sTmpTs)\r\n NetUtil.Fire_C('HeartbeatS2CEvent', p, sTmpTs, cTmpTs)\r\n else\r\n --* remove nil key from cache\r\n cache[p] = nil\r\n end\r\n end\r\nend\r\n\r\n--- 心跳事件Handler\r\nfunction HeartbeatC2SEventHandler(_player, _cTimestamp, _sTimestamp)\r\n if not running then\r\n return\r\n end\r\n PrintHb(string.format('<= S = %s, C = %s, %s', _sTimestamp, _cTimestamp, _player))\r\n CheckPlayerJoin(_player)\r\n cache[_player].cTimestamp = _cTimestamp\r\n cache[_player].sTimestamp = _sTimestamp\r\nend\r\n\r\n--- 收包时,检查玩家是否加入或重连\r\nfunction CheckPlayerJoin(_player)\r\n if not cache[_player] then\r\n --* 玩家新加入 OnPlayerJoinEvent\r\n print('[Heartbeat][Server] OnPlayerJoinEvent, 新玩家加入,', _player)\r\n NetUtil.Fire_S('OnPlayerJoinEvent', _player)\r\n cache[_player] = {\r\n state = HeartbeatEnum.CONNECT\r\n }\r\n elseif cache[_player].state == HeartbeatEnum.DISCONNECT then\r\n --* 玩家断线重连 OnPlayerReconnectEvent\r\n print('[Heartbeat][Server] OnPlayerReconnectEvent, 玩家断线重连,', _player)\r\n NetUtil.Fire_S('OnPlayerReconnectEvent', _player)\r\n cache[_player].state = HeartbeatEnum.CONNECT\r\n end\r\nend\r\n\r\n--- 发包时,检查玩家是否掉线\r\nfunction CheckPlayerStates(_player, _sTimestam)\r\n if not cache[_player].sTimestamp then\r\n return\r\n end\r\n diff = _sTimestam - cache[_player].sTimestamp\r\n PrintHb(string.format('==========================================> diff = %s, %s', diff * .001, _player))\r\n\r\n if cache[_player].state == HeartbeatEnum.CONNECT and diff > HEARTBEAT_THRESHOLD_1 then\r\n --* 玩家断线 OnPlayerReconnectEvent\r\n print('[Heartbeat][Server] OnPlayerDisconnectEvent, 玩家离线, 等待断线重连,', _player)\r\n NetUtil.Fire_S('OnPlayerDisconnectEvent', _player)\r\n cache[_player].state = HeartbeatEnum.DISCONNECT\r\n elseif cache[_player].state == HeartbeatEnum.DISCONNECT and diff > HEARTBEAT_THRESHOLD_2 then\r\n --* 玩家彻底断线,剔除玩家\r\n print('[Heartbeat][Server] OnPlayerLeaveEvent, 剔除离线玩家,', _player)\r\n NetUtil.Fire_S('OnPlayerLeaveEvent', _player)\r\n print('[Heartbeat][Server] OnPlayerLeave, 发送客户端离线事件,', _player)\r\n NetUtil.Fire_C('OnPlayerLeaveEvent', _player)\r\n cache[_player] = nil\r\n end\r\nend\r\n\r\nreturn ServerHeartbeat\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ClientHeartbeatModule","guid":[2448379167,2886812809,2770381150,2704067643],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ClientHeartbeatModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 游戏心跳\r\n--- @module Client Heartbeat, Client-side\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Yuancheng Zhang\r\nlocal ClientHeartbeat = {}\r\n\r\n-- Localize global vars\r\nlocal Setting = FrameworkConfig.Client\r\n\r\n-- 心跳包间隔时间,单位:秒\r\nlocal HEARTBEAT_DELTA = Setting.HeartbeatDelta\r\n\r\n-- 心跳阈值,单位:秒,范围定义如下:\r\n-- 0s -> threshold_1 : connected\r\n-- threshold_1 -> threshold_2 : disconnected, weak network\r\n-- threshold_2 -> longer : disconnected, quit server\r\nlocal HEARTBEAT_THRESHOLD_1 = Setting.HeartbeatThreshold1 * 1000 -- second => ms\r\nlocal HEARTBEAT_THRESHOLD_2 = Setting.HeartbeatThreshold2 * 1000 -- second => ms\r\n\r\n-- 玩家心跳连接状态\r\nlocal HeartbeatEnum = {\r\n CONNECT = 1, -- 在线\r\n DISCONNECT = 2 -- 离线\r\n}\r\n\r\n-- 正在运行\r\nlocal running = false\r\n\r\n-- 上一次服务器发来的心跳时间戳缓存\r\nlocal cache = {\r\n sTimestamp = nil,\r\n cTimestamp = nil\r\n}\r\n\r\n-- 临时变量\r\nlocal diff -- 时间戳插值\r\nlocal sTmpTs, cTmpTs -- 时间戳缓存\r\n\r\n--- 打印心跳日志\r\nlocal PrintHb = Setting.ShowHeartbeatLog and function(...)\r\n print('[Heartbeat][Client]', ...)\r\n end or function()\r\n end\r\n\r\n--! 外部接口\r\n\r\n--- 初始化心跳包\r\nfunction ClientHeartbeat.Init()\r\n print('[Heartbeat][Client] Init()')\r\n CheckSetting()\r\n InitEventsAndListeners()\r\nend\r\n\r\n--- 开始发出心跳\r\nfunction ClientHeartbeat.Start()\r\n print('[Heartbeat][Client] Start()')\r\n local cTimestamp\r\n running = true\r\n while (running) do\r\n Update()\r\n wait(HEARTBEAT_DELTA)\r\n end\r\nend\r\n\r\n-- 停止心跳\r\nfunction ClientHeartbeat.Stop()\r\n print('[Heartbeat][Client] Stop()')\r\n running = false\r\nend\r\n\r\n--! 私有函数\r\n\r\n-- 校验心跳参数\r\nfunction CheckSetting()\r\n assert(HEARTBEAT_DELTA >= 1, '[Heartbeat][Client] HEARTBEAT_DELTA 必须大于1秒')\r\n assert(HEARTBEAT_THRESHOLD_1 >= HEARTBEAT_DELTA, '[Heartbeat][Client] HEARTBEAT_THRESHOLD_1 >= HEARTBEAT_DELTA')\r\n assert(\r\n HEARTBEAT_THRESHOLD_2 >= HEARTBEAT_THRESHOLD_1,\r\n '[Heartbeat][Client] HEARTBEAT_THRESHOLD_2 >= HEARTBEAT_THRESHOLD_1'\r\n )\r\nend\r\n\r\n--- 初始化事件和绑定Handler\r\nfunction InitEventsAndListeners()\r\n if localPlayer.C_Event == nil then\r\n world:CreateObject('FolderObject', 'C_Event', localPlayer)\r\n end\r\n world:CreateObject('CustomEvent', 'HeartbeatS2CEvent', localPlayer.C_Event)\r\n localPlayer.C_Event.HeartbeatS2CEvent:Connect(HeartbeatS2CEventHandler)\r\n\r\n -- OnPlayerJoinEvent(玩家第一次加入,类似现在的OnPlayerAdded)\r\n -- OnPlayerRejoinEvent(玩家离线后重新进入同一个房间)\r\n -- OnPlayerDisconnectEvent(未接收到服务器心跳,在客户端第二个阶段,玩家离线可重连,弱网,转菊花)\r\n -- OnPlayerReconnectEvent(玩家断线后重连)\r\n -- OnPlayerLeaveEvent(玩家彻底离开,退出房间)\r\n world:CreateObject('CustomEvent', 'OnPlayerJoinEvent', localPlayer.C_Event)\r\n -- world:CreateObject('CustomEvent', 'OnPlayerRejoinEvent', localPlayer.C_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerDisconnectEvent', localPlayer.C_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerReconnectEvent', localPlayer.C_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerLeaveEvent', localPlayer.C_Event)\r\n\r\n -- 掉线直接退出(默认,可选)\r\n localPlayer.C_Event.OnPlayerLeaveEvent:Connect(QuitGame)\r\nend\r\n\r\n-- Update心跳\r\nfunction Update()\r\n cTmpTs = Timer.GetTimeMillisecond()\r\n sTmpTs = cache.sTimestamp\r\n PrintHb(string.format('=> C = %s, S = %s, %s', cTmpTs, sTmpTs, localPlayer))\r\n CheckPlayerState(p, cTmpTs)\r\n NetUtil.Fire_S('HeartbeatC2SEvent', localPlayer, cTmpTs, sTmpTs)\r\nend\r\n\r\n--- 心跳事件Handler\r\nfunction HeartbeatS2CEventHandler(_stimestamp, _cTimestamp)\r\n if not running then\r\n return\r\n end\r\n PrintHb(string.format('<= C = %s, S = %s, %s', _cTimestamp, _stimestamp, localPlayer))\r\n CheckPlayerJoin(_player, _sTimestamp)\r\n cache.sTimestamp = _stimestamp\r\n cache.cTimestamp = _cTimestamp\r\nend\r\n\r\n--- 收包时,检查玩家是否连接服务器,或者重新连接服务器\r\nfunction CheckPlayerJoin(_player, _sTimestamp)\r\n if not cache.sTimestamp then\r\n --* 玩家新加入 OnPlayerJoinEvent\r\n print('[Heartbeat][Client] OnPlayerJoinEvent, 新玩家加入,', localPlayer)\r\n NetUtil.Fire_C('OnPlayerJoinEvent', localPlayer)\r\n cache.state = HeartbeatEnum.CONNECT\r\n elseif cache.state == HeartbeatEnum.DISCONNECT then\r\n --* 玩家断线重连 OnPlayerReconnectEvent\r\n print('[Heartbeat][Client] OnPlayerReconnectEvent, 玩家断线重连,', localPlayer)\r\n NetUtil.Fire_C('OnPlayerReconnectEvent', localPlayer)\r\n cache.state = HeartbeatEnum.CONNECT\r\n end\r\nend\r\n\r\n--- 发包时,检查玩家是否连接服务器\r\nfunction CheckPlayerState(_player, _cTimestamp)\r\n if not cache.cTimestamp then\r\n return\r\n end\r\n diff = _cTimestamp - cache.cTimestamp\r\n PrintHb(string.format('==========================================> diff = %s, %s', diff * .001, localPlayer))\r\n if cache.state == HeartbeatEnum.CONNECT and diff > HEARTBEAT_THRESHOLD_1 then\r\n --* 玩家断线,弱网环境\r\n print('[Heartbeat][Client] OnPlayerDisconnectEvent, 玩家离线, 弱网环境,', localPlayer)\r\n NetUtil.Fire_C('OnPlayerDisconnectEvent', localPlayer)\r\n cache.state = HeartbeatEnum.DISCONNECT\r\n elseif cache.state == HeartbeatEnum.DISCONNECT and diff > HEARTBEAT_THRESHOLD_2 then\r\n --* 玩家断线, 退出游戏\r\n -- QuitGame()\r\n NetUtil.Fire_C('OnPlayerLeaveEvent', localPlayer)\r\n end\r\nend\r\n\r\n--- 退出游戏\r\nfunction QuitGame()\r\n print('[Heartbeat][Client] Game.Quit(), 玩家退出游戏')\r\n Game.Quit()\r\nend\r\n\r\nreturn ClientHeartbeat\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ServerModule","guid":[2630836918,4157751681,2512528126,1629800954],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ServerModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 游戏服务器主逻辑\r\n--- @module Game Server, Server-side\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Yuancheng Zhang\r\nlocal Server = {}\r\n\r\n-- Localize global vars\r\nlocal CsvUtil, ModuleUtil = CsvUtil, ModuleUtil\r\nlocal Config = FrameworkConfig.Server\r\n\r\n-- 已经初始化,正在运行\r\nlocal initialized, running = false, false\r\n\r\n-- 含有InitDefault(),Init(),Update()的模块列表\r\nlocal initDefaultList, initList, updateList = {}, {}, {}\r\n\r\n--- 运行服务器\r\nfunction Server:Run()\r\n print('[Server] Run()')\r\n InitServer()\r\n StartUpdate()\r\nend\r\n\r\n--- 停止Update\r\nfunction Server:Stop()\r\n print('[Server] Stop()')\r\n running = false\r\n ServerHeartbeat.Stop()\r\nend\r\n\r\n--- 初始化\r\nfunction InitServer()\r\n if initialized then\r\n return\r\n end\r\n print('[Server] InitServer()')\r\n InitRandomSeed()\r\n InitHeartbeat()\r\n InitServerCustomEvents()\r\n InitCsvAndXls()\r\n GenInitAndUpdateList()\r\n RunInitDefault()\r\n InitOtherModules()\r\n initialized = true\r\nend\r\n\r\n--- 初始化服务器的CustomEvent\r\nfunction InitServerCustomEvents()\r\n print('[Server] InitServerCustomEvents()')\r\n if world.S_Event == nil then\r\n world:CreateObject('FolderObject', 'S_Event', world)\r\n end\r\n\r\n -- 将插件中的CustomEvent放入Events.ClientEvents中\r\n for _, m in pairs(Config.PluginEvents) do\r\n local evts = _G[m].ServerEvents\r\n assert(evts, string.format('[Server] %s 中不存在ServerEvents,请检查模块,或从FrameworkConfig删除此配置', m))\r\n for __, evt in pairs(evts) do\r\n if not table.exists(Events.ServerEvents, evt) then\r\n table.insert(Events.ServerEvents, evt)\r\n end\r\n end\r\n end\r\n\r\n -- 生成CustomEvent节点\r\n for _, evt in pairs(Events.ServerEvents) do\r\n if world.S_Event[evt] == nil then\r\n world:CreateObject('CustomEvent', evt, world.S_Event)\r\n end\r\n end\r\nend\r\n\r\n--- 初始化心跳包\r\nfunction InitHeartbeat()\r\n assert(ServerHeartbeat, '[Server][Heartbeat] 找不到ServerHeartbeat,请联系张远程')\r\n ServerHeartbeat.Init()\r\nend\r\n\r\n--- 生成框架需要的节点\r\nfunction InitCsvAndXls()\r\n if not world.Global.Csv then\r\n world:CreateObject('FolderObject', 'Csv', world.Global)\r\n end\r\n if not world.Global.Xls then\r\n world:CreateObject('FolderObject', 'Xls', world.Global)\r\n end\r\nend\r\n\r\n--- 生成需要Init和Update的模块列表\r\nfunction GenInitAndUpdateList()\r\n ModuleUtil.GetModuleListWithFunc(Module.S_Module, 'InitDefault', initDefaultList)\r\n ModuleUtil.GetModuleListWithFunc(Module.S_Module, 'Init', initList)\r\n ModuleUtil.GetModuleListWithFunc(Module.S_Module, 'Update', updateList)\r\n for _, m in pairs(FrameworkConfig.Server.PluginModules) do\r\n ModuleUtil.GetModuleListWithFunc(m, 'InitDefault', initDefaultList)\r\n ModuleUtil.GetModuleListWithFunc(m, 'Init', initList)\r\n ModuleUtil.GetModuleListWithFunc(m, 'Update', updateList)\r\n end\r\nend\r\n\r\n--- 执行默认的Init方法\r\nfunction RunInitDefault()\r\n for _, m in ipairs(initDefaultList) do\r\n m:InitDefault(m)\r\n end\r\nend\r\n\r\n--- 初始化服务器随机种子\r\nfunction InitRandomSeed()\r\n math.randomseed(os.time())\r\nend\r\n\r\n--- 初始化包含Init()方法的模块\r\nfunction InitOtherModules()\r\n for _, m in ipairs(initList) do\r\n m:Init()\r\n end\r\nend\r\n\r\n--- 开始Update\r\nfunction StartUpdate()\r\n print('[Server] StartUpdate()')\r\n assert(not running, '[Server] StartUpdate() 正在运行')\r\n\r\n running = true\r\n\r\n -- 开启心跳\r\n if FrameworkConfig.HeartbeatStart then\r\n invoke(ServerHeartbeat.Start)\r\n end\r\n\r\n local dt = 0 -- delta time 每帧时间\r\n local tt = 0 -- total time 游戏总时间\r\n local now = Timer.GetTimeMillisecond --时间函数缓存\r\n local prev, curr = now() / 1000, nil -- two timestamps\r\n\r\n while (running and wait()) do\r\n curr = now() / 1000\r\n dt = curr - prev\r\n tt = tt + dt\r\n prev = curr\r\n UpdateServer(dt, tt)\r\n end\r\nend\r\n\r\n--- Update函数\r\n--- @param dt delta time 每帧时间\r\nfunction UpdateServer(_dt, _tt)\r\n for _, m in ipairs(updateList) do\r\n m:Update(_dt, _tt)\r\n end\r\nend\r\n\r\nreturn Server\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ClientModule","guid":[2395017761,2395361208,3179134946,4246078414],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ClientModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 游戏客户端主逻辑\r\n-- @module Game Manager, Client-side\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nlocal Client = {}\r\n\r\n-- Localize global vars\r\nlocal CsvUtil, XslUitl, ModuleUtil = CsvUtil, XslUitl, ModuleUtil\r\nlocal Config = FrameworkConfig.Client\r\n\r\n-- 已经初始化,正在运行\r\nlocal initialized, running = false, false\r\n\r\n-- 含有InitDefault(),Init(),Update()的模块列表\r\nlocal initDefaultList, initList, updateList = {}, {}, {}\r\n\r\n--- 运行客户端\r\nfunction Client:Run()\r\n print('[Client] Run()')\r\n InitClient()\r\n StartUpdate()\r\nend\r\n\r\n--- 停止Update\r\nfunction Client:Stop()\r\n print('[Client] Stop()')\r\n running = false\r\n ClientHeartbeat.Stop()\r\nend\r\n\r\n--- 初始化\r\nfunction InitClient()\r\n if initialized then\r\n return\r\n end\r\n print('[Client] InitClient()')\r\n InitRandomSeed()\r\n InitHeartbeat()\r\n InitClientCustomEvents()\r\n PreloadCsv()\r\n GenInitAndUpdateList()\r\n RunInitDefault()\r\n InitOtherModules()\r\n initialized = true\r\nend\r\n\r\n--- 初始化心跳包\r\nfunction InitHeartbeat()\r\n assert(ClientHeartbeat, '[Client][Heartbeat] 找不到ClientHeartbeat,请联系张远程')\r\n ClientHeartbeat.Init()\r\nend\r\n\r\n--- 初始化客户端的CustomEvent\r\nfunction InitClientCustomEvents()\r\n if localPlayer.C_Event == nil then\r\n world:CreateObject('FolderObject', 'C_Event', localPlayer)\r\n end\r\n\r\n -- 将插件中的CustomEvent放入Events.ClientEvents中\r\n for _, m in pairs(Config.PluginEvents) do\r\n local evts = _G[m].ClientEvents\r\n assert(evts, string.format('[Client] %s 中不存在ClientEvents,请检查模块,或从FrameworkConfig删除此配置', m))\r\n for __, evt in pairs(evts) do\r\n if not table.exists(Events.ClientEvents, evt) then\r\n table.insert(Events.ClientEvents, evt)\r\n end\r\n end\r\n end\r\n\r\n -- 生成CustomEvent节点\r\n for _, evt in pairs(Events.ClientEvents) do\r\n if localPlayer.C_Event[evt] == nil then\r\n world:CreateObject('CustomEvent', evt, localPlayer.C_Event)\r\n end\r\n end\r\nend\r\n\r\n--- 生成需要Init和Update的模块列表\r\nfunction GenInitAndUpdateList()\r\n ModuleUtil.GetModuleListWithFunc(Module.C_Module, 'InitDefault', initDefaultList)\r\n ModuleUtil.GetModuleListWithFunc(Module.C_Module, 'Init', initList)\r\n ModuleUtil.GetModuleListWithFunc(Module.C_Module, 'Update', updateList)\r\n for _, m in pairs(Config.PluginModules) do\r\n ModuleUtil.GetModuleListWithFunc(m, 'InitDefault', initDefaultList)\r\n ModuleUtil.GetModuleListWithFunc(m, 'Init', initList)\r\n ModuleUtil.GetModuleListWithFunc(m, 'Update', updateList)\r\n end\r\nend\r\n\r\n--- 执行默认的Init方法\r\nfunction RunInitDefault()\r\n for _, m in ipairs(initDefaultList) do\r\n m:InitDefault(m)\r\n end\r\nend\r\n\r\n--- 初始化客户端随机种子\r\nfunction InitRandomSeed()\r\n math.randomseed(os.time())\r\nend\r\n\r\n--- 预加载所有的CSV表格\r\nfunction PreloadCsv()\r\n print('[Client] PreloadCsv()')\r\n if Config.ClientPreload and #Config.ClientPreload > 0 then\r\n CsvUtil.PreloadCsv(Config.ClientPreload, Csv, Config)\r\n end\r\nend\r\n\r\n--- 初始化包含Init()方法的模块\r\nfunction InitOtherModules()\r\n for _, m in ipairs(initList) do\r\n m:Init()\r\n end\r\nend\r\n\r\n--- 开始Update\r\nfunction StartUpdate()\r\n print('[Client] StartUpdate()')\r\n assert(not running, '[Client] StartUpdate() 正在运行')\r\n\r\n running = true\r\n\r\n -- 开启心跳\r\n if FrameworkConfig.HeartbeatStart then\r\n invoke(ClientHeartbeat.Start)\r\n end\r\n\r\n local dt = 0 -- delta time 每帧时间\r\n local tt = 0 -- total time 游戏总时间\r\n local now = Timer.GetTimeMillisecond --时间函数缓存\r\n local prev, curr = now() / 1000, nil -- two timestamps\r\n\r\n while (running and wait()) do\r\n curr = now() / 1000\r\n dt = curr - prev\r\n tt = tt + dt\r\n prev = curr\r\n UpdateClient(dt, tt)\r\n end\r\nend\r\n\r\n--- Update函数\r\n-- @param dt delta time 每帧时间\r\nfunction UpdateClient(_dt, _tt)\r\n for _, m in ipairs(updateList) do\r\n m:Update(_dt, _tt)\r\n end\r\nend\r\n\r\nreturn Client\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ServerBaseModule","guid":[2864628079,2098938141,2169623745,1280720590],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ServerBaseModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 服务器模块基础类, Server Module Base Class\r\n-- @module ServerBase, Server-side\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nlocal ServerBase = class('ServerBase')\r\n\r\nfunction ServerBase:GetSelf()\r\n return self\r\nend\r\n\r\n--- 加载的时候运行的代码\r\nfunction ServerBase:InitDefault(_module)\r\n -- print(string.format('[ServerBase][%s] InitDefault()', self.name))\r\n -- 初始化默认监听事件\r\n EventUtil.LinkConnects(world.S_Event, _module, self)\r\nend\r\n\r\nreturn ServerBase\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ClientBaseModule","guid":[1957111763,3183362511,3175239265,4168800515],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ClientBaseModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 客户端模块基础类, Client Module Base Class\r\n-- @module ClientBase, Client-side\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nlocal ClientBase = class('ClientBase')\r\n\r\nfunction ClientBase:GetSelf()\r\n return self\r\nend\r\n\r\n--- 加载的时候运行的代码\r\nfunction ClientBase:InitDefault(_module)\r\n -- print(string.format('[ClientBase][%s] InitDefault()', self.name))\r\n -- 初始化默认监听事件\r\n EventUtil.LinkConnects(localPlayer.C_Event, _module, self)\r\nend\r\n\r\nreturn ClientBase\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Plugin","guid":[2555489138,626346364,2851147263,1364204280],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Plugin"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"FUNC_Guide","guid":[684900468,2789166335,2666626870,2188212943],"parentGuid":[2555489138,626346364,2851147263,1364204280],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"FUNC_Guide"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"GuideSystemModule","guid":[2070445638,524698677,3104412276,3887386575],"parentGuid":[684900468,2789166335,2666626870,2188212943],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GuideSystemModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---引导系统\r\n---@module GuideSystem\r\n---@copyright Lilith Games, Avatar Team\r\n---@author Sid Zhang, Yuancheng Zhang\r\n\r\nlocal GuideSystem = {}\r\n\r\n--- 引导的枚举类型\r\nGuideSystem.Enum = {\r\n ClickGuide = 'ClickGuide'\r\n}\r\n\r\n--- 显示强引导Ui\r\n---@param _type Int 1:点击\r\n---@param _position Vector2 生成引导UI在屏幕的位置,Anchors值\r\n---@param _area Vector2 响应范围,Size\r\n---@param _content String 文本介绍,nil则不显示文本\r\nfunction GuideSystem:ShowGuide(_type, _position, _area, _content, _callBack, ...)\r\n local args = {...}\r\n if _type == GuideSystem.Enum.ClickGuide then\r\n local GuideNode = world:CreateInstance('ClickGuide', 'ClickGuide', localPlayer.Local)\r\n if _position then\r\n GuideNode.ImgDot.AnchorsX = Vector2(_position.X, _position.X)\r\n GuideNode.ImgDot.AnchorsY = Vector2(_position.Y, _position.Y)\r\n end\r\n if _content then\r\n GuideNode.ImgDot.FigTextBox.TxtContent.Text = _content\r\n else\r\n GuideNode.ImgDot.FigTextBox.Visible = false\r\n end\r\n if _area then\r\n GuideNode.ImgDot.BtnClose.Size = _area\r\n end\r\n GuideNode.ImgDot.BtnClose.OnClick:Connect(\r\n function()\r\n if _callBack and type(_callBack) == 'function' then\r\n _callBack(table.unpack(args))\r\n end\r\n GuideNode:Destroy()\r\n end\r\n )\r\n else\r\n error('param #1 :_type error')\r\n end\r\nend\r\n\r\nreturn GuideSystem\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Define","guid":[2737473538,3344386760,2691081356,600893088],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Define"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"GlobalDataModule","guid":[1468398307,3695920924,2815319038,3596233864],"parentGuid":[2737473538,3344386760,2691081356,600893088],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GlobalDataModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 全局变量的定义,全部定义在GlobalData这张表下面,用于全局可修改的参数\r\n--- @module GlobalData Defines\r\n--- @copyright Lilith Games, Avatar Team\r\nlocal GlobalData = {}\r\n\r\n-- Test only\r\nGlobalData.PlayerData = {}\r\n\r\nreturn GlobalData\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ConstModule","guid":[2512024877,1669350945,3212507937,4057137757],"parentGuid":[2737473538,3344386760,2691081356,600893088],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ConstModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 全局常量的定义,全部定义在Const这张表下面,用于定义全局常量参数或者枚举类型\r\n-- @module Constant Defines\r\n-- @copyright Lilith Games, Avatar Team\r\nlocal Const = {}\r\n\r\n-- e.g. (need DELETE)\r\nConst.MAX_PLAYERS = 4\r\n\r\n--语言枚举\r\nConst.LanguageEnum = {\r\n CHS = 'CHS', -- 简体中文\r\n CHT = 'CHT', -- 繁体中文\r\n EN = 'EN', -- 英文\r\n JP = 'JP' -- 日文\r\n}\r\n\r\nreturn Const\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"EventsModule","guid":[3985164048,3143978161,3122137539,183444988],"parentGuid":[2737473538,3344386760,2691081356,600893088],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"EventsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- CustomEvent的定义,用于事件动态生成\r\n-- @module Event Defines\r\n-- @copyright Lilith Games, Avatar Team\r\nlocal Events = {}\r\n\r\n-- 服务器事件列表\r\nEvents.ServerEvents = {}\r\n\r\n-- 客户端事件列表\r\nEvents.ClientEvents = {\r\n --通知事件\r\n 'NoticeEvent'\r\n}\r\nreturn Events\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ConfigModule","guid":[2110201861,617827622,2622314683,2453149769],"parentGuid":[2737473538,3344386760,2691081356,600893088],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ConfigModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- CSV表格的定义,用于CSV表格载入\r\n-- @module Csv Defines\r\n-- @copyright Lilith Games, Avatar Team\r\nlocal Config = {}\r\n\r\n-- 服务器预加载CSV\r\n-- csv: 对应的CSV表名\r\n-- name: Config里面的lua table名称, 可自定义, 默认和csv相同\r\n-- ids: 表格主键, 支持多主键\r\nConfig.ServerPreload = {}\r\n\r\n-- 客户端预加载CSV\r\n-- csv: 对应的CSV表名\r\n-- name: Config里面的lua table名称, 可自定义, 默认和csv相同\r\n-- ids: 表格主键, 支持多主键\r\nConfig.ClientPreload = {}\r\n\r\nreturn Config\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Module","guid":[1574945571,3461236255,2335300488,1410656948],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"S_Module","guid":[760758812,3011002765,2609222735,3236427035],"parentGuid":[1574945571,3461236255,2335300488,1410656948],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"S_Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"C_Module","guid":[3651921500,3839968525,2765586875,2228388046],"parentGuid":[1574945571,3461236255,2335300488,1410656948],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"C_Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"LanguageUtilModule","guid":[1327910891,3901181274,2716120486,2266956693],"parentGuid":[3651921500,3839968525,2765586875,2228388046],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LanguageUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 语言包模块:根据游戏内语言设置返回对应的+\r\n-- @module LanguageUtil, Client-side\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Xiexy, Yuancheng Zhang\r\n---@module LanguageUtil\r\nlocal LanguageUtil, this = ModuleUtil.New('LanguageUtil', ClientBase)\r\nlocal lang = Config.GlobalSetting.DefaultLanguage\r\nlocal defaultLang = Const.LanguageEnum.CHS\r\n\r\n--- 设置当前语言\r\nfunction LanguageUtil.SetLanguage(_lang)\r\n assert(Const.LanguageEnum[_lang], string.format('[LanguageUtil] %s 语言码不存在,请检查ConstModule', _lang))\r\n print(string.format('[LanguageUtil] 更改当前语言:%s => %s', lang, _lang))\r\n lang = _lang\r\nend\r\n\r\n--- 根据ID返回当前游戏语言对应的文本信息,如果对应语言为空,默认返回'*'+中文内容\r\n-- @param @number _id LanguagePack.xls中的编号\r\nfunction LanguageUtil.GetText(_id)\r\n assert(not string.isnilorempty(_id), '[LanguageUtil] 翻译ID为空,请检查策划表和LanguagePack')\r\n assert(\r\n Config.LanguagePack[_id],\r\n string.format('[LanguageUtil] LanguagePack[%s] 不存在对应翻译ID,请检查策划表和LanguagePack', _id)\r\n )\r\n local text = Config.LanguagePack[_id][lang]\r\n if string.isnilorempty(text) then\r\n print(string.format('[LanguageUtil] LanguagePack[%s][%s] 不存在对应语言翻译内容,默认使用中文', _id, lang))\r\n text = '*' .. Config.LanguagePack[_id][defaultLang]\r\n end\r\n return text\r\nend\r\n\r\nreturn LanguageUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Cls_Module","guid":[522079265,1772703138,3001358313,3723987968],"parentGuid":[1574945571,3461236255,2335300488,1410656948],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Cls_Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"Editor_Module","guid":[1822279021,412438229,2868384553,280931416],"parentGuid":[1574945571,3461236255,2335300488,1410656948],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Editor_Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"Xls","guid":[3561931272,2228374286,3075622864,2620868908],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Xls"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"Example1XlsModule","guid":[1154807438,2590788022,3089941547,2078280727],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Example1XlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/ExampleTable1.xlsx\r\n\r\nlocal Example1Xls = {\r\n [1] = {\r\n house = {\r\n id = 1,\r\n name = 'house',\r\n use_money = 1000,\r\n use_food = 2.33,\r\n is_init = true,\r\n defense = 100,\r\n args_int_arr = {1, 2, 3},\r\n args_float_arr = {1.23, 2, 3.23},\r\n args_string_arr = {'sdf', '23e', 's'},\r\n args_bool_arr = {true, false, true},\r\n args_vect2 = Vector2(-1, 0.5),\r\n args_vect3 = Vector3(2, 0.3, -4),\r\n args_euler = EulerDegree(12, 23, 43),\r\n args_color = Color(129, 12, 3, 0),\r\n args_lua = function() print(23) end,\r\n Des1 = 'Example1_Des1_1_house',\r\n Des2 = 'Example1_Des2_1_house'\r\n },\r\n MMM = {\r\n id = 1,\r\n name = 'MMM',\r\n use_money = 123,\r\n use_food = 336.2,\r\n is_init = true,\r\n defense = 0,\r\n args_int_arr = {1, 2, 3},\r\n args_float_arr = {1, 2.3445, 3},\r\n args_string_arr = {'你好', '你在哪'},\r\n args_bool_arr = {true, false},\r\n args_vect2 = Vector2(0, 4),\r\n args_vect3 = Vector3(-2, 3, 5),\r\n args_euler = EulerDegree(0, 0, 0),\r\n args_color = Color(0, 0, 0, 0),\r\n args_lua = {a = 2, b='234'},\r\n Des1 = 'Example1_Des1_1_MMM',\r\n Des2 = 'Example1_Des2_1_MMM'\r\n },\r\n ddd = {\r\n id = 1,\r\n name = 'ddd',\r\n use_money = 456,\r\n use_food = 222.33665,\r\n is_init = false,\r\n defense = 130,\r\n args_int_arr = {3, 2, 5},\r\n args_float_arr = {3, 2, 2.5},\r\n args_string_arr = {'我在这里啊', '你在那', '呢'},\r\n args_bool_arr = {false, true},\r\n args_vect2 = Vector2(2, 0.5),\r\n args_vect3 = Vector3(0.6, 3, -8.4),\r\n args_euler = EulerDegree(0, 0, 0),\r\n args_color = Color(0, 0, 0, 0),\r\n args_lua = nil,\r\n Des1 = 'Example1_Des1_1_ddd',\r\n Des2 = 'Example1_Des2_1_ddd'\r\n }\r\n },\r\n [2] = {\r\n farm = {\r\n id = 2,\r\n name = 'farm',\r\n use_money = 100,\r\n use_food = 220.0,\r\n is_init = false,\r\n defense = 200,\r\n args_int_arr = {2, 3},\r\n args_float_arr = {200.3, 3, 234.23},\r\n args_string_arr = {'df', 'ssd', 'dd', 'dd'},\r\n args_bool_arr = {},\r\n args_vect2 = Vector2.Zero,\r\n args_vect3 = Vector3.Zero,\r\n args_euler = EulerDegree(0, 0, 0),\r\n args_color = Color(0, 0, 0, 0),\r\n args_lua = nil,\r\n Des1 = 'Example1_Des1_2_farm',\r\n Des2 = 'Example1_Des2_2_farm'\r\n },\r\n MMM = {\r\n id = 2,\r\n name = 'MMM',\r\n use_money = 0,\r\n use_food = 22.1,\r\n is_init = false,\r\n defense = 234,\r\n args_int_arr = {3, 6, 6, 7},\r\n args_float_arr = {3, 6.3, 6, 7},\r\n args_string_arr = {'ss', 'd', 'd', 'd'},\r\n args_bool_arr = {true, true},\r\n args_vect2 = Vector2.Zero,\r\n args_vect3 = Vector3.Zero,\r\n args_euler = EulerDegree(0, 0, 0),\r\n args_color = Color(0, 0, 0, 0),\r\n args_lua = \"还没有添加检查\",\r\n Des1 = 'Example1_Des1_2_MMM',\r\n Des2 = 'Example1_Des2_2_MMM'\r\n }\r\n }\r\n}\r\n\r\nreturn Example1Xls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"Example2XlsModule","guid":[3966878609,3696577721,2175744138,2596481351],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Example2XlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/ExampleTable1.xlsx\r\n\r\nlocal Example2Xls = {\r\n [1] = {\r\n id = 1,\r\n name = 'house',\r\n use_money = 1000,\r\n use_food = 2.33,\r\n is_init = true,\r\n defense = 100,\r\n args1 = {1, 2, 3},\r\n args2 = {1.23, 2, 3.23},\r\n args3 = {'sdf', '23e', 's'},\r\n args4 = {true, false, true}\r\n },\r\n [2] = {\r\n id = 2,\r\n name = '你好吗?',\r\n use_money = 123,\r\n use_food = 336.2,\r\n is_init = true,\r\n defense = 0,\r\n args1 = {1, 2, 3},\r\n args2 = {1, 2.3445, 3},\r\n args3 = {'你好', '你在哪'},\r\n args4 = {true, false}\r\n },\r\n [3] = {\r\n id = 3,\r\n name = '',\r\n use_money = 456,\r\n use_food = 222.33665,\r\n is_init = false,\r\n defense = 130,\r\n args1 = {3, 2, 5},\r\n args2 = {3, 2, 2.5},\r\n args3 = {'我在这里啊', '你在那', '呢'},\r\n args4 = {false, true}\r\n },\r\n [4] = {\r\n id = 4,\r\n name = 'farm',\r\n use_money = 100,\r\n use_food = 220.0,\r\n is_init = false,\r\n defense = 200,\r\n args1 = {2, 3},\r\n args2 = {200.3, 3, 234.23},\r\n args3 = {'df', 'ssd', 'dd', 'dd'},\r\n args4 = {}\r\n },\r\n [5] = {\r\n id = 5,\r\n name = 'house5',\r\n use_money = 0,\r\n use_food = 22.1,\r\n is_init = false,\r\n defense = 234,\r\n args1 = {3, 6, 6, 7},\r\n args2 = {3, 6.3, 6, 7},\r\n args3 = {'ss', 'd', 'd', 'd'},\r\n args4 = {true, true}\r\n },\r\n [6] = {\r\n id = 6,\r\n name = 'horse3',\r\n use_money = 200,\r\n use_food = 0,\r\n is_init = false,\r\n defense = 333,\r\n args1 = {},\r\n args2 = {},\r\n args3 = {'2e', 'w', 'e', 'we'},\r\n args4 = {false, false, false, false}\r\n }\r\n}\r\n\r\nreturn Example2Xls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"GlobalSettingXlsModule","guid":[915862330,741691007,3064296356,1451895238],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GlobalSettingXlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/GlobalSetting.xls\r\n\r\nlocal GlobalSettingXls = {\r\n DefaultLanguage = {\r\n Key = 'DefaultLanguage',\r\n Value = \"CHS\"\r\n },\r\n PlayerPosition = {\r\n Key = 'PlayerPosition',\r\n Value = Vector3(0,-1,0)\r\n },\r\n PlayerRotation = {\r\n Key = 'PlayerRotation',\r\n Value = Euler(90,0,0)\r\n },\r\n MaxPlayerNumber = {\r\n Key = 'MaxPlayerNumber',\r\n Value = 100.0\r\n },\r\n ScoreRate = {\r\n Key = 'ScoreRate',\r\n Value = 12.5\r\n }\r\n}\r\n\r\nreturn GlobalSettingXls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"LanguagePackXlsModule","guid":[700825784,2334608193,2463848717,1647971739],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LanguagePackXlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/LanguagePack.xls\r\n\r\nlocal LanguagePackXls = {\r\n Example1_Des1_1_house = {\r\n ID = 'Example1_Des1_1_house',\r\n CHS = '我真的很想%s吃饭',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_1_house = {\r\n ID = 'Example1_Des2_1_house',\r\n CHS = '做什么',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des1_1_MMM = {\r\n ID = 'Example1_Des1_1_MMM',\r\n CHS = '我饿了',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_1_MMM = {\r\n ID = 'Example1_Des2_1_MMM',\r\n CHS = '工作是什么',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des1_1_ddd = {\r\n ID = 'Example1_Des1_1_ddd',\r\n CHS = '到底什么时候能吃饭',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_1_ddd = {\r\n ID = 'Example1_Des2_1_ddd',\r\n CHS = '我是谁',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des1_2_farm = {\r\n ID = 'Example1_Des1_2_farm',\r\n CHS = '今天晚上吃什么',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_2_farm = {\r\n ID = 'Example1_Des2_2_farm',\r\n CHS = '我从哪里来',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des1_2_MMM = {\r\n ID = 'Example1_Des1_2_MMM',\r\n CHS = '下班就去吃饭吧',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_2_MMM = {\r\n ID = 'Example1_Des2_2_MMM',\r\n CHS = '就这样吧',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n }\r\n}\r\n\r\nreturn LanguagePackXls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"SoundXlsModule","guid":[3708642180,138366140,2150857251,245324002],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"SoundXlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/Sound.xls\r\n\r\nlocal SoundXls = {\r\n test_01 = {\r\n Type = 1,\r\n ID = 'test_01',\r\n IsLoop = false,\r\n Volume = 0,\r\n FileName = '',\r\n Detail = '',\r\n Duration = 0,\r\n CoverPlay = false\r\n }\r\n}\r\n\r\nreturn SoundXls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Csv","guid":[294313154,1450328249,2442680246,272497625],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Csv"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"S_Code","guid":[630163054,4187112824,2753371896,1919329423],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"S_Code"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cScriptObject","name":"ServerMainScript","guid":[344478647,1240680084,3003146376,138016020],"parentGuid":[630163054,4187112824,2753371896,1919329423],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ServerMainScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 服务器代码入口\r\n-- @script Server Main Function\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nServer:Run()\r\n"}}]},{"class":"cFolderObject","name":"SpawnLocations","guid":[3060414528,2729002180,2754306229,2661207025],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"SpawnLocations"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cStartPortal","name":"StartPortal00","guid":[2311234828,1873955616,2655392405,2525362369],"parentGuid":[3060414528,2729002180,2754306229,2661207025],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[10.0,1.5,-10.0],"m_localRotation":[0.0,-0.3826,-0.0,0.9238]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StartPortal00"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sStartPortalComponent","data":{}}]},{"class":"cStartPortal","name":"StartPortal01","guid":[2880873389,3573762209,2456263445,1546252123],"parentGuid":[3060414528,2729002180,2754306229,2661207025],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[10.0,1.5,10.0],"m_localRotation":[0.0,0.9238,0.0,-0.3826]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StartPortal01"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sStartPortalComponent","data":{}}]},{"class":"cStartPortal","name":"StartPortal02","guid":[3258803195,2884980247,2417960272,149547500],"parentGuid":[3060414528,2729002180,2754306229,2661207025],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[-10.0,1.5,-10.0],"m_localRotation":[0.0,0.3826,0.0,0.9238]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StartPortal02"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sStartPortalComponent","data":{}}]},{"class":"cStartPortal","name":"StartPortal03","guid":[4114460255,208751620,2876153555,2633735762],"parentGuid":[3060414528,2729002180,2754306229,2661207025],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[-10.0,1.5,10.0],"m_localRotation":[0.0,0.9238,0.0,0.3826]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StartPortal03"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sStartPortalComponent","data":{}}]},{"class":"cAudioSource","name":"BGM","guid":[1596674502,2319271992,2900844459,2801180766],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"BGM"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sAudioSourceComponent","data":{"m_playonawake":true,"m_loop":true}}]},{"class":"cSkydome","name":"Sky","guid":[3956874685,1991789763,3220541584,1456680764],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Sky"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sSkydomeComponent","data":{"m_latitude":-0.0,"m_shadowDistance":100.0}},{"id":11,"class":"sDateTimeComponent","data":{"m_clocktime":15.8}},{"id":12,"class":"sFogComponent","data":{"m_fogStart":60.0,"m_fogEnd":280.0,"m_fogColor":[0.0006,0.5378,0.4872,1.0]}}]},{"class":"cFolderObject","name":"Players","guid":[3397398191,850542637,3138524115,1333674022],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Players"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[718760084,1257915969,2962466066,2150526306],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[438081159,4019275113,2394302102,997050187],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[154289415,229392516,2254790969,287930189],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[1151332762,2713075851,2532352188,4124889855],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[784260868,2609333952,2774312238,2440002730],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[36220398,4069804567,3087648968,1513344952],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[301805571,3183757047,2970218906,1035586513],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[329052330,2108966125,2246354006,3727422990],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cTerrainObject","name":"Terrain","guid":[980033814,3447341367,2913170761,3546742988],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[-62.3988,-10.7204,-283.398],"m_localRotation":[-0.0,0.0927,-0.0,0.9956]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Terrain"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":610,"class":"sTerrainComponent","data":{"m_memStreamBuffer":{"m_guid":[4256450858,2503622707,2701896730,3388076770],"m_revision":-1,"m_type":"kByteStream","m_autoGenerated":true},"m_memStreamBuffer2":{"m_guid":[1237361744,2766752550,2609897848,3988635486],"m_revision":-1,"m_type":"kByteStream","m_autoGenerated":true},"m_memWaterStreamBuffer":[{"m_guid":[2896065380,3843705948,2645856480,1113501019],"m_revision":-1,"m_type":"kByteStream","m_autoGenerated":true},{"m_guid":[1940847020,2248952508,2340679954,660790013],"m_revision":-1,"m_type":"kByteStream","m_autoGenerated":true}],"m_emptyPlaceholder":true,"m_terrainIndex":[0,1,2,3,4,5,6,7],"m_textures":[{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false}]}},{"id":13,"class":"sRenderComponent","data":{}},{"id":15,"class":"sRigidBodyComponent","data":{"m_frictionRate":0.4499,"m_rough":0.4,"m_restitution":0.3,"m_responseContact":false,"m_statusFlag":10}}]},{"class":"cStaticSpaceFolderObject","name":"StaticSpace","guid":[2324326883,3665710638,3070246021,2579386889],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StaticSpace"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cPrimitiveObject","name":"BaseFloor","guid":[2469667971,3052095412,2449968994,2749907796],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[0.0,0.8,0.0]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"BaseFloor"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sBasicShapeComponent","data":{"m_size":[50.0,0.2,50.0]}},{"id":12,"class":"sRigidBodyComponent","data":{"m_density":2400.0,"m_frictionRate":0.4,"m_rough":0.4499,"m_restitution":0.4499,"m_statusFlag":10}},{"id":13,"class":"sMaterialComponent","data":{"m_uvScale":3.0,"m_materialType":"kSubwayTiles"}},{"id":30,"class":"sPrimitiveRenderComponent","data":{}},{"id":3,"class":"sSizeComponent","data":{}},{"id":666,"class":"sStateSyncComponent","data":{}}]}]}]} \ No newline at end of file +{"sandbox_archive_version":62,"resources":[{"default":[{"guid":[953679908,159793237,2165885860,1104393525],"revisions":[{"type":"kTexture","name":"Dot","revision":-1,"autoGenerated":false,"rawdata":"0WSam051Rl009610096102BZK0Dc7k0EtjeM-2)Z3IG5A4M|8uQUCw|F8}}lF9-$z0046*ldJ#$010qNS#tmY4#WTe4#WYKD-Ig~00MDIL_t(&fz_J5ZWBQiMvupW1}PN$iy%am6d*;Ao;dG7LK>igm!N|lWOx8lVG@)AQadivfeA<;k(Pi2L?Ag94aj^Hvy0bDEIhOJj`v8TwMM(XN8_KFxpPaB$cO=EfVt>81xx~mfWtrw_yK$bwt+Xm2JjmAoh5K7gZxF{2CxVm_jvv*@C0}Syz(-TKZQf=zX1BTy8!q>DiE_(>#yj3|0P$q}GVVV_K3Hh9O^;RLw~`O*M+|C4W%TYW~hWtVSMA@?}Z25VrfUqCTHMKlxB!m3~w;p!`B8!~d|TlxupDngref6JC?XXxRhK13PhtN!@Ut@e~K^buZbG9Qirynq%Cl01fCJ`P)HM-1+MnkNhm~BAAXlvp_Q*`D-CfJh%>aFOp0j5J+crs;x)1fUqIZ=}I6F3ms#DjK8sg+rgFuZboH9TMs0EgBr3Q53&B$^{6AzZ5yN7)V)Wf+rQ3smPy8*uw#>JVt@r$H<16a#l#f>!*>6vuK>f&lmb+r0{1_$#zxwo(3)r;x(u|lFmt5AF=p)6lWkO7x}o&Ycwa|^R6ygY7Z`9YY&!yBOcFx10G{*51Kv#S>$8N*ktYH=sHce>SF>sGsaaP@3B=M4Q$m%CrjWze+IZe`C-a-.NkvXXu0mjfiBL{Q4GJ0x0000DNk~Le0000l0000l2nGNE0COjktN;K232;bRa{vGi!~g&e!~vBn4jTXf0&z)1K~!jg)tbF-6G0S4kH>-rDHQyRAVifEAVrX#IPXA08lZxgpo1P{cmPsi5|jc`J1)|J2}mH3mVg9AAUPHd$b1yDi`PpmJhS$W_ei6)M!UX8XK!1b76z@-mR*+A5Oj8ZPzdeVtrfZaypN*^s5vJdffGRsM>k)`-PpT9OuqAzzkM%}F{=D+1KiBh$bnlV#Nl6gu!md^a?u%_hwS%vp3c0go1b6FHwoOAr*h2C7y4wR@MluVc2)VF1}+WaHK;t2&PiGyvG{rvXCNmR`MAw%G$-Kmt}a<>4=!PA50-!<9?yRR9%E|{nmz(q`T4M(S=I+exefX0}sEUu_qAq`krn4Xo|7Rx1+7KK{-hKIa^tv^8Zn!h)q(%PNJxe*A<$@)TZ2d63tc*vRucEw1jqEZE%*OE^6F0FGPImUSsC1#MYWhO{Z9TrCB0>rRXy=n;F9!qe^me9JFDrnCT&AA1ZRhG3fbI1ZBV@I43vJVn72;fWVu62xSZfc*oVH_cb%a`tKO%k5IQv9=}%HBI9;6JO~y!by^#AVgv4Il?o$%F@=W%)ww`CJfWEwo+A~+ta1m+7!Yeg|Z&aR>tqs@tG=7OKXJv01Y5#BUf*A(L3i1i9(d3vc|D^Wb2Kx%52m>OpnKp9JomG{P|eQa-@wtyS2~LX0fXUx)erjH4|sIF3f{Yy(XC!)GEDDsu(WnVu%T38F+IoE3O*z_5m(t?oDABgn<`CUKlNKL~uedeis~(Ah<_!#pP=EH##L@M54(f)D;Y;mc9scMpjCf)TJv+x)!Le))0(8kz}e++BtSsCJ*KFEW6NZB@1_8E}-rQ{^U(a|(wu_y2$?LOFZRkrk&G^Cg><0+$)L4T&$wkv=^pZ}-+S1nxw(&E*2y0+{solE2Hbhj`2EAf55DkSKOg;3TtK?+%aogD7P^3{LfUccFh>e;%u$BbIphH7!Tc3Hkqb;yN|{$zokCWxLi_8@Z8qQ{3ZD>6P258+KsW_EL@R73xY@9000cQNklFPN3Z7so%lkC(6p3=lB<3=lA2fFNNB8YEzVpg{rz3=lA2(11Y#1Pu@*Xn>#r0tN{ZBw&yLK>`~vNPr;04c@>T_7~2by_em6XXgDg@4x%~JkNceg?H{d^DJM^nK?6a&cfQ-8UtW2a2i+!b^+UGo~;5;fxEyJ;CbVtn^P8gF0LEE5n!pncgDbN;Nv7C66^F;T)zSD)$pD>z~LI+Ber0%?Ew_zEbx+ggaz=y&s@+4xA=f&gvF$v2MzT#c)BjGTfqLvw%N?*1r~Bxoje;){yv8NlYT`-x-6~}z|n|y8787jz_!U}C-5fl2Cx9U5B$zld?AhkpA;Xmn5D~eu9v_lqTT1fw*}to+-%Id7Ye*rY@n-_>lXfheCPTWC?+g`1C#H>STMQA3Ut+S`3B|O;LeJDoC7WrwqUYeCF!E&`YxhPRbhcyuj}|NSO#3Hp|825i`n-!R4)Pcw*I0_KiOaVkJ;L_?QgAiUZ(SSZb4AzYTzRpr40s3}OxSm5g{pa1LAoZcyuplt*Aw^c3))ItCFq)4Y!lzSOOyH%NL*9KZ4Y2*NL(Tr5|>DZ#3hm;afxI|Tp}3~mq>=hC6Xa=iDXDzA{i2wNJf{$wLRPI&t;m22m_RO5tiGdT1uI5{nSiCtJq^ca?~Vhj&3%l;C}!1xjq>2JWP1Y@jBm)s(>o3`=^w&H7&Rjl~cR-6L9#)KHlBh2MKsyMD%khrFl71vgk&_=|&(7%8^Gk;#j3d0u>)kZo|lvHl9JWf=1FyYUB?h;>qZ#VG$xia7;;Z@%ataf9J6&q77#1Y~;RwblyWVy<5n7DAl$H1}3ccu^)SW(m)iQer+;wsMDnA?lxHff79#PAWe;%pR&E1nEEyFY{JaiqyDGL`cr8d=ab%c4s#Hw7f(f&zz-AW>+km$wpEo9dzCygn?iK925}MFY@lELK>fg7;wGmelcMoy97x#d72`?Ugz-m?|N0zHuR*7?~&i(Bqep~Diwr?4-U?&34cVnRpUxCvoB(CO(Dfs?^R|BiDoWxvcNMPU3{Cp9!+G~@~6W~v*g09Rr3ts_@cq)qz~}gDK!$jRKM9Cg0gKpy}*$Pt*t)ecQ%jxOpN-A;hp4Tv4*4s1pF3KG|_iJjXyLEUD0Y-roe_hUr17q~Y27hMw9JmsNp>b^jX`(4FOc)ZT$6C4MC1s!6R^$n6gvGxku7Q(k;cy1O^~ylv66Lw#c{TzbOI)HXzU`$Euv_91Rq&;S$|NpP1@8vD#3icmyYHbg3r@L#e>Av4!@KB9%k#vEsA7FTp}4=5?9-K=tXgM_#SpiTw-8ITp}3~mq>;}5%WQlcQV(JxQ5R{TOHRat|jc&hf6y40=p^lqQ*W%kEkN9Wh_V2GJz#2w$1SnOKSF0g+4`(C^MnIjFoe{hm{@)2@A}sEn~H9UIM?A=vVZNBH}uS3F9o)HdjZ$5iIqgL^fbz-C|6M-ZC7)tl%Nq&vGMbCM2%?z#r68em9RLEVsZ`$4^9$@wvsL*gtwoVa+m_b4vs}x-tpe}yr8q27HB}6VJj)eZfXE4Ih0WDc-RB|GT$^H!*h*Y~M|{s&s&BCfu&29M~uJfyyC5tJ3BnH4nah(ga&wxvm%Sh)qNn8nE(Q{_SbuG}=3G1sG`n`!2Cz1slFtg(F)#1yqTncpv%W(&|fCM&JoVZR0+GRwRK`Y1w%9Z97-N1X|#l^W!dD>*uYq2)+4*vRsqNg~^wJ*>vrn+erE+l@xXn{p++a)i8?!peK`HEr*_eBp_#43O0kx?z|P=vP|>PIxhB6jkjE2Ny;iH2DuztDC@^Bc}?^(Y3wBKZPp>m-BR<3L!%e_Elf_>)$T17MN#Rou()70uLv771Ad?t7k(2=6(jLhiBWEaI<%Fv5;Iw?T&7W3O=$df&XO%_GWkk3GhT%NKcbI3A~u`zp@=r;Q0hrBL#((~2p1<%a8F0Q|bTRqA>ic+9O)IK-S%rN;G1;CZ0UF5tTgeTW`YwSanzdpt!rx4jr3RmHUe{19j}!YZGM;o-e4R#jX8pAzRDzptS$(Je}yL@i28aPETu*^3fi@%JtD%X0;0-m^B`$!^2QO3jL0rm71y}paC!S{+@L?*q=!kKTH?dxFF6w9-^80j-os}FmdIvo;$Y*x9IpXqRlVKM``C^I)^(0}o{fR4?hj46MMq4BhY6?86wPyg%of4&HZ2#}xvu?zHg}1;!Y0xY63Ketbe1+P7r+6+b+q5Zzx)+x3Eg`blXC*5ZOa9AE+}-aUKP{Y;V9vi`<7M<+P7R_muf>NB6P8~9exZvALE%jcfoQwUJ@>Oe2j4M1yK)w0PlL9XV_)vxAGcv!E!l{N3;q3PZIU;HGW|^a)kxm64y<_F|85WdrZwT;ldRe5gw#VZqea8E97_|3sH#riJ6|P0&j#~)FsR1I1tfB)+-JF5Ej;^ZJs>3D6Xf3llm^9q`r0YjIhVZu+{xa%nQ5d79IZ*PC5P)aSH+ACuS;=VWN5`_N864T#isd(G0sgr>xQC*@taNhlB<8`;HbCbX#1mplIIqkeeHvMH}2@xQtCLV(T>pC-58TvRiccGO5h+9CMLcMf!OUix?3ZJVS-tzzlcp(DEw?Cjm#&E!-x3zTIzpTabY$3>x>85Z`y#HG4OCGtm4pS|o^#FeGj=sCbUa5?C!2f@Q1Oz?Yqbog87^Uy`16DVeWDy0=4N+>8phBU%jLMhG+~^qSiOv~#ZWE6g8t08w&EE!IacZ8YB)D|oSKg__04k4@+NjEU_ANAk`=@K&aPr7NozkR*JsPMkq21Bc~)ean@w@Qmk|~dyaIfivcvxa;()H}usI@h.NkvXXu0mjfiBL{Q4GJ0x0000DNk~Le0001&0002M2nGNE00|WFHUIzs32;bRa{vGf6951U69E94oEQKA0)1&zSaeuTOgdw4Z7yMCZ(?OGcx`Y10RUB0MlCo1SWQeiV{dIPVPtP&WiEJaZ~!=sos`{fn=llBU&V57Hf>-`3Mp1u)sb$^1zXid>TVs|Nvr~9wo^!7Z5Ml_y}}F)tnIW`D-y^){>~ph=NzB3HDx!#f~8o?Duus({DeUA6kbPpkk^^m$n!fbuI|1p*xe0FI6V0Pj$6`}btxy5I?Y}7wYkH?Q3xJdH+`B=(wq>Vkhwa?RLv8x5T6h>_|6KA(B%!5w7CYcu0D!ovu7%u8!hzVpFcylc)t{U_90WN0lO<@>>ffqzx7%gx_a6&MC7aWlwxJPrvBD}X_t|EK;}EnNfB;?;J#w(KpPOXKczw=erE@qNE4BZHHshcZm78wbjj`V>T#-Txf=Au}01UWEL_t(|ob8=2n3csB$3MG|m#_v55HS1<5HMhXAYlm_Bw&D`K>`E}5HMiSfI$KT4G<(~fS>^a1_=@*V2}Vo0vj+$fFQvQ-oP977tWo%m)(75=KVA8zx(|>&wZYSckVm$EMLx-1GzzX9*n@SZ!s;Tql}wqUXC0Tkpc@RE9j1@OV-eWz=BpO~Rfmg`qc45_#ce3Wo2Y|OhC3cOcrpsSYa7XE*H=lT{XCMFuBJHbk%bC2Ibt~&We4U11=J_V6t8%>7wQOE}~6UVS!n%>-a5L23)J5ueqd)mg_(A_X;!MQ%n?f@i*{4&+`mhB#44^&2sI+zX|=EdW8jWlCbp>CFq*C&U>DXfinsEyo)Uk@)#l4Sc4+Dcp{=$cz>6W_c`lll@!TvNtv4`66WTp}3~mq>=hC6Xa=iDXDzA{i2wNQT5Ek|A-4WJp{h84{OBMwi63J=`QI+CQwBE{RJF42er5L*f$2khnxLBrcH*iAy9y;u6V_xI{7}E|Cn0OC&?$63LLbL^32Ukqn7TBtzm7$#|{p0SticSc;|HSR#{EEF;hTHV4%OB(8FN1bjg_<@hRA8SOLXu+C$bQYdW){>^#TL<*(HSW(ce$bbu&KrU0PzN2FWD_r$gsnPWiiED~aT#k&m;s`!|0*+UFM=$8ognl*LBq=eXsVXe6vMs%;i3+IIdcdxTcg9*H)F#M#Q_&zkoe6e_qB4!xs_NMmkZHRBo|6PE>a=;m>~V5?_9AH}L)B=O2N4R1+3hh-8hHYj?|XO(|lzGT^ui_ft=%AW)6j$6~#SM-b`;yQ;3<1E!SS4Y4REcKy8Heh1iVoZtNG91CI;33-2awBRcB(D9yAJkKRH;*MOx4>4%PehOLxy7T{KYB`G%`%vCOv1>sT+6_%0`KvqI4n{%RSbwc%N1LI$O&qN&DB!f=ONQvn_`aGN?dz9C#$A;XU8ZlhSP^Q)L8iz$922EayfoeQ+jfJ>CiNar|7TnS&%b7sYLEzs5p>#G|2y@?ekk_8(uv*PmA;mfdG3Uvs}aR<171U6WlxK0P!Wki-iE64@PmF5-QzCFVHTgx@i?IB!0hWfkkZFB`<>R!VanVied@(MGsiSDu3pYQ7!CHgtr^&M>NDDcJiSsq@3G{hFK)P&~`@i8_sU^CVMI?y=`A;;(`*!j3z)L5AF8uW=E2-@L2MBg%4*J;sU47lj`IPZ}6Y9($OB{Gmg{iOc7HM%d|8-;o?4HnFg_wtgVtU;GzSGwf)y5RQGopE=K7DT#L};$?X+5@Hh@#pOHv*8Y(^F*Gw?gKvuzB|J5b_ua1z7$Hw2)oCPay}thiPP7bj3~RlGuxJoLv2yT{#F0y!ZHSI{^Z(dI15DK984uD^&|J<2_bQni0#R*(Nfrzhr~vXx?Ef~%*=PZc2MfU@Fx1Y8fa8DSMT#GF#4#`Q_yd7#ZM;JXTah#ph5fO?F3JViLSy%-=>#kB(b5NI>PDxZnr;k_+ZRa^j{66YSjuc0r|ElQn4ElNyq^#m5d7sJ6j-Kk4lPk}3eHY4Jzp^1Lr4K>vzE`ZMn=lYU+92VZv)U((5>tKWxSr73R-ApfXmca^C`1ew@e6Cr7Qym1Ef?6iuKj^FcZs{gCejfS$$H>)mNqRHzyZQ_wBN(O{1s^l-Fq05a{{Jq%LR5WD0Hq~71P?`DB+d+mR1Yew_ISCYC|U?bg{M_ehfSxoo-@@Ehr}TXgs`sm$^mbCFs_`gsqF7sjS>cN6%BMLX()xFGKAmt%}sz5CRYrYTe{w-_y;u$tMR_WtvI5&8lnvXN}&2r81CUz-cJo(6y6~q0`u3{%iYdw`M5f&4?0(_gY!~X-~fUfJXIU;lb0000Y0AB-N0sDb3fiHl)z;57kU?;Ex*bZz1wgOv{f0G(JNr6%uUBD>dY+xiX0yqm84x9<}FK%c6>;papHUpmkn}ChL$G`?GjHKceDAjZZFb)`-Y@?HHaFK(`U^}or+14i88sM`c2PJ+Alxmm=TmXDK*(P9jBz3kMSOxqq+1>+I7CIt1Aq7e~jRz(H7Xudo7Zy95(s&nG0lWh&2UZt5966Q(r5w6|Dam$8vh^!;MCG?1cq`eKC7YhZ6ik6qoMFIslI>F9ykdt_c^?380{>06EyWI}GbvEoYY_0=WV;MFx6skl*N4Cxz-!62qtMZG5Cuy9`T$oZ+f?j&_jHb?c76x_8gUx_QggL-S&?EyRLVGEksoGa8tcY?BKf%V*eae6!QE*?GFX$U${uFs;tsW~c8stoye}ZvzXGZGFLGsVoIbI_Cp(fZ13<6k#RSJ=*LfT}5^KFuJgZ)y+=e3$X6sBCG}G11|vY7c_>-QlR)u1g-`ebWYknE<_0ov>c-QT8bmrXxf9mLI!;&wfU*#JD7Y-;sC0R>9$MgTVeH)5aj^R|Unr{|kk_op21JgmF8*=ani4cA-)JPka9J=~UVC{Vh08gNsxP09IMJ7{%uzJYalO1N{euH$A0@}Qirw+whH+4kgot;$iL9B&SAGxjxbUJlag++2!1P?hW^U`>S0&g4G5@1Ds*;6K>ITS+KTS|$OvBwJpyo2{YMskt<#YlxXjt5Z3zkE{EDCzEY?PS>au1&Vcg&P}$wz5-r^b!EPUHI+&G49B{bufn>R^RO0}mux$8x<(~*dZ5ezzK1oY#kLK*EpB#VO7WVV%rmj>=GYbh|E7c6`8zdGh6A?(w_)#|#I&AP$K~6R&nF)aZe!~U>zj7|!ap}?KV7V~y@CDu*(Jk|sy)iVm~e!h;lE&tnqN0V)9#PgO`rv%Cs!1uAgMzO4>)j_EssfSUtI-+B?`p*X*0bY%G&Qhv-pqvEUm26`oopTe`E%`K>q^GXNVcpR;VBOL&tOFiSw%Qh5ode|@;O=CLVGpei$z_qwDdjeWR>$;dkKkqaZ8PpV}m);&EZ()ktx|HK~L)=C`%2P^{bfR?uob}lSfgYmRXI>@2Y!Yv&ydzuTAhqqLP4?^LaP%y%lly)fxiI%iZQ5mt7@R!3;Y~=5S!MkSoh-mC?gUvv$3x1D>9s87w~7`fhZ$tqpAf;U*MO(FOvT|jYG6L7S}}@m&lRE(CW}WDdjm10Dl7hnEcFwNF{~?5A91AJ7Smc&`i_+6_z^bY_V2M(5=yJ=cPN8^-(nBB(%40-6R}el$&zyDN2`-N?UxCUVD(ViP$_eu3;=#d`a6>~Soh()Xd@EIZZ6iQl;_X+p`tnuehlgoXYJD%o2>s+k13Q<&`+(Eh@`Z=u6L|+X{-R;7D*V9Y~c@ODdM9id!K$(WEo8w*T;&trvrJB~HQ*6Y3=e-kG_rq!|%ajSJv^!AF!RChWF2c12dpAez^wMcoV88p`B{s%jwU%XaC%1M6%1PMlRo;iNyRf&0^`f72s>N6hct7e-$7(L^G?Z@b43xV_w{ZD`XbJ+6c~~uY-Hp{=TeK@st{~kH^%&{?BZ}0~gQQyv`7W}}!XK<1fie`Ean1YOWC=EHqh?wYG5fKa@GeL<2CKm=>jedAN1)t^tySXj5jLT;=71GB+B&Q@K5`y68>_`!!|MV;WYQxyUO}W&pQ2kJ*YX6QJiNB4O5HHM7-u+-+EmMwdsR3$2Z;AWt|q)CSa!Xn0fdgB#B7x7T|w7eqQ98F0el9zc{jd|RJ)p8~=0_6r^iu0H!fwd8?Eh5KTi`AIN6s(pr$r>o*fg7F2ynz3Q6%n5&v0C%E5v%FcvIffa_!bu01N;Zrm+`tH(z|_Ft?h9fGY+fi)UpK1#1t2Q3Rsxonj#WzAy#u9*JHJvN|r#m4jARwe-r*6PDHYK3ahz15Z=lQsCOh_jA9&t*4H3!5^H@!WnvC-uJ#U~~f6DTvBCtr`|h!rWD`B-g+x(3ID)}BDQ%CRrlYt(uHP^4Ve;-5Aoxb{N>WgKv|V*uOGTqh!BwE*87&Q}xo&R%GsOvAU@*h1iK$NnPHK5yfDr*1fbi}r;CN}te^9pOdi{vy&|-Wr{TWkIh(0_93zs^h|oP@ssE-HZ4pw4RD(L9aps#p7k?{vuKgZ>?qyltK9JPSyY~IrbNk;=F|K9lNPmCiLh;f%08og5!cq@ULDbBE{H@Z;`zTSSIu+Bv3*fL@NM_RLv5sc0;8OlwrVSjtjj4EOqQJBE?&Z)o!TEux#j7Z-Me1;9SRrJcAOE^6=E|xmY%I>qLPPY6rSI5vi)BSPh4A2TB)kspA5#0jnJQi%5B_!fH6wrC3HBr>8)f0-Wc#fM+lwQa+va*5-_{{=pD>@Ona^C4Etp}Nh8?gC|ePdoR(GYAnWFK?h6w+?c5fiel`7kJX$z?+Uih)8+8iPdz7epprLdOZ;a_lQ2<>#sC3$e^N?ASoLz;UwG&Wnpk`Mrg29ps}j$u>1j(tU>dOS6LI6Kk?%Kseuib(Z%YP{P(8J*L`M5KB=HGVidnt{?qc%XcSCQ}lryH!}NhZs%r{IwYxjm4?JiCu-!Q*5viu_Sj~qR4i+e<17`*v^l^$oiAZ&Y*8G{k=?!e1(*A)5ZA>*J5vi)sn(q$`YXCzXC*R~BkBC&5r{;$?fFX_pK5-64M5@hG^FtbhcgO;4b`C~Fs?AgLgB!r0z@0w_KJ|}BL@Ml4toB0;Y5)TRcisZ*OEn}Bsj7X1uX#MM`L%I~t?9-iA{7@}`=>VupZ0NE%E5?8wT0GxR|7aTaA$SMigc#X+CQ}coD#V6j+BEDk!lOA{Zksiw*q(GnQ|~9Qf;BN|E&hlFL3A3uZ~2dUP5cXUjsNfaA!^XDAJiiYyad1@Xf%T_of_-h*Vo>?SHcY^bOqki}@=h)8{e*8c7W;jbop76cQK`UtK4&l`kKp5j@uR7C2bKUVu8b~Xr~-p#Y9x`@=nnS_6ByrY3{^6BCJ@rX!;d1`)p1K;u(XE_HWBGu-p`E3n+3wVui4n{<(%~SJR8~BzO9qAvBh*Vf;&F=%YG`}_uT?GB?R6`PxstT?7Pf5Nu2G|^U&?wX~E7BKUQGuVu60G|XN)CG)o4oXC-X*A(e7kmP(K-uKHh=|mmr`9(?D^NB%4jk*)S467IQ|lX{6(}F)bP*A$E>Eq046Q)fKzPUOI5ZukNZoZ|wH{(SmLZ1&WjC-s@Zd9m39g}uNYzZhYCXhyl4(#H$NWD-ecQ3Gh*XcK#t&!5k%6){r;CY5^>}LhaCW2(lnWgDib(Z%YP{P(Swr}AkQ0Fm9Tyal>bMZA@esSQ>;R4ql+S=wj*DL8*jGfV!&BR9vUq7t2L-iUc?>SC19=OD@zlfCIC0K2TIw~`c4V0C@yUvS?NPT!}`dutD4m&nb09H6oIK{EAh?JkFrjN^t?gHf<=f%%+>@OnabsoMzIW8-@3zX%+{=gG<0hc-kAR^^;Dd8{v?#Hs?DBT6hYTzx$Aim?+S47IoQ_F8*S#gx^0;RdLXAtK)_7{=zITwGR9H*y1S>`zLFyJ!B{vuL7ml3{(G4zlTy#>k#j+1}av9E}fkEez|z!@mJfj6Cl(WFZv)ieQLpu9;BFB10@C;>bl*d%8hC}t34JQhe4;>e|(y_0Il!vEwKO|+KYysYITNOKW0>v}w(llVWV}B7T&TxD;0jtOuC_8|qjth^p%3qk|MbC)s$xW=)+h_v4|SgnRyN~AzJ2rO}27!)WXW#_HYC0G{pDkM;vC)@0}_)K83V}B88pUGH_hT2TvO+{#+tN~tf3}6T_%dx+Rw9hQ8Mnk>C5o_=CCNxk0UUuvYHt4a=V?-ohWAXp753YSrpezDjbnHJGnC;kKMDjNqtIbd^VznQ{6DZA-d&~hwIgb&Me2l_s(!*8r-auIhyzSWkeBfH=HAEyI*J3pp>TRs%gLwmG8?eB6%ysB4MWkq>uv+w3fYp33Z=f_UvevQxMBw@q*AbC;uE%OI)LQ&E52*rWJuu&S%=P%*84!_p#^L{?J|C-fmo$L_@B*;GvHy7B#+26*k!)_nw_foEtj68a2Fm-u^Uh;#Kued3wA&P{_B@`)YTPYtpa49F?=EEoa8risiHP4#SnY+{fYo*?SpsDx@T~KgoAA9uAR_6_#Q#V9S**5G$r32diwE`iCr$%y2Kr~bu88!mKUQm}IgVL_|0dxmSp#J?@U-)oIlwI$uPY+Gy9Iw&?9+6!Ua?2kKmm9L-_oUEKdfFH6v@X}{C~8UVYQq|j6m50JmoxQ5->NywMFE3bFmupcnYiKOkxBI*kD{e|JV<$Ia5U9nuY(5^+NnN1>K4jD0_ha0Q(%r3c1jK>nJHgl0@plk*n!#O7<_8;l)_`?yYmAmo(@!f{iq-PaSmplUn?7`2Y&SOr({$ov#KOB)-n2y!RNzUUQCB;0k9(e}}*bm{IgZ3QYhv<2V$g##?wQ-K~xcT`1xVyC@P__b(0PCE`O#^-m^ou?sk!tFP)r7YZbRAZM-fv#r+7T!KuL2LJIPMPMp2%Ynsj7Rhn(%rUtHIp0D^S3GsE1MnBn*1F+w9?*ivwwe?us;e;tFP{4lZG;cfJ4*VSG8+SM&rO_Ar9lt%rvGYjx$2y8417#QRH~bH20qzBU5qCHurSS{wcid|+R#Us;4kuiZfdaO$&|iTSDUSUm_W4qzPVU2gx4l+iwd7fNFS?=w1>kkyeqf{XxV|VRJo`~mn4dZr@P)ff96{sZt68IAfAWP5%IX4eX6JqDa0`^ZpH1o4hyrQUM|v%*9^x^S+zY^nX}wlrBu5ymu%~gMi;+Gkd483+ozu5dXVFA~E&DrXRS6^skOQg4M&0NMnkw?12InD!(S(ol4VV*vnGhx12>{7=z8UmS%FKrskbVX~P7n5h!4Z{%g`dQvrMP@}KCrinP~sY!28g>v#VH3zO2fL{ueEz#i0Q?Tq~hn{X!0%yA-KcPE>7Lg^#j5B!mmJ~KI1l>!A??e@PQeP`?;Y)ZU`(MKT3#~5tQoV)NZbofTyCHrc)H&qLirhBpO!YuQmyox>K@-Fcq(wo`Xmk8b!{f@K?Yj`VF(^`nC1`60*5kDhaXY5vNt&&IauW%@Gq#@X%<#*y=YxqbTv3GO+6=P8CR^>ne>oUBL>~qa7z`hA;`;>W73)8S~aAsvZ&kF3BNhx0ROQY(60@lR#Q?k#BzZqLZ`f=K@Ad=L=P~bM;R$zF>b1lZYy9=L!Ad5N#3Ru_SPl&UA&m!R8G6`yY88ur}klq+Z5if4JrX12dj$Kdnyg()XBzs8gUc-GOx_##m^5Iq+n%?Tc_esjmLOEy*@1!ui%=-P?uCPM?=L1`60ivL6y}wZRvF{{RakolmM|CU7$_C(`*AV_n;|`x2n8fdbZ@*z8n{VGrKCIZLTi0tKvRpxMbdB;t8jVvR`8qhP73QNXoWqvXVh=iNrD6T2gNn>r~_nr30&1Z8=hygTn>4{ztAubQi3EOysA2RJ|SxtCzy;B-_!Qzr%rSXbk%SeIj#_b$4#9{VL)0K6Uf+!DuR><`N1ea-x0vPto-Nw%59{n*>I0PC_|l*73zq0<8etlP2K`8YPGYb*j@2412A|7sO%IB+#^6>-|+yR(*7XZ6mUu2BgJl$J@fIwAY#bd5FGXX_=v(wwd#W-9iaY#Qljm_Lzyv^uGm=X8xqQJ@@e4%QVpGskNj1YSwDrNF8juhmH=U_o+avh~UFS_`r6=@)XmM&&3_x_26_4#_DwUuy^OTC%+Xe3gHVFGT^((HYn%oEu+;jy(i~uRgMCscO$TF$s5V!iU&^bw59cbwf)41xh;SV-Ig$S!g2(b1_BnrTvaL_H?L`i%8-ua`Zlkd<9*aGnI-|%zVO`D54(Ig+kEOB{C~*wIzGiNAVwS~j?AHBbYHPv!l4GeK@tDKq;s3Sa;~fShwhf#SW)5-o?6v-@&?u)e2uS3Y2P?h;@%PJ4q){u3nBj?8Y9bHamgeLoM*7r$DKuGqA6vo1Lc3&eOp~4l0A~v^sm6oxW?Z?q8`81xkH%VO^+aV_m5uurAf%Sl4P^v(Sawhjr&}#=3PkVcokQW8J)Z{!s%8l+JKE_N~%TtebT(_7(QPY0AB-N0sDb3fiHl)z;57kU?;Ex*bZz1wgOv{f0G(JNr6%uUBD>dY+xiX0yqm84x9<}FK%c6>;papHUpmkn}ChL$G`?GjHKceDAjZZFb)`-Y@?HHaFK(`U^}or+14i88sM`c2PJ+Alxmm=TmXDK*(P9jBz3kMSOxqq+1>+I7CIt1Aq7e~jRz(H7Xudo7Zy95(s&nG0lWh&2UZt5966Q(r5w6|Dam$8vh^!;MCG?1cq`eKC7YhZ6ik6qoMFIslI>F9ykdt_c^?380{>06EyWI}GbvEoYY_0=WV;MFx6skl*N4Cxz-!62qtMZG5Cuy9`T$oZ+f?j&_jHb?c76x_8gUx_QggL-S&?EyRLVGEksoGa8tcY?BKf%V*eae6!QE*?GFX$U${uFs;tsW~c8stoye}ZvzXGZGFLGsVoIbI_Cp(fZ13<6k#RSJ=*LfT}5^KFuJgZ)y+=e3$X6sBCG}G11|vY7c_>-QlR)u1g-`ebWYknE<_0ov>c-QT8bmrXxf9mLI!;&wfU*#JD7Y-;sC0R>9$MgTVeH)5aj^R|Unr{|kk_op21JgmF8*=ani4cA-)JPka9J=~UVC{Vh08gNsxP09IMJ7{%uzJYalO1N{euH$A0@}Qirw+whH+4kgot;$iL9B&SAGxjxbUJlag++2!1P?hW^U`>S0&g4G5@1Ds*;6K>ITS+KTS|$OvBwJpyo2{YMskt<#YlxXjt5Z3zkE{EDCzEY?PS>au1&Vcg&P}$wz5-r^b!EPUHI+&G49B{bufn>R^RO0}mux$8x<(~*dZ5ezzK1oY#kLK*EpB#VO7WVV%rmj>=GYbh|E7c6`8zdGh6A?(w_)#|#I&AP$K~6R&nF)aZe!~U>zj7|!ap}?KV7V~y@CDu*(Jk|sy)iVm~e!h;lE&tnqN0V)9#PgO`rv%Cs!1uAgMzO4>)j_EssfSUtI-+B?`p*X*0bY%G&Qhv-pqvEUm26`oopTe`E%`K>q^GXNVcpR;VBOL&tOFiSw%Qh5ode|@;O=CLVGpei$z_qwDdjeWR>$;dkKkqaZ8PpV}m);&EZ()ktx|HK~L)=C`%2P^{bfR?uob}lSfgYmRXI>@2Y!Yv&ydzuTAhqqLP4?^LaP%y%lly)fxiI%iZQ5mt7@R!3;Y~=5S!MkSoh-mC?gUvv$3x1D>9s87w~7`fhZ$tqpAf;U*MO(FOvT|jYG6L7S}}@m&lRE(CW}WDdjm10Dl7hnEcFwNF{~?5A91AJ7Smc&`i_+6_z^bY_V2M(5=yJ=cPN8^-(nBB(%40-6R}el$&zyDN2`-N?UxCUVD(ViP$_eu3;=#d`a6>~Soh()Xd@EIZZ6iQl;_X+p`tnuehlgoXYJD%o2>s+k13Q<&`+(Eh@`Z=u6L|+X{-R;7D*V9Y~c@ODdM9id!K$(WEo8w*T;&trvrJB~HQ*6Y3=e-kG_rq!|%ajSJv^!AF!RChWF2c12dpAez^wMcoV88p`B{s%jwU%XaC%1M6%1PMlRo;iNyRf&0^`f72s>N6hct7e-$7(L^G?Z@b43xV_w{ZD`XbJ+6c~~uY-Hp{=TeK@st{~kH^%&{?BZ}0~gQQyv`7W}}!XK<1fie`Ean1YOWC=EHqh?wYG5fKa@GeL<2CKm=>jedAN1)t^tySXj5jLT;=71GB+B&Q@K5`y68>_`!!|MV;WYQxyUO}W&pQ2kJ*YX6QJiNB4O5HHM7-u+-+EmMwdsR3$2Z;AWt|q)CSa!Xn0fdgB#B7x7T|w7eqQ98F0el9zc{jd|RJ)p8~=0_6r^iu0H!fwd8?Eh5KTi`AIN6s(pr$r>o*fg7F2ynz3Q6%n5&v0C%E5v%FcvIffa_!bu01N;Zrm+`tH(z|_Ft?h9fGY+fi)UpK1#1t2Q3Rsxonj#WzAy#u9*JHJvN|r#m4jARwe-r*6PDHYK3ahz15Z=lQsCOh_jA9&t*4H3!5^H@!WnvC-uJ#U~~f6DTvBCtr`|h!rWD`B-g+x(3ID)}BDQ%CRrlYt(uHP^4Ve;-5Aoxb{N>WgKv|V*uOGTqh!BwE*87&Q}xo&R%GsOvAU@*h1iK$NnPHK5yfDr*1fbi}r;CN}te^9pOdi{vy&|-Wr{TWkIh(0_93zs^h|oP@ssE-HZ4pw4RD(L9aps#p7k?{vuKgZ>?qyltK9JPSyY~IrbNk;=F|K9lNPmCiLh;f%08og5!cq@ULDbBE{H@Z;`zTSSIu+Bv3*fL@NM_RLv5sc0;8OlwrVSjtjj4EOqQJBE?&Z)o!TEux#j7Z-Me1;9SRrJcAOE^6=E|xmY%I>qLPPY6rSI5vi)BSPh4A2TB)kspA5#0jnJQi%5B_!fH6wrC3HBr>8)f0-Wc#fM+lwQa+va*5-_{{=pD>@Ona^C4Etp}Nh8?gC|ePdoR(GYAnWFK?h6w+?c5fiel`7kJX$z?+Uih)8+8iPdz7epprLdOZ;a_lQ2<>#sC3$e^N?ASoLz;UwG&Wnpk`Mrg29ps}j$u>1j(tU>dOS6LI6Kk?%Kseuib(Z%YP{P(8J*L`M5KB=HGVidnt{?qc%XcSCQ}lryH!}NhZs%r{IwYxjm4?JiCu-!Q*5viu_Sj~qR4i+e<17`*v^l^$oiAZ&Y*8G{k=?!e1(*A)5ZA>*J5vi)sn(q$`YXCzXC*R~BkBC&5r{;$?fFX_pK5-64M5@hG^FtbhcgO;4b`C~Fs?AgLgB!r0z@0w_KJ|}BL@Ml4toB0;Y5)TRcisZ*OEn}Bsj7X1uX#MM`L%I~t?9-iA{7@}`=>VupZ0NE%E5?8wT0GxR|7aTaA$SMigc#X+CQ}coD#V6j+BEDk!lOA{Zksiw*q(GnQ|~9Qf;BN|E&hlFL3A3uZ~2dUP5cXUjsNfaA!^XDAJiiYyad1@Xf%T_of_-h*Vo>?SHcY^bOqki}@=h)8{e*8c7W;jbop76cQK`UtK4&l`kKp5j@uR7C2bKUVu8b~Xr~-p#Y9x`@=nnS_6ByrY3{^6BCJ@rX!;d1`)p1K;u(XE_HWBGu-p`E3n+3wVui4n{<(%~SJR8~BzO9qAvBh*Vf;&F=%YG`}_uT?GB?R6`PxstT?7Pf5Nu2G|^U&?wX~E7BKUQGuVu60G|XN)CG)o4oXC-X*A(e7kmP(K-uKHh=|mmr`9(?D^NB%4jk*)S467IQ|lX{6(}F)bP*A$E>Eq046Q)fKzPUOI5ZukNZoZ|wH{(SmLZ1&WjC-s@Zd9m39g}uNYzZhYCXhyl4(#H$NWD-ecQ3Gh*XcK#t&!5k%6){r;CY5^>}LhaCW2(lnWgDib(Z%YP{P(Swr}AkQ0Fm9Tyal>bMZA@esSQ>;R4ql+S=wj*DL8*jGfV!&BR9vUq7t2L-iUc?>SC19=OD@zlfCIC0K2TIw~`c4V0C@yUvS?NPT!}`dutD4m&nb09H6oIK{EAh?JkFrjN^t?gHf<=f%%+>@OnabsoMzIW8-@3zX%+{=gG<0hc-kAR^^;Dd8{v?#Hs?DBT6hYTzx$Aim?+S47IoQ_F8*S#gx^0;RdLXAtK)_7{=zITwGR9H*y1S>`zLFyJ!B{vuL7ml3{(G4zlTy#>k#j+1}av9E}fkEez|z!@mJfj6Cl(WFZv)ieQLpu9;BFB10@C;>bl*d%8hC}t34JQhe4;>e|(y_0Il!vEwKO|+KYysYITNOKW0>v}w(llVWV}B7T&TxD;0jtOuC_8|qjth^p%3qk|MbC)s$xW=)+h_v4|SgnRyN~AzJ2rO}27!)WXW#_HYC0G{pDkM;vC)@0}_)K83V}B88pUGH_hT2TvO+{#+tN~tf3}6T_%dx+Rw9hQ8Mnk>C5o_=CCNxk0UUuvYHt4a=V?-ohWAXp753YSrpezDjbnHJGnC;kKMDjNqtIbd^VznQ{6DZA-d&~hwIgb&Me2l_s(!*8r-auIhyzSWkeBfH=HAEyI*J3pp>TRs%gLwmG8?eB6%ysB4MWkq>uv+w3fYp33Z=f_UvevQxMBw@q*AbC;uE%OI)LQ&E52*rWJuu&S%=P%*84!_p#^L{?J|C-fmo$L_@B*;GvHy7B#+26*k!)_nw_foEtj68a2Fm-u^Uh;#Kued3wA&P{_B@`)YTPYtpa49F?=EEoa8risiHP4#SnY+{fYo*?SpsDx@T~KgoAA9uAR_6_#Q#V9S**5G$r32diwE`iCr$%y2Kr~bu88!mKUQm}IgVL_|0dxmSp#J?@U-)oIlwI$uPY+Gy9Iw&?9+6!Ua?2kKmm9L-_oUEKdfFH6v@X}{C~8UVYQq|j6m50JmoxQ5->NywMFE3bFmupcnYiKOkxBI*kD{e|JV<$Ia5U9nuY(5^+NnN1>K4jD0_ha0Q(%r3c1jK>nJHgl0@plk*n!#O7<_8;l)_`?yYmAmo(@!f{iq-PaSmplUn?7`2Y&SOr({$ov#KOB)-n2y!RNzUUQCB;0k9(e}}*bm{IgZ3QYhv<2V$g##?wQ-K~xcT`1xVyC@P__b(0PCE`O#^-m^ou?sk!tFP)r7YZbRAZM-fv#r+7T!KuL2LJIPMPMp2%Ynsj7Rhn(%rUtHIp0D^S3GsE1MnBn*1F+w9?*ivwwe?us;e;tFP{4lZG;cfJ4*VSG8+SM&rO_Ar9lt%rvGYjx$2y8417#QRH~bH20qzBU5qCHurSS{wcid|+R#Us;4kuiZfdaO$&|iTSDUSUm_W4qzPVU2gx4l+iwd7fNFS?=w1>kkyeqf{XxV|VRJo`~mn4dZr@P)ff96{sZt68IAfAWP5%IX4eX6JqDa0`^ZpH1o4hyrQUM|v%*9^x^S+zY^nX}wlrBu5ymu%~gMi;+Gkd483+ozu5dXVFA~E&DrXRS6^skOQg4M&0NMnkw?12InD!(S(ol4VV*vnGhx12>{7=z8UmS%FKrskbVX~P7n5h!4Z{%g`dQvrMP@}KCrinP~sY!28g>v#VH3zO2fL{ueEz#i0Q?Tq~hn{X!0%yA-KcPE>7Lg^#j5B!mmJ~KI1l>!A??e@PQeP`?;Y)ZU`(MKT3#~5tQoV)NZbofTyCHrc)H&qLirhBpO!YuQmyox>K@-Fcq(wo`Xmk8b!{f@K?Yj`VF(^`nC1`60*5kDhaXY5vNt&&IauW%@Gq#@X%<#*y=YxqbTv3GO+6=P8CR^>ne>oUBL>~qa7z`hA;`;>W73)8S~aAsvZ&kF3BNhx0ROQY(60@lR#Q?k#BzZqLZ`f=K@Ad=L=P~bM;R$zF>b1lZYy9=L!Ad5N#3Ru_SPl&UA&m!R8G6`yY88ur}klq+Z5if4JrX12dj$Kdnyg()XBzs8gUc-GOx_##m^5Iq+n%?Tc_esjmLOEy*@1!ui%=-P?uCPM?=L1`60ivL6y}wZRvF{{RakolmM|CU7$_C(`*AV_n;|`x2n8fdbZ@*z8n{VGrKCIZLTi0tKvRpxMbdB;t8jVvR`8qhP73QNXoWqvXVh=iNrD6T2gNn>r~_nr30&1Z8=hygTn>4{ztAubQi3EOysA2RJ|SxtCzy;B-_!Qzr%rSXbk%SeIj#_b$4#9{VL)0K6Uf+!DuR><`N1ea-x0vPto-Nw%59{n*>I0PC_|l*73zq0<8etlP2K`8YPGYb*j@2412A|7sO%IB+#^6>-|+yR(*7XZ6mUu2BgJl$J@fIwAY#bd5FGXX_=v(wwd#W-9iaY#Qljm_Lzyv^uGm=X8xqQJ@@e4%QVpGskNj1YSwDrNF8juhmH=U_o+avh~UFS_`r6=@)XmM&&3_x_26_4#_DwUuy^OTC%+Xe3gHVFGT^((HYn%oEu+;jy(i~uRgMCscO$TF$s5V!iU&^bw59cbwf)41xh;SV-Ig$S!g2(b1_BnrTvaL_H?L`i%8-ua`Zlkd<9*aGnI-|%zVO`D54(Ig+kEOB{C~*wIzGiNAVwS~j?AHBbYHPv!l4GeK@tDKq;s3Sa;~fShwhf#SW)5-o?6v-@&?u)e2uS3Y2P?h;@%PJ4q){u3nBj?8Y9bHamgeLoM*7r$DKuGqA6vo1Lc3&eOp~4l0A~v^sm6oxW?Z?q8`81xkH%VO^+aV_m5uurAf%Sl4P^v(Sawhjr&}#=3PkVcokQW8J)Z{!s%8l+JKE_N~%TtebT(_7(QP 0\r\nend\r\n\r\n--- 检查并尝试转换为整数,如果无法转换则返回 0\r\n-- @param mixed value 要检查的值\r\n-- @return integer\r\nfunction checkint(value)\r\n return math.round(checknumber(value))\r\nend\r\n\r\n--- 检查并尝试转换为布尔值,除了 nil 和 false,其他任何值都会返回 true\r\n-- @param mixed value 要检查的值\r\n-- @return boolean\r\nfunction checkbool(value)\r\n return (value ~= nil and value ~= false)\r\nend\r\n\r\n--- 检查值是否是一个表格,如果不是则返回一个空表格\r\n-- @param mixed value 要检查的值\r\n-- @return table\r\nfunction checktable(value)\r\n if type(value) ~= 'table' then\r\n value = {}\r\n end\r\n return value\r\nend\r\n\r\n--- 处理对象\r\n-- @param mixed obj Lua 对象\r\n-- @param function method 对象方法\r\n-- @return function\r\nfunction handler(obj, method)\r\n return function(...)\r\n return method(obj, ...)\r\n end\r\nend\r\n\r\n--- 计算表格包含的字段数量\r\n-- Lua table 的 \"#\" 操作只对依次排序的数值下标数组有效,table.nums() 则计算 table 中所有不为 nil 的值的个数。\r\n-- @param table\r\nfunction table.nums(t)\r\n if t == nil then\r\n return 0\r\n end\r\n local count = 0\r\n for _ in pairs(t) do\r\n count = count + 1\r\n end\r\n return count\r\nend\r\n\r\n--- 返回指定表格中的所有键\r\n-- @param k-v table\r\n-- @return keys' table\r\n-- @usage example\r\n-- local hashtable = {a = 1, b = 2, c = 3}\r\n-- local keys = table.keys(hashtable)\r\n-- >> keys = {\"a\", \"b\", \"c\"}\r\nfunction table.keys(hashtable)\r\n local keys = {}\r\n for k, _ in pairs(hashtable) do\r\n table.insert(keys, k)\r\n end\r\n return keys\r\nend\r\n\r\n--- 返回指定表格中的所有值\r\n-- @param k-v table\r\n-- @return values' table\r\n-- @usage example\r\n-- local hashtable = {a = 1, b = 2, c = 3}\r\n-- local values = table.values(hashtable)\r\n-- >> values = {1, 2, 3}\r\nfunction table.values(hashtable)\r\n local values = {}\r\n local i = 1\r\n for k, v in pairs(hashtable) do\r\n values[i] = v\r\n i = i + 1\r\n end\r\n return values\r\nend\r\n\r\n--- 将来源表格中所有键及其值复制到目标表格对象中,如果存在同名键,则覆盖其值\r\n-- @param target table\r\n-- @param source table\r\n-- @usage example\r\n-- local dest = {a = 1, b = 2}\r\n-- local src = {c = 3, d = 4}\r\n-- table.merge(dest, src)\r\n-- >> dest = {a = 1, b = 2, c = 3, d = 4}\r\nfunction table.merge(dest, src)\r\n for k, v in pairs(src) do\r\n dest[k] = v\r\n end\r\nend\r\n\r\n--- 深度将来源表格中所有键及其值复制到目标表格对象中,如果存在同名键,则覆盖其值,如果存在子表,则遍历子表进行复制\r\nfunction table.deepMerge(dest, src)\r\n for k, v in pairs(src) do\r\n if type(v) == 'table' then\r\n if dest[k] == nil then\r\n dest[k] = {}\r\n end\r\n table.deepMerge(dest[k], v)\r\n else\r\n dest[k] = v\r\n end\r\n end\r\nend\r\n\r\n--- 将来源表格中所有键及其值复制到目标表格对象中,如果存在同名键,则覆盖其值\r\n-- @param ... 多个表,第一个是目标表格\r\n-- @return 返回一个新表\r\n---@author Sharif Ma\r\nfunction table.MergeTables(...)\r\n local tabs = {...}\r\n if not tabs or #tabs == 0 then\r\n return {}\r\n end\r\n local origin = {}\r\n for k, v in pairs(tabs[1]) do\r\n origin[k] = v\r\n end\r\n for i = 2, #tabs do\r\n if origin then\r\n if tabs[i] then\r\n for _, v in pairs(tabs[i]) do\r\n table.insert(origin, v)\r\n end\r\n end\r\n else\r\n origin = tabs[i]\r\n end\r\n end\r\n return origin\r\nend\r\n\r\n--- 在目标表格的指定位置插入来源表格,如果没有指定位置则连接两个表格\r\n-- @param target table\r\n-- @param source table\r\n-- @param start index\r\n-- @usage example #1\r\n-- local dest = {1, 2, 3}\r\n-- local src = {4, 5, 6}\r\n-- table.insertto(dest, src)\r\n-- >> dest = {1, 2, 3, 4, 5, 6}\r\n-- @usage example #2\r\n-- local dest = {1, 2, 3}\r\n-- local src = {4, 5, 6}\r\n-- table.insertto(dest, src, 5)\r\n-- >> dest = {1, 2, 3, nil, 4, 5, 6}\r\nfunction table.insertto(dest, src, begin)\r\n if begin == nil then\r\n begin = #dest + 1\r\n else\r\n begin = checkint(begin)\r\n if begin <= 0 then\r\n begin = #dest + 1\r\n end\r\n end\r\n\r\n local len = #src\r\n for i = 0, len - 1 do\r\n dest[i + begin] = src[i + 1]\r\n end\r\nend\r\n\r\n--- 从表格中查找指定值,返回其索引,如果没找到返回 false\r\n-- @param array table\r\n-- @param target value\r\n-- @param start index\r\n-- @return index or false\r\n-- @usage example\r\n-- local array = {\"a\", \"b\", \"c\"}\r\n-- print(table.indexof(array, \"b\"))\r\n-- >> 2\r\nfunction table.indexof(array, value, begin)\r\n if array ~= nil then\r\n for i = begin or 1, #array do\r\n if array[i] == value then\r\n return i\r\n end\r\n end\r\n end\r\n return 0\r\nend\r\n\r\n--- 检查表格中是否存在指定值\r\n-- @param array table\r\n-- @param target value\r\n-- @return @boolean\r\nfunction table.exists(array, value)\r\n return table.indexof(array, value) > 0\r\nend\r\n\r\n--- 清空数组表格\r\n-- @param array table\r\nfunction table.cleararray(array)\r\n if array ~= nil then\r\n local count = #array\r\n while count > 0 do\r\n table.remove(array, count)\r\n count = #array\r\n end\r\n end\r\nend\r\n\r\n--- 清空k-v表格\r\n-- @param k-v table\r\nfunction table.clearhashtable(hashtable)\r\n if hashtable ~= nil then\r\n for k, v in pairs(hashtable) do\r\n hashtable[k] = nil\r\n end\r\n end\r\nend\r\n\r\n--- 清空表格\r\n-- @param table\r\n-- @see table.clearhashtable\r\nfunction table.cleartable(t)\r\n table.clearhashtable(t)\r\nend\r\n\r\n--- 截取Array其中一段,startIndex从1开始 return截取后的新数组\r\n-- @param table array table\r\n-- @param @number start index\r\n-- @param @number length\r\n-- @return @table array table\r\n-- @usage example\r\n-- local array = {\"a\", \"b\", \"c\", \"d\"}\r\n-- print(table.subArray(array, 2, 2))\r\n-- >> {\"b\", \"c\"}\r\nfunction table.subArray(array, startIndex, length)\r\n if array ~= nil then\r\n local count = table.nums(array)\r\n local tempArray = array\r\n array = {}\r\n if startIndex <= count then\r\n local maxlength = count - startIndex + 1\r\n length = length > maxlength and maxlength or length\r\n local endIndex = startIndex + length - 1\r\n for i = startIndex, endIndex do\r\n table.insert(array, tempArray[i])\r\n end\r\n end\r\n end\r\n return array\r\nend\r\n\r\n--- 截取Array的后半段,startIndex从1开始 return截取后的新数组\r\n-- @param table array table\r\n-- @param @number start index\r\n-- @return @table array table\r\nfunction table.subArrayByStartIndex(array, startIndex)\r\n if array ~= nil then\r\n local count = table.nums(array)\r\n local length = count - startIndex + 1\r\n return table.subArray(array, startIndex, length)\r\n end\r\n return array\r\nend\r\n\r\n--- 从表格中查找指定值,返回其 key,如果没找到返回 nil\r\n-- @param table hash table\r\n-- @param any value\r\n-- @return key of value\r\n-- @usage\r\n-- local hashtable = {name = \"dualface\", comp = \"chukong\"}\r\n-- print(table.keyof(hashtable, \"chukong\"))\r\n-- >> comp\r\nfunction table.keyof(hashtable, value)\r\n for k, v in pairs(hashtable) do\r\n if v == value then\r\n return k\r\n end\r\n end\r\n return nil\r\nend\r\n\r\n--- 从表格中删除指定值,返回删除的值的个数\r\n-- @usage\r\n-- local array = {\"a\", \"b\", \"c\", \"c\"}\r\n-- print(table.removebyvalue(array, \"c\", true))\r\n-- >> 输出 2\r\nfunction table.removebyvalue(array, value, removeall)\r\n local c, i, max = 0, 1, #array\r\n while i <= max do\r\n if array[i] == value then\r\n table.remove(array, i)\r\n c = c + 1\r\n i = i - 1\r\n max = max - 1\r\n if not removeall then\r\n break\r\n end\r\n end\r\n i = i + 1\r\n end\r\n return c\r\nend\r\n\r\n--- 数组混淆\r\nfunction table.shuffle(_tbl)\r\n local j\r\n for i = #_tbl, 2, -1 do\r\n j = math.random(i)\r\n _tbl[i], _tbl[j] = _tbl[j], _tbl[i]\r\n end\r\n return _tbl\r\nend\r\n\r\n--- 对表格中每一个值执行一次指定的函数,并用函数返回值更新表格内容\r\n-- @param table\r\n-- @param function fn 参数指定的函数具有两个参数,并且返回一个值。原型如下:\r\n-- function map_function(value, key)\r\n-- return value\r\n-- end\r\n-- @usage\r\n-- local t = {name = \"dualface\", comp = \"chukong\"}\r\n-- table.map(t, function(v, k)\r\n-- -- 在每一个值前后添加括号\r\n-- return \"[\" .. v .. \"]\"\r\n-- end)\r\n-- 输出修改后的表格内容\r\n-- for k, v in pairs(t) do\r\n-- print(k, v)\r\n-- end\r\n-- >> 输出\r\n-- name [dualface]\r\n-- comp [chukong]\r\nfunction table.map(t, fn)\r\n for k, v in pairs(t) do\r\n t[k] = fn(v, k)\r\n end\r\nend\r\n\r\n--- 对表格中每一个值执行一次指定的函数,但不改变表格内容\r\n-- @param table\r\n-- @param function fn 参数指定的函数具有两个参数,没有返回值。原型如下:\r\n-- function map_function(value, key)\r\n-- -- no return here\r\n-- end\r\n-- @usage\r\n-- local t = {name = \"dualface\", comp = \"chukong\"}\r\n-- table.walk(t, function(v, k)\r\n-- -- 输出每一个值\r\n-- print(v)\r\n-- end)\r\nfunction table.walk(t, fn)\r\n for k, v in pairs(t) do\r\n fn(v, k)\r\n end\r\nend\r\n\r\n--- 对表格中每一个值执行一次指定的函数,如果该函数返回 false,则对应的值会从表格中删除\r\n-- @param table\r\n-- @param function fn 参数指定的函数具有两个参数,并且返回一个 boolean 值。原型如下:\r\n-- !!!!该方法有局限性,执行后会修改原表格t中值\r\n-- function map_function(value, key)\r\n-- return true or false\r\n-- end\r\n-- @usage\r\n-- local t = {name = \"dualface\", comp = \"chukong\"}\r\n-- table.filter(t, function(v, k)\r\n-- return v ~= \"dualface\" -- 当值等于 dualface 时过滤掉该值\r\n-- end)\r\n-- 输出修改后的表格内容\r\n-- for k, v in pairs(t) do\r\n-- print(k, v)\r\n-- end\r\n-- >> 输出 comp chukong\r\nfunction table.filter(t, fn)\r\n for k, v in pairs(t) do\r\n if not fn(v, k) then\r\n t[k] = nil\r\n end\r\n end\r\nend\r\n\r\n--- 找到表格中每个符合matchFunc的条目\r\n-- @param array table\r\n-- @param match function, return T/F\r\n-- @return all elements matched, default is {}\r\nfunction table.findAll(array, matchFunc)\r\n local ret, idx = {}, 1\r\n for i = 1, #array do\r\n if matchFunc(array[i]) then\r\n ret[idx] = array[i]\r\n idx = idx + 1\r\n end\r\n end\r\n return ret\r\nend\r\n\r\n--- 找到表格中每个符合matchFunc的条目,并执行walkFunc\r\n-- @param array table\r\n-- @param match function, return T/F\r\n-- @param walk function\r\nfunction table.findAllAndWalk(array, matchFunc, walkFunc)\r\n for i = 1, #array do\r\n if matchFunc(array[i]) then\r\n walkFunc(array[i])\r\n end\r\n end\r\nend\r\n\r\n--- 在表格中插入一个新值\r\n-- @param array table\r\n-- @param new element\r\nfunction table.insert_once(T, elem)\r\n for _, v in ipairs(T) do\r\n if v == elem then\r\n return\r\n end\r\n end\r\n table.insert(T, elem)\r\nend\r\n\r\n--- 遍历表格,确保其中的值唯一\r\n-- @function [parent=#table] unique\r\n-- @param table t 表格\r\n-- @param boolean bArray t是否是数组,是数组,t中重复的项被移除后,后续的项会前移\r\n-- @return table#table 包含所有唯一值的新表格\r\n-- @usage\r\n-- 遍历表格,确保其中的值唯一\r\n-- local t = {\"a\", \"a\", \"b\", \"c\"} -- 重复的 a 会被过滤掉\r\n-- local n = table.unique(t)\r\n-- for k, v in pairs(n) do\r\n-- print(v)\r\n-- end\r\n-- >> 输出 a b c\r\nfunction table.unique(t, bArray)\r\n local check = {}\r\n local n = {}\r\n local idx = 1\r\n for k, v in pairs(t) do\r\n if not check[v] then\r\n if bArray then\r\n n[idx] = v\r\n idx = idx + 1\r\n else\r\n n[k] = v\r\n end\r\n check[v] = true\r\n end\r\n end\r\n return n\r\nend\r\n\r\n--- table 深度复制\r\n-- @param table\r\n-- @return a net table with same data\r\nfunction table.deepcopy(object)\r\n local lookup_table = {}\r\n local function _copy(object)\r\n if type(object) ~= 'table' then\r\n return object\r\n elseif lookup_table[object] then\r\n return lookup_table[object]\r\n end\r\n local new_table = {}\r\n lookup_table[object] = new_table\r\n for key, value in pairs(object) do\r\n new_table[_copy(key)] = _copy(value)\r\n end\r\n return setmetatable(new_table, getmetatable(object))\r\n end\r\n return _copy(object)\r\nend\r\n\r\n--- table 浅度复制(不处理metatable)\r\nfunction table.shallowcopy(orig)\r\n local orig_type = type(orig)\r\n local copy\r\n if orig_type == 'table' then\r\n copy = {}\r\n for orig_key, orig_value in next, orig, nil do\r\n copy[table.shallowcopy(orig_key)] = table.shallowcopy(orig_value)\r\n end\r\n else\r\n copy = orig\r\n end\r\n return copy\r\nend\r\n\r\n--- 获取or创建一个子表\r\nfunction table.need(tb, key)\r\n if type(tb) == 'table' then\r\n local subTb = tb[key]\r\n if subTb == nil then\r\n subTb = {}\r\n tb[key] = subTb\r\n end\r\n return subTb\r\n end\r\n return\r\nend\r\n\r\n--- 打印table中的所有内容\r\n-- @param data table\r\n-- @param @boolean showMetatable 是否显示元表\r\nfunction table.dump(data, showMetatable)\r\n local result, tab = {}, ' '\r\n local function _dump(data, showMetatable, lastCount)\r\n if type(data) ~= 'table' then\r\n if type(data) == 'string' then\r\n table.insert(result, '\"')\r\n table.insert(result, data)\r\n table.insert(result, '\"')\r\n else\r\n table.insert(result, tostring(data))\r\n end\r\n else\r\n --Format\r\n local count = lastCount or 0\r\n count = count + 1\r\n table.insert(result, '{\\n')\r\n --Metatable\r\n if showMetatable then\r\n for i = 1, count do\r\n table.insert(result, tab)\r\n end\r\n local mt = getmetatable(data)\r\n table.insert(result, '\"__metatable\" = ')\r\n _dump(mt, showMetatable, count)\r\n table.insert(result, ',\\n')\r\n end\r\n --Key\r\n for key, value in pairs(data) do\r\n for i = 1, count do\r\n table.insert(result, tab)\r\n end\r\n if type(key) == 'string' then\r\n table.insert(result, '\"')\r\n table.insert(result, key)\r\n table.insert(result, '\" = ')\r\n elseif type(key) == 'number' then\r\n table.insert(result, '[')\r\n table.insert(result, key)\r\n table.insert(result, '] = ')\r\n else\r\n table.insert(result, tostring(key))\r\n end\r\n _dump(value, showMetatable, count)\r\n table.insert(result, ',\\n')\r\n end\r\n --Format\r\n for i = 1, lastCount or 0 do\r\n table.insert(result, tab)\r\n end\r\n table.insert(result, '}')\r\n end\r\n --Format\r\n if not lastCount then\r\n table.insert(result, '\\n')\r\n end\r\n end\r\n _dump(data, showMetatable, 0)\r\n\r\n -- print('dump: \\n' .. table.concat(result))\r\n return 'dump: \\n' .. table.concat(result)\r\nend\r\n\r\n--- 用指定字符或字符串分割输入字符串,返回包含分割结果的数组\r\n-- @param @string input 输入的字符串\r\n-- @param @string delimiter 分隔符\r\n-- @return array\r\n-- @usage example #1\r\n-- local input = \"Hello,World\"\r\n-- local res = string.split(input, \",\")\r\n-- >> res = {\"Hello\", \"World\"}\r\n-- @usage example #2\r\n-- local input = \"Hello-+-World-+-Quick\"\r\n-- local res = string.split(input, \"-+-\")\r\n-- >> res = {\"Hello\", \"World\", \"Quick\"}\r\nfunction string.split(input, delimiter)\r\n input = tostring(input)\r\n delimiter = tostring(delimiter)\r\n if (delimiter == '') then\r\n return false\r\n end\r\n local pos, arr = 0, {}\r\n -- for each divider found\r\n for st, sp in function()\r\n return string.find(input, delimiter, pos, true)\r\n end do\r\n table.insert(arr, string.sub(input, pos, st - 1))\r\n pos = sp + 1\r\n end\r\n table.insert(arr, string.sub(input, pos))\r\n return arr\r\nend\r\n\r\n--- 判断字符串是否为空或者长度为零\r\n-- @param @string 输入的字符串\r\nfunction string.isnilorempty(inputStr)\r\n return inputStr == nil or inputStr == ''\r\nend\r\n\r\n--- 去除输入字符串头部的空白字符,返回结果\r\n-- @param @string input\r\n-- @return @string\r\n-- @usage example\r\n-- local input = \" ABC\"\r\n-- print(string.ltrim(input))\r\n-- >> 输出 ABC,输入字符串前面的两个空格被去掉了\r\n-- 空白字符包括:\r\n-- - 空格\r\n-- - 制表符 \\t\r\n-- - 换行符 \\n\r\n-- - 回到行首符 \\r\r\nfunction string.ltrim(input)\r\n return string.gsub(input, '^[ \\t\\n\\r]+', '')\r\nend\r\n\r\n--- 去除输入字符串尾部的空白字符,返回结果\r\n-- @param @string input\r\n-- @return @string\r\n-- @usage example\r\n-- local input = \"ABC \"\r\n-- print(string.rtrim(input))\r\n-- >> 输出 ABC,输入字符串最后的两个空格被去掉了\r\nfunction string.rtrim(input)\r\n return string.gsub(input, '[ \\t\\n\\r]+$', '')\r\nend\r\n\r\n--- 去掉字符串首尾的空白字符,返回结果\r\n-- @param @string input\r\n-- @return @string\r\nfunction string.trim(input)\r\n input = string.gsub(input, '^[ \\t\\n\\r]+', '')\r\n return string.gsub(input, '[ \\t\\n\\r]+$', '')\r\nend\r\n\r\n--- 将字符串的第一个字符转为大写,返回结果\r\n-- @param @string input\r\n-- @return @string\r\n-- @usage example\r\n-- local input = \"hello\"\r\n-- print(string.ucfirst(input))\r\n-- >> 输出 Hello\r\nfunction string.ucfirst(input)\r\n return string.upper(string.sub(input, 1, 1)) .. string.sub(input, 2)\r\nend\r\n\r\nfunction string.firstToUpper(str)\r\n return (str:gsub('^%l', string.upper))\r\nend\r\n\r\n--- 计算 UTF8 字符串的长度,每一个中文算一个字符\r\n-- @param @string input\r\n-- @return @number cnt\r\n-- @usage example\r\n-- local input = \"你好World\"\r\n-- print(string.utf8len(input))\r\n-- >> 输出 7\r\nfunction string.utf8len(input)\r\n local len = string.len(input)\r\n local left = len\r\n local cnt = 0\r\n local arr = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}\r\n while left ~= 0 do\r\n local tmp = string.byte(input, -left)\r\n local i = #arr\r\n while arr[i] do\r\n if tmp >= arr[i] then\r\n left = left - i\r\n break\r\n end\r\n i = i - 1\r\n end\r\n cnt = cnt + 1\r\n end\r\n return cnt\r\nend\r\n\r\n--- 替换字符串内容\r\n-- @param @string input\r\n-- @param @number start index\r\n-- @param new context\r\n-- @return a new string\r\nfunction string.replace(str, index, char)\r\n return table.concat {str:sub(1, index - 1), char, str:sub(index + 1)}\r\nend\r\n\r\n--- 检查字符串是否为指定字符串开头\r\n-- @param @string target\r\n-- @param @string start\r\n-- @return @boolean\r\nfunction string.startswith(str, start)\r\n return str:sub(1, #start) == start\r\nend\r\n\r\n--- 检查字符串是否以指定字符串结尾\r\n-- @param @string target\r\n-- @param @string start\r\n-- @return @boolean\r\nfunction string.endswith(str, ending)\r\n return ending == '' or str:sub(-(#ending)) == ending\r\nend\r\n\r\n--- 四舍五入\r\n-- @param a number\r\n-- @return a round number\r\nfunction math.round(value)\r\n return math.floor(value + 0.5)\r\nend\r\n\r\n--- [0, 1]区间限定函数\r\n-- @param a number\r\n-- @return a clamped number\r\nfunction math.clamp01(value)\r\n return math.min(1, math.max(0, value))\r\nend\r\n\r\n---最小数值和最大数值指定返回值的范围\r\n-- @param a number\r\n-- @param min threshold\r\n-- @param max threshold\r\n-- @return a clamped number\r\nfunction math.Clamp(value, min, max)\r\n if value < min then\r\n return min\r\n end\r\n if value > max then\r\n return max\r\n end\r\n return value\r\nend\r\n\r\n--- 高斯岁间变量\r\nfunction math.GaussRandom()\r\n local u = math.random()\r\n local v = math.random()\r\n local z = math.sqrt(-2 * math.log(u)) * math.cos(2 * math.pi * v)\r\n z = (z + 3) / 6\r\n z = 2 * z - 1\r\n if (math.abs(z) > 1) then\r\n return math.GaussRandom()\r\n end\r\n return z\r\nend\r\n\r\n--- 数据结构 队列\r\n-- @usage queue example\r\n-- local myQueue = Queue:New()\r\n-- myQueue:Enqueue('a')\r\n-- myQueue:Enqueue('b')\r\n-- myQueue:Enqueue('c')\r\n-- myQueue:PrintElement()\r\n-- print(myQueue:Dequeue())\r\n-- myQueue:PrintElement()\r\n-- myQueue:Clear()\r\n-- myQueue:PrintElement()\r\nQueue = {}\r\nfunction Queue:New()\r\n local inst = {\r\n _first = -1,\r\n _last = -1,\r\n _size = 0,\r\n _queue = {}\r\n }\r\n setmetatable(inst, {__index = self})\r\n return inst\r\nend\r\n\r\nfunction Queue:IsEmpty()\r\n if self._size == 0 then\r\n return true\r\n end\r\n return false\r\nend\r\n\r\nfunction Queue:Enqueue(inElement)\r\n if self._size == 0 then\r\n self._first = 0\r\n self._last = 1\r\n self._size = 1\r\n self._queue[self._last] = inElement\r\n else\r\n self._last = self._last + 1\r\n self._queue[self._last] = inElement\r\n self._size = self._size + 1\r\n end\r\nend\r\n\r\nfunction Queue:Dequeue()\r\n if self:IsEmpty() then\r\n print('Error: the queue is empty')\r\n return\r\n end\r\n self._size = self._size - 1\r\n self._first = self._first + 1\r\n local value = self._queue[self._first]\r\n return value\r\nend\r\n\r\nfunction Queue:Clear()\r\n self._queue = nil\r\n self._queue = {}\r\n self._size = 0\r\n self._first = -1\r\n self._last = -1\r\nend\r\n\r\nfunction Queue:Size()\r\n return self._size or 0\r\nend\r\n\r\nfunction Queue:PrintElement()\r\n if self._size == 0 then\r\n print('{}')\r\n else\r\n local f = self._first + 1\r\n local l = self._last\r\n local str\r\n local flag = true\r\n while f ~= l do\r\n if flag == true then\r\n str = '{' .. tostring(self._queue[f])\r\n f = f + 1\r\n flag = false\r\n else\r\n str = str .. ',' .. tostring(self._queue[f])\r\n f = f + 1\r\n end\r\n end\r\n str = str .. ',' .. tostring(self._queue[l]) .. '}'\r\n print(str)\r\n end\r\nend\r\n\r\nfunction Queue:GetValue(index)\r\n if self:IsEmpty() or index == nil or index == 0 then\r\n print('Error: Get Value Failure!')\r\n return\r\n end\r\n if index > 0 then\r\n return self._queue[self._first + index]\r\n else\r\n return self._queue[self._last + index + 1]\r\n end\r\nend\r\n\r\nfunction Queue:GetValues()\r\n if self:IsEmpty() then\r\n return\r\n end\r\n local data = {}\r\n for i = self._first + 1, self._last, 1 do\r\n data[#data + 1] = self._queue[i]\r\n end\r\n return data\r\nend\r\n\r\n--- 数据结构 栈\r\n-- @usage example\r\n-- local myStack = Stack:New()\r\n-- myStack:Push(\"a\")\r\n-- myStack:Push(\"b\")\r\n-- myStack:Push(\"c\")\r\n-- myStack:PrintElement()\r\n-- print(myStack:Pop())\r\n-- myStack:PrintElement()\r\n-- myStack:Clear()\r\n-- myStack:PrintElement()\r\nStack = {}\r\nfunction Stack:New()\r\n local inst = {\r\n _last = 0,\r\n _stack = {}\r\n }\r\n setmetatable(inst, {__index = self})\r\n\r\n return inst\r\nend\r\n\r\nfunction Stack:IsEmpty()\r\n if self._last == 0 then\r\n return true\r\n end\r\n return false\r\nend\r\n\r\nfunction Stack:Push(inElement)\r\n self._last = self._last + 1\r\n self._stack[self._last] = inElement\r\nend\r\n\r\nfunction Stack:Pop()\r\n if self:IsEmpty() then\r\n --print(\"Error: the stack is empty\")\r\n return\r\n end\r\n local value = self._stack[self._last]\r\n self._stack[self._last] = nil\r\n self._last = self._last - 1\r\n return value\r\nend\r\n\r\nfunction Stack:Exists(element, compairFunc)\r\n if compairFunc == nil then\r\n compairFunc = function(a, b)\r\n return a == b\r\n end\r\n end\r\n for i = self._last, 1, -1 do\r\n if compairFunc(element, self._stack[i]) then\r\n return i\r\n end\r\n end\r\n return -1\r\nend\r\n\r\nfunction Stack:RemoveAt(index)\r\n if index < 1 or index > self._last then\r\n return\r\n end\r\n table.remove(self._stack, index)\r\n self._last = self._last - 1\r\nend\r\n\r\nfunction Stack:Clear()\r\n self._stack = nil\r\n self._stack = {}\r\n self._last = 0\r\nend\r\n\r\nfunction Stack:Size()\r\n return self._last\r\nend\r\n\r\nfunction Stack:PrintElement()\r\n local str = '{'\r\n for i = self._last, 1, -1 do\r\n str = str .. tostring(self._stack[i]) .. ','\r\n end\r\n str = str .. '}'\r\n print(str)\r\nend\r\n"}}]},{"class":"cScriptObject","name":"ModuleRequireScript","guid":[789428111,3886039363,2156446074,3559120475],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ModuleRequireScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 将Global.Module目录下每一个用到模块提前require,定义为全局变量\r\n-- @script Module Defines\r\n-- @copyright Lilith Games, Avatar Team\r\n\r\n-- Utilities\r\nModuleUtil = require(Utility.ModuleUtilModule)\r\nLuaJsonUtil = require(Utility.LuaJsonUtilModule)\r\nNetUtil = require(Utility.NetUtilModule)\r\nCsvUtil = require(Utility.CsvUtilModule)\r\nXlsUtil = require(Utility.XlsUtilModule)\r\nEventUtil = require(Utility.EventUtilModule)\r\nUUID = require(Utility.UuidModule)\r\nTweenController = require(Utility.TweenControllerModule)\r\nGlobalFunc = require(Utility.GlobalFuncModule)\r\nLinkedList = Utility.LinkedListModule\r\nValueChangeUtil = require(Utility.ValueChangeUtilModule)\r\nTimeUtil = require(Utility.TimeUtilModule)\r\nTimeUtil.Init()\r\n\r\n-- Framework\r\nModuleUtil.LoadModules(Framework)\r\n\r\n-- Globle Defines, Server and Clinet Modules\r\nModuleUtil.LoadModules(Define)\r\nModuleUtil.LoadXlsModules(Xls, Config)\r\nModuleUtil.LoadModules(Module.S_Module)\r\nModuleUtil.LoadModules(Module.C_Module)\r\nModuleUtil.LoadModules(Module.Cls_Module)\r\n\r\n-- Plugin Modules\r\nGuideSystem = require(world.Global.Plugin.FUNC_Guide.GuideSystemModule)\r\n"}}]},{"class":"cScriptObject","name":"AutoAssignTeamScript","guid":[811022213,2158838612,2670341129,820752393],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"AutoAssignTeamScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 玩家加入\r\n-- @script Auto assign\r\n-- @copyright Lilith Games, Avatar Team\r\n\r\n--- 编辑器默认方法\r\n-- run once when script init\r\nfunction autoAssign()\r\n local container = world:FindTeams()\r\n local min = 1\r\n local teamTojoin = {}\r\n local playerfolder = world.Players\r\n for i = 1, #container, 1 do\r\n if container[i].CurrentMaxMemberNum > 0 then\r\n temp = container[i].CurrentMemberNum / (container[i].CurrentMaxMemberNum)\r\n if (temp < min and temp ~= 1) then\r\n teamTojoin = {}\r\n min = temp\r\n table.insert(teamTojoin, container[i])\r\n elseif temp == min and temp ~= 1 then\r\n table.insert(teamTojoin, container[i])\r\n end\r\n end\r\n end\r\n local a = 1\r\n if #teamTojoin > 0 then\r\n a = math.random(1, #teamTojoin)\r\n return teamTojoin[a]\r\n else\r\n return nil\r\n end\r\nend\r\n"}}]},{"class":"cFolderObject","name":"Utility","guid":[626626282,3227665360,2471910562,2840544587],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Utility"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"ModuleUtilModule","guid":[1931854124,2576109163,2694171240,1407146350],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ModuleUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 模块工具\r\n-- @module Module utilities\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\n\r\nlocal ModuleUtil = {}\r\n\r\n--- 加载模块目录\r\n-- @param _root 模块目录的节点\r\n-- @param _scope 载入后脚本的作用域\r\nfunction ModuleUtil.LoadModules(_root, _scope)\r\n _scope = _scope or _G\r\n assert(_root, '[ModuleUtil] Node does NOT exist!')\r\n local tmp = _root:GetChildren()\r\n for _, v in pairs(tmp) do\r\n name = (v.Name):gsub('Module', '')\r\n -- print('[ModuleUtil] Load: ' .. name)\r\n _scope[name] = require(v)\r\n end\r\nend\r\n\r\n--- 加载XLS表格目录\r\n-- @param _root 模块目录的节点\r\nfunction ModuleUtil.LoadXlsModules(_root, _config)\r\n assert(_root, '[ModuleUtil] Node does NOT exist!')\r\n assert(_config, '[ModuleUtil] Config does NOT exist!')\r\n local tmp = _root:GetChildren()\r\n for _, v in pairs(tmp) do\r\n name = (v.Name):gsub('XlsModule', '')\r\n print('[ModuleUtil] Load: ' .. name)\r\n _config[name] = require(v)\r\n end\r\nend\r\n\r\n--- 加载多个模块目录\r\nfunction ModuleUtil.LoadAllModules(...)\r\n local args = table.pack(...)\r\n for i = 1, args.n do\r\n if args[i] then\r\n ModuleUtil.LoadModules(args[i])\r\n end\r\n end\r\nend\r\n\r\n--- 将有包含特定方法的模块筛选出来,并放在一个table中\r\n-- @param _root 模块目录的节点\r\n-- @param @string _fn 方法名 function_name\r\n-- @param @table _list 存放的table\r\nfunction ModuleUtil.GetModuleListWithFunc(_root, _fn, _list)\r\n assert(_root, '[ModuleUtil] Node does NOT exist!')\r\n assert(not string.isnilorempty(_fn), '[ModuleUtil] Function name is nil or empty!')\r\n assert(_list, '[ModuleUtil] List is NOT initialized!')\r\n local tmp, name = _root:GetChildren()\r\n for _, v in pairs(tmp) do\r\n name = (v.Name):gsub('Module', '')\r\n if _G[name] and _G[name][_fn] and type(_G[name][_fn]) == 'function' then\r\n table.insert(_list, _G[name])\r\n end\r\n end\r\nend\r\n\r\n--- 新建一个模块实例(ServerBase or ClientBase)\r\nfunction ModuleUtil.New(_name, _baseClass)\r\n local t = class(_name, _baseClass)\r\n return t, t:GetSelf()\r\nend\r\n\r\nreturn ModuleUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"NetUtilModule","guid":[877830752,869352727,3042692887,2948948613],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"NetUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 网路工具/事件工具\r\n-- @module Network utilities\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Sharif Ma, Yuancheng Zhang, Yen Yuan\r\nlocal NetUtil = {}\r\n\r\n-- 格式化事件参数, Table=>JSON\r\nlocal FormatArgs\r\n\r\n--! 事件参数校验, true:开启校验\r\nlocal valid, ValidateArgs = true\r\n\r\n--! 打印事件日志, true:开启打印\r\nlocal showLog, PrintEventLog = false\r\n\r\nlocal FireEnum = {\r\n SERVER = 1,\r\n CLIENT = 2,\r\n BROADCAST = 3\r\n}\r\n\r\n--! 外部接口\r\n\r\n--- 向服务器发送消息\r\n-- @param @string _eventName 事件的名字(严格对应)\r\n-- @param ... 事件参数\r\nfunction NetUtil.Fire_S(_eventName, ...)\r\n ValidateArgs(FireEnum.SERVER, _eventName)\r\n local args = {...}\r\n world.S_Event[_eventName]:Fire(table.unpack(args))\r\n PrintEventLog(FireEnum.SERVER, _eventName, nil, args)\r\nend\r\n\r\n--- 向指定的玩家发送消息\r\n-- @param @string _eventName 事件的名字\r\n-- @param _player 玩家对象\r\n-- @param ... 事件参数\r\nfunction NetUtil.Fire_C(_eventName, _player, ...)\r\n\tif(_player == nil) then\r\n\t\treturn\r\n\tend\r\n ValidateArgs(FireEnum.CLIENT, _eventName, _player)\r\n local args = {...}\r\n _player.C_Event[_eventName]:Fire(table.unpack(args))\r\n PrintEventLog(FireEnum.CLIENT, _eventName, _player, args)\r\nend\r\n\r\n--- 客户端广播\r\n-- @param @string _eventName 事件的名字(严格对应)\r\n-- @param ... 事件参数\r\nfunction NetUtil.Broadcast(_eventName, ...)\r\n ValidateArgs(FireEnum.BROADCAST, _eventName, ...)\r\n local args = {...}\r\n world.Players:BroadcastEvent(_eventName, table.unpack(args))\r\n PrintEventLog(FireEnum.BROADCAST, _eventName, nil, args)\r\nend\r\n\r\n--! 私有函数\r\n\r\n--- 格式化事件参数\r\nFormatArgs = function(...)\r\n local args = {...}\r\n for k, v in pairs(args) do\r\n if type(v) == 'table' then\r\n args[k] = string.format('JSON%sJSON', LuaJsonUtil:encode(v))\r\n end\r\n end\r\n return args\r\nend\r\n\r\n--! 辅助功能\r\n\r\n--- 事件参数校验\r\nValidateArgs =\r\n valid and\r\n function(_fireEnum, _eventName, _player)\r\n if _fireEnum == FireEnum.SERVER then\r\n --! Fire_S 检查参数\r\n assert(not string.isnilorempty(_eventName), '[NetUtil][Fire_S] 事件名为空')\r\n assert(world.S_Event[_eventName], string.format('[NetUtil][Fire_S] 服务器不存在事件: %s', _eventName))\r\n elseif _fireEnum == FireEnum.CLIENT then\r\n --! Fire_C 检查参数\r\n assert(not string.isnilorempty(_eventName), '[NetUtil] 事件名为空')\r\n assert(\r\n _player and _player.ClassName == 'PlayerInstance',\r\n string.format('[NetUtil][Fire_C]第2个参数需要是玩家对象, 错误事件: %s', _eventName)\r\n )\r\n assert(\r\n _player.C_Event[_eventName],\r\n string.format('[NetUtil][Fire_C] 客户端玩家不存在事件: %s, 玩家: %s', _player.Name, _eventName)\r\n )\r\n elseif _fireEnum == FireEnum.BROADCAST then\r\n --! Broadcase 检查参数\r\n assert(not string.isnilorempty(_eventName), '[NetUtil][Broadcast] 事件名为空')\r\n end\r\n end or\r\n function()\r\n end\r\n\r\n--- 打印事件日志\r\nPrintEventLog = showLog and function(_fireEnum, _eventName, _player, _args)\r\n if _fireEnum == FireEnum.SERVER then\r\n --* Fire_S 参数打印\r\n print(string.format('[NetUtil][服务器] %s, 参数 = %s, %s', _eventName, #_args, table.dump(_args)))\r\n elseif _fireEnum == FireEnum.CLIENT then\r\n --* Fire_C 参数打印\r\n print(\r\n string.format(\r\n '[NetUtil][客户端] %s, 玩家=%s, 参数 = %s, %s',\r\n _eventName,\r\n _player.Name,\r\n #_args,\r\n table.dump(_args)\r\n )\r\n )\r\n elseif _fireEnum == FireEnum.BROADCAST then\r\n --* Broadcase 参数打印\r\n print(string.format('[NetUtil][客户端][广播] %s, 参数 = %s, %s', _eventName, #_args, table.dump(_args)))\r\n end\r\n end or function()\r\n end\r\n\r\nreturn NetUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"CsvUtilModule","guid":[974164082,3633467084,2781912883,1451329930],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"CsvUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 读表工具: 将CSV导入成Lua Table,支持单一主键和多主键\r\n-- @module CSV Utility\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\n-- @see https://wiki.lilithgames.com/x/RGEMAg\r\nlocal CsvUtil = {}\r\n\r\n--! 打印事件日志, true:开启打印\r\nlocal showLog, PrintGlobalKV, PrintLog = true\r\n\r\n--- 将表中的字符串改为数字\r\n-- @param _t input table\r\nlocal function StrToNum(_t)\r\n for k, v in pairs(_t) do\r\n _t[k] = tonumber(v)\r\n end\r\n return _t\r\nend\r\n\r\n--- 类型解析配置表\r\nlocal parser = {\r\n int = function(_raw)\r\n return tonumber(_raw)\r\n end,\r\n float = function(_raw)\r\n return tonumber(_raw)\r\n end,\r\n string = function(_raw)\r\n return _raw\r\n end,\r\n boolean = function(_raw)\r\n return string.lower(_raw) == 'true'\r\n end,\r\n vector2 = function(_raw)\r\n return Vector2(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n vector3 = function(_raw)\r\n return Vector3(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n euler = function(_raw)\r\n return EulerDegree(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n color = function(_raw)\r\n return Color(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end\r\n}\r\n\r\n--- 读取配置表,会根据id生成lua表\r\n-- @param _type String 数据类型\r\n-- @parm _stringValue String 数据\r\n-- @return value 解析出来的数值\r\nlocal function GetValue(_type, _stringValue)\r\n _type = string.lower(_type)\r\n assert(parser[_type], string.format('[CsvUtil][GlobalSetting] \"%s\" Type字段的值不是目前所支持的数据类型', _type))\r\n return parser[_type](_stringValue)\r\nend\r\n\r\n--- 读取配置表,会根据id生成lua表\r\n-- @param _csv 表格\r\n-- @parma ... 表格的主键ids,可以为单一主键或多主键(多主键的id顺序决定lua table的结构)\r\n-- @usage exmaple #1 如果, 单一键值为主键\r\n-- Level.csv 表格内容为:\r\n-- ----------------------------------\r\n-- | String | String | Int |\r\n-- | level_id | level_name | reward |\r\n-- | easy_01 | Level 01 | 100 |\r\n-- | easy_02 | Level 02 | 140 |\r\n-- | hard_01 | Level 03 | 280 |\r\n-- | hard_02 | Level 04 | 320 |\r\n-- ----------------------------------\r\n-- 调用函数 local levelCsv = CsvUtil.GetCsvInfo(Level, 'level_id') 导入的lua表格结果为:\r\n-- levelCsv = {\r\n-- easy_01 = {\r\n-- level_id = 'easy_01',\r\n-- level_name = 'Level 01',\r\n-- reward = 100\r\n-- },\r\n-- easy_02 = {\r\n-- level_id = 'easy_02',\r\n-- level_name = 'Level 02',\r\n-- reward = 140\r\n-- },\r\n-- hard_01 = {\r\n-- level_id = 'hard_01',\r\n-- level_name = 'Level 03',\r\n-- reward = 280\r\n-- },\r\n-- hard_02 = {\r\n-- level_id = 'hard_02',\r\n-- level_name = 'Level 04',\r\n-- reward = 320\r\n-- }\r\n-- }\r\n-- @usage exmaple #2 如果, 多键值为主键\r\n-- Enemy.csv 表格内容为:\r\n-- ----------------------------------\r\n-- | String | String | Int |\r\n-- | enemy_id | difficulty | hp |\r\n-- | foe_01 | easy | 100 |\r\n-- | foe_01 | hard | 150 |\r\n-- | foe_02 | easy | 300 |\r\n-- | foe_02 | hard | 400 |\r\n-- ----------------------------------\r\n-- 调用函数 local enemyCsv = CsvUtil.GetCsvInfo(Enemy, 'enemy_id', 'difficulty') 导入的lua表格结果为:\r\n-- enemyCsv = {\r\n-- foe_01 = {\r\n-- easy = {\r\n-- enemy_id = 'foe_01',\r\n-- difficulty = 'easy',\r\n-- hp = 100\r\n-- },\r\n-- hard = {\r\n-- enemy_id = 'foe_02',\r\n-- difficulty = 'hard',\r\n-- hp = 150\r\n-- }\r\n-- },\r\n-- foe_02 = {\r\n-- esay = {\r\n-- enemy_id = 'foe_02',\r\n-- difficulty = 'easy',\r\n-- hp = 300\r\n-- },\r\n-- hard = {\r\n-- enemy_id = 'foe_02',\r\n-- difficulty = 'hard',\r\n-- hp = 400\r\n-- }\r\n-- }\r\n-- }\r\n-- 使用lua table中的数据方法:\r\n-- health = enemyCsv.foe_01.hard.hp 或 health = enemyCsv['foe_01']['hard']['hp']\r\n-- health的值为150\r\nfunction CsvUtil.GetCsvInfo(_csv, ...)\r\n local rawTable = _csv:GetRows()\r\n local ids = {...}\r\n if #ids < 1 or (#ids == 1 and ids[1] == 'Type') then\r\n -- 默认用Type索引,直接返回\r\n return rawTable\r\n end\r\n local result = {}\r\n local tmp, key, id, idstr -- 临时变量\r\n for _, v in pairs(rawTable) do\r\n tmp = result\r\n idstr = {}\r\n for i = 1, #ids do\r\n id = ids[i]\r\n key = v[id]\r\n idstr[i] = tostring(id) .. ','\r\n assert(not string.isnilorempty(key), string.format('[CsvUtil] CSV表格没有找到此id, CSV:%s, id: %s', _csv.Name, id))\r\n if i == #ids then\r\n -- 最后的键,确定唯一性\r\n assert(\r\n not tmp[key],\r\n string.format('[CsvUtil] CSV数据重复, ids不是唯一的, CSV: %s, ids: %s', _csv.Name, table.concat(idstr))\r\n )\r\n tmp[key] = v\r\n else\r\n -- 多键,之后还有\r\n if tmp[key] == nil then\r\n tmp[key] = {}\r\n end\r\n tmp = tmp[key]\r\n end\r\n end\r\n end\r\n return result\r\nend\r\n\r\n--- 读取Config全局配置表\r\n-- GlobleSetting.csv 表格内容为:\r\n-- ---------------------------------------------------------\r\n-- | String | String | String | String |\r\n-- | Key | Type | Value | Des |\r\n-- ---------------------------------------------------------\r\n-- | CubeMax | Int | 200 | 最大Cube数 |\r\n-- | BattleTime | Float | 5.45 | 战斗时间 |\r\n-- | GameTitle | String | Boom Party | 游戏标题 |\r\n-- | IsFree | Boolean | true | 是否免费 |\r\n-- | UiMapOrigin | Vector2 | 3,4 | UI地图原点位置 |\r\n-- | TreePos | Vector3 | 12,3,-3 | 树的位置 |\r\n-- | TreeRot | Euler | 45,90,0 | 树的旋转 |\r\n-- | TreeColor | Color | 255,255,255,0 | 树的颜色 |\r\nfunction CsvUtil.GetGlobalCsvInfo(_csv)\r\n local rawTable = _csv:GetRows()\r\n if table.nums(rawTable) == 0 then\r\n return\r\n end\r\n assert(rawTable['1'].Key, '[CsvUtil] 全局配置表的没有\"Key\"')\r\n assert(rawTable['1'].Type, '[CsvUtil] 全局配置表的没有\"Type\"')\r\n assert(rawTable['1'].Value, '[CsvUtil] 全局配置表的没有\"Value\"')\r\n local result = {}\r\n for _, v in pairs(rawTable) do\r\n result[v.Key] = GetValue(v['Type'], v['Value'])\r\n PrintGlobalKV(v.Key, v.Type, result[v.Key]) -- * 输出KV键值对\r\n end\r\n return result\r\nend\r\n\r\n--- 表格预加载,预加载配置模块:World.Global.Define.ConfigModule\r\nfunction CsvUtil.PreloadCsv(_preloadList, _csvRoot, _config)\r\n assert(_preloadList and #_preloadList > 0, '[CsvUtil] ConfigModule中没有预加载表格')\r\n for _, pl in pairs(_preloadList) do\r\n if not string.isnilorempty(pl.csv) then\r\n pl.name = string.isnilorempty(pl.name) and pl.csv or pl.name\r\n PrintLog(string.format('[CsvUtil] Load: %s.csv', pl.csv))\r\n if pl.csv == 'GlobalSetting' and _csvRoot[pl.csv] then\r\n _config[pl.name] = CsvUtil.GetGlobalCsvInfo(_csvRoot[pl.csv])\r\n elseif not string.isnilorempty(pl.csv) and _csvRoot[pl.csv] then\r\n pl.ids = pl.ids or {}\r\n _config[pl.name] = CsvUtil.GetCsvInfo(_csvRoot[pl.csv], table.unpack(pl.ids))\r\n end\r\n end\r\n end\r\nend\r\n\r\n--! 辅助功能\r\n\r\n--- 输出全局变量键值对\r\nPrintGlobalKV =\r\n showLog and\r\n function(_key, _type, _value)\r\n _type = string.lower(_type)\r\n local showTypes = {\r\n vector2 = 'Vector2',\r\n vector3 = 'Vector3',\r\n euler = 'EulerDegree',\r\n color = 'Color'\r\n }\r\n if showTypes[_type] then\r\n print(string.format('[CsvUtil][GlobalSetting] %s = %s%s ', _key, showTypes[_type], _value))\r\n else\r\n print(string.format('[CsvUtil][GlobalSetting] %s = %s ', _key, _value))\r\n end\r\n end or\r\n function()\r\n end\r\n\r\nPrintLog = showLog and function(...)\r\n print(...)\r\n end or function()\r\n end\r\n\r\nreturn CsvUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"XlsUtilModule","guid":[2760847323,3965272240,2950769927,3980104073],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"XlsUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 读表工具: 将导入成Lua Table,支持单一主键和多主键\r\n-- @module XLS Utility\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\n-- @see https://wiki.lilithgames.com/x/RGEMAg\r\nlocal XlsUtil = {}\r\n\r\n--! 打印事件日志, true:开启打印\r\nlocal showLog, PrintGlobalKV, PrintLog = true\r\n\r\n--- 将表中的字符串改为数字\r\n-- @param _t input table\r\nlocal function StrToNum(_t)\r\n for k, v in pairs(_t) do\r\n _t[k] = tonumber(v)\r\n end\r\n return _t\r\nend\r\n\r\n--- 类型解析配置表\r\nlocal parser = {\r\n int = function(_raw)\r\n return math.floor(tonumber(_raw))\r\n end,\r\n float = function(_raw)\r\n return tonumber(_raw)\r\n end,\r\n string = function(_raw)\r\n return _raw\r\n end,\r\n boolean = function(_raw)\r\n return string.lower(_raw) == 'true'\r\n end,\r\n vector2 = function(_raw)\r\n return Vector2(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n vector3 = function(_raw)\r\n return Vector3(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n euler = function(_raw)\r\n return EulerDegree(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end,\r\n color = function(_raw)\r\n return Color(table.unpack(StrToNum(string.split(_raw, ','))))\r\n end\r\n}\r\n\r\n--- 读取配置表,会根据id生成lua表\r\n-- @param _type String 数据类型\r\n-- @parm _stringValue String 数据\r\n-- @return value 解析出来的数值\r\nlocal function GetValue(_type, _stringValue)\r\n _type = string.lower(_type)\r\n assert(parser[_type], string.format('[XlsUtil][GlobalSetting] \"%s\" Type字段的值不是目前所支持的数据类型', _type))\r\n return parser[_type](_stringValue)\r\nend\r\n\r\n--- 根据id转换lua table\r\nfunction XlsUtil.GetXlsInfo(_xls, ...)\r\n local ids = {...}\r\n if #ids < 1 or (#ids == 1 and ids[1] == 'Type') then\r\n -- 默认用Type索引,直接返回\r\n return _xls\r\n end\r\n local rawTable = _xls\r\n local result = {}\r\n local tmp, key, id, idstr -- 临时变量\r\n for _, v in pairs(rawTable) do\r\n tmp = result\r\n idstr = {}\r\n for i = 1, #ids do\r\n id = ids[i]\r\n key = v[id]\r\n idstr[i] = tostring(id) .. ','\r\n assert(\r\n not string.isnilorempty(key),\r\n string.format('[XlsUtil] Excel表格没有找到此id, Excel:%s, id: %s', _xls.Name, id)\r\n )\r\n if i == #ids then\r\n -- 最后的键,确定唯一性\r\n assert(\r\n not tmp[key],\r\n string.format('[XlsUtil] Excel数据重复, ids不是唯一的, Excel: %s, ids: %s', _xls.Name, table.concat(idstr))\r\n )\r\n tmp[key] = v\r\n else\r\n -- 多键,之后还有\r\n if tmp[key] == nil then\r\n tmp[key] = {}\r\n end\r\n tmp = tmp[key]\r\n end\r\n end\r\n end\r\n return result\r\nend\r\n\r\n--- 读取Config全局配置表\r\nfunction XlsUtil.GetGlobalXlsInfo(_xls)\r\n local rawTable = _xls\r\n if table.nums(rawTable) == 0 then\r\n return\r\n end\r\n assert(rawTable[1].Key, '[XlsUtil] 全局配置表的没有\"Key\"')\r\n assert(rawTable[1].Type, '[XlsUtil] 全局配置表的没有\"Type\"')\r\n assert(rawTable[1].Value, '[XlsUtil] 全局配置表的没有\"Value\"')\r\n local result = {}\r\n for _, v in pairs(rawTable) do\r\n result[v.Key] = GetValue(v['Type'], v['Value'])\r\n PrintGlobalKV(v.Key, v.Type, result[v.Key]) -- * 输出KV键值对\r\n end\r\n return result\r\nend\r\n\r\n--- 表格预加载,预加载配置模块:World.Global.Define.ConfigModule\r\nfunction XlsUtil.PreloadXls(_preloadList, _xlsRoot, _config)\r\n -- todo: load xls lua talbe\r\n assert(_preloadList and #_preloadList > 0, 'ConfigModule中没有预加载表格')\r\n\r\n for _, pl in pairs(_preloadList) do\r\n if not string.isnilorempty(pl.xls) then\r\n pl.name = string.isnilorempty(pl.name) and pl.xls or pl.name\r\n pl.module = string.isnilorempty(pl.module) and pl.xls .. 'Xls' or pl.module\r\n PrintLog(string.format('[XlsUtil] Load: %s', pl.module))\r\n if pl.xls == 'GlobalSetting' and _xlsRoot[pl.module .. 'Module'] then\r\n _config[pl.name] = XlsUtil.GetGlobalXlsInfo(_G[pl.module])\r\n elseif not string.isnilorempty(pl.xls) and _G[pl.module] then\r\n pl.ids = pl.ids or {}\r\n _config[pl.name] = XlsUtil.GetXlsInfo(_G[pl.module], table.unpack(pl.ids))\r\n end\r\n end\r\n end\r\nend\r\n\r\n--! 辅助功能\r\n\r\n--- 输出全局变量键值对\r\nPrintGlobalKV =\r\n showLog and\r\n function(_key, _type, _value)\r\n _type = string.lower(_type)\r\n local showTypes = {\r\n vector2 = 'Vector2',\r\n vector3 = 'Vector3',\r\n euler = 'EulerDegree',\r\n color = 'Color'\r\n }\r\n if showTypes[_type] then\r\n print(string.format('[XlsUtil][GlobalSetting] %s = %s%s ', _key, showTypes[_type], _value))\r\n else\r\n print(string.format('[XlsUtil][GlobalSetting] %s = %s ', _key, _value))\r\n end\r\n end or\r\n function()\r\n end\r\n\r\nPrintLog = showLog and function(...)\r\n print(...)\r\n end or function()\r\n end\r\n\r\nreturn XlsUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"EventUtilModule","guid":[2566228644,2776910007,2667900515,194242277],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"EventUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 事件绑定工具\r\n-- @module Event Connects Handler\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang, Yen Yuan\r\nlocal EventUtil = {}\r\n\r\n--- 检查是否为Json化的字符串\r\n-- @param _str @string 输入的字符串\r\n-- @return @boolean true: json table string\r\nlocal function IsJsonTable(_str)\r\n return type(_str) == 'string' and string.endswith(_str, 'JSON') and string.startswith(_str, 'JSON')\r\nend\r\n\r\n--- 处理Handler的传入参数\r\n--@param variable args\r\n--@return variable args\r\nlocal function ArgsAux(...)\r\n local _s = {...}\r\n for k, v in pairs(_s) do\r\n if IsJsonTable(v) then\r\n local json = string.sub(v, 5, -5)\r\n _s[k] = LuaJsonUtil:decode(json)\r\n end\r\n end\r\n return table.unpack(_s)\r\nend\r\n\r\n--- 遍历所有的events,找到module中对应名称的handler,建立Connect\r\n-- @param _eventFolder 事件所在的节点folder\r\n-- @param _module 模块\r\n-- @param _this module的self指针,用于闭包\r\nfunction EventUtil.LinkConnects(_eventFolder, _module, _this)\r\n assert(\r\n _eventFolder and _module and _this,\r\n string.format('[EventUtil] 参数有空值: %s, %s, %s', _eventFolder, _module, _this)\r\n )\r\n local events = _eventFolder:GetChildren()\r\n for _, evt in pairs(events) do\r\n if string.endswith(evt.Name, 'Event') then\r\n local handler = _module[evt.Name .. 'Handler']\r\n if handler ~= nil then\r\n -- print('[EventUtil]', _eventFolder, _module, evt)\r\n evt:Connect(\r\n function(...)\r\n handler(_this, ArgsAux(...))\r\n end\r\n )\r\n end\r\n end\r\n end\r\nend\r\n\r\nreturn EventUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"TimeUtilModule","guid":[3212895662,3876605484,2451409624,1852532118],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"TimeUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 时间管理器模块\r\n-- @module Module Time Manager\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Bingyun Chen, Yuancheng Zhang\r\n-- @see the functions defined by JavaScript syntax\r\n\r\nlocal TimeUtil = {}\r\n\r\n-- All registered events\r\nlocal eventList = {}\r\n\r\n-- Current active event list\r\nlocal activeEvents = {}\r\n\r\nlocal running = false\r\n\r\n-- Set update delta time\r\nlocal DELTA_TIME = .05\r\n\r\n--- Find all registered events to trigger\r\nlocal function CheckEvents()\r\n -- now = os.time()\r\n local now = Timer.GetTimeMillisecond()\r\n local i, event = 1\r\n while i <= #eventList do\r\n event = eventList[i]\r\n if event.triggerTime <= now then\r\n table.insert(activeEvents, event)\r\n if event.loop then\r\n event.triggerTime = event.triggerTime + event.delay\r\n i = i + 1\r\n else\r\n table.remove(eventList, i)\r\n end\r\n else\r\n i = i + 1\r\n end\r\n end\r\nend\r\n\r\n--- Trigger events\r\nlocal function TriggerEvents()\r\n local i = 1\r\n while i <= #activeEvents do\r\n event = activeEvents[i]\r\n invoke(event.func)\r\n table.remove(activeEvents, i)\r\n end\r\nend\r\n\r\n--- Update\r\nlocal function StartUpdate()\r\n while running do\r\n -- print(os.time())\r\n CheckEvents()\r\n TriggerEvents()\r\n wait(DELTA_TIME)\r\n end\r\nend\r\n\r\n--- Initialization\r\nfunction TimeUtil.Init()\r\n TimeUtil.Start()\r\nend\r\n\r\n--- Run Update()\r\nfunction TimeUtil.Start()\r\n running = true\r\n invoke(StartUpdate)\r\nend\r\n\r\n--- Stop Update()\r\nfunction TimeUtil.Stop()\r\n running = false\r\nend\r\n\r\n--- Call a function after a specified number of milliseconds,\r\n-- use ClearTimeout() method to prevent the function from running\r\n-- @param _func execution function to call\r\n-- @param _delayTime\r\n-- @return timer id\r\n-- @see https://www.w3schools.com/jsref/met_win_settimeout.asp\r\nfunction TimeUtil.SetTimeout(_func, _seconds)\r\n assert(_func, '[TimeUtil] TimeUtil.SetTimeout() _func 不能为空')\r\n assert(_seconds >= 0, '[TimeUtil] TimeUtil.SetTimeout() 延迟时间需大于等于0')\r\n if _seconds == 0 then\r\n print('[TimeUtil] TimeUtil.SetTimeout() 事件立即执行')\r\n invoke(_func)\r\n return\r\n end\r\n local id = #eventList + 1\r\n -- convert to milliseconds\r\n local ms = math.floor(_seconds * 1000)\r\n local timestamp = ms + Timer.GetTimeMillisecond()\r\n table.insert(\r\n eventList,\r\n {\r\n id = id,\r\n func = _func,\r\n delay = ms,\r\n triggerTime = timestamp\r\n }\r\n )\r\n return id\r\nend\r\n\r\n--- Call a function or evaluates an expression at specified intervals (in milliseconds),\r\n-- the method will continue calling the function until ClearInterval() is called, or the game is over.\r\n-- @param _func execution function to call\r\n-- @param _delayTime\r\n-- @return timer id\r\n-- @see https://www.w3schools.com/jsref/met_win_setinterval.asp\r\nfunction TimeUtil.SetInterval(_func, _seconds)\r\n assert(_func, '[TimeUtil] TimeUtil.SetInterval() _func 不能为空')\r\n assert(_seconds > 0, '[TimeUtil] TimeUtil.SetInterval() 延迟时间需大于0')\r\n local id = #eventList + 1\r\n -- convert to milliseconds\r\n local ms = math.floor(_seconds * 1000)\r\n local timestamp = ms + Timer.GetTimeMillisecond()\r\n table.insert(\r\n eventList,\r\n {\r\n id = id,\r\n func = _func,\r\n delay = ms,\r\n triggerTime = timestamp,\r\n loop = true\r\n }\r\n )\r\n return id\r\nend\r\n\r\n--- Clear a timer set with the SetTimeout() method\r\n-- @param _id timmer id\r\n-- @see https://www.w3schools.com/jsref/met_win_cleartimeout.asp\r\nfunction TimeUtil.ClearTimeout(_id)\r\n for k, e in pairs(eventList) do\r\n if e.id == _id then\r\n table.remove(eventList, k)\r\n break\r\n end\r\n end\r\nend\r\n\r\n--- Clear a timer set with the SetInterval() method, used as ClearTimeout()\r\n-- @see https://www.w3schools.com/jsref/met_win_clearinterval.asp\r\nTimeUtil.ClearInterval = TimeUtil.ClearTimeout\r\n\r\nreturn TimeUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"LogUtilModule","guid":[1448450812,1700152430,3083838744,455591299],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LogUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- Debug工具\r\n-- @module Debug utilities\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nlocal LogUtil = {}\r\n\r\n--- 日志级别枚举\r\nLogUtil.LevelEnum = {\r\n -- 指出细粒度信息事件对调试应用程序是非常有帮助的 主要用于开发过程中打印一些运行信息\r\n DEBUG = 1,\r\n -- 消息在粗粒度级别上突出强调应用程序的运行过程\r\n -- 打印一些你感兴趣的或者重要的信息 这个可以用于生产环境中输出程序运行的一些重要信息\r\n -- 但是不能滥用 避免打印过多的日志\r\n INFO = 2,\r\n -- 表明会出现潜在错误的情形 有些信息不是错误信息 但是也要给程序员的一些提示\r\n -- 指出虽然发生错误事件 但仍然不影响系统的继续运行\r\n -- 打印错误和异常信息 如果不想输出太多的日志 可以使用这个级别\r\n ERROR = 3\r\n}\r\n\r\n--- 日志级别\r\nLogUtil.level = LogUtil.LevelEnum.DEBUG\r\n\r\n--- 开关\r\nLogUtil.debugMode = true\r\n\r\nfunction LogUtil.Test(...)\r\n if LogUtil.debugMode and LogUtil.level <= LogUtil.LevelEnum.DEBUG then\r\n print('[TEST]', ...)\r\n end\r\nend\r\n\r\nfunction LogUtil.Debug(...)\r\n if LogUtil.debugMode and LogUtil.level <= LogUtil.LevelEnum.DEBUG then\r\n print('[DEBUG]', ...)\r\n end\r\nend\r\n\r\nfunction LogUtil.Info(...)\r\n if LogUtil.debugMode and LogUtil.level <= LogUtil.LevelEnum.INFO then\r\n print('[INFO]', ...)\r\n end\r\nend\r\n\r\nreturn LogUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"CamUtilModule","guid":[1786588942,1014908186,2189044832,747029913],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"CamUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---摄像机工具类\r\n---@module Cam Utility\r\n---@copyright Lilith Games, Avatar Team\r\n---@author Sharif Ma\r\n---@class CamUtil\r\nlocal CamUtil = {}\r\n\r\n---将摄像机在水平面上转动到和角色朝向一致的角度\r\n---@param _player PlayerInstance 摄像机看向的物体\r\n---@param _cam Camera 转动的摄像机\r\n---@param _time number 转动过程的事件,不填则瞬间转动\r\nfunction CamUtil.ToRoleForward(_player, _cam, _time)\r\n _time = _time or 0\r\n local dir = _player.Position - _cam.Position\r\n local forward = _player.Forward\r\n local alpha = Vector2.Angle(Vector2(dir.x, dir.z), Vector2(forward.x, forward.z))\r\n local left = _player.Left\r\n if Vector3.Angle(left, dir) > 90 then\r\n alpha = 360 - alpha\r\n end\r\n if _time == 0 then\r\n _cam:CameraMoveInDegree(Vector2(alpha, 0))\r\n return\r\n end\r\n invoke(\r\n function()\r\n local curTime = 0\r\n while true do\r\n local dt = wait()\r\n local dtDe = alpha * dt / _time\r\n _cam:CameraMoveInDegree(Vector2(dtDe, 0))\r\n curTime = curTime + dt\r\n if curTime >= _time then\r\n return\r\n end\r\n end\r\n end\r\n )\r\nend\r\n\r\nreturn CamUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"UuidModule","guid":[4157188643,3705227052,3158561090,4053995093],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"UuidModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---------------------------------------------------------------------------------------\r\n-- Copyright 2012 Rackspace (original), 2013 Thijs Schreijer (modifications)\r\n--\r\n-- Licensed under the Apache License, Version 2.0 (the \"License\");\r\n-- you may not use this file except in compliance with the License.\r\n-- You may obtain a copy of the License at\r\n--\r\n-- http://www.apache.org/licenses/LICENSE-2.0\r\n--\r\n-- Unless required by applicable law or agreed to in writing, software\r\n-- distributed under the License is distributed on an \"AS-IS\" BASIS,\r\n-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n-- See the License for the specific language governing permissions and\r\n-- limitations under the License.\r\n--\r\n-- see http://www.ietf.org/rfc/rfc4122.txt\r\n--\r\n-- Note that this is not a true version 4 (random) UUID. Since `os.time()` precision is only 1 second, it would be hard\r\n-- to guarantee spacial uniqueness when two hosts generate a uuid after being seeded during the same second. This\r\n-- is solved by using the node field from a version 1 UUID. It represents the mac address.\r\n--\r\n-- 28-apr-2013 modified by Thijs Schreijer from the original [Rackspace code](https://github.com/kans/zirgo/blob/807250b1af6725bad4776c931c89a784c1e34db2/util/uuid.lua) as a generic Lua module.\r\n-- Regarding the above mention on `os.time()`; the modifications use the `socket.gettime()` function from LuaSocket\r\n-- if available and hence reduce that problem (provided LuaSocket has been loaded before uuid).\r\n--\r\n-- **6-nov-2015 Please take note of this issue**; [https://github.com/Mashape/kong/issues/478](https://github.com/Mashape/kong/issues/478)\r\n-- It demonstrates the problem of using time as a random seed. Specifically when used from multiple processes.\r\n-- So make sure to seed only once, application wide. And to not have multiple processes do that\r\n-- simultaneously (like nginx does for example).\r\n\r\nlocal M = {}\r\n\r\nlocal bitsize = 32 -- bitsize assumed for Lua VM. See randomseed function below.\r\nlocal lua_version = tonumber(_VERSION:match('%d%.*%d*')) -- grab Lua version used\r\n\r\nlocal MATRIX_AND = {{0, 0}, {0, 1}}\r\nlocal MATRIX_OR = {{0, 1}, {1, 1}}\r\nlocal HEXES = '0123456789abcdef'\r\n\r\nlocal math_floor = math.floor\r\nlocal math_random = math.random\r\nlocal math_abs = math.abs\r\nlocal string_sub = string.sub\r\nlocal to_number = tonumber\r\nlocal assert = assert\r\nlocal type = type\r\n\r\n-- performs the bitwise operation specified by truth matrix on two numbers.\r\nlocal function BITWISE(x, y, matrix)\r\n local z = 0\r\n local pow = 1\r\n while x > 0 or y > 0 do\r\n z = z + (matrix[x % 2 + 1][y % 2 + 1] * pow)\r\n pow = pow * 2\r\n x = math_floor(x / 2)\r\n y = math_floor(y / 2)\r\n end\r\n return z\r\nend\r\n\r\nlocal function INT2HEX(x)\r\n local s, base = '', 16\r\n local d\r\n while x > 0 do\r\n d = x % base + 1\r\n x = math_floor(x / base)\r\n s = string_sub(HEXES, d, d) .. s\r\n end\r\n while #s < 2 do\r\n s = '0' .. s\r\n end\r\n return s\r\nend\r\n\r\n----------------------------------------------------------------------------\r\n-- Creates a new uuid. Either provide a unique hex string, or make sure the\r\n-- random seed is properly set. The module table itself is a shortcut to this\r\n-- function, so `my_uuid = uuid.new()` equals `my_uuid = uuid()`.\r\n--\r\n-- For proper use there are 3 options;\r\n--\r\n-- 1. first require `luasocket`, then call `uuid.seed()`, and request a uuid using no\r\n-- parameter, eg. `my_uuid = uuid()`\r\n-- 2. use `uuid` without `luasocket`, set a random seed using `uuid.randomseed(some_good_seed)`,\r\n-- and request a uuid using no parameter, eg. `my_uuid = uuid()`\r\n-- 3. use `uuid` without `luasocket`, and request a uuid using an unique hex string,\r\n-- eg. `my_uuid = uuid(my_networkcard_macaddress)`\r\n--\r\n-- @return a properly formatted uuid string\r\n-- @param hwaddr (optional) string containing a unique hex value (e.g.: `00:0c:29:69:41:c6`), to be used to compensate for the lesser `math_random()` function. Use a mac address for solid results. If omitted, a fully randomized uuid will be generated, but then you must ensure that the random seed is set properly!\r\n-- @usage\r\n-- local uuid = require(\"uuid\")\r\n-- print(\"here's a new uuid: \",uuid())\r\nfunction M.new(hwaddr)\r\n -- bytes are treated as 8bit unsigned bytes.\r\n local bytes = {\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255),\r\n math_random(0, 255)\r\n }\r\n\r\n if hwaddr then\r\n assert(type(hwaddr) == 'string', 'Expected hex string, got ' .. type(hwaddr))\r\n -- Cleanup provided string, assume mac address, so start from back and cleanup until we've got 12 characters\r\n local i, str = #hwaddr, hwaddr\r\n hwaddr = ''\r\n while i > 0 and #hwaddr < 12 do\r\n local c = str:sub(i, i):lower()\r\n if HEXES:find(c, 1, true) then\r\n -- valid HEX character, so append it\r\n hwaddr = c .. hwaddr\r\n end\r\n i = i - 1\r\n end\r\n assert(\r\n #hwaddr == 12,\r\n \"Provided string did not contain at least 12 hex characters, retrieved '\" ..\r\n hwaddr .. \"' from '\" .. str .. \"'\"\r\n )\r\n\r\n -- no split() in lua. :(\r\n bytes[11] = to_number(hwaddr:sub(1, 2), 16)\r\n bytes[12] = to_number(hwaddr:sub(3, 4), 16)\r\n bytes[13] = to_number(hwaddr:sub(5, 6), 16)\r\n bytes[14] = to_number(hwaddr:sub(7, 8), 16)\r\n bytes[15] = to_number(hwaddr:sub(9, 10), 16)\r\n bytes[16] = to_number(hwaddr:sub(11, 12), 16)\r\n end\r\n\r\n -- set the version\r\n bytes[7] = BITWISE(bytes[7], 0x0f, MATRIX_AND)\r\n bytes[7] = BITWISE(bytes[7], 0x40, MATRIX_OR)\r\n -- set the variant\r\n bytes[9] = BITWISE(bytes[7], 0x3f, MATRIX_AND)\r\n bytes[9] = BITWISE(bytes[7], 0x80, MATRIX_OR)\r\n return INT2HEX(bytes[1]) ..\r\n INT2HEX(bytes[2]) ..\r\n INT2HEX(bytes[3]) ..\r\n INT2HEX(bytes[4]) ..\r\n '-' ..\r\n INT2HEX(bytes[5]) ..\r\n INT2HEX(bytes[6]) ..\r\n '-' ..\r\n INT2HEX(bytes[7]) ..\r\n INT2HEX(bytes[8]) ..\r\n '-' ..\r\n INT2HEX(bytes[9]) ..\r\n INT2HEX(bytes[10]) ..\r\n '-' ..\r\n INT2HEX(bytes[11]) ..\r\n INT2HEX(bytes[12]) ..\r\n INT2HEX(bytes[13]) ..\r\n INT2HEX(bytes[14]) ..\r\n INT2HEX(bytes[15]) .. INT2HEX(bytes[16])\r\nend\r\n\r\n----------------------------------------------------------------------------\r\n-- Improved randomseed function.\r\n-- Lua 5.1 and 5.2 both truncate the seed given if it exceeds the integer\r\n-- range. If this happens, the seed will be 0 or 1 and all randomness will\r\n-- be gone (each application run will generate the same sequence of random\r\n-- numbers in that case). This improved version drops the most significant\r\n-- bits in those cases to get the seed within the proper range again.\r\n-- @param seed the random seed to set (integer from 0 - 2^32, negative values will be made positive)\r\n-- @return the (potentially modified) seed used\r\n-- @usage\r\n-- local socket = require(\"socket\") -- gettime() has higher precision than os.time()\r\n-- local uuid = require(\"uuid\")\r\n-- -- see also example at uuid.seed()\r\n-- uuid.randomseed(socket.gettime()*10000)\r\n-- print(\"here's a new uuid: \",uuid())\r\nfunction M.randomseed(seed)\r\n seed = math_floor(math_abs(seed))\r\n if seed >= (2 ^ bitsize) then\r\n -- integer overflow, so reduce to prevent a bad seed\r\n seed = seed - math_floor(seed / 2 ^ bitsize) * (2 ^ bitsize)\r\n end\r\n if lua_version < 5.2 then\r\n -- 5.1 uses (incorrect) signed int\r\n math.randomseed(seed - 2 ^ (bitsize - 1))\r\n else\r\n -- 5.2 uses (correct) unsigned int\r\n math.randomseed(seed)\r\n end\r\n return seed\r\nend\r\n\r\n----------------------------------------------------------------------------\r\n-- Seeds the random generator.\r\n-- It does so in 2 possible ways;\r\n--\r\n-- 1. use `os.time()`: this only offers resolution to one second (used when\r\n-- LuaSocket hasn't been loaded yet\r\n-- 2. use luasocket `gettime()` function, but it only does so when LuaSocket\r\n-- has been required already.\r\n-- @usage\r\n-- local socket = require(\"socket\") -- gettime() has higher precision than os.time()\r\n-- -- LuaSocket loaded, so below line does the same as the example from randomseed()\r\n-- uuid.seed()\r\n-- print(\"here's a new uuid: \",uuid())\r\nfunction M.seed()\r\n -- if package.loaded['socket'] and package.loaded['socket'].gettime then\r\n -- return M.randomseed(package.loaded['socket'].gettime() * 10000)\r\n -- else\r\n return M.randomseed(os.time())\r\n -- end\r\nend\r\n\r\nreturn setmetatable(\r\n M,\r\n {\r\n __call = function(self, hwaddr)\r\n return self.new(hwaddr)\r\n end\r\n }\r\n)\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"LuaJsonUtilModule","guid":[1171767027,1956007823,2562650671,2698147667],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LuaJsonUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- -*- coding: utf-8 -*-\r\n---\r\n--- Simple JSON encoding and decoding in pure Lua.\r\n---\r\n--- Copyright 2010-2014 Jeffrey Friedl\r\n--- http://regex.info/blog/\r\n---\r\n--- Latest version: http://regex.info/blog/lua/json\r\n---\r\n--- This code is released under a Creative Commons CC-BY \"Attribution\" License:\r\n--- http://creativecommons.org/licenses/by/3.0/deed.en_US\r\n---\r\n--- It can be used for any purpose so long as the copyright notice above,\r\n--- the web-page links above, and the 'AUTHOR_NOTE' string below are\r\n--- maintained. Enjoy.\r\n---\r\nlocal VERSION = 20141223.14 --- version history at end of file\r\nlocal AUTHOR_NOTE = '-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20141223.14 ]-'\r\n\r\n---\r\n--- The 'AUTHOR_NOTE' variable exists so that information about the source\r\n--- of the package is maintained even in compiled versions. It's also\r\n--- included in OBJDEF below mostly to quiet warnings about unused variables.\r\n---\r\n---@module LuaJson\r\nlocal OBJDEF = {\r\n VERSION = VERSION,\r\n AUTHOR_NOTE = AUTHOR_NOTE\r\n}\r\n\r\n---\r\n--- Simple JSON encoding and decoding in pure Lua.\r\n--- http://www.json.org/\r\n---\r\n--\r\n-- JSON = assert(loadfile \"JSON.lua\")() -- one-time load of the routines\r\n--\r\n-- local lua_value = JSON:decode(raw_json_text)\r\n--\r\n-- local raw_json_text = JSON:encode(lua_table_or_value)\r\n-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- \"pretty printed\" version for human readability\r\n--\r\n--\r\n--\r\n-- DECODING (from a JSON string to a Lua table)\r\n--\r\n--\r\n-- JSON = assert(loadfile \"JSON.lua\")() -- one-time load of the routines\r\n--\r\n-- local lua_value = JSON:decode(raw_json_text)\r\n--\r\n-- If the JSON text is for an object or an array, e.g.\r\n-- { \"what\": \"books\", \"count\": 3 }\r\n-- or\r\n-- [ \"Larry\", \"Curly\", \"Moe\" ]\r\n--\r\n-- the result is a Lua table, e.g.\r\n-- { what = \"books\", count = 3 }\r\n-- or\r\n-- { \"Larry\", \"Curly\", \"Moe\" }\r\n--\r\n--\r\n-- The encode and decode routines accept an optional second argument,\r\n-- \"etc\", which is not used during encoding or decoding, but upon error\r\n-- is passed along to error handlers. It can be of any type (including nil).\r\n--\r\n--\r\n--\r\n-- ERROR HANDLING\r\n--\r\n-- With most errors during decoding, this code calls\r\n--\r\n-- JSON:onDecodeError(message, text, location, etc)\r\n--\r\n-- with a message about the error, and if known, the JSON text being\r\n-- parsed and the byte count where the problem was discovered. You can\r\n-- replace the default JSON:onDecodeError() with your own function.\r\n--\r\n-- The default onDecodeError() merely augments the message with data\r\n-- about the text and the location if known (and if a second 'etc'\r\n-- argument had been provided to decode(), its value is tacked onto the\r\n-- message as well), and then calls JSON.assert(), which itself defaults\r\n-- to Lua's built-in assert(), and can also be overridden.\r\n--\r\n-- For example, in an Adobe Lightroom plugin, you might use something like\r\n--\r\n-- function JSON:onDecodeError(message, text, location, etc)\r\n-- LrErrors.throwUserError(\"Internal Error: invalid JSON data\")\r\n-- end\r\n--\r\n-- or even just\r\n--\r\n-- function JSON.assert(message)\r\n-- LrErrors.throwUserError(\"Internal Error: \" .. message)\r\n-- end\r\n--\r\n-- If JSON:decode() is passed a nil, this is called instead:\r\n--\r\n-- JSON:onDecodeOfNilError(message, nil, nil, etc)\r\n--\r\n-- and if JSON:decode() is passed HTML instead of JSON, this is called:\r\n--\r\n-- JSON:onDecodeOfHTMLError(message, text, nil, etc)\r\n--\r\n-- The use of the fourth 'etc' argument allows stronger coordination\r\n-- between decoding and error reporting, especially when you provide your\r\n-- own error-handling routines. Continuing with the the Adobe Lightroom\r\n-- plugin example:\r\n--\r\n-- function JSON:onDecodeError(message, text, location, etc)\r\n-- local note = \"Internal Error: invalid JSON data\"\r\n-- if type(etc) = 'table' and etc.photo then\r\n-- note = note .. \" while processing for \" .. etc.photo:getFormattedMetadata('fileName')\r\n-- end\r\n-- LrErrors.throwUserError(note)\r\n-- end\r\n--\r\n-- :\r\n-- :\r\n--\r\n-- for i, photo in ipairs(photosToProcess) do\r\n-- :\r\n-- :\r\n-- local data = JSON:decode(someJsonText, { photo = photo })\r\n-- :\r\n-- :\r\n-- end\r\n--\r\n--\r\n--\r\n--\r\n--\r\n-- DECODING AND STRICT TYPES\r\n--\r\n-- Because both JSON objects and JSON arrays are converted to Lua tables,\r\n-- it's not normally possible to tell which original JSON type a\r\n-- particular Lua table was derived from, or guarantee decode-encode\r\n-- round-trip equivalency.\r\n--\r\n-- However, if you enable strictTypes, e.g.\r\n--\r\n-- JSON = assert(loadfile \"JSON.lua\")() --load the routines\r\n-- JSON.strictTypes = true\r\n--\r\n-- then the Lua table resulting from the decoding of a JSON object or\r\n-- JSON array is marked via Lua metatable, so that when re-encoded with\r\n-- JSON:encode() it ends up as the appropriate JSON type.\r\n--\r\n-- (This is not the default because other routines may not work well with\r\n-- tables that have a metatable set, for example, Lightroom API calls.)\r\n--\r\n--\r\n-- ENCODING (from a lua table to a JSON string)\r\n--\r\n-- JSON = assert(loadfile \"JSON.lua\")() -- one-time load of the routines\r\n--\r\n-- local raw_json_text = JSON:encode(lua_table_or_value)\r\n-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- \"pretty printed\" version for human readability\r\n-- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = \"| \", align_keys = false })\r\n--\r\n-- On error during encoding, this code calls:\r\n--\r\n-- JSON:onEncodeError(message, etc)\r\n--\r\n-- which you can override in your local JSON object.\r\n--\r\n-- The 'etc' in the error call is the second argument to encode()\r\n-- and encode_pretty(), or nil if it wasn't provided.\r\n--\r\n--\r\n-- PRETTY-PRINTING\r\n--\r\n-- An optional third argument, a table of options, allows a bit of\r\n-- configuration about how the encoding takes place:\r\n--\r\n-- pretty = JSON:encode(val, etc, {\r\n-- pretty = true, -- if false, no other options matter\r\n-- indent = \" \", -- this provides for a three-space indent per nesting level\r\n-- align_keys = false, -- see below\r\n-- })\r\n--\r\n-- encode() and encode_pretty() are identical except that encode_pretty()\r\n-- provides a default options table if none given in the call:\r\n--\r\n-- { pretty = true, align_keys = false, indent = \" \" }\r\n--\r\n-- For example, if\r\n--\r\n-- JSON:encode(data)\r\n--\r\n-- produces:\r\n--\r\n-- {\"city\":\"Kyoto\",\"climate\":{\"avg_temp\":16,\"humidity\":\"high\",\"snowfall\":\"minimal\"},\"country\":\"Japan\",\"wards\":11}\r\n--\r\n-- then\r\n--\r\n-- JSON:encode_pretty(data)\r\n--\r\n-- produces:\r\n--\r\n-- {\r\n-- \"city\": \"Kyoto\",\r\n-- \"climate\": {\r\n-- \"avg_temp\": 16,\r\n-- \"humidity\": \"high\",\r\n-- \"snowfall\": \"minimal\"\r\n-- },\r\n-- \"country\": \"Japan\",\r\n-- \"wards\": 11\r\n-- }\r\n--\r\n-- The following three lines return identical results:\r\n-- JSON:encode_pretty(data)\r\n-- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = \" \" })\r\n-- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = \" \" })\r\n--\r\n-- An example of setting your own indent string:\r\n--\r\n-- JSON:encode_pretty(data, nil, { pretty = true, indent = \"| \" })\r\n--\r\n-- produces:\r\n--\r\n-- {\r\n-- | \"city\": \"Kyoto\",\r\n-- | \"climate\": {\r\n-- | | \"avg_temp\": 16,\r\n-- | | \"humidity\": \"high\",\r\n-- | | \"snowfall\": \"minimal\"\r\n-- | },\r\n-- | \"country\": \"Japan\",\r\n-- | \"wards\": 11\r\n-- }\r\n--\r\n-- An example of setting align_keys to true:\r\n--\r\n-- JSON:encode_pretty(data, nil, { pretty = true, indent = \" \", align_keys = true })\r\n--\r\n-- produces:\r\n--\r\n-- {\r\n-- \"city\": \"Kyoto\",\r\n-- \"climate\": {\r\n-- \"avg_temp\": 16,\r\n-- \"humidity\": \"high\",\r\n-- \"snowfall\": \"minimal\"\r\n-- },\r\n-- \"country\": \"Japan\",\r\n-- \"wards\": 11\r\n-- }\r\n--\r\n-- which I must admit is kinda ugly, sorry. This was the default for\r\n-- encode_pretty() prior to version 20141223.14.\r\n--\r\n--\r\n-- AMBIGUOUS SITUATIONS DURING THE ENCODING\r\n--\r\n-- During the encode, if a Lua table being encoded contains both string\r\n-- and numeric keys, it fits neither JSON's idea of an object, nor its\r\n-- idea of an array. To get around this, when any string key exists (or\r\n-- when non-positive numeric keys exist), numeric keys are converted to\r\n-- strings.\r\n--\r\n-- For example,\r\n-- JSON:encode({ \"one\", \"two\", \"three\", SOMESTRING = \"some string\" }))\r\n-- produces the JSON object\r\n-- {\"1\":\"one\",\"2\":\"two\",\"3\":\"three\",\"SOMESTRING\":\"some string\"}\r\n--\r\n-- To prohibit this conversion and instead make it an error condition, set\r\n-- JSON.noKeyConversion = true\r\n--\r\n\r\n--\r\n-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT\r\n--\r\n-- assert\r\n-- onDecodeError\r\n-- onDecodeOfNilError\r\n-- onDecodeOfHTMLError\r\n-- onEncodeError\r\n--\r\n-- If you want to create a separate Lua JSON object with its own error handlers,\r\n-- you can reload JSON.lua or use the :new() method.\r\n--\r\n---------------------------------------------------------------------------\r\n\r\nlocal default_pretty_indent = ' '\r\nlocal default_pretty_options = {pretty = true, align_keys = false, indent = default_pretty_indent}\r\n\r\nlocal isArray = {\r\n __tostring = function()\r\n return 'JSON array'\r\n end\r\n}\r\nisArray.__index = isArray\r\nlocal isObject = {\r\n __tostring = function()\r\n return 'JSON object'\r\n end\r\n}\r\nisObject.__index = isObject\r\n\r\nfunction OBJDEF:newArray(tbl)\r\n return setmetatable(tbl or {}, isArray)\r\nend\r\n\r\nfunction OBJDEF:newObject(tbl)\r\n return setmetatable(tbl or {}, isObject)\r\nend\r\n\r\nlocal function unicode_codepoint_as_utf8(codepoint)\r\n --\r\n -- codepoint is a number\r\n --\r\n if codepoint <= 127 then\r\n return string.char(codepoint)\r\n elseif codepoint <= 2047 then\r\n --\r\n -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8\r\n --\r\n local highpart = math.floor(codepoint / 0x40)\r\n local lowpart = codepoint - (0x40 * highpart)\r\n return string.char(0xC0 + highpart, 0x80 + lowpart)\r\n elseif codepoint <= 65535 then\r\n --\r\n -- 1110yyyy 10yyyyxx 10xxxxxx\r\n --\r\n local highpart = math.floor(codepoint / 0x1000)\r\n local remainder = codepoint - 0x1000 * highpart\r\n local midpart = math.floor(remainder / 0x40)\r\n local lowpart = remainder - 0x40 * midpart\r\n\r\n highpart = 0xE0 + highpart\r\n midpart = 0x80 + midpart\r\n lowpart = 0x80 + lowpart\r\n\r\n --\r\n -- Check for an invalid character (thanks Andy R. at Adobe).\r\n -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070\r\n --\r\n if\r\n (highpart == 0xE0 and midpart < 0xA0) or (highpart == 0xED and midpart > 0x9F) or\r\n (highpart == 0xF0 and midpart < 0x90) or\r\n (highpart == 0xF4 and midpart > 0x8F)\r\n then\r\n return '?'\r\n else\r\n return string.char(highpart, midpart, lowpart)\r\n end\r\n else\r\n --\r\n -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx\r\n --\r\n local highpart = math.floor(codepoint / 0x40000)\r\n local remainder = codepoint - 0x40000 * highpart\r\n local midA = math.floor(remainder / 0x1000)\r\n remainder = remainder - 0x1000 * midA\r\n local midB = math.floor(remainder / 0x40)\r\n local lowpart = remainder - 0x40 * midB\r\n\r\n return string.char(0xF0 + highpart, 0x80 + midA, 0x80 + midB, 0x80 + lowpart)\r\n end\r\nend\r\n\r\nfunction OBJDEF:onDecodeError(message, text, location, etc)\r\n if text then\r\n if location then\r\n message = string.format('%s at char %d of: %s', message, location, text)\r\n else\r\n message = string.format('%s: %s', message, text)\r\n end\r\n end\r\n\r\n if etc ~= nil then\r\n message = message .. ' (' .. OBJDEF:encode(etc) .. ')'\r\n end\r\n\r\n if self.assert then\r\n self.assert(false, message)\r\n else\r\n assert(false, message)\r\n end\r\nend\r\n\r\nOBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError\r\nOBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError\r\n\r\nfunction OBJDEF:onEncodeError(message, etc)\r\n if etc ~= nil then\r\n message = message .. ' (' .. OBJDEF:encode(etc) .. ')'\r\n end\r\n\r\n if self.assert then\r\n self.assert(false, message)\r\n else\r\n assert(false, message)\r\n end\r\nend\r\n\r\nlocal function grok_number(self, text, start, etc)\r\n --\r\n -- Grab the integer part\r\n --\r\n local integer_part = text:match('^-?[1-9]%d*', start) or text:match('^-?0', start)\r\n\r\n if not integer_part then\r\n self:onDecodeError('expected number', text, start, etc)\r\n end\r\n\r\n local i = start + integer_part:len()\r\n\r\n --\r\n -- Grab an optional decimal part\r\n --\r\n local decimal_part = text:match('^%.%d+', i) or ''\r\n\r\n i = i + decimal_part:len()\r\n\r\n --\r\n -- Grab an optional exponential part\r\n --\r\n local exponent_part = text:match('^[eE][-+]?%d+', i) or ''\r\n\r\n i = i + exponent_part:len()\r\n\r\n local full_number_text = integer_part .. decimal_part .. exponent_part\r\n local as_number = tonumber(full_number_text)\r\n\r\n if not as_number then\r\n self:onDecodeError('bad number', text, start, etc)\r\n end\r\n\r\n return as_number, i\r\nend\r\n\r\nlocal function grok_string(self, text, start, etc)\r\n if text:sub(start, start) ~= '\"' then\r\n self:onDecodeError(\"expected string's opening quote\", text, start, etc)\r\n end\r\n\r\n local i = start + 1 -- +1 to bypass the initial quote\r\n local text_len = text:len()\r\n local VALUE = ''\r\n while i <= text_len do\r\n local c = text:sub(i, i)\r\n if c == '\"' then\r\n return VALUE, i + 1\r\n end\r\n if c ~= '\\\\' then\r\n VALUE = VALUE .. c\r\n i = i + 1\r\n elseif text:match('^\\\\b', i) then\r\n VALUE = VALUE .. '\\b'\r\n i = i + 2\r\n elseif text:match('^\\\\f', i) then\r\n VALUE = VALUE .. '\\f'\r\n i = i + 2\r\n elseif text:match('^\\\\n', i) then\r\n VALUE = VALUE .. '\\n'\r\n i = i + 2\r\n elseif text:match('^\\\\r', i) then\r\n VALUE = VALUE .. '\\r'\r\n i = i + 2\r\n elseif text:match('^\\\\t', i) then\r\n VALUE = VALUE .. '\\t'\r\n i = i + 2\r\n else\r\n local hex =\r\n text:match(\r\n '^\\\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])',\r\n i\r\n )\r\n if hex then\r\n i = i + 6 -- bypass what we just read\r\n\r\n -- We have a Unicode codepoint. It could be standalone, or if in the proper range and\r\n -- followed by another in a specific range, it'll be a two-code surrogate pair.\r\n local codepoint = tonumber(hex, 16)\r\n if codepoint >= 0xD800 and codepoint <= 0xDBFF then\r\n -- it's a hi surrogate... see whether we have a following low\r\n local lo_surrogate =\r\n text:match('^\\\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)\r\n if lo_surrogate then\r\n i = i + 6 -- bypass the low surrogate we just read\r\n codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16)\r\n else\r\n -- not a proper low, so we'll just leave the first codepoint as is and spit it out.\r\n end\r\n end\r\n VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint)\r\n else\r\n -- just pass through what's escaped\r\n VALUE = VALUE .. text:match('^\\\\(.)', i)\r\n i = i + 2\r\n end\r\n end\r\n end\r\n\r\n self:onDecodeError('unclosed string', text, start, etc)\r\nend\r\n\r\nlocal function skip_whitespace(text, start)\r\n local _, match_end = text:find('^[ \\n\\r\\t]+', start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2\r\n if match_end then\r\n return match_end + 1\r\n else\r\n return start\r\n end\r\nend\r\n\r\nlocal grok_one -- assigned later\r\n\r\nlocal function grok_object(self, text, start, etc)\r\n if text:sub(start, start) ~= '{' then\r\n self:onDecodeError(\"expected '{'\", text, start, etc)\r\n end\r\n\r\n local i = skip_whitespace(text, start + 1) -- +1 to skip the '{'\r\n\r\n local VALUE = self.strictTypes and self:newObject {} or {}\r\n\r\n if text:sub(i, i) == '}' then\r\n return VALUE, i + 1\r\n end\r\n local text_len = text:len()\r\n while i <= text_len do\r\n local key, new_i = grok_string(self, text, i, etc)\r\n\r\n i = skip_whitespace(text, new_i)\r\n\r\n if text:sub(i, i) ~= ':' then\r\n self:onDecodeError('expected colon', text, i, etc)\r\n end\r\n\r\n i = skip_whitespace(text, i + 1)\r\n\r\n local new_val, new_i = grok_one(self, text, i)\r\n\r\n VALUE[key] = new_val\r\n\r\n --\r\n -- Expect now either '}' to end things, or a ',' to allow us to continue.\r\n --\r\n i = skip_whitespace(text, new_i)\r\n\r\n local c = text:sub(i, i)\r\n\r\n if c == '}' then\r\n return VALUE, i + 1\r\n end\r\n\r\n if text:sub(i, i) ~= ',' then\r\n self:onDecodeError(\"expected comma or '}'\", text, i, etc)\r\n end\r\n\r\n i = skip_whitespace(text, i + 1)\r\n end\r\n\r\n self:onDecodeError(\"unclosed '{'\", text, start, etc)\r\nend\r\n\r\nlocal function grok_array(self, text, start, etc)\r\n if text:sub(start, start) ~= '[' then\r\n self:onDecodeError(\"expected '['\", text, start, etc)\r\n end\r\n\r\n local i = skip_whitespace(text, start + 1) -- +1 to skip the '['\r\n local VALUE = self.strictTypes and self:newArray {} or {}\r\n if text:sub(i, i) == ']' then\r\n return VALUE, i + 1\r\n end\r\n\r\n local VALUE_INDEX = 1\r\n\r\n local text_len = text:len()\r\n while i <= text_len do\r\n local val, new_i = grok_one(self, text, i)\r\n\r\n -- can't table.insert(VALUE, val) here because it's a no-op if val is nil\r\n VALUE[VALUE_INDEX] = val\r\n VALUE_INDEX = VALUE_INDEX + 1\r\n\r\n i = skip_whitespace(text, new_i)\r\n\r\n --\r\n -- Expect now either ']' to end things, or a ',' to allow us to continue.\r\n --\r\n local c = text:sub(i, i)\r\n if c == ']' then\r\n return VALUE, i + 1\r\n end\r\n if text:sub(i, i) ~= ',' then\r\n self:onDecodeError(\"expected comma or '['\", text, i, etc)\r\n end\r\n i = skip_whitespace(text, i + 1)\r\n end\r\n self:onDecodeError(\"unclosed '['\", text, start, etc)\r\nend\r\n\r\ngrok_one = function(self, text, start, etc)\r\n -- Skip any whitespace\r\n start = skip_whitespace(text, start)\r\n\r\n if start > text:len() then\r\n self:onDecodeError('unexpected end of string', text, nil, etc)\r\n end\r\n\r\n if text:find('^\"', start) then\r\n return grok_string(self, text, start, etc)\r\n elseif text:find('^[-0123456789 ]', start) then\r\n return grok_number(self, text, start, etc)\r\n elseif text:find('^%{', start) then\r\n return grok_object(self, text, start, etc)\r\n elseif text:find('^%[', start) then\r\n return grok_array(self, text, start, etc)\r\n elseif text:find('^true', start) then\r\n return true, start + 4\r\n elseif text:find('^false', start) then\r\n return false, start + 5\r\n elseif text:find('^null', start) then\r\n return nil, start + 4\r\n else\r\n self:onDecodeError(\"can't parse JSON\", text, start, etc)\r\n end\r\nend\r\n\r\n---@param text string\r\nfunction OBJDEF:decode(text, etc)\r\n if type(self) ~= 'table' or self.__index ~= OBJDEF then\r\n OBJDEF:onDecodeError('JSON:decode must be called in method format', nil, nil, etc)\r\n end\r\n\r\n if text == nil then\r\n self:onDecodeOfNilError(string.format('nil passed to JSON:decode()'), nil, nil, etc)\r\n elseif type(text) ~= 'string' then\r\n self:onDecodeError(\r\n string.format('expected string argument to JSON:decode(), got %s', type(text)),\r\n nil,\r\n nil,\r\n etc\r\n )\r\n end\r\n\r\n if text:match('^%s*$') then\r\n return nil\r\n end\r\n\r\n if text:match('^%s*<') then\r\n -- Can't be JSON... we'll assume it's HTML\r\n self:onDecodeOfHTMLError(string.format('html passed to JSON:decode()'), text, nil, etc)\r\n end\r\n\r\n --\r\n -- Ensure that it's not UTF-32 or UTF-16.\r\n -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3),\r\n -- but this package can't handle them.\r\n --\r\n if text:sub(1, 1):byte() == 0 or (text:len() >= 2 and text:sub(2, 2):byte() == 0) then\r\n self:onDecodeError('JSON package groks only UTF-8, sorry', text, nil, etc)\r\n end\r\n\r\n local success, value = pcall(grok_one, self, text, 1, etc)\r\n\r\n if success then\r\n return value\r\n else\r\n -- if JSON:onDecodeError() didn't abort out of the pcall, we'll have received the error message here as \"value\", so pass it along as an assert.\r\n if self.assert then\r\n self.assert(false, value)\r\n else\r\n assert(false, value)\r\n end\r\n -- and if we're still here, return a nil and throw the error message on as a second arg\r\n return nil, value\r\n end\r\nend\r\n\r\nlocal function backslash_replacement_function(c)\r\n if c == '\\n' then\r\n return '\\\\n'\r\n elseif c == '\\r' then\r\n return '\\\\r'\r\n elseif c == '\\t' then\r\n return '\\\\t'\r\n elseif c == '\\b' then\r\n return '\\\\b'\r\n elseif c == '\\f' then\r\n return '\\\\f'\r\n elseif c == '\"' then\r\n return '\\\\\"'\r\n elseif c == '\\\\' then\r\n return '\\\\\\\\'\r\n else\r\n return string.format('\\\\u%04x', c:byte())\r\n end\r\nend\r\n\r\nlocal chars_to_be_escaped_in_JSON_string =\r\n '[' ..\r\n '\"' .. -- class sub-pattern to match a double quote\r\n '%\\\\' .. -- class sub-pattern to match a backslash\r\n '%z' .. -- class sub-pattern to match a null\r\n '\\001' ..\r\n '-' ..\r\n '\\031' .. -- class sub-pattern to match control characters\r\n ']'\r\n\r\nlocal function json_string_literal(value)\r\n local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function)\r\n return '\"' .. newval .. '\"'\r\nend\r\n\r\nlocal function object_or_array(self, T, etc)\r\n --\r\n -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON\r\n -- object. If there are only numbers, it's a JSON array.\r\n --\r\n -- If we'll be converting to a JSON object, we'll want to sort the keys so that the\r\n -- end result is deterministic.\r\n --\r\n local string_keys = {}\r\n local number_keys = {}\r\n local number_keys_must_be_strings = false\r\n local maximum_number_key\r\n\r\n for key in pairs(T) do\r\n if type(key) == 'string' then\r\n table.insert(string_keys, key)\r\n elseif type(key) == 'number' then\r\n table.insert(number_keys, key)\r\n if key <= 0 or key >= math.huge then\r\n number_keys_must_be_strings = true\r\n elseif not maximum_number_key or key > maximum_number_key then\r\n maximum_number_key = key\r\n end\r\n else\r\n self:onEncodeError(\"can't encode table with a key of type \" .. type(key), etc)\r\n end\r\n end\r\n\r\n if #string_keys == 0 and not number_keys_must_be_strings then\r\n --\r\n -- An empty table, or a numeric-only array\r\n --\r\n if #number_keys > 0 then\r\n return nil, maximum_number_key -- an array\r\n elseif tostring(T) == 'JSON array' then\r\n return nil\r\n elseif tostring(T) == 'JSON object' then\r\n return {}\r\n else\r\n -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects\r\n return nil\r\n end\r\n end\r\n\r\n table.sort(string_keys)\r\n\r\n local map\r\n if #number_keys > 0 then\r\n --\r\n -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array\r\n -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object.\r\n --\r\n\r\n if self.noKeyConversion then\r\n self:onEncodeError('a table with both numeric and string keys could be an object or array; aborting', etc)\r\n end\r\n\r\n --\r\n -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings\r\n --\r\n map = {}\r\n for key, val in pairs(T) do\r\n map[key] = val\r\n end\r\n\r\n table.sort(number_keys)\r\n\r\n --\r\n -- Throw numeric keys in there as strings\r\n --\r\n for _, number_key in ipairs(number_keys) do\r\n local string_key = tostring(number_key)\r\n if map[string_key] == nil then\r\n table.insert(string_keys, string_key)\r\n map[string_key] = T[number_key]\r\n else\r\n self:onEncodeError(\r\n 'conflict converting table with mixed-type keys into a JSON object: key ' ..\r\n number_key .. ' exists both as a string and a number.',\r\n etc\r\n )\r\n end\r\n end\r\n end\r\n\r\n return string_keys, nil, map\r\nend\r\n\r\n--\r\n-- Encode\r\n--\r\n-- 'options' is nil, or a table with possible keys:\r\n-- pretty -- if true, return a pretty-printed version\r\n-- indent -- a string (usually of spaces) used to indent each nested level\r\n-- align_keys -- if true, align all the keys when formatting a table\r\n--\r\nlocal encode_value -- must predeclare because it calls itself\r\nfunction encode_value(self, value, parents, etc, options, indent)\r\n if value == nil then\r\n return 'null'\r\n elseif type(value) == 'string' then\r\n return json_string_literal(value)\r\n elseif type(value) == 'number' then\r\n if value ~= value then\r\n --\r\n -- NaN (Not a Number).\r\n -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option.\r\n --\r\n return 'null'\r\n elseif value >= math.huge then\r\n --\r\n -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should\r\n -- really be a package option. Note: at least with some implementations, positive infinity\r\n -- is both \">= math.huge\" and \"<= -math.huge\", which makes no sense but that's how it is.\r\n -- Negative infinity is properly \"<= -math.huge\". So, we must be sure to check the \">=\"\r\n -- case first.\r\n --\r\n return '1e+9999'\r\n elseif value <= -math.huge then\r\n --\r\n -- Negative infinity.\r\n -- JSON has no INF, so we have to fudge the best we can. This should really be a package option.\r\n --\r\n return '-1e+9999'\r\n else\r\n return tostring(value)\r\n end\r\n elseif type(value) == 'boolean' then\r\n return tostring(value)\r\n elseif type(value) ~= 'table' then\r\n self:onEncodeError(\"can't convert \" .. type(value) .. ' to JSON', etc)\r\n else\r\n --\r\n -- A table to be converted to either a JSON object or array.\r\n --\r\n local T = value\r\n\r\n if type(options) ~= 'table' then\r\n options = {}\r\n end\r\n if type(indent) ~= 'string' then\r\n indent = ''\r\n end\r\n\r\n if parents[T] then\r\n self:onEncodeError('table ' .. tostring(T) .. ' is a child of itself', etc)\r\n else\r\n parents[T] = true\r\n end\r\n\r\n local result_value\r\n\r\n local object_keys, maximum_number_key, map = object_or_array(self, T, etc)\r\n if maximum_number_key then\r\n --\r\n -- An array...\r\n --\r\n local ITEMS = {}\r\n for i = 1, maximum_number_key do\r\n table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent))\r\n end\r\n\r\n if options.pretty then\r\n result_value = '[ ' .. table.concat(ITEMS, ', ') .. ' ]'\r\n else\r\n result_value = '[' .. table.concat(ITEMS, ',') .. ']'\r\n end\r\n elseif object_keys then\r\n --\r\n -- An object\r\n --\r\n local TT = map or T\r\n\r\n if options.pretty then\r\n local KEYS = {}\r\n local max_key_length = 0\r\n for _, key in ipairs(object_keys) do\r\n local encoded = encode_value(self, tostring(key), parents, etc, options, indent)\r\n if options.align_keys then\r\n max_key_length = math.max(max_key_length, #encoded)\r\n end\r\n table.insert(KEYS, encoded)\r\n end\r\n local key_indent = indent .. tostring(options.indent or '')\r\n local subtable_indent =\r\n key_indent .. string.rep(' ', max_key_length) .. (options.align_keys and ' ' or '')\r\n local FORMAT = '%s%' .. string.format('%d', max_key_length) .. 's: %s'\r\n\r\n local COMBINED_PARTS = {}\r\n for i, key in ipairs(object_keys) do\r\n local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent)\r\n table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))\r\n end\r\n result_value = '{\\n' .. table.concat(COMBINED_PARTS, ',\\n') .. '\\n' .. indent .. '}'\r\n else\r\n local PARTS = {}\r\n for _, key in ipairs(object_keys) do\r\n local encoded_val = encode_value(self, TT[key], parents, etc, options, indent)\r\n local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent)\r\n table.insert(PARTS, string.format('%s:%s', encoded_key, encoded_val))\r\n end\r\n result_value = '{' .. table.concat(PARTS, ',') .. '}'\r\n end\r\n else\r\n --\r\n -- An empty array/object... we'll treat it as an array, though it should really be an option\r\n --\r\n result_value = '[]'\r\n end\r\n\r\n parents[T] = false\r\n return result_value\r\n end\r\nend\r\n\r\nfunction OBJDEF:encode(value, etc, options)\r\n if type(self) ~= 'table' or self.__index ~= OBJDEF then\r\n OBJDEF:onEncodeError('JSON:encode must be called in method format', etc)\r\n end\r\n return encode_value(self, value, {}, etc, options or nil)\r\nend\r\n\r\nfunction OBJDEF:encode_pretty(value, etc, options)\r\n if type(self) ~= 'table' or self.__index ~= OBJDEF then\r\n OBJDEF:onEncodeError('JSON:encode_pretty must be called in method format', etc)\r\n end\r\n return encode_value(self, value, {}, etc, options or default_pretty_options)\r\nend\r\n\r\nfunction OBJDEF.__tostring()\r\n return 'JSON encode/decode package'\r\nend\r\n\r\nOBJDEF.__index = OBJDEF\r\n\r\nfunction OBJDEF:new(args)\r\n local new = {}\r\n\r\n if args then\r\n for key, val in pairs(args) do\r\n new[key] = val\r\n end\r\n end\r\n\r\n return setmetatable(new, OBJDEF)\r\nend\r\n\r\nreturn OBJDEF:new()\r\n\r\n--\r\n-- Version history:\r\n--\r\n-- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really\r\n-- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines\r\n-- more flexible, and changed the default encode_pretty() to be more generally useful.\r\n--\r\n-- Added a third 'options' argument to the encode() and encode_pretty() routines, to control\r\n-- how the encoding takes place.\r\n--\r\n-- Updated docs to add assert() call to the loadfile() line, just as good practice so that\r\n-- if there is a problem loading JSON.lua, the appropriate error message will percolate up.\r\n--\r\n-- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string,\r\n-- so that the source of the package, and its version number, are visible in compiled copies.\r\n--\r\n-- 20140911.12 Minor lua cleanup.\r\n-- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'.\r\n-- (Thanks to SmugMug's David Parry for these.)\r\n--\r\n-- 20140418.11 JSON nulls embedded within an array were being ignored, such that\r\n-- [\"1\",null,null,null,null,null,\"seven\"],\r\n-- would return\r\n-- {1,\"seven\"}\r\n-- It's now fixed to properly return\r\n-- {1, nil, nil, nil, nil, nil, \"seven\"}\r\n-- Thanks to \"haddock\" for catching the error.\r\n--\r\n-- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to \"blue\" for the heads up.\r\n--\r\n-- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces \"2.0\" instead of \"2\",\r\n-- and this caused some problems.\r\n--\r\n-- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate,\r\n-- and had of course diverged (encode_pretty didn't get the fixes that encode got, so\r\n-- sometimes produced incorrect results; thanks to Mattie for the heads up).\r\n--\r\n-- Handle encoding tables with non-positive numeric keys (unlikely, but possible).\r\n--\r\n-- If a table has both numeric and string keys, or its numeric keys are inappropriate\r\n-- (such as being non-positive or infinite), the numeric keys are turned into\r\n-- string keys appropriate for a JSON object. So, as before,\r\n-- JSON:encode({ \"one\", \"two\", \"three\" })\r\n-- produces the array\r\n-- [\"one\",\"two\",\"three\"]\r\n-- but now something with mixed key types like\r\n-- JSON:encode({ \"one\", \"two\", \"three\", SOMESTRING = \"some string\" }))\r\n-- instead of throwing an error produces an object:\r\n-- {\"1\":\"one\",\"2\":\"two\",\"3\":\"three\",\"SOMESTRING\":\"some string\"}\r\n--\r\n-- To maintain the prior throw-an-error semantics, set\r\n-- JSON.noKeyConversion = true\r\n--\r\n-- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry.\r\n--\r\n-- 20130120.6 Comment update: added a link to the specific page on my blog where this code can\r\n-- be found, so that folks who come across the code outside of my blog can find updates\r\n-- more easily.\r\n--\r\n-- 20111207.5 Added support for the 'etc' arguments, for better error reporting.\r\n--\r\n-- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent.\r\n--\r\n-- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules:\r\n--\r\n-- * When encoding lua for JSON, Sparse numeric arrays are now handled by\r\n-- spitting out full arrays, such that\r\n-- JSON:encode({\"one\", \"two\", [10] = \"ten\"})\r\n-- returns\r\n-- [\"one\",\"two\",null,null,null,null,null,null,null,\"ten\"]\r\n--\r\n-- In 20100810.2 and earlier, only up to the first non-null value would have been retained.\r\n--\r\n-- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as \"1+e9999\".\r\n-- Version 20100810.2 and earlier created invalid JSON in both cases.\r\n--\r\n-- * Unicode surrogate pairs are now detected when decoding JSON.\r\n--\r\n-- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding\r\n--\r\n-- 20100731.1 initial public release\r\n--\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ObjPoolUtilModule","guid":[2729864637,1304447132,2322341388,2859273176],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ObjPoolUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---对象池工具模块\r\n---@module ObjPoolUtil\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yen Yuan\r\n---@class ObjPoolUtil\r\nlocal ObjPoolUtil = class('ObjPoolUtil')\r\n\r\n---创建某一个对象的对象池\r\n---@param _folderName Object 管理的目录\r\n---@param _objName string 对象的Archetype名\r\n---@param _maxCount number 对象池最大上限,不填则为100\r\n---@return ObjPoolUtil\r\nfunction ObjPoolUtil.static.Newpool(_folderName, _objName, _maxCount)\r\n if _folderName == nil or _objName == nil then\r\n error('[ObjPoolUtil] 管理目录或管理对象为空')\r\n end\r\n if _maxCount == nil then\r\n _maxCount = 100\r\n end\r\n local realPool = class(_objName .. 'Pool', ObjPoolUtil)\r\n realPool.static.obj = _objName\r\n realPool.static.folder = _folderName\r\n realPool.static.maxCount = _maxCount\r\n realPool.pool = {}\r\n print(string.format('[ObjPoolUtil] 创建了一个%s的对象池,目录为%s', _objName, _folderName))\r\n return realPool\r\nend\r\n\r\n---从池中创建对象到世界下\r\n---@param _position Vector3\r\n---@param _rotation EulerDegree\r\nfunction ObjPoolUtil:Spawn(_position, _rotation)\r\n local realObj = nil\r\n if #self.pool == 0 then\r\n realObj = world:CreateInstance(self.obj, self.obj, self.folder, _position, _rotation)\r\n if realObj == nil then\r\n error(string.format('[ObjPoolUtil] Archetype下没有名为%s的对象', self.obj))\r\n return\r\n end\r\n return realObj\r\n else\r\n realObj = self.pool[1]\r\n self.pool[1].Position = _position\r\n self.pool[1].Rotation = _rotation\r\n self.pool[1]:SetActive(true)\r\n table.remove(self.pool, 1)\r\n return realObj\r\n end\r\nend\r\n\r\n---从世界中销毁对象到池中\r\n---@param _obj Object\r\nfunction ObjPoolUtil:Despawn(_obj)\r\n if _obj == nil then\r\n error('[ObjPoolUtil] 传入对象为空')\r\n elseif #self.pool > self.maxCount then\r\n error(string.format('[ObjPoolUtil] %s对象池已满,该对象会永久销毁', self.obj))\r\n _obj:Destroy()\r\n else\r\n table.insert(self.pool, _obj)\r\n _obj:SetActive(false)\r\n end\r\nend\r\n\r\nreturn ObjPoolUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"SoundUtilModule","guid":[375943343,233195050,2377557310,1976802289],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"SoundUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 音效播放模块\r\n---@module SoundUtil\r\n---@copyright Lilith Games, Avatar Team\r\n---@author Sharif Ma\r\n---@class SoundUtil\r\nlocal SoundUtil = {}\r\n\r\nfunction SoundUtil:Init()\r\n print('[SoundUtil] Init()')\r\n self.SoundPlaying = {}\r\n self.Table_Sound = Config.Sound\r\nend\r\n\r\n---创建一个新音效并播放\r\n---@param _ID number 音效的ID\r\n---@param _SoundSourceObj Object 音效的挂载物体,不填则为2D音效,挂载在主摄像机上\r\nfunction SoundUtil:PlaySound(_ID, _SoundSourceObj)\r\n local Info, _Duration\r\n _SoundSourceObj = _SoundSourceObj or world.CurrentCamera\r\n Info = self.Table_Sound[_ID]\r\n assert(Info, '[SoundUtil] 表中不存在该ID的音效')\r\n _Duration = Info.Duration\r\n local sameSoundPlayingNum = 0\r\n for k, v in pairs(self.SoundPlaying) do\r\n if v == _ID then\r\n sameSoundPlayingNum = sameSoundPlayingNum + 1\r\n end\r\n end\r\n if sameSoundPlayingNum > 0 and not Info.CoverPlay then\r\n print(string.format('[SoundUtil] %s音效CoverPlay字段为false,不能覆盖播放', _ID))\r\n return\r\n end\r\n\r\n local Audio = world:CreateObject('AudioSource', 'Audio_' .. Info.FileName, _SoundSourceObj)\r\n Audio.LocalPosition = Vector3.Zero\r\n Audio.SoundClip = ResourceManager.GetSoundClip('Audio/' .. Info.FileName)\r\n print('[SoundUtil] Audio.SoundClip', Audio.SoundClip)\r\n Audio.Volume = Info.Volume\r\n Audio.MaxDistance = 10\r\n Audio.MinDistance = 10\r\n Audio.Loop = Info.IsLoop\r\n Audio:Play()\r\n table.insert(self.SoundPlaying, _ID)\r\n _Duration = _Duration or 1\r\n invoke(\r\n function()\r\n if Audio then\r\n Audio:Destroy()\r\n end\r\n end,\r\n _Duration\r\n )\r\n invoke(\r\n function()\r\n for k, v in pairs(self.SoundPlaying) do\r\n if v == _ID then\r\n table.remove(self.SoundPlaying, k)\r\n end\r\n end\r\n end,\r\n _Duration\r\n )\r\nend\r\n\r\nreturn SoundUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"LinkedListModule","guid":[572803863,1537884583,2902871108,3023603150],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LinkedListModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- C# 双向链表\r\n-- @module C# doubly linked list implemented with lua\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Bruce Chen\r\n-- @see https://wiki.lilithgames.com/x/7yRZAg\r\n-- @see https://github.com/BruceCheng1995/LuaLinkedList\r\n\r\nlocal LinkedList = {}\r\nlocal LinkedNode = {}\r\nLinkedNode.__index = LinkedNode\r\n\r\nlocal NativePrint = print\r\nlocal EmptuFunc = function()\r\nend\r\n--是否开放内部日志\r\nfunction LinkedList:EnableLog(_enable)\r\n if _enable then\r\n print = NativePrint\r\n else\r\n print = EmptuFunc\r\n end\r\nend\r\nLinkedList:EnableLog(false)\r\n--新建节点\r\nfunction LinkedNode:new(value, list)\r\n local o = {}\r\n setmetatable(o, self)\r\n o.List = list\r\n o.Next = nil\r\n o.Prev = nil\r\n o.Value = value\r\n return o\r\nend\r\n--克隆这个节点\r\nfunction LinkedNode:Clone()\r\n return LinkedNode:new(self.Value, nil)\r\nend\r\n--节点失效\r\nfunction LinkedNode:Invalidate()\r\n self.Next = nil\r\n self.Prev = nil\r\n self.List = nil\r\nend\r\n--打印\r\nfunction LinkedNode:tostring()\r\n return tostring(self.Value)\r\nend\r\nLinkedNode.__tostring = LinkedNode.tostring\r\n\r\n--验证新节点是否是自由节点\r\nfunction LinkedList:ValidateNewNode(node)\r\n if not node then\r\n return false\r\n end\r\n --assert(LinkedNode:include(node),\"instance of LinkedNode needed.\")\r\n if node.List ~= nil then\r\n return false\r\n end\r\n return true\r\nend\r\n\r\n--验证该节点是否是属于该表\r\nfunction LinkedList:ValidateNode(node)\r\n if not node then\r\n return false\r\n end\r\n --assert(LinkedNode:include(node),\"instance of LinkedNode needed.\")\r\n if node.List ~= self then\r\n return false\r\n end\r\n return true\r\nend\r\n\r\n--将节点插入到node节点之前(list:链表,node:插在这个节点前面,newnode:被插入的节点)\r\nlocal function InternalInsertNodeBefore(list, node, newnode)\r\n newnode.Next = node\r\n newnode.Prev = node.Prev\r\n node.Prev.Next = newnode\r\n node.Prev = newnode\r\n list.Count = list.Count + 1\r\nend\r\n\r\n--将节点插入到一个空链表之前(list:链表,newnode:被插入的节点)\r\nlocal function InternalInsertNodeToEmptyList(list, newnode)\r\n newnode.Next = newnode\r\n newnode.Prev = newnode\r\n list.First = newnode\r\n list.Count = list.Count + 1\r\nend\r\n\r\n--移除链表中的节点(list:链表,node:被删除的节点)\r\nlocal function InternalRemoveNode(list, node)\r\n if node.Next == node then\r\n list.First = nil\r\n else\r\n node.Next.Prev = node.Prev\r\n node.Prev.Next = node.Next\r\n if list.First == node then\r\n list.First = node.Next\r\n end\r\n end\r\n node:Invalidate()\r\n list.Count = list.Count - 1\r\nend\r\n\r\n--新建双向链表\r\nfunction LinkedList:new(tab)\r\n local o = {}\r\n setmetatable(o, self)\r\n o.Count = 0\r\n o.First = nil\r\n if type(tab) == 'table' then\r\n for _, v in pairs(tab) do\r\n o:AddLast(v)\r\n end\r\n end\r\n return o\r\nend\r\n\r\n--Add Value\r\n--在尾部添加值(若传入值是表,则遍历表,并将所有值添加到尾部)\r\nfunction LinkedList:Add(value)\r\n if type(value) == 'table' then\r\n for _, v in pairs(value) do\r\n self:AddLast(v)\r\n end\r\n else\r\n self:AddLast(value)\r\n end\r\nend\r\n\r\n--在尾部添加值\r\nfunction LinkedList:AddLast(value)\r\n local newnode = LinkedNode:new(value, self)\r\n if not self.First then\r\n InternalInsertNodeToEmptyList(self, newnode)\r\n else\r\n InternalInsertNodeBefore(self, self.First, newnode)\r\n end\r\n return newnode\r\nend\r\n\r\n--在头部添加值\r\nfunction LinkedList:AddFirst(value)\r\n local newnode = LinkedNode:new(value, self)\r\n if not self.First then\r\n InternalInsertNodeToEmptyList(self, newnode)\r\n else\r\n InternalInsertNodeBefore(self, self.First, newnode)\r\n self.First = newnode\r\n end\r\n return newnode\r\nend\r\n\r\n--在指定节点后面添加值(node:插入在这个节点后,value:被插入的值)\r\nfunction LinkedList:AddAfter(node, value)\r\n if not self:ValidateNewNode(node) then\r\n return\r\n end\r\n local newnode = LinkedNode:new(value, self)\r\n InternalInsertNodeBefore(self, node.Next, newnode)\r\n return newnode\r\nend\r\n\r\n--在指定节点前面添加值(node:插入在这个节点前,value:被插入的值)\r\nfunction LinkedList:AddBefore(node, value)\r\n if not self:ValidateNode(node) then\r\n return\r\n end\r\n local newnode = LinkedNode:new(value, self)\r\n InternalInsertNodeBefore(self, node, newnode)\r\n if node == self.First then\r\n self.First = newnode\r\n end\r\n return newnode\r\nend\r\n\r\n--Add Node\r\n--在头部添加节点\r\nfunction LinkedList:AddNodeFirst(node)\r\n if not self:ValidateNewNode(node) then\r\n return\r\n end\r\n if not self.First then\r\n InternalInsertNodeToEmptyList(self, node)\r\n else\r\n InternalInsertNodeBefore(self, self.First, node)\r\n self.First = node\r\n end\r\n node.List = self\r\nend\r\n\r\n--在尾部添加节点\r\nfunction LinkedList:AddNodeLast(node)\r\n if not self:ValidateNewNode(node) then\r\n return\r\n end\r\n if not self.First then\r\n InternalInsertNodeToEmptyList(self, node)\r\n else\r\n InternalInsertNodeBefore(self, self.First, node)\r\n end\r\n node.List = self\r\nend\r\n\r\n--在指定节点后面添加值(node:插入在这个节点后,newnode:被插入的节点)\r\nfunction LinkedList:AddNodeAfter(node, newnode)\r\n if not self:ValidateNode(node) and not self:ValidateNewNode(newnode) then\r\n return\r\n end\r\n InternalInsertNodeBefore(self, node.Next, newnode)\r\n newnode.List = self\r\nend\r\n\r\n--在指定节点后面添加值(node:插入在这个节点前,newnode:被插入的节点)\r\nfunction LinkedList:AddNodeBefore(node, newnode)\r\n if not self:ValidateNode(node) and not self:ValidateNewNode(newnode) then\r\n return\r\n end\r\n InternalInsertNodeBefore(self, node, newnode)\r\n newnode.List = self\r\n if node ~= self.First then\r\n return\r\n end\r\n self.First = newnode\r\nend\r\n\r\n--Remove\r\n--找到表中的第一个指定值,并删除,返回是否命中\r\nfunction LinkedList:Remove(value)\r\n local node = self:Find(value)\r\n if not node then\r\n return false\r\n end\r\n InternalRemoveNode(self, node)\r\n return true\r\nend\r\n\r\n--找到表中的第一个指定节点,并删除,返回是否命中\r\nfunction LinkedList:RemoveNode(node)\r\n if not self:ValidateNode(node) then\r\n return\r\n end\r\n InternalRemoveNode(self, node)\r\nend\r\n\r\n--移除头部节点\r\nfunction LinkedList:RemoveFirst()\r\n if self.First == nil then\r\n print('[LinkedList] list is empty.')\r\n else\r\n InternalRemoveNode(self, self.First)\r\n end\r\nend\r\n\r\n--移除尾部节点\r\nfunction LinkedList:RemoveLast()\r\n if self.First == nil then\r\n print('[LinkedList] list is empty.')\r\n else\r\n InternalRemoveNode(self, self.First.Prev)\r\n end\r\nend\r\n\r\n--Find\r\n--尝试找到表中的第一个指定值,若有则返回这个节点\r\nfunction LinkedList:Find(value)\r\n local ptrnode = self.First\r\n if value ~= nil then\r\n while ptrnode.Value ~= value do\r\n ptrnode = ptrnode.Next\r\n if ptrnode == self.First then\r\n goto close1\r\n end\r\n end\r\n return ptrnode\r\n else\r\n while ptrnode.Value ~= nil do\r\n ptrnode = ptrnode.Next\r\n if ptrnode == self.First then\r\n goto close1\r\n end\r\n end\r\n return ptrnode\r\n end\r\n ::close1::\r\n return\r\nend\r\n\r\n--尝试反向找到表中第一个指定值,若有则返回这个节点\r\nfunction LinkedList:FindLast(value)\r\n if self.First == nil then\r\n return\r\n end\r\n local prev = self.First.Prev\r\n local ptrnode = prev\r\n if value ~= nil then\r\n while ptrnode.Value ~= value do\r\n ptrnode = ptrnode.Prev\r\n if ptrnode == Prev then\r\n goto close2\r\n end\r\n end\r\n return ptrnode\r\n else\r\n while ptrnode.Value ~= nil do\r\n ptrnode = ptrnode.Prev\r\n if ptrnode == prev then\r\n goto close2\r\n end\r\n end\r\n return ptrnode\r\n end\r\n ::close2::\r\n return\r\nend\r\n\r\n--Other\r\n--清空链表\r\nfunction LinkedList:Clear()\r\n local ptrnode = self.First\r\n while ptrnode ~= nil do\r\n local lastnode = ptrnode\r\n ptrnode = ptrnode.Next\r\n lastnode:Invalidate()\r\n end\r\n self.First = nil\r\n self.Count = 0\r\nend\r\n\r\n--向给定table的指定位置插入数值(tab:被插入表,index:序号)\r\nfunction LinkedList:CopyTo(tab, index)\r\n assert(type(tab) == 'table', '[LinkedList] bad argument \"table\"')\r\n assert(index >= 1, '[LinkedList] Index out of range')\r\n local ptrnode = self.First\r\n if ptrnode == nil then\r\n return\r\n end\r\n repeat\r\n table.insert(tab, index, ptrnode.Value)\r\n ptrnode = ptrnode.Next\r\n index = index + 1\r\n until (ptrnode == self.First)\r\nend\r\n\r\n--将链表中的数据拷贝到新表中,并将这个表输出\r\nfunction LinkedList:ToTable()\r\n local tab = {}\r\n self:CopyTo(tab, 1)\r\n return tab\r\nend\r\n\r\n--克隆当前链表,并返回\r\nfunction LinkedList:Clone()\r\n local newlist = LinkedList:new()\r\n local ptrnode = self.First\r\n repeat\r\n local clnode = ptrnode:Clone()\r\n newlist:AddNodeLast(clnode)\r\n ptrnode = ptrnode.Next\r\n until (ptrnode == self.First)\r\n return newlist\r\nend\r\n\r\n--检查链表中是否包含指定值\r\nfunction LinkedList:Contains(value)\r\n return self:Find(value) and true or false\r\nend\r\n\r\n--将链表反向\r\nfunction LinkedList:Reverse()\r\n local tmp\r\n if not self.First then\r\n print('[LinkedList] list is empty')\r\n return\r\n end\r\n self.First = self.First.Prev\r\n for item in self:ipairer() do\r\n tmp = item.Next\r\n item.Next = item.Prev\r\n item.Prev = tmp\r\n end\r\nend\r\n\r\n--返回头部节点\r\nfunction LinkedList:GetFirst()\r\n return self.First\r\nend\r\n\r\n--返回尾部节点\r\nfunction LinkedList:GetLast()\r\n return self.First ~= nil and self.First.Prev or nil\r\nend\r\n\r\n--返回第index个节点\r\nfunction LinkedList:GetNode(index)\r\n if index < 1 or index > self.Count then\r\n print('[LinkedList] Index out of range')\r\n return\r\n end\r\n local ptrnode = self.First.Prev\r\n while index > 0 do\r\n ptrnode = ptrnode.Next\r\n index = index - 1\r\n end\r\n return ptrnode\r\nend\r\n\r\n--返回链表长度\r\nfunction LinkedList:Len()\r\n return self.Count\r\nend\r\n\r\n--返回迭代器\r\nfunction LinkedList:ipairer()\r\n local ptrnode = self:GetLast()\r\n local passFirst = false\r\n return function()\r\n if ptrnode then\r\n if ptrnode ~= self:GetLast() or not passFirst then\r\n passFirst = true\r\n ptrnode = ptrnode.Next\r\n return ptrnode\r\n end\r\n end\r\n end\r\nend\r\n\r\n--以文本方式表示此表\r\nfunction LinkedList:tostring()\r\n local t = {}\r\n for item in self:ipairer() do\r\n table.insert(t, tostring(item))\r\n end\r\n return 'LinkedList:{' .. table.concat(t, ',') .. '}'\r\nend\r\n\r\nLinkedList.__index = LinkedList\r\nLinkedList.__tostring = LinkedList.tostring\r\n\r\nreturn {\r\n list = setmetatable(LinkedList, {__call = LinkedList.new}),\r\n node = setmetatable(LinkedNode, {__call = LinkedNode.new})\r\n}\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"GlobalFuncModule","guid":[397078907,1337606489,2918347355,1376574677],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GlobalFuncModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 全局函数的定义\r\n--- @module GlobalFunc Defines\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Sid Zhang\r\nlocal GlobalFunc = {}\r\n\r\n--- 埋点上传日志\r\n--- @param _tableName string 表名\r\nfunction GlobalFunc.UploadLogs(_tableName, ...)\r\n local args = {...}\r\n if localPlayer then\r\n pcall(\r\n function()\r\n TrackService.CloudLogFromClient({_tableName, table.unpack(args)})\r\n end\r\n )\r\n else\r\n pcall(\r\n function()\r\n TrackService.CloudLogFromServer({_tableName, table.unpack(args)})\r\n end\r\n )\r\n end\r\nend\r\n\r\nreturn GlobalFunc\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"TweenControllerModule","guid":[3340226311,3486273272,3143424947,180252541],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"TweenControllerModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---控制某个变量随时间变化的协程类\r\n---@module TweenController\r\n---@copyright Lilith Games, Avatar Team\r\n---@author An Dai\r\nlocal TweenController = class('TweenController')\r\n\r\n---_name:类名,_sender:使用它的类,_getTotalTime:获得总时间的方法,_update _callback:回调函数 _isFix:是否在fixupdate中执行, _start: 开始函数\r\nfunction TweenController:initialize(_name, _sender, _getTotalTime, _update, _callback, _isFix, _start)\r\n _start = _start or function()\r\n return\r\n end\r\n\r\n local updateStr = (_isFix and 'Fix' or '') .. 'Update'\r\n\r\n self.Start = function(self)\r\n _start()\r\n self.totalTime = _getTotalTime()\r\n self.time = 0\r\n _sender[updateStr .. 'Table'][_name] = self\r\n end\r\n\r\n self[updateStr] = function(self, _dt)\r\n self.time = self.time + _dt\r\n if (self.time > self.totalTime) then\r\n self:Stop()\r\n goto UpdateReturn\r\n end\r\n _update(self.time, self.totalTime, _dt)\r\n ::UpdateReturn::\r\n end\r\n\r\n self.Stop = function(self)\r\n _sender[updateStr .. 'Table'][_name] = nil\r\n _callback()\r\n end\r\nend\r\n\r\nreturn TweenController\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ValueChangeUtilModule","guid":[506887186,2798406641,2460390889,2104928690],"parentGuid":[626626282,3227665360,2471910562,2840544587],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ValueChangeUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 值改变及值改变事件\r\n--- @module ValueChangeUtil Module\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Xin Tan\r\nlocal ValueChangeUtil = {}\r\n\r\n--- 数据变化事件\r\n--- @param _table table 事件表\r\n--- @param _index string 索引\r\n--- @param _oldValue mixed 旧值\r\n--- @param _newValue mixed 新值\r\n--- @param _targetPlayer PlayerInstance 这条数据对应的玩家实例\r\nfunction ValueChangeUtil.DataChangeEvent(_table, _index, _oldValue, _newValue)\r\n\tif not _table[_index] or type(_table[_index]) ~= \"function\" then\r\n\t\treturn\r\n\tend\r\n\t_table[_index](_oldValue, _newValue)\r\nend\r\n\r\n--- 将目标表(或其中某个值)改为新值\r\n--- @param _table table 目标表\r\n--- @param _index string 目标索引(改整个目标表时填nil)\r\n--- @param _value mixed 新值\r\n--- @param _eventTable table 数值改变事件表(不响应时不传)\r\nfunction ValueChangeUtil.ChangeValue(_table, _index, _value, _eventTable)\r\n\tif type(_table) ~= \"table\" then\r\n\t\tprint(\"[error]传入的目标表类型错误\")\r\n\t\treturn\r\n\tend\r\n\t\r\n local tmp = _table\r\n local eventtmp = _eventTable or false\r\n\t\r\n\t-- 参数含索引时\r\n\tif _index then\r\n\t\tlocal idx = {}\r\n\t\tif type(_index) == \"string\" then\r\n\t\t\t-- 将索引通过'.'拆开\r\n\t\t\tidx = string.split(_index, '.')\r\n\t\telseif type(_index) == \"table\" then\r\n\t\t\tidx = _index\r\n\t\tend\r\n\t\t-- 一层层向下索引\r\n\t\tfor i = 1, #idx - 1 do\r\n\t\t\t-- 若目标表没有对应的索引则建立空表\r\n\t\t\tif type(tmp[idx[i]]) ~= \"table\" then\r\n\t\t\t\ttmp[idx[i]] = {}\r\n\t\t\tend\r\n tmp = tmp[idx[i]]\r\n if eventtmp then\r\n if type(eventtmp[idx[i]]) ~= \"table\" then\r\n eventtmp[idx[i]] = {}\r\n end\r\n eventtmp = eventtmp[idx[i]]\r\n end\r\n\t\tend\r\n\t\t\r\n\t\t-- 若目标值不是table,则直接赋值\r\n\t\tif type(_value) ~= \"table\" then\r\n local oldValue = table.shallowcopy(tmp[idx[#idx]])\r\n tmp[idx[#idx]] = _value\r\n if eventtmp then\r\n ValueChangeUtil.DataChangeEvent(eventtmp, idx[#idx], oldValue, _value)\r\n end\r\n\t\t\treturn\r\n\t\telse\r\n\t\t\t-- 目标值是table\r\n\t\t\t-- 若目标索引不是table,则创建table\r\n\t\t\tif type(tmp[idx[#idx]]) ~= \"table\" then\r\n tmp[idx[#idx]] = {}\r\n if eventtmp and type( eventtmp[idx[#idx]]) ~= \"table\" then\r\n eventtmp[idx[#idx]] = {}\r\n end\r\n\t\t\tend\r\n tmp = tmp[idx[#idx]]\r\n if eventtmp then\r\n eventtmp = eventtmp[idx[#idx]]\r\n end\r\n\t\tend\r\n\telse\r\n\t\t-- 参数无索引时,从目标表根目录开始同步\r\n\t\tif type(_value) ~= \"table\" then\r\n\t\t\tprint(\"[error]传入的新值类型错误\")\r\n\t\t\treturn\r\n\t\tend\r\n\tend\r\n\t\r\n\t-- 清除目标索引表与新值的差集\r\n\tfor k, v in pairs(tmp) do\r\n if not _value[k] then \r\n local oldValue = tmp[k]\r\n tmp[k] = nil\r\n if eventtmp then\r\n ValueChangeUtil.DataChangeEvent(eventtmp, k, oldValue, nil)\r\n end\r\n end\r\n\tend\r\n\t\r\n\t-- 逐层覆盖数据\r\n\tfor k, v in pairs(_value) do\r\n\t\t-- 如果值为table则向下递归\r\n\t\tif type(v) == \"table\" then\r\n ValueChangeUtil.ChangeValue(tmp, k, v, eventtmp)\r\n\t\telse\r\n\t\t\t-- 若值不是table,则直接赋值\r\n local oldValue = tmp[k]\r\n tmp[k] = v\r\n if eventtmp then\r\n ValueChangeUtil.DataChangeEvent(eventtmp, k, oldValue, v)\r\n end\r\n\t\tend\r\n\tend\r\n\tif eventtmp then\r\n\t\tValueChangeUtil.DataChangeEvent(eventtmp, \"parentTableEvent\")\r\n\tend\r\nend\r\n\r\n--- 进行数据验证,将对照表中存在而目标表中不存在键补充至目标表中\r\n--- @param _table table 目标表\r\n--- @param _contrast table 对照表\r\nfunction ValueChangeUtil.VerifyTable(_table, _contrast)\r\n\tfor k, v in pairs(_contrast) do\r\n\t\t-- 如果值为table则向下递归\r\n\t\tif type(v) == \"table\" then\r\n if not _table[k] then\r\n\t\t\t\t_table[k] = table.shallowcopy(v)\r\n\t\t\telse\r\n\t\t\t\tValueChangeUtil.VerifyTable(_table[k], v)\r\n\t\t\tend\r\n\t\telse\r\n\t\t\t-- 若值不是table,则直接校对\r\n if not _table[k] then _table[k] = v end\r\n\t\tend\r\n\tend\r\nend\r\n\r\nreturn ValueChangeUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Framework","guid":[756373066,1240223530,2766704094,915472147],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Framework"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"FrameworkConfigModule","guid":[3532304481,3064676706,3106629846,811992798],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"FrameworkConfigModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 框架配置\r\n--- @module Framework Global FrameworkConfig\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Yuancheng Zhang\r\nlocal FrameworkConfig = {\r\n -- 启动心跳\r\n HeartbeatStart = true,\r\n Server = {\r\n -- 心跳包间隔时间,单位:秒\r\n HeartbeatDelta = 1,\r\n -- 心跳阈值,单位:秒,范围定义如下:\r\n -- 0s -> threshold_1 : connected\r\n -- threshold_1 -> threshold_2 : disconnected, but player can reconnect\r\n -- threshold_2 -> longer : disconnected, remove player\r\n HeartbeatThreshold1 = 5,\r\n HeartbeatThreshold2 = 10,\r\n -- 显示心跳日志\r\n ShowHeartbeatLog = false,\r\n -- 插件中需要使用声明周期的服务器模块目录\r\n PluginModules = {},\r\n -- 插件中服务器需要生成的CustomEvent, 模块中必须得有ServerEvents\r\n PluginEvents = {}\r\n },\r\n Client = {\r\n -- 心跳包间隔时间,单位:秒\r\n HeartbeatDelta = 1,\r\n -- 心跳阈值,单位:秒,范围定义如下:\r\n -- 0s -> threshold_1 : connected\r\n -- threshold_1 -> threshold_2 : disconnected, weak network, can reconnect\r\n -- threshold_2 -> longer : disconnected, quit server\r\n HeartbeatThreshold1 = 5,\r\n HeartbeatThreshold2 = 10,\r\n -- 显示心跳日志\r\n ShowHeartbeatLog = false,\r\n -- 插件中需要使用声明周期的客户端模块目录\r\n PluginModules = {},\r\n -- 插件中客户端需要生成的CustomEvent,模块中必须得有ClientEvents\r\n PluginEvents = {}\r\n }\r\n}\r\n\r\nreturn FrameworkConfig\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ServerHeartbeatModule","guid":[4201358790,153109338,3063225923,2088318747],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ServerHeartbeatModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 游戏服务器心跳\r\n--- @module Server Heartbeat, Server-side\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Yuancheng Zhang\r\nlocal ServerHeartbeat = {}\r\n\r\n-- Localize global vars\r\nlocal Setting = FrameworkConfig.Server\r\n\r\n-- 心跳包间隔时间,单位:秒\r\nlocal HEARTBEAT_DELTA = Setting.HeartbeatDelta\r\n\r\n-- 心跳阈值,单位:秒,范围定义如下:\r\n-- 0s -> threshold_1 : connected\r\n-- threshold_1 -> threshold_2 : disconnected, but player can rejoin\r\n-- threshold_2 -> longer : disconnected, remove player\r\nlocal HEARTBEAT_THRESHOLD_1 = Setting.HeartbeatThreshold1 * 1000 -- second => ms\r\nlocal HEARTBEAT_THRESHOLD_2 = Setting.HeartbeatThreshold2 * 1000 -- second => ms\r\n\r\n-- 玩家心跳连接状态\r\nlocal HeartbeatEnum = {\r\n CONNECT = 1, -- 在线\r\n DISCONNECT = 2 -- 离线\r\n}\r\n\r\n-- 正在运行\r\nlocal running = false\r\n\r\n-- 上一次客户端发来的心跳时间戳缓存\r\nlocal cache = {}\r\n\r\n-- 临时变量\r\nlocal diff -- 时间戳插值\r\nlocal sTmpTs, cTmpTs -- 时间戳缓存\r\n\r\n--- 打印心跳日志\r\nlocal PrintHb = Setting.ShowHeartbeatLog and function(...)\r\n print('[Heartbeat][Server]', ...)\r\n end or function()\r\n end\r\n\r\n--! 外部接口\r\n\r\n--- 初始化心跳包\r\nfunction ServerHeartbeat.Init()\r\n print('[Heartbeat][Server] Init()')\r\n CheckSetting()\r\n InitEventsAndListeners()\r\nend\r\n\r\n--- 开始发出心跳\r\nfunction ServerHeartbeat.Start()\r\n print('[Heartbeat][Server] Start()')\r\n running = true\r\n while (running) do\r\n Update()\r\n wait(HEARTBEAT_DELTA)\r\n end\r\nend\r\n\r\n--- 停止心跳\r\nfunction ServerHeartbeat.Stop()\r\n print('[Heartbeat][Server] Stop()')\r\n running = false\r\nend\r\n\r\n--! 私有函数\r\n\r\n--- 校验心跳参数\r\nfunction CheckSetting()\r\n assert(HEARTBEAT_DELTA >= 1, '[Heartbeat][Server] HEARTBEAT_DELTA 必须大于1秒')\r\n assert(HEARTBEAT_THRESHOLD_1 >= HEARTBEAT_DELTA, '[Heartbeat][Server] HEARTBEAT_THRESHOLD_1 >= HEARTBEAT_DELTA')\r\n assert(\r\n HEARTBEAT_THRESHOLD_2 >= HEARTBEAT_THRESHOLD_1,\r\n '[Heartbeat][Server] HEARTBEAT_THRESHOLD_2 >= HEARTBEAT_THRESHOLD_1'\r\n )\r\nend\r\n\r\n--- 初始化事件和绑定Handler\r\nfunction InitEventsAndListeners()\r\n if world.S_Event == nil then\r\n world:CreateObject('FolderObject', 'S_Event', world)\r\n end\r\n world:CreateObject('CustomEvent', 'HeartbeatC2SEvent', world.S_Event)\r\n world.S_Event.HeartbeatC2SEvent:Connect(HeartbeatC2SEventHandler)\r\n\r\n -- OnAwakeEvent(玩家加入前初始化)\r\n -- OnPlayerJoinEvent(玩家第一次加入,类似现在的OnPlayerAdded)\r\n -- OnPlayerRejoinEvent(玩家离开房间后重新进入同一个房间)\r\n -- OnPlayerDisconnectEvent(未接收到玩家心跳等待重连,在服务器第二个阶段)\r\n -- OnPlayerReconnectEvent(玩家断线后重连)\r\n -- OnPlayerLeaveEvent(玩家彻底离开,退出房间)\r\n world:CreateObject('CustomEvent', 'OnAwakeEvent', world.S_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerJoinEvent', world.S_Event)\r\n -- world:CreateObject('CustomEvent', 'OnPlayerRejoinEvent', world.S_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerDisconnectEvent', world.S_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerReconnectEvent', world.S_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerLeaveEvent', world.S_Event)\r\n\r\n -- 玩家退出,发出OnPlayerLeaveEvent\r\n world.OnPlayerRemoved:Connect(\r\n function(_player)\r\n if cache[_player] then\r\n print('[Heartbeat][Server] OnPlayerLeaveEvent, 玩家主动离开游戏,', _player)\r\n NetUtil.Fire_S('OnPlayerLeaveEvent', _player)\r\n end\r\n end\r\n )\r\nend\r\n\r\n--- Update心跳\r\nfunction Update()\r\n for p, v in pairs(cache) do\r\n if p and not p:IsNull() then\r\n sTmpTs = Timer.GetTimeMillisecond()\r\n cTmpTs = v.cTimestamp\r\n PrintHb(string.format('=> S = %s, C = %s, %s', sTmpTs, cTmpTs, p))\r\n CheckPlayerStates(p, sTmpTs)\r\n NetUtil.Fire_C('HeartbeatS2CEvent', p, sTmpTs, cTmpTs)\r\n else\r\n --* remove nil key from cache\r\n cache[p] = nil\r\n end\r\n end\r\nend\r\n\r\n--- 心跳事件Handler\r\nfunction HeartbeatC2SEventHandler(_player, _cTimestamp, _sTimestamp)\r\n if not running then\r\n return\r\n end\r\n PrintHb(string.format('<= S = %s, C = %s, %s', _sTimestamp, _cTimestamp, _player))\r\n CheckPlayerJoin(_player)\r\n cache[_player].cTimestamp = _cTimestamp\r\n cache[_player].sTimestamp = _sTimestamp\r\nend\r\n\r\n--- 收包时,检查玩家是否加入或重连\r\nfunction CheckPlayerJoin(_player)\r\n if not cache[_player] then\r\n --* 玩家新加入 OnPlayerJoinEvent\r\n print('[Heartbeat][Server] OnPlayerJoinEvent, 新玩家加入,', _player)\r\n NetUtil.Fire_S('OnPlayerJoinEvent', _player)\r\n cache[_player] = {\r\n state = HeartbeatEnum.CONNECT\r\n }\r\n elseif cache[_player].state == HeartbeatEnum.DISCONNECT then\r\n --* 玩家断线重连 OnPlayerReconnectEvent\r\n print('[Heartbeat][Server] OnPlayerReconnectEvent, 玩家断线重连,', _player)\r\n NetUtil.Fire_S('OnPlayerReconnectEvent', _player)\r\n cache[_player].state = HeartbeatEnum.CONNECT\r\n end\r\nend\r\n\r\n--- 发包时,检查玩家是否掉线\r\nfunction CheckPlayerStates(_player, _sTimestam)\r\n if not cache[_player].sTimestamp then\r\n return\r\n end\r\n diff = _sTimestam - cache[_player].sTimestamp\r\n PrintHb(string.format('==========================================> diff = %s, %s', diff * .001, _player))\r\n\r\n if cache[_player].state == HeartbeatEnum.CONNECT and diff > HEARTBEAT_THRESHOLD_1 then\r\n --* 玩家断线 OnPlayerReconnectEvent\r\n print('[Heartbeat][Server] OnPlayerDisconnectEvent, 玩家离线, 等待断线重连,', _player)\r\n NetUtil.Fire_S('OnPlayerDisconnectEvent', _player)\r\n cache[_player].state = HeartbeatEnum.DISCONNECT\r\n elseif cache[_player].state == HeartbeatEnum.DISCONNECT and diff > HEARTBEAT_THRESHOLD_2 then\r\n --* 玩家彻底断线,剔除玩家\r\n print('[Heartbeat][Server] OnPlayerLeaveEvent, 剔除离线玩家,', _player)\r\n NetUtil.Fire_S('OnPlayerLeaveEvent', _player)\r\n print('[Heartbeat][Server] OnPlayerLeave, 发送客户端离线事件,', _player)\r\n NetUtil.Fire_C('OnPlayerLeaveEvent', _player)\r\n cache[_player] = nil\r\n end\r\nend\r\n\r\nreturn ServerHeartbeat\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ClientHeartbeatModule","guid":[2448379167,2886812809,2770381150,2704067643],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ClientHeartbeatModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 游戏心跳\r\n--- @module Client Heartbeat, Client-side\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Yuancheng Zhang\r\nlocal ClientHeartbeat = {}\r\n\r\n-- Localize global vars\r\nlocal Setting = FrameworkConfig.Client\r\n\r\n-- 心跳包间隔时间,单位:秒\r\nlocal HEARTBEAT_DELTA = Setting.HeartbeatDelta\r\n\r\n-- 心跳阈值,单位:秒,范围定义如下:\r\n-- 0s -> threshold_1 : connected\r\n-- threshold_1 -> threshold_2 : disconnected, weak network\r\n-- threshold_2 -> longer : disconnected, quit server\r\nlocal HEARTBEAT_THRESHOLD_1 = Setting.HeartbeatThreshold1 * 1000 -- second => ms\r\nlocal HEARTBEAT_THRESHOLD_2 = Setting.HeartbeatThreshold2 * 1000 -- second => ms\r\n\r\n-- 玩家心跳连接状态\r\nlocal HeartbeatEnum = {\r\n CONNECT = 1, -- 在线\r\n DISCONNECT = 2 -- 离线\r\n}\r\n\r\n-- 正在运行\r\nlocal running = false\r\n\r\n-- 上一次服务器发来的心跳时间戳缓存\r\nlocal cache = {\r\n sTimestamp = nil,\r\n cTimestamp = nil\r\n}\r\n\r\n-- 临时变量\r\nlocal diff -- 时间戳插值\r\nlocal sTmpTs, cTmpTs -- 时间戳缓存\r\n\r\n--- 打印心跳日志\r\nlocal PrintHb = Setting.ShowHeartbeatLog and function(...)\r\n print('[Heartbeat][Client]', ...)\r\n end or function()\r\n end\r\n\r\n--! 外部接口\r\n\r\n--- 初始化心跳包\r\nfunction ClientHeartbeat.Init()\r\n print('[Heartbeat][Client] Init()')\r\n CheckSetting()\r\n InitEventsAndListeners()\r\nend\r\n\r\n--- 开始发出心跳\r\nfunction ClientHeartbeat.Start()\r\n print('[Heartbeat][Client] Start()')\r\n local cTimestamp\r\n running = true\r\n while (running) do\r\n Update()\r\n wait(HEARTBEAT_DELTA)\r\n end\r\nend\r\n\r\n-- 停止心跳\r\nfunction ClientHeartbeat.Stop()\r\n print('[Heartbeat][Client] Stop()')\r\n running = false\r\nend\r\n\r\n--! 私有函数\r\n\r\n-- 校验心跳参数\r\nfunction CheckSetting()\r\n assert(HEARTBEAT_DELTA >= 1, '[Heartbeat][Client] HEARTBEAT_DELTA 必须大于1秒')\r\n assert(HEARTBEAT_THRESHOLD_1 >= HEARTBEAT_DELTA, '[Heartbeat][Client] HEARTBEAT_THRESHOLD_1 >= HEARTBEAT_DELTA')\r\n assert(\r\n HEARTBEAT_THRESHOLD_2 >= HEARTBEAT_THRESHOLD_1,\r\n '[Heartbeat][Client] HEARTBEAT_THRESHOLD_2 >= HEARTBEAT_THRESHOLD_1'\r\n )\r\nend\r\n\r\n--- 初始化事件和绑定Handler\r\nfunction InitEventsAndListeners()\r\n if localPlayer.C_Event == nil then\r\n world:CreateObject('FolderObject', 'C_Event', localPlayer)\r\n end\r\n world:CreateObject('CustomEvent', 'HeartbeatS2CEvent', localPlayer.C_Event)\r\n localPlayer.C_Event.HeartbeatS2CEvent:Connect(HeartbeatS2CEventHandler)\r\n\r\n -- OnPlayerJoinEvent(玩家第一次加入,类似现在的OnPlayerAdded)\r\n -- OnPlayerRejoinEvent(玩家离线后重新进入同一个房间)\r\n -- OnPlayerDisconnectEvent(未接收到服务器心跳,在客户端第二个阶段,玩家离线可重连,弱网,转菊花)\r\n -- OnPlayerReconnectEvent(玩家断线后重连)\r\n -- OnPlayerLeaveEvent(玩家彻底离开,退出房间)\r\n world:CreateObject('CustomEvent', 'OnPlayerJoinEvent', localPlayer.C_Event)\r\n -- world:CreateObject('CustomEvent', 'OnPlayerRejoinEvent', localPlayer.C_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerDisconnectEvent', localPlayer.C_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerReconnectEvent', localPlayer.C_Event)\r\n world:CreateObject('CustomEvent', 'OnPlayerLeaveEvent', localPlayer.C_Event)\r\n\r\n -- 掉线直接退出(默认,可选)\r\n localPlayer.C_Event.OnPlayerLeaveEvent:Connect(QuitGame)\r\nend\r\n\r\n-- Update心跳\r\nfunction Update()\r\n cTmpTs = Timer.GetTimeMillisecond()\r\n sTmpTs = cache.sTimestamp\r\n PrintHb(string.format('=> C = %s, S = %s, %s', cTmpTs, sTmpTs, localPlayer))\r\n CheckPlayerState(p, cTmpTs)\r\n NetUtil.Fire_S('HeartbeatC2SEvent', localPlayer, cTmpTs, sTmpTs)\r\nend\r\n\r\n--- 心跳事件Handler\r\nfunction HeartbeatS2CEventHandler(_stimestamp, _cTimestamp)\r\n if not running then\r\n return\r\n end\r\n PrintHb(string.format('<= C = %s, S = %s, %s', _cTimestamp, _stimestamp, localPlayer))\r\n CheckPlayerJoin(_player, _sTimestamp)\r\n cache.sTimestamp = _stimestamp\r\n cache.cTimestamp = _cTimestamp\r\nend\r\n\r\n--- 收包时,检查玩家是否连接服务器,或者重新连接服务器\r\nfunction CheckPlayerJoin(_player, _sTimestamp)\r\n if not cache.sTimestamp then\r\n --* 玩家新加入 OnPlayerJoinEvent\r\n print('[Heartbeat][Client] OnPlayerJoinEvent, 新玩家加入,', localPlayer)\r\n NetUtil.Fire_C('OnPlayerJoinEvent', localPlayer)\r\n cache.state = HeartbeatEnum.CONNECT\r\n elseif cache.state == HeartbeatEnum.DISCONNECT then\r\n --* 玩家断线重连 OnPlayerReconnectEvent\r\n print('[Heartbeat][Client] OnPlayerReconnectEvent, 玩家断线重连,', localPlayer)\r\n NetUtil.Fire_C('OnPlayerReconnectEvent', localPlayer)\r\n cache.state = HeartbeatEnum.CONNECT\r\n end\r\nend\r\n\r\n--- 发包时,检查玩家是否连接服务器\r\nfunction CheckPlayerState(_player, _cTimestamp)\r\n if not cache.cTimestamp then\r\n return\r\n end\r\n diff = _cTimestamp - cache.cTimestamp\r\n PrintHb(string.format('==========================================> diff = %s, %s', diff * .001, localPlayer))\r\n if cache.state == HeartbeatEnum.CONNECT and diff > HEARTBEAT_THRESHOLD_1 then\r\n --* 玩家断线,弱网环境\r\n print('[Heartbeat][Client] OnPlayerDisconnectEvent, 玩家离线, 弱网环境,', localPlayer)\r\n NetUtil.Fire_C('OnPlayerDisconnectEvent', localPlayer)\r\n cache.state = HeartbeatEnum.DISCONNECT\r\n elseif cache.state == HeartbeatEnum.DISCONNECT and diff > HEARTBEAT_THRESHOLD_2 then\r\n --* 玩家断线, 退出游戏\r\n -- QuitGame()\r\n NetUtil.Fire_C('OnPlayerLeaveEvent', localPlayer)\r\n end\r\nend\r\n\r\n--- 退出游戏\r\nfunction QuitGame()\r\n print('[Heartbeat][Client] Game.Quit(), 玩家退出游戏')\r\n Game.Quit()\r\nend\r\n\r\nreturn ClientHeartbeat\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ServerModule","guid":[2630836918,4157751681,2512528126,1629800954],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ServerModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 游戏服务器主逻辑\r\n--- @module Game Server, Server-side\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Yuancheng Zhang\r\nlocal Server = {}\r\n\r\n-- Localize global vars\r\nlocal CsvUtil, ModuleUtil = CsvUtil, ModuleUtil\r\nlocal Config = FrameworkConfig.Server\r\n\r\n-- 已经初始化,正在运行\r\nlocal initialized, running = false, false\r\n\r\n-- 含有InitDefault(),Init(),Update()的模块列表\r\nlocal initDefaultList, initList, updateList = {}, {}, {}\r\n\r\n--- 运行服务器\r\nfunction Server:Run()\r\n print('[Server] Run()')\r\n InitServer()\r\n StartUpdate()\r\nend\r\n\r\n--- 停止Update\r\nfunction Server:Stop()\r\n print('[Server] Stop()')\r\n running = false\r\n ServerHeartbeat.Stop()\r\nend\r\n\r\n--- 初始化\r\nfunction InitServer()\r\n if initialized then\r\n return\r\n end\r\n print('[Server] InitServer()')\r\n InitRandomSeed()\r\n InitHeartbeat()\r\n InitServerCustomEvents()\r\n InitCsvAndXls()\r\n GenInitAndUpdateList()\r\n RunInitDefault()\r\n InitOtherModules()\r\n initialized = true\r\nend\r\n\r\n--- 初始化服务器的CustomEvent\r\nfunction InitServerCustomEvents()\r\n print('[Server] InitServerCustomEvents()')\r\n if world.S_Event == nil then\r\n world:CreateObject('FolderObject', 'S_Event', world)\r\n end\r\n\r\n -- 将插件中的CustomEvent放入Events.ClientEvents中\r\n for _, m in pairs(Config.PluginEvents) do\r\n local evts = _G[m].ServerEvents\r\n assert(evts, string.format('[Server] %s 中不存在ServerEvents,请检查模块,或从FrameworkConfig删除此配置', m))\r\n for __, evt in pairs(evts) do\r\n if not table.exists(Events.ServerEvents, evt) then\r\n table.insert(Events.ServerEvents, evt)\r\n end\r\n end\r\n end\r\n\r\n -- 生成CustomEvent节点\r\n for _, evt in pairs(Events.ServerEvents) do\r\n if world.S_Event[evt] == nil then\r\n world:CreateObject('CustomEvent', evt, world.S_Event)\r\n end\r\n end\r\nend\r\n\r\n--- 初始化心跳包\r\nfunction InitHeartbeat()\r\n assert(ServerHeartbeat, '[Server][Heartbeat] 找不到ServerHeartbeat,请联系张远程')\r\n ServerHeartbeat.Init()\r\nend\r\n\r\n--- 生成框架需要的节点\r\nfunction InitCsvAndXls()\r\n if not world.Global.Csv then\r\n world:CreateObject('FolderObject', 'Csv', world.Global)\r\n end\r\n if not world.Global.Xls then\r\n world:CreateObject('FolderObject', 'Xls', world.Global)\r\n end\r\nend\r\n\r\n--- 生成需要Init和Update的模块列表\r\nfunction GenInitAndUpdateList()\r\n ModuleUtil.GetModuleListWithFunc(Module.S_Module, 'InitDefault', initDefaultList)\r\n ModuleUtil.GetModuleListWithFunc(Module.S_Module, 'Init', initList)\r\n ModuleUtil.GetModuleListWithFunc(Module.S_Module, 'Update', updateList)\r\n for _, m in pairs(FrameworkConfig.Server.PluginModules) do\r\n ModuleUtil.GetModuleListWithFunc(m, 'InitDefault', initDefaultList)\r\n ModuleUtil.GetModuleListWithFunc(m, 'Init', initList)\r\n ModuleUtil.GetModuleListWithFunc(m, 'Update', updateList)\r\n end\r\nend\r\n\r\n--- 执行默认的Init方法\r\nfunction RunInitDefault()\r\n for _, m in ipairs(initDefaultList) do\r\n m:InitDefault(m)\r\n end\r\nend\r\n\r\n--- 初始化服务器随机种子\r\nfunction InitRandomSeed()\r\n math.randomseed(os.time())\r\nend\r\n\r\n--- 初始化包含Init()方法的模块\r\nfunction InitOtherModules()\r\n for _, m in ipairs(initList) do\r\n m:Init()\r\n end\r\nend\r\n\r\n--- 开始Update\r\nfunction StartUpdate()\r\n print('[Server] StartUpdate()')\r\n assert(not running, '[Server] StartUpdate() 正在运行')\r\n\r\n running = true\r\n\r\n -- 开启心跳\r\n if FrameworkConfig.HeartbeatStart then\r\n invoke(ServerHeartbeat.Start)\r\n end\r\n\r\n local dt = 0 -- delta time 每帧时间\r\n local tt = 0 -- total time 游戏总时间\r\n local now = Timer.GetTimeMillisecond --时间函数缓存\r\n local prev, curr = now() / 1000, nil -- two timestamps\r\n\r\n while (running and wait()) do\r\n curr = now() / 1000\r\n dt = curr - prev\r\n tt = tt + dt\r\n prev = curr\r\n UpdateServer(dt, tt)\r\n end\r\nend\r\n\r\n--- Update函数\r\n--- @param dt delta time 每帧时间\r\nfunction UpdateServer(_dt, _tt)\r\n for _, m in ipairs(updateList) do\r\n m:Update(_dt, _tt)\r\n end\r\nend\r\n\r\nreturn Server\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ClientModule","guid":[2395017761,2395361208,3179134946,4246078414],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ClientModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 游戏客户端主逻辑\r\n-- @module Game Manager, Client-side\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nlocal Client = {}\r\n\r\n-- Localize global vars\r\nlocal CsvUtil, XslUitl, ModuleUtil = CsvUtil, XslUitl, ModuleUtil\r\nlocal Config = FrameworkConfig.Client\r\n\r\n-- 已经初始化,正在运行\r\nlocal initialized, running = false, false\r\n\r\n-- 含有InitDefault(),Init(),Update()的模块列表\r\nlocal initDefaultList, initList, updateList = {}, {}, {}\r\n\r\n--- 运行客户端\r\nfunction Client:Run()\r\n print('[Client] Run()')\r\n InitClient()\r\n StartUpdate()\r\nend\r\n\r\n--- 停止Update\r\nfunction Client:Stop()\r\n print('[Client] Stop()')\r\n running = false\r\n ClientHeartbeat.Stop()\r\nend\r\n\r\n--- 初始化\r\nfunction InitClient()\r\n if initialized then\r\n return\r\n end\r\n print('[Client] InitClient()')\r\n InitRandomSeed()\r\n InitHeartbeat()\r\n InitClientCustomEvents()\r\n PreloadCsv()\r\n GenInitAndUpdateList()\r\n RunInitDefault()\r\n InitOtherModules()\r\n initialized = true\r\nend\r\n\r\n--- 初始化心跳包\r\nfunction InitHeartbeat()\r\n assert(ClientHeartbeat, '[Client][Heartbeat] 找不到ClientHeartbeat,请联系张远程')\r\n ClientHeartbeat.Init()\r\nend\r\n\r\n--- 初始化客户端的CustomEvent\r\nfunction InitClientCustomEvents()\r\n if localPlayer.C_Event == nil then\r\n world:CreateObject('FolderObject', 'C_Event', localPlayer)\r\n end\r\n\r\n -- 将插件中的CustomEvent放入Events.ClientEvents中\r\n for _, m in pairs(Config.PluginEvents) do\r\n local evts = _G[m].ClientEvents\r\n assert(evts, string.format('[Client] %s 中不存在ClientEvents,请检查模块,或从FrameworkConfig删除此配置', m))\r\n for __, evt in pairs(evts) do\r\n if not table.exists(Events.ClientEvents, evt) then\r\n table.insert(Events.ClientEvents, evt)\r\n end\r\n end\r\n end\r\n\r\n -- 生成CustomEvent节点\r\n for _, evt in pairs(Events.ClientEvents) do\r\n if localPlayer.C_Event[evt] == nil then\r\n world:CreateObject('CustomEvent', evt, localPlayer.C_Event)\r\n end\r\n end\r\nend\r\n\r\n--- 生成需要Init和Update的模块列表\r\nfunction GenInitAndUpdateList()\r\n ModuleUtil.GetModuleListWithFunc(Module.C_Module, 'InitDefault', initDefaultList)\r\n ModuleUtil.GetModuleListWithFunc(Module.C_Module, 'Init', initList)\r\n ModuleUtil.GetModuleListWithFunc(Module.C_Module, 'Update', updateList)\r\n for _, m in pairs(Config.PluginModules) do\r\n ModuleUtil.GetModuleListWithFunc(m, 'InitDefault', initDefaultList)\r\n ModuleUtil.GetModuleListWithFunc(m, 'Init', initList)\r\n ModuleUtil.GetModuleListWithFunc(m, 'Update', updateList)\r\n end\r\nend\r\n\r\n--- 执行默认的Init方法\r\nfunction RunInitDefault()\r\n for _, m in ipairs(initDefaultList) do\r\n m:InitDefault(m)\r\n end\r\nend\r\n\r\n--- 初始化客户端随机种子\r\nfunction InitRandomSeed()\r\n math.randomseed(os.time())\r\nend\r\n\r\n--- 预加载所有的CSV表格\r\nfunction PreloadCsv()\r\n print('[Client] PreloadCsv()')\r\n if Config.ClientPreload and #Config.ClientPreload > 0 then\r\n CsvUtil.PreloadCsv(Config.ClientPreload, Csv, Config)\r\n end\r\nend\r\n\r\n--- 初始化包含Init()方法的模块\r\nfunction InitOtherModules()\r\n for _, m in ipairs(initList) do\r\n m:Init()\r\n end\r\nend\r\n\r\n--- 开始Update\r\nfunction StartUpdate()\r\n print('[Client] StartUpdate()')\r\n assert(not running, '[Client] StartUpdate() 正在运行')\r\n\r\n running = true\r\n\r\n -- 开启心跳\r\n if FrameworkConfig.HeartbeatStart then\r\n invoke(ClientHeartbeat.Start)\r\n end\r\n\r\n local dt = 0 -- delta time 每帧时间\r\n local tt = 0 -- total time 游戏总时间\r\n local now = Timer.GetTimeMillisecond --时间函数缓存\r\n local prev, curr = now() / 1000, nil -- two timestamps\r\n\r\n while (running and wait()) do\r\n curr = now() / 1000\r\n dt = curr - prev\r\n tt = tt + dt\r\n prev = curr\r\n UpdateClient(dt, tt)\r\n end\r\nend\r\n\r\n--- Update函数\r\n-- @param dt delta time 每帧时间\r\nfunction UpdateClient(_dt, _tt)\r\n for _, m in ipairs(updateList) do\r\n m:Update(_dt, _tt)\r\n end\r\nend\r\n\r\nreturn Client\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ServerBaseModule","guid":[2864628079,2098938141,2169623745,1280720590],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ServerBaseModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 服务器模块基础类, Server Module Base Class\r\n-- @module ServerBase, Server-side\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nlocal ServerBase = class('ServerBase')\r\n\r\nfunction ServerBase:GetSelf()\r\n return self\r\nend\r\n\r\n--- 加载的时候运行的代码\r\nfunction ServerBase:InitDefault(_module)\r\n -- print(string.format('[ServerBase][%s] InitDefault()', self.name))\r\n -- 初始化默认监听事件\r\n EventUtil.LinkConnects(world.S_Event, _module, self)\r\nend\r\n\r\nreturn ServerBase\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ClientBaseModule","guid":[1957111763,3183362511,3175239265,4168800515],"parentGuid":[756373066,1240223530,2766704094,915472147],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ClientBaseModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 客户端模块基础类, Client Module Base Class\r\n-- @module ClientBase, Client-side\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nlocal ClientBase = class('ClientBase')\r\n\r\nfunction ClientBase:GetSelf()\r\n return self\r\nend\r\n\r\n--- 加载的时候运行的代码\r\nfunction ClientBase:InitDefault(_module)\r\n -- print(string.format('[ClientBase][%s] InitDefault()', self.name))\r\n -- 初始化默认监听事件\r\n EventUtil.LinkConnects(localPlayer.C_Event, _module, self)\r\nend\r\n\r\nreturn ClientBase\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Plugin","guid":[2555489138,626346364,2851147263,1364204280],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Plugin"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"FUNC_Guide","guid":[684900468,2789166335,2666626870,2188212943],"parentGuid":[2555489138,626346364,2851147263,1364204280],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"FUNC_Guide"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"GuideSystemModule","guid":[2070445638,524698677,3104412276,3887386575],"parentGuid":[684900468,2789166335,2666626870,2188212943],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GuideSystemModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"---引导系统\r\n---@module GuideSystem\r\n---@copyright Lilith Games, Avatar Team\r\n---@author Sid Zhang, Yuancheng Zhang\r\n\r\nlocal GuideSystem = {}\r\n\r\n--- 引导的枚举类型\r\nGuideSystem.Enum = {\r\n ClickGuide = 'ClickGuide'\r\n}\r\n\r\n--- 显示强引导Ui\r\n---@param _type Int 1:点击\r\n---@param _position Vector2 生成引导UI在屏幕的位置,Anchors值\r\n---@param _area Vector2 响应范围,Size\r\n---@param _content String 文本介绍,nil则不显示文本\r\nfunction GuideSystem:ShowGuide(_type, _position, _area, _content, _callBack, ...)\r\n local args = {...}\r\n if _type == GuideSystem.Enum.ClickGuide then\r\n local GuideNode = world:CreateInstance('ClickGuide', 'ClickGuide', localPlayer.Local)\r\n if _position then\r\n GuideNode.ImgDot.AnchorsX = Vector2(_position.X, _position.X)\r\n GuideNode.ImgDot.AnchorsY = Vector2(_position.Y, _position.Y)\r\n end\r\n if _content then\r\n GuideNode.ImgDot.FigTextBox.TxtContent.Text = _content\r\n else\r\n GuideNode.ImgDot.FigTextBox.Visible = false\r\n end\r\n if _area then\r\n GuideNode.ImgDot.BtnClose.Size = _area\r\n end\r\n GuideNode.ImgDot.BtnClose.OnClick:Connect(\r\n function()\r\n if _callBack and type(_callBack) == 'function' then\r\n _callBack(table.unpack(args))\r\n end\r\n GuideNode:Destroy()\r\n end\r\n )\r\n else\r\n error('param #1 :_type error')\r\n end\r\nend\r\n\r\nreturn GuideSystem\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Define","guid":[2737473538,3344386760,2691081356,600893088],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Define"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"GlobalDataModule","guid":[1468398307,3695920924,2815319038,3596233864],"parentGuid":[2737473538,3344386760,2691081356,600893088],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GlobalDataModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 全局变量的定义,全部定义在GlobalData这张表下面,用于全局可修改的参数\r\n--- @module GlobalData Defines\r\n--- @copyright Lilith Games, Avatar Team\r\nlocal GlobalData = {}\r\n\r\n-- Test only\r\nGlobalData.PlayerData = {}\r\n\r\nreturn GlobalData\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ConstModule","guid":[2512024877,1669350945,3212507937,4057137757],"parentGuid":[2737473538,3344386760,2691081356,600893088],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ConstModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 全局常量的定义,全部定义在Const这张表下面,用于定义全局常量参数或者枚举类型\r\n-- @module Constant Defines\r\n-- @copyright Lilith Games, Avatar Team\r\nlocal Const = {}\r\n\r\n-- e.g. (need DELETE)\r\nConst.MAX_PLAYERS = 4\r\n\r\n--语言枚举\r\nConst.LanguageEnum = {\r\n CHS = 'CHS', -- 简体中文\r\n CHT = 'CHT', -- 繁体中文\r\n EN = 'EN', -- 英文\r\n JP = 'JP' -- 日文\r\n}\r\n\r\nreturn Const\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"EventsModule","guid":[3985164048,3143978161,3122137539,183444988],"parentGuid":[2737473538,3344386760,2691081356,600893088],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"EventsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- CustomEvent的定义,用于事件动态生成\r\n-- @module Event Defines\r\n-- @copyright Lilith Games, Avatar Team\r\nlocal Events = {}\r\n\r\n-- 服务器事件列表\r\nEvents.ServerEvents = {}\r\n\r\n-- 客户端事件列表\r\nEvents.ClientEvents = {\r\n --通知事件\r\n 'NoticeEvent'\r\n}\r\nreturn Events\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"ConfigModule","guid":[2110201861,617827622,2622314683,2453149769],"parentGuid":[2737473538,3344386760,2691081356,600893088],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ConfigModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- CSV表格的定义,用于CSV表格载入\r\n-- @module Csv Defines\r\n-- @copyright Lilith Games, Avatar Team\r\nlocal Config = {}\r\n\r\n-- 服务器预加载CSV\r\n-- csv: 对应的CSV表名\r\n-- name: Config里面的lua table名称, 可自定义, 默认和csv相同\r\n-- ids: 表格主键, 支持多主键\r\nConfig.ServerPreload = {}\r\n\r\n-- 客户端预加载CSV\r\n-- csv: 对应的CSV表名\r\n-- name: Config里面的lua table名称, 可自定义, 默认和csv相同\r\n-- ids: 表格主键, 支持多主键\r\nConfig.ClientPreload = {}\r\n\r\nreturn Config\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Module","guid":[1574945571,3461236255,2335300488,1410656948],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"S_Module","guid":[760758812,3011002765,2609222735,3236427035],"parentGuid":[1574945571,3461236255,2335300488,1410656948],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"S_Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"C_Module","guid":[3651921500,3839968525,2765586875,2228388046],"parentGuid":[1574945571,3461236255,2335300488,1410656948],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"C_Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"LanguageUtilModule","guid":[1327910891,3901181274,2716120486,2266956693],"parentGuid":[3651921500,3839968525,2765586875,2228388046],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LanguageUtilModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 语言包模块:根据游戏内语言设置返回对应的语言文本\r\n--- @module LanguageUtil, Client-side\r\n--- @copyright Lilith Games, Avatar Team\r\n--- @author Xiexy, Yuancheng Zhang\r\nlocal LanguageUtil, this = ModuleUtil.New('LanguageUtil', ClientBase)\r\nlocal lang = Config.GlobalSetting.DefaultLanguage\r\nlocal defaultLang = Const.LanguageEnum.CHS\r\n\r\n--- 设置当前语言\r\nfunction LanguageUtil.SetLanguage(_lang)\r\n assert(Const.LanguageEnum[_lang], string.format('[LanguageUtil] %s 语言码不存在,请检查ConstModule', _lang))\r\n print(string.format('[LanguageUtil] 更改当前语言:%s => %s', lang, _lang))\r\n lang = _lang\r\nend\r\n\r\n--- 根据ID返回当前游戏语言对应的文本信息,如果对应语言为空,默认返回'*'+中文内容\r\n-- @param @number _id LanguagePack.xls中的编号\r\nfunction LanguageUtil.GetText(_id)\r\n assert(not string.isnilorempty(_id), '[LanguageUtil] 翻译ID为空,请检查策划表和LanguagePack')\r\n assert(\r\n Config.LanguagePack[_id],\r\n string.format('[LanguageUtil] LanguagePack[%s] 不存在对应翻译ID,请检查策划表和LanguagePack', _id)\r\n )\r\n local text = Config.LanguagePack[_id][lang]\r\n if string.isnilorempty(text) then\r\n print(string.format('[LanguageUtil] LanguagePack[%s][%s] 不存在对应语言翻译内容,默认使用中文', _id, lang))\r\n text = '*' .. Config.LanguagePack[_id][defaultLang]\r\n end\r\n return text\r\nend\r\n\r\nreturn LanguageUtil\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"PlayerControlModule","guid":[2099073945,1798851418,2239907179,704505236],"parentGuid":[3651921500,3839968525,2765586875,2228388046],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerControlModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 玩家控制模块\r\n--- @module Player Controll, client-side\r\n--- @copyright Lilith Games, Avatar Team\r\nlocal PlayerControl, this = ModuleUtil.New('PlayerControl', ClientBase)\r\nlocal player\r\n--声明变量\r\nlocal isDead = false\r\nlocal forwardDir = Vector3.Forward\r\nlocal rightDir = Vector3.Right\r\nlocal finalDir = Vector3.Zero\r\nlocal horizontal = 0\r\nlocal vertical = 0\r\n\r\n-- 相机\r\nlocal camera, mode\r\n\r\n-- 手机端交互UI\r\nlocal gui, joystick, touchScreen, jumpButton\r\n\r\n-- PC端交互按键\r\nlocal FORWARD_KEY = Enum.KeyCode.W\r\nlocal BACK_KEY = Enum.KeyCode.S\r\nlocal LEFT_KEY = Enum.KeyCode.A\r\nlocal RIGHT_KEY = Enum.KeyCode.D\r\nlocal JUMP_KEY = Enum.KeyCode.Space\r\n\r\n-- 键盘的输入值\r\nlocal moveForwardAxis = 0\r\nlocal moveBackAxis = 0\r\nlocal moveLeftAxis = 0\r\nlocal moveRightAxis = 0\r\n\r\nfunction PlayerControl:Init()\r\n -- 获取本地玩家\r\n player = localPlayer\r\n self:InitGui()\r\n self:InitCamera()\r\n self:InitListener()\r\nend\r\n\r\nfunction PlayerControl:InitListener()\r\n -- Main\r\n world.OnRenderStepped:Connect(MainControl)\r\n -- Player\r\n player.OnHealthChange:Connect(HealthCheck)\r\n player.OnDead:Connect(PlayerDie)\r\n -- GUI\r\n touchScreen.OnTouched:Connect(CountTouch)\r\n touchScreen.OnPanStay:Connect(CameraMove)\r\n touchScreen.OnPinchStay:Connect(CameraZoom)\r\n jumpButton.OnDown:Connect(PlayerJump)\r\n -- Keyboard\r\n Input.OnKeyDown:Connect(\r\n function()\r\n if Input.GetPressKeyData(JUMP_KEY) == 1 then\r\n PlayerJump()\r\n end\r\n end\r\n )\r\nend\r\n\r\nfunction PlayerControl:InitGui()\r\n gui = localPlayer.Local.ControlGui\r\n joystick = gui.Joystick\r\n touchScreen = gui.TouchFig\r\n jumpButton = gui.JumpBtn\r\nend\r\n\r\nfunction PlayerControl:InitCamera()\r\n if not world.CurrentCamera and localPlayer.Local.Independent.GameCam then\r\n world.CurrentCamera = localPlayer.Local.Independent.GameCam\r\n end\r\n camera = world.CurrentCamera\r\n mode = Camera.CameraMode\r\n camera.LookAt = player\r\nend\r\n\r\n-- 移动方向是否遵循摄像机方向\r\nfunction IsFreeMode()\r\n return (mode == Enum.CameraMode.Social and camera.Distance >= 0) or mode == Enum.CameraMode.Orbital or\r\n mode == Enum.CameraMode.Custom\r\nend\r\n\r\n--获取按键盘时的移动方向最终取值\r\nfunction GetKeyValue()\r\n moveForwardAxis = Input.GetPressKeyData(FORWARD_KEY) > 0 and 1 or 0\r\n moveBackAxis = Input.GetPressKeyData(BACK_KEY) > 0 and -1 or 0\r\n moveLeftAxis = Input.GetPressKeyData(LEFT_KEY) > 0 and 1 or 0\r\n moveRightAxis = Input.GetPressKeyData(RIGHT_KEY) > 0 and -1 or 0\r\n if player.State == Enum.CharacterState.Died then\r\n moveForwardAxis, moveBackAxis, moveLeftAxis, moveRightAxis = 0, 0, 0, 0\r\n end\r\nend\r\n\r\n-- 获取移动方向\r\nfunction GetMoveDir()\r\n forwardDir = IsFreeMode() and camera.Forward or player.Forward\r\n forwardDir.y = 0\r\n rightDir = Vector3(0, 1, 0):Cross(forwardDir)\r\n horizontal = joystick.Horizontal\r\n vertical = joystick.Vertical\r\n if horizontal ~= 0 or vertical ~= 0 then\r\n finalDir = rightDir * horizontal + forwardDir * vertical\r\n else\r\n GetKeyValue()\r\n finalDir = forwardDir * (moveForwardAxis + moveBackAxis) - rightDir * (moveLeftAxis + moveRightAxis)\r\n end\r\nend\r\n\r\n-- 移动逻辑\r\nfunction PlayerMove(_dir)\r\n _dir.y = 0\r\n if player.State == Enum.CharacterState.Died then\r\n _dir = Vector3.Zero\r\n end\r\n if _dir.Magnitude > 0 then\r\n if IsFreeMode then\r\n player:FaceToDir(_dir, 4 * math.pi)\r\n end\r\n player:MoveTowards(Vector2(_dir.x, _dir.z).Normalized)\r\n else\r\n player:MoveTowards(Vector2.Zero)\r\n end\r\nend\r\n\r\n-- 跳跃逻辑\r\nfunction PlayerJump()\r\n if (player.IsOnGround or player.State == Enum.CharacterState.Seated) and not isDead then\r\n player:Jump()\r\n return\r\n end\r\nend\r\n\r\n-- 死亡逻辑\r\nfunction PlayerDie()\r\n isDead = true\r\n wait(player.RespawnTime)\r\n player:Reset()\r\n isDead = false\r\nend\r\n\r\n-- 生命值检测\r\nfunction HealthCheck(oldHealth, newHealth)\r\n if newHealth <= 0 then\r\n player:Die()\r\n end\r\nend\r\n\r\n-- 每个渲染帧处理操控逻辑\r\nfunction MainControl()\r\n camera = world.CurrentCamera\r\n mode = camera.CameraMode\r\n GetMoveDir()\r\n PlayerMove(finalDir)\r\nend\r\n\r\n-- 检测触屏的手指数\r\nlocal touchNumber = 0\r\nfunction CountTouch(container)\r\n touchNumber = #container\r\nend\r\n\r\n-- 滑屏转向\r\nfunction CameraMove(_pos, _dis, _deltapos, _speed)\r\n if touchNumber == 1 then\r\n if IsFreeMode() then\r\n camera:CameraMove(_deltapos)\r\n else\r\n player:RotateAround(player.Position, Vector3.Up, _deltapos.x)\r\n camera:CameraMove(Vector2(0, _deltapos.y))\r\n end\r\n end\r\nend\r\n\r\n-- 双指缩放摄像机距离\r\nfunction CameraZoom(_pos1, _pos2, _dis, _speed)\r\n if mode == Enum.CameraMode.Social then\r\n camera.Distance = camera.Distance - _dis / 50\r\n end\r\nend\r\n\r\nreturn PlayerControl\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"PlayerGuiDefaultModule","guid":[2808842556,2341291725,2636353436,620642667],"parentGuid":[3651921500,3839968525,2765586875,2228388046],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerGuiDefaultModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 玩家默认UI\r\n--- @module Player Default GUI\r\n--- @copyright Lilith Games, Avatar Team\r\nlocal PlayerGuiDefault, this = ModuleUtil.New('PlayerGuiDefault', ClientBase)\r\n\r\n-- 获取本地玩家\r\nlocal player\r\n\r\n-- 姓名板\r\nlocal nameGUI\r\n\r\n-- 血条\r\nlocal healthGUI, background, healthBar\r\nlocal RED_BAR = ResourceManager.GetTexture('Internal/Blood_Red')\r\nlocal GREEN_BAR = ResourceManager.GetTexture('Internal/Blood_Green')\r\nlocal ORANGE_BAR = ResourceManager.GetTexture('Internal/Blood_Orange')\r\nlocal HIT_LAST_TIME = 2\r\nlocal healthBarShowTime = 0\r\n\r\nfunction PlayerGuiDefault:Init()\r\n -- 获取本地玩家\r\n player = localPlayer\r\n self:InitNameGui()\r\n self:InitHealthBarGui()\r\n self:InitListener()\r\nend\r\n\r\n-- 姓名板\r\nfunction PlayerGuiDefault:InitNameGui()\r\n nameGUI = player.NameGui\r\n nameGUI.NameBarTxt1.Text = player.Name\r\n nameGUI.NameBarTxt2.Text = player.Name\r\nend\r\n\r\n-- 血条\r\nfunction PlayerGuiDefault:InitHealthBarGui()\r\n healthGUI = player.HealthGui\r\n background = healthGUI.BackgroundImg\r\n healthBar = background.HealthBarImg\r\nend\r\n\r\n-- 初始化事件\r\nfunction PlayerGuiDefault:InitListener()\r\n player.OnHealthChange:Connect(HealthChange)\r\n world.OnRenderStepped:Connect(MainGUI)\r\nend\r\n\r\n-- 姓名板的显示逻辑\r\nfunction NameBarLogic()\r\n nameGUI.Visible = player.DisplayName\r\n if player.DisplayName then\r\n local addedHeight = (healthGUI and healthGUI.ActiveSelf) and 1.1 or 1\r\n nameGUI.LocalPosition = Vector3(0, addedHeight + player.Avatar.Height, 0)\r\n end\r\nend\r\n\r\n-- 血条随生命值颜色改变而改变\r\nfunction HealthChange(_oldHealth, _newHealth)\r\n if _oldHealth > _newHealth then\r\n healthBarShowTime = 2\r\n end\r\n local percent = player.Health / player.MaxHealth\r\n if percent >= 0.7 then\r\n healthBar.Texture = GREEN_BAR\r\n elseif percent >= 0.3 then\r\n healthBar.Texture = ORANGE_BAR\r\n else\r\n healthBar.Texture = RED_BAR\r\n end\r\n healthBar.AnchorsX = Vector2(0.05, 0.9 * percent + 0.05)\r\nend\r\n\r\n-- 血条在各显示模式下的显示逻辑\r\nfunction HealthBarLogic(_delta)\r\n healthBarShowTime = healthBarShowTime - _delta\r\n if player.HealthDisplayMode == Enum.HealthDisplayMode.Always then\r\n healthGUI.Visible = true\r\n elseif player.HealthDisplayMode == Enum.HealthDisplayMode.Never then\r\n healthGUI.Visible = false\r\n elseif player.HealthDisplayMode == Enum.HealthDisplayMode.OnHit then\r\n healthGUI.Visible = player.Health ~= player.MaxHealth\r\n else\r\n healthGUI.Visible = healthBarShowTime > 0\r\n end\r\nend\r\n\r\n-- 每个渲染帧更新姓名板和血条的显示逻辑\r\nfunction MainGUI(_delta)\r\n NameBarLogic()\r\n HealthBarLogic(_delta)\r\nend\r\n\r\nreturn PlayerGuiDefault\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Cls_Module","guid":[522079265,1772703138,3001358313,3723987968],"parentGuid":[1574945571,3461236255,2335300488,1410656948],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Cls_Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"Editor_Module","guid":[1822279021,412438229,2868384553,280931416],"parentGuid":[1574945571,3461236255,2335300488,1410656948],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Editor_Module"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"Xls","guid":[3561931272,2228374286,3075622864,2620868908],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Xls"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cModuleScriptObject","name":"Example1XlsModule","guid":[1154807438,2590788022,3089941547,2078280727],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Example1XlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/ExampleTable1.xlsx\r\n\r\nlocal Example1Xls = {\r\n [1] = {\r\n house = {\r\n id = 1,\r\n name = 'house',\r\n use_money = 1000,\r\n use_food = 2.33,\r\n is_init = true,\r\n defense = 100,\r\n args_int_arr = {1, 2, 3},\r\n args_float_arr = {1.23, 2, 3.23},\r\n args_string_arr = {'sdf', '23e', 's'},\r\n args_bool_arr = {true, false, true},\r\n args_vect2 = Vector2(-1, 0.5),\r\n args_vect3 = Vector3(2, 0.3, -4),\r\n args_euler = EulerDegree(12, 23, 43),\r\n args_color = Color(129, 12, 3, 0),\r\n args_lua = function() print(23) end,\r\n Des1 = 'Example1_Des1_1_house',\r\n Des2 = 'Example1_Des2_1_house'\r\n },\r\n MMM = {\r\n id = 1,\r\n name = 'MMM',\r\n use_money = 123,\r\n use_food = 336.2,\r\n is_init = true,\r\n defense = 0,\r\n args_int_arr = {1, 2, 3},\r\n args_float_arr = {1, 2.3445, 3},\r\n args_string_arr = {'你好', '你在哪'},\r\n args_bool_arr = {true, false},\r\n args_vect2 = Vector2(0, 4),\r\n args_vect3 = Vector3(-2, 3, 5),\r\n args_euler = EulerDegree(0, 0, 0),\r\n args_color = Color(0, 0, 0, 0),\r\n args_lua = {a = 2, b='234'},\r\n Des1 = 'Example1_Des1_1_MMM',\r\n Des2 = 'Example1_Des2_1_MMM'\r\n },\r\n ddd = {\r\n id = 1,\r\n name = 'ddd',\r\n use_money = 456,\r\n use_food = 222.33665,\r\n is_init = false,\r\n defense = 130,\r\n args_int_arr = {3, 2, 5},\r\n args_float_arr = {3, 2, 2.5},\r\n args_string_arr = {'我在这里啊', '你在那', '呢'},\r\n args_bool_arr = {false, true},\r\n args_vect2 = Vector2(2, 0.5),\r\n args_vect3 = Vector3(0.6, 3, -8.4),\r\n args_euler = EulerDegree(0, 0, 0),\r\n args_color = Color(0, 0, 0, 0),\r\n args_lua = nil,\r\n Des1 = 'Example1_Des1_1_ddd',\r\n Des2 = 'Example1_Des2_1_ddd'\r\n }\r\n },\r\n [2] = {\r\n farm = {\r\n id = 2,\r\n name = 'farm',\r\n use_money = 100,\r\n use_food = 220.0,\r\n is_init = false,\r\n defense = 200,\r\n args_int_arr = {2, 3},\r\n args_float_arr = {200.3, 3, 234.23},\r\n args_string_arr = {'df', 'ssd', 'dd', 'dd'},\r\n args_bool_arr = {},\r\n args_vect2 = Vector2.Zero,\r\n args_vect3 = Vector3.Zero,\r\n args_euler = EulerDegree(0, 0, 0),\r\n args_color = Color(0, 0, 0, 0),\r\n args_lua = nil,\r\n Des1 = 'Example1_Des1_2_farm',\r\n Des2 = 'Example1_Des2_2_farm'\r\n },\r\n MMM = {\r\n id = 2,\r\n name = 'MMM',\r\n use_money = 0,\r\n use_food = 22.1,\r\n is_init = false,\r\n defense = 234,\r\n args_int_arr = {3, 6, 6, 7},\r\n args_float_arr = {3, 6.3, 6, 7},\r\n args_string_arr = {'ss', 'd', 'd', 'd'},\r\n args_bool_arr = {true, true},\r\n args_vect2 = Vector2.Zero,\r\n args_vect3 = Vector3.Zero,\r\n args_euler = EulerDegree(0, 0, 0),\r\n args_color = Color(0, 0, 0, 0),\r\n args_lua = \"还没有添加检查\",\r\n Des1 = 'Example1_Des1_2_MMM',\r\n Des2 = 'Example1_Des2_2_MMM'\r\n }\r\n }\r\n}\r\n\r\nreturn Example1Xls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"Example2XlsModule","guid":[3966878609,3696577721,2175744138,2596481351],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Example2XlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/ExampleTable1.xlsx\r\n\r\nlocal Example2Xls = {\r\n [1] = {\r\n id = 1,\r\n name = 'house',\r\n use_money = 1000,\r\n use_food = 2.33,\r\n is_init = true,\r\n defense = 100,\r\n args1 = {1, 2, 3},\r\n args2 = {1.23, 2, 3.23},\r\n args3 = {'sdf', '23e', 's'},\r\n args4 = {true, false, true}\r\n },\r\n [2] = {\r\n id = 2,\r\n name = '你好吗?',\r\n use_money = 123,\r\n use_food = 336.2,\r\n is_init = true,\r\n defense = 0,\r\n args1 = {1, 2, 3},\r\n args2 = {1, 2.3445, 3},\r\n args3 = {'你好', '你在哪'},\r\n args4 = {true, false}\r\n },\r\n [3] = {\r\n id = 3,\r\n name = '',\r\n use_money = 456,\r\n use_food = 222.33665,\r\n is_init = false,\r\n defense = 130,\r\n args1 = {3, 2, 5},\r\n args2 = {3, 2, 2.5},\r\n args3 = {'我在这里啊', '你在那', '呢'},\r\n args4 = {false, true}\r\n },\r\n [4] = {\r\n id = 4,\r\n name = 'farm',\r\n use_money = 100,\r\n use_food = 220.0,\r\n is_init = false,\r\n defense = 200,\r\n args1 = {2, 3},\r\n args2 = {200.3, 3, 234.23},\r\n args3 = {'df', 'ssd', 'dd', 'dd'},\r\n args4 = {}\r\n },\r\n [5] = {\r\n id = 5,\r\n name = 'house5',\r\n use_money = 0,\r\n use_food = 22.1,\r\n is_init = false,\r\n defense = 234,\r\n args1 = {3, 6, 6, 7},\r\n args2 = {3, 6.3, 6, 7},\r\n args3 = {'ss', 'd', 'd', 'd'},\r\n args4 = {true, true}\r\n },\r\n [6] = {\r\n id = 6,\r\n name = 'horse3',\r\n use_money = 200,\r\n use_food = 0,\r\n is_init = false,\r\n defense = 333,\r\n args1 = {},\r\n args2 = {},\r\n args3 = {'2e', 'w', 'e', 'we'},\r\n args4 = {false, false, false, false}\r\n }\r\n}\r\n\r\nreturn Example2Xls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"GlobalSettingXlsModule","guid":[915862330,741691007,3064296356,1451895238],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"GlobalSettingXlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/GlobalSetting.xls\r\n\r\nlocal GlobalSettingXls = {\r\n DefaultLanguage = {\r\n Key = 'DefaultLanguage',\r\n Value = \"CHS\"\r\n },\r\n PlayerPosition = {\r\n Key = 'PlayerPosition',\r\n Value = Vector3(0,-1,0)\r\n },\r\n PlayerRotation = {\r\n Key = 'PlayerRotation',\r\n Value = Euler(90,0,0)\r\n },\r\n MaxPlayerNumber = {\r\n Key = 'MaxPlayerNumber',\r\n Value = 100.0\r\n },\r\n ScoreRate = {\r\n Key = 'ScoreRate',\r\n Value = 12.5\r\n }\r\n}\r\n\r\nreturn GlobalSettingXls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"LanguagePackXlsModule","guid":[700825784,2334608193,2463848717,1647971739],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"LanguagePackXlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/LanguagePack.xls\r\n\r\nlocal LanguagePackXls = {\r\n Example1_Des1_1_house = {\r\n ID = 'Example1_Des1_1_house',\r\n CHS = '我真的很想%s吃饭',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_1_house = {\r\n ID = 'Example1_Des2_1_house',\r\n CHS = '做什么',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des1_1_MMM = {\r\n ID = 'Example1_Des1_1_MMM',\r\n CHS = '我饿了',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_1_MMM = {\r\n ID = 'Example1_Des2_1_MMM',\r\n CHS = '工作是什么',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des1_1_ddd = {\r\n ID = 'Example1_Des1_1_ddd',\r\n CHS = '到底什么时候能吃饭',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_1_ddd = {\r\n ID = 'Example1_Des2_1_ddd',\r\n CHS = '我是谁',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des1_2_farm = {\r\n ID = 'Example1_Des1_2_farm',\r\n CHS = '今天晚上吃什么',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_2_farm = {\r\n ID = 'Example1_Des2_2_farm',\r\n CHS = '我从哪里来',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des1_2_MMM = {\r\n ID = 'Example1_Des1_2_MMM',\r\n CHS = '下班就去吃饭吧',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n },\r\n Example1_Des2_2_MMM = {\r\n ID = 'Example1_Des2_2_MMM',\r\n CHS = '就这样吧',\r\n CHT = '',\r\n EN = '',\r\n JP = ''\r\n }\r\n}\r\n\r\nreturn LanguagePackXls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cModuleScriptObject","name":"SoundXlsModule","guid":[3708642180,138366140,2150857251,245324002],"parentGuid":[3561931272,2228374286,3075622864,2620868908],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"SoundXlsModule"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- This file is generated by ava-x2l.exe,\r\n--- Don't change it manaully.\r\n--- @copyright Lilith Games, Project Da Vinci(Avatar Team)\r\n--- @see https://www.projectdavinci.com/\r\n--- @see https://github.com/endaye/avatar-ava-xls2lua\r\n--- source file: ./xls/Sound.xls\r\n\r\nlocal SoundXls = {\r\n test_01 = {\r\n Type = 1,\r\n ID = 'test_01',\r\n IsLoop = false,\r\n Volume = 0,\r\n FileName = '',\r\n Detail = '',\r\n Duration = 0,\r\n CoverPlay = false\r\n }\r\n}\r\n\r\nreturn SoundXls\r\n","m_scriptType":"kModuleScript"}}]},{"class":"cFolderObject","name":"Csv","guid":[294313154,1450328249,2442680246,272497625],"parentGuid":[344576033,1668630255,2683907297,2226597768],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Csv"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cFolderObject","name":"S_Code","guid":[630163054,4187112824,2753371896,1919329423],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"S_Code"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cScriptObject","name":"ServerMainScript","guid":[344478647,1240680084,3003146376,138016020],"parentGuid":[630163054,4187112824,2753371896,1919329423],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"ServerMainScript"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sLuaComponent","data":{"m_luaContent":"--- 服务器代码入口\r\n-- @script Server Main Function\r\n-- @copyright Lilith Games, Avatar Team\r\n-- @author Yuancheng Zhang\r\nServer:Run()\r\n"}}]},{"class":"cFolderObject","name":"SpawnLocations","guid":[3060414528,2729002180,2754306229,2661207025],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"SpawnLocations"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cStartPortal","name":"StartPortal00","guid":[2311234828,1873955616,2655392405,2525362369],"parentGuid":[3060414528,2729002180,2754306229,2661207025],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[10.0,1.5,-10.0],"m_localRotation":[0.0,-0.3826,-0.0,0.9237]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StartPortal00"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sStartPortalComponent","data":{}}]},{"class":"cStartPortal","name":"StartPortal01","guid":[2880873389,3573762209,2456263445,1546252123],"parentGuid":[3060414528,2729002180,2754306229,2661207025],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[10.0,1.5,10.0],"m_localRotation":[0.0,0.9237,0.0,-0.3826]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StartPortal01"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sStartPortalComponent","data":{}}]},{"class":"cStartPortal","name":"StartPortal02","guid":[3258803195,2884980247,2417960272,149547500],"parentGuid":[3060414528,2729002180,2754306229,2661207025],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[-10.0,1.5,-10.0],"m_localRotation":[0.0,0.3826,0.0,0.9237]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StartPortal02"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sStartPortalComponent","data":{}}]},{"class":"cStartPortal","name":"StartPortal03","guid":[4114460255,208751620,2876153555,2633735762],"parentGuid":[3060414528,2729002180,2754306229,2661207025],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[-10.0,1.5,10.0],"m_localRotation":[0.0,0.9237,0.0,0.3826]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StartPortal03"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sStartPortalComponent","data":{}}]},{"class":"cAudioSource","name":"BGM","guid":[1596674502,2319271992,2900844459,2801180766],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"BGM"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sAudioSourceComponent","data":{"m_playonawake":true,"m_loop":true}}]},{"class":"cSkydome","name":"Sky","guid":[3956874685,1991789763,3220541584,1456680764],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Sky"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sSkydomeComponent","data":{"m_latitude":-0.0,"m_shadowDistance":100.0}},{"id":11,"class":"sDateTimeComponent","data":{"m_clocktime":15.8}},{"id":12,"class":"sFogComponent","data":{"m_fogStart":60.0,"m_fogEnd":280.0,"m_fogColor":[0.0006,0.5378,0.4871,1.0],"m_fogHeightFadeStart":-1000.0,"m_fogHeightFadeEnd":-1000.0}}]},{"class":"cFolderObject","name":"Players","guid":[3397398191,850542637,3138524115,1333674022],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Players"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[718760084,1257915969,2962466066,2150526306],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[438081159,4019275113,2394302102,997050187],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[154289415,229392516,2254790969,287930189],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[1151332762,2713075851,2532352188,4124889855],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[784260868,2609333952,2774312238,2440002730],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[36220398,4069804567,3087648968,1513344952],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[301805571,3183757047,2970218906,1035586513],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cPlayerInstanceSlot","name":"PlayerInstanceSlot","guid":[329052330,2108966125,2246354006,3727422990],"parentGuid":[3397398191,850542637,3138524115,1333674022],"components":[{"id":0,"class":"sRegularTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"PlayerInstanceSlot"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sPlayerInstanceSlotComponent","data":{"m_archetype":[821939657,2231651804,3205518288,3133023722]}}]},{"class":"cTerrainObject","name":"Terrain","guid":[980033814,3447341367,2913170761,3546742988],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[-62.3987,-10.7203,-283.398],"m_localRotation":[-0.0,0.0926,-0.0,0.9955]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"Terrain"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":610,"class":"sTerrainComponent","data":{"m_memStreamBuffer":{"m_guid":[4256450858,2503622707,2701896730,3388076770],"m_revision":-1,"m_type":"kByteStream","m_autoGenerated":true},"m_memStreamBuffer2":{"m_guid":[1237361744,2766752550,2609897848,3988635486],"m_revision":-1,"m_type":"kByteStream","m_autoGenerated":true},"m_memWaterStreamBuffer":[{"m_guid":[2896065380,3843705948,2645856480,1113501019],"m_revision":-1,"m_type":"kByteStream","m_autoGenerated":true},{"m_guid":[1940847020,2248952508,2340679954,660790013],"m_revision":-1,"m_type":"kByteStream","m_autoGenerated":true}],"m_emptyPlaceholder":true,"m_terrainIndex":[0,1,2,3,4,5,6,7],"m_textures":[{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false},{"m_guid":[0,0,0,0],"m_revision":-1,"m_type":"kGeneric","m_autoGenerated":false}]}},{"id":13,"class":"sRenderComponent","data":{}},{"id":15,"class":"sRigidBodyComponent","data":{"m_frictionRate":0.4499,"m_rough":0.4,"m_restitution":0.3,"m_responseContact":false,"m_statusFlag":10}}]},{"class":"cStaticSpaceFolderObject","name":"StaticSpace","guid":[2324326883,3665710638,3070246021,2579386889],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sIdentityTransform","data":{}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"StaticSpace"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}}]},{"class":"cPrimitiveObject","name":"BaseFloor","guid":[2469667971,3052095412,2449968994,2749907796],"parentGuid":[1981988479,3555894,2597800867,69741929],"components":[{"id":0,"class":"sRegularTransform","data":{"m_localPosition":[0.0,0.8,0.0]}},{"id":665,"class":"sPhysicsDataCache","data":{}},{"id":2,"class":"sObjectDataComponent","data":{"m_name":"BaseFloor"}},{"id":19,"class":"sNetworkComponent","data":{}},{"id":40000,"class":"sScriptHelperComponent","data":{}},{"id":10,"class":"sBasicShapeComponent","data":{"m_size":[50.0,0.2,50.0]}},{"id":12,"class":"sRigidBodyComponent","data":{"m_density":2400.0,"m_frictionRate":0.4,"m_rough":0.4499,"m_restitution":0.4499,"m_statusFlag":10}},{"id":13,"class":"sMaterialComponent","data":{"m_uvScale":3.0,"m_materialType":"kSubwayTiles"}},{"id":30,"class":"sPrimitiveRenderComponent","data":{}},{"id":3,"class":"sSizeComponent","data":{}},{"id":666,"class":"sStateSyncComponent","data":{}}]}]}]} \ No newline at end of file