From f736567a374e718574763f545b9cdea5d97d9ce0 Mon Sep 17 00:00:00 2001 From: Neo2003 Date: Thu, 9 Oct 2008 08:42:22 -0500 Subject: [PATCH] [svn] * Updated to 6743 and 685 Moved language id used by Arena to a higher place to solve conflicts Added the empty script folders --HG-- branch : trunk rename : 6731-680 => 6743-685 --- 6731-680 => 6743-685 | 0 dep/lib/win32_debug/dbghelp.dll | Bin 640000 -> 1017856 bytes sql/characters.sql | 2478 +-- sql/mangos.sql | 58 +- sql/updates/10_instantiated_battlegrounds.sql | 35 +- sql/updates/19_pack.sql | 4 - src/bindings/scripts/ScriptMgr.cpp | 4240 +++--- src/bindings/scripts/ScriptMgr.h | 182 +- .../zone/karazhan/boss_prince_malchezaar.cpp | 2 +- .../boss_priestess_delrissa.cpp | 2 +- .../boss_selin_fireheart.cpp | 63 +- .../instance_magisters_terrace.cpp | 42 +- .../temple_of_ahnqiraj/boss_twinemperors.cpp | 10 +- .../scripts/sql/scriptdev2_structure.sql | 56 +- src/game/AggressorAI.cpp | 2 +- src/game/ArenaTeamHandler.cpp | 925 +- src/game/BattleGroundHandler.cpp | 1904 +-- src/game/CharacterHandler.cpp | 2131 +-- src/game/Chat.cpp | 39 +- src/game/Chat.h | 3 +- src/game/ChatHandler.cpp | 1166 +- src/game/ConfusedMovementGenerator.cpp | 310 +- src/game/Creature.cpp | 47 +- src/game/Creature.h | 1 + src/game/FleeingMovementGenerator.cpp | 758 +- src/game/GridNotifiers.h | 2 +- src/game/Group.cpp | 2908 ++-- src/game/HomeMovementGenerator.cpp | 172 +- src/game/Language.h | 125 +- src/game/Level1.cpp | 163 +- src/game/MiscHandler.cpp | 3524 +++-- src/game/MotionMaster.cpp | 684 +- src/game/Object.cpp | 2894 ++-- src/game/ObjectMgr.cpp | 28 +- src/game/ObjectMgr.h | 2 +- src/game/PetHandler.cpp | 1297 +- src/game/PetitionsHandler.cpp | 1872 +-- src/game/Player.cpp | 74 +- src/game/Player.h | 3 + src/game/PointMovementGenerator.cpp | 2 +- src/game/RandomMovementGenerator.cpp | 2 +- src/game/SocialMgr.cpp | 620 +- src/game/Spell.cpp | 4 +- src/game/SpellAuras.cpp | 12720 ++++++++-------- src/game/SpellEffects.cpp | 4 +- src/game/TargetedMovementGenerator.cpp | 4 +- src/game/TradeHandler.cpp | 1267 +- src/game/Unit.cpp | 16 +- src/game/Unit.h | 4 +- src/game/WaypointMovementGenerator.cpp | 2 +- src/game/World.cpp | 13 +- src/game/World.h | 1082 +- src/game/WorldSession.cpp | 1004 +- src/game/WorldSession.h | 1285 +- src/shared/WheatyExceptionReport.cpp | 1978 +-- src/shared/WheatyExceptionReport.h | 234 +- src/trinitycore/TrinityCore.rc | 8 +- src/trinityrealm/TrinityRealm.rc | 8 +- 58 files changed, 24398 insertions(+), 24065 deletions(-) rename 6731-680 => 6743-685 (100%) delete mode 100644 sql/updates/19_pack.sql diff --git a/6731-680 b/6743-685 similarity index 100% rename from 6731-680 rename to 6743-685 diff --git a/dep/lib/win32_debug/dbghelp.dll b/dep/lib/win32_debug/dbghelp.dll index 597188d77e94eadd006c02d3e8afa6a704684c5b..6f646de49ae2ee709ee93203ce758aabdab6a58e 100644 GIT binary patch literal 1017856 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%0A{}5!de8!#elCfyJJYa7)jnw26O??bHC&E zWsE;zIGSzG?&`O5(px&ZV-9g|WN?%)jOYSAXyq!p1uT!YcQ11p&$5id@MC~ij^PSa zj_I^5s2md(139%gDhxxrwh^~^bWth0N-6+W?8_-iUGQ&Cxm55+JfA8KxEAONATYHOWC9o@aIuFSoS#@IG#IzJ>xFF4Ru{xl7LrjONn^Q8z{*>| zg*2`!$(LB&S{3^%CUM#ErS61pyV~g+&MP6lf=SMFE;;hCZQ~Uw{8y@Dr>NXaCnNr1 z(8MerioUAFEn&{1e@y4m--}k0^UFep^i$6ebw@g6Yq;r2n?0Q#cb4=xe0G=^#+xw5 z$qSrWsy^Kww%1X6Wo8TCu8nYbdXmz!I$ZDPE6iIjYk%`0CqexbA zwqLIT0MpLRX9-ppr$9jr-li}?0f!gbvu}BYzFX7;3v$58+401~OBnMynSodOBYF<& z9QSylU(eGNV{r^SNPmIqG_ESt;N{+B}!!M23e=| z;e3ZSAODN)ZS=HT%jb;UG~1s2`aN#42Bbl>9^B;W!HPuwc&1NR&^A5Uul!AeIQJ`+ z^ZQn${_*JZRq0uq`cDdR?gb-aP7`ULcv8v1v;U9hZSyrCS0?Ip2SYKwcPo2;Ii@#TUIxAn6 z|C42O3tF=L7Y$tWbdjRL7x%AycVwWWnx`35iN{15Lg*4t%J%6MgMTjFB5qG(XVyj^ z<8fdCWFnTrypHU5TtNyE|0T5?eMlUeZe5mIzB+hrO)Xzz^TxtY0(5HWRVm!PHS;ZU z@7Jd~Zm{=D+O8uCjvFwp3PMZbUfWXod>cGmI9KW5o%h9-p=8T1lU} zlnaM$Dk{QOD}49NqP#wMHuo|qB=vI4WuKKL*lg=*!$nU3Z>nW(YPoE6>DES9YwP?} z`}sEU&-FzXq?RtUBJ+HaMXBYNSdsa@$dXk1vSK%jOi)Sx|Btr!fsgYl?!DKNjAdto z)>saLh#-hV4R#WPO>0-7NU9eUW6@pJ0#OXO;JS-qifgZICD$gBR|0P~OC1`uq0Md0 z#kA?YrkK_=rj3j2y2wcdRa`?2RY6eo_Svl3>ID^4D$xD@<~+~-kz@t#`@a4ptMfe1 z`8#LMoH;XdX1?qIl)7XGGW`9-yuZ*Z&3~Pn|Auz&3BA(%uXFRi3IA@cpfwmv@7NT~ zc~5%B_8@&Uy`$B-*r`GK-Q8npI0WV@Vd;A;eZbO9N+0iaT}U_Yv`5XbeFe+-s;RZd zT?Dfr%wAhA|IocH(P{C2`#?<-5(ai zgSq+do1CCiMS5wR-hezcn%h1_wqK9*GUknb+NX)wQ(K5gIEzg%oY2Pf;5xEo2JAO$ zzbAXC;U>d)=~5#ZTLKw-@!1CNwSQbec&U0yrJX~c3rcJKcCSrcXdsQzyVw;2D3`vo zS@nXAl9$-mjO%1MH_Z0$o>1}H(RxX8}J-2`Hp1sKR%zU%FA@v4TjwiL$8`K4Df8yGz z;u+g?EG7O2(^UYOZjLEHU1Ye$cnEe!w^Q_?Y7|W^;Yn=0#I->vQ+2v^yE4p+@6*)1m;G=q_Zz$sl^BTeT^-5pB9S`QfEz$>83WC9 zG=Q7K1G{4K zUhMt83Ut)75{3sl$uxc&Eg+m%di2iam8oM~4|%cIWA(1*Le22NUOx4zUa|%0U_Tb0 z+wMKX`37mQoJO9dZ+*wA71`Ci!JZN<96KOR(R&c@_u0WIpwyn|4eQ$hswb>&6?iFf z)1Dm=oZvu^xjFGgf8YCho!jVB!XftOWgZax>R2J#c*fj z65A2;X7C2$RyEF+G@a{;8+F-yH`Vdg@eKh1Eq-sC^}flm1-uvzormp!gBfn-1+O}0 z5ocOTKE@a*@ANfdDSfRie61^dg-x-tUuj5w7iYb6u8DPCo)r1~tu|8zwY zLftY}ve;kb-!g)bug}`X*7`#$&cB@wVUAbu$u;97o--KE&pivhu~q$AlqHy7H9&=5 z4#hVFQ6(&E@7lD(y7LE?Q&YovHIFb9^NN;w0f5YrcJIqi7`=6~T(YWz*({tN{r+J_ z)IcnX9l8wy0onX?C7WV6XyJ0e(Iyiw>*p&U~3v&-HH+V8-^O(qz`JeTB%T;9v9V?3(A~MVTnuvGmT!<0XJyTZ*N_?Z46hx8-YjHoU}I*zj7J5Jua_BqRSm) znHJo(K{-~P7kcKtXF#0#IKpMRN8#7F!WA6u1g*_OF?NSBhbYt$o_n@fSbmCYo`hfh zc2)BJuIj8AToAh%24p&QHAJ&*W8Nt;p~g!cn^Yvm=%guNYo4?a2F~Dt@YIzTF+=&6 zi{GxRyu__^`qGA!DCpHbE_Ut{K%x^Dy%$RY5Z;GSaNosmh4-b~JlBI6AWE&?w&?6Z zAJbpfB}R}@%x)g4?3hAKv*t=%qwz!>N@vW6AM0`2sl3XS3k*k7J)J=e9h?m1BoFCP zl2tfk3!Nnb4lIi9j$esqbNK^598QK*VSmFEm zWg-v@GTHaO$+N)0KW=5SyKo!k{U$`Zuz4Q-!R(wfXw>G}$#>g4cPoJidF>D^i+0iO zHSAKnwC&Ql5FlV}v>|P8wQVX#f*iI#7ILtOX7x68tR9|;-ITO8M1`2HPY>!*d!s(> zCv6)d^{!9bqlgt=Q3S%~8bEH~{1Ccf#=ZagJ?(TcwQxbd zA&eN?X1t^V#S9$ag})&TG2 zg&g!Qw@O2?VPz-;^uk@$-T~DhPeIC*IM1kT4ZNe6!v zB`QI zW0pT0PdtmZ2UxdzpXhdnJ`vnV`h-9V*MLJpCG@f`ed4UWWY9wtzmw*B!Hjg@IbJ7X zcQ3E>M;k!JT2&7Sc>LIlLX{{-7}Id?$v0D!fA(Ef1-(+*SYDx7DHrIk?|Qj_PU_BT zjY8+w@}9|8C7%c=LkB zx46au;|$Kp>A3z48(C0_@mL&mckFpZ{&8#AFeIndmVQX-#7P)fZ{>GqO(Pa>S}6zX;2wr4y~4SbDdV1 z$Q&+UUF;-~6!8zvmZCEipyhW$-Fd!l=$L-?2dGo2C({~hY@5WGi#6X8Lk*e7Spt;^ zwXBkH5X@EbjZ}0(m3+f0nb^=Vu1c zMkPX3wsSiaboeKK?xF|Vg2>wuzj3O3##VxTeyNcQ#>)yHWuyw48VxU3^NVh_G~wfS zt>}Z`jVm=#bX>F|-}o8{pD_^v`|vJM4d6ZF!&}^n!FyJ-4}Wg(J|vzi0Hx8jXynK( z2OTht>UD$<;qz@q7YCFR!Qdp8yx=w7_71-22stO+62adtfwL>}4~s`O^(O3rOl7pv z9EmazyxQ0)!RqDIDHOH7mX7Gs_BnNYAJC#(<^o4Oq&)nKs zqF?}@UA%vfKYo9q&s*=pnbrfgf1#8-u&rYz`97BKolU?(lg;8oqk@c)sfTuQ{E0Ni z!w26W69}FOAy9n~EftbYq&E%fub@1K>^9_lEY1e8 zHMHp*f_LiRDA3H97rmK*t2H0KrZWK29 zs`uOMpd&ah?K?K2SKX}V(GdIro@~acZkmfS7`otE)O_gTlx+>p%k-*ym7Uwn!|QN# zf>+chEBq8_H6hknb65q7H5EanAg;m2q%DapVE|~MPRfZ8>)Y|)C4we|7m_$z#sS9r`y4;<`7a)}2_T_wZd%9L;i+t`uyYRWmi<|-3S9X~;v zLb9>SahnlLR=Z>rqs!cVvojrs>h7B#hJlDvQkYP&qYST7iAjO2l zyhI(9XeXGgjCTecYo~-Ea2$0Kh9J$W%hdryrd<1YQK8}EE9afM(BTjE@ykj+Oq;Vx zfHEMXmXDCFp60^K8`#2_6^RCs5nQ=O*tv~V`#YK~+<1*P2cPu4s=H74yGbm)jbjU<%8(c5K z?TL-=#;ijd9`-m^pfkn!Ejc?QRMc+QIAs+h%;WPbG6n8zm+Zf8?PK8 zu=hlz%muknk>s~B-qUy~=qw>IB8FvP)s)Hapr)t+n061>jUpQ34l(a84noJ?+5CE| z@jxXj7(s39fIS|q)WCQ=3Vl*ds~uHcgu4&$1N5}M+n_dxq~SN|fQ@l~@VMU|k8Y63 zBwvpT*%p8&n6G;0bZ()kj`tTOV-Us6^+9J$5y}(e!5mT28f0u*DQqQ>SR+(5AC|8Y zY?b;?v7SH5;DNO$tA_qz z{+cp+gCe5H+M_stVu!S5g~?R@Z*X5Iho~&lcS$4V^(4hGlUQSGs9K$Gn&S zb}01hxUEujT`#b`KkW7&#|1g>biPFmi{IiQG+yC`n-m1OQ_OduoD<4`5xcE}W^(8x zSr2M=AL5Gpl#tP%d2`KRbldn4BeJdxG}~F1CQdF*WM}d)N8D~=m4VgMT4jFMEzV*% z-DlgV=^{$#un&LP=IuAEh3v(oqwAQilvtd{R-1;MWz!HSy0oM~&cle{*~jc`3>sKB zM8T&W^^M0|U1_8?>~^IG7b_H!pj)H zvC~M#s`JBDAj#b--8S2oGgeAhk6o;;49Q0x0|vV9U6KDYd8M{!6{&AkC0~~{%Y63h zRyuVi)3O)9`$XsS^ZWND=#Oj&PE#2WEmTvH>`_@>>^QKYwI|b_yft<--S$|z<8iT` zkA)6*W}~NKUDADL;;^UHGDQ0R*MY_v^)7Z`L%b)`l)Sb1D17XhOq@T>J#-`x+Css` z3=>L#)#P{Ply#;!#%kHTpGln;R)&%t*|sO*U6sad)TPF&rMLgSt^olP<*+WC5Z@Qi zoa}WkN@P^~=C`WLWCFnx{5253tiopkI9RHutn>B8xSq+hHkg-mKEc>u$?O38Ml5HK zf_LlyK-U(7+VxEI6gFVi!(JMZ0;o?Nw2Q5Nw{4qy!e|E&Z8a0M)9@0Ie6b2!aglkx zIFSd8Ryf!};?Lz*Sd5vnGn2nwIzc!|L``J#WEllHxDx3Jjlw{&NB{>c|C!92P z4=`kc{ze+kQl@N9o3eJ} z%U?1dHhghf6Cgbaq*rrRD1)@yBn{qulMge@x4qp~d1tz15=rc5(gR5y zI4*hQP`o#w}!nCQu8UErF!gUo=a3Ui+ori-rk#yV1pkA2t zM}7EVM~vTy@Ei}jz!Br%jdc1q$egd#Xlu67fmEk8c${80ul+Zfou_ zqOeADs=GCN=ov279d#J7O%`7&ZKIHejw=p_fvR|wv&CR$Vk55Q{+2%4PPp4*J8&vs z{{(e=OqB!b_H65zsOkR$%LeK`7%fFz6YjQ6^yMbNVeFSUs+emZ=dFG$bxFrzA2+-0 zj>|O#6cd|tE&q0%{K0ZBqj*A-dP2D+rU{EB=5E^To|w29Tz81jaX-#0qQs|&>gk8G zarLdpy@hF9xWIjSx|4A&@6|YfV4o5O#NX5E(@_r~67Rzk^q9g1{@5P5$7p1Argg8e zEI@m&j`MBjF!v@xh@7S?8OLGq5eV3Frpf9tp0DaZ=QlL+2+7*Lsot<4B#I@a=ld%{Ho%zGI&vH{K zbHx`BxMuG{7(-{ywI>d|XGsCUd5b;g3xOceRYsu4jj}_anI8x=kIWAQy4F?*kHF-* zHN8jB>ln}6ySL^uJL)Ri&fzZ{A=^1XW!p3GTao(Kj052Y#h@DNYRE>1M97$ae-JNX zH`hVWb$+8d-_?-V;<~=*@vc^IeHswDUQpe5CSK1p%m+$Iy*^Oj&iBL;vyKT@FQ|)B zwV4;iGYSrCf|K+45Bei(W@IgOu7QT78$dTIs59r|nH>~st&1mymN=to5Kx4FHHvTp zdQexOH`9zZB%`fqQ@T&%lta+WiV!7ttq%8>=P$YW(dRr`MU-^SSy%`nID2~OsvymqDJp)e;C0b^gSyUG(mRKPkxRF)A_Y723vvA)L6Xe3QG zl0xqyN%Bu*TK8k7nkYhe!&`;jg$_07y5U6X{yiZAy;{^Seqi0p;DoUmdH$H%WlIe)ccN6t3cUb%*Sb}P#@Gn z5&@r$dT$G>V6zs0CSWoh?=;x%3_#6%Ox`!NA1GDoVh+_&m6bSk7E!@=;jq9;p7Bvyu{Y-KTgSykUbWKs9CW5-u#=LI~&>r^@cQs=v zG7F!CM+`uR>4R2aXUacdbh^#+zN=s`F2H%jVx`jr7V2>zuu$4{G_Zyqar_Qc2{y61 z8{mgC%b-_z3so4-RD8VDR=Y&L#dsVK2!{Cksf3`xema{3ac5KBIyd_FHR{Qg3L{(;$7jy>81C@ zsWP!l0U`YUFj4S*d)cKbckC282skn8giW);NzD#iJq!vv?YsAjCI?1zJE^=V@LM|# zjiiHdjGA}gpR^fF+ofnM#HLHMRMSpNl|NNN`mIlXf7mRMza*>!EOjSvao9e4rwLX> z$%BcUp&wev*%gBx<7h?+vB3Pd@E(~fc^H8sQ z;V;;45&0|k0qUQRvjt$1@cX}|fRB-zu+~F#hv>9iuNk==B$=<*OtEVzZ_S4l`=vzv z#5U-J<(ygYFf|#_l7bkysLrv-1KdrpIjqjWMWOcurzO1;-#?RkneB}j7D)sxatgl_ zos_ReL?qx{>6F)e_##knB67()XEmqe1&RH*5j1ar=uq-g;id@O*?lor!GDCVHVr5{ zSz~nc$V(4`D&vi9BZne|Eq#LOLukgt(CxnHRq&sbf#VxSDRk>aWGV<$jG*kk_*@0p zC4v2TybF6Ue`4x(i@6y~9UCLqi0LFDgp6pqQX-$}+y*1Ecu|-_1&1Zgq%IXikaf$@ zgq2o{CTRl{7<1@euv)mVsBT2_?9Xp_G5lmZff@(+2~-38Bso0~6{i=V;wAXKV3v$^ z;w}t^H9k_Zcm&%8Ncr&mQY&&@OQERsKVH~RnW?(ex9Tvq4uJXJY=TV>(OqX?M^M<5 zyqN(5cf<@oFc6vAv`DrKOTg;yn`~u+mDk(z)Ta3z9(c-68Li~jfXst_p-PyAiYGld z%V$H_s&saS^zcB4k*O#>!>L|+U`RQnu|BXZt8`8OZlUp@(tXdF_+(IxWL!@CDq0D9 z2L(wFR#9Qb`k`}7wpx!IWd_3~@3~n9ptKWEbP{07`nWzSq8f&E9 zE&(;UKBX=ukeFD*EzHjjoUF|*u@BUpui{pT`7`xPS(CS0ldeWbm~$UvmP;m$n?et4 z#K6x@3rAD)h^Q%I4jf;W@Byyt@ri9VEj9h`(iGW*II3+3k#RG_tlJE^cg%=Q(4bHz z!(H$Jn;@OUS}_>yJ9d49W)E|Iu!K30%39v|-uCQ(CNbu{@SwAw^*9OMp(` zkWeYx_T{*XwD=6^D{hssPhnwEo#Ct^3yXns6i8jrZwOv8t3*7pdyh#`v?^;2Y$cjz zuCFLl?!{UG9n}xDY(EkGTg$Wsiy)gU&RM7)DBNVmO@Muq9R|67-})vDuqXBnNB_6( z(5Rz>L)7L70iqQO`8G`(=lAZ;pm!Uezjyhge!X7R1GKR{geUQ*Xh(ePpc0ModT#XF z?WlOxpccz!)f3zH8AZ(d9R;>5gAYytHiTmU?K3-Cb(?`?BHs{KUqt zjgoL)i+TUJ-$)qoh&QUaw(F^<5jI@sSRwI+&d{m*(Z3>Bxt@B4Q`mLm-0Msl2(s22 zv&7K_ahu1d6y|A3A`nmwXIrbLWuvLE$OF1@goW|PFu(*wEnDp@%O`X&vt1k2BjJLh z2Qly0{=s#=D53WI&jifp*OQ4NFI38_|2S;K=YYf!tQx{PY%-CJU)Fs>ry3-DP5LdKe|Lq0??+_*RFn9#+{%gbsdcC~Y zCMN9U$YtL|fidcKe!)+58s)eTB9?JZS2 zSK=B4UKPtPNol6=HDI4OA@fP;G$hEpO2MKHLmGpF7c(=RYl9uL{E#W~+j+L7K~=Rj zq%Lk$@g3_7KCiT1tbfAwBJ+p|aIlV!wNf(e9@QsKom8~m8s2Dq0%>nb+{nY9wwb24xOMz6_)i|#yWc@HbEE=cqmsKVRq#Fo?; zihcO{{8CNUfXw_m#0G0Tq{pd{PlS0L3hufE`GEcIlIf z>`KRWTee+Rb0_IE0t|iHL%K4ySG74-#Z`Dha7jO;9;({odQ{Cm#fqel+gM!vk0|Yo z=L*8NSzeiNxp1Rvwy~}L5Qq8M`>ukD91tcecSfKrlliY(6FzV%1Ib_77$h6QWcq|o z?zVYh15lBa=eE@S`$I=qD>zu`2_0Uaxa^3uco~jmwy~~^`Id`eXNA+NN&;rb#Run9 zP-Q?7=+mtdnmHk_;BwMh6`&L9q5z$cbsTBA)B(_L+LG2wpqWg^j#{>))!75-W`R6( zigwc$dcX+)ZfuX2nnZp**0#MZ#wqtV|F9CzXkls15&CkLVa zv`B90;TT@VPZ|lgrGMF2An}aQADgvOg~@czb8D2%#&u|;E4nr1H96ll|CJi5X0<}L z#^+OuGb$_8;A+@wtba|e^gXez)mG%w^NZYXMd;g3Q2@>k?On2ijd`j0Ie)=A7D-)X z8ArD5=F_@YlUuY+lTJHb0Lv{&%sGoPa-n222ziD)OVW4b^s$}<6S=}5(L6ur&E)KY%mM9&SeHD-67OPu zCXfD+CH3d${7zH#38k&}<7d@<&JQ;K&&?aK#%5)ova)xoTPKuuWzSn#-QkLRr%TEr zJ(gh&83IjL|=~(^*IVX7i^VRhVZ|YwN3TQUiY5+jV2X1Rtk(#B~pX8aMr|w2TyAgi1qm z^MCOw;IL=~K`0*>>lkJ7dlTB9VHjQX4)2fN>yEupG#L0!iMzTZsV`Q2{#9z{!X92I zrVDt+QFPcF~S=WK(bRb|0Y&r?HHDl8Pe$Q4~e1X~gZ#a;b zm^%jjBbU~g6JVRqb;&d@dT1-)MQ707$;4-h#>j>UMJwSE+)g zQkS44veI^~7J1+zH7&p=rkt{UI-C0ZX(l0b1dKYH`twsH=h)`1IkLIbHYY1%b?=1A zfE4M6H0kI?VIyUvu*v>R@ypY+{&Yor8wnS`!a?#c{#K(h|DDNIxzkjd`+a`=IST}V znx{|PUTSD?j7(Wf1^gu;5IL8iWv8v9k}SK*Tl$VoR3KeKfn*lWn#q}1;_)Rmh`L;@ z`ctEy&oTgM3XTv~Vro}M$MS=BV_Kt=XX5A*RfO#}Z*GLru*E25(KFz{h6zSY)3qrZ zJ?B;!%nF-RSkiFQ{kuM|S690>C1S1x$8Wg348tmCGG3cMn1@+7S1Fr^^J}7`EFl(8 z?uwlYr|x?iveUW8sAD*K4us*)6@K|M;{J#O;mO#Oa)nmG zeq~ZwY=3FKt(3mDP^%wui$fGWkhK^N$BfV-+ymZD{AeWBb*HKC*y#$9*w-f{g{S868M~)@!1@2$T>UtTw(_@UvwKOa!|mQ)yC`zbSzO)}?Zx%+3O@E78-+~P zbl)Y*zwY&k&Lz6)PuZ*KtU^DClwUJa4#mCZB7T|ern-YwyEEv-wmn1FqNg;Gk2>Iq zoAhtT8LfA~+bEcIMH1^>7Z#`Wg4PpNny@SW=%XVx^egG;2zFf7tEa42=gstQywVR< zHnGM+%SkuNInSb%Ee^ja>3hUbByb~~`OaqPpJ7)dXd8`tBk!mPRZgw>LAv{T`WPGxUm)E%BZ9_7Fw7K?cu<5o+Jj5TVW4)mxH>WNfr4_XIN!FOX3ir8J ztgyp!l%~(5^T^kg6<=xUmc0i73fIT1w7fS`vez9a(ji6;`~u zW*n6|u!VbzMQ%-;Ho`L}3*e;Q7Yr6Vhfs)!25X8)j|1u|x5=WR!3j_N47H z!y1B^o&nZRkPdDiPLLkw{ZqV`4d)2yE2NRa%q+n&HgS%z;4+JV^cYt9c)!I7=g$*9 z#RP{0{zU?X8*8Lj*LBaYZETI?@!|CGQ_MyBA+e9*7Ds7Xgo9pi#{U+f%N88cj6dXd zudl5Cz`tsJST|()F`L_I(hL8D`OS%WU+=QT8eyb;@{j_n2UbesjQ(pWU*~d_h@D%|~xTOcmsK6?dAKb-zdvh5V)(=9hoankwn; z3vKT%>5S>0OYbi|K2mJK03!crEWF}?p8L|g9OfUbG)KY?iY2XlpZO`PL+9sU9M;NL zEJtvi)}NfSQ}c3tM!B?}5RXfWIZ~ilnO%q z^T{qqKegNGWO+_Vlc?gAL>5!CP!aEYaT8f|ZQ+%j723Uja<7I>4ZVU9X6--`^NZM^ z8iYxhbkALNdTE_FLhu^@Q?nQi_w}?wquzKJFVH$x$X82FiuBxFHp{G?Rw9YG<Xa4N06v8xzx?00Z-ho1xL_9IJ1m{C2X#v53<_{wa=Ic$d1@pDpy>jza zEl?TI(*v_qlr@u?cy>_Jty)X%S4xhyKE8;*F?`t->wd5+rrUxx&}wX2s%(@Ct=dX1 zfh&+lyen2xBoW(tW8HkcD4|3TU_DZucHJHG{sd3icB4BXu~s%4al0MR{xLg&I)i;d zg@2-cJZuGAKL*rj%riqW;rTqdAhHYKVas_DSxgB$ARWRKaHAEZ0qxPCH6yLBc0f_p z3#9b|O5S(mP?T-i{Jib$CP~b&WgZKE_fzkAcTrn!-&2!_S8$P}f966=#$E5QB}W9% zU9n^%ed&8(mtd2;+mK;bJaJ`-8HzYn&syqW(Ve0kFikyx;@bo{NL^)_FH=Q_u>)K% z@Qvs2k8CUWYbu1a9Bp~`y|mBmwwR7PGic}?P^yTpC9{z=^Ildn2@n&zM>CUtNK#o} z!j`{-tjkYX%`@pfeL;Xp7kr9-7u*LTMBb@16iH{x`uStrNYyBhG>Ap4dl=*Ug7#vr z>Yc9Y)&*79DvAPtm2RnwUdv^;eX$#QrIcJ|e)eUr>-`=zF^~Um@UtWqRJ+c;^l>W$ z*b2{RmD^3#ZbXYNe}BbU#06!*{gPb|PksBmiw8sd>DVsy>HgSU&A>%NNH@W{bQDam zC6ce_O)$aJHo?D;e;@B`g73+4j-4p`oU^JEx zx=sb7nJS{|Q~_Pp0}JMqk69lRA<#dMrk_!|_gk%(3Duk5=vw-5dKnPpLI4eazl@16 z4#*ITj??o7WH^NP#+_+HJQ3dK5Vu~g&*3>^=J&^+;4$OO$Pefsrfm&<-@0%Ns@2wj zA1pX_?ln`}9~>1SYp~BRU_O$Yr4XaKTB{A_>y?QihfO$-=2QxM*lf4td?`*h&A^2D z9V{B$4G&xnI*l{htO?OM=nRhVmOUgrAbAnZq_fwQ=)mOmx_zoo4~*HThIDqE#PGlt z$h1a3Jg}7%0AcUJMipLlqtsv%mV2XayA67z6&ghEToJudDm$Pq#Jp+U%{SY()CC#7 z+5K)Ma{r4dE7-iPYYz%k+HGs)2nwvqy@jl~H}T^FgJjy)TkA4{lW!cxtHEan1(rfZ z54}NrXn92ys_2IdDRsf-IQeR30zMr-kY)#%X@M2r!6~e~s+u}*!PzEe2Q{vCFKkN0 z^4beZAZMo+%i1)=y{1L-_RxvkA$~H0>bqE6{=|rB<9bF+n|OAxi3#A7##N~8;MeCv zbXD#(s_hQd*3r=SZ9}ZLApQb@YvEo&F579!fbnWLU8;>IPW$9|9#-eR$}E}^DBs3` zKBl_?MO-DmLpf|;jpem^8B~XdB@C}{AGf1w?pd4}=EQsDj`LGKV5H;fMFHR%abc(* z(Lv7YEw)S~5Kb4A@zLWbF^2RQI)+X{iL!nX-m!e%&T-a7{!hL_E3ZJ-6_}u|DRji4 z?*QxHjZB=u`l1gpqZgZp3U0`K`e9eq&_>tvf105V8_Q0Dlx%=qO4i+?%dj%(0oi5` zn@P6&D0^#{lD)i3$yDB@E>p=e1l9Ac*fG5kH;~E|q(Q)_Zn)`EIf4qMY_hwe6Iq1NR(9vt^PP6T97U1 znteo-F( zTN7L7;fQ%OOKt>|A~uYuXmB_uH3>I^g-{MRCR{Wy_-6OUKTH6AGXR_H+p05uH%#Uv z`vqZCSw($6_{~3L+toqpcLl=eq)hEv31=|1n7IXCXK>khsGcK*S*A>7!Vx(RE`+bF z$OAJx%uCm|(_Cl1(B)Np6*k_8nST%LPi5)lY~(2xUvg)zsmhyepzjp1GySp8AOjV>Dn z=NErjmNkdfe({v*=2pMfKkL-~nm9nJyfV;Z| z!=SCD`&E>0Pwdr<<1}y483#oGJW(89a0`j3#c8`G<2RDEOKNIIwr_?e$Rx`Lip8R%3W*bLu*$sPBuVh?JoeaZ zbhvNzFJfuU6~XOwAR|s&0l!77LQv%`lV8sw?)k z3@*vr5@+3V*Wy|r?+7e5pelt!PjUIW_1iaD7{5R4@2IV%CiaEOP=dIHiA`ti{+|vS z;$%ue)81&H;mG2T?8g9731C#p0t_t*7?Y2>D|11a*OYE0i7^MHMUke?Hw`Hxy+!;W z{?Oa}T|$1hyIuf?%^vH$5pD^cr>>3H^xw@~Ax_$S_4D<>=KKE-ZjN1$`i`j$9bvA* z<9og4BP-|SVoMBUVVL_(o1A(YffW92Yf@slFb=-&X||!M%=T4yRIl|V1to8B9bH$G z`d=k^r|-Lp^l>Wn<+%;XNQAQ6lB?=!J_64}52x=NsxPUug2gh$&O|Md;M)BDAQ$`6 zvWQF^88zq-_dZ}8#iX2_5RT0l$TzZ&v0=Mvr@)EIUCZitF&XpzcAHH&hh{lsfYWSX zJGAiXt8z6^OA|_(i>3naVC%7XpOz@+6=EIhaCzoJJ#!|9BWqp9W)TWC;nPEm(z-i( zc~U?YWlVj$3I0Dy*`_3tZ1<`G?m`8tS%s9q`!9qYFWhU8|Hfp~?28<4eYxgz*54$H z8?nD;tbbiUN+jO(!pg*b&#tK4`w3bO#riJygY9-D?zJGnU*nbsKkzXO(D!~)8PdPv z1x;kzhP+QfZ?KpowE+^DHD#&TaIED~&NU+;$CO`pQGM-%mNrZyRKzZzZ?`+EVrK57 zBJsRB^>GlU4`QBh+Tw_4NtsQeJ7>wjxrtveR=bmLWn+SrZ1y}KXcI$zpuy!=5v;`( z1*J75-(td6;%DPU3cZIqVHIl6QDpL1b$jCBUo|1hq8n5_CpZe__Haz+Rvbx;iR4Zk z+mGw4HMn-KvRCtB9_`dTUf#3LK$F>_EeM&_C)TVAj&I(CQ)C}a0My}r^cGt2o^Z=3o*aE9O11^tSP zwnqp4{AXx@t#l6Q8Vu@c$ySnNvkmI6GT=#8k9|+m6|PSUFn$5&XP{$ZL&tSEZZ2AY z68DC=Ks(q&rgZkGWfjrT9g_|`hN1Ktb0dAhwze@xP`cJ<2ZgK17gPYvmr5@+)uluE zc@%48cm49`x@&!TRf$?R5sQ-A`uxuX?H62T`}*h*1b#l?%34!LQFAMEBg9)Z zeX`=MUS@#2pb^FEHp}m{Y)Oc$if%yIMch?MP?b{6h0B=5`bv&C%A|FI@>0q%DvqN| zdE})Or)(L6R#IMFl5e+|arBA}99H)7UA{E`aOjHqZ9NLYsM+__q(rDFd%?I+;GFlD zAxy^ZwDw(kf{{R1(6}i4AU0V7bf6g8Y8l7-E-DviP&~Vc(z1Ag$T$mBPHl0OBuNEl zO?<8=<|<$A>Mo&Ate#IqLCa=2`65|ED}g!l1X7gIZqA?M-0Gw8 zP=2lH723UDqj$Ik8*Zv*vD3nY-(=z*B*-_?2*E;*t_QxzTK z!!2q8sxYBHxh)Xf+}-^6)Y##fd4`KgOEA7|SvM|Pp)TDHKuR%{<$>wc8y9-m5{Bi+b*N7){6|>ksVn6x)3*i0~W6wP)01&a~{&i9xbaRBB;xYov zhirm7rlcxt0})R$E(Rr8&-n6Q9#DXL8VpXZ^iF}s$>*EF_W;p#gXnSL8lQB zxEm-K4V~gZT~qBt-N&x+z>nEi=!wC351NnY6Fc&7QlhL)bpy~$f~Z{HDaY>6Gl5OG`|OB?~|m>x&? z#Vm$1b~&=KHFg*!E$fL9i|gbbrk1l#;^RyJbmUQPkuvKdSYWVN=jJaLTWaBL%XDuWi-LtH+Nt%#C36`7fS8Ns+3Dx8^-UhvvmbDSb~tKeyo7EC zJA&Uo<|yH0KO1dvBvh80FVE(5Pa(1a2VS+eJ_BRPr7*u<#!1w#^3$g;KS90&NS@th z$epciHg8(Q)U7_e1sb2qRXEjr&65?WncZBkZ6m(#>eLVJmTrk;b?QHVwwpE1rP7N0 z^896WWx+;J%D<*2W1=z9^L@w8n`UmEpQ!1VmY)aH&NYxQ?c9rV+gNTqNkLcB|2gvH zeu_6Z0cp*_o1mwbi^paD7Vvde?q5WeLAi!(x3q5nB7vAp~U{9 z@&8SK8-X?C#^>h?ymjLG5;U?NmQVYhMgH2W-3?gnq3a$sX;*t50Kc}Z!>&-`)KU$q zrJ8|0pakc*L6lyPu-i3R5`DQ-A&6^~$Lhc91ro{%FYTWz1T zONb_)4;Yze5GEqjPF+ODTZnulJ<4LhzDGNQQ*AU$$l(|CKJc1~5OJ(AcxSdGuW}OY zL_-%i?bw|KBH8*Zinc_86=k&~*t>+LDqVi=(;3b+>QH!48VBT z+jvJb@$FvTqhHZ0fKZqq0N2J6C*9yRkXjLsQn$AC=XEa49i!JQVzpfJr0J>6rSnL} zY|_##5shK6j@>D(Iz}?4G)`kadSN>+R~`HrG{M(EK?l-HEfCxCd++4?E ziO_mT8{Eh>Iyk{wh7iS0Sq=C;G^->vVFq?P#ORE>GEATFOiEr@xYd*y4ld-l6Y56f zHdrYjl$MsdctRc2W1oTsARW5GFm;~wD=HIIaO^VeUw~I%!v48{S10u_AFo=U53klC zB*=Y_X+%OwvjQ+R7CemB^nZglm`n0aQV|pkBSmqBkcts6$}~5LX|5UgeTw%vn+4!n z^O4_B1Fkwlh%Q_l0@`#lEYC>l0o=VGu<28S>DFC(>E)BH(4Ukj`DnSr;xwYKe z7+rU0)p@fAzoakP2KJrCZAbso6$Aerr?6$~G5S#|GA@fMfbIxq4ry(Sj zt@uL7Zjx_KzWggT_OKuk2_R8xkf=+?;2Qu;y}9pcNXHsR}`{1T9BZe5O&Dg04P?Wp>)ZLG!CHKMu(fX13N%G>aTuqf4FI{ zz0b>I`_nPD&C%4*0Up!MNxte+Ytme_jN+7-M>%9sBclq{*35;S(ZJ`= zXxevzY_(aG!Su;qwI3Z_URz`RO!w6(;VM3%bowEEO6*1}1Sdp*#!EVaw|jf=$qzHs zwYLj2t&UkclVtL~@>+84g&$($v9}@0dtcx!rBj#0XD|enK)EP*&!boS#j;#CqlJV` zuhh2e3CTB7P^IGD>u{ke$uTb^k=P^t>jwM^XmdR$;YuFK4PdSoa0{Y5pdHZ*Cn*`j zx_#<~{W@TRd6W)Jn+)O+?N4#vgaogM4K9Yv^Mc2Eh3fEh+ESHJIJd%?6{|j{WOe< zdmrW2uQk|r(U!@N?iOm`kjs|Gl%ANV<63NP7=0I&)=0R3jK`yi;uw5RaKksK@+$>G zRnMcq989qfU$B8CW>=S_Rk}>Kedbk~1poSso!Dma4p5W&1R1j2mv(DGD8RW^ihGW5 zwJ5M6wn9o&R_3-!i`KbK#)IArg=Sfu=x;cj#3Jp9$J`Qi{Iok*tc)S$RFqAQ6f=3~ zxh8a)l2h$n+@$h1{$GXH4r=D@4ACCm3`6R6#4_=@+uu@?%z=53n!^0n&1|N+?^|;# z0C21-^=(72Sar64wO(EHZjwe&@_zps;=t22AC`UwaT;L%>`mSsFROs^B@`*5J?5>r zO;24_BzN+GRhE5pnJA*OPfBeKaf74HbU#dt#1@jiF?R&CsA_DjIvA0A6MJ6OSbeZU z!an$LE?Phn@)szY+J=e0&z#FF2@5hZm?Nx73%Iib91$Yg|isD9IT(cZv*xU zqN$-Mh^F}zh+e}K;Ygi?(4&oQwTE7a)I~Fe^G*GMm2K=8#r;pTt}%MeJfFOp1w-Zy z@)>^ZRuuEm@LztDtQ*d1UT`hZb}rpWq1tLwxd=l_eI2rmXW|jt zO(UGG4ML~WZBJx6o@ng2X0F$xr*XnA*Xzg#G)kP48Cr@hqZa3F!DG>9vDS31aJ>y3 zSD<3IMme`&Kk39oiwI?o_tGKJ%Q3Uw1hL0*@?fNZ-UhjU*P3aoSM*~l98vUR>?GOx z>Tm~tY(7tkreI46cmeL5bOP?A$CJ){M2&1EoQIF+BDRF|xORu?kIk=jyZMBVU zqX)GWd_?VFo-5>a;?yfXq2!<|Lg{TF3Sh_Ntvn7b!;?d{f7y+RW}U5cVWRLK*Kb9l zd9|*H|0KcxOh7cBN*#L&x${Lpg6vm&H01Ir5jn*sL=mRLPuqCqYW$|44(|YME}Q=2 zR3>@~6KNunYM^(FjRrtNWd99)?(!I&jJo~brO zmUaX3S}T@HV7v?pI4}-@0s;aLaX2dPy7YA1QGqlzGv>S__j=G{aT;uwGfaRp z%9dCvu=BULbFd<2DN}km7!vE#*DVy%as=yY*F1~+0U@x&x1dj*F7&1h@R2)giv!EQ z#iZ6{`y)4=s<k4SvbUp;!1~c-GuBFrH#^#IO0M_+3mAtDMFT{)`CBD2*My z(^fzGhgQE-T-VL9#Ta`h5}MNDYcKnBvd`9@#q^?eibxfM#S@Huo{SJerWK4T&zH-i50b065@OBIONpm2K=MhSH) zszOoP5;!u`#Z^haD2GvwB^PLqk3U66M0)H1uLuyc%%b`Mt4R;bJZV|~6w*>_2v-e^KW65My2x+dcvJ}eM2@NA{FkaKGY zVgrNMF-wdFnWEPWd`wxRW4A{qYx+N|bldptZBxk)$qCdE1~nQwe9+Pu$B5y2@G#dD z&M1Kq9R$^a7}KY7%)#0AF7`ElSGmg^Q@Ej!lLMrO(-v(cXVnof@vKD|-O6pxz=zIQJ6-Io_2wRpsix_E8?VP>`3 zID;W1t2R;8w&<+vJE2a9C1x^ENAJ^6N(Kt={a08|;cIfO7C!7f7k5@=n!@Q8-Pc%= z|Ll3qny7IRwd}xR_yuoEB@3>i&~7F$wy6pPQdJOAVro5;i6!T*miz8dg;w8r)D*o7 zzZJhPqcaO_1B%~W`Lc~eANl9%cjXI{;64?x|rD3Hk<6^P#Z)e zMgkH5nc#kn(QxDLm%eLKMd% z)%Ozc!XI>i!rasH9YYO2|{`CKqbO}KS4~(@eQ*ctSM))4fNINb=qPG7900&qf*wdk-m>9 z4J_jk1z@xSwQd})2Ueyw?mbtlGKBIo(mJSMOPK-tZ6#JzgC42T-pFusHZ!PqvU44< zkJ6W9Trt=H(bIRc+hIlfSA-xHfETX!zR5jsp#}I(2X`r~5v|L(cMVl%z+Z^g=l;;x zXHghixQi$OENn=TbJ$E@1Eft{{=ki;0kye*M5vd$&JP`d5kQk%#}G*XK>FhSje@Tw zSHltvOO^k}%Kk_Epsy#N%&INXcfl3IX7;DumWR6<*RaDu5uDNxHdKgYa5VQWRoKZ- zc2IYGnNB0lwzDDMx#EH#1VbapYwk|Encrp(tIEc?cgb=72kL~}6hB#u!WGXHnu~dx zpSD5Xl(A1KqIxdR{RoCGYw^1<4v)|?jKkGI3JO8g&zSej-xX%+*s9#e)I^paXFt>{ z7-kKd;@fpta(EQm?q0Iz1YxPFsF2C^?+v+2Lv{-;{fOxbEdo`>qKc(YoKm&cvA#?0 ztAbf^1<=a%U9@+u^p2|B&oGRVDOIP}vertc^dB(wF*lx>tW7BaXe|*;Dc%Kt+U;+# z#MUKXjO@ov=|ie;fFHNyY#qh3CDF!M&PGFQdL`T{onA#=TWor(mT0;*cZ=Fw%}>@f zm$9gCOr370F?E^-M`~_CuaF&(>nL999vd^NOm0T2uLP)|91az}Xs9r4c@`^}HDXZK z$S;4rfEe7VzMmDJOY##CmtFGpX?T?0vCF+gNtqt-Xd{!gU%8f;)T6mkb}!Q>bll1e zswAUE@+WS?m+;V$mH6rZH%>soIY!KyL;Rak-;9sJ?+!a-7gg`Nc zEm49E7}T+ttJ)Kz`;D?Jy44i0;RnoTcHkNvG~&f^C_6C39K^EOQC@-=y5+~qab?M7 zuj{3cAcajv$J78xxIfPVTb9IcMUluo>bl~njO$P_Rzyw#FAEp!)HsNt#+kDbYc2@< zHQ`>WE1<;~f5@pGsqgF;9L{AI^9t%y7p)Rbu6wCSSMp3DzSvj|VkHMX9d6AoeWxGh zjN010-<51bCng7(@_1;8)+L5mpZloDf_0*dLamqSh);y}mJp%6?xj<8W6}gni2tTv z(V?f5a3ivp>G(Pq6b<7aKBZgNyW5(nOT4!?5tlu6ah7~zdXp0rx%Z|t*Zv{-K8Th7#t*LqAUzbz0<+PTQ8vF(u z6;$o6V55pS$S%22<$JP&XVf`dk*LR+tU3;?)p1^)b}!X`hM}%Ig0kWt)h~RA`;)Mz zWD@ppfr$Kl7sYb<(}qz(yLXjQ<*{GReHGGU$o{&%V@6!TdOK3P1$b>KUe7+<^D)3Jn;7~1VEZq6h3e-K^ zJxUDdH+BBB`go)U53_pwymBf?Lc@U@%OiXh$x)b+F3?BSZXSv$KD}^}UIR+=_KzQc z^uTUp3#kkG4RHd`D#28$PK~+tLv;a!V%>`a`joRef##k;Bdp@8J*SBFOoN7{*bu_DAL{T z#s3m~7)Szy)CK(t#};6qYs@JHYp5e|VXWFzD4p%$*E#xaG;hN-seX1*pFH&PG1k?T z&GtBuD|6~nJ!{cYx)@{?q%Lw)A9P<4`P$b7WaSAPxM=$;X~8JUzYZBi!L`$F8Zhqn z4dODk^5Z&X-0vPfx=J&Kft*&dgX&VdchkyH=-KhKd3NT8tNnE?mmm>#E9FQ;EKfCv z&0wP6&F7+osW$f+dYb#6>aMv2S8O>;I9JlpR0kN9Mk%BFkr^x#7PR-%1*F}pUMF@g zg|xY!Rim$0qc$8j<}3kjaF)yj)I2$$c>$39VG&<{3iXLgWm)?e^S%SlmjI^V>wW2t zmH;NQBw&6^Jj^5FVQv5zbk z8Z-mdc%Of9EZ)|hCEJqPHg~+xmK$VtXM~U;8g8ziP1X^JjE!7In1;6)l4*tl;<27P zw#whdo;_@l3y?@NllxgV>y^(Yc!QC>kAGfozW<-VlCQ%9Nq%`DX@d`B3s05F>N8~1 zl;p(WIj20@(nK7|TxoAm>9Vgqbc%O{_%7Cg=Lgt^fSM4UUX=}kcihAc#!O%f zVY-V4tQ)2>!~{G}Y`oxd*m2>2SX^iFxXHERHu$I15G)gX2(ml_aq*bLw*6t+5yRI_ zI6p~m{g%oO+~=)&GRuoDj#0OM^q)e9alN{9ez(=XiX!Nl4DX}U4fZrkeIbR&Ks3C_ zUQ-GzLS*zV!Fbi?K1o$MIUYcX@&2o-A5eLV;$b;>g4~JLrKTePCLCw7x^QTALSQJg zf|)Jv(}!;WoO*-CM;}j-OrJPyS(XMCRxOL*tdkcrZBDqi^2ioYd~RKK z{2!KA+AYKU9d-pVf16z{%)i)f9OfhE`+-A#<@tUvv*@%lGk1n^U*-QR{$Hgp^8cUm z|KIxW{+z#o3Z%UKNa#ZnpRr)}Wh(Z`(ponuVad5UNe6*K>LQmd!vj|l=W*|(G)e(02g zpYMO+jY31|Pj*4lR2sr3RkCWdcuDwQ&@#n#8zHylosW{m`czN-fn|&NL(q9G<$#y@?=A0+p*mtFPINwi_ zGzBKW^}|_3d@=40{LF?q7K0eh{gY_OfJDg7>CZMARX$@UU6| z3A$GmOF7S$A!3k4<+{h(t!$Ag`sFaUKRpJl;`4ywmg$1eWchGk;SmBzBch4W2xxb901NfBT z(7gJ9OsuNrGj!SR++YSB_tKvh>R2n?Rj(V8$9N{shJy(ucLCI4?FPaIAW+$vcsVR0sStOfw4=`4zthJ1B$rr%cV|0%ZHol43+h@-ctJ7 zU@1{kcW}{HokL)B_`0$1b(5v^bxYyv*233&3SXP(q$|6<@U^w@RgMwd*I41}uEN*O z!q?q}uX_q#_gYH5(p7JRMAQDl*8_#GNlR%&J%z7FgRjSXmExZO+1u~ZdYHcw9L+1j zUvO_d$i8V=gDEQi?$1l={_Wn4T;%B->*yTU9_I{iCv4n-Il}sO{6FMK;Fck_M0-oTk1?DwYl#5=Z5KAfhtx&0#L<23kXJ+P0cE}gXt2L7lRs&g z@n%?Z@qqwPq+Hppl5nhRnfLc}D=5cJ0D>sET~uKfBI+Pn`?fZBCllQ*eD1ry>Q{a( z`TlNc!22GkeqVFj_HLZ7!Wa;{oQkp8XMnw4Z%MUm>s0F~HKQsVV|@H5pKtQ+1C0Bm zJ>ewQHoH&vej@ua(I4%Ta-{a5j;RxTtBZM)wBhD`9-w@dt_1&3!+1Y@MUfxEqH8w) zW?h}Zu^HI34CXVBycOb!#Z+CtX~Ao>xc|jXf9n3 zO8Ja{qx9SN`San>h@^{bjPYtN6e5*amV7zC6`b_Yyv_FYgnzR-6gngqv%||qsF_0= z_ID42zl?PFhz^oeq1VM6^mMp_iD%(q#bt>Sp2ynv`HA6B6-CRLi1P0;CtdQp$_LAB z;bJTBi|#a1>`h_11OECP6&3YW>Fh50J!}e}$NA!`e&*IrL_(F)KS+_A(3fHi_B;N0 znXV4ILXWDDi@g{W>VDVU+NZ2|vN=dS_(GR*QQq^nx~DrNg=9uom<#G&_t8auPh05r zJm-v^bWr@d3+7HNLC&ZyQl)UzN&E~+U06lKQYUFouii&o^Y38tC4oXmJy<*i7HfG* zmiOtmGILCre{aL)qi=EqtSW`icF=rypp{?U8qQh@;?=3I7gH=iX%i+Qht zZAO^&J;34=NCqGmQ1ZtEP!o0C5J38v8Psoj@E&CZK>F(dWN$tMR3xH`cg?#QyxE?Nml)NPpc7DV6&#dY6A|Fhut9!Vq7IRU7zkb)DQ!K3CnRIpnaYlAv8h(^(Eo`yb z>*l$BF&4##=NYiK0MD_v zz1^XCZ~!oz_g3GkjynYmtnFlOQoM|}9WNa}&3 zg&d#N97U&Dz1-lkubGyl^gA<|VmV}$Gu&?Z3N@F|+b{AMRCvIACtKw`#maY4xt5|& zk+p1%B0G+bP|I!)S`rtYe;p3kI<)n-$T}Papt>QD9aQs3F4f6Q>jC2#hKedGg^TTsEAO5epJni5aN5`c`5mI2P*jr9p^+(VYmh~fIkdx=vzo2ouH(I%nLT)TX0alE#ryXNyI zPVM$G$LhZODG`!!AVg=zLrFFVqbm7#FGavR>E#VquRdaq!!Nw1GO~L0*H-VKQg$6o zW`;IV^B^UGe)Rva_x|B=RoA`uSdwjl1v7x)DnTI%F^L;HtrJL#D+spgX@%=)CC8Hz zLK4zCWa_vH*VbT@+QITji4Nmw<<`0-t&^+VI*D`ZG`_bK47C%9x=3){R#1oQD@prks{ky$CD=A^eW58jI(3 zDBy?h&tJXK@$NvA_`Z9x$P^Fx)vOCk=-!ji?kdPnJp?L7 zEJWirTpq(C}$ID-RI6L4rhpo8$Ro4#e+i+t}mEEY=?XtofCNq5_1x7hYR zP=0@amPqHtsdtoFf5NNS4YCw`kSOS*l1LT0}7@>*LdfcDX*i zk{UwfliPJmNUy&J(rf{i-pBYnBC&0zZ#n-AOYzT#cw2x=`M=V^L2rfH?(9h*vTV1(Ih!u+K#v-4{gBA>v1==5{q z=<}i)ZOjsir1Dp2+ay>T9T^#0Q%zmIq~i}m6FYY=d!86KcyBrI)i8#OGNYKJb1KKF>IMIaA(j)!O-mm8qR9 z+O==EZiL@^*nm<~O4v+3IkL`RC_;ZW?RB>BM0(Tq<*;5BLa%Lwn;Y{f(C>e<@*E@^ zqXP#qF=d)Y#Ij=}Yo(fj1e={Ilfi9GPw(7Bc7i0Qx=ih8N1$0$NJ*r1fs6sQfv{EJ zw0qceH_Y5;W5hX6v7oUB5@|FnOC~I)d4dMYUxLb8Q`45jgN)tjDNEwvauuPhjHJ

=P9U_>Qr6;pEPm|aKMd;+UVj|5_Xt-k>Ez|h+1|lI-6^Do7)l_0K^3~U58O|AWZC}Jib^A3;osqrLo9I6XJ@*uB zV3u=`v7K3_0Euu6KIewPZyi}XGh6%W^UqMqb3ywk503X!^!&v+-o@5sg^owoW!C7M zs2_gsS%P;+5vuSG>^Fdb`XMg#A1_Ai&}`i}3XP~jj6wSNS z(X$ZTxx8gTV(8B052!0xkk1usIAuYM>di_{Y_HTlV|*^(r$U`7^oC2VkR1m>E;Xom zQhOQdQ44bWCp)*DGhkuYi%l~g6$lKz?#XSqLWk=;`@KU-$I+|b+;JPDc+b6(b?rs& z{ub>E2JH1Gmxm+%8%PHuy<@Da%ee|Zms&BFT~6c;FOX5KY8yI)LA&W}k<2>_(yhh7 zwf+p{3j_KJ@!ksh{dQHfEtiaIX3}`iDcbOBv+y+wc+fXqzuM3DPEZZ}M+$pZYga?B zTJZJ_s(^30N1N6=g|upV_E3>?(LKaNv~ocaL@)CN{d$FiDu@W8zs4!;swA5wb6e?N zIQUycUy=r_Bs*w;;}y0KQ-5?eL9h+HP9+S5#l;QKLGNk$lRK$j3y0N3=_M*0=^f&+$tM5*~8;Kd7;rCCsl9ZW;O(N;yfWNR5ND? zEE73C4^9fo51fcX(?=yivuzv%!CJOl<5U=M(Nu_B#m{}~{CPW2^(#71)!D0}lx`dJ z|9-QR?=EORkp4cL85i15Xy;wU`YQ9g>idv2R==s(uz zk$p$pwF-~yJsRAo*sccmnq2WXaTY6zroQ!-m& zE6~wPOYJwdSBnuZchF|;ajUtDSl{W~aci;*m%!=#0bQFIC6^H7s5%ScH8(L{8$jiE z=*>Qr#C?fOwl(H3f|$Yg>Wkb!0Ku=#OE|Pw$y#&9ryj11p$XZaU~t>;`+jNBH6R?1 zSF6vxuO^;=9=a!$zc}R2)1Ji)7kYcdsWPua8-_CG2wH#)#rB@RIuY5UTzY&lTC$Ro zax;0Jd7gz@Sx=E$ItnhhSQ-X)4z4z=7p9RHUq$)8kIy2i9_^VHP`-D45TW1H^K3;5 zg+9PymNrt@^c(sLUN2}7O&Th|>Fi8J6JR7g?|-JoLcWVk$ter~KA>-#qf6*nDuuyvaai}H)-JM zpHa3mOH?5*YiGjw76qYX6IKgmf$O2uk#)U&;W`Vw`T(5mU?@(R%hOcTxpn zT~`s~ppW|mKjT{Y*imonoxDW6uB=oc|A2JzD6E;N>*{rG&TNPTt0b!95uXbF^Z7mc z11Fz1hiu)}YaQX+3@mOQilgDGr;}fO4F}eFaz`~N#pz#}+Z(#VY0AHf66}XkFR(T7 zchHq?2jB<$XSN0^l-t!5!Y`%`!so)z64O*!VnVyuMYRQ!7u?;lFJkLd)b6%u^yqJo z*j&OPG3F6s=p%@uG<;@eCO;NeOy<&}Xf9<66-dRfLPU=@qGj?14#c;oi$EQXOgNL(SjI_D?-a0C; z?9qGc-K|r<<$G7T+vUDC4yr>v`~IJBL|QFa_?`0UO>UC(JGX1?qIj?DHknm~49<|K4%U{h&)iK@wkWTsaqid%9p#_S>~Hh&e^(U#yE9!2KHp;fx+Cn@9aa6BJ02POb2+PB=H_e_ zJ#Dl8Y>Q<#|7htcQyLWZT4>EkYFo5R_h#xBeBPCr4u-I*{I1OSg3otn#uj>RhMdgM zg0Fgj$`F?c5tpe#+?@G{$4U>ZVA`j!xW%4i!l`rdOQDyp->d<1mn%|?( zn8PNL>qF+Kft1g(Bbd(j2}sw1Nhz4oQY+WGU(UknRqlgYGyP=${z!hke)9W?rJ zn~#R#KfsX9r@V{vjc_gFBeve<%kVS{e7lq^FS-ob_uZ3PuPgFn#3{-J(T6@i7u6KV z3G0I5hhFB*Ps=NGs3201{yHg0S0!zKe5a@Z`Ag+a>X)P>?xLn^nY*a%T2;Qy56IS9 zRQykCY{=LHS!^;AH$F^PHo1pb!xe+gFcL$5D^@}Vx9p46>rB}8&q=vS6CfeUW8WDd z3CFfeYHCF*%;pKCU`!QbAfI^eia;t~@ndeui0+IWff{~ozbk)N5W=J}-TrA_13v#~e6Nwel=}aprTEdIMf6e%z77fVPUS12WpdAi+mrLzbjV9C zsvQ5Jq@D6|^JJCf;In{o`XtAau&n;;vf*XV1!gd%^rFY}wcUPyZT~*Q9>i?KE=Mc% zxely$o){N}_+rXXrc=6F8H!5@@pldEwca3y3*QVe57jm%KE4%)M*7m?8~$7(klXG< zA%IF~N4?2KZtaGU#9K_9VqfQz8crG_wBS&k$b_t-m!HtbqL;gVczUt#w)yP4A_-LL zkJd|U)cLE=KpFE`tl!@sjlcH;%tH`Y@%O&mbuhx!ti6To!4Bb?GzRRX1GZu~j;;rq zZ-g8v(uTdazOmV>SR-N?{iMH2?k>d!bE4>1w!n~w_)>lBZ#3*8Xa@yByVd6BK%Q?z zVG+Y@tj+#83i^p-QS23txY<8g;dJj;F(%u8A5D0U7rensCptR}YU!WtvrAAWX zB=4cLN#_mEMBY9BEsvxFSVK>&lpjQpxLWqqx!TaNndCf_rS{Lz`8!>)Y zZJM7yKkAA$Ghjz zdB8v`=`SzQ=4L1kZw|E< zzx!G4em1;o=dL}x+rZrhcb9M9=xx{-Tz7EFZg9O>Io5o5?(jC;;jU@tF1|+g_S^X& zcONta+%2sn0eW}a>D}~Vi|nGD8IA*4-Qs~~` zuqb!i<@HNYM2#j|QlK=E){ER^OY8u%T4M9-pWc<<`1Ed8Y=r~&Jl98AZ~t@q^~s;l z2?{oPAl_qiUcATj#o|3C)V^3myyvU#>U_NC_qp;xV-N_S?tUqCC$^%G3#ep$ZQCuL z@~BUAujxqgm=ks;;*Yj9-MS2^vkQ{qj&m$uUWObeA0}&~{0<8u!KT1lQc}O9!W?;F zR^5q?;wm^o`Lf5_XgZ&r$nRR3sQC1h}Gr4psM*{Bf+#Ye7JZ}+G_;z!um@& zGG*3R>?x371}RniL9yd)OV%W3c0jveKGE9QAUh~r77CxO-H=#AK79+DP<%TcBND~F z;rNli2D1djI&m}LkNYND~$&dqppFmOO^>=lwk zxN6!|{8=q*Fx7eL^f~Q!i&_nYI|C5*j~O}oJWx>F7J=g6GosSh4#-JHx(bj0pSI}2 zf$MY6zv89ppG-EeHZfwGikI3N;#jbnzY7#cNb5Q}jzHbmK^#q@^FiNxpm>;u%I_%u z1Or{6((HA7%fK~u5`L4|^q?)i$micz;V^+gmDuOtGi?f5a)e?q3TMCJN%}z~V@-d^ zvF8M4`3yetJ^bBD^ZBP=ZGHL=l4q_Xy_G0*&iX2Ogz&2VKax*a9ewl`RV~87VinS> z;lsT5x^Ae_j7jjAd%1^RF=(M0zeJxHlR}|JKz%y#T(2VF$O=}PM zon51NnH_B5dC@HAm1aqCyRo(DOy)jhNQA0D+uzLd+)4eiUBGbu&qCdR?pxEd+b`$a z+T`?_UOl*v6ny%d?<8ZeUD)H+q{^jcYg5x}?EQVJ=MSjov3IJnKUUGg$rb!+b+Eya z-}j7iuUS-C&5noWCTl zp?w^gt^W8zXC1}g0?Zqe4RZ+A+JEo)tC!0;b;=6dE|-WJb7a3 z!t#XJtvF%v<5Z1|prfTNO~9srF8$@+3aNc0eW4G38h=qQM}tJi!Kx$?hKUfJ@$*^e z+n&jFZeGM_gnRh-D8C}LQRt%^>7JVp^soMdm)B-K_RP}SEniS$9o?taGPP`qydybt znGSJ@3*g1EO*ihXKTvqUFHre+pFBsjNdp@dZDsS`GF#}wGIqXt_w9-&|)xjuQw)Q z@x623IXGjVg5j$Ja&OLj72CIKZ5OxZ@lGCvaUeZv_Y<%sS6ugbt8&h$r_M6de&rgQ zY1QNG=9}Yg*&i?d0KgH58n&{L9i%q#UYi8d^kvQq=ZrOnZmhn_Tpw9%)0lzrlRDV@ zPM@7-CGLwf zyH{(O{`0E}R;kGM$QM=`QSC7+-L<&*c8Esu%GKYf;!>3vV&D7zDF5zw`A?{|sQ%;+ zAJv0-@O%DW;yV> z^)T{w>2c|B%Wsbg?e@WHW~8b75|yH^g>cnCwOKM+g0f#oez_fALpGZ zmd{^(FSEh31ik?n7Y!h8xEL6EnYRS>F7J`R5L0nr=&HOX{z&1iTns1k-eva8x1Ruu7~0G!lAHv#^?Cd!-m^+&&h8STXY!VV&=;nq?zGe9GAAzdb_jqo zM=XiT0^e#hGJ_ZTR>PC&ztCHaS7z^p-f93dnG3zuh-U7(&|3|0hTJ_1_g7<{S#hDa z*>GJ=f2MerEE6C46x)NKTP;-OpaNMR& z5YtAC5Igxm7sZ)OO%w}c+jPVCzkThqxfg1Py75KJ>QmhmSX8hSoeC@xvpbtVp!(%s zW14m)>zKx5NU{#!Dl~V^^6@ACcXNEVDI0+IL8RAa`M^6>i@CO|Kp5n>8;M3?BSf1^ z$!s=TTxF1|-dyH9$qVe*k=hNWi}3FNzsDUseq3!IZO=h=@&B5J-i1=aRL9HrM1fYU z$k}|K1G;x;w|sz*B`t^6v^2OJBy_XaaSPwn36)#fM$Y}_Dm!5ojo&$Xit1dt>}sV$ zMO@B)$kbW-rm@(rn6z#-A=voWuv;x6E9Vehc zX;5G#xTWhlR<}9=Whjs2Xca5+fhe*As@EC_**M`B-BUqpf@3DQ5ggOOjo=v9O}yvF z9mM&4x)&@=l1Ye9z{-wvKp%n5j}2`Gu5-rG&-ol3LmH2~ZZ!{lxyKETc+a=FcZ>Ms z$DZ2E8cJ97iVBLhq6U#W@t&`RwR6l~ymwHyv~objNtNVJVyX1UO{G817Kd|ocl;2s3Ehn-Xr}mRLr~dV@ypS_ z^o61Rty-rXU7vHuZIBf%0dys06x7mtKycqP(zc0P-~4Hha!)8T2AYlbufUaqg83So zm5%?gg1b&P%IqZ_cfQXaaB?>3pAc{S>Xl?*g9Ur>i3o-CvWDVWhew2gHC9!&QI+U3 z8`yr;R=zr{I5&(LY1Vtt8c}QNQLcp33lA$euLyC~{a2mJ4-nK(|4ism4!Nk4JkpO9 zr8H%WKlhglNFoc2Cy`gie+ULbmGszYwHENApA*ZImU}BbbdIG5j28vIrCK{?-P!?H znqg{tea3(l|20cZb^M#h2d#hIlO4SD?EDtubxZ=CEfnkmIoOUz>3&>w@ZX~C{`ikW z19U4{xLs%LlRj<_-Hzz3p(W&(2yDKA|sZ2f{Y77wAXJ{o8`Vr zA@9o{?ZlDznp|H+m}$IU0wQOYzV1sxPrO%$fPt0VV`D&RX?hxK1KRoP+K_fE^@^h% zqwA^s0g)~0Bu6jVUkH2{x)$(g{UKb9lf}|KG%F23%2sduyY@`fblp+<67A*owA5p5 zOSf)S-&ZhNR(SHf`HJ0**%(F+{QQoslZfsmgPe zDwTS?c@tFg8mhd~ss!?wnKq2~K1d6hI$<1+Q2g>?hw(+iI7yY96sF^W8^hW9F~0C; zt115-#^wK>oXS_v(2HaIhYDRT$-5C**GrD+SYLvF2{vzOR**U(*J*X=bh8j`8~W3r zx7ma(nE(N6RES2{a>6mux6|EE^C*GUpnRnTBMTc9wCvzEGp%`wh+pjgeTSC!3WoGl z?s4%;M~HO+A#Ubfz{KRg;?Sa9H39V&t;)mKLWrXmbgdrcsB4b)7L=zCf={J9ygZKYdtFG1V7(@3w{w?BeJ3 z)N05?Y7o22Xvm4$;jzG**WyEw@^`2fYtc?B^IL4wQ)|&qWAmS`(o<_uY(0RPlU#jK zpjz7V^<(BgY91uLPkrhJtoFH2vW7LY&frwSuqfTPL8-%t@QV;$81HIHH6q`*c#IDp zoF9+Ty{Bo0R2CY2yKvo7ga*w-6RRnBL!)nh-RLhAp>ZdNMN-nwOS%0GxjhF>VV4lb z4$e{7x!St#Z&*Rp_kEOoSY_)WmwfVJl_ehqYi4skasF8ZCkxj2!ymk2dc{CO^w-1n zOz?d2RPOj0FsqQ78dTJ!cR+qHxL9m0vRlTbCpZm%BWTa8v}bh4IfdoIU8y8yexO3VU65sf@%H&a9)xWGbY3mk2qPn`y1XEtqJZssfw1v`~L zAYimkjpp`@#`dt09yR$8+nA+3Kyb6K#^3{Is6l%{-`n+~YwpkmS=~@RJ>;bww{ooR z1dWyPEjrzr8ry+W(b;?D^2%_Z1vaRDFYFhxJ;QRpLMBdj|4>qZ-B4d!Z~lNHzuZfA zxd;sB{!^2ixh>v1KwZF(XGmS{W$t@*FKTUHLiz0d=6JI1b6BA{u^8p6D_fs?Ii39( zSMJi>OTUo)DOYDhZsupQD_xn(b1&beB>l6w;^jh7ZuoLd08XFSRQxZ$ZDJw^>*=** za$ei~*i`(ad#BD+*)fe?-24i5_p`;fzGx!qNX-h9p-`ks!>s=xx{+>ChTQ=8+-Vl6 z5B;fqFCD3U@9Ri)4QvbQNcX7asE*X9=O2x~%7^CKk3P;2fUF2s{Pu`Pn?kfI7EaI6 z$EE>ZBF(P5@kx_3dH%<-pxyGb6rXxSHnIZpH4)OeL}0oAxiyQ<2DFegTGE8GrfTvJK5!!iUxa`YI5bhN#wb-|96r=M5+Mx<0n{enyCJ z>7L2E^N*>_z|A)vfZcGTRS(!P@wi+B|7HjK`PoLu+@Vt=W{O{D%?kmw*p?a3vM0>j z2L~#_=zI$CYrA_UkRuzzFVoVfMF?Pr|4l7V(Rs8$eUajjt{Aq(r=v3J$6dhLGq1FY;aHvG!?XniFwnd)A>-vVcUm2Y_-O02`jys z;#3&rl&2${QaO|?0c*Uc8T2T1qZG!9O_|C)QJsR! zqTu~C_hzF|&^yg*zoSIE5Em(Xyl2^@L-)!yWK-lXy@bx8-tuYr3K9CWBL-&Fi%> zf(RH+=<@^+4EzZ(I2-E*K?O0JmeA`?xZ9C#?KJcs9Ua+Mbk~lkCxe?5)k{~Y|E$=I zyLQE-2-Sa9dA(6;r*A*c7f>Ka`AP1$Krsy?f6BM2(+3P#-2OK#S3Xy?t$L6f zTFXnX+g|FSKGX?cdZ=sHQkA3=k^gg(_j;+pd~z^%++>+9{3ZwU>A}2-Hm$aLhz|fo z>0W;I))pK*=S;pm+Of60kUGW)NHH)N>MLH$3^{Z`Y4uP(eaK5cq?Pv}C#^nI`bS{M z?K#>WOyK9aM{W)mHXWi6@A)4*#cTSEmp+Wj%jxhiRv&InC+=G!BUSQnZqMOZ*QW}p zqX2yvpvC>qay=Mo6@1_?y6zpkAn*tCse@jsU%>Y};QKiYAbqqoeK^SEyCR=FECptR zQy;oTaj^K6X))=+eCoK@SCIv({NXi2VBQ}}YrH7&G+2TC^|DDphUNT~*{$iu?d;VWnP2cC|2;G9ndcb_w{#Vgy-q_dPFBzGZV)u4beUr?cJo~^ z9Ols3=IqWD`}g^)N7;7ePC>FJ1nfpLM~=h>LfKqU&B*$WS2VxNOUspPXO%{L{L~ku zE2U+Q$4co>$rGy8K+nzWXyLZrhlCh@8?D0t&~7Eum2Jk_g%@w z%-j5L-nP>JW`e?jS@EqtObGPkbv&bLn7C&~xzO1xslfgic0`hT^o*7XozXHL>uSY- zh?dQHc^7kgiZLu+=K86Vh0LfrE85f>9RfCq!j#^~EJ=g5$XGzn+r*KajfFAO7F?R( zB1~!$onH&99a(Wm_2AE$c<*fLEaIGxA0Xr)OoQ}-w?Rc7+2?+3t z{{^ibG)g%yJ;rKp$6K<_jop*K;vO&kbS^oj3HP)nTykt>^7Jz^Uh)(=5Zt`VD*`)> zrH2-T&N?jAAf6#;g&hTFi9nfI1kLg}k}mW<96x5{LXtj<6^9cnIGXh=ZS(CZn?7~; zqM~FydyiDNyfcn)w)~g&@oG2&v-6((#*SCO%9-E`WjL9(GdU%T(}Hj2dD^~O$|pAI z`a~qKq{^fVHW&PL^ZWmk@q`bWn+>|cn>vbFXi{Ry{~uWR9~)S>uzeqYHy2rkl$)wY z$p|qMb%Mse{||{hAmTd8ZqY)tL{<|3GbC zjnBFLP2fCm9KD*PH%BoRZ7<^eAGFU(E1k$sW?`=H@GppXWd5>VBWG8#kpLRt2G+QfgOH*_Cv*Iz6(JJnND zNS4+#QrjuLa!~ahWSk-uekD?&gqKK#zY(cWs+>rLvZMtS4pvuqN2J2T)fHNtgb(47 z>I$!qRCu(y!ljW4k5yNwjZo0#hpQ`mMz8!XAJ?mAC3xFdJfc?$->uJ^J7%QIzo%C| zghTqOGO~O1%CGR0UWFBwGeK>mdLHIGEO*ePz0#?dx07|bj$M?^gggnDFU_xQ_BbLG zM+KHHNT!9{_nVgEd1vCEKcm(%gJLn~)9yR{HN^Uho0ru7Isg7A|9*#mpMZ*M`L~o2)Y8gBub#Uew(h z#UEjdN}-yS!|Wfj@0nNVW)@ZIV0ESF%BK<1vp+ts5R&fP<`B|s3tn#I0_sHMnTkDt z0L8gI+B_+T*^NVzvEm1-HL1Qli!30CT1y^TIQPs1e9JE4J#{wu2@K;bX6}7r;&%|a*e^>oX{H644^)oS+($}k>iKCSMd-XH15~}AoRoae~_UqO$1^mG5-1zdZ zM;@HueR*r-!H^!jFY-YAqTC*NAV$G{JUJ_FQBP)eZ$o35T|jW(AN`dhd?_EYa$Qu}89#rgMt?^sfMh`&G2zrW}DulV-|{QD&LU*+F7xc(jf<@ooj z+~@iC2Cjdcf4A|kgMT09-**0ek9Yr(d>~*6!TQZ=Sy%kax3d$`tY|{ymvy%pQ9Rg- z`niyNTXJ9l^ygE9pPGkY3u#g|w1mzM*=zmutIFoc?s(m5^K+F5EicIFGKM@gVJr4t zjT7PYwImBt@P-8sizQ*q=^#y`1fXh}#czL`F)}veqC}*iA@@hs#kQ`NA$7E~*oKtL z5r4Fm`{asOI22JNwZcmsE|{xDLjYlmAUu-WbA)VhPOF6p-GJ#>Epx4V^^=9-M_hZL2gMsKKP5 zI!0s1LDV4ECia-_ots2QW~L`b*5Q2W5bqBGYkTwgXL6^Oo5dV2Bl0%tY)#2T<{w>~ zI$oPPR+~E9no8W)T#ftVwaH_($-~Hk{w>Oxuu^Kdkq4f)hU@G-TaHSx!?{`|8VV>cwf3)p^#o@)5-5#K)}*NMm=DCmp#;i6@DAJ~Qo zdY+o2*sI#5`1Q5;_5GUFH(8L+o?qXj>YMcI>(T+>AK03Z$Esg|cK|*K@jA#Ua0@0BV&v5DTdvecn(C;($5PKF$0N$KV#0N|qsBF~@ zK44LAO{?mVHCz0f2KFP5jP*WiXOcsWjs9_`G`+3?>lSSj#rS3t56rDI;H~=W>ZfMg zQ-S#|kk>t&FaZOfX0$WV1gczJpyw$r%i_QSlK0sgi4GgXTT(Ub{UGZG=s%#>BW?9# zx(~kR5c8oAaUXm>vbKFKfR@=+Vh`09`j7FcsQCQxfqk*GuIVj6(ZOa036PqxjkTRANuFfhK%yD*h{vXkcXTJRDp5ThC{9U3qyB z3g77mvJ(70z2)}>E!=~g@pW6tLa{ZGIn9&IMg*VCE-eM751T6f%7<-tuj29^ShQa6 z(o@|}$h@OT*wL=%>(<+}zzJa=Iys&czg=%1I>C4fp?!;gAhhP`Jd02TUs%muKCMW8 zw%bj%&Rj^M90~Xl7N2jYHu261-|CzrI z@csl{ey}R)Vz&6dR7FK>FnDv5?);nyxyMc9+g8aVVyl^8%q!JhRys^EB)iMk#1A} zY80utZ5NMla4~K$F|tql4mVKLu|D^FUA#w29AVxYB%E0E_aDkMLqTK z$WddFR$-BnJkLF@i&%CCA1&6`_EG@oqkXy{Mc3sKZkc;|5nc;4(dS-v^72Jk=?k#Z zCvr12WR_AN1Si4V=gY4PE5etnVfgtP?!5vWeo_POC^K zwoerQCKc8i@BLqX7g<>*@)FvpH~vWb#@?smy{jD_ivMkiLxVaifV~GN{IA;Xmg#iE zmfW|5C$G&V9Gl1u-QA&<-iJwnzq!XmF0t&}GE(w7?t8iWQICntk7U^r2+k}}Z; zh-l%cbJBdd3NU}|cx&eLYpA1qpDF;}2i07voXUOlk2ejvdJZ^#^R?P{N>BOiy*0D@wX_#kh3#w2A!tH5>{vRY zi&kWT_2+Yo^8Mlw#nDMJ_(`F^XjxmGT+-@u_czpLe;O$lMFngKo(M@S>%57VS!Zhf^<-1dBn%gvJ_H{H3j;J?Esu z{GTmJ-<97p(>)^Q{1^fq$=5iB(qJg&lh}NV`NXo$I^TZ?k^NX3~AUkG?13{}{_6 zf=%Zh(@s9twE_^HY}-h5#R9MqGD#nN?vk8&a_vU-*&P&RSkP;M`zF9fJqT|PfDPoW zAYVZ}qcv0f)~{AZ$Kcz^Q#_Zb=EkAR${HMo|G-`@yzU;|D$|7l(RHIh?9;WhjYMqq zt5p9%l}go*bO+rV>9(Hn7^MM<3yVzPE;eFHVqG~<=PtNDev!XKy#h=lg|d>7Qg*$O zwq_O%2m=+E_*WJ`e@KknxA%G*TJmeVytd``)$&bS>!=|&bcZ^f?`)Cgw7#~pC4c+! zt42FFY1B8=7Z2TGl~tz6x{hUSo4f83iYd|7;dQHv`AwU>1J=tVL2u*qU;gDno35>D z=g#6;$8oWhXm?KUOrK)?_Z}2dINKiDb@Pr1xbBW+ZFhB%C(gBZN6_9~L3{Nhdxh2O zCWm7qdjqUGKtO24W6+o+beCx0np6IqV)~yiR5cVx=h2J-XNH?1}&r z(6hlqeeIcCiKL_3a^DvG9wg8)r+8(*K>={=^_jaot)S@L*FfhrUm|$)>CZoJBHyPX z(4sG;DGS)FWdWlC7%Mzp_3d~c!yRzbhSv7F?YHhE#Sq-JzO`oiJB!|F~Yw4d_YhfUdW{i|tFV%C+{Y#CG!X&EELz%GA`g z!~+zRTFV=dESGKQKB+sFdHLtwyO^~^njgl8i8|GpS?y%U0fP}i&H)>hA=r%1qAF3x zY;#Z;Xrf}eHf=SKpoUvb?ysGwbPrB9S$=Q#Nfmt~jdxw;u61}VnXLr}`cH>IoUGgL z09#(iUHOx$EV6JF{#Z4zc3DxlF{7$nY&vm$-DwN9h|1(XJ+Yj^&!pEd6&|p{vD}RJ zMO9OzxFyq=GE!h7Zwb^JeTuQ2d%Z`lP#u#ge{f5)o46;BREn1fvfvZE^49UTG2hmd z8-K-an;f&K?C~S)!p)D|bk6csAsMQ5$k4Jck;} zt0=UXLTL&S(U6f(M=>|NSiMX)jP4Y+~YV~@gwiU}}3LNqGMUoGr(w^-b7z|Vsm;2|l`@TL5h_Wsq zY1D9Hs2Uo_*OX&K4am=^R@2fW?A+R6c!5h|s_jFC;`v z8uGPb5EFDk#GeTek7mKkPV3Zk#4i})6f@D4L4HjNiGM(`|D|c>m`Nc z**Vn~-+o+s4ui3UGVw=KXS-iIkJiAC2}*w3XJpE8A>FFa!T<;U^$(ARU%vnj?BD>> zo;1$eq}FCHHIq^4i$YcMv|s6K=x6o~z-^A6{@TD`urOp@%ObLm#UDwZ6(eZJC*xbU zDj{L^*7zf-Z|7d>$lk=u|C4)pOLiqM&)xb3y=>v-Un7|{@$Nh53za4KyYe^U`FQVB zRg{}Pvr@rHaJ&{TA<9inz(-$t(gm$mAH`^F%jj9_Kb6*l*=rMeTJt;LRQ?lq#GEIIc~4rZpX8o~POXw^siDGAqwtX$*}<1s z06rxhuhulwW)m`AplCyNqgAJjUWH<&b7(;4ut+)w>C)|BaeDQ_c@#X$#$>xyzY_Fx z_{B>o$p6DH;4VX!u||~cM3pRinkB;!6Es|pLU<_(;oJ*&ZmD4I`I-5pmQjj!DMgJi zQoly2@xvPJ7mi9uXZe$)J>t>xu~x_z!Qg!ZJs@AtxbI$h;}8;{q$vxpVQpoaHZV=E zasDJ!QRk7}pVa;xj?Bh=MI|2ah;e&Me?e1@p=*CR?)1)^icb_Id|BcG#lg4~LZvT= z3%ab~H(AEz^I#0 zDpIJWi?tFWfQ}7g0&4`7K?00<;|=E#H4SejGC^%^2^t(OLG6n;CqfNgtp=Aonf&ge zavi?{@4Gh5ki#{lm}cLNPBXr~Mqg7SUniiC+{<-cRPY_E;5(||JOBQ9T0M>246RJE z+2LaS4Z>RdcZTDSxalcRew)YNsGghM&p#keX!B8aW(+i(f-- z@JmZDXyK;Q1+7dk>LRFrx~A)fGKww#WT*0tR9?G7YuHL^j(@!!Z7hUSzLw`p>^bL^ zKo01WYP>{Q1#6BiT426(PaXrhP=uQF3SB~|WUgk@98iOdix|xU(|t##qtCRUt|;UCIC3MTN1w1H<(|TL?ThD(Mak)W4k;s8}<*enm1@2;}DF_5?z{D}4T+RXA3s!uj;r@Qd{ng??rV`xhHt6vkNmukSI3 zkV5iwNAW$$SKL`?@!mgU?46d4F6=-7>+tgr0XEPmlSY?Sh?Zwm4>bM9!i3HHXD-*Pf^OoE9LV&^n{A+-KNKG>UX~kwV{WE4`$?G2neLZze4hO{{OOzB zFKcdOe>wj3dFjyI(Re?qX|Hn|{t?gJe@{xCDDNC^8W! zbZt-!=!)?_)e%4uHXhNy};`OE@5D(YX^v~UOOO$jtU@#4gq5L_-F+X^tY#0rk+!l3!7)d#U-rB z8kD;uQ0UsADCi1~c4||Ajt~ps=!D#{vGhHe$|}TB6W{lJdhZCt(^c=^^BWm9N{m^F zJ!HpE?z=UvM7vC)nd8P z`r$^L`OVJS@yhUt_Bas^YXc~YW;j(@rH#FrjKgO>!@FWrX5*_)t3*lr-fBchf#?vC zH$*rG%8-6*;=M;5BF^0E3VhCPMaQBbcUY_&v&&op62}_@*23vgZjx!6qw&0pk)zF+ zCYi3ejOFbrkl`x*TgFx?=pm=1Wmdg<7t33s{oDRtO}XKhYLV}AFV$s#4n*cYY0S@z z6|L=!J8pE)=SJ58DttYYc-3j%P&#A-6{4v&-YcD`P4$8OzsVrCms># z>^Pzb8}^u*2Q~A9mLF3*BvRFG+T2s+v2@{U^l7fa?jtEdx`GI=RN-Q^#8WO zsteWm$?tyw2YKBcdM;B@+9eT7c_z(Dz^w z(zWBdx_a$g_Z3a^)Tvyl4u&euuS7IV4B>LPB;03LiDXy>*>T%^vrsh~K*SaQiD{7V;U?}aA^diLZnSFp=GtQW`63D* zlsHbpHYH9-*NEIpJ2RuMZ()~0cq*g~0pZ6A0hO;RUlH;=HXAEPO{?$oCL}mQ07}33 zFTO~xU`WSX5R?`n*N}@I!m3b4XJ*hh=on%VXy-THEPK-=ioe(&ImKUn?9^wL)PAdX zN$m@tUQ%1yyQH=?x1{#;rZjh=$HQ#9^!OxwqFH-9mS395@ze> z_;iK(ITR-Owe{7;MXK=kyX601vo%V8hMdn0eHse=j8a3zRW?t3{Pj^|6C@1b0 zHaDYu&5cjh(yR0-Z9p(so<1(1yyPJ10`SPa$4egVOa7r{XpR@m5BoH*j6xBIq!Xi+ z+w1cQ=V6;A*LNXxf_D#t%0nTlhPa#UD5M5EDPR2f-}>9K>`y97tI`HOY}J`>7RBU( zJd2Dps7@E(`1b)L^)vfn)jxzsrS;-OVe_IonsRO;%eNr(@SN12;m*fCrSA*;hG|Uf~*t3OO6ON zN{4xCfMia&EiFkFgs(x3VmKq4E%$=TY4;a=ZSll>uR6Wd8Op>TX-g@i(_sjSV!6lE z@GJ40ycG)ODX&qXap#(O?a#@kuzPDEdAOtaaZaXk2V&ouPalyD5i7&XiR?yPxhq>A z6#1#6{?-R}mHw*R`gFZ3Qfi6R{VLT>71?MhbhCr(9cm3BY(ERA24pEwF1M{$SSOvY zzV!57#h+Zx@1m|>;J2ph zePyLEW@XcbfK(pG%%5N!O z&&M2(g|tlQ8LM#lDr(V#m)(Q#*u#pB+Rd!0`E?6c%Skh@0=Cj$H)!5sMV-`O$gn!s zcsE7!D6XdUM=N#k5q54|FMzfc7s%cx?d>$S z;z&p#UjKI8a!x9C2F^54?!)96FMAfncWdhG_S8ohhO^H|WH*QVMWPx^?WAkt>HMi_fo>F zqwj)-_@H*XG}5p%{0%BOPV^%F+ryl$Srk6d5wluM7F*=eO-#;ZRCv zZzQ5+3Oaj&{D-x&n&7r$EeG1;2|uUur%M0RAf8$*m2~5h9q5Rs>axGW_0sJh@Ii9> z_Utcc8WISJyen(1uVMtA4&ZSf&{8GeBc~f|%*kngOfcwP_q*nT+MtI-mD#Bl<<{~V zO=Wpl5HblEGoOO&bWy(>Ka3eBy33-XK2KuQG>&ay7{0oCuD>72+~I10$k5w^4dlIE zVJ&|cX0LZh7esm%aELvx6rN`o~hdSs2ehLF>)EijEZ@$-l3-;T877Q*saE{-2&n&+VJg;|!N*3&Q zz<#^ORMG-5;yo6jX+0v}uSyQsFTq!KC`Ejqaw+Y+nsl{#nXYfOAdmPyvnm<_yxtaT z!G7`Yvb3f<_iVp{qaF1kRo~jD7I$pLmqO7x%=dlB`oqzBQtvD<>=YlgGWGd!^3Pmk2bduMC^w({f6|XwP zxFapk4-8Q)Y2157QluR?4*kVSyGfM`FB_KA(CLxh3?t`&uH{H)7Z4NPYG5Jv z3>*<(DdmNyKx_JRJoj9KhEBKno?b!!S=6`u@dmo|OyszF_{BPHCGTZ+V|?|sT))gh z(~SRt@|wr8n{y|v{p`<^wU`f=_3HC1NyO?qGqd}RkRATCMOmw=qtY0LisgW*{VgsA zoT*y+ua^Ut|Hb;;cncxoCsk!^=T+PBaZ?*Q;eN;B(&Z3qq0q+zayaWb9E*LXF3!d} z$Z;IBR+&G67tG?)GZe$6y)YR4T-KyHpX0PKG+^B2P7XX%Xv!GE&1<)IKs&(bwhQ%eh*2(9L8sWm0%OL1AkO8s6+} zS~D{zT>nCRh67vK%abafq4!t-F>Gr*{}MVYFD`vpi;8{d2Y*GDo z^JHs+T-h{mla(s+1^cz?IO>LF-G3~QTH70#Dk7zg{{~X(F%urFsEN%> zy!U|UXF$~gwxFlONBKivd@T5VK{di-!fZM_90gEbW5gc_9I#7|+M1KfK9cknyXyOCcYHHN2aoxfxcL!8EYYghnt zC-ru1JohgMw_BGKdQ}NyFBH$|Bg%)Gez827w!|0(HDwZWiI%e0JHp8qI(b}JU%$9& zLYBTE#2Iv?`?}Tn7k)|4tQxsD47BrMzE{67!s~U&oXb+PM5ri42)AX# ztl84*=}_rvesI!WmYVk8PT5=em-|!LT9Cl&<}xzx+kVsUmprvxH>!Y#=YCUfG%p(j zWWj!;^U^)8o|g~1A)n7@FT&y{&2GoMya=)X{G0#%#NI92vv+XELmAP=O+)y*SUSofzv}9q#s&jVn*IAd&E=!)vel)reZhxPR-dP); zb0J}NttIIjzA3LdU)&5nE2eQy@*ET#D;Y}IbcA4 zQA>Mv5v~omDqD$GlLxeD_zOwAw`}u8pJ6Ng4mX8>Q|ejg;}S4dT5W}%t9SDVD%KnJ z-FLO@}*HyPEvqH!~@o&sVihswFB2Ilx zj%=6NY8CGxm!;UJSE#3!{DA!$w5@GgM*PP{uAN#&#HLZ~DJIe&lVBOP7VhKc^UPb> zM%&0KO^8G4~ln!IPFHr{zBiPny{N zi_&9FwWi1KODch9(BF`nLcGo)-*GXFiI8FZx|;cbO&2X1T}fkg4X+V-b6A}YWGuPp ze(^gh!*4xcW#RaSAkHE9v)a0yW9#M+@I2&P`ctFn;xeKLNHkkSj&ORmu#O9Lv#4%P zhTW8^y?no0HoMq?%xuaWb~S|+gz(LwzZs3-Y(OC8RQa8b1eoojhtAA4S5f$V2wT`c zn?9!0p^K)|Q7@si*a!249x6e({N>+qhP8P|9Qpd;d(p3sp}<>~Y>xNHnFZ|>?v;es z!AebBPyz+Qn&g@7?{>+W&TPsw{)TZv`xcYEG{hn9@jq;l z{*4Bk1KgKfMzch&SO?!i2=|iM+8$>9x27N7`EEZ#@7T_p;0uNtRN-?_bHpPX59YD# z;e6^yYidyHRVBFYx{fH8CiOo#DHU*nO>eUC+F_?;E;qyESIVT_crDB(rEt6^oiJ4S zq!w*X8b3f_V|yuQZD}7TI|@QYs2v=>XP&enLp2E}A1i+0b;9>3IRkp+#9=y1=)yXL|F2IU$%imd zdg*>IdoZ7Ts5RN2{S_>4t*L|A_tSAq%vdkhB(}q|S%|25$Y&5gS|r-|yBV9!<=auol9<RdNu_!g5vL zaA~RhC#chvly_Ix_O=Fis3J-P9!r%@KK@Q*-V!DwVMu1%SPum@lbig*<#*x>y) z#wWA(B84<7m!+D!7=sl|d-29*xP~;*0s~DqvYOVvXrSsug?rHo&XT5%1mw(w{fOJ)V_X~mn zM-7E1@}SXKo)L$o-t4kkT`R<0>+=LSP%a5qyvxsLkOYSIUDg$Y!GC?X*Kg~!f1C08 z&9v#?Zu9z23t<2DX0QK9iFufHjB+v4>%M{bvU{VkE( z6<+`H$n7exe+9Qy;jT7+yC)BWYOf&wK<69i4CrhG?2&m4u;SW{Wxd&yIp=1Nc@k=V zxsuUEgk6Ds;L?nEy>hAb2ITac*-vvgFqltqXYt!NIJfEnd6~k5cv&zHWSUfQz;N!k z^Ba@WQ2O{@xyj~E>Q|B4c3aMLpHz*iZ^uvP?0OexDOf1;Mx`ppa-ML{cL&dJLVWFh z!KmLadA$$IRB0hH56rtg_wq7wl~{ba4&I7C+WQL4pyRP3&nIe$n;EFp_b)WnK7mT2GJad{F7(#( zX5RQ1goq;VMW4AaSbxD_%o+aw^JW|V{J;vT#Mp+FN*AOnDXi87GRDA7_Uc%^_hx%_ z6eF0uI?YzvUZEv#w^yV2!bW>_DnB4EK93_kdNSK zsEO>WqCf2MM+-tfG^oL6dnYvbgHinW%TVj(z_KX4TE$mBHxH*{Gz&M44z$sX_-{SeoZGx@$_l)fjQ z{-+?iy>=9fS&$iqD$aP_j-j5BbBN9RXzaaK79ZXgjKNsGZ-y#iJiwx}0|&3vNfOxk zE%#DZp-s-ude(x{CoSr|h}G3)?f<)6a#C6Q|IV`Z6LMWt*8XvYyrriLbsH=a$SsUB zsNr%mt9M_++8BS7F#0tQC;;At(U-r=(^hsvL`=AXE#^Y@Iv!gdewfugvXvD8_a()syeswGyAIb-S-ct{vDd(n;>k?b#dKdDC{Z|9WtK=a0=P z$cOlI(UKZ}oW}hcyIQSY9FTYM*)=t!T(|@w(X-pU1?YIGgFvcU_3gX)mUA#Ixw-B? zKg?8`S(Lp@H_tz*o4Oqc*ZnnXV|ydtEwS(VOFxa6p`uI93hpE7XW)(16MAFy1U&1e ze&bmV8R~jOk|RO%Lk@om89$;NMMu!e+@x6bc(43IEqO(YstE8orwY7)Kw5fXp>LwFPTe?AejIX(cNP0-Cg^pOxdd1pO@66)f;dB{umMZh1C-8$KaE@Vq3H=yGz zHvfWrIG?x@;1D!p+ia+uq^b>$jKrE&vmeCs6Q&ZR{KC)WSZ>(mhJ-&b0r7Z zS+BFjf|Zd;6JFa2o{X%6quO_30SkFZ`TWh#-{%j%J7CluA4g)cEhHem8;o&TuUuNU z%PZ5l1!d#rY4XYgSs2@FEPJ<{R*J6dHa^SPB^-LoPTPF1%_oat#*E@`GQFJp;0o2A zEC&8|6K-4wQd2ZHBoezw7g1q|fLt#xuk-o42&Vov z(w`6P*U9psK*1mabQ+pxH!HIL{@H8giKr=6Nvst8zI(C{OrR3$c{2IRq9>DhIV2^y zFoEH)rXufJFRr9)u!$^tURF^sUeiTvKHS}*`Fb9AAJ=t_&A7+O^5Asqfz!G(VD9~68rVtk z^1A`X>y}xBPUuTR!y|+b_!Il5)ZCnIU*;#BK%aovG)0gs zDzCEEKHnMNIl%W6Klp@JhpFLGoeb?g*Me)7CO@#`XD^JQ+OD@|?*1)X?H61&P>+zs zT0&!a{aoOJM9PYHe`{uitHmulZn2C`l&Rck3$Uv?WrPSvw?lbEgWM9Wqwc6+YcNu1 z4hhsMRHk~&Bl1EEZ4d&8$q1+o35t-*B47D1l$#2vb9Qo8gc6Dy|3s43kXuhhc}5>@ z4=nQ7ctT5DNcC$p2BLbAdUDE-Z6F&DW3X8JP0m9-XXl{?Qk5$C%tM3r*NwESSeG_aB z&*J|MUR8;)zyY-&5xFtf7hdXE1&KEpx>V)L)VQfWdC9=)2{85qY&n<=8*+P&kv;dj zg4I&z_Y|Lj;kecN?fNu%%%v#Z{zl0F4|npw99Mg^LHo!jwEV|={;Pe@xL+o%*ajL3 z#pq*^5y;$oksdV}=zfo2urU}c9^H&&8-WbjH&Lk{#IWNE2UxA8z(gOf1RbxyoygTc zVdyBLaW^`&q|ElVV5b5c-6T(RKt4F^aBhP~f_Y_7a6My;{sI9b$Suz*d!+Og#0=J< zR8JTxBI)$Fbq(eE2S2Vg$a2xUH2L>Sq)8t)L8r%8;At*gYW(PZynQnHW$j@K1U!A-d_v}rMq3@?oK(V+V9qkaZOXGxof}UrLe1yQ-&UzB( zNOV5Fl4L|Dt{vNiVW_9rUYs+ke?=dRN-T8xQr5bfEVS}va*SK#QF=d(_637{|F+5? zzkMV*sLd$BAXX&Px+YzZOEx>4KD}O( zs<%3QKlBA6P<(kz@hAAw$9CdfpQzcsO$NInmIG&Jn=(5?Jz4M3Ip~;+>FkEBH7;c( z2E!V3^v+WjfSHA7x7uFht(Y8U5#fO&doASsgIkBV>{3AG4@k16$I7Nz<5Fn)=?{H) zHd?#_`C8X)BSfHZ_OznJ#%NRdptw7ud{Bb%i(tZfF-qJK z<{6$FjAVLCFURu{7%?xlyYQAu{;eU)Xt6?pC|l$Wu$=59+wTNMB{@v30KVl6*4+4; zYA7M`79UC(dxxUG=f9H;R-yWsj2Cj9y@a)T2wZCr*&~zIsmi^a7eSV{${1IndJSL$X)a zQH{)i!6suydLbp7HfLYX+YYi|%cpK+odY{P!#NcW*qM>FQLq=-;QQ@2uo=PP+Zg|v zrQD91)TWDPQ7KqMO?BBdnq~LhsF_t-1klP@%eh^MCd|K?*zVWayierox{(rQ^4Ww} z_ZQygzo;wYDK=|?J&|CNK!57vZzT2*GMkVkiyZ9F={fKdoCOVDTf$y7av+7hYSM8P zde!W;HQTEeG#vJ7x!1njUabi99IJ(pk##p2UNpovYluhMZV5+vt-aDnuMZ2g+m*B` zck60o?G}5WLK%CoZks(&h4U*P=j+`g)?msoMj*xJr zy!1?l|0)8FzR&GymwiOz)1d4KOD$7-8CM}lT@0n(QGI)7@sEAD1L}8Y0{BN-wXM+k z53>)Z6Mdc7k!Fj(b5&qfn@6Fqp?#Qu-#+n*?aY??&f>dV`=+w5+Jswu=(o>C_1l{l z&~FdQY>gpbVR^MUPb4jA;2AG@mZg)$8Qt?l@#`3y(d)TU8I-R{d$aV#r$0do`%vLG zQGAYa@Xyknwg(@~x9Om-F))T*!yHitweH-#MS{UOoemuD`B#md|2}i(x7}XSB6yR7 zRjMK!7An(KKz)VA{A#jq)`QHjcEepVr4$>mpVEQWbLJRB4%{yl;fXnw)mUXGN}r}O0aZgk z06VSgFa6=7*qY?{eeF^9ooTs91L*&cYcL|vegPM8_68*s>-2KGHQ9V0K7-?(#qTT? zp6Wc7y7a>YZ0Gij#j>|#rhQd`k;fc_bF{><%d2U!&8k0Ek`s<#Zu^G8&uQV~9lC!f z_eQ0~qP5phChOWbG$MREl-VfO7ms{6q}#4LB_pJoQ2M0_J3X3D9_=Vz@8Au-%ACFk zps@CH?0_O@_}v+y;fOLYxcL?cWrxiI|txu17+4#!0=z8N=LuDcNix_{% zO>W}^X%ng%^Y2_ag;oz|s_Zc^zO}{e|CD9mm?nz|4+#vpg!oFm6>4znsO!jIDwe3# zpB(ZWer8MP3_9RN45%IM>C!mK|NSW6VOQl~JnXF;4C*XEhu2)HgF%mSDCiOX9p+%r zqjoUp(coav3A#)wYpZRGWUSsdmTmDypsZNue-o>J<3PALk;=J2T4Y-6PWsBldR{@00zlBy!J= zNF&VCAR@De;XGVsm%!p8u)# z$znw9)33EJr#r9p@u+2@Oj1yR3_6RqQ`vwy1S`Ym^(VX%|DokspwS(Td2s;$b0RHc#WL&2Ku zBL7>%D)nnsnjIK3XH;bxi{FXDhaK7f69nOU`#;aN>^Kf5R%hj{-P$2;SI{4;yh`@TUK)O4o*!>X#%O_RyWz z@jzVZ1FCd`_0A<+6%j*Go(jL$X*B*4C41>1l2aE|48>Ip1qTgcy zT*VGm5f=M;+hU!+TOvM6$F0YmG|W0S=zwIRwYC@AJ9qr+^q!z++;Q!AFGSR9!lwZz=f0(fq!O!OAbm6V6GSMV7 z%YarhP!z$6D5GWR&JTuKRz9Jc#1kauz|p6IC(f^>Lz*Ad0K`~wA(pC0y1&_l^9(pAT9?q5=ADfOBY zYAt4r^vl#FC)Cm&hOup%OEgdm@()o<{x7)TK|YRXog#Hnd*zCi&1D$9t*X#nxI-5H z>X8|qs=ZYT3+|@Ys7ebK?c&%};Sh-ZhUkll;O>iX9Q+7sq&ppT#%)?;mGeFGDf;eL z4BY~fE^KgJ=&-Fb3CioRz40R0UQ}3X*+I^Vup~5>D;iv(OSyH_fJacjvaHl47b$hA z$P+bBG_cFpCpNxm<4tNKdc)n@z&pBWwd=7V-Qyt%PT5mnuK>+rs>u{icPfDk?dIWb zB|SA)d<>XGcLWquDLfTBOnWXyvyq{2*l!pBOu1-msw)I_hVllO!|`JA0#Vfw@zx+s z_J2?LELN=ni2D;so5{X!alPwufhpf7J@)P~t|%ulSUh;H@i6VeISYqdCTuf-IMW;~ z`6H6Zw~v(k$$N}DD?@a!MZ?jOHC6EvmA?pong<$zw@*k`#~Yp)zsMznZ|5Mk(OgRf07aXm?hp?Vomx1uGf(~MKV zcq7uc7ZohhO^OkS*D%(wz%GvhSe%eb_*|{C;v>!0iB(>4}wqdTMG`!wBXfKRZQc*POS zxGZ~c<*yanYLZTh5p3cgHeCp4u9IS2LD)aeo=b+?(qoqn)JZ9DFz(U>XQ>e;#vKPihrl8 zuN*mj_?mQWxt*rvFL`Cp(SFMEb>Jx8wU{xs%jli9?CN+e0HaqeQG&U927==3T;jnPzBX; z^eku^wP%Wv8-mMRyo{n5oz;i_{$EP27Ery{6^9YvBuE6aJd9fx~BG9vIDIATRRpJlm zOM7r<{Dq|u-<{{2{pMPS<}>yP##o#D?9D9WB&*5sadpnzH%JC)bM*vYAggo6_DlP- zJ9tpu{dPd!?m`WyYtEM!QI@50?oIMV}@H+rU;DAwYR2I-L(L!+{BW^OET<(cY1QbJPWA}Q+{O`WbIoTaht z3A65gNv3|ehg7iKdTJ^DUA-Ds37M1AepcTsLPM^$&oh@-Ir}Ky1JZX|O;&X9UpfEp znYR&dcs1{R^KepQ_iDBdKD+K1i0;aoea$u0Y^nTVrO5vmK*|3)mx!9)=9^2Y(R_;I zMb5*Szw#hLe(k~bU`AZ6#y3V`ME`~-gYtEz11qd&MICan6Go*;6?*Z zrC;qkw+U-U5#Fmt+k!*qrS{Dk;05uTYl-mMMbQqgo1?ojy#Ay?cvUKYSSj+qRnh>5 z*O7k^UKjH*#B1{L=!Nq)2wuaMo=lCNg~N_F5~5{1>KzVdj2lLO>F0#&1`6`b`yjZ+ zJ@!*;+Jk>R@5?Nx7`A+Mqt0=m2WBFICUye0Bb4$iZSk`8R{lbP%Z7&Q<(!pY{1SBx zF|s0}&e_-6!@l!T2#x38RHae8o05*ah?!f0(ueDK>fNKOBE2X37eIpUnIJRLxj!{W z2vw%hpypa-x~YOM$?U(7K0--M86^P3qbg<1sHFsNIK{O{8Lyp%GQNCXYUgLjGf!MZ zvb9onagDH5=Q}4|LH;rIVznx`+#miDziWbbeKmR)PAum1#k1)p^3C=-m$-Cpp?wwk z=eR}otqT2=5r0Pc(+){s!bv3FdhKPMXHL;;R;;=GR$t=5## zs$~_eDz^vaL~zA??VCXFWyh_G2~`pEwO%W~_+=L4x_shm%4&Sg&S_XTC_Gs%;623t zQ}7p6ebGK$X5h$ols9}z;9Rs%pFc0PKWsS>_uar6ON~ykRv*vR^{S(*b6Jt^jK03- zeb(1Y_h+$i2>ZG-x-0AJf4x$DtyG*@=34kjg!PgEYU9Qe)-|4l_52rKB8T~XTwgiAs%y#lD~qpuVvX}+Qd=?f$?wcdocZp&#Mj2=C4TYbyu{q^ z&r7@xaj0T$$-he7&qq_<`Pcb5AmtPG`bKN*oetr1YO61qt zMy%q~gK=kcSJpwR zRh*^r%?W6}#)K}FIhubX5mq<_&ppT4xn^Fx)N?m`^oW%CJJMHK2PGdJI65IIGxs<@ zCA4cr%{uq?P`8t}ou@p}uBDz;f_gK(U7EU75_e41b6#IfDjyXor|x%t#u{;!E1r2J zev%&H*CmYRjL^F*N162v|LAW}MXQz0By3zR*!bkgvl6Tc?lZWH34B*$P%?{{Sm|j} zZBse0;QS5D|F0-~?;y47uKS|9vhMnPz3{D6;iBMHmFlooCVUHzvkBiC&#~}r)F|QG zZc2r3C}&#)Kz1m)D+A?n14XIAb-)qL>NbR(xSlyPN7KP83DyT+uO=x~C|G+Y9SBg9 z@x>F0GX6t6<=mv|#D8f_@O5dF|NGPG+m$2<{TG@8xEho6H5j(=#bLK6x+`nadkqw& z3T5G!kuvgkbBQJ!Wc&+L=q|E5IoUJ!OVIPMrB=|-NF0i#ku~72G_`LMw#oRG>4Gx8 zbrv#y<$IzTw<~H*c5`%B)~?@uolvY)p)4v#l@a+nS#;^cnsIL+NhphE;upSiygiGI z{{-cRuDFMnDr-C!b{i^|@XoU@5!y;+eEX`oP+4s*GqsIi zFQ&$4>A?uEHBNf$J?kXF$N4m)r%I}fS7Y-G@I?mRVy?xWDw&qzo_grK)PB}-8oLMu zbz=|_)5F>JBNW;eeCO|k)CN^>dFazPXxtUO3nwinxA#fWkgwz7P-VivLUPjLmuXg1 zHo|~W$E*zX?XOjT)RD_Gx8N6Ns7KCA?VAiPR|Z86S}qJ&t0KYA$dIkIPd7zmcsmul z;k7ZkDA{99o* zMHcAL=2$;e-+U$h;(YcZQ><6XCX$rP6w3@oeRTrM44ERgDwZjp&Cg5x;Kg}~%YHB~ z@mJ&X5|{sQUgGDO{3KJDC=p2y7Zxezo-b2G`mm3b$`kUD6U!6FKKP+hjhIl4Jb+0L zidg?LSf+Z=lspU}A-H2aS2gH4KhbjHTg6F2#?86+G98WQYKOemzzO2LdrhZi+%;#N z5WMOAMVSPl!wN}$wi=C3sKM4wSx3-#1dU6$yKcTRkyb=3!6oKc0p(bb%zTnoMA{#d zRs?$B7b0n8ZvNLu(YxnCUN=9mHGa3qi)FU8|c;7hReI1;sa>IPfKrT(%NlD2vcY0`t)K>()! zn^S1^b3g`&MCY-(tc5Fh@Qfgg*gTMTAl9`Kc6A5a|n&Xp93o#q}<7?%>tNR z>2%PjFy-Oqpn=jBcH0M2B#~1$x-CG$$m~%@R^|V~L#$$}ZgZ~8gZ=qPuQ@mWm;{nC zd|uvlbA-<~X2#x2NZPI&%DdnS!ogXVs3VCf#I96`LbujWu}H+x_=ip2vFt& z|9jj8Q31VYABPf>S(!9Nl{@jwWv#KbbNBB>aA=Y+7W?$cYfKLA`t>NATXMT>ZZ45% z;{Ux}m+kYG9rRc3tJ3^6OJS#aR0xXwzn32DpW;yuaiF9WH|3e!(!8}%3>P;x`& ztlWu}5hP94gf~llt~TdzK4O!|EDn07mo&KJnQ&URZ&@wPupQ&7nb1;)GK-~-XTm>~ z`CmzDy#FX?8c#x&NiqwT!pFsM(xUd<4!$FA8nrtjXaDN`@6wck@BQ=`wgO>&HUSt` zy{d0}tG``_PHW54V{Dih343on&6JV((HGu zKtM@5))wrIUpTk^Y=}of1j-B$k4og_T0y_o;JHOv5#Yv5rTB=otN0~CbIAyB`6R$8 z>#G|8{py^u0r2z5?C+P*vS>7OMigH{hMBWayd9k@>9LR`96==e-%247M$iIYn1&Zv zn^#+#+tgYA*4K%Kv;72t9<`=w)0|_=F}0TM;>1-{L~HgBK5JvrVIeS_Z#{x zwZA<7Y;g=vX585?Ms{ds@iG-GVJ;8t0k+uIK5jlaGE-&+WAJ6h7k}_WcA^qFcalnV z-ng=+xk`R8u#BrsbpJ9E4N76d&4k~!q^5l-ACZYRFH6W52>^`Ctbr}GRSh*Qi*T=^ zP|VjQBIA9B=2=X8*!BL(GmunhDP{H+-vt%^)~Vk8%JtZgb|$*(fAW_GL%^WH<-MP( z;mq&#(Ik=r1U$+HhyDyUoj%3(IM#)1moNNtdp4V?Q(j-aI$OYw!HD6~*b3IPEbVHE z*v9LWZTS*$jb;>eoL+iUscjF`gy(E03H4WYo+%_Z2fZpFiOwQu%nNW=c%4v?2Q z0hI0-_e4#{WS5SaI`^$pf}wexw_>xuX1&+GB_!9#Y_6<+jhw0A#`Ei2uU4s)PH)9V zf6eV)dl$ur#!jhFY7R|ro-fS_#bHH4rT)R!HoHQ3dN;;KDsf@knMtCJb0jSfZj zaf{5rs@jaGE@Z))+}1*mLN1 z`C5ZbdhNH+N^ixj(JXkC%)0ty>fh!Q0#9}fms*KBT0)64zK+CSg$4YTH?L!$d#lz* z)OVFSV_>a{@`u{^Sra^tL<>E08lDnoeAS5<_f>R@4~~{j1>kJEy}Z@8swltr7Jk}- z4OSu(>R8z7vabuRM18eZANSffGEBS`UG|6-UYpn3qkdeo*4y6C&3Q&#Mf#bJU|{rs z+j?uxFaD{9qq#4prFz#Me9FB8xg3F>_ExO(+Rwv?Miv;+NLv&9wbWMTaXZ9F+1FdG zbS2Oz%egeM$tVyYga_AxF zNdlKWVZzXhTI-=8{X=m`&Y2zM?9Mcv-?tt;*Uiv6Ob;-$j_N1EH;$qYw+CN)K$t$D z;HDTny!P|H)`c3WYpwN<nY{$%uthHO?nnc#tAimm`#i(D zVtJ&yp#qaGd3KixBONbi4mad2()O8J&2?ti!;xIB7g8LRGp zxIQ@d&C-l>?|(QIj7P65zBD_zGbnxv_xK!aX@`EQ<*A{q)@!fz+frV8%3of`p%kHgmf0?x@0@*N)OR{N zG96qajHh)j-W`b4yW|l>MoKC;857*jDHH%#Jqi*RR$r|dILkc zr;^#M*F$)1GD^ue2<)-eYsQLR2s0lQU9MH>8>aioe# zBrjU5vJwbz6Bv;~eH1dmLEOyP-3XA(rV7z@vWNFwABcEGGp`d0w~X@kZiL&XPhxCp zF%~8g5?f%=$^d&-RKt!bJO<$~W->D}Uv`#r`0ww5*}qI)l4(s!BZ~IV()mz92ru#p z2Qte9A4TJxD@OJNzCg}2B)2_5rO780FSh|dRMW{PFc^OVe>bDaoF-O2>0K(fdd?`$ zn#Oq^&px%~4LEF?zHM%01&%kBems+xFQ@Usca&lDWFjhy$*(%mJE8TKH_7#igYbG7 zy{SD~j|cBuf%A-NXA^yj@jM25IL=fSyyhC#VhV3!l!#Igy2|eNOIJ`x7 z3bYZ%HO_2ZSkX0)(`q;#*208O<-}j?kHuc=v3=hRVpO}?ZFyXDucRruiipV zL0hoQ-k!3$EOhdzHJyiDU3do3aT%*I_H)bsH-^|ui6?fu|>bPcpY#xtOnb&?DFZM03s?;-Kw~xow7yjoU zBcyz4Fn-uOE@!Io4qJe-A@?AJK{wZDrWPdozgQw?}aECkV20@@|-Ns=&D-xcGq5Wlr#_HB~^Z!cmaL%6ac~F6WD1i*hQ= z6Uh{FX58OCK|e_Y(>tjfsc9fFGHd1OS#$h*wHWoRg+_BxuAv?`3+U@WzwW^@nwkMs z>FiUV(Y%xj z=OulecJtCS^U|Xo^*Cou_J2l2YAgVkL~L(p%;1t3?+sWG)DW&}y%`)axN3FZ_oTrU zwpft%ZlnWgvF^8b={_vE!Ajn2B`>m)x9C1Bd5O{wrce?3Jxg`pSEu_Bm+Bg?w?S|2 zRY!W;7ui?vp^PknxIX-r;8*t2g;?~Ttax}%aQ~~)aZ$X&feB025e66u{j-&bLf9} zsDj)!t7b?QWc7w9#VW?$ch?iQS?~I+=&VvD`~OvROw+fkL~mI00_3Ip?3Z+}j#3%y zW&?!ZtGt=97@C(7Im@y~iLZE>l`g3=->0O0Ua%vsYi^6KdwRI~_P%LYZ@S-s!kgcw ztiE(#6Spud(*RK1J=G9`lT$rtr1gr~1^N|0CugFaU7B@-66Pd&rjAqiY3@+H<-g zL%o(Ipov>n$zQt9WUs8+CW&R(Za{x5v@Enq=Z$9CB0z5Qt8epi9|jCY9nU^TfW{KG z4XH=g&t6VAl4r2tbtK!Dy@hMVzU*)42eV)O@DF3}@J@}X*7#2mS+%w9%7cv@aAW8m zdAKUry$4CYVnYJ;^uKazt7j&Sjyjmr1H5p(pP0DftoQewln4Kzax6``#{*OL08edl z;2*q)D46En>>8IQF)T<1=L}@OAaHYf%8XWecH`T}CTl=4a|2U2+iGfuHc;Qfpzjk&<6S7I!8d6?10v25G_NG@^>mM1MUO+MT*> z{|a$?9n`NCj_k`kec8WavAxiz>%vc1dr^fsM3}-h3h2D%21{EPr%gp^rK}&5ONmf% z&wah!Piieoezo6gUP6Ake>CXj*Z5%Q!M?4t-_eRAF$DdXXo~VgJ_DbK(K1HenKR3i zS?RkRMlDY7A~Vo7d@Gu@ZgPfo_@8=jO>lAoSh8vzAZA7o7`5B5Zw0s#%D`0`T*)D) zHP_)vU%*uwN0$y)!kWXC+Gn_GUPK1M52>y44@p{wE2VX~lIVyFg)wgx>aO@60hs$q zi$KR|7h2%8FCn3X73+rf&h-vI@eqm;6?3pu{&$Kr;ubWSv+I)Ct9hF zl%fW6Oi~FBx=?S|<(WPD#RvUggW&vs=K>)AAHnD(>{nGtZryWY&v-QpRarh zez&G`3J)E%T8?)jIccdI2Lb9J6>^L~kK3BBOp_6NK zIWcnB;pCcJb_uuifw*9B69yNKITWr5;ns1v@)lQuq2~>w)Y}`(#Uk+=JZjOMe4WpCk$r<*;|nwFz;we*xo*um?Q0=Y|C= zhWiHWE@RO8pj8{;AqbplqwWS05iv8F^}tIm{mr&qI1>!(3z5QQ&Ufy;@|AtG?KZTs zU}9lZ>H^zW7jWLNz>205w-T9)od>H5-h(nFYVWLl#W7Hq-=+>KuRV0Fy)dy|=F*qho~ON;pz5 zY+rH2vnPjpm&<2*RWIkiZ>#rV(QfZq5q;M!YKZlNH(+SALU0rhG6zjsQU4xom!yG2 zxe!Va1tCKk1-*?B<``^%9!`;Q&Hp3$@^%z>7DD_j^^-x8{7QSZH+-GCA**X!=Tx}x zw62+TqCn+&43I1E7~FbHlP=e-$1cInQ&Vg98j4{9=~(hBRUL=e+DOnD*)OU4a;m}n z-D?t-f~Ef%H3Bgpnf(GKDiUH3DWT%%+h{Q7T9Np9tq+_13)qly`cBQRM5hr^I5tFj zx1aHbsKO-8zrr%#LD9ldTlobzCdU-O*WNi6O0U*rNv~1cW8Pn%?`EN2gi3Q++ST>r_>K zfvSoNX82KflDPlCPiBLsVazQd3mr`E82 zsd4X>{}h6h6G(LAyzZ-VHr%3KGk~#6FqjE(z5(e$=w%oy3&U6`7~6(6^t2zl#76UL zUCZ_ZIK9C1&p&#>x(311xz#-8R}HHd+M-^FkslRT)OghAe<&ox4L8)xNoJqZTl&)| z+TZc@rJx+Q#+&{i!q=jTuHP?aE5pDD4B^6zE93`{Hdis(yfv(#ot2urX&DnEo`B)y z%EO*Ld-hcLgy7lsb)$^@9Bs9pdoNq9l9LxE+yL(}KFLDT@N0}Q9Fvc>SHWo(hdiT& z1vVKImd-IyEkBF7!amE1MRH1&tLqVMm0PizKsdBv{2v*!O&R~P0xPEt{*$?Or|sOX+Us0(8uNAjEw;Z=T%rf4_IHrIN_ z!0682(IFZXVL}Pf?7ATy(^M?y#Y?3Uu9iMV&?mNrL2O+z^Fsz~HO@f%i9SeK+Ze+lCHV!cvwXrN0cXpFk?9-SI=Y?}a zdg0t5NG0M3!fAWw$SbnV5}bA%Gaa#TT9wTF7mZl!FDCM2{~A5o=*@fzoL=b|?G5m{ zi2W13F?r8hESaHo6dajM^OTMJ{;!uN)?EBj;{hV?lnhBo*~SL!N?Z%%iqPR<#dd+0V$#S_lqp4N#44O!Z24kYLd~4+y#4i01rvW$QE~e4>8BvxYfKusW;Wr( z#2AQ(jKM#W9~2bZ&Un{7o(|OOo9MxVV8fjw9CbMIBTSdhENZ)^DKzS8#LM1 zI7pZ}5^nyhvLYQj@yBFjHxCgzTY`rKXVdK7E1mAcOM~q+CNm~ZjJlXtD2lm;>Jn6j z#eNE2SY%676Pzq^M>2aHs$iKV1%+g&ZHJ{(#tM~OY|Tlj>_j5;GKX?iU+48Xb|4p) z??7H6K~w(2P`}0S4GKO&WmRCBNQ?WVDDnnP@j9#G$f;-b9X8eg zM9%$m9Bn*^jveJunwX%UX}rz154Ryop`1~g3#pU7m3+dALT95U_zz3Fg;#4+jb6D| z2((w8y--6W9V?MHXo^D9&3b)O6r#EU9pAbbIKMOk<0b+t>jOu7 z?)V+5>>ZVsF=Drt#RLM*9V)wHX|FW+-ZuBbgHLUk1Y-25YKo*^e?XMP3lSkhSt2h( zS$*E#IrNHriCA++9wsU~TgMX11gAhO?w4y{%O9{E+1BxW=^sPE?1WbPW>?uX;C5v_ zHg@#BPdkh#aR=)v2ReRiIrWCV<*PHrWE}ta+A*l=rXDWc^9z5M{}pnxo%7?~6WUhn zJ+o6E@bVFsrx=8o%JizF{2MK8$B&63ue4`(xwO%aaA{5XA22Ccby{QGv|A~!k^Z=S zQD$!fISBX?wo|O)&5SDl50*v6D^KrPx$Fa!V(}O!hYiX9VO0DWiEbKE$_4r7;U@WR z`%-L>5+t+V0e;p|l$~T04s`qk*nYz1KYpu|$$S3PFgJ$RqJit?bDn&7Ge1-uZRL)) z2lqS=MWNs+*~%Sn3qEOY*>_^&-0dNv?Ww-eZ6LBpsl`Jik$>$kh!PDNau`bnUh$;SVYOu$oNSKL6waR$bsG6SA|Y zv$y*##jXtdwnlq4x8W|>nJG!r$hoUx=gp44sPkt3it1CEx1X$u8aXQk)X_=x7o&mv zzZgNnfs7^SXk_;dHgb$j?G;OK#v`3mP8~EmIQjo-8 zd9efpY(GK}FbNy%cyZt|I=6t6vaYK-k6jEVGmQACqIrlDSbN0c80kv~(?53m|3km9 zcz)s^_`8?C2i`J2QBgT3QCafeyWcuLaTk9yNBa{iH%@N#?|m}%b_u%5Fcyroca@I%=5Uh1z1FXe>P1J{JQO&J{6g+R9j7*Qdg7#}esr z)A%68he>J=az3)=fLXCrd$osWk!GY;!(*o~JD%A(e#srnR3cdITCBZc7^z__a{!S*kDib=Zc{|9 zzh;qU31v7kHIRP3@b~$@m8fS>7bS~i_C@Ns0G+*nQ3#p?%J}xRnx&6$+xkN9i5{|y zo|#`WO1ZMhQzdPkkGlrVN?$Tid4BW+bC>bV8ny>viYuxw(9X3{JC~QXv!sz@MY1qF z#AGoph9R;bz9}OJfIFT)a(1BO`NHS(e+x>jEfSV)lB|@>x|t;!i!YS+Zn2^9kgGX3 z(2A)@n!kaCZWiF%cQR+^b-$L^FCgKA{efPsPV+~pK<$0Bc`h2GH_Sq*$$kMbEAfY= z6`QF+ffK?1VCpw{qHZ)+gQ+XO)7H83VqKZPZGJ>YZf-YO^55~V}4~sB28&Tl2ZtuX9PB|VxGovb8QWYFz8)@gr~w4ft@(*+kl8c892jF_bxx>bly|MRK6yG|4d5v{G%D546oIyuqbQ zvb;MF43{s3XK$F{qb>6=&(}y7Yy{09)S+f~n0!iGYp4I2C{`DR^V_xx08SzBrZGX7 zmA+f`_nt8ET2$Nd9Zdc+TOUEZ{w!ZH{)dV;zenPAZaqIrQ4~$J)lN6839cskxa4dD zbuMJaT;!_P?Ctt=D4W)e;(Q`=kf~OrrSqsAQ*L7lMQNC(Y80Khx-n+-lvh3_ zbpIdma1&*})84t%p~-cm<3sVBRhR!e2IU3X*!0xDp^eBU|1Y$$?xJnvlt4v1_kSEq zdwMRp8WLo-k+o3Nt6{$o_x?u7 z3)>(+ihA-QMM*+^=px^1^FK8*F7gdgcrv@j$X9%0uo{L8*ReSg29W*^0iTHi!^&6nA8 z^cEv59Jx$H>z~=lrf`eR63Tg#E@EDhM6o}XD2ng)#qWS?iEGIP;OhF{Hoykk=P}9H z0IPM=iB_JYQ`HDy471ZUXb=%}Y7U}vS-@6Z#S_obg@u4{chbK>f*+XO7rTi4BGc~4WYLE(ApK;BM$0vf;wu7?nHDQYqo{>k@CTI*U}i%U2ma+eamI^RT~8wb`z&9w|Of8IR6pL z+wpyM&97;x-?%V2{Y&)Rr=7Hm0AkiP=U8g~dTcXP$>ylZ~qSNYq; z-)FC%pV+W8)b~_X#{X@2H+IDQwcjv5vGJGm-Tt8zivKsgkLi2x_xg(dK2mu5&|j^Y zQCqmT&PNtj#Qr{>?l1TEvFHK5mHYeX+)3YHC)_&0G0hq_I)ahya3;b>cTDW>YrsBA z|00(hQY@8vENUVSR`<8}xcEV?MrJe0B_c{{lsnlK=Vg}vl77_$c0Ep;ICL- z)4URAC317JI(Am_i{b5*Hh4o7>hhZxc>`746t1(9kI8HTdx*B>HSH^9MBz$a9;IICQp;`^dHTX$ zOQNIWSknWzPGc3bT*#A2+5X_Jry&5mh4F*{`MlUiNT|VP{ z^>R~?wC>J4W=sWzRrl`RePTIs9q?7Xd~7iT0S`Cn-9a>_+F`Ctmuvt3J?=oGk>VtYskK5CDB31q5^Ncq_%cx9iO~UH~H+ zV_n}G@wyJ*;;;VJliupXxAMUjl#dtwv9NGMgQ4vfkUq&6lU{u{y7S<3pOeo6w__YJ zhOo=5zub9vik~(X=t3oO^o5Xz3n3LIardpw8ye8#&1=1ZZQK|?D?hj$_P?Xgx=g?s zi^q17^z@I!+d8JNAA!rb({{v7+jQw&$U%ykwn=R4$_*7ZhcJ!OgzEO-_oxMjUAdKW zs>Rf1iC`4gS51Vlsot%rho~yqCoLak7&ncW4dKF^1tkOSLsFY_=Ac)U!pO@Q$y9V- z50@>oODgiJ((Lu+*(1VHydp<9;>PdneL)K>e@zt@zcu?LAe~1?(>}yqi5~-eno+S= zO{~tsyMSxI!9`|Zn_2iJR&04;(acLgJ&l64;QfO(@mgH!GRFe0zco6iLv(E-(qHTf zAsPd!J7Nj#^acJAG%PrRYx6i0j>0wB=u=XNH5`g;S5Nz2HoR_~?%gkF!l78GkD;6n zq5Lz?pmc;;24>7q!v1a0`H=La`Ee;lf4YOTd2mYr&*FXpip+=!;STQr$9*AiZ<`zDc!n|YdvgH@07 zPi-#NHFYc`ETxLH*`~d|siUDgrMahNVN>fOoPQp3;AHkT;07Vz27NG^=WhByXOBM6 zoSo_{25TrUoqO$}%PJBkFirOEt$;Ym*ExiwMeI%vedu`iq~Ba}QVLC$rXU@sVTFi% z%2Ucyx;n4TeXRFdIJ<-uK%ISU@LJG%EwWQNi>SK+MuF}9EI|T0Z*dj7D#1Ty)v=E9 zI*rGMinyPmn`k^bd4?cU%+i{;HTLmDe~&u6%1OGGB}^a7C*dgVd@^h_r($T#R9T~m z^=^@`wLau%H#UzZJtViu*acb~Ju%n1b5bnY#7uah-#zK~SKbH0Yx-aJ06)oo?XASg z;c;wT;ho6LP6Ly5PWjswY$It?3ub%=NxccwK_rmSDSt<5K%Xv4-lKU0NJhuzLMa;N zgmO&d@(F1Hv2x7J0xNeYadTC&uaDH2ts-+sP{Fox!H_;x--$$tzX{FQVM*Q9{^OPU z*!UhmR{>Z43hIq1|B=hoj>gmej?2`NE7@15_LivsCFfX}&(2*Afuxpy1VdxIg<8i+ z{87g9EHgsbAL8G|K?Z(hSt=~gz2n9hWlVfU4DYDaSl6(^=i3A{?eD1Uw>P!jRl6z+ zRfXN}94YDekYKF7cH>7h+~2jBESVQ3{N2}aLrs2fgKoAj;>zL6H+`1R(Lv#jSvu4F z*i3ty)_I?1JhME*HAikCCy~9cbwW!8g#3PfC%65;8 zsq@^Rg#B?V^V?CDH6}+K^<$4&+|s*8HApUJj3~D3gq&g~vu|W9~5LKkGiOz%~f%7No5oHT68LW?Hx^#w>`(9vZIc zIMua^gxcQH_R&~V`?~Js=;#XdF`8R}rZ;Gih4eH{xmZ^e4m`XgVnHZ5L{tAcd^b2Ru>O-f36)VFn*x1!U(*E$^+ zWuS#m+f|R(w$b;j(_6aoPqU)v-8wPHoM7$K@wwQEu_qx$N_9e*r8S z>Png5LUQ7;%TGV)w?63)DR0eo6{HxABebHTKzl~4J-GJLun?@+UU+hG^*FMz%LU4u zKd$Jd3TbLz?;PhEav$*}iaz4^S}g`{*Sl3-`#Ss=9R(vG2DeQ=g}YPk z^jaOV3x$~9y36nQlE0#}$q?9$;|llU_NG1|lI{y(e#q;1&`&?)w?62%ZEP~M(tY8J zuBPqf-4`NXzoP&gU$s8-oG|Y-Z}wJgi8{;D4{5Z8Sq5y(se^3H z4ceGHVPlTG_7dAxr4MVIIri1qnPcBI%kP^KV7*$h`}ZoqJobdTr_t`EJz^O97rjgw za@l}kEaodubRV-cqAuUs&Z+CWNyCwAMm;GTe|($QeH?4q<7lWjYTbIwnaZ}38CM`L za?~1En0OgVAHy(KF{qFFUsiEg#@bl863c_5-or+}y~3{Rx3DRL)q2yT_g-HuR}K3u zH8mCmBl$ zHr`D;pR8qOLY9K-GN4{Th2-WJ(Cz&E>JORGZ zO0$&R3ez0V{{R@J%9ZZqI9AIflsD8N?azrpVi&l3n)^i8b-C6tSj+L=y-EacJ#MlM zdZy5gex3NS72W+dVoi&U?-|8eYR@L;#5MrIfWw5+x|FL2j;%7RQm-)<~KS+f!U0_l{ac=R1506%wrH8W0y;2hR?TTQV2x z3F&X478vPMbFFlQxC>Np%28t2qzg3{wEHbI{kkyS9L+qmnSN-dL#F&ys2dT;g%{?I zQ-Q35p&Z<$R_|ZFnFsnFvp!5xCe7bAmNJvlP++`*h&dU1+&~^obT#JKj}P6RnKW`L zltv(D3av$Gkd&w)GD#CLw`t!{WtHE;7>M*Dr$o~~{Li6p##ntB(Ni7jMq&ZK1}2>WMw%pVSWO!cB`#=(f+F=BKb zk@E^snDg-L0&PeWeUxU>A>sMWC&5ne($V7JH7=s<4;BM>_7tv}ERJK}Ckb4gArEs7 z!A}~3RfNPnhhW+|R?~V)BFDH|Dv@Jc+45SKIH&w#xij)cyyndt=H*%sz?n_~=$NIH z|ECfVBmUBHfatizihdmyb1()ueF)zn(V{`D&h2a)u*gUyOb)8JAkenBIt+h#=DWsU zij?5Sr1b}CWT&P<$#b4o$iBat`APoGH9u8vXz3gQU7d#(7AL0{K2bh7?VLF|y~;T# zq#xFVaKrQbiB}+dhHKZzpSb$1jH`zE8!o_4*+2ac@+W>kHP502V7(dq?x%{J7;>G? z|A`XEDtdV$y?{S)=Qdoc@xeCQW&`IGEC*u40eDUG(k_G%>E=O>H`Jme(iV&%`%96s zszI*L5lztZH%JX*o~ES_xQKfF!pyulp4A`W`c`4XwW!{%+8F^;{(wMbJV@)%77VgA z$8?FOYB^GdP07s}~l1L*>Ln+4>$I(-B~+a*$Y=GMCKyScOp2l3Kv+=_@AGG-0=7-7K( z4%Cf5WAJ;*+pcC7uA{iQDel^G3QPyy`}AI-2Fzwn@I@ZD_j>hKpTWGgR^*NyAlLCK z>RUaQ`1Bo%eEN=M9#du(ceWc|_@rw|S2Hcv0cG~MD!N~r*-GFd0<{y>8&-B)8N|wt z^9ulk7&)d#snn2GjL|Yp+L%Enk}MTHsY@xlCsp13&XO!{bATc85X;7}Sszi}HeEiTivoc;NUp7zXsf-c zVj@-5{R^O&fyS1cgOR>&54tWP`RkQOe)27Zo+RGW%|kT1laA$c)X0+b8?~hSnDg!@w+a zwld^joi1{m3N=Yk-8kFP+70&HT&~fhE z)i3uRdQem_p){pr@U=OJLTU59!RO^V$43Mp<*LFZUJPTa(wu7aX9`p=?dM&%7s_j zG7DJeW4t7fyg9vGnn(OC#&paldy3UwPqq0M91Rx9dF-b7@Y%6noSV!7#;Zecy=Ksb z7Y3$)hy8dYsZ?y?XT$mHwX`W4UFxACswpSNqz>2j2)wu(c0$QfEe^+bIHt_fskYF< z$4s6*{i*y(e1BxNK3LI($RhiXkW{Q(aAa3*o^YrAA7i$ipz{vjwghL`@Cz-KSvfl_Ls)8*S zU!nXaWP@h*(Z+77M_*(YJ}KYDXw0->D81Rd7*)(bB>Cpn2kmdv-i|5X$tg|ZX4N|sYB856- zEZJ2vD26!sG^m^`e8$>8pD3Qz1`t=3IWeD2No+yp-L;O*N6we8b)Z(5SA9V-CMe#g zQYAT;L;s?R{e|k*Z(HDFcmT+( z%hmP54Yj^T2yBP-h0}bqk<1jHU5FL+NZ}TWMLf_MQVP`gv{k^CXF&ZY#j>|q*>MA> zjxhWMen$~}3KXAr;NJwXA-wf4BQKt%xaHrdaC)!dF(x&XEql z?K!kuhdA%m4%tFlv{lNh)yu{Ht&RFO74>heaZQj5ryk9}o}#fZ=$JhH&+rIF$!PF8 zkR%o|RYgn1y{70V#y_b=4P7vx+R+!;)drAMu69VXJpD^~)2^pk zh`eNWtw{N`LkVpQ6yMOOLWfo5#$SqV_5E%l= zEb|UWR3@41%1YLZz%+QRWqDovDRc1L(=VQWss<*4>67CtcrtP1)U_0unJ;N1F}*vk2O$V;{o(enAk^2f$r&`=CA0B@8QCLOAfW*!FqvCU|G5UZ;MXN#8}H zt}7CC$g~dOGYoPT(antsDOEA_k=y4dKFQxb{000)95$q<`cN6q{9%Z`{u^42_pqPm zc3upJ%da0{X;o3NJh@}UxUM2!oG)L|S5`#I6_K(ckQi#M2yg)_$I>zA#R|TnV%=Sz z2yc@8zXx@>hb4EtJG@EuznQnPm{0bLXxwYWsPD%Q4at7(Bjn%6(#U;Yp6q`){@j-A zPf$eur*Y}K?po{8?Mn75G_HFXy6d{|CfVN*zulPZ&&NgeB>S~{YU&x6c4xA`R)x!_ zj(al}6*HFX?~UJ%B>R6pemI=$U#?95DSk7N>{kev{D-(`DXs;P+Y(8KZ)9iU-e@Hv z1!OVJ&xLP_X_EE^#c!PJXTU90a4}86-XPe;X`Tz;6w~atH`H~BpenxqyXd{tpCu(| zk!}iS-Xq-AT8-r8KB}c~XE8xnNrD)RLWk9LburoO<-E2oaMDv)h#RCNstxgtNnqt*5jXLlluuS685mL(RipY3opC z>riD^HP<;^H3ghn97R1~bKp@;hB8#?*p}J4t+MMfu8FQ+<670VF5kz;usMvwrZn;P z^52$b;fORDSU|NN=FO9O^Mu}X)Dn~LP6?$=>BDz{&7%}hA@&KAWs&kSClRroYQ*XZ zA>bD%+93hOihX><0IssOVx$-}c0A&-Ew}Y(MY4YdBbGZNgZz@+jiDqLX|ZrMMK2O7 zE!_m}-96mp(ho|x$eDgqh#mQnvAzYy_VQBF5HULa5R>wQ?ZHYO z6Y1Uf0LW~8sJeR*2kVmES4H4@tg8@OH9;*GcCAtZ(e+k!6$4O3O! zm-SLJc@cM7RZRhv9(;9Vf7OsWH-9bZ@(o-NS()lGCmS@r9#EyZ^iDQLc7TxwD076y z2aOiUz#U+*c%pvmeHe`}U2&SE8HM_BbNSCw9uvjr*#({k=5);|{HB#xuMF*V+HCF~@vA=TRc$Z4PfzVj$((3QaqqP+ zB`T6?XSaRCiDDyu%Tm{m&;3xQGI}OSVim&6h}W_-e+Q^m%YsjdI5jQg-^9fz^zfbY zAVM!)n`?cXecbUi*pgY}YZ*S2s-0qFJ)>v?#awBO&urk^)xg##6D=5&K1uZqx;e5M zJD%BGe~Ao{tZe{SJ4A%g!q;qe7b;~OgT;bF<=xh{YA@ZN+1hVt>Nhm?J2Zubkb{y9 zO?7{yCExpIwi+}kHFM^Oo9ajW=B0Eh%M-=()_#8TQj#QAph+I_nwRD=Q#z-exxh~H zOJ*I(EE}vs8%kC7=+_QjZ;caA z`Kv2ar=Noi8B0$GLV)eBxti#yYgVV~_-a1pYBNN$1CWxk>IF#DHgHidKuTM-yxL!V zS-l;}1*-s3(u(>D0Er6oS1+gs5ENf+)la`DxaAp*z=bg4%-QPh*Gn?%fk&XR;(d+4 z#PMELzEk>i>#f~lF9?pXmWr8YF-rQMnVZ>YPGLP|;%>cls(|qgK^1K$p{q%%N8V0< z^&tPGzhqOf3UDx8Vs{bKeolwWqF)CUpwP)R!P{P&?+t5cbhP6(u z8}fGKZea8v#qVF49daN{?C;M_P~`I?|Ldz7?hURuVHe|(>^PqQr6RLOcNN{Qlh~5% z{~`^bahw=R-tz_Bqk8Q)n#+!n$fhhU6WIy7N{-brV#u}ju*0LTYl1({b_qW&ucYU$8{NGorDT7-81UDW2~YWc#{kT=AD*<3 z^H0{+h6H==4PIsb3rb`5L<4;9C+)&D!CAEGd`@8P+u+T~|AEpFRw4er0Y!Al_mA)$ zE}HE6Fw@K>67u>l(YE`*rJQ+54`jB`Q(f!=_X7w!RO&0XHT1)~{VQ`r>Yf=u{AGD1 zFKI4K573?*ti~HYuIyP|@tWq@4W;FFSh?(j>WkyT*X3jN!9S>w4(c4XI`e-P7P>7f zXigu|;i-;(RPY8|y5Dad@Ym*sytUb(e78$$tcL3u{Ci^vs*}!*`sU4 z)=P2j%+2ZN2!Ft@HQExm&bds8?}$5RjssWAGWO|e=tH)JdLTUj1DR8~l?�*~JvJ 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ƫ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

|;XiTr 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 #Tf8$tUbk+#^zwvq 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 literal 640000 zcmeFa33yY*_cwkEWog+F0olALmA#Z55tJ?|6q+`40Y=c0wrLw^o0_DwVEj@96%`Q_ z6%_>q6%`R>S2h)CleUNoipr*ff~bIkqN4mi=gysElBQ6d6A6$D*P|K>~b8`@H zSvB_D8yeo`Tq(l*sxjwQX<>(RtF>_Kxs_U&aBi&@j?=^9bL+HtvKCLVX62FYz?zs0 zijo)-s?1z?L$pXcrZn%`B&1Qea=fvk{KC=WjnFzRi4OzqVH#ggQ5q;A8d1Axzn0-z zBc8lBTx(A)qUFOME#$$9?;9(7AiDJP#!4w=G=(V_sRIIkQ^S-*JNn@oMd=inCdC{7 z&HHK*Y$_uhWep0ZPtOzSw3Ir*=X`@Yl3i3+ZzZ^pswh(o~(ksxJ)Atf~- zIY~pK<0eF#5_FLiQ*27wAPpDV{_hE&oPD??F6D4Z{C$T@I?gy$GPm?lNrmlD$qO?N zm7J+MQc^nYP|4Zphf3C$94c7|qL#6?oa2h=R_2OmTjqM}=p5Huo620N%3N36UvpeD zC*JGYzp>2K;L;q|Pc0X@8h{>Zzu5J{@-o-mPnEfrfS!N6%(Z)OnQPKpWv*t&%3PN= zm${~mnd2H&f3d6N*)mtR4rQ+X;9qVw$MyM{GS_vB=DOJ6Ij-w6?{&F`%yDghy3BP4 zsAqYZ>#sidx=hg7qW2tE>YzEUzh^FX?QgKyH8OOuD;sw8SOYoOb8JtUt7WS>uAV*T zxMsAO;~L*%j_VPWo4=&YmGfGe>(QojTqT{$Ty0@bdG~u=Z!AN&qRf?qa@xSAry!Hq zZjP&}Ynf{w?3?pUnQP?lb6m??KwsD#*OX`PbG2Jl=34VunQJKQInfTfx|F#d>V3cK z{`zxVdwQWgU`qn@w0v}~tJkSHuG0(Wx_WFWbA1ZD(lf_px;V$RZ~1+$F0iZq?~P)&CZg`*X`8s@ktfN?YVB_^fI^c+yA(Y z{Sf}yz1%o{T7~i5X>Q}@&-WOgDlIp@74A0nKD5X9+fC)h6VoeO8>awU*`dOi{_7s& zgBSJ~-#k-cydU{@T_`ucsZv+=ljZydB@9*Z+};A z?1nN_(46)a#tO6BczU+mSOA;y+Ls#--dJhOeGdB9x{d4Xu<1{?vEq*k;|IISjb_+z z!~1)TzrMG}IIX?g_|WAFqdL=Vbi4N$lQP}L8Fy9~n`KlO8|^MPc6`6wsAjp1S=;s) z&%Nq4PIbABA1*659tN3g6-Lu6w{d5-+t>l=RVcqd$~bnn+qek$k|`C&PMH;&&Z#rp z#vjg>8z%{_eTErM8A(3RY-cGht3PVZ}*h#?+Jvy_9TAW^rzh2uDxM`-L_& z$C_^erzpwe!WCi)td0V+Gb_)Mtvam*7KhVZP^1Enw-#m_N-bH%PIG3yC85BaYcZ7e zy;V)L*``|ybJZN1U5#)=Mn*!Il&ZAc=Gcl0v%{4lvolYQYv~rd9a*AEBTBXMgOy7O zRBjyEoKTo!i-+P`OH~xd%z{i?KA(vrvomv%UHe_eZ*7Zm_BLn=kjO|7Lc*_aQ_lK= zNRZ+)z|R8drq^2UREyc3l}9y?aQIbT*YiT#a`qC4^v*^20EqNI)2^I74@xn_reh75 zU_gGVXHj;h>T41n&oevFY^t|fh740&Oh$?!)sQm55Qn*sdO;;AYvBxYzE$cnO&|6@ z4yWB(RD{m7<)~RRAeUoP6f}+2@xDHRE?tAg+sCS=p6uu4CL|^rMn@-Rq%o5r!w{E{ zrsL7Ev4+&tj5tG50*vCaX$k$YMsq$kIVC0Cl;+iwm|#R+%?BJa9ZpMu3O^{cI7@8y z>3S2`E#_=$S~QBU{0aT3`o4Hla#}`$(UfQ~8j{keIrQ?9)6+7N<1^9{^tcI4ooqCr z-NvUUq>x~7p>T0tfWN<@eXI`M5=F5UJE`poECn|EOq3zosmP3NrNwEn!`+g{Q695j z|B;;`N)6#n>}oD#_&6cs4m5Y(+h1Sh14ag!+`+7w4u`^judJy<9Ys<{#BQ!^|0TcD9|kgdu}y zR*F~bidiX8EU?S0I6;^E%XP!NuBqw5QJk8(wsXCA#HhkAPhy=5@7>`7dufu zE6Op1N)tt?ps*SCjuf^~lw}lR+w4NPpu-+E4^#@u2TcWS=*aO&;1fZkK!ZTYqx3=8 z4b&dglrYfQ>-MmdphKYjpxvN#pcSC`AeTg>BgNvd*k@R>iBBjfve}(#G+M*raHwfC z;}af3)49r1DV}IEXREQc!W?UEvEA&n+6omVEsqATn(DOKF*#w~DY9f)&G|~4#gT=n zNE6cLbJAyaSkx5Si}TE>SYGT_Ck->jP?}}o(wc3yVtbZmgDBllN;blOYz}Lw8k?Sg zJQJ~2*h(B)Oz@+Tk_7cLkUOE!X~|a;qK6Dpl<54TJhK`-$%KfPhYst&VRX7s(z(AY zaWSKn_=H3QVMc>7IR!1Ak9uQD_09>}nwo3D3CTMrqYZeRPu{B5xFq16dudVh%JYa< zmX;QkV{r-5DouM>ziCQU;tcVI6m1qm8%;7Lr(j~G^wf?q>dR38Uz}s)eB)NwVqFcVB(IRjHTNY)cMSWef`w<~_}` z%~@GAD6%buRvIm_GiXg!vD9YS?Dpa!xFmeXZppIc7FuV)OK8Hx<_I$n#+9~SYrcW* z&820z7#ZMWV+@HVH926dF=$&PT&&Pc@ooc1US7JX|NY|W-t~<<1MS84MVcCv=_uB! zkW-wWKU1Oo(1h5b=w7w28fSITSjU#lhZzb5q>L}NS}=#=s)=eVj0{JcSsgQ_cvqOH zjwmFwU%$IZ_*N)Y6pckWS_;C5sA1aW1`4O@5zM*{RaF%&L?-a~Wzr1Mm-DJ>!R(`I zvv;^6Fg^4a^d`zVP;Rzp0@R^6-$L6}S~OzC@{ofGgf1CrJxohOxGyaiRToKl=ECfJ ziZ_C~FyYPQWndr2pDi!&^|S!uC@C&QM>ak#$XbI3c;;>B98t4XtFT*{$F+?Ze5M@1~(S_t(N)pb!x1!mfKlCmr_t-aa~6`KUHpY5YsbI_t# zcM6@6N~$$?xEewFqH`^ri*R7jfz|d6D#{J__A6(|mH^0`S(Wl)4Tu}A_CSXYgMI{q z{DJ{I_HP0@J<*;>8s~g?>O)=C=HG}c{vQnj-o{e9knN~iOiY@ZjD@a%CJSH@gQyK6 z25WzZ=9j934d{=ZmZIEt8|Df+3#nR%#aZo+;cD8=c>owKa_CX5Ic92Vl!zj1 zwb8Li#5p``Iu;;p9}!+7AXc@*1v-ldbe4Q?*}T`r#~AqG}xJvubM= zO|Rja`3@L}jh>3*W^Q2thAxM-Bb1EgO~N{psUPsP>kSdZ9I6f7h0Pd_c6!PT9H5{M zN^pqPijonZ1|Nzh6pf-~(9P1@iBF#n4&o|C)^ta40kvjCUU6hZ)PTI=n)RiDpx2i= zNLxzKU7LlbK_>Zy03NK(LA-YmL9K0}YW|M3Y}JztDp6!;`h$Rdk{{#t4sD;j7G3#sW&3xP%*Dgx7gL7mCwR5%&X~bjzX&wTWz!i_AQvv{ZoqztwGa; zBcjIWP5Vr>h^7yoS2TrI7TQp2!eT}D7A>HmF>rvIX?3bNz++R)2a`Te(cXx*KIAdJ zQJbnA`!Sc{O1RjgVvbDLw_-}nOef|8yM^jalcMj4;JcuKX2cmmT#v$8ghp^-aX}{X zNJV+~KEBei$1EzMH3@4WE#vB~19CIPc(l-br#ZUj(OO%e2e1z6^SgHWi?4C>#H?S%<`Ys_&BHYMQ~T(6O%rCpj#@Ek3-!>=~5l_ITlx%Cj2>+u0XZFgR3i_aB80Fkt9PI#&-=!r zCKqCpiC+r#H6kL?2&1%#IP?igk)~lHpB@ounl2p^dmp2AfkutbH|J{mh5l+LyogUz zR0dqsFFq(BJ}e;Ke~3RoJ}xU55}=?5F4SrBpX#6!Zw?Lzw7e*(=y3AkB0e(3eAG8c$V3otsT87Z!E?YrDN=wIq_z@wo@RSE}v3}bB>8xJr>gk$9n(WN8 z(D~In_VqO}U00NdF0{kh>3*5uaU?`}g?;t$%WI55oo}8AnM}JGSJV+UM@E| z#gbc`Z?-FG{1%}y+N8?K6xXr}n?;BsgO6X@qlyWFk44{LqBiH(AZR@KjkADknB;TF z#WWw+<_WZe7S6ERoyBMXeLm%&m<#OY5@|{foLa7u>NQT~laC^bBJEV)qP&?7YnHEU z!opKA+Hr&z^U948d6QsMBvuu={H4$}F6^kVbD=${neWbp@pL|^^-QFebwpz|giTt{Wek37}9FFaf;|>+}Vr#DUy%m3gP3Ak`Y&v>L#{#Mt)fqnu zK+xMGVn}{*PbI8KlUAwjVYt@H@~GaRUwgEo=6Y&ddC_6B6)dXcUfU3&lJ3BQG+fQjQz!Gy3WlPXzR3Or?>!->-K?7Jxy9OsU-`4Kn-N`@UCtVo zm9xQ_hmsM{^Oo<_9sZhv&+kw9aQL~$9MRT z?7G2S&Z0me+GX^ir?`u#wSRm9{~TY z6gPfS&L)HOYttEN$>R)kKS0R$K3L9f28sB>qvdQlNW^dZww#$jNzU=bn5}5B*AIf& zq0;qL2x@Jt4}@6wh|`j0v*qVm@{0xzNG&uMIr40DIbFYoIdDKcE_e~ZHxL5{B)W4b?5w@=1nk^UTvfl(XkR z)W#vF%UJ^u@w*W21`Quh&llXL;D1ei>~ClT5Xn4tp`1MlBL4Wra&{6#^`rRCkP7wz zNW{Mktzbt$6vxQYu4|>^>d$CPAzebTWDi#-Vtve`ZJM@!!2U0v53HQxLhTw&q^dmv z>D}TLBfFX_^6LXszXYx%+i}MoM+n|He8$jj{HZkaXtxCXuXiRJ<2J8x!MjHdF0QMu=6%R2p(dHlmyGYfPZ=o9z{A9;3 z&SB2xZiv03DYi#M5%za9jrCE}Qqm1Qy{!g0&5x*(T*^UvNUwC1PgL`RaJ7Op2a!GG z7uR>KV0}S-6qO+Dmx}w}xXY$Y#2xY!#ejP_M#ZXR;cm4}ae#7guX+kntxBPijd%%A zRY^jQDN3wT3^_aQX%|9j0zx}dt&l22>KKGhJp9&JUxcbn=*y4Pwe+AauJnczj zx_yvur8OUqE|iAtYkV@SENoX;1s+75AGRoLY1oRe)nO}?`AUVdO4+9DLdmO@HOe|= z1D=cysb5bCZGa~YLcrFqher<@GzbmFoCLf9Lh)Dx7o4znW;ZukzVM-(s_PyYu{!^J)}Ldo=h_+uI}OL zd)D-c=KSf}r8gRKSvIs?p?+DkR2il{)=M5uPkSW5^M)vN1p(LVfW+ysr}pyxGks9O z?gTlM8Sr523E`1h@JI4yLL%Wu`S_21CCYToUwtwhk!39KDf;1?}92o z-M@w$C>E3kvVg=Mo5BI#RIn#N{lBeX(?DlIU4N`#bB|ZBHs4jSHGd#2x|&@Nz8C0b zP%`M?>s9P_gxR1%(A@7U*fpP5Fw9B%oJ{vc917xzw!&N!o=Bj*wswC%0E?#j%Ihu( ziMX~d1K`ho_||0+7wdL%k)@DhI2(EAPpmFfs-aXX*MO@sx^Y}RCYmCLxyULu%qW+9 z`)#!OCeZb(Z^(Ro_)Q5f&7JpzbMTFS@%1a#W~T`^TD#G$9LTR?e}7rcdOe17%c%-> z&kww92=9dc8v)7yk=_A6SFj-W zPW3%$a^^Ig_|bng_o%{4y=nZGg}fQm^2r`e&x_Ie#pe<7i$DH1wePuqc>8{O*?)eZ z_Ps-KU$uS18~pF_i>4uN7E<5MhJynAq8r-sYJPDVc25WWPv+zK$RBJzrt$X<(*FM# zhxdNf>x1)-jxkRhSXESb^o||Q74zebx8EPPcJerUZIo7x>jhEb2{!)Wsr6;5QS(A1yZq7S5 zA6e33YKkMmd?3P_7dL*9Ef*~hA4tXwmP1A2%5B}=QKZa^1asI*)s4 z_M7Ls9T=N8ZdUp7P9uJbdu3^U=bvA?_4<>s)-ex!JbmoS-91~g%fB`2vZ1o^%cCDi ze(YSQWe@D#7dI=i*QQwwS0-&-8oKMLcBVdq9=;~GdS=YH&0}A9=&Q|7A00F6Uc36~ zn@RJAq&;+G^nnwpsb^kXePMmlqE9MQrd5tO+~)NB^~N2YV^+VA|NH9=itcZ|>3q|- z2InVkKX~7`WebN*oVU5S(d6=kxOoq6-rk?3O+GYb>e>c#ZXCP08*}TI&z*fL z>Da2I4S%fLck+uCcWj=wc;%33M`9NKc+VfdURXZxljg@OzIdT)%>9pz_|DvZTwKXt zZ0wrXpN(JH$6VH>-|K{`JVoE-M`7mX44qN%Ouxb!|-VQ&0W; zSNL~*O>5)td-PuWif2D>lrgZ=vU@(6IA;5bK920P(G7pQJ)!?ojk_d2vZl@Gb0N+b zmf518oA>qGhE-?xj-P$k^UpS@_rs;rIZH;bfApIB)F;aC>m2{wkIucVMz2usdGwji zpQPI^d=)!;cG%cub0@z(D7jPXMXd(4`Q^-;@sBNP^XJlfCF|=uZenXU>jqwydlR~+1i|J5d$0?ySHVR$b9O~t5T`o* z3A!_^l3i@*Vc#5fv)@1ykXEI-wK!pdUIZT~_lA^Ca*slPUF2S`qg;6%<-V<>+~qpT zg`$n>QV+F`awF;}H>r+t9sj7CeM9RgmwFXB8`7zNiLTTx57$xdl{(5zk^JyV<(Ae_ zZb=>GL|xZG{z~NvIT|l@kvn-6IT}Ag=aux)__>lCjT@0J!eIL3cDs@sjdN-XAwyv> zeIyq+cLb9Q)O96&G-fGX5b1m22H*U(4*YNs&D%7;MRj+xhY|lc%%jcoC&2%Flbhug zxY>2LV@?l9YuL@r%s0B({o~ziBErD+=e7u6S&!V|VV5B<%K8UsPeXSS!ai249|*UB z4ucx@bhBq4_ORDL*Mk4Ohnp>1C{dWByZNQi&=1G&Vro9SX=3&;ZX9BMiTsboD=%YNY4X8He|yhx-vpCR7R$L zLgBXYQX!^kD1c3LZH<1#r~|FsR+M8!zI^YTh~X|3Z1wfXvSWE zTZZ>2z|r2o32W&$0Xq``OabS81QmEWusfkDvby1P+h(`(bN>t<=T`^x3@N=4~()t9FV zuOy$LcI9ox>y!&g-UfNFl5$T#x*fGB)-9oH!@S@4n^C+DnaVV1BMVjS>T#e1?qT@Z z5KIf68`9c^`$O=W1-2`%pRcb~^bYquB1)A?(Ek_Z`v!QNw=qfXl^47IeIJcIh)U=LdKUSW&qy^rn(Bx6Y@eQ^&;;TfBoc@&kpmg$*+MQ3leMc zX7EoAa zMx}E3%?gtGpy4fPo&mehE{&W`KcQ;Sv`|iLr6Y#f!q>WB?vlD5!zA`Cz zzPs|>tbKa$w9W~&=TpY{(lV3`%(pZfXJEwA+C#G=Uo$8zSW0dC=hjn#*ZTx;I&XyE z={qM}2Y%iizI}g_OgFn7bQADb5jqim0BW6qwgasLDN~W(jB^0O7eT*)#v%R>`1M~SZtz~=#wP4rE_a%JYBAMy9 z_em=-{b-Gn*Ue(Ic?wo`D$Td!5clQqG$Qig5y{+qTc$bR0glJoQ(;Sd zYsKr2yV(|ySSubtTAwG}?A^t<-UpuUGtzyuTM-gZGzjqrU-7WUGql^_=1l07eO~ZM zPx_y?1n!dX*TB zf%cdyc=;LcxY-}zA76$LeD!u*TY4AQ$3ZU8o1pH`yV+P}Os87W1*2?fMkgc0R$q$@kst9B3c<>7h#8b9m3q z)_;IL+>Lo2v=ub;L*xgIg}z@A+LmF?M>rd_0@S)1_XZ$$0n`?8(Kmm1APb#$gNRSS z-d>z-=>#R8V`;3>JnuWv(_Bp}0LOjjOMYIE^NM@Ll#+_GUIFHIT0?mp-%-FGIssP) zlCZ+!OA6XuniNPTW5!QVM(btNE=K}M$T<;f3jfjyp3gw;aln!^9dTG$=nE@!tt0{8 zUP;$@c@8HXPewH=>0@sG^E>0ip1y8(L8C{{Pb(VyQr4-JPh>woVE$LlS8X{ov&*Ri z$L4?W$y2=(H-&9?z4f0)eYbqSxbx0M*)2DB{zY9kZOmO;e?1btW8OXczwB`O_~C-_ z8~2|tzwgtz6PD&*vupMA>{)+g{{5rd@=)@WkTzfBH_XZ!+2YR&%^E*ap%&h9XG?41 z@#v`s?04n9`0?@jBS!BJ9ryQr%|4mFG5n=JS8p7gzx3^x&)n~RHR_LTYrf4tIG}jS zQNu^gK0Cxle*F5Hs4s3Bt{fR(HvaR9w&s01H=f_n^~T-nCp`VcJ0G=pY5SJvx76#= zcz%P7x4&q*r`xF}Z$03$^=p-wH|h1x=J!VS&Dpdl`p}GqcYPJIP5oqPua*bSJb2$* zcU`}6Q^S{z?_54`|CW<4x*L@a`a@~4aCO*|?Uy$H`rT&JX7?Rg(D+iFvVf%BoE?OBOD`>X* z_Gz=4eAN8-bC<&&@4i+Ef9v^1_ryKk;_N3Y8$XcuOGuZ0-fw8VV`Ap-mARI(yN2Xn z_kF|Zg)=(t|L0imPanK})|dFi=HXo1(XD^{ac0ND*d3eO9ay+->dBR#efFQnKYn5N z`v-@m{Bkt;T<*Ic=T6-A^5KRXZyPt{?J-~WZgw!SWB8lDjjcaq>a5V{56(O?&G_A#D&n*perZfp@ zb@_SAqt>;V7eBo`-E8EfUtj-b^G&(0{N8rzKI3R>!dE4Q zw~n3m@!CVTfA&RAlcS@~-g0ng>e_9ue)#;mM`nNY_UKcWH%4{(W&8Gr-hamv_4xYK zmsf7+)?moKcOGx}d3Dc;N78oYesN>uteo{*#+rB6>zH@PlHRsnLw7XU@WR5TA9Ot0 z;Pyw()ay(8FzZ1#D*?%C;g=_HpLJ~|b8o3+^ANT*$wv>R}xZz@FdT?3rKPZ0e2vWzf2M z^JEWu6Zw1NK7xAA&He*A1RDN}n=J(eyPq6*Pm1pM$oAZVvSfP_BDg(WKl892pZo0T zGKJeiWzc;@ac?{Do;BT9qB4TnLTNHxNlw0}btO6Z{@9h|=w6{HJD42Z^A_nM3??V) zOZT+vB1iYCWjVR+9|>stQ)pxIg+>QGtj{;TwjXyVZ+mg3r?D~@d_XTZxc zp}%pNk1>vfOc5~IhC=w8-2bz?a~n>7>tP#y_1SPNgWDkI?T9v$^Um+d^R7SdVMCNk zuP;|kt(o`UfV}m3^SpyYD%rzLe0jH+eSSynDEpm!|C!QW)cuZpN0t-qFG3+lzEc-D z@}0WKk^VqA`QEW8H<+C4e^-)|@81TKll_v)AqplZ{Ft7(sEZuQ2wx7SkDj#L|Cfj&i5!D0j_YSFgt?NrviNTYU@aC`US;kz{MrM{-2vy6lzY&i@O2q_gX#+Up_8 zr7)?Ea(7=vZZ*<{-WQ=)_{#y{^h}u;M?!`|SuSuMs`qzreSvcQ>L@qm3UUu3?cdn9 zNz!$-dWiadi#%7VZ_9t`R^O39;oi0{Isaiuon59zyQDI?DZAN4bvmu3YXIq>Hj^v#(f^A=%pGv@}WfN^&37 zQI2#HU4s0T^a;71^{*^90qH_k=$i|yw*DYlA-ArMa+D`f-{BzT29pa9t!ZB{xuJEG z%dVr`;;YE*kkZK>q8CB6`SWQ>{z`J~8`Q17h9GiwV1ex{Y`dC$%LDYu=eZvmd9*X% zCkVxP?m|ER>myG#<}$Mp3YoJ}{xZGsEGL~EWtr3c{bf$*GP@88J>L!Rmst|d^~^#j zWKswF%S=HzRF*80G{nQ`eY&KZtJu*o$cuFG4#EAX+Xs6p+4`fE>}WZ^Ui3Py(QE={ zLR^;pv6+XZw60=14_2}nNT+K{F~HVj4tzccEUuD8sAtgQb(2_E+Jrb@PDozIp_ zkePUm@0w8#_;w(1J;wpwlw8dgA>0c33=|H!1e$uS$NLOxbsG;GkGQyx{Kofg_8q9- zNjzH%+5q|yblQmaNWwKS$cyJ!Rme>S#kQ$rE0U3B^02e*D%tOyDp~JSZng?^x=STH z0sTExw9A*cM+IvCfro9}?O`iF#B;QJJot76t|Q?(*}w`98|L<~kF21^{;QN9^ z-7Gg%vfH~?vPxVdTiK(MJ=C+3eRMtKYkAi7^{%*n7U|1>%TQc9y4Ay8=T-r z^WAFCcQT^(w7xxRPwU>x!@3|{yr)6W9@l2uCfIkfk5Be@@QXpB9TNKTYZY@d(KlJX zYb2Yq`Lzmh53Dx*Cy~Flw3ZmpwWVD^`^CT~MID;l>|tZ0eRfsd=4;>fNP7nHUq*P? zdW6nf{PRW)uRU)v(yo*@7rJUI_e-Q*DQ~ZlwdWm*v@7LJhVRr?ZZXoXl=l&|L2Y>- z9aS^0JP**E)Y#x**Mr7XRIYu65c2^8P)}zuYTbqx=Q3a=Cel{&}x-jq(o2%6UHx$a|%0logPb z=Zsr1Z`XCs*f0Tei0WYjkw^BsGb=r;Gwv-c$?&jOK(xO7`AO~b!!wxEAd02KS?y2N?*o{d40yH$&!ENZTQe%G|x^Oxp( z*n>#7PRIDdweIN!HS^zqvT7?o?Jf_y9qDa~uvUTM?KSgna@3yx-D1qoNPor2%YS1= z&HPWGp0$<#^epVzk4(+ z4Q&5gpuaZz1IynruV(&d?yo)n^YhVmNWb#|%qyTr9_0BKJnY-YYz61!z(2Xb!_I?p7uMAK4&>^Z|K-PO)@#X<+Uxbylh}hG zec}_?4}l(js%HMbme!vC)C(SV3&zj|P~)dD=0Jh{a{IHj=g)W!bw>KQW!x|Fp0Ald z61r>a*TC|Bd6DOr&kAB430$+jZt8!}RmcUdTYokSE*H2iwZOU*tlYqLq@-mf8@}4- zHwobBnUM|f3*jSo^{QlLz;}bn!H?^W{Uxa7s~(mHnhkmlbO6-$H4ig`UILv0HCu!I zH0VjtS0MEb_#x;m&~Kny)_PbO=tEHWn;w=7S_1k4)MXv!DcCU$;hUgeK||m2Fc;`O zP{X&;SD^Pnjn;z)Jq|hq>ahXu$${Pio#|t4; z)u8tyD_LrvO7<$~GAJGS2LM~Q1-=Cuz7=%_or3I>urKKy4|@!>8(0{^&p@uIO18XT zCHoQ70@MrnCZPQ~yz5~GP!VVY=rSmFn}JKc<1-xzpE&X2h3+egv(0&%=6whJY8kuXYc6@K-+Fao}$PiM*q~_ON#lA9D=$ z9zvgiz6Ti&<4gl0e|qyvpDiN?RB7#QvSatCd;X*=Ij&9QnO}T_O<|b-8pFb0Fe95N4kAuw9@or_i6EFF~8X^REZl zNA{ELZ|n8={3py|r!hx^eg{4EBiaKr6!-(6?my#s1W-As9qKRIf$G=@@}nV3brffB zdUsu}`_*i$t?mz^P9LKF;+&%C{oU6VVHc5xxM+*PPj&_0lQ3Hd?Kf_?zWb-0@D+UjuR9}k;fztVeNe+v9ekg)mX z@JhA{@xL2avTH*tnFI7Os9%Fh_6VpR(uB>Y5g*pDl064%-l&qz2Au_sLSEU{tLd!G z*6HXsdEdAHn#!R2Mp>pUt{nvKEopBUfwysn>CceUcuL1JjaKC@JZnyGCRy=RBfUSw z-eTO_|TDBjmol^&3%x1C6i-XoLWxN`s{Ef%Gx_U@o~(~#a6uK?=|_6`TX^ElNy2>giDxPK+qLwZJUvNCnhWu)BZZmJWXF?_ zzV~v2rBhk_9Xw0We8JMVeKl<%oIlq|Pmt1Eu>2YRbUewKhNm#873ul>WY{L2FZF-3 z(AO%-upIvymh_Zpf}WRf&eM~p)UKr0BDH85a66t*r8m|5-{p*k4&U3gWUnksZwgYc z(L0Lt?%==4E5FkksF$9X^*t9$zF36+sCQzt9;H^{HKO-H`TM!S(t_oqvV!FyJOyppo9|^7j?$^90n0e$>BIqIi2+yeT5yAtf2Ioyw-4e>qe~{^oZoe1x7+ zw&<-K4J*k`e2E|Wxt|Kp6a5evkJUVb-W{g*xvBp2HY}H>cX&%t8~)yB&Gf6=6==I? zBmdD6=wsY^dRLkImw!1zhtKkwZ4_K4RqIJ=FY0;xJCCR_z5uWNbz<~#-z86={ub%c zkfFDY$xFnXRhw)Qbkht+C5V>h^Q4?YWhZMR(T>))p?vDA>>w6!KPc36+Toux+PH1h z8#L>Rx?HL6sk|7}fZo}suZr^T0MJ)Lxqf<2iQaFfc_IhIy@1|Q7W1BXo?Xn&zA-Qw zqh8F_Y0yk9#NS||FDTGFWb=+l(JP{5kPP3ZcW`O-B3t;I+BEOwY9pXfYbCNyd=0?l z9i{YLNlKyj)dKx2FfK~=_p~&9-J>~V8uU`n(T{Lylj-OY`qBc~LzdA`p5tq_2DMks z^~2X%{QY0*H@<$wpd8$K9&+;JM|iUTY%;@`kA!3g}y;cmeQN-G^blJd(kXq z$2{e0nP}9UJdVa1{b*l9ZO+G?Jf7q+;q#;kC_}7q^bWi)h5Cd>8Sh(~)5uQR^U%-V zBlsAQ->Mh$rvDt|zv_@48atx=x~#GO8vL#BPYYaS`MOARvKSR|UBwqYe7h3zXWyR8 zH*3oCIq9rzuBUfTeET=PbD=eezaJ{^SqBcqd36}h%r{rEr3mflY5E0z<1X+Uc!A%@ z3;c%O;*q$I26_{8W_TsbM(6r5sS_`TKk$tT~>F<9RhTvC^{^0lDgrWH1W53$(zX`eJ|AW6N_;-zCcsqnhd_9S; zFY%$Reew-D`}l_V(y`FfNa7nydBY?=T;iKZeACuGJbk#t@!@v4-!tHj?R@i$6* zx7I%W5t9C!ptFhICfz0dJ*2!nCBB!$_m=oRt$q6YO8O%u{rx2UQBvOi5WOMHyP$4Y#h#2X|& zUgAef{1}N(kod6@KThHkCEh6UNfMtd@g|)Q!4xoF;!`9(RpQemK3(D`Nc=>JpCs}4 zN9?sRPG#LL@slNfip1X`@pnpmhQv?R`O8phmiSDmPqQRG8#2PrEfSvt{$AbxbHP8R z^Le@tT*hmbR=giC=BR19pIpW#yQWX};rYlbWC|p{P~vS8UnKe4T@r7Xc!$J0CB9hV zXGnaB#Ft9^Oo^W*@pntSOXBa5`2R@!Y>6+^>)z^KMVX_wPpkXUmbwh#b0wL1DZci( zPvY;F_y;6@zQjMM^Yjglha~)Aou_YcERgU=Bz~d9KPvHyq`p`z@sCOT;}X9_;-8TC zCnf$ViC-%5PfPqW{e124tb{L<_~#`4d5K>x@hdvF^!nQi691yauax*zQr?#&{$+`O zMdDYt_SyNWG#|bu$-FM{Yb5>+iC-)6Z%X_+N&j0C|F*=hm-r15zfs~hN&IGs-y+$( zRpQ@~_;)3Ko5XLI_#G0zQ{s0?WB)ygf4`%z-9C`&yF0~)e<<;Lq`c+QJX6ulC-0W@ zcqILm5?&?o)lxg`mGF-wexGE+#}faE#D6OB`&;|$IUw<$LFXua&iq``e^AQ%g~Wd; z@n1>&A&EaM@kb>7sKg&@?X%}=iT_68zm@po5`RMCzmxdyCH|z|*EIj1lKTAzY5bm+ zTlV1plwk15(}yiKp{`(Aiz$dq{jwiSH%xy(PYn#P^l>NQv(!@ljIW^_Tbo5zbUFbX@csdUVp3Vb;r}Kc|={z9#2@*e1 z;wMQwlXyB0h`f^}eu~83A@Ot`5b|^$5d2iBE_5Ccc&+CFN!}vyITD{M@p%$&mH25A zKV9PUB^wGPzEF~}NqmtcbCe3`UH&XM?gC4R2N&y)E3B>sMhe?a2rOZFY(JIeno5F{PTjuzbNr5 zCHYm7FTEt;bRH06ht30n_d5?rI$x9c*Cjn`B>itl_*#j7Q{vZ2{96+Lw#2WO_ze=j zQQ|jA{AP*YBGqN9#J?li`K~0tP2#sp{0@oVDe=1`{ym9*Un+~v1EM{5OZ^X`<(|Q{3D6qCzV^L^ME9OAjM}};CVpQx7PE3q^C~j0ZHEP zJRsp;OZI#t@!v`^$0h!RB=enwe=qSTC7Dwa|AQoRTEc&n_@5-6&I6)N&Pe!K2|p*{ zzexPA5>Mv=q342x|0dzTOZ-Ji<`0SgQ{w-UX zxApG1qf+~~Z|L8je9VYv&z4rQd&cnZ5(=!{(>1Z%gJ3TQ!A=ChGM=faZ%YvDLJ-XO zY|Xq6OW1R$!vkY0+0)A^SqJb=d@E95uPyOmRJRq)PT{fIe!|PS5$v5wjno!AZN`tMS=IOYX4B7^o2E5Vjl`I;R0(%z(*!(!F zWYwUdlkg2B$ZQ1loQ(U@2!BKVDCB02C%|719SlU+BFIif zo+HS67t$6c@;0P!#Zmqp_daud`GuZN_u-whMU|}K;!2i*^bQD%5fUu~T?85D;rVlr z$TJ!;g5Ln#1L`kfB8|dhdcNNf5{02WqREh71NsDXQO`T*u}U@?bO-3sC((A0n+Gaf)~%Q()wk~oTkWYm;8)E?=A58wf^6`YrPY7d4qrNPL_RaEBbaj z<~`7;jr{%PC6JY6=>3dE>-^tAejD<#%;1Jq>=Iw_x!5g#Fklsm4e;+zgXZoI>+%DztTk_+&-|kvRgH$a6lKmH}nm^GhW=0Gn4J zJcW?z_Huv>l{Ksq^W+6z8*K%jFkhv5@ zMy;x35r6pf3=8lxvYBLPTnatTATqZ==AJ)&GG_zo^?9=@c1QavRsfqiVZ3VNF^E0E z=8|CZ$w`b``5P1?Fs_1)NwPoc^GbHz_deUUwy0u7pVjo=n`o|+_Yxp6VjMk3#an+J@eR zZy8zTeNX&Zybmwy8Q-;vDK}KH5BgNGd(|rTC}r?^kv}*d0XQuaxzi z0!Dg{NO}e#F6;RfWsx5G#--3h-?)_Zthlj?<#(xKL%UV62M|)ddiCcALYN<6Nuap9;^ln+sI> zSI0BW4vR80vVT+whXOew;=?ijLoZN9bQ6T8_4s{S-MX6nbyGEq0yY9<=~d0fx29)?WWkSg|R?`k#zvTuMwfc*q)VnP-BCkFmAp^D+Qx-$Ouy=+Q<{($yue#aX@UAEjs7hhk(cra z8G(tA{8Lb4ofo0NuN2alp!8rNl}kA3lfw>Doa!Z~30a}Dlb%j-ktRYR(?^n#b&0(6 zZv_!9&qv{aO--|k^Fcc#6&M;jZ9ZrjW10Z#+ofQ0aS zaok?^@#wv*{m{q+Oh81|q2hTAz`mybog!hjzGPVKlz=m$=1 zvmdxu379(whkONavU7zlPcqcD+XCbXZvy#sy8J3#-VQx9KdcHOPwUCDAb4ZcYY}j2 z=heENyHKxY&_55j=$G^87lyb1I-)%EPxaq9Q_ zXqZsof$dNqdd`3C-;Y#ZdJg6|a3SA0j<;I_;0J+|odq{4N6zMTafY^&gk)p?@rGo{pc? z@%?Cr5a6Z2$xnXN@sq&o0ngX*^Aa8oe2T8;lBB;W+Bs3jm2KRvkH*5^fREDgP#v$I z0DlHPNXHvX@?p^5L&uxyIJIX};GJ~5rH;26g?}FhdRppu8%a+q;EIm7*YVycw;u4b z$NbyvR^Tmx9|unT*h$huYv{fJ`5egC#{{q|052sR{=We@wWpZh)&dU&z8bhZzda7T z0q~_dF6KAF8vCg<;DE=6Yv&jw`|~0X25sp_?GRw-Xz}~_!=GGCgIe+%XIvr-tRQu zQvWRgF2?&M%#*EPKlPsrIHh0G<%@vRdXNvC(nEFq*8`{glXSeLgwwn^2Ds*LI!^7= z2>1wHzpCTZFSJhg(D5ER-Ua@TTYgF#;8cE;j`s#mXN^!@|0o>?q%;GaKf=q6)A6?< zPtS562Tt`GqvO=S=yqj4aM2E8y|@PWZr}*KKeCgC-4@_fKa*50ZG_eUr}|Ek@DSk3 zbonVdJ`DCW0KP!Svn9M9aF?zpU&pso`vcDd?lned8UarOF6{4X;PZAv;BmU1B3%#l zdt=~(blj=qW2v2ichm7w9nS|I4!n(|-v#{(fj0pjD&fxnUqSUkN1Z>++rg#lq4uYZ z<#8P^)A0wfFA4#^A2`%0^K?8N^{oedw~jB6@Fu|50jG9eqT{4L6!9RHwJzPIE_F0h2Xzoz{7y=3xboM?FOz1Yx10<{@4->hn_Wl zdbm7|pH)HRX`i`N!nrgb*9(H+v~Mg6f}fKRVEO>3`HX%7Zv;jKPV$9C)aN3q3SW&wu5U7kCpeC;jkx%G7OpSX1DKfXnTW54;KWJ8;^+^IwSK zBzfSwfm3@6+y%TD@GTNf?Lc^Q;OqQwo~tGBRlvJ@wS*}1Am0M`QVFLPBD@~(1;DTK z%5$ax@G{`E4$x23w?1$uaN5_%zfjOhvFuh>HR|9VYT$OOD0pStA+xY3>8M^~-EaAF3n>IPE#?1G;5z!yk3l}mUJ;4a{zUqpR-0xt@J)4GrcT-2ZRR6tKJ$WM{< z3q8GonqPrTKRrAJuRJNkg5X_%_X&bu4_pm`cS5;sfQx#GaytWWEaAe=F2FB- z0Y4IgdU!qXlfVP#6BYQOAo5*-R{^K&qqP3zif#bD3wYo-x)Jy~T|PyU?}2))(s8aK zMA?US?gsfKz|jra7wtyMEitn2^aEoeI^aKJddd;m(Cq=y1cv& z(si3bIxeq+bgtr={ii6AI|mD^^W_OE_Iui2@!tPtv(hm2j#T z&6C#vZwcIMh|aVDuIRWtU$p~%_H+M!q4}yk@MA&nw!rrR51f~-1-?td#k|x3_&Ny} z?c5Rg3JDkXs=yZk_q7{O=?c6|!iAn2fae1b9Pc**p9DN`ymtdWCWw3l@L|BIGpmFo=Rd32F9U#|41)Iseo)8d@fZob0=TcfA<9AY zS3lrefcxqjq8tMr1$;GdpZy`qN#Ol~FV*!6e>e*q?ySrQF2=jokJyjXdRD5-(>|W? z3gC3^&C_x1+yZbvZaCtpF37pP@ zog|#}6HeFnS_Z-CT&Vz$AYPL^Itiz82@gTTtpGkmFM#kiz!l(&B%DenJT)WHl#(24OfaQp^dI6> zQZq&e#?!{67^33_4$#vR1L7%$(J6*RJsJ5Y#HU5aBpQ73q)#LZ{ZibN9^;iA9T%Mz zEu|#}#A8fSQP35e9Fv@w9+#$>hPap2(vnisqLaL7#^@vlTe&FG!6&4q8B#>v=(Mzy z3|$D{2FlRlymDwuUtG&1;_)fbMgwd@99d(Nd6AlYs=*YS9B0t@G>Dm!6BCjs3fc4| zWJ@(g#~LzH(|BIJd%9M}9MA$rIlO+fb-DQ=2JWxbX`e{yVeq7>IFp;wgp&}d3XGj z{>S4!f8=rAXmBEmBLBpMv@txEnh^tI#)%}PB}T_+uB(Y6o;D^u$uC{^U6HPh4JkkO zU0$y6T_G!cSHy+yigjHCd&5uvL?WNS;z{<5_Qts(3{um%X(hw-xNc9y3CWYFW+LgQ2+haf zy>l_~!tY5&;)TDH9`D=>p8GwwU-x@nF86tj*ZtjRk8Y>#=Nhj0IF--mVlFH3x?gKJ zx1~Q8AFnNlpluA3jF=0tN5nwY7x|vY}I+qR-M=E6Ef6Z0w=o!F7cX; zI&MfxOJQka5~zJs3~A{pN!$hlX2leI4S?(cuZ8*;)p&iL()iR=Lv%{)7#-(p5aq-A zqhn$r@cM{`i*bs*N_>XVV2m-Opj{H;8O4(fsc9H>2E!!CN^xz0LOeOiU^JwSNsiN( zFs?%aATuF89jj1$yvU=)u^x+fY8o$9#HE;4W_(Hl>K-4PB$dIj*hIcT;I<`9h>1;% zPUR4lD@DmJIZE+_q&UMQNoQPwaYA%TV!E({$9a_G5yzb8myWnEJtiS7KEaU4n*#E@ zVG=Rh=!F2&1SCk)YlufD`()w_DG8WsIDkAz^yNT2bzFi;*oXNlE~bcitWYeQUmib28*N$K1Oq^BqO%crKL zq{qUaWqDsG@qUxd(8@?oNb>H1$PV(x1btIPamh!u)dZN&PxbW$n9nz{0!cC=kM6j9 zJwv+J4+%kB_d{>G&kwo2INopGbkY7Afc)O}CV=!sCq<7&zl!we#Dr)}#hPte8|rer z4LR;>KOX08$9djvoTvPTl;|{V=|)*JX0;_Ng%>B+pLnl4x`5)|w$kIgZFrg(za%Hd zF7cwhi05s^<-P5s>++6OJub$nrjHLw%?7~|u8mDy9*a;aqjeKFACrmcK0aOG+St@_ z8kbzBJ|+qGjYqD_I~MhFXpm|7wSLvh<6}|NrH@06r!h#fzIMhc;uq)nF+K+nt}Xls zF_GzR!qdGGr0L^T(<6L`@+PGxCQ_&^Ji4uX>~bC2xa}|TQJkNUmO3Wdgxx*zddIS! z-#d==xbB;ptmc`W zlYfo4KuN(DP{lV;Gw?;vG$jMyMP>LFbqv0NnuwSQ-+E2MH<16@db;nTYjp=Y|qLwnVor5k5oKnTVl3brg$H^oif4dC^qM(I*YTd zwkgI`Q(O$mF=J}VgvdB_l@d|n!2iHBn^RGr3HUC394t&!dOPS^Lp^p|%v`G-;BOFsA91#vh7b0OQUk;;&)0~W?tV}~`mc`*vvrA(0%yvz{T42itlU+hjRma-1 zh)uEN+p@q@V?_*(9GtC2IMf~yj-FI6Ij$PxV^xRdNe-lC+bj;1o=$cY7Zus;PBq^= zYo_*aGNq!AIqV3;fBJww>ub+a6nj>VLeI|Iik*t{Tot3|s2|%<&31fP&C>T&vx!yJ zZ2M=`tkuWW`1f3@*~g%1JF8jdsVdfQQ#Cucv08h+w;b^|cH?HK^0B zDz@t=p0NfW3BLLwo@HKB#SUGnVhJ1Zocu~W5B_Zxn+=LU-u|G2uUD}`=$L(++p+OP z6&w6r6|4BBioFG!x}lu@AkU#H)@v1>+u!1Q&iNta-wA5)JJLX}fM19*r+|lFMh%*z zM)VyJ6*(Zv0cIecsV8P2x_wAK{tTr*gXs^tQTLac@~Wc{4u)yPfdB&+u8@@(+kYTu z9kyb7mIYn_cPq}fC`yi%{whjISpN|18aS@6Dn$6vGpe|}<`);Z2>a1k#k})NziQSf zs+vs)Ed;#*`W|!%)C*Jv5_uL6fxm$MKkD8-E~;w#A0J?VQATG}G)mMlwM@(`w8X&z zaS&5<5Ctd2r;9j|Dd{<=C59c2aXcQgZglOYWjF15Z{D&Lok|cN0*Q_@Vpk=e^fnd+oK?Ui)FlPvi+V6lWl?9M)Q=@}Ch#~zar6ZftMZ!>8f}M1S8Xe#))z1lQPwQzY9kqv51qY>P z7Zi|*%Aw4Zrd>Kj1mUwXxNevZ00-gwo$^BALwyXtH>fwj`fX|Vq$MQSihP$Y@_m;s zZ1)WrI9HxH6CuF5fjoUzz_z>e+}-w$RApdDZPC?og63^ z<`tOpFjVfK7mnPpo})dFv@5IF*}klM@O!oNo3&5C&~Mh(!~PRY3rq~s4ujbNcOL8r z+({rf7lDV6>6ldS`r||OuEDsqJTeD73jXFf^{#ny>s<#C_8RQ_GV5KXv+G@#rq#RN zoLcXC68Zl7RXyWFufhGm$a>dS7%G?kUK$#`bnu+c|NbDW!^^iGdOZ5M%Y{Y5xBj8= zjf(tN?<+skd5h=l^4^V~pD#as@=pm9c6X>+@$Sm#yFEWV-)rCceCwWG7i~KVGoE<= z(l@EqWhJM+y3Kpx>&LSyPhGAPKNE5`Ea+CV?XmnPug?8%(~)^=Cd~+oJ-s064}TbC z`Q<;IOv+)~Lil$_9t?ffk-!angO;%~vmxp`IIliy*@~*!94(-f& zx#ELkmaSEuS3ThaP34iZ-aBos?cW&l?z1Zv-P2`)d)iyQ=IkGJcfs!Usb`C$o;VY> z*LHHlAnWHpN#eUt^r_q(we`Zj*Ah>8)V~~wUN-Ei!Ln{!hnM0ubpH0kPK8h3J?e4O zpBCglA2#C;|Gb>_*kg0&EQoIyI!~Nvn0xWZil_xoZ4K+W|FY?s+(VZC)Slik&)K-A z>%TnJ@{02J+s^g+dhWQpzv_Nz#b;~hO*r-Iv^no(9?hy8$r<*(@!il5Po-`>_wbh$ z4Wk}EbYjb|m%sR|u<_Bfw$lrTrI*kD$8pc>ua?|x9P@pzh{RbFI{y90icViAKN0Et zdusTdR%=Z4YMc4UfJ%4nPH~Z~!>NMzU%N2pXy>cn%-nF~^j)`aIKE@owlC{$8MV{3 zs?)9^_8C=s{;}YL8k^wxare%Be>Q)&JL1nL-{q3Ve*Uj_mY&?y{f;w6p~u-JU1R?6 zKV|m(7auHqywkQv3ZINQ*7?FFt-~MheB03R-B+Va#{ALJeDV#`v+j#wy{{dJdNeP0 z?v+pG&ntc6{sp)G5cSx?;-06jolp2odgRHkte-yo_54NW-oJYFr|NZS)q8IFeBF-O z>Kk8tvGR{6UO#l8VZ`K%=O+C;|HBjWbN9aabyVd;gmC~-u`C#jsy9x+;Q;_O&i}R z-qrW@#bL-Bp-3x`U`qcNRUN!xW_glVQx?|DOv3uhF`G~2!sL-N) zY>z6K5fj!$dv)G(kG?&(^)v0UIgVosdMx{{3&0=EZFIJc+R2lp6+8%1d!CDYMENQF?(@YpJML|nnf|gvF+FsxapvDEdS7X*80Xk?_vXvrepLLyfQ~b^ zhEKV5jL>QQP`9+M+sQufy)wSf+4OZ0+n@UJ&Y`>R9I)ihzVrbf@mY+g5MQ1DXuWGC z3s?)89(@8l680T11CX}} z>GnL2xum$>wR3sBYcj&0excsg4`n{`N8Fo3`p^Gf?~>lCcb(o|?{Yl@8>U&PcWo-G zcReiDyC$wiKGeG$^(73*XWQN><0edjdmy!Q$Y{D@7=+g^f4&C(`zmBlvP0$1@2Yja zOfn+fFQeaJe`rs=Ybnggk+Wv;3rS$fXLBOSa)%mpULJaHlz98{^N>|T;-{afcQqpI z-w@Zd{fO(|-?fkbE8;txu6ON5d?ehHE&wj1r!qRj-}!94YaHSTUMipB{pC~q$#d=F z??I>Dcl_))@0onm zwIBXBM#0>7)b+swM_n`Er?71g9(8?z_|Us^3y3)IM|8Ebwchn5jNec8Jy+{p6JSU- zqQ4sUHDEVE|Etlh^dw&bzn^3=`m14Iq`xlSNE^iXt6^V5df&EsmpS67>lEw~!%^33 zaBm1Z>KgS61FETVPq&UO4$JuJFVDX~IZ3`ZqyLisxF6S0>i@!+$4!BD5D1k&^yOo-#Hpx~*#bju~%0`RgwOj?K>cspOG{ z!DCmCiJ!9koo?bQtLm=)FzM5@Hy$|o{)lMr#VygpYG(Cckh*K$)qlUxVSj_vu)9(I z^ho+E$+`Jz!*NuIGJQuU9=g|Mu#?W#6 z|2KCBkl)J!7b`3n~rJ@JVf?=}8rSQn zs~Gu0^ZVO8=1x6-Kg9dI)kP z_z|}W#$R9Q9k~01c6^Jl(E9F+Z(mYX#gA zOefUY0r^7f^TO|^3+j(w;1^&{BJHT&M_tQdzJvKQ!u<6wLEqRwOa6SteK1e-J?eS} z_92+ibe|&Kjo>Uz0G%Swa>V=L?1Qk+fRi%_y9!eU6AJ5WjFs!_JdZMr2=~`XbkhO9 zUAoLLz%b1)(qMzV6p|XzYKQbWnDp~)+#=ijd3bZcwgf6i%wqEv@{iA3JcnDfxT7Ik z51KM(?t*zCjDpQy1BTu=%|_+4E1xvqi}4$${yOgp)XCCbUnj{Ownw1^Zda`#oq}&r zYlj6;Jz;(&pz*~tH(=-p=(lKc48*ky4GEt%+`4g zP;JoFZ9$Gr9lE>tm_3>#LbxU@ z`U8@|;hY22IXSYq$1>+EgGB@~jx@@6tz!Xn;@NYG=jBg^z8q;B8Ti`p9k+x;5k@1w z>2A`++eq6Kf}$ZE4Waz~-N8p)UxHsBg57%v_yXKyZiVRwb2kjlClA0T{yrNfl)ukK zo(w|P|I5F&0modW|E+cX75OJ;kI7zAF*T{@cNf#w4Scj;^{a)&Bx>f*o$sEvplArK zk3#>-O%Vn$-(=Vbvm2%!<|mjj^Gt?3n58h!!~7lQ6PWK|Oa&%GADE8wO@<{f)iA5v zCPVzACc|);S7G+R#21<{6lmVcvsjf$3gkG7N(m50ej50`nJ`_hF917@k18VIG00fY}Rk2IeA6>|(SX zW(te~vjyfLOdCwkC4d9wd6*qAbuiz+{0C+rXEKb2c?@O;%rTf&n7+Kp@G#60m<=%R z!+Zx5U2HN8g?Sj}ahOdo)i9sKT!VRhsmV|V<5~vzV19(TW4XzY3sVkr1SbDUlOblM z$#6SNGRzw=RWPSuuEKPC+GMyJCLLxr%u<+jFn@)qhWQf4Sc-PR42Q{uSq`%mrVgeN zrpGFiAqi$O%wsSL%odnxn6F{FK4UUG1Tz=rDVR+#yI{V6X@=<_0N*$GleMXGQfh>oXe4Uvv4ik^320n%H_%>R#CH*je>ik<((_pa-J#TQk|oj zfRgfBCH}GwZ*9WC#FJO9qzJ91rQKymtTQ)Tu2s8Z`6(PSH-3q7PpFo6>LL!xt-0CS z)L6YOgBa}n<*8NzU7Fgq1|Uzhtg!<0HB&9+R(Wb$%EJif|CtlaHQ4@*T+7{|=Q@g5 zS^J%QxgoV(Lq1m9u>{I@^IY~{i{m5mtoCzDx`%-Kqy5*Vz4Z2GT4l%Oh|~6lrRI>}=IkQaAenxjT4!fpl2w_P#>PcS=Caqo4S}bxA9L>vm$uEjj zv|J`q(bCjb$#Ge3mcME||6fg;K5J%x>U7%AdY$-vPA*7Z>t1TDb-A%^Vo20-b2UCz z$L2kBRk@!|VIa}ML2C+s;o5pogat5vGIJ%rgwkWHIpw8aGm9K9ts}c*k~wQd3N&cl%h3`loU_Xc)u@T|Ee}(S52htC_u^iw>L_z=j+!B|4tqjA zoNzRZB?qWy_o)OFN|3PG+UHV|)c^v7&rMn}g=;&~P@P@hgD@f7n8mZ~nmA&JNFwUqTi7lCpu zWj)|%PrVU!-l)qgOU|^l>^E^Hsq!pxdhb_66}BXjFR?~EqEwOpE&Oq@E&IcHOUr&E zXO*KQmd+a`RZ_foMrJJnintD?b^~XWFM014s;Jq3KL_a0D#@dY3^Yqw8ufZgJdxc9 za)PofWyBy#A4G-$a1`2ewC?=@soWr*ak;pD3AOTJ`2=`@QbjZZj_@(#2%snO8`8Y7 zN(H&(+JrCGn99xJ#I#xpqZLL|?inZxL&N|JQ?OX02lFf`8RnITv{?qh{I3<(D&t(g=y7S*)MhXI*+u-G43MD_7-I;3{&?u;`G~0aOu43d9zt7 zHfhC1?S4S4{uh0ccm%y_sn{>2n`g@V^A4DF%(yutrsHX`DD*$Sg!!4k1$nATs#yCg zXp=WfmF48fv`We$ayY;awlGJWByNU7P!8j(C7zKs?S(_yv=QI(TN7)Ps!!pf!5F|O z@aaRQB$iR)yteXN74a;bV5w)@bc;BvBCe;v$y)b#G}%1?Ao|j2-E9aF4q-I%CWIli z*X)Z^A1}qUDsD&C9CiOyVhse7{U9GBWVV4tG;TzLv(;x*3=to=8<0TQT7xeJF*<_= zvCAcir3e8jM}SKtVjkHBuUth9ma1OFw=kLwQDg`VQHzLK2SbnWgj`9LRH#L40(&4K z`~vJck;;L?peWQ_rGi-3ny~Wl)X`-%e2Y>={z+@bVwl$Ot<}`-ty^S~YLqi>a2F1+#jsp&=MfvzdX6e$ z00u5bt0afoX$7H52c)a8l^nql>+KPW9pb*~Lj-wZO_E3{`6)@PAGirEj`%Px9TSh! zKlU`nC$-A8RLhoMXhcyKbW|TUq3X0NS1=lCH=%-)U;H2HSV}KvUQpjG!|mS9{|Pf3 zhzps(RyE~gUcRF_m$Iue9w9F2!Q!sDPBjLllGqnj8$DyJ^q?G7XBaeO5Egn29u%^T z=Dk);R#+8f%0?i-Qq~`2-V$Nu7Aa|i3&Zyj(Qne-7+#3fqBtw{|dF4U@t4D_qj`6#DgYvb)0Ef_SE zXUSklax}$=H7*xFS1(z9krmipR+B3T_j+sE%Sb{M^@V}EO{=7i&?+gRRzZQ7o$&Q` ztYbEfXrYQi41B!IfXdc(F(T8tZvlE}*pj@i#|5G`6XsaY7&w#08t>f$Li*dcXnbZH z*f5HO^sheh3`tnAA#|imSiS|i`Z^P-%zAuboV$ovQ{%-jgD|hnfR)yDi_mwLq5M{< zp24%6kegV^nFzSMHVSLM3+EvNI2thN4=OZ3;@r;A$am6t z+NeE1IUidaH6urTh}8txynDA<`KDa;r=`f6*XB*cCs+LzKJXgvowB1zHejjlIPYUU z8h>6@5v%cM)em9o!iC!OGsdiM;p&+ke$1uo=?cy6QbOwsi1wjXa$NA5eTA`EU z%ow#e(lZS?)y*Mgxs!ou_hBkyB7%K9-~wn{_Pk|a+uqnL_(IV#sofvhk# zfzwiG8e-TqHwlhHyN@f7>_n>paK7{emR1rE!T){)QrY(}4wv_%D=R4|M_<4wY&C}L zu^4q&?9`SGrzM6#W94{z$V`M#*oq`bYCZuhkU1!Zm2E{!87B`4`51|WDPL;$i-gM| zKcVLinWd^-h>icxhLF`Y1TOSfkI11AX%TS}L?N3PldW zl)9M*nTs`WvWaV%FC7!&|M_pElUWNJ|ArJkV(>4K7@e>Vg(T^=iY-(dy_?|B#G5|jN`OW745Z%s7m zNq&SYdCm%B@{=n~$*C(kCO`6YRO!=UBe}aVitobAF`#fpt~{Kk0__VISQQ^rcu%hG zXsKhgAC>_)N!_=t2Vl7wAzX-HD&eQbR6$^|t(dA)dnT#kq&UwfR z!?BVe5ny(p6kXMrtDXkG0{~-%k_!gzAqJN<2v_F-Tzmxh$#uD*T>CTQ$$sP~)~J1f zm3GrFElcA3)6unO^ecZ8p96J{S=6{FIXYK! z8llmXqcCWrWRp|NGGV^1n}r&ED@9f!=7YqO_5)n+Br9(#NOBta+whIzV-t^zm|`;V z=2YRTX~pHl8dBe(=e$>fY9^t?Y~YPhx5c0oq1DJo$PRP+;-NEEUj&!{B9d~OAa^-T z&>XZ+2DS&SvH7FpJ!+w6!Inkb;-7P}+>nj0UXoEquQey+&8og>y^es_p?{b(U4nt{xvS+Rcm+SNjy8gEGS=Sd&8q+RRVwM@i6On4__Qgyupi4Y? z9d1ZB%Ef^zbKTs>qiKdRJJrSyz=s@WZ#eZ#yAI7XmnIFua-Zu^8$QSzLCerJVYEyu zU9Jt{j0hZE8*aEh6E!TfU8AU`VnS9D)HPvgsOYO%i8ZwmhLq{oks8!Bpw7fIla}2@Qpm7AC$1^RW~bt?I@QCY zNMY!5l|(8lHE-lft1>gp{%gsXi8YXTNEbpmjLqJkgVd*QSCH~Jvkjwr%<}7{BHvgT zM|!O>Ky+!6PObLqni{cxrnl1?Yw0gDICJSQ_mE0+|B|1)VJQ$boN6A{99J58f_5W7 z!05$x$aa{}8*Fr0ZyIymkU&d(d7h`uy_;t1f76)Qo5qZ}S!I*9uNpD8pG?E^y zyjf({(3{4j+$`pK=rnIy;PZ!mFY@s3Mbc)#P3yG&P9vUg{k@!E5jQXP?2U5DCS|H= zG8pbKOeNhh66MTT8sSsT-FU0iMS8-Bff2g%5<(H{e^X$V?1;n2DIA!1(skwM#3PAk z6ypo~&qoL)@zGoqzF~Yawvy$q5;ZBaEvX~8)WgL16|*FpkJ>ZaiX-K(5Hohw0lEtX zt)b>z(cm@h|Di(!*w#74InaK9PQa35h#N8#Q85MIWx<;CEI8Gi9j|7(urhmXkap3%I zbt!Q`twEY*151|DjCCXz8@wGDA^^A584C3&~9;#aFYtJU7X_mF3`*}vi~avyu18FIlaqdm{cbxarPkbK1%Zy(_r?GaGt z?^cHG#|CDimQW`>$nhE6+zgIpf)zqZTo`{Z_G+~v6INlnu+p&C$(iv|7?&#-+vJHR zq1Jr;eyKMZ*`oRTDM3lRY*Et9b`yW6JjrO!F!Ftn3CqS$<%z&uykwFm_6HexO{jzw ztVO1vO~iR=9tc&QplfpEpM^VUqclb~<+#g7)AmXipM(&^AG9n`rm+(iO23VN8us2y|9m57x6m5ctb~{E8u``t_p#hP9j2@sSyv&3mt#l;wd= z^@OB;2dTA6qMJZrFNZ|0N3;(Gf$Ym!Jfn#~*gS-lvYkhu;{zbTO&jCLiG>cD(u#$N zQ$4Z6uMa*5ZAmv@u-gNqs$I_0Is_YgfCS4P^$}s%cakz9dFt1k?BpkcM*>ool1)=|S#61DmMOB9277 zw~z)Z(K(jU3h|)2RVy&qX@>B|=>w6shQ`o%NOHtl#E;?f^;b_UFiF|C2SEYYEaEnJ z-5aT`!*%!wEg&hJ2tiI_bpbMc8ztO`${xcM94PTRDtimlI;af9!w8EAXs8a5{GeL? z^G!+Uuy{mL2ozmQvFa)#k>#tdG8%cpLll7{1orklSA_z7Br=~>93T-I93dovJ&jP1 zvs59N^KO>2J%y|YX*k3`xdJgS^pw+YFhf6=cLU5Y{&geFT)r`87G95;JRLKZwa*ide-ArFgq;s(Bh~)` zJMU4t-^0#>3_GphOAyEEgbST?`jrGCV`*m-i?|tfZ-}sg?MaCcCf4NSTFPjV%1{?d zQW%SH=<6?WsU z2##i~UvqKZQ{vl#T_EvIY+!S@pgH#1U?r{g+QwI?w{FxiwwrX!bjkpngp7BKn2x~2 zmh>e4vSsZ8UxMbz*bODz;aI9~-0Kk3g+fr*cq`H;@unpVy6fWZK^%>Poq4DfM_JJT ziC4M3`pEeWqp*k#L>0scJ_QTNRUB zxZ`M%^x?+e=W=ucF@(!u4JN09VF!?(j!_l#ke1U=%IT!Y40wzop8GLEqU5L?&1ssc zZ?G{v?y9EC&_e1fx2a}fYb<6X1BlW}XI1H(vYhUe#Qq2^?2%aGlCNEQOJ-gv-6@YY zNmxUUHY+PLa;5bCazwUNVMEe7F&ifZ z3qM+pP!uXWD_e68(<;d+iE;R9l~j-S9OFW9Y}H|6=Mz~jaGt$wWb3fjZ#UU8B)%AQXOJe?%?kfBSlgt5=KEY77;sG zXdVT@S5^dHfk3BhP5?fp>{gV4lpX^88(7^|k!Ofr7uu)GFyV?w!Q2BueSPAQgag9L zjE>66DOkp4$qM1bvS(a;7FI2k*r^peb5+j)h|{+qk^H4zPrE~r?M`ds76qSP7~vj@ zVGY$sTUWNNiN@V)sZMvvql}REo!XbsVRL;D@`%sf4eseax1T~1wWKO)F`aTDTkGzD z%toj&!$+tYgK}eJk-SoRymt!4Q46xNr7E&Drwu7=djEh4R8UHqdnhBY?aPRjBc*n# z=fC%Nraj0A*)X8}1D{+MF_+}J(A}mi8fJ3(gj{)LhI}mMFqAg!&-3acoqHH?lB1TO z&s;91aC72BbQZQ6LB^{OqbIo{c?X-wTeyORWu`&{G=|DbTqPI_zyw_UlG7n9`h~eR zGz24nA0kJ~#YVX^_EJnaOqYhj4JRwB@vZGF{$4ZIHx#B$BF@;oI}bmD9rh z4)$}BG6P;57lzhD7O08#I!oCt@IgbSh?Lf;&b>e@sxortsLDUtYN3d60B(b#l){~*RZ^u=6?wEuik72r)nxq?BtiA!N@5vN zin4$bgQY@;Le4N*q#TyA1jI{KE8uQCg@y@LZi=;t-@_w|6iy^H8#WWYWs#hM8p)Y5 zGA!acq{3k_1S!I72pri?)yX!_xuu02_Kg84C_8r{oSo$1rpqGLEeTr?qzL8smYPb1 zD#ARDUs%d+1JEsp!ub2JfvXot+oYA$AzCG+(sr&uCTJB1{pwSjiK;AR-H}a45eWkf zXs3GhJAlp_Ne!S@O;#!h(#eHkps6@yR63)?Bvw7wNykg1rHs_IiozjfmQ+O+n~EsC z*acBZEKILdtE6~;*FMW?q>#{oMOB9S3GE?%U(Z^^!O+;~uvp4Y1FiNe{z;IOd!Gfm zBV?jvtqb)NmB4>g-{JMv>KzCzna~DVye?GuyIJ1He0l$%=M90B@mMQYQxd`h8@WWkkDAO%95901WGzE1gB`I5Md)j}<#)~|<+VNYoG z4SRdtiVi{*g@@6YXXRPMG?d`;@XpXAX?}F+Rik$d>TBQpFA!%D={NZrkF)-w`WY7@ zIMAg~@wnj(u+hIG6*Q#?3b4@*SWFUcdyX3WTUPp1l1Kzm$5<&U0`s(ileY*GKn&w& zU`9bSc)@M5ya7SjOOZE{onaJeped(V+^z64*?6szhK*K9g$9`H3V75Wudu0GEQ7~V z){MFslOWg2aVT{@vp9TMNKk2G-6`Hh4>U!vq=-a)ONk`VCDmHd9d?J@3PQfha zc4CSM==X8TpNV67oKsCYN6d8x)#MMl0^IAP(BCntm0)O3$HGyoe^hh~p_5V6Bg9F4 z1Yi*p5ON*j*E1EbrEC+@DFRJiKJ>FQsyf2y4*#V$xe{670NjW!To+7Fa6vM)zeecd z=L&4VqFW@S)pio@wVl)xp~z8xMstvQ*#E983kdtfswH2C*x zmKnj#qzPiW%IDJj5PI7OTpxIgWVaXSS($2u>Itj1EUeAYX#I@8j6}`QcQS3NRVZnV;R5TU7}6EDvfU@WlUZ;%f|X~DSaz^Y8Z4CBYB5_q2|@)W=a=rIC<8Yg;rb#|Pb{V3(1Ixd z%wW(L3}FX|E7+GQ-T{H4e=i-we&{*JRZYp5 zy4WgAzw*GY0O!gc>W#!R_Fu87OgEUS4&iGli$`MNLBcg0a=Lj$oTZF@w~!rqR@il0 z@Zm512y9fS$T7JvT&bYjx|5|%ODbQ_LNQzGnol2sU{Uk_$z zM>}Paf-PbhOG$U;Q5;#p1mv@b*(|}Qlt8IyAd8AR4u`afywau%?0X7dZd*o`oUF7u)T#a zX(dFF!*HlCeF0xO#4f^;{h*W+`~O|-DZWXe#(Djuu&QYoh=UQju$3+<+&Z_B7A8%o?K^37TJB6H%&hzwJ=i#Woz)L5`; zS-c?}2blH?=Og6in2TJjWy^u^{pZ7lZ`)$_$K)ENXek#b;*55@Z(&*7X=$W=ytOz= z8c)!&V6$vl+G#O@O-m4L0$JlOmwjqvwuMNO-9RO?H7hrlo)frCBnE9V?J?O%a%(N= z8LS6lW6*=yWIpK3J0K~qnNK7E2dfDyO)icmozT`Fh2jo%_V7_#b31A~{5#YZS`T)F z+i#`O15{Zg51Dj|!Bl;a>x+A9zCBka-N&PofQGiv*{|^kk%+@AACAeia*+w?CZAqE zS1AFNNjQ_yB}&<}hZA6D2kH!gT2&=Tj!A@rz z)<*Qtx5oEvh4P(!N9t*A1FUGMf1j}qf`91f&eDs^&cQ)yv?n_|8~AAjl66%Yc7reB z*{)Fh62AzFF16mJ>~0>U^IW{gfFT?&KWP6Iq*t7(aH~z(v^lIU{4~Id%#1j6b}do6 zQ8=H3voXkOhG60_@na_mwMM*TMNuJDh1Sb1LQqR?5)T}CzeaZNx!?+v==LS1oZx7Y z1{68f51PP5-PE05&mfcWO?}GmQ7;TLwgZ@v* zf57jj9wc=Bk#cSKhqkGE$iET(b{J-oBsRg~3}R-T>rI%QFf~3?5Bn>aA7C!SgiWk- zb%yB;GX&;An29hmVHU!ygjomkD$Lt3)i6h4F2DpSE{ABMAM|K+=7o=DAb96uWgo9p zAKy&f9lQdUtM)!YGZFt#Hp50Rh6;HWm}dea`Z|79Ey-)4nW2vPhKTCmbmDY$sV#IX z0gHR$qO@gj1)LlQC77%M7T;HbMusi0uuccLW0qiwgi!9^ND?5u0}}&$t6R%3D>E@Y zoA^Z`ZW^}HTtc%qQ@+a$_H%qTn1sIkH`C$|mnmp4#j&_SLdoSYdjE!ivgY7~DfJXm zV9+xrfJp@-(|(u*2O*)ZzSoZRaE<*OURjw=BPdjI0WS&e=HgVwk#?K6tD2l9{}Njl zV^BNOdFbnF4^EvkQDzq$(xyZB*0FO2bn(klRz(AvDM#!FEoJY(ho>tji@rJ~XF!-< zg;;P+>xkZ*8P|#hq!p<=oRG#B3q(qcV=hq8Qn0j%0@#9_ArL%J+<_2_cnpZg!Wr(y z=cx}W_p;z^aAOt0GAR``u$cgaw$UUQ2v4%5J&}K8=i2Wr-H~>L3v4KLwa0@O_biLZmgxeI^;?P0S0l1q9#)x(t2K* zfN`xCA7s*clhsF)s2`Iy?LY?fCAyLo6-=k&D#Tyw6Pgx`_6to^SxAEahXNqQR0Rhn zL?_%Kp@Ahrqny}cueFpNM)UmB!W;GILWJ!iRI{mpd~d?1OtU5>@{gn6Y4XJZFbU#` z$%(U@O?1%i@_K0OSo`@oU_T@znCNUL9p`Kcp1tG8ut_}I?MY>qG%)P%LrBOx9XdJC zWE26=WV$H_><-o^rTClY@J)}QVgn8)sd;Cxk>@alanU(y{jM%^xpMv)LB8Rehs$vG%7_>45XzqOAq(m(8ehIu(BeO!Px1%EpwkEdJG5H&S<0uXw8Z0;aUJOG)wZYfISSI#n zSYUaW*y00#&(o_$_UzJ;1}^SqwfP{taSikyS)gnkDkvgAd`j9N+-But6u|~wLl;|P z6@?I*h2zKA8loF7(EpZJQJ8_ZRBu7k6z1EyC9$P2PVKZa4CLzKZk2{gMMkN}Bo&#Z zBCAw{r5W9Wpl9L8mf3X)`!(oQB5-`XaV^k-Zv2oAD61bI08?;}071GNaCJyM`ymx- z5$97P^}bgzI#@?2BK0!V9@JNgFavw)+D<3?PQlly2n)!{R@Z%kJ=rk1Rc*lxs3%v8 zhGSq|lUQDh$X=RbFy#n!Mx1{E2r`41RJ*huv=IcP7}S=SNF+ zm^|uYbKR;wnkbg3Ed+W861~<(w#Z#_oU%aAK`O>l+LOfspg$!nr()zLN_dp(gaWNu zPHi&k<)28}Nhbig=}9~33?P>c0CrMB1_Py-gybenf$Z#~wv!G+0=j{S8BQ@d*!4n? z#)|Pp#Oqn~M(~~Ghhz*k=(6Gf<`lU*-Lrom5S8{jVL!q5bgE7JfomU1^{XF``~!>O zL*UYcf3?prni!~aX$y{QH4z#BX(#n5n4TqDoe#MpTYUgZ%HY~_0Cob~9|$Uh%xfx) z*Sa4be$3T=;1mDC#Xm=Lp;d7-I}3a3iNLM;(8QhDioI1_iNtkBw#yJHXI_@*LKev) zfmzry+tBV(dI7RBsie^C+P4{XUfNpl#s%1ju+A!Y<1CzzV|)j&Fj+*Zc#aj5#T#Uc zHJ-J-YF*g>S_TV)WDAnJ+DSb*^6(~Xyym*kBL#_6U(k)}eS}<2e+4O~&Z*9tiS5lW zwImJOB}3f}p-H(|cHLkA7~#`S^?pid@P6*Dp)`7Z;wb8`Su;I55C&K_!}7z@UIb(8 zo}M`Fh?%43Xroy3boUiT@yL=$;jor>5ND)go`$!WUIoZIh)e7t>-^VC@5hyYWmpZp zU91e_o6*!aC~QTvFo0|W)Yq80OdM7sW_L4y2s{vA^;+C&Xn>iq{Lo`<=v~Fp>Qq}G zVq)cL;JdhA>>X%g<6;bdY`&;7u-}Afsu$euWx;q?VU&K!qH!D&W-HT7as&wrBZ+HZ z4mc?B&0I&>kdB4%Rk)R5yZ8Uoh!;Zd#W*@Ho|L9u*3Yu%%16N&pQ9Dl zrN7IG@-N;LIo%{hL#-rX=VGE2YDbGQ*4hm!#f;X%hm?uEvz(F>dph)Tfn`s6Jcfr; zo&OMOxF2(rQ0KH?;`hpV&HC=V69V>SeOEr)Jv-18;Sja~+6*YzsVy~YOHJBRqxJw7 z?H-II-blAOG^KcER&f?yB^GM23Te}dFKflk>O_n_ygL&}N7sH3hyShWeJrjGU7^!X z4jat6TR3DDYMtZxO2DN~9BMGoiQ-TJp2i6l5OnGEUf-jKW>q`Hmae^&jG1(mKSIgq zjW-dq+`9tx0}UXj>z*B0)wB%L4x7bMtW}@H-~ThX1v@WXVphiXmcK~AyPEz7JLRz^ zX|)MQWMy1bUTr4FbDJ@Yl$We1cu%UG7Y9KjIQ?Ii{}Sb`sRI&P6v;<1Rg!?CK0rFWpDJ>rYVDk~fCl~%{v z0QI_|_;QH_tE#pkFglFyjiUx|32zQV1a>AUhqQVGJdGbAz&!%UZhRi)I@QOLSyN{- z*yz|XU@OP?{$1FfLm>>d)o|$hS<-5%OJ@6AdR2u;z^FA)%O1?ggS zuoTe*t|`65ayy60DZ>)A2u2=-@!hHkDbhVa@t_M~=|Rwu*N z_;&-!{0%DIvD8XxFS=vQI9(mgh-IkaDEASIwwox?k9E!sY8lGUwd`@M)p093)=R4i zy|Uv)UlSc0_4XmrIvoJnu?^I6XJI^hX+X~0BD_h!f}99Y*|8adgfeFTP-!)_gr>Vu zI;rZlyPJq@^l_gJ3`dvuwsr~j24dJi^=&E0!>`{`Sk+X79`x-F(<>hG9)f^YvClFj zvL~qIX9+rY|Gs$CkUf{AYvpwgBepa?MT3t$R_iQ`S40o`3NjqMLgjm7H$bC>NU3Yv z<8j~~9&E%S84W!;$}6%54lp>Lr2G zW?0JdF?eCs8(?K{-2@d^0O&4}`?3u^{3c@ctum??!Y2rxluQwPF{kfJPQcK{5%ID@6^R(KTs zVzIj!KM@UE%iyDk+b@o${sz__&0mO@tS|ACA^XIPnRmPZ&N?PWSNulvqG` zBk(!^J%D5>I|Y1>nmGF2p_Vem=e*Za_Ob39I>J&$ViFPzP14uEqv+>OxQ^}})X0s9 zt2Oq0_Z!XZm!akJ*VCxg&ibxfdJV(*htV>2U~&bq@=K2#1or z8FDQZu4Dur71}SfcJ|niXlbM;9+onZMD${M0odIZm|&=ZX7{0hA4l*b?t)M^-`@*d zmi#B`0Y4qt6lFaSm*ww~ZYQAq@YB!$z0++F8!L$?Yn7DNy)95YhAgUE7Vx_SHVWg} zJywA|~xn0L#k;u(H?Mffgs%OIb?$xE%_ri zWOB{~x$?HfKYL@fVIM6(fO|n8rOTyM6Q4x(DS<%T_fY-!JvfUqbSQYE@ysd{#qlaX z^0|`(xrFL52*8zy_vyXe4~8Lv5691gFlN7xhLBjtMZP}};nXB^Fe9X+YGQbJ;*=>= zf57>A;JzuqRb29FnvQWJs1P@Tilg!NR&ht~OtG5g5k5y7CT;<*a-RxhP^#HXAB~tY zirEo}A=KdaM7Uc8EgH-4)g%Ep-5Ub&E_z3nHBeqX7^S0xd@7+dkV0Oaf^fXtdBkq4 z-+`4nXTqCdF*<(hcOk&NERY!8%7hb4G@KnKnLMPdOSX9PA5*FU&Z-p7T&yoJQ#W>m#s`_%UX)Ti&w_n!bs&QBQ3_{ zjJN`6eB7Fh+sen^wkD%z`S_l4M!YmWo?dAlZ^Q1jG`_!_F;E&mP|g@EjUS9z9_pi) zrL>_^_Aohhgp@WyPED55k_+V26#TYD3hdFaN5dWidkpM!*y*q{U}wO781}<*>I5lm zf}HA<(wuVY6bZljkU9-NaDn(4h@Sy_ChVDV>TJ|DTguLtQ}f|+OW6zLR5x5jQZ^^2 zE`V#9l)XYuEs}&%14d6OCkbV6U=m4Pw(20Q{avzE>LxplQZ}6n77m)m@jw0stE-ww zz+*3VVX)5^v=rq3QKLnAqYS$ud$o zIA2fV5w?zk*+BupPw^Mg73`2q76%e_N%shCJ{gic)bB!CWE^>gX@xk<;t!* z(e(q|8_DYZx{A1#lx{Xelv9Y=?ItVIJpq+iz5E@r- zy;e06zPK){8qW)`jfqFRv4xQ?uca`;d$rK${ZC=I7tb)IYDGrej0<;a!xYo47{cz> zJKB{`*MI$d1KMG(b34A_0%{rOYzI+B*DTAP3~QEFWXAm=cX|5~EqkVQg(Q)PXO=Bx zBsf%;;?r_4Oh36yoc7pf=k2vmr*IE2?DT_}5htid&Mc^Le5S{Z^1SmsQ^=a_NhZtX z8A{e`Moe^Q#of3{4 zdXBU1%~XQ+C5zFDVwKFZT9H*p3qw3)7Ps2pU%qf?Xko@ZJZeice?7IzW=n+}+qVl5bT-oSM%7}bmg4_!3U;oFljW*paJ9M^dq*R9Yv zJ>e4$Z`pZGh2hijGTfOoble~i#K0_P^$e7%I5t9+sW$*oLl8BXnzlDI^ge_RWepC5 zYDFomvB_G|h%6;@FU-a)ZP+FoXbk0y0d4fxitqqaJf4bodxK)<$xA7Ge};D;_GBb7LcIx5UF0`Xj=XCNgj zj)P>R6c_y6zJ2X@;FhauPTX}8RP`8ND z=r((UrEEFm_rj>ellu%tgJF7gGO~`wHbuV!mNKSJ%`{=e)Gc*!ne>oPs-W7q;q)0S zs41N7ZW>@*=eub*@UuLFk=uKhTwCWbV_+D(PkZ7im_1Eo_41s8B~3M>g2Q_F zUa}%RyI?upTk!P_F)Z3fpLo1Y!@~nm>R7}c7;265yDh75*XcRzF_fbbkaBVV@t|-X zJmWF=F?~_{Ezed1220u7_`Xkr-oAfn9+xGk02!=XIxHNdi zkZT9IMv#le4kZ_f<(~fJf~F2|1H*uO5|<8yhBNksPAB$-E+F=WP-0)$VBym}ameS* z^O(uX@fgXPw%b>&qHGL`_y-F_sSRJJ|AFP6i+#w>c>Ce7!)f7+TxmE0(SBWuWVB)b9L3ksE^lyhC=uv6y6M6r@yq-pm}Vpq$0eWV8~Wy0|@M)ucfwBQdq9~ z>r*Td7Hz>$_kz&+ymvuEg|t{f>QMfGR|~-QyDB0D`$HF9B&jR24Od%!=|>B-P<8C8 zCKK4FU;M&5ec4VO*u*nVX;UkZ{Cl{aif6|BxE(q;z_92QBjm(cDO`6>Vx?S%TRQZC z_EIYK=##)L&Sr(+mcnm>+vkxpRK9ZgXaSFv>BZgk4(qC4xkr^frbws(dvmkgwg7PQ z{jS3N=}hzNjtzvO_k>#0c>V`vj}>_YEH(zYO@U~ZX_lU;Ewl@eX%hbfe%2?mZMwP< ziqM;4SJ+C8($Aa3`)~|Wy*b@pMYs8JJ0<`L(XEc#u5`h^iWSOt4kcVKI-Y`6emRNn z`~Y7mdgzVO>Wz20J=5#_gN8jutryZ~AOQ>f7HFJ%0w!N~>D|~`ee8vhC+=~O zwsJdE1K8eBGBn|8^iqe`U$Gb0x!fEQd+%|($r|XMK~`V4ldRj^W60{}9zmAbJ(#RW zw+)s)0ebNxEO7xJb|U-|h1(Peh3tWMEkXQu3Mn>uw6- zQJxql9)YNUnGHF@E#gOTvFA=KWmDnAujo)iimi&c7Y--JY7NmeMLEtamEO&xUZJCHtXI54bVQMIj_=hDN$$0Q zdPq}cb1w_{z4+BRYMOgmAjky;29_Q^(TU#DkL-h^@Y-|i#~yYqsdIe@a{%TD%=a+I zVZMa<1|~T32u=O5kW18j0w~Xsa-ql^F`T*`2WY#EqMj%Hv*Rot#h@3IO6V#U0!b@L zi#Vag1g$geu_T^J%2PQU1~WO+1V?K;%Oy;0!xlT8L2mWu!^1Dg!w(TkR0Ds)dj}KW z~%Zof9aA=~|FU>vx&1e`z?ke^JI`k@Tea~rEC7O)Go@lN4OgL)Ux zIG#U=Ze1HFfmAZ>ch-fbWHwjGooF$RnHWpcf~3@N$#KZ?3=J{htZ?9THuo2HbSYd! zL!n7=9LDmfSt&W}ytFm&COd^{{GnP$t3lm}Yr1UdkH>HLz(PNqPbrMvrve;>$4|=O z=VSPL@E~YwI6qKFOerA-1b(ZWc?!Q((N&(>5OKnOU`YomTOAJC@`)uW;hN)+aByG} zz+5*Ag9r^HruMIRFg1ONQ<(2QJOupg6(xk9IZSU*cjG63B{tBNshcNbCvWN&sP%MQ z($OHCd}Kes1mz`4@KIjlK@4M|WOIr|d>gKURV7<8AF+skp~3Aq|FYZ=(Tg#V?nFk? z)D2Rv_2lUOBEIf&jLgZ|>V$vODB(N0*P`6U1;|<#NqAq2=(*ic%2r!1*358 z##|TKfOs%xgLjzR=~aZcd}QN%!I1t9-d=^_kCc6k_f@-7N^j@HBLz4*f>&ZYksn$f zE>bN(y?9vAQidgkA%H?9xpe;z5q`Gx28nlQdq?*S0*;nkI6>x1jBUGJHrm!%D<8gc zy5|w$TkER*v|vbVq4QqCd28f(@|{dL-1DNp0_)2Whr3;j_(bY-97T8IH%P-z!J#-d zLvR{S)#czte-CWQv61#9+{r<7d+$gp+04bejlm$2UXE?mUUtSZ1-{eS7zMI2_Gxrp348%B^ zE#gZ+3sjx*HO^|O#rGQaP0?#yHYw!5GI=lA*hk<5KL_q;sk zoaa364|^fibPIG7DxLD`Grm@n%kt`zzSbsr*3mCN(Fd;`T-=5{Sd)41nxI?^HAI>m zt;jr1Kbd~slfulC=*i5CMGs^yQ(qdprX(iPBh?)rUS54Xo>!V=EyM?s@uT(v5Ehw^ zhvFw}W9(bm_d_jK#2xr$;anvg1#$i}GUNK>j1ENJ$qBn8Op_QkL-j!q+i@S;RD%0W z$Q1d(SP6ynV*r*I_F6lpDhM*Oq$66}q4q_r&rfL>ClSj&#`a@=f%etn8b2)#5bdh9 z%A9?xlLsdZ0UcH(0Bf}9}97m{i5~0S>3|qB8J{;hLUQ)C!L>`OzW$>%}&l(-BVDGxCBeYP`Yi)dy z0rT0$=PmdChH+T^S<^kJHq>2#hH);Exy324iW0n*0RNiZ8>I)ES?xF>8Aq2peZlC} ztbSfq7n;>SwyL+)aH=7Z#5$J_ig|9x%e=ps>}96DAqFb(uW*9BTK#!31wQ#0U>Z=N z=gFCls8XFCzp=pHb+JB*Jp|w$RY5Izp7Os|L&}6gO_8)2FYeW^Hi> ztqVCN%&Jd#$9^j?$BN#B-~tsMa~6$c<9Rlw8jC^wV5SGmQQfM>oyq(VCex1CsfPV# zxWEh-n&E)D$>XZ_t{hKp1awScQhp4rQX34a{8UJTO7~+lYu0$N4&%yi))c@)aaCyI zgjDJ0s>rM%5q>Dhd`<;)m-^U>_pvxem5?HQ3fpT{NzY2uTIW!6 z0+^}_79q z+bIoG_;!+hyC>_djsS@#hp6hb;T8)6)ALiwxY=*Yd!jvme@=GF2uQ;>r(<)pRtn*-A6s`!?dC80ro*S(5$HxSh zOlceuTr$;n?*mdpM^!RZR9;=wi2JBlT;lE&g_f-Qy;_ll@uA0llLLlhofwdHCu2*n zarRIUv3ih4#{iF*nX4#J=V$&ROVd`H=uUv};tTC;pA(+Csbc0xe*;Tvbj3PXvyeE{y; zCk9rb1Br`O<#dW%o?JImmjYs`g8s02Gh)9?Fhkv_A;|?= zPWxXeX|9=TKg%!Wo!o5pcezt*=Jp??W_xq0vW0_{P0FgQYp}9gb!7|fCAzYOz7}Dw z?D@K~h1!YOz9CgvMSPm{tc0HR8iPH%VX(6Cy0Qv;l&-8I_60jvS5%RhYkx#69T{KM zU@e=oYWY4Bm>S>DDQT{$vVX=ef_Cu)( zR7U~zr)+00*=O}M!6dWi0_p-@s<*TsA|^JAJyeh_tpsNX&hpojwuVzUqBa!ck#Br( zCb+@f_@(vk;2VuA)EbZRTON4VFK%=P-&(s+Rr&YzpXkhueUU4*p@1(a4oTd2;d)Q4 z`bF@U%Nj>hzH#>Ye6=t5PUH1iJv_-8gUmQ%2Zius$BTz#B>k`#0MPXj6kx5h@Pr=C z_*T~2-$V8c$+V^_HpzZ~YgJK&ZTtv433}{_%#SU}AOxm&@LRrSA;rF|i!IC)I}S>) z7wcjR?fJS`1;r|Kv5HKwpG&b4U97^sTo;>5v5R!ExtU`BD8<~m*xYRD70+33ZRxu= zh{`+yCXJ712yJWk!XQr7s^Yl!^?P}>?fEf0y7&gc?j+@rJ*3me%xajst>a?M$LmEy z6~D&vPTtpV74)dj1mhm^Pms2Vb8uuTtz}{)m~d%s!e!l=48Ff^j|!bxxoXfRfCQX2 zNX#Bf1X-cm#EWO9af&Fgmxj))ZZ3xi`8!oJv-vbT!oT(s%RQ3(oXut75S{Tu&XTNi z1oW>Y9d;5B+IiG8Nc2MytmywP(>aJ|wKYV*_tJX5E6$k`AXM!=c{x}`Pbz}3L80Q$ zj~7Kn5>c-?vZcgJg&5us)lgaBl7QgeM7b1m!Id7T^QmI7YcZri_7E_-kUaz%w}5Sp zB1WZXUb8!$QoHG9RU8A3LWEl3+DTngc;A zU>d=PVOlwtTJ&H{TtIx4LLwdKLx;BRj;y;_g}vo?psQflTrwR?BUL*g_BmD4t4mH+(@cWL>LqHP%4W_b%se`!5Ob=-n*CA zL(d7IF!=g_em#VV*@)J6tQ5*g*U@)gAznMq94ywM?0`sYNC<)<*@Q!U#(vAK6zj6w z%JWpk1vz ztD8@82#iowGu7N&5EJ*|P<$A?o2<}JH~?~`*6bxDYXBLOO{gFM0eBQeST36I&{asN~D49{@c zR7d#=P_nkNWeq|dReO~m&#Zd&X?2quvxy0iLGNhiti)KPOAURiS&bV3SdbtIKqa*ye;tt-(T-TQnSPp`~|d=C>wvj zY!!5TetfcZ?;bu;9Nz4R|B%lUdFH2amRaOBr@;?Y;UYHVO_f_-t&t3h*}KI!8Zd4j z(9so%opK1_9ATKk_5lbP2_is%55%mHBhEWnRfK?fq>pmGHi<-M%@0IY8+YdCtQ(`s zv4-^oH=bGRLIBEXR~7#+=oJiT#!(PFwj;3G_$DRCsyqfdsw8xVHl(Uyl?BQv98|P@S7s2S~zQ58AXYRBP@oD7DJujBT( zZ|j_~oVC_1dAYE~bkF2w`J8c$|3GH=HWv+9_^O1CWLv7My`8^?1GlZYxkUB44K5Tj z;7(u5Z&=8x?u??BVUMaiZf*!z8>X0;&{k;yNL7Kkp@;$KuHRm|e|357nD`3a_lWQH z1ztt;!-Wr=3BD1l;uZ3zGx*KHxl6)a1S&P2@plNpZC*$qF5gQvZW2uTUaBoLI`avi zl?)v(r^)IwjeC8s*GgUH!bi^dwk~jnH&PGJyIT)$!8qT&>*X-3wpIMdEVU1xJaa=K zVv#rCcpX`+?Pg1-gKgFoFkk9T-@W4erdFI>Rcqf5PfaEkQG;cRZ;Yc0McxawxwFtHb$7elx@B)JEVgg!VsgA6nvKXJw2Osk z*7fs)G-OHHYIHTTJy-h7PvDs@&!QL_tK&~vnrsz zqnGp7D24?{PlwRn%~ys_P|&+Un-JB-Gk8 zPf(~^{AT)5w0UT`wPn($p#aZ44p*t zky;YIdmCga#QnaP@{=Lk2px5oGt>7@mZzZstsc!@7wl~2vybJ~_SX0n=@p|^8H(29ulpNQ=(;Ht(F&kbsttT%>n6nGz^k!E1_)sB-i)37+RcF}e%sMwPemB4Xo(J&ognRRxVcx%RK~=#%0D zNpGDEJTSe8r&^Obr0!9cmhrkoGRh&KSr!={u`l=+>>u~=Li;q=ciO|SdPKu~l5H(S zbi{sfIa_34)DB(KQ3g(?Du7X zP-XmIg&~ezGKzR(7wJ(PN8@kr1?!}l0qt78Y$dU*vUvhl61lN}?6gFYK=yfkf=+O< z#N#ehX#eFJEqoiT$gmT~m`)S?=sL2qxD&unKq_HjG$;hqmy;uw$s#h8morGPnCj}Z zCpTlNmAt3?9|Yoz4r!i-Iimrdt6MZg#5%A4H9avM9D~E-ekYW^KgHO`T@aOsa=qOl zztk47r||&6p1Cnq-iWhqDi!M94@NVrShQE#ASS?dKi zRujB=K)sJ~s-XRo;Kg87pmC@8hsQ)#t9hkfJyr21F?8_iH$>zC-o<**q< ze)Qj^8;-gYNQtna0H&*uz7E*4A+Jh^#=ucOt^Z=WGq}8+&{&Xj#%s<>EKChJ=Bu~o z=t|?_^`2NNJRIW53TNre6R`V{P>Jv-u_=FVlninAHI()v;=sNJcUzPeUNOWE$k~DF_3?B@)(*h4$@QnXW!O(%lXD ziL061*k>h>5vJC>orG5i$GR{BnJpByFJ+90Q4O0Ms+;a>!o0!S;8CA7BnQVa==hYTTLO$Wbb$5p|qFjO>D!~*9}y0F;P2+>p;@-#m- zAqTy*n5Lqt=Aj#g;*4+?Y9Kk`K`zAKCabdu`N}7KEe}_@;8RTmd=P<7vNt(H9wjHZ zsms)_+b6Bzi8*NlWz0!;>%UUooYcbKWm#MhED&bB8ZdXk+B`~|Y5@0?uNwB9vAKr* zjo25icDn#wks6v}D1K9Yv_ls3Ehre4VvilYpSwu(j}#`3h<*HKVL1vCm)Ri}EKAxL zt$P{0O^P+#C1rYbnL@^R4wDU>0am4Cw>|;4aZbx4sLdWH&1&cjMZ6x7TTFQRIe24c z=;*2zX-NZIge{y_9_34Tw9#;jsNE zBgvW$t&-Kp9+ns1k`OJvmH`l~y03|ASS+`zubd3!guxBjC}?yM^IH;~g~yydzP1jE z*RO6ayE+|D6xHN?I?-E>>m{j$4F~yRIiX_A>ZA6fnD>gQQhj_~ z*a}0tq5zgfiWkp`9HOgzO9zP|!QtH8 zP8uOkL3!w8BO=5j=6%o6JOMb(`wnqYx;qI%580}20C{P!R+tOly|V$x%4M<-hgx#z zgNf8{?D!IBs))MN8$8%JnLwYlBCvel^jeoFR6@L@c5l9p=al%Af+Ny}RTUy)zf;6i(sp)1=S*o* zGju-i8+x%8ihp78VyjhxB!yF6+F0*$mwZPk0LuxfWLfga3Dg9ZIp}Jh+|rIBdl3oy8qk;oRCf7R0~7ewqB4}SM}K~Y{*T8o+{~<7TAGFE)E3PPp)2|Y8!(Y(@XlNeGUs6cIMzy$Qn zgpewGT%e;hO{LZ&0()op>O<92@+W5&kV~DIzj=1NPe#<~*CZwRv!xx+RHW?T ze}7zhbdF%i4?U#~Xl{^0uLaJ49sg-|*xZ}qlW`@5XF0VHDKbATx$)6zT~Wyz&qwb~ z#5WQ(AU$jm6ew6XC7FqqfzhIUg6eL#JjTkoa{%lI58R=06Ci<#zf)JKUg z>hGuXnD1ZJ-;&ULcI_G_GbK1#0oTrEshy_lFVW>(kC4gOIZbSy&!cf$SY0jvV@kj0V(1+@AgVvc9RsEo; ze$05uohCKuAYP_=h9{=cr=L&Zxm4Ckam*J3m@k^4Ch`GFo9-rM70U&&I{JS=34xoK zm9_y+2{H3b7RfjW3BavN;j(n5-=_eS`aJ6BPkjnF294GhdO-WxjA&{=V|0zCOTT+R}3> z?)`2Y{&CJ7XU>tht; z#UE@Kt}WAjttTkg9ctng1s~Mh_5I!2z*awkd_9{NAE$`hV?3|B)MOejav|h%ldt6q z>}CmOGnaP7#`XTeK|V&mQp+busq*c&P=?egT;q+qCCpuF1kb}_saAQwSt5zp`1y?d zaG(^*a&BGDyP!TWd`42bTKxl8r`hV}Fm2qu3^Rxv%rDj5YMn@+KrJ#?Tyw?q8?v zG7Vu}x(z=>gYNzdGo>A9RZjv#U;V+OnNH~s_UI41nGecR9}G{-G+(Gl-3&`yoBI0x ztgqdsu}y!Sllgjj>H}A5BmC2U%aXW1kgRU%|Na@+9mgI-%O@P8cdYu4{vK5HTe6d% zOOvuwhFI)cZL)ew-z_VQ#J+MKG3wBr)&sJ#)G38_w4mhlW`BFZ@QUl-j-NPDc}3B- zqY!@tRg`F_1a0-pi&G_CH*K!ToyFxzJ{uBJwZ9)gXXxLRkiX#8nscdOr^yuA`zNR2 z52fLJ-7pj~UpZ$Jg}DBtrW1zMCe(9TVGLJFhq1`e+5ZoQ)-Qw$uK&$l0-xm8U$Q%+ zM*s)10*MhJj(gbYA(g9GW`CY>52eT9*ZfErg70(e-Bl!t`-=Xj0FIWj+Ay-(YCpfC zU-1wY^y-DQBChRav5PV5!>hf87dZOwj_@<_>Ig`{@t8o_UEO+9<)ywMKLTO1sDq0-^0%3`*TQWg8PM6>#I?3YLonq?=W z1Rg3eXg-SDlCwgEX+n4eb$-SQ*#!*pKYDe1^$nE>$X+`fkhrW$W+qE|B>q)=Olkp~F6IGk638!j7>ALFwIwUVwNyVu}MZCRh0`p!r zddvBwApOY_-b!dTDY=mA<%L80t?<8cRA(u-D*ATngxYL~*Ul&RFQI_sj z9}{-#R^uLM?2)sK?m)1o@xw$|%`0^6G&NkwYQu0@DcGF*jeitLMUL^0A`K!E$uhG| z&S|Uis&KvegdEoPoqyo0hQc5Os{;c+%pyBsl~`UZ%NEOum%=$R%2CndU`hM4w;-jI zp*hEagw!rEUXw-1vagT+?ngLnFS@EGd0p7f`?B5=dd0@_>+L5%0kn2*JJ^SG5*{lP zldj9vNqDWY<*|`f!A?{{>yIVe75qLW(pF@l8kRRRYP~ax!P$}5ML6th?VuCL08?^V zqhq)_m>q1jY_u-c@+*PQRT0N>@@bJM`&xGMtP6BGOm4v}UIZDf9Zze?^zCYfm@Gw? zd|FTzp^l#x`Ez`Hwv;+83!Y1v)bn?uQ!_VDi>x~NPU#6m;>o8)YHMFYaD@7X+y;+b zz*S;+y4N>op)xZ2bKrkfBULu^b%!&QPD@DN+NRLh{IHRRa8 zoW+3RfyylXh?i4NI`VA?M(2P6LM08ip{nd3l5zSiaoOu0d4@-E~dt86FClg1hY*x zM)?YVjHt(Cs(1l$5IQD5H_pQ1Y>-$4YGVOO1V5EV07E2J^XjO=ccvrrI%D5P#_$g7QV-DK&d8CEwm(Ngjt z^u1U9PVkSF$&Zgp32toGno|Rn=stgzqn1^^Ij=o;_AG>XClXhbKOv39C#ALkyDpL% zh<*A?(ihnxs904&;z5O^r zMG{304B~~U?lSkytbakz_4dFMoD7v$=J6K!EbA(Hrtq@YH%jRgrm$17h!0Q^OmX!nBj$Ze2HUMO+~#Ku26Rr zq9`p^??kL`=58K2Tg2)m2ZQ18szTnmWmg|S`s`0fl5sW!(wmhY6~xK~0^mZn=Xi7DA$247s8#E2f(qp%`t2eBtty4G z)PK+a$j9~ju~>OHtrRS~HCHJ&C%w+l90Nn)YIq>P@nFEv5Qc%QwUQpW8^a`XgY zlDTWqImVMf>}d%WkJSKP56Ehwa@T-4uH9Y=VJdeSidElb5nM9XL(?_UFiFT}$z0P= zWL8xywKh}{AHZIIm$*^L37MI#hQesOfGtUiLNNrDIv%AC^vkT&k!ffpzL-*BA7N&u zqr#r1y3mb_5*z(BnUxE2WNkPpa&uynjI+bZ)S+WPFu%pBcW`kfE>KO+v5t7|&3^72{MmGt5E1CZ zy+isj%KqN1Y~o5`tOf}MEN>qbfIk2?g^XSUnL}ERpTy1Dp*(b0d2WhLyeZ}%9F)K& z@tLsw0IMwVA=uqxOHAg(?=s8mNCuim?E3u~Xnv7qhLVI#F-Ma&@kstf2*Ye6S0N13 zZ-1TrmUZ%+bo<}}&QUZG+Ct(R1hK?y7}bMVNJyYd`8I5SpXajS4v`gHU_VnwwzG3q zYgqmf1~X-oZ$G@iDcQ%88Y>RJ8!;kGFO{j$-!Zo-*j)69BmLUG%t$xTvmu5L#?dPA zk&J}&y0cY1BmSTJLze>S+ zY+fR;K;#zu9Hr9OOn|$(!Qt3{c$k z8XKJGZ>PS7OCbq`h;}0TJW8qHd%m_GGA#9R$ysY&F7ofU*pjVJIu*E)p>0=tNq#5> ztYW>YqX?kow507W_eEGs5Kl<`u3GTnfOKefz)FEj0&0DE%M0Ma*DR*5P{n% zAm7=YbLETYxg;1!OP_Q`4@g$~z%fz}RrbHhLoF`VA3T`(0O0@P($-h`)YvY6^L&;B zu}~TSgrQZ1=Ht)tJo6yfhx7l(rq<=CiOx&sQv@_3!PrG<)M&$z@E7Ci*RRR~K z(bE>Wv0ucT-`VC81LU=L;OMr^V|irbqRjr)xatkC(csSY+rhG|o%`26`_4I^?fUHb zpEVRki)1B!OzQfK_1AZ-Fwd{~($g%loFaq$>D}wZ7K<8z)#*+k)Sc$7@IG{n;-va?4AEf}U`_J;@oSJ~k$& z=OL7R;(lgNdIs#zG43?v8w#S+1Q!1tO5u66qRClDfHvARGDpXys6i@aDaO6 zV^b#h$hfgEUR-NO*+r_yv;<9+?hn49Ps1_64`Vgu56iaALi;EBp>9Ab^)x4t*B2z$`)llJ()NI*+_cQ}~26WPGmB zF(r^1KKpmFhs3gUkf1%0h=Wxmm~Yr##(Tjk2L`dqF3hgkGlEs#N@11JETf41!A77n zkSNpK$YFJ#7&AL)WTFZeVN_1&m_X<6+^*BXy^Uf`{(*4JAnO#-DZ2_}{Y3(`G?P~i zOZ5-jF8PrCDK^b1@@pohv}W~T{D@@PAO^`bEAex5o0SFU@7!$D)Hk}*u(#it1rRuBhW##=X zI3MHvIdERS>=WRe1kT^Q`~L}?ZwXcvoeSCi|8J08gJVGoC*EE0KSFjY^jsRUJI{ga z0Z7Urkkx9H8oV0n)sBeBGd*H&Qz0n z#&s$T)U)PUM;#A&(5`PXi9pScF@+I z4|!%86|I`s@ljuvp|(G|My5QB>7Za>W*n?14y*PDS1_49tWiYNBh#xpQEJ{X&G^D} zq0bB{ zi4F)+Q>HzN{)cv--j!+hC$y1ffE%VpAC-1*O|>fxo+@Ubn1%PE(;427-!Imca^(3@ zRzde)B5)Aq3&2cH@dj;6^r{}-u&KX<()d0F=yKwkhTQ1=R4*Z7?1f7+-^Jfk%S4Pn zTpgpH1e14IPD!j)939|t(E%1RDK z4vF8V@8(k?aYz@wUS8~zIuO*1OjDDIJB-&3LkH!=q7AtcjYqyjzIT<>yvwZXA%FWD zF`wGlM~E1Zrg!`sI5;G?9_O3nR{64I6LeQik?siylTW=`$vN!2T~VXKE|{i5R1qRRhy3v-d-u$!Zla2QQa) zgs;+G1$yE%)4ZA&GOePj6Er^#w{nF6@sZ|JRR}-VjZUxW{uk*yCjDm}uH{D#*YYo7 z6`U_Ng&=2a3XxG`JOqw5goq?MnZm}ZvQW(_0A(xkEXczmtFpL@)ic>zKyo}{wT;uJ zC(3w|CIFR>BxsA~IcP z4Sy4E*~h2q$iVHpBumV!tb%Bdz^#gBX}JBmNZ4Qu)ktNaR?0FG~* z7!#Lfn1XEVH!0Iv>3;iDS?%cqAwyShf=FSJF5rVl5JCK_)bI zN3b*Yb&(r}aoX|5XA@=tztH&g_%2r;kSJj|E{?<<1rDb5VxrR%JAk=zvY;RP(aSkg zIKy7s!vr4tQH2a9aiw^|97#Nv8BO+@yZQAD?3U5FH0*|s<%S_+AwG(Xyn&9+CPfEA1Pjy=iB07nWn5~IKJ(m+E7LC7(E#tM1U8y&?M03%DxE#SLky3E1 z)ez0#wj=h4%s}i3GK>_jjbhDHN}FATa?&TUv>EuDZQk?0Zu8;oY2evUQGcdQf3#5A zT%2lC@M&UFdew8zdJ*YzJdo!DJGvAyli@L(EX;uAIEB55nFtssUN0L7<-3; zv7)3ibuO^r&b0-pH+E~WVC6u6A4@!JFW_tLoWy&Gp`_L1!Twix*{wfyaaCIzEu))i zQoAVr-c~1#ZkM_J5JhE;MT>cte4BGoKZ@8Ny`t-s9`{j3>@yyvmahD3DxD`cJ0-1o zSh|oOPFJ3M+iVqQG?#aBUCGU@;6*|4U?kLOjz85QF#70ps>Qq4hXn zC1_c6&hc(UoF03gmgXGR)#08)P+ryfS60MDnP%-Pq`CBZ4@9HV+&j~=n(H%eKk!M- z^*KV4|4noM^3(Jv?X6UuY0e+rAkBR_-P|CddkW0>iG*&E@r7dP4TSC#vu3ITyT7*Z zKQq|w=Z3c1F75tzX=<3dT}|jV)9xn_xOn%?)d!H9N4J|DmXxW}5Py)o^s3xeRTZI{_2YqF|iM}yB(^_*&>j^?U z#>V5;RESb<TavIFBSyQIM#jpf~G&E}}j13hzD`WYp zqQaa-^kMuEhw`db#_ij3A`N3PO13t>dhAE%DHr{&US6Ck!Bv!%?Rt3E&lwOAROHq9)MVG z#ir5VR2m$JU8(BjOxF$8%c<@(h#5l=n|cp^qCtW|Hev+Pa4Dh(5z`as59ZLnZiN~s z`ABewz4RrRY$1TX1S!%wF}fC3ZxKiKBP8?^>{kN3!LOV_Qcph#JAgCr&XQ0;m%9ud z=|cLa2JC6n1N*aMp1BdhjSRWoE=2h$5wbt()3F7z9TUZKO{i`f`+B|Fc@D>$lUHwd zaNafrwXnbwo6EAQpi`nWkg&&T0(_^HaorLd%98GIZ9^VM_k=>wz08>a_F-xgXIfMp zI#OBox~`Sa@H_%GYCytzEsYb7%-c@jv5LeMVvJbJ(BXb47A=CZ#cm~_>yG7@cC-gR zhsW)?wvi!Ew)a@Q8W4G=cePQ(tXCv17x}I5jZ$THXJfc27O$5w=lcCoGMf<6gPH-= zq6W~uh#C{e!V|=68nnUDdt1kW^AgkjtYauv9uLGD%i~3{uUqqO3EMF+zUm9U7i&=S z!fJ+qfGD}%n>Bh2^%VFhNNyQcd)H#PFsKek56t380e+G7o&`!ZLv1B$Ct-mgwgcSceOf-y*Kjmg+dmd z)sxqqFTSm69uN&gV=^W5p!c|fa4xl*tPN>6ZBSK3!o zSPszG43@XozC>;%{K^DDdbPGKjMFZ!8W5k_kKU`bhd&RDlCgZ2%THMM*pUlPw6A_y zEXjwG9{$t@xEFLF{eeEpE+8f_T0me`t_<5NC01`c#l}K&`%Jl55ilRG;zDSCRC+#8RqHL>(_99z>iqVE zt7|RhMZ5Mf6)|y^fq?T0jeJ|X%xbTMN3pgC=%MFnII2yD$G{thVg8e zzvTLsA9Q3jvI@3K^G3IX_}Eg$r7%@j^CI|>ujL9UfU>NJ=b|i=_aKZ>&_Z8FSSPem zAL2S_qj+y(jq@h{m=-z|)kd^YfYfg96;q2H`@rt(Bt(mYbF3vr^0jl*${z&*d! zxX{=7xQyMH2KUg#@*)->7GZ_S7vw=kE`a@owf(^HWD>g%UDRwn%3XKsbJY8_5dtxJ zi@%3-BWxERq*27HtsA6uB&@A>b8$XgRI7B4e51P6w|3Oeadc;8++}K)8&LY-na$kc>R?exY_2O64JSvoD^`wM4>oS zy1S<4qdu$d_~MBjSDf0?y);|{sZTt!<8p>J0=nHpxH0`X0ci` z`f$8jSCUOyHy^EF2RL{NDjO`}|EU~*I?lHf2f*jEvf55-{3M;E4hLN5v=v<+RbT3?j!>um=u z>%;Y`)I)|xV-b6X!=L>Zjp@R=OPR)SygB=VG?pE3%aQM$M<I?*inH(|yf5iTScfZ^UyKqwa$OfNANI>i5=@v<@AltM1%fJT z?9P#bDhr$f}TS$W-O!)y}?vtIVJ0bQ<)66Y7(jO4!zh=J(4)|JrAP>%(+XtvcFsBquVa{`shLp|^t5bmI=rHPH@`_8%b@=L)pk($&ev4L>Hd561a+EybRen|k{TT$T=#YeaTL$>c(m%>ixHkE)1dIS_lEYL& z6Oh&OVQGsMwHqc?T<~NG(Q8S$xoVj`b6MqHs`4W7&b=#_jnww8Z3Jz)~|8qLG5%ptiiJ+@l83F&N@A0WqVExdkjXl z=w&cfQ_N*_R@9^Yt2?PqN({aj?m%^CBKERp0Ye2@Gs+9sm6kF)erw@ycapfLvpjHV z#QMINke>l`ARdg=fd&!Grp;;Y4p3{buyL={lFA!$4s}bMB#EFHyIQp8=u&L&;~Jb| z*{6E2;KL-#9`;?Jf1i+L<d9O?@wl zXe4Sk6tk#dteQ|%=i}+7og=lnn!3{WcB-!Q{ZW0tOd7jdS2mC;5Vj}jd!c}w#zyP= zCo`S<%hS@iXEOJH(D%<}?ti84{h9B7uJ0uatW)21eP0YY?%e;YzE1_M9HYvx3g-z9 zzTNQ;Rhv+iPPrd{Ra{x_;oZ3`p%c4_1V$c=PM|N=kPA?91`yBeT2(>TthjfxfAbsH zUl6~zWp`}c=5Q`I^|^6xMMY&L<|-esoQIL`sV}+G!I}u`hB9{8S!zK+@WaU6S0b5& zK(V|R>^o(-IbDrl8DT*0Ahx_@z!+iG}?2 zHL$)??c4YJ*iB|NacaG}1k!R@vq>1{c6BLFd9|m}ClSbgK>+f`OSoM-M#7p~8^CAQ ze2|p2#!tM)kJkM(6Jr!o_n68j@>%0iY`t7P^3exaZXMRK^&ExOlWZt^I%_$F!H!7$ zJ_4s=Nl;yg!s>C+QkAbN^0mme>~0l3OCc*CeXRMgsFxH8mL3*WkE$S^i)e`2R~&AC zT+~UyH)9uQi?A9?E1w3<1tebL`YBFy7&ewyY!f3zph~{#&KLg-LauO)q{CcE3?Qjr zEE9D7SR-r>9d2)v4XA@kq-r<13c*ULSQ3eE$YGjTUB0R~7EM)2m+KMyd(C>>l?u&z z0&|Pxr2Pr0$*L@DFQmtQU1gSOLn@qRa{W}gEb>;Tx5Z9x6Zp1jS(cpWN)JFz>r4KH z)4FgXr*(?7#Y%RYfO&&-F#La#4a^MQ;(EXPD8}ak&3Y0`VXuokwIL~%b{MZ zSe9$yXt%pq>fr7ueL(j8(R)iLo``((p8aL{SVthtKBd)YfhV9AN@5DBWKaB59wa6N zXNW9x?O5~$8w$uSphm=oS7OSTN{BwBjyfkm>yFF2#1Z%Jo_h3iI{x4ctvn}bUVMac zg*?kiT!wkVXu0e+*foqoNLOZ>aDL8n-H(P~Pi-W2D@~73r$?Q2-ozGb*C;P>dgB$r zHQw0lM7i4F2_7WEdwe8`MLfm^g04;_N64iFk3ZOm(WakJ-3P}XBVTA^!TMq4b>7C& zgv*6-lH_?$f)B*KbtnST8PAC2064~NzGY0Z`GfcA>8OL9A9&R1n@abe=yb(@hghR^ z_ofSLRp624-@MO%rG1?{-Lr%H)XuvT%SyXT_rLrXzWV4-h+21?gD6SU_HE(JgWul9 zU+uQsO4(ZV*sF5FIJ%R#=BuntxtsGVu?RY)l{#IVFUN5?#v!5?{C->V@De?~2;Nvs<(42hPpfG9Ua8mjV`9&=4V zD?Ek*gPDn41;hw%c)6P8FPTkv=z@v!3tD&IGwN=4Yln{4zk(3MGHok%vSqRZ^|J$r z8ZCKPIAi{|=Sy}4Y%d*fFB1LB9|`wgmE0G3R=n ze@*HEDP!DzJcmq!#B6f)sHK76(%a(qpzdW?YEr^QO2u308ky5J`-!C9UN`Fc@$pG5 z`(qP00p+eXH$H|;$axhNK<-^&?;L{QnCVUx5I~+0WtrI$@PVH4y2*g z($>K<*^T53>m)N)jzO~CQ_0X7vrL7~7(3JT?$BDlYz!a-gzEZg$9xF8g68g=9Cwb@ zQkKJc6}Ef7C$ycYn46`|*mcHJ((@erZ)Do^F0cx4w&;~8DrU1RWNZ3*IOxYZq`<$+ z3a-!s+w8?Di4DBGBDLxi3j8?H`-Hz?Q>lCGw1O+_($j7p(e6OI0PYA z5`fpAxJNxAwwCm@WRa&fV$JLgcAFdhYiFp_W}Ro>f1?S_RRn$58Ro8S0mP*)$zH#U$7%U9Y_x<3(6v)R(|(TP8mRGlQ`uQ^gxoFU<5 zrDuG-_k2v5)l-L*jgNAA+W)XDqelupsk4=Ty|aP8-r0oD>Dr9)P+_d-CbdI7B25N& z#Ks#oXrJiGev(!k(^sf(8O*p6BFyU{xQ7g3$i#t|+BKA&<8L9Du7qq6O#)|>tX7~C z@_2x=Y2P0;bi06VCGibA2~K}7cJbQLI<9MLw>l5&j-`2j}W(c_>(W26nZmhjR zxcNz32yIN#XSQ!s@J1(;Q$t>KkFca_#u@04(rS5t7D6 zasrRs?3%eMHe%*Av0+WYiumw3<_dq3BxXcf@4J+A5p@OSbw}`B4;^vsY$~fjjT|e?gJVW7hb`8I#YhLa(BpW|b>D(q<{Jt(S?}$C(ku-UE8At+=StE?J7g>M+Da$^tUj*ie z%9$q1izTp{BrlV11Aqu0lyrpwmg^(P)X9z`!R%m#%qTHPC@lqEco-J^GEIqw z3eXE@n$`0@v|)$G^Tdi?-vxBl459xLsE=M zy9c)!aZQm#H{@L<6OI=zZ!rd3Q#fb|r~}7lE0D9lFVtTa@TIPW2=mlZU@Z##Z)(xF zZ;=POL@k0u@u)=@LV499Qea?==(nm0L}ywfRcWxRbzXo~+A*qYr4tYLeAbkqL3Gu) zja$7rlJ7#YUic;bNVtH=3M9g1Xc&1~;XuveuDn#e2y=IN&oNvQN`{aN5Kv+*h0T;^ znn;IBMqNy%Dp;nYdj483bQb`V4%(peZb%QvZsIp?Ey!VXdLO1O@}@>@*<3G-W^=9Z znk#l`Mxuw6f@QtPsR^184-kk!1ky~deGLj0*$Y@F5BfYi($vH^92RIijE+^eYccX_ zVS$9pxk=qwtaX~23$jAH;DVgj2NUlbw;#sO<*;x!tY&AWPXitBA^ry6%u}%a5M)FB`p>JlM8K#8L)@$$6af+GXVk(sj&LeedB%sm zu4>8WY>u(2y)}!I*O~S1uzd#_5H66+_m0|U<`Y(`PI73uddHtKH@VFVRxwrL?(|p=RA0rJH=rrt-Mws=* zURglI5U;sl<|^NPq{hx^KAlT3pOFhlUe4i9aNqj%z7`z=^R#>Y?ATSU``4{BPUrdB z-lI5~x#I}uD&59{id^GMRXN6&7Gi?p?-#!ldCs*}Ih=T?zO9w{{T2F?VU3fF(_?&X zkL&i5bd*?lr@it;o5^3ss#9M!pZ3v#^(&fB=kx0`zOKtl&l(Rg9Ddhb(tLWDl-WaD zm+7{yblSS2{{Z!p5@#e%CECZ1Y&`7r>ND1&(ZsLyZC&K;|Bxc&%!G|@R1BP7UzGtBS3EJJY?CYP;u1Tt?cd2+)qTU`dTY!^4S- zyRcO`%IVC=#QbddC=HhW-|?YyX;qGJ3VJX~N7xebQ*V*;`Z04(*sQ(9svPYU@tSjP zOROg082Ud(gc|US9vh z#?iaAsEqwBOdIhK-(?4Y@ER^QLoJ|&6$MMJjSrbC?lm8gK%2Rm4U_P$|7^+6m#WNbLBL1Wtq8hru~qkmDWDC0uby~LeH6%(^Tjo zvl1!Jqh{q)30_-Sq(bfb+$kf*Z3AX6Gx3ny6Pw=P*S`}Riu5mGgAa-P40KjW4hO_) zi9aBw5WO2J|=}bL?byjJmd= zDtc1Rk*^MS{5lWd>t?PvY_5Pr0@tu4e;j->KB9a{v9G0;%?6`!V)m#eG8OFTujC$M zf)yRY+&v&XTky?w4GynMK3KE*dF2Vt55#X)^U06aqpHq8!`A&yt>}S^o~*g(8o21+ z3K#94HOmO~S9;3=jqO(3nir{PeCmJ*BcA3@N9L`$R?Q-oi>z-5J3?MTJ-WiDB zPChR6&BD!-H@|)TMF`CIBQRf;%gs^*=JV#w5+eQs#yCumk>hxJGX5NGMeM)FaKV9} zs@_FXNYd(FWVtJl-21vTt__kN8L^b0PuH|oF# zhlQYif_oYDFjuI9_-FdX0YGta3KWa=lReOVfZ>4sMQ$-J?Ej*C#-e8h9~MXuD5v^> zP+OtH0uM3$H(t|hy01-awT#i( zkJwYSnPt38-AJ~qKcpl>SDK_!)*>CsxUfOChdIT@8YFC(6V&pfMl>#oLT#RGAHjM; zEO`B5tZc6eipuC5b7ve{TV%1VX{HN3&`)*Xa#+Kt=Mm&*n6ZhfF;;4a z@8oif*)EiwJx6Xh6x&6CY-_t_5Ni9Qa`!3T4UHu{Hy)hAy5(^To|$EOB*;3g~_>M-@uL05kA-Sn~HC1qvCP@ zQ^jA&s`yXqor;%B#Yea?I+kZu+~A3NW`OKB<)$1?v<~GWN5pUH8>XDZx37w7AaSF^ z6^vuk@HEQc**~HhxzbUMOirst#3ncM`kf(L!>*>SAv0)&{H$DW^nFf`=%ts4%t5pv z$T6dZa>s(Xh0P(%PFB1%OH0K(&Q~KY%l;~dI|LGw&;JP3i%uZ++LK5zvQ;lMCNhkp z5Ja>yBzY{G>_mmfeSRR#QPDzy-T$E(QQprI8eY`Y!43ZSu;7{kk%Jeg&4Lrv--{rV zU^1k!+=kbNKe%T3`r(Zuf*Y1-gAAd@WLmPtl{Gckk8u`A#MdYI+IZQq2E?brFQ#Jx zm&!5_d^0aqvos?_VQqbtFiRW00uzW$3MOc1=}q1IO#ePaQDpe&46ZCt_5Ssv8_{rh z*5==gYi@$n>{yz(c8p(C5F|6Dr|~9qB0Y_BPE>l_gqOpMqqfHWJpdB<1HK(6AtQsK zlN*1ag;!#M#c9W;hd}a_RQe}E{N$QT-J&D~gd8w`E?K0XlA+U`um{ONtkfXC)8-}K z(%p_Q$}O-Ij>tOn5ylt-hK&NsHi^1lRU5mpN=q1oQPpzmYssf>r$B9T zt3>rrZf%mkI%ZIEi~i=o-wQ<0=?X2h^RbdPDdDs+@e%#a+9KmwoBDF z&ZsR})(=aWApvqNc@kUe2$=FLT?w8rvDv9u7QUBm4m9M}ltYY?W>+{WmzL(0A`OJW~r`vs-ts zo2(ud2&^_{&d!OCHuPtH=er6rNdG#yMJj4=C$~sd5qmMDa%%2miuVc$)aK-RaOrs- z|A5Re=0p^SU%|=Wfu?TW*Y+7cAa;+_B}-4xOMzKRFX+|XZai9)C6k|la`R9qx6{5f z{PVv7Wk)!Wf^=+l3f5JbD9D{DSbq|vzxsOtm8>lX^j6@}1eO8vfXmd>D3^+`DP8kC zkES#6zl!C!aXkVS)bojHNu^6 z^EWR&OR^K4g%+%0uJ#K*+6VEpwqV^VrB^F+wI(ON@Tt2&wMa#tYSO<04I{14u`s~` zTwRz#Y}3aDfohJ1o!%?=^G=U%_SVqlEgxiexiYx6pz*1Q{U!r3w)fD5oY*LAx45ul z9InQ8StR{*I8ZyyvZRo$_ znl|`Z_qM!j6`y4C+Ie$)4+v?Z%~q{c#)GiE8K0S^?P6MbV|-%M_P^+haZTILa zVty{yz?Q$htrcUH9RFBHd4^%kddY=>G|K9i52P19I~`U@SqDJqN2Y(6Hh?DC-tN_ zz~Ka;p~ML5VVN}Y(A3-i`hL2P|9fcvfsgC$e>hA)iT5lr* zJB(d;c{jmJ#@0liOx4v)U0My$|E~}U&^{Q7Y2MJ)Wnt`4ff5kZ+xDJJdUN?zxv|l+ zjZSn#jeqKYgJ-B_t(nuTdHx2BbUaJSaVXn}{Pv{p-hbjP(H==zM%QcYam30e12-7+ zj-auNg}MGKD)eeo*^P1baPEz9Z_|dnRq_0$4X!z{o6|Et!}rnxnQ7nF>W&&`zge%y zCQE+S-YN@i_@K9INp~*lij4z_F}|rJM~Tf}=4lvC&Ny_83IRBK^wUhrn}t>@)Q zQp}LY*S3_^qM;5cDVYk+)Y@NSk|FLc*B};n`C5O)3`bj1AX|jOVChnAf2}qFFHer^ ztZ@&Ddwl0YUSAs-XLO3O=2L)c7HI@Ol^ZWkubPQssaN4vrp#(Xj;k|_JIsB;sz_Ho z5S)@c^JZkt-o~Hy`(-0mxLpHQWg5cBTjR6?gpUn{@bf}12!wqtTY2L^*>@jiV>wD` z`MtYI-Q9@>;#G`$?(&RZ>ve71Gd|wSB$ykIn(={8n2K+tr{ap#R3zgaDa5BeS?p`U zIF3_GeIyY?vZV9`nQA)Iqy~njyj9xAMd^24Zck3i8wN5jezexU;1C1Io+>?0zywhV zO10{fzP8acR%>6x7Sf!8maGp_(8kfZ`s|VIBqz-r&vP!07_=tw-uRVJKOB@+-2uS? zWQa$~Wp@fDAaqmwBI8SlM7|_$=&23-Z}&Q<8{!=V5@N;MgWx9AGyXK9XGw^*3Fm6+ z*=6^$UW9}7wXBorn!}Rl%PHx0(M0G#dH&kZ>nz2?j4n@NL4)5u1z*cHcm&Q{RCQuS zWMM=&vSbW0LlkyeDNA%{ckm8RqleT%BjA3Hk!ySyL2D9u=5P~;98e!(*!M$3J2KxG z!lS4-AbHm*L#tSd9#(4k|`WQT53@!ksJ86OHeXQgWn7JzB7ZwhJ0JCgBNiRt@G4J9|B zehCMC!2V#ptnR+7QEFc@dDShi=J| zZI!a0TzewlR8m&ynoUCtFN{|X!>HvT=9LLFj&sxC)*J;EzvK=|{$JMKKQOB5O!&`a zhGZZEcQ8SNLUiBuo+w1A&kPEC}0TO0%?!VFu_c(Se%@xjA0yZq=3V z+FiSquDf-&bXByiU;-8b+KT8_RJIM)UGH?VHP$Q!OquWJId>8gYWLkgUfVGDo_l_u z=RD_mp7Z>evVc2C8H1>BXF{bY$)*n6r(5z}J`O={#K@Cs)ss?c>*qyupmI3bEByI2 zkd<}L4%xTj|I9z9!jG1Yl_Q7@4aK9uknTmxixM|G)1z$ZO!`UbYI@3iIz(h-J(Osq zObLG;4Z9w$h&vk{XAWk^J`YQwgs%r-Z@pIOOs>3<#=YEY&{lcKbuor!G8UzSr!!)? zvBW8!gIwZt7(oz3x)uWpV8o5yKF8siZvk5U<)KmuUYmWjFmZ@c; z?K||ds8T8&AcaVfJjW(}XCvWs5O?{cSlJ%IE*QY*uxz!?m2W`|n~#&xJS1_a2<>6( zNF%AMh&4n!pct{9G(w7w&y-1x)6P?zx4@B{85$tV4h_(HG}lxz`@S*_cCk;|?Gmn| z*4aTHK?!xabddzS0D=c}Rj~Wkr{z4|MV1ym6O{3dN2DeeFHZ>2?NrQ+p(X zrLuqu_gNi~vX^76;d&*^maeyl2WMxdUNJ|JdSB3W4e5F%gQa}BUU^mrr0mDntJ-@d zw<&58<{=#xE6Pn&T;uVgOe11o6TLwx$)pa5MkLx3ABW2I6r?zjBo9zWep`p)LWMMX zrP^~wl9dq5_(U^>j7#07bla9DqlX**?q+w^Bu8}i|F&U$IyhgUtffh_VMsHMY3Gb` z9wtS4V;&N6H7i=*fUUL0SfBcUy{@voa4Gj^$c_C#yX@J9 zI?EJswbP0OW#cSnvccIPsF%xEQ`|W7ds`;GcIg=bPvmp*Czkiynmb759WZXbRb>iwNr9SM0Bg{b9D_REyQZQFkNile!&Qw+_jJhV%L;t566 zof@2C$t4aTK5f0Ladt(A7w+FE3>ytWo( zScDCjefM;9H=A#^M}jfB+Kk0SJD}Mt+<)OmoTp)vG5T%@woT0(&QVC|iaXp~M}<5X zR)|DxQ#o`41grn;=U=7uyEsR_Y%VC!&hSf`$6=^q|3pZO5B8;>ERuol-6VZ>jo`4s zM%PgsA|;P72+J%ahfMq!{xlKj%6$YGlH)|mo3L%n`q|{0`D0y8kI|}81AM92rmGuRzs46 z@9{fpbTvlXPN1yp8Nr{?&)UxjJz#zzD>~79UdWVjMq4lPvqmT4*Nsk8H>ll08E8Mx z2+yC9T+#9d=UT{Klu~bc&GYi=?1l#1f;D>^Y+r)XS;#V=h11VGCICI<0f4L-p;5o{ z+gF&+)_ylQ;t$EmU6iv5&OJoPDMWX7lB9Y{yPA}z>88l-h~#5mQ@1SicAH&=O)A*5 z^Fp2{HbHIPuF7|K-%kjH#VT+_wgW@@G6q!eqv`*6bGU+%;b*f zW?uLlowPIAYYHRqJJ4n7_U75CRm}ydl}$G-zST(lk~Bq4mBpEW*{J)f2I4{t3cXyN z_cy2oX(9ps(F!{wEaYxkK>W9m?NY~$HW7W%d86%px}F)p*M_+}UROTkllPS!C<|v2Fo%@{L}PuA ztpd-bl$D?LNzzJ>UMC@Kv(?Ja%6yTQSt>V8=9)A;fy4O%ZkoVi+1kODhwSiRtq5P8 zv_=fBT)_G}kvq8i9)SZ+x1;EyzA4@@zEIXW5FIZ!#8V`aZ&_=%=`V=mvuXX%KLGwr!$dHLrgucYJ+;#e$FKKiieUH-?1WKG+;YASi zwM!>2(})mZid&_Wyw@!`MVyfqK(%xKIDXCv$mELaZ8m)P) zC1QeAEOf+(pw6g&;>mrsc0%L>{Yw034|IBoDT>Ja29lbtOk_pdj{I$ z5!Pp{k&M|_h!Ga%yc?&YAyk@f+&G(qaU=L34h0I%ol@+ssnPa2;N7VIlRHg;5?)wn z5m&bkLWPl0@aOl)hwq0c5J(Cb^@$-ero)4($M`{{&t81X#pY}x&7$1`;1J#Wi^Lnh zv#ga9cL>LypWiQn%y6GH1dg+H93PYqhBQbO>NaB2fHn+osI;1Z5vx}d@u-Myi#y2G za>byPKw5s3nncwTa9DGOI#+YjwGP}7ouDCAr|2q6sB#^i1Tz-Q8)uJ)f?u!z>frZh zMp5t*{bw$G13L89gY)G}HmcbvG7=fb zZZ=5hV(hOnV>t^MikBn`4m$w&xGs4AcU}10aO3HxWl=xA16pw>F~ls>jm}IrZr9x? za=XEyjB zIch{}ovTUwENFmAk1T`OM5Gg0uKvJ_ShhMtcguX`j65=S+L9hla~*^-}L>BgSIR-&4JHx`h8V}O4!se4?qzlsYzK7h?^`2|0sL!q+{fYNaC z@<+eVo&cum)0zHwC8ym=%ooc4SUvb!5QU-tYfaCM?G8fi%X=I@xPcL5MGI_O9+tb~ z)MkLG2^;sj<&nmGTZRX+w%1f-)B62xB(;==kFA(6PW~gb1^wm zrA&2vSgo$0;kmmvsO(id>^#euvhvVW8{p%y#327J0St~M1hX%$kJNH(auQ|$UC!9X zk4LNG1SvLOVnfcQUKqJd5%IK?)@kV}3qeUBBG&s+_&ZiI)42&m(9j!U$6Q zTLREKqc4Hzf)GjpYjP8Ih)rUSSRNqr<+-%s<76w?PaL)*xlqDI-&J63F1Bh*ux-Hmyj45*vKF0K4Nw#-;_h@^>}O4)O?9S{YIbduP$kQdtW9uVGZuEED{$VsO?pA~ZsU7L1QTNPJw{xh z>0@+Gaee%47;!WY@C}RY)BXCTn3uv@2Nz&`na2xGZnyHHs^(dhwW?~dRk>YNRa=!i zRMmW|auwbPD%YrG3#_H*x;{yFFhB>1W#`c^g}fBkIs;Gv zt|Q!ewa&Y|sH$2qgsR$ZRW4RlJ1m4)RjaJZ`KpR8FHlwVccH4Hv*l`cT>4VEMD5mn zWWc+nck8Umm6t8M*eT%csIj6H0N#2lAjhUg?Dni^coMIyA0z9dcBD{(2O+D{t1?Aw zL2NdRC{(%{z<5DUB{U+4)Pi2sQM}*9kn#EA5feNP!PHE%JF);3vzdKJ|m= zyFS;mTE%8ltKl^2lEFh?mrvvL4@?$rIVXuAFW~&{D(?v*~U~GvU67mK-aVuG7&5=TGnS)k(yfH+xZjOS~ zPlq3asUHtN22wv5e)Ojv8-6TI?Y;g6gyGQz)z#6G>gZ%?v^Y!JeRdoRmMWE;wYX{t zT9Z|IQEIw=FV4J|q{L8x2qu~Lxz;U!o2+f~4Lz&Z@VAt|a{l)7x1GPw@Hd^mq0jfM zet_RY{7vL9#rvE5-ONJEUpr}k&);Zr!G)1#4I_1}=*aWzezZsj>C24X^Ix3$4TJrGNGy%8dzl=$% z_Gy| zzr^2C{yxFqZ~6NZ(*Km-EBLzzi2ay%`FoT1Y<@3+8rY9^^wF9jCUJ(Ht__ICv*&Aa?u;qgDRE`}PTHcVZErxKuUfu%oh7dhALwa88SAQk*4+8bp<2=b*Kh&JH z0u7brMMMZ4ed>yfJF(`rTl@F%pV>O(U3W)xqDa0n=Y4(2s4yl7oce|Gt1mu&c(eoU zoqdZ1av~+es*U~zEAoQ)-OXrTKL>FG%10*@ffeYdxbz%)_ODs7(?s$>^HDtW;+HDo z;Cl^g;~3%AN>3%bsI;^z^m8O$FB$FMmnPbDeCfTSS*QlL1$M%Mc(ROLqD$Vu@l*do z9xv8i^M*Y=7uIwl`Fd z6OUf2{vBhNSlDhG9|{*3yLQX>NKt8*H&T!vE<{85z_zVrk=~Xcr2ay8AV*|~D=6FoS3p92*e9~+^S>B>jesKdLkwnp3%nP51eU_0)+(D#x;WL@EYnsQcitiZ5 z;s-mIjQj8S*?IBvL8DD99IC(ZpBN68V~2_|@Jm7pqTy)ls^=4hRXysMS{q>8Dv@xH zKEerFuYpC?gXK#an#=D9ArKt`e685C+q_o&vv&Qn%mhEr@xumzSb9YyRy&zQz~fw5 z_}Q)7>CkiW^JXlM4j$}_`O3>vcf`-D*sXjxSl;=Hao~7&GAI6au9-w5^493&TVr1+ zJs!W{BeEIp-F&HequxBJYx$!6M#nFVi%mgNoeIX!*S1WKpKsh2h@Y>IUR_xp3P0P@ z;R1jR&iPcMjIEd%EFkW$FM;R^*LCO^=Hu7v-!Ev>7ufk+&nyp3C|d z@SbglhvfZFve#rGKa*1KquT7y&xz z#Ud&4J6L{i_qp)7INtA2Z^XkzG_U)tIKmM9UDVbm8PC)cXKFBXV#|;0`oR^=s8#wJ z_M*Yvlm9y+xYGyM3%ro4qwOZmzD>q6=#-Y9h_CojBptpyqcWD@vCAhG8y&ZXAIlEzaa`ou$V#k(tH8#&$ zEiuzn_$h1AMiqY6TC^E$b88XARF}1=9PM;#(L#k@^rAIr4O@%WqsVM6S|xhDi#U+Z zSc~Sft*k|xRM@c=EkGu3En-6Ztwr<1hJI005np!Ec2;xU@iEsJJNtwN(D=Kuwn>!} zue+e>x~#6Io3i4u<*Hq%26X0CyJTZ4$a2{U6eSRYW5err#F;vs85x(@Z3{j2(oD^q z&fL>R!M%9Luo_BI_hyQw4@h-8h^%dizL5 zPR{|IF{M?0qUGm^)DRtiEZiqerRD=<=k|gUo48hK0+ZTAR<3KrnXQ-4R{fcDy_0u1 z3dL|5{23jWI*RlNyZ~Ilwm5-0K@OvtV6(48Fph)j>J;HSWn{YzSj*S&#P%Ds zmT%yR%{pW)-^9~qYk3r(J7MkNbhA|*wW>E-)tjv9^;Y!;t9q4Hz2&R^(Vf*MD-Kj&D*b`1%B^UK?_Nbs5U16e{T zjir+~p@h-RkVuZfzSZpc*tIW~ekfBmMxLqhL8Fm)i8tw+k@x{m(wi;C)TVnW4v3q5 z`^50xow2|^=>uC?y5Zb_tQAzrNm1n6f>8&`1~@y${r%Cjx?sMJq8T~S8MEpNa7v7< z1KJH0!*Bs1tNZQNUZMI_Y*6uGOF?=IlOWWcR+u4Qw4fPv`_*==m+*c6fNLC`)7m1o zC8?==pOX5W@`_4Yg->f8S0JveDAeN~z34mp;tWi-u?vbyLzVd7Y9#Ez_v9mJLeLk2 zE8Oywq~;PYhO?5B0T9=OTwDNKj!S0|3QV+p1^5VH1O$azsjL;K1D%YfONHUzK0L2N z=!yZ%-_r`tam;Q&T&QF`<5}9xs;aKVCVs6LmzADsQp+bbstZj!-vvWMW_HC&)b?Uk zUSdc5%QkSY{tPpq$^RwP@*lzs z3g1_rh~d54if zBf!E#uI8Tf`i@51JNtyq)E(sXXiRT~8hj^xgC1Ow1fCT0r971@I32tOJ8zV}1#$4E z6>+S!eOy7WEie*)!ZtfsWptq_q?VR|@NlVxKilY+RQ)^R|D{Q-nAEiMZCQ^Ctj7$y zzIO#HQ6IoqN;g%=?Cn*j>2Cc{?5fK_Ejo_S=aHeY;HHk`5xTQnE%TSY)uh%L)yMLYS#l6Bi`G>u0`}6Vmd&HlASln3;MJE#=A%NbI_w?j?A%i0CG^wgd zKhEkx0VZ+SNc1xw026(_byCx;6HTELM&b-<=UxaJiC1|T+YKS$g@_Z67a0tau7@f# z+WvvOGAv8uaO)x2BuQZ@n5mQMj$&0=Vy%TN8z4Z@-^NLIWzlf5CN-HC zJkj<1n;pGf<%GhHy2BTn7+TxMb;KQbl9->WDDCR(zD@|fz22~sU*8vcQ@m`K<+M!e zPWsKWxuPZW26H5H17qfWBmZ#9uh#jQAb6QO=H*w;XHsMan!24yO*eV6VmHawO{^5^ zMGO{MALktKBtvpVWyJ)pX-8>DILZv`h>?H&A^x~1u+?5x;Yst}wnq+vg#kglwG zwsv>Pn4KSi2ZZp_6p0)dhotauwa=N~6FM7f>!v~>K7v+5!LiCBIkl5GFl3oPn@u-) zvtsi^m@A$Q>8bD;>209bKGF2abiD9=8tMV8u*U?l#Jz>c0cE_hp4#Q#xfi;Ogy?u^ z-1!;ic94pAZVdMNXH9U(`Y`gCA6+m{+rV$UK=AH)xe58h1jR zk3OsfUxML^;acbr;%Py5PC#zvw`q?zV=!%f5QpyQbK~<8hl>~ua({(~_EX0Y`W;=3 z{UDw=xr(xgCd@sQ^%UcGuc9u;a|;B`nfC;|9E|VEeBR_-hjV$O?M<1o)?fmKjhA`U zn`Ns{V;N^_t_xaXRH_%?YO#aaEv*o?#M-U#e2%qSBwD~?ksaw_OnN{!0isNh^4s-2 zOd392Pk;Ezci&o8s`IlM;Czk6s3y#UQ}cTj!6QO)ih<}I@T#yDM?FDJI5j$t zW7Q?RAcW;jm~KhiQf9ix@3;r)Z17iUflWmXYQHT;U}}4O)?tp zG3S*#E6v-hs@ikqhChn9Vz#DTitKF+7GD?75kttOUE2F(hN6(1v*9%9&0gtC4y{9c z)0$N^52y@Sjq_!*$u(edJWHf@$C(rLO_>=Xo9d`J8WvB!$bEA%syb@An8qLRnI}1V z;3T7eCYbC-DoWRvkWr1d>ifao?9={y5J*t1@TF?#H4Y@XOAaEt@Ke7h$7~?#?eXXj zvo*8k_YJIL=aPeD;>0`bdaifj2f*lmnoITTu-N zMRFqtd@KgPCcl%Nl_ex!eQ~BGA7??!XO()C%9yQBs=C_5DSQP*uTh_4mc_=9FZuOD zK;Z;sir^fd=6^Pt_>W$r!H(7j*jvAN&)t(D7CgC8R=oz#;L2<4uHvO7kNY%aou&x$ zhr3|xDwlkjJ{WCc2c^1^J~}N1W(?tXT5!wM$Fip?)VJhnLGxY^8hy;9S3N8pJh@^y zqbd|>0!%A2CHbSiyu%pvMf&g-*q>$U2H-AYw}BT7x&MQ>3x z!aU5nMDInf8tF&_fw%>Z_Q8{-uAUDml!56@&@}8i8R#oQK7_@A=~&z*m2pqEeTU4< z$b3pSKq=8&=N?Fb)Hr7u^ECE)*6f=vyg)(XTx0XlI?QvH7ONc9P%`UfEYTW#Ei1%Z z+14c}zsP%F+aj!RdbTW7gcvK9Jacf;*dLu+>$EJOXQy4hw_oMd!C1^XMh8G>vxSn} z=MyWbKbKs>E)snQ&>kd1(CuU<+dX6xny33G>2z;;){*Bu@!mhj#mJ2AWY9c2L$&>! zjg_Sy__>cC7fU7I)Md1kLMj#cIwmPT^A(W5r4E!xF5GMO%z82OvXRh~ZT(=k6GUjK zo;#|medMUF_Slg5YFc4(GtTV2SB&3y#CL89@eWROJ5!`9Z_f}}=h+!%Yl$Ch6`O4i zPLFLeo(~W1^=A5bpYCJ+39G%IzFK=Es3?jKttG)&h1vQ}aqK1}|I=ePo(rE3BFuk4 zcftFBJHR&Gll(SahxL%Ip?(IP(jNCBYhN?km+;6Pf)1d>C4!b`|KTz|0|wU1*zHWzh=o}+p6@0O zacUecC|-e4=zecX_ivy?ae>>NyxRQrz2>_!GShngDr2|Yzq%eU%%tCF^YVJDYX@Q` zbB(rd(n}0h_R@~|QL&M@UmgZ$pd`zKp3!^F^bDduUPu|{g&DDI!fjkI5?_^UX|k=? z4_B-E^sHO=UDWIE&brlhQNRHAC9R*N4|`;G!Y?69I!W2-MnZxU##_bS^lF}%Ay0C$ zRQFLaepy&dqb*z4KEp_yr)((P2b0MIoM+Oe-{wc3G7`TcL1#|`Ok&vxxc62H~)Uc^x2Y%pmH;Vm`x@Cm)hhtu$fFLw%n_{}}Luon)(2!_Wz= z;!w`plgc+Q(9E4uWhUvvH*5`^9+Xe?%A2*mtYn4ch@9bKA(tx7Grgacx|zRENu4r( z#gZj()Cycwi;w!O#TR{=Q!|sFFxrmjwQ1WaLrvxwi2-?#mQ(T|rQha(UMjsO_Ih;} z@=w_H{oF^`59qZR78HOyqMOY0)kY!?9V{ynL$EE7%kBqGRl9~>Nc*q=tS8wnjbxT9 zwS#tJ+h{Y2SJyw%UctptZN+ZW*O|NBF2@#1QDiS4TNHW5=}|?aKYb&7+IfIe)$(1m zR$heQD^MQMf9V|xE%zC_e+m+A8DEBsY4?wL#3u@pcceBPMuDA21wuTNgr4KtF)vb# z+=zf9^~D<3k0zpAWC z3$%;jVts2Z0N?Bx*Tzq+73r|b9AgeD&3Hh5!f2BXFQLAd@hd^4Tc0ALbmS@6OgsEk zGxNTm>zH*|I`_MNgSL$?iv)~aj$YU2)X7<|hhkk;{e8yne< zxjBrzYueE=Lg0LQn+^!}NmYNJ-0{?j6Kbmt>oE%#@jk0g3q6Fs)+yn9)uSki44d~4SHkDkGKu0DX?%jSyPmJSVrbo zn@dzJ$8u=1uVpTi^GeGnMyf30;C^M>m6yi(0o#dnOs4o{x$57+yK2ml%S)Vfb2I-c z>BK8z6h`7ZvaZa@JZ6Pc|3kH%xNcVcV^(X2vFo46xJ}&u?0Qs+;K=*kp%5_)TUq@* zq~NLWWy5TfZ}q`YJtekFiScDIAm(d4lG)h3L2@FHxKD#hgU;Q-u&q+jkwB@I)q0y? z6{FWdg&(tOU=K@@Ck3?5dt$fCB)BuSMbFq~nK9wM{i6X1Ghy|QO!R1N*SO=vieQaa2M0=y4mR#l1CEe51 zT#+Tf;GHzF7JfhfL)Y4w5_gNF-nzTcx*NmsVymV^c7p4xzGZ=2&CGSR>Nd8~v5gNS zsd1*G2A07C?{K>;5@t~bco;E|#seLq-5HLH5qh{y?G~hIh3`|l1!G#_`;jCGu9%d! z)TasS6C?|V4E;2F1Vy+A!ujA6K#RWN1Rui$wWVY6G}aFy)u~fGP7QD`k`W@vL%_Na zB-p^J7fQmaf6cb>3XxuJ+uQX|-$$KJ(58IQu;@ACl*5%t4=iC@=?yV&iZ|5S?HS{vQ750{jsD3*QG~ zaZBQNJSR3ADi`gG$ir%C=)zR=$ZtreRjC+@>izkbmCu-YxTERWhPSx|3F zD#ocI_k||Ku2tdtLzP@|JRG}P6=v!SLZrF%MQ2h?_+bH5P~QDqi@cJ@Po8;xNO-mT z2}&((sc-6)nxwv|7YfP2H+7SK^-bL*8h_JZ#uP3*N{`=!JyI7M5~}+Maf$R)NSsgB z6$SjV8xUZX{=;Qf?QZ;f4hYvtVDqO?*KhWM62E_}Vh3ZwWlInQ2glc5Aiov9Rd5x8 zC->-n)D*i1H*y(nl(+^5OLGtbw1`V<;A%K@25eI}R@S)44QG$QiG6r zQmk13{|Eb=>-04qR{{9*?9k({;XzM8O+kt{Q-s(Eiio)a*d_*umJX|qxU50VD%Bz9 zm{qezb#&;5^=O6ahYhOZe*Lgfbv&RSHmMFlmsZVY6@Nq?kRN8mMPo0*B=n7*CB!_% z_D^IyJQdj$a9D-MAhO)yfc!(jf6nN;1C!SG&P>b1&U!FPjk0o>AoN)pT#}P`Lxj{ zfw{D{SLMjwZ^&%^bJyyDwfzfOm;mwp4hkq3EmM&3S0S^Lt1P1}K}PPdBgG>5jn09n z8!Z&@h6nReiUT%u|Er4CvJz+{ygsC^7X}Wc7cD%vEFZw7Yf)7~QJG&BLJ41tL6J-V z92#Zr(g512w@GXOSt8OV&REt@d4;5wTP+QS_CfI-^1(iYN>+{la3z9^OAsp`-9d7d z>xf9u?|?x>;5BY$&P=v=0!}tBlgut_Cq z^}}Y2b$D0@=s{ZwGd~*gq_=V#D$IOFG9oZWAK>K0=nzi66j)n3rW8Pu)38M@JY<{I zp>)AbITzA7z-&PMN+z2nlbfn&Ggr_#u?=QxFbfXpSZd9=+~UrvY~w(`9lZe$ueh$r zuB^ntU?^6+wLl`=b0~hk!Px_n*N#{|B{gg5#OP`{(Tk|>NGgEQYEGSV57`j{N@&;7 z!`sCHb_M%TG^!Bp@8=a-x(o zccNyG9tm4CeouB50h4$#4lKTBoa!;p8WzfT%TP~z$2bsJ+U@wtB4^OYGj`ohF7x~Z zWA~r&K&Ya|ub?5UWoveQu`nNtk8H<1nTX%mUbEBnb47MBQA)KI`;CMIRx&l5_)RUO z%Sv8FlJU%vdvdCn*@?z8i+K_?tezC)pVtK{k6-guqNZZIt@(bc<+mPlIsBGjIqvl; z@$g}&B}cYfI*(d>gewE-3h|-53<6Jgdq1TcpQ_tV3ifiFrlr&ss`VXt^BK>qyeC_f zt>CCe1|^D&h50Po@}#)%(#0g0NEJo-LReH4c@A$Nqw6s5dh6)ceZxh;QkT_zdTiaNGj%ryrS@-0<=(@((n|{= zqz?-bOzp70C7V@EKrRjwY<3en7Am6;HMvBWuz~LVAA0a%w9@MP;0Oo#9YUz8?=;Lo zN&{mENuA;9qiA9l`(v|)vt+OVg1W(xEGTaj`=g(9VH8&YTtA3eHdoHIq`ZY;lcX`Uyj!K`GV9tvP>KP8$w!nPoeRP+i z;~ShXRl?@jkz%bJg|AxnunUaQ2k1c{wukLuhr37>S>q)n;mXoU zHS>J)@bKIj(4d}vQpP&mgwb|A`%4CDBw(DC|evx+zLto|~8d-(evf4}4} z$={Sadsbi1-(CD!{QVn$mozJP_Q@J%6vt+xPAbyJB9K*aGsGy|Y@z*ap&;wfvI_TL z-GNX`gvkEpEcQQ(%=VuMkto~pUtmsEq=#F#Ia5oIcUF7ntcB@f@4pbcQnkyFtX$PD z8_25iCFM@iI8f^ey=j;~Z6;-E!yiECWhZD}OFEEcy8k)VPq?kEuZ} zu9`V+&oBz^fP)MnU5}=AhM!&gocWPY;WzUPK{{Kmj<=qvjupgP|J{sDinspq1p=p{ zEmt#?+GKu|+k)JS&!}Ag`_33D*S~xrdK;x%YCG%C#NQr_U#O0S`DVv%X3`^Ph_qY( zORad|HQ&z7JApIT=(Qo%;F`|JFXLwi#rw2B#>wxId6XCX;}ff&+Y8d)3YkJ*p9rk98KKbTzm0R&YuI4Gkw6_l*8-TC8b+m zVpHmyb6u_9)Rksc)ujH>{3yF+!$_sou~n?jGEe6#nF+iV?@_oEWTEo9-RAyiThUe0 zPHcrawvDQqnxv38cK9W^3Tz*wLm%7MXrV5DMW+0IPo|ftlFxOeirHlVClpmr=E~Cr zSGlNg&Tw^`;d$@*l<>TxMQi}8~piG$5jXqV!nQo*{~>!MyX)!jLys7^?Bb@*&Sc z*}_*3h8DqC3VWQ%&226Wk`#eKvh`G{dDdqUKUjD9G|z8#_lwHG1Z$B`(`58QJtT{& z4#@(GCiLM!E6BpXo)*EJ`%OdDI`8ZG+^hhVL5rZ5sAE7E7uI4nF$OjmJDLDSn{c3I zt%XKn7Z098(ne;6GE)zq=8h3cSN9pE$gF5Y$-S!CmDUCq%oco`Oq=K(M&~h6sy|(7 zw{k-I_wC!~CQpJ>6g5Lfs~L){IYWQL^$y=l$t8JHT^3zYt=q$TUl10R1*-=SIZ0Fw}34qxL@8c0cr_N&~=0RiF2}!H96Oy;qvO7pq zHS_F*@D;q5==b?{A};Ugr)4TFd$y#y)jud7tOrG$XW4W4*IB38UVurLNk1*q=Vs1y z5xzYkz0A}G1Fxq5cSN@IwKl`dqlx7FjCaIRB1y{NfvwkFu9ZD%!Bsu60EhL?gA$+# z2wP$4)&PLzR;EkRXXd}5*1mfld+2HDjb+O`S`SKhk7-C@-U&4C5*LXk-e&6LOBeXP zq}kdNuF92dCt`|k5D!BpDJ#{GxwhBy1Ug65MLIg)UQM%0m>0L@w4Ot@*i`eNEQ23M zd3x0lXW3F0KEr!hNImzt?t;$JZ{{G(^-!bP6nYU+fX!yfoz*i1E6BZSE5|2i&DObDz|fH3phTbL_*wEJc9n_43o; z_SfmrIswD%x-D4%0rSWz)?ezj{BB02dt58fw~VzHnSvezIzGwE6wGQy!Zk`)ep9hHqW2qY|VM(9?k+S|s9Gzk=YD9#hd6%MEb0S_Gu7qT>2 z8rzCKa+~0EeP|6IXR~QrYvH&j8OcuTRF*+BcR8U7vqDf;1+nR&Iv>K!ve2P^T8J6y z8@|w?0eRG!1Z{J*!m)D_R#foNYx0YCigwA5fU{jPi-1$#o5)cSLi@ZRR#z4oieb1p zh&U;lpDta#@o}Wlun*2kYQQTN6%&zkkJKCc6dpZ8d!*{vbY+D0=)M%-gf+BB_hr0v zSj3`1wMS;c%0uH^egPd_1871Q7ez(4XAt$eXI12I$f_QaO6~UI2^1rDpA;vzAVuSe z?%Zx?xCHp&g7L&LsGxhrpXR$atL{0+&@Q#=2k*I}x(RXJ^EYQTefS~4-H7es;Y2f@ zKMsRjnx6B@v}2ZT7Jb6@L21?Pgl?NNm6XgP&33lsh#$sSL*{txzjV9~B6)Mq(Xx@_ z6(01`@p_et^o0Z=MEVI*f!c3mAXM$g-VzZPlZ5z_bJo?`A4?mdqsA#3{hC&({EF(3 zDN%8mrv~+Y_iw%Cf8K0f42cwRYgseV$4_fXA!o0jQu8@@OOH3&3n!WBn^ExF@w3y` zpYrb+>;L%7!~R>$^W$3HUYDBK4E0u!IkWwD2$nuRM)iGXUk7X8QfbaxNTxG-J{iqN zLJ)tVv`Yus)(0u73z?IZFq*rA6jgs+z`sBIr1LszLh74^lJraw|C(p}&E7y>H-0I- zdB0fAtqE5{HH6~hFdjK}cWX%I*xju`dCajJ`-HuvTwR$bb~9=aa%NwuK>f9b^q~4{ z4Nt>IFhqgUHnv9M*X%ug77O>N&tegimenjV5wA`8bU@XFx{)<5gD0X2gw1Bdv-}R>gWkWpkZDY(n?6)lB_Z zmDY!ucD^ym^3SY#5>*`e$*qH=o+ z!xk!EiwynFds>icBtE3p;lmYk7h{*mP)47xVyFI%9PuBST97$h(Iexy!fs;Bv8n~h zz%i;01E9gkU@T8gQ1(nNB<(C*l*++r>gq2YoKgyLC^$Fz=akr)7qsWvQxk@GiM@`9zxa-75-`dD&_)eFV-7n>%790&E0W&F8HYm{gwoa*m~vTt&HE$4C~$tAfgJ1Z&DPyf z>+X%#-J7ht*IRdQ(3ywH_0fw?kN(J^=D3I2m0IjPx(Kwr-@G_(%d|%4t?YFAtLGkP zmO(Jj&FkU;lKO-1i!rQkluM9I5yKQ6EN9x-+F>H=g|xFJxH*;aKC0~?Knhv#B5JH`2#XLvU3Ha!=1haOTl0bsX8 zXs8Y^8hN!v(K%|lPpt}s-c+C6c2!HRTICP@a@(X(ZNawjEd)Fb;@M|Vx=t*TpC!^? z#3M&l;hd_VYAl-8Sm5y>;`kW9MS4w$(O32Hy+{?RAe^eYkasaUIaJS{4aI`kchO9D z%R3z2Y~zU>pP?{8SBFuA;Cn_q;*_AlNoGMX_3u2I#ojl+Fs{XmL1p>|=hpy>^E3H2 z>8r;D9?Zx5va;Y1VozF=?idwtR^|+cIRPo2=lla)f|#79By{U#V6(H&M>DSIP)7=2 z<4Ub=^pYnWw^ULIK<}AmU`^OeRg6TaJ})r z5$@GC?Hh@cYbPWZhR)8M4zUn~G<4oAgd!Z(eXV{IM1Bd}Sla`HF^7|Hp6J5;sJW_F zeaqQeNIU0I#4gC`yc!YE5dftM#DJO=nlbmby@k#8H|BLN@*un68spyXv$>v#zpd*V z(@ZkaepP4DRZ@=3ul9Pv{0D}tFVTwvvp{ARP zvvzE@t9zSRX2TX$I*13H6?8)i?U>wXcHMl56k6x)Ar?`u*Lc2bFUAyMzgLqM`S;6_ zDd&}34WmR%;MGAUQBDVNroPr4&@Lznkx^JVskzi0{bbWkfvo7vYU6pSNj=cB9mJ^G zdUo&w4JOpDwi-qOA<=b5)O%EVHi-Sq<&q%wqQ6e>tkM2Xm>Nv-v$5zUV3~ znxElK7DnZC78I6SIh}_~x)#Pe>W$bnOyOblH_sjB%6c%GBPx8UyRhT27L}yFM84jI zU9qVR=G!ysWL#emUN_uN&3G*Kr>e=&zpyuUC1D_D@rj@ec=(>GGYzEPSlAz%&zJGM zp9>pM^urAt!79tTrGzR}l|Npc8z1hnv1>J8A4j7wI-O7F%5&q;DD{J(+LGvmP*ou6 zH=X$@2f1;)^e=(oL>WX^Fa$D?uj0Jbj=j)tszn55N zJLmDdZC2tG*(@CMRQa}Esr=jtPc+vPq>RfON$tKZ_M3H`VM8EjWQPN_JNv5H7VfSC zXLYVEiGMT!Kq_psAM9emSu>V$h0%77RyHmmul?W)q_0RHkW(*xKu*AnI|#X!ZF@^4 zgd|Z1pOP7C?-e>V(MyWe{*HcrNk4b#=aZ7t4O#i1PjEx~3p!nH04*I7LnVUxIVzBp zct@AruJg!|Wwq~c^Xs;xE994F&2_1R&+2sPsL~F>tV2Rsse^qwT~nk9ztn$7Ga3i` zC4G<3sMbT8-Doe7=R?ABL?>;%CjL?OwnFpmd)1q%0^^Cpxnh>_Cj1BLt?)Q&i&R1a z52*(wUWxI8SAIsHfeun9! zgw^h6h)PEjMG=`USnUCwBK51+F-y@Lq&g(ixN$R|%)kV`s(2Lob#*$ zy{hLPpVUg@+ zTZM9@kLO@}i~M5Fc9}VW*@wg%1pBayVE1bTJ1Nv52v$(4VagqhMy#DD%HwBy0N&M#$es0uzKGZ#{V#dE@NDb~wG1h+7b&l|$C{K}CFL&s z2$iiaZ|X$+Jd+=mq}Ft=kr2n-U|qvF&}9x9ko3lG>56e6K0nd38gFnV{D5>H0L}#+ z4?3L!@nG7&6h!E)A1wPzjRhx|_ZDZlipZ#;da6XQZ&d~LJrHbQdwS6vgyirH|0-rd`JCGP`+(3@>lxloHV$l)tyT_xks}fjJ zIn~veuYvCM(g(5}6Hi!eJ*#K)cbLB{Pqx=H>VJ|Z-x-bk$EZjCe@P_fUeNfJsi8(& zz~6&r@<69AwO3h?@8f54PoC}iO)i8D-dsbC1=A^&+0YgY1~IsA~^wMFO2C;%AMMXD9T~9F&{e z4sm8CGdnbeNpc!4-gm?bo=gr`|Er_~BxZ{ZF5Dmc8a3mFiK>&iC*PicW2O_v0c$Sc zR29FF(-B6f(I1;wa&Lz@6DUYNNlCpHQ%9|Z(e{YIS)?CQE1ahng{76{^SwB-DoIfU z7F=)@LuTK1JJ|>m0Fo^4U{a#C2tpuSBmL<-+4UQrUY?bP$2hRW$VOL2ZdecNYu3(4 zo`2~$^O$#hr9XMClx)rGicLFrI2g_2dfwkKE*T)zeA^h`;7^MGR^|}I0)Yv(Vx{&JWa=U&fs-QKnFb^6qOA1zEii@ZWsDk=|3x#IjLuKVHT?V|o|9oJ z8%}tf1dCXMl?BO9lMEgqt}ul=0}Q9TN-#<5ps{<3Zoebq4(TR-Hav79C&u| z*nZVz`UM{n&Ce{SdRaCXW`uIw13!AWcELyDw6mO#y~NMXNpu^De~=6UyEs_#r7Hih z_W#Cu&4rXvBQ~CC$a(eM#Op@eSEPKTUpL?ZPBhe90+wn%3j(~Ns!C%&7q=zn>ty1! z&*7n}Dp|tMx$p&lbQ)LlX|u9`ptuF-b*)!H)bY)?y{IVoU%7MeVkDhlU%vLQd8v62 z>cQxBC`>BfkrMAh5Ak~|3$iH(V93^7ASK`R=DlqBPu^)l_|%|oXR3d zM~TQIqN+Z2hxcgs_t% z#e$noxzYBx`w;z#yd^~DmO|I#_gPy57bCxCczQB^iz>tKiE`AP%RwfX3Y4n+@w49e zc@K94vHZ-CM-S93o)pVYL6%bA(8)XV^b}Dau76+N?>W`$uN-GR@R3hu!ksp|?W3`C z=9aVksU>jle%-xv*48{$jCD)EP`yX*pj-bVR*nZKL(+r^(g_nu^`43iXiC?qKOX!Yl~EZ}#3?`j+77a1TE1x*`0VLPr{L zta3E`lBnhG2xhqNAIU|;`ySU$R8%=dGAuz$dTX(^&{96FGEsm~P{g|%UtlLUr=3@R zE8zmQ5z})VG`NWBMY!K&h5r_~5M!WRP5h%;FgWe}occg>@ia^UF)Kc`wxFIf8sM?cS<^)TKj>CN)QmNGS5?`uXpg140)3uqpj z-?0vl>+5o?b^eZZa=vh4nK}`0wcL1=WcrVM=Gu=wzzu=@3*Rk>#pjB22}Dds^+huLl!O)I&FPy zW2Q!DF4a*=HWqfE-Z=y|td8B*v36D}4E6O#8~VSr^0?J{+NzpswZ29rjMm#-7Jgz2 z`}wub$gHE`M?4+Xz6EP%MW=`c4mvcbhyl#C&aE6usVe~qS)t*?SLq_N&U#RpN9?uq zYV3+pM73+~y7jeC>zT|Y8%F%?!dhEsIkgKThYk&7QODXDM%!;$6_g3x#;$-IJ69!V zpGeluB}5Ux8w=UtS-|ECbZa)3{iI=$3?RV)kko^>fd=Q#aUW5(_5}z+L_J2^Y7VB( z@T^oV>IvkB56~`ay!z4)Q^0)P4bjQ1A8@tWQR>x9mC<$$Wzpxw?*O)9-AV5AZg%!< z&B`kAbcAQwdp0u=gsSmB^MEM2aER318`wMEGoGZc^wntV3Om7>cq<4*qU@o*m!$r3C zB_z9$t5HC*3X- z(*+X%&$HV%vsg681x`4~_8h1((*-SUw2Ce!EQEAO_6^1%T{CvaC6(*xF_Z01$(WuE13uTmna*7f{Eu&;rii2AU!RK~p4>aLUG@P)(5#sc zMR|I)ckg(eG+w{DZM7EavH%5>KT;2Atx$TRtGwX6)A z<}+xgVE`=~5ee->`p(_Guh*MJwTr;)hx46D^z6DWBP(mxt5*9-(yWJGz^r0hKVHJXFA$np()7*~Om5uLN&#%ZheS@->8c2;i zlY3M7PZ0k`@aTi}{ghS0<;R9fQB|K(%yVgfddkYoZ>|2-qWURIBwu)-G~8c$RJ)bD zgRsg{(|3nZT>tRocL({&+;4;?N?L6Ehuw&A(=pGHBwX;QYrLI(^T2hD#6SiJEc-S2 z_dyZZTHn?p+^2iw`QQuu+jQT9{rbli0d3QL-|o^siRbjszGwB%KMOPU$Tt(hgdI!F z=3ftFp$hvfEgrVzp^sD^RQZslrO3?%qO^DLBs4$PYYF)s@5}1ah0gF|>m&{`TUW@U zyNZH|Vk-KM4Ax4N@Q7=@DI1s@cQ)7av{488ni40l4fDH(ly#Tg`_!3TOSsMn^`m#@ zkIuK-@1r8FBFmtsZr~QLrDz*iEYLCqgHf>Hi}|mY)dyQ9kr)ZILtN034qi~6bZwBkvC%#UxvUh6pMct}MU8*g!Af~Ge(}DD z|Bl>e8?$obsj}h9Cre;kEC*Id&DTnERPNDt$F-u1iA}XwrDt1HJN0kw34YFO`WSfeU8L%o7bLnoLH?0NM`vUC$NZ|~BLc0nP=$9Kcus$~H+S%xP5 zgLU)Gl0;}9bk=p-w{u}d608om$DM>BSQ{?6thGXt$jrD?GEdzbP`S-PJHp-C%Wi4z zuZzvf4MEQxo7pAf^nI>&hA0!DH`HiTq>*W>ny1#y_g2jvJzL`4OfSWl*_!!>vqi)M zv8qC>>e)7*E7Hp+Yx!~spAmHa2~|c}qh;IGx*gtS(N}tbN3#nOVbFU7N3WSxs{Pn@ z%Uqzq^lxZ|JJe5dCkTNxGzuzMy2(tR+Zph(r)r1p&+#$9;zCMQxmvf_TeZ-x*>nj) zZv5D`@$o1CxoKpLI3zQTm{p56WrjQ&9y3iWQR`~GRn>M);s3G;H0d&6FuI9apZRK|r=2R2AA09q=#U1%0XXilw(Mb|KtI+``GA!3^_4s7XD42nuW^$@(5!LF3UUH<3y}qidiuxF$%P<%65mj~GF_ z7n-grR|q{n_zkNq`0_S48j^vnPn%uJ#2gR1r3 zsx=r1E%p}MtE5xv#W_9LNa~#-E)`w8a$|ZM5w-&9tws8zVYaSQ4HR!&uQRSlZ!LB+ zg7<>r%|zR>GM8V(+uK<9z@LM*F}D{jK32J4p7hv{%0s9J(WKQnAWC0ay|s?9%FylA z`>2r4Mg*O9Ai{Oj;eeyT-v#cCD~|rRm_`Hv7idU2=9JO=qsw(G(Jh7iE^4C zJs6!>ht8efSz1by^#PKKqxt6D#atM;AGXZZALtk^NMgIb$d0!b%~eYm7(akr8SrRl zpX?k@<)-d;eV%*Po83Oo(s}DZL}LfJe7UId*taZEg@mpP5hQ~s{% zkZIBK^fQ7rDxL4*VF?8>!XygjS31Mve%!q*Tj_}mF=U?B$WwMSAhx?Qju3N4J2VqI z9ox5#iMQ@r@aN|?QIDs3JGZ8u4mtSoCen)B7WtaGDN#d^&arDSaRhpV&x9h!h zgV|M3>lDx(dq-7H=!4k10GwIAgnjQLpY=Hm)5zC14(1 zLp0^%cyzp$h*ov#_BD;_|E#xb&!`oJD$Dr6>)uyGN9`q(XU9_Om}U5_LQnN7rg1FU zqi-DLOqq+S-tb9?$cNAc1EP<`MCot_cu(OZ3S_YO*mI5A#kr$jZL9X;38iWY7J8qq zQ>U3&^Soc>;1<8Zm-1+*`~mO#PAQ@cTFf;&WQ~F~v0r>qG^s{|FjeM-3mO`Ywx7xL zwia>)bc82CZQ1|*$xhfYI$?Af{>IygsSI~EZs$2!9 zeX2X@3tiY2z^ls}TXIU@Dov++bKaPC0cH{_A#Rx^+~HFY91IX24Rj0O;wq^yYujJ5 z3`L^LC4(KU%BK;xU9q@PEl7QVgV^4-ACIhah*@_tt~VB@w=U2q^w;{(i(M;6_10=m z+4R)-&7X!%7zfMxg0#`~|?y||oUmYEzz+n-ZKQtom%-`s;+@MXVUvEE9ER+5~X zi6Q+!D_L@oaC#oF zS{S|8lp{P)HRO3m)l|cV)RgDf@kYRcK2@`X-6hJjwk@;0e7M6#+t)PfKQOamiS98- zk{*9)xW~uz)C)J5np)O66#bm`CW@qNpc!lh;Du%hp)@iV93`N77w4xNn2{KP@7QZ& zb=7c7!ZK?9%-n}mAguv~{UnTy{h;W#tgs(E>i)do{v39Hy7&=>-op{5X^ijFx6aCP z3OAYie#zIYj>Iv3#KUl+M?cKB5-;h8g;wIEewb@%?Hn$G7isNK`@td7o8UC%v($}N zTC5JqIPbO2Ti9IKi?gyrRYioV(hSy(c0xK14CpM44bDegs8MLT(jS@^z%7#jmnf1g zy^Yv=IpWe${QNjr+odm`61gUESyruQB~G9`f_6uBFw?JVhx=8L>6cy2iA4~~qGOeX z3z!dWQgw%O-5<2Vy?rC5ZZOiVjzn7t;!D(HZZF7E!REr%8dmz(MXy9%l@Htv3@GB1 zmKVDUd&?nb0QP|EAm3Slugb9pq+k{E2uLLq`Y3v1!yh7*%n|ojz|06f$)w#wTIi$y zpS8DvkE%Ko|0l^L8DL}v2^u6yC{d$eiw2Q&K*P&~C)N&M>i*hY+uGLc%hughNw>Zv!X}`u0$QcjRaDyEbkc386oSoq|KI1_JChku z|Jz;tNao&i&-;1KbDp>JJn`og@t6KQZ`nW6CYu1DRnf~>6P<|^lAOKI#~j}BHty} zm30_Ziqt3~E_2noa!)J(NAOu>S(193n#@%YoH5R?I_sNmvRpYhxnf2Zc6zr5J+XW~ zRWTp@JVXbHAPQ|_th&X($#q8&J`=Jkw*cO_3|?D7gi(Tz%HtZsj}mY}9*IG^PJRK2 zm|nRvXMkuVSa)^NjN{9_Z7MfSaZ8mmS$oERBk?1Y2m73Z`rq6oc~sz3_zmr7Hnddd z7EgPg)_IF3d02l^+{R$gg$j1-$ICfZTu={g}7G z`Y|H;Gck-JgV;QF+X4Wmb8&^;HlNd^N;@i6N0B<~R(G8yu}++79C}f2opqC&`Ys&@ z7X_UaJkM@h1H4A3)>)r+GaMp=vygmTZ?~bw`^j+{{=9M*9uGH0FK>!oVm`T^ z%Os&o7-gPueQB&e@NOr;w16&tL(3S@1d z65^BlrXr0R7Vn`j!i|D>_i+muNml*7A1G)JDnClS;xJ}Tcw`Qw3?83E^kdfh_;-wd zBeju;a&~z#JPF5lC&Rw1__|Zr&J+{UVRd(hoR9%=E4Ck+_1jd0%P=jxFN+rS@S_k) zTK#^Aq&|s(g&r+zN%N12{!g}(+W{f0$dDzRv_&7Lg-6fohO+V=Vb9u&gkg+<(t28C zfpL0Kw?PooiY>;9EoC;BWS;q;aZG=)QHU2dy}bx|1(%61kx5piZ2zEEQKU@D3iOI1 zMTQ#a{R&UX;dU}M;dMpBeKgfyIvW~7+}d%hO@i~`!M3~Om@G$IJ`(RNVX!wIqgf?= z#8~~PjWh&WeVdBMvRw(iAZ$;#vl0Jn*2dSTg^ymg@z~6;75@=|DB2%pg(XI-J43Xn z>ieKkI@2Wes~N)Arp=6t%5?jN2FvpsCQK-(2Fp8@IUiCGpAK-{BeJ<=t7%`3M2ywG z20SDIyIIkQS^o%Jxli2VmC=2Geyg0~{a$1Zk3_6{r$MMKGN!di1!k|TjdNl{p-PEb zDRGrxw&*3GgeLPb$pUE^_ituPbG^y_7iHqOnkGd2+~!y9oM|d9TM3lFbOh&sU{$}&D5}#_W@QNEi>**k4nr5_l!7N|)HmS}E>sN{P5rcN+HYss_U4!FztkFrZ#8izP zu66j~IP{#@BwT9W=clG%G_byWMNwOhz5)p{3PAA7S^98mPl(;q&0wgTAjIRjagY$7ePH? zR*Eb%sM5{Kbv&a?5Jt6OJ>?*A)bo~FvvWhE4Rt5w&C0D59kXkT``9d=^87Y;=>+|k z9nd=0`G{q9z4lg>HW1N5geQr_KMN&mOzIw9LxEUW8;-?3r43iaW_AzDVB=3CSs2RN zLHVH^R@sxd(n;`1!ex>G%TErk%E1zQ@cbtU0)9g|Q}`?9(Vw;vNu@P(o-|Y<4V~{a z6zHu5g2g~haT#9EP_`$B%6`W$NH_8837Z{+7Tuo~dZLbi+n+R+6AQ-nc$VJ#J-R#F zSnfAkeL2rSDV=PahC%i0&_;i3+A=vEUuZp^Ivmwm81tD+t~ZzT>DMZM>ai)j8aw-Z zIq#9eMZ?3fNeEMnmAk#laxRf}iwGf7xI`Nhx@rtZLTF!OlL(|3=)F%CGh8qQU2Bgn z&-nW|9O&ufQ7{1lUStql-JM%qC^CqfNf)nfKx1)ZtQaK05_c3e)}q=$v|b?F7D+^) zR{|3xh};RV7a6M^S%g@;56j(iySm7cm-viMFJPR&xj&N1_qvgvF?+y7ybL$yImELK z7yLxDu5-DU8yesu7kyJ~_|Q*Ss(wk1`$V4k&%BDrn}Zo7=ePQf$=S?E9ns4WCMuGn zqm;9%S3TjH?slO=`d1pk(81USk&ld<%O45tSYn*pI4W2oOMsyKNDr@zRx_103(gYa1E9I9r=QhVrN{JYwh*ZJa;xEZCw zm-u*~HO}_c>vE1~RTpUWg>;SR!*O5WD{q2A&Qi?E?q_I44c7O-4eI!Ck=IRXYjKJc z@m(!}I$#97Eh-Ne$5bTs5-IC^Mkr4t^6dHRFSMoja~4E0Nb;DM2BORRz&(xp03pE8<9$ zTzL|MrRYzXQUS^XEW8x;c_`|OU1d~?Xm_5(Z!>Nzh;mf`L!T_J-YSukQ?|n`E7EW& zs}MWnjc5Y;lr;PaB@OR~k<%eaDvOlFe6i?7V3N?0CWbjKxK?83!6tJ$j)PG*rIjgyhTicw&Xdwq%gx-e6WfRwAHvKadFgRR*n+N z&Da*C>22yDUI3IRVedb(`kcslp;Pf!T@;fplY~)nH-;~?AKyPrxUj>%M%D_2Lza2l zBLXjM(+2C9kQJ*I8;uK$8<)&$WQe0vP;{M$#X(26IwH(bK_4fk41m1VF|ZLS?#5uP zqI}vH3&8@)h_7U7T&blHvA(tiHu6NosyT>N>r!G>>y4voXD5ZK6v_EH7VMt(gXA|h zR~U^|#_cPKtb`?UPxB^cl`o+Ww`sH&_# z?jwW}PqFh6-9ifbt?6eExd?HnF z5nw6VzMo2w04I_nbe&WA=iYWwcBN7dvq3_8<2#)1_k+TCi#nK%W0>O(EB6SSJ$7q<1ReHm8^}U65k!ezy!JT@EIdq2@CiOjo9qyL&|N z7a(4AYOe=TOOgWnGN~tr(7trP^CTlZxi?!Lzl&Uyz=lXB@&Lku*bF0IObrBfTxw`J zDke;}F=(u0s_@Ev?K@?hXPoh^pB(}17di)_h zj=}0(5$gxY`9p(&-eD#}kSF(RRr&*1c`B!;D)K1wf^y;khe`c&MkT57)?`YH<)&;YM_L^(Ko(Mm>{2c{(yUX;1b|ejCFL;Z=Nm6Z zVmXlnz6*NnTA%tbmy%8)PzKewo!Fw9bEXR|2m%87@F;_jqcjGhbLyWbP(6r%@4y*n z*?f@(IX9P2aLJdm#;W^Na$Mh5lY`|GW)s$o){d*3q&SQr;^xL!ogt1S zk)HM*FgspGq!}w?_JxMLQVdK-YTZc94cpX^D&Z$FqdX!EW&P`7P>CgFf_N%kBTJ>@ z8SqzS9MQ!L1H8oe;2)uE#$6OHw?2%~%`|{wpF%bUgJTnqCz6k>hPt^psllFc7p6u^ zy_jL40@kepsM#HCicGFUXc&lN&pAxS$z=suvAiKqfM?F4A&*GsNz9I)JLEZ^XYIC9 zn3o|B+CI{eD&}au;xn}~L0OD?pHL9Y_=!lZbuWa7v0RiG%ZFougK=E$wMGqk5{##EC9(qcocRh()v@)1p<6Eu|~ z=fm0-9zwN!j*+MB56;Ex*zLfX;vcQkXA89yo5c-nnddd-d6<#jC=9i{uG5fhOuSYJ zm5BIugF^V3OTx?{T?8zB)>1wr1(ZmF^7=_$wdVC6xf*KC;pGf@l9c3@2Xi$^6@MQH zQhEQt!x?!Ml}Dh#HINe1OvmnQkt)?rn~)6&^ULd;Z$||Xoo_$n(fPKLZ&S_6N8AtZ z(i=Yfm-*!l&WG3C51)5xv&OEHvLdSS!XN@8Z`xawz!l6 zy;R*|T<^QAYFp-n@kO&du}@c3!6{lfvAI>TnN_g>uM)$yv~MdXNn%c4k8o`l^eN_T zdS5>%H{tK=k)PZ?;Waa_{jw_rgnLhRUZK68)me%KK(mqEtYGac^lxD+MQPE8J_LK9%zC&YGXEh}@4hbT-Ue%0uky-5* zsYZ+}+KK;$YlY?6pjEYi+I~B6l0A?(p2mJw3%!nV8h=~Zuq^XJxe_+pr`G@tgpyqr z)N2Z$U>o&ih0%aEP^=9WY&KxXvM_5$+(~HU8{@wdP9S6UCBCQJ?3Ww<%9n|M$WwwO z0gon`IaT9plL&@m4d-BMTe8lmPZ2SMwgd@B^B`Cujt;oEI2M8?i58U|ZaQ_m_b@Es z3-hY|Io0E#>R@{@d~<)>1!hCg_{(we)RJ#}8b>Xy)0b(_6ovM;4l7ZRNa>3_e|DfK z=Qsm3))waMm%{G|Ktl;x>wrS~-WA1uRvqGnb>c)LXm0W%G{Fi3AxPBIV&}}pRB@%E zdSh2F6-8g{69m2GXVy|g5(i@ixXsAt4rVSoTj))_r@i z06%SM z5QZGY?8KUhlUlLh7%m_PSqvh`cU`qe4%BDUa2eeAbS||wGJWDpIcV3;Ctpar`KVMQ z60<_2Z3X-biiw9h`&D%dr8;A5xIju%kop`avpk@l>5An=tX#lO-z8>1EV)|vLcJ1s zX%W6GNR+d}>|959pMZ|KZtaG0eAf5;3;;5f(whDpwf=Cqs1f3mpT5J34&l3e6hg`9 zEtI<1KfQ8(qQ*3oR+}55Csp0P7#=$+Bas$QDS9F7Mqj5gv{OvKlR+5z$_+rudN+?Z znddZQKCZty4r(~B!;b^X078#I9)Zgw=m^)0x?iLp=#2qEq*8Hg5ab|*jfyOgcn}iA zSzF!WsKbn*VKJG*CIP#rR>=#{9>XG7`Xy5;a|d;U`p2$a!eG~m;GTZBhq=PS zrF=SsQj-b4nkXlFK-JI~p)``|Hd)E^(y<^Xa-H=fG+Z*l+NDg`F;8;P@yzBwsDgM+ zviU02{9gQ*>6L84*kYPTWIE9U?}zqaeVsK=^Ai-QQUF1Y z)s5nGT*!47@m<{_a0%)a3rMR#?Wv&L_6LwSU;!lXNRa}kVu6EjiL?MBPj{oCctxXf zgVw(w(qO$Om8419zl0(TLZ~G0;~~Dj&R38Hp*N?(eSQhH#F58~zahtYr1&BvBo^%w z6-$Bf3fe7}AVw<+aJ5P(BV+9bgDYVJT<-!=?>EBpl_LuAK%r=c(zFW&;y*y4>6U)x zpwYTXx;F|t`)+bGiysK_Vv~nzxUvMHwrIBzrJ&`G$nQFqMSjsbZ;{`vqI2dq7|}PC z>YEnon<^X>SqUNq8Ey^jMU)Ynb#fI1F`lKZ-NIyreTiH3O+I~-U*8mz{Md6@sJ%q> zHC9WxNDDBzs&1%K_BwWXhyli)?!tX@BhKQhD?AY2fAPEPF$UnIIsp$M$%{xP1%I_8 zU|mTmAqBGnnN1ezEVdpKd$qB#hOaLad+MUH*K)?D|047Y1SjoN!OcT$leOpIPDk)> zHWZ%{dP$j6T_@P(MQ(2Go%M_mmD;oRyoPOJ08ZAaLUfx9J<8x`Co!GKE(IH6gI0FHIG9skV4R-bQRsM(K6P;J!BdCk zo;IFAwnJcX{89J(n3|~F=oo4JCmMgX^n7Kp*1jmZwyTt zjGY^LxAkAz@fSDJ`h!F+Q)#>*CL--y@y0Y5=n+wdEAt#Ru7w!GWKf*@8J)rD&vLJ) zW32si_-MP9^6gEb>cZA_5_YbjwK`N^*gnnqQn1};T&s5!==Fu6=UV-eVWcfrq=-MK zsfI>Z+HJk0nBj2`-uvR?iK&X(&quI9`>IFfaiod23Z)Vr2jFI*^#?D6m8M=qL@r`r zZpOK5a^LcGkv1J*aOS!)$FzfauBa1;rbY0DHc{FJ<=Xn`MyS4^eNpRVV=7c1 zdujXSC>-aV7kW-_@U`c){ykv+ck_mjwF+u=1f+7aXIQa16QLv4Y1LpU|u#N}_F z@`=M-`mOEQUa3hHsn~EW6R9>?Ith}z$0F<>^lkM>*=yFnDI72s&1a4=;=m|j%)#mr zvDu+%c0jgq1f1cG&vDVi2>Mr!8999rhuilsEZa2vOm=}f22!E>^+9WIWB>uA`r zR;XrAz_&ypd&g+G7+V2M=P>^eN%OBc^r#YJ&T-ZB#VO1r2H7f`D1f)q`om7^k`>rc zgHC5^u-g~1|77J1Zt)(IHAD687#4_uffyQ$)Rx&|X3!`qdFA5M$iiUT(OoMEU>#Tt z^|{iwk7QwWA0C|+wz?6FqPB}~IvE~Y8C!ocJUKg7ixU3)@{WqU)U-Z3hAc%(}9 z?QQu_%a6eO2qe?dy69xqt-%^s4nk$9C(8PB-V~jC$H>g^XuQ8XJktKtGTV_0D^rMx z9!q~!IG1B7CA%1rVC{NhGcAkZcKRxGk30&-Q?_-^skqi>Dks`3rB}_I-?(3-k<38v z1|cl7?0W<~h^k_~AX(*6o{f2~>W+QSgM38o9pqc%z|mZe&AHQ$+E8&wtwZ1rG3%VCoc9}Qy|nMFmkEsqKQmzG>Q9Q8V&K=gXk+ga<6T{? zt==aqN?)-omDC|+&s!{M$_geqK^Sn}Cl#n!8F)Ll)^S=+C!J|=UK^|`k(Rc>&KoZ{ z%WSCm6+s~G#J@jyrh%t07z(G^xBBf{efF(h%Z5NcLkK|}*_x8F{@%m4U2*Ce`c@IU z7!L}uVoJcSa^o7i-YVbph`+Wb3e~QZNPVgrSJc(A1DI@iP#C7)EJ_)a9s>#Jk9}Q+ zML_6pJS-2mMQbq4E*`CaA;!I-Jgn=z_mD88p1o?mgkpC?1hk{n@R_T&V8;TrUI8HX z>~$fSJdaQ?QubOMRzIrlykpCo}ud0dqtd87qNbIAyicFL%g6M2n_W;%9G-ZW>ZH0hP(#$R27&TvI@%% zm)|E%Ix1#V+AvzC+&z_h`b0HfY$Dk7?j_jLV=@;FRwoeUxZwx!=IN?#yH`pWszpPU zPV@`kGyG$j7?(BbeULm!?nu3ej22jt?kFxvO?#qq-Zx|zoRyn>)6vzM_w%OTCoMFW zNAlV>soBAs?tOAo7NC>wN(Uw;JB6mYKs(Xj7x+5SQ*CdlcApdgDM*b@pJlA&>b><_ z+LJOJn4Kz+wI5UK@d7-Xp*jk4W1ZRyBphD|my&sch(kS}*0~ii$C!vc6zM4nByNNX zu^KcsL-ilB@F8%-ST(-}T2$YNbr$hOeAXSxG7ub_r0f621uAGw;i^$0AV@!vRQ7Ql ztvbm4SwgX_n%}+#QN)d4=urKF`R)4}ncM^u$e<$jqdFtHh;Ru$~Uc6Owif$x4c9!8`O^whB zCs(0}5((vva&`nUMBG%^cAa}7i-q18`cpZUvG)wx_uR$!Sg_*c(8i*%Dg(lsDDAZlXWDPQC`~7r$1p zwfdw=&WbH}+s7Lv-<@1kuGB>m1M;p)>OdE_qSSa&I?AOGVi}t^Mvz=Ku7l)6m!h=0 z_euR2A??J=v7Hs~8vX(sS2~eP1#_O1uco>H9XFan)qbP7WHw9^HI{*OZs^&OH_K5lO7^{AqmJxs9s;O1HYW=VW(oU-S$lFQD!VM zq;8tiDUbE{;!7hrFH*vOOe#Q)>CDbwCTHi?jM;JO!&hgj1lC5@A~hNr=`65cx#Jb} zKAEdnLG&E6waAG*Berd7iR~2&P3~jt@S1nVD8M}ao1k@MMFfPsT2nLSiE)Pw(!sp9 zi~fQzq)F~hq#CXjs1V7QSNnX-B{QZ9c2#*e1!X+7_B~QuEl8ku8(47o|M7123ZzBM zUUB^C0!I3v?8l_@wz~IkKi03F1#dN2T^uE>RTFo`13y*^ZYaM1s;^-DV)H(!(}ktB z4Rl~P4T|Q@halot@I)6fV_%j$D=N$t8x$y&2q=}NgOY$El%N>+?-+uX= z#HIK2s1^b}7<}0%mWu;DUnS*fnT-U;l^UzlqvZNYZOK7S3=cYQ89H^wx6WR%#83sz z)$5FDxy7plo?CC(*~P{@*V-e(bd|oA~DCYXikIu>gJT^?KX;MjZNOJ7h7mw5Y%aDKW{V9Jr$ZVPS2j`V8j_j=_gniFxl@Ukz69 zb#>M@AO#4_=N^w-hA}&XjnG_`IOEPrDo}Dopf$Vu+GX)rOZ>9#PVdcMihVI&+}-K> z{Icqm@$=CvHPqGBwE+KJN(oDS+4@CFOqcGyKV!O@83f}-Y}>4(DpM9Q&eQvhm?y@1 zXZ7fApC#U8Wb)G4Z{#>b+0U~}jAh=c*i>Q?cHic^*XQwUpO%=!;|mzNwdauqK$Gc5 z4U3(X?@anQj1pYv(#&P0TW}a~qh}?vjLu?a>cS|X>P{bQ_sAnuJaGdH8g3l}5xJ*| zgqZ}8FS&8py z7NAQvkE!p4jS*bW@Q8C8{@OEE(v#4jaiOe=t{h^=uIIXPY^CE%+o-7E8uUe?5_(K( zaC~yxAja4vWQ2h`UXbOLuTrU#$6EasXk2_k*e_{gI|98A@YvAMqR!-AaYG)7x)n8G zBT}U1qO)Cw!T)$=Q(5d8U!zNO#qCvNvO`N~dyzWQ8oA?1Z8bHjKc5HRo7x&PI zbOt=EU_jZJ9(W0 zyVd27NtN64`!13pUdsw`#zS;T-4W{)mqP^4_X&@SwS4aqlJStwUmO*xi^{U?#ZJfF z%`(g@AD3aK>zUH_h4vi*R+JO60}5qu+>w@jVX7%aTfPI8qXYFyA=R!xHNITjQ8<9= zfL7FQ#L0&Xqp`VeaTi3bOQjO@SwqUJJe$%J|76-4=i4t0kEm@hu~x&ck)Wn!SXj#j z!3NuyQ5t!fHNxw_fCKPJK2omzmaE+xX{d=!O*Z`?R|^t(jn-ZK0(Mo%D7eVskD(_q=<$*`xRph?8)D5*i;q>WxZg}rv-b8&RB#1XPU{1(z76`_e)7B zl_|KmW||i+l>5FH3z9n1HCRy;)6Qdb+UaqZTFXCes;=Tn9q`P03W0^C(-to%cvQ$q zG^k_pTa0d1ehH|e?0quhMF=;vb}?34?pE2Zb$YX--u3Zm(7WH6gsSsgt$Cd%T9lYH zRJl&D5&nn-KpTl!AqQz?FO;g^I9geLsdM)1RDSf1O-1(*oG@n7I=k{Zx@)^e?vj1j~ZuZK7i9A9EX8rM1 zAvY6~;i0e%6g25#*(-i5JKU9G@->NL<r08`CBJ*?rmR0n zI`EW^I~*JHB*7q7Y%F#It`bjh3{Yd8aU5qSJlivR)b22rDw?u#Ykaf)Ds*71OdYOD z0i?p;)IPaLigZpzVwJwv#7VaK<|FScI=Wfsa`7=>38ZT71ZM`@zP0o2d7tb}E? z+qT*>gxO~b=c!^BsbYBu3&s*x$=?iAQLIo!VkVO>oqkM)(Xvn~Iw+#W*d$eB^-CYq`%W>&*Nb)H`4EYBJ9dqu|l&Q|j) zm+xYTtNrC`{TP&pTU+BfYYua!5Nx^(y?bOIxIPIZMYPXV_Kreu=Y1WJp$ftH|(xFoJ6&e*-6@Y$nBr2|-x= zLs^|nf>1PJ$?gcqHhd_X_92+DBONIu{=xyc79s&N{HoZcRk8D{l8#c4OvXsHE@fF0 zh3?D$yMUJ|?#l~UR;HUj6%&6_lxmL(S`nC=tdwj)V<_R(*)6V$ZPgECVCfuu4f_o? zgoQZBpu~ig2_1NEHo7)@#@B@oL+)(HuS`RA7oWj$e=1{ObDV))!1;t3|1>o04-nzy zRc;1$fDdCqg&OI_qp=beRhZox17T?5NMmzDTIs zCfqs8rGWM9P7w?%aup~Sj{|s=!eSF9KU40(+(dmDly#=W#5u^*vK(aAGOJLwp65zM z6C}FOxS16hVwNo6NiV8=We0{lBBR6@h2V-rRwS$hqCp%|PLrhOKLGN0ai0tL(Bc;1$a(?5!RJyl7w9X2~REf{8Z5 z7Cf$Xu4k>FUE0>*Uqs=Ywn#kRI>6DA$hFq<1Im=8z?_B%(%3=uJFyBgYm0iV?Ua_~ zU@f+>7H2I4qD5JVbWoLQ|Jo6~9&d-oWn-M%5Y}_n1`?eRagBTBjq|u3E3JUeEUpeBZ$DCVn>= zRa^AcTTrO3-fBd)>UAyp>RXIRi(a=)UmY_dx9D{{^wm3!s%^T_B|Eq(rW+!F@7^nd zeyx8;G}vSW_2~%qq2qDmqwV!wAOEewnl7A2aeBl=TBU`US-i&3I14@};+KtcHjX!U zFRuPf{6=H_lAI&@#$(-~NElM%Q_aST!sew@xEjOXY%X+n_(2_|k`$kWC!Jmnvc$~j z@+RY5SUdE1l%0^jzNS>Qd#&SKpvkZu5o*Lyvm0N3-!}FGj8lze1@o5qbHe-|l*P_G zZUTGYWWt4^su;#$>-O)!R9-l>OS{As!J7R$1^p8kGd?X8S=Z_p9caY*Di|`6%RqCm ze@__%iXYFQw znC3++b76D$X=d$QhZ&La*QtXoF-tyh&nW#vy)?okaQ7~qy;M#fSu*dC90$fY)TK-~_bF^^+#6-+K{2zR%bF>*Vj>#c_8El~EahbC^>+AHsd?8g`>tu;6_kOVT z32g7rk@0|rCeO*0CmHu9(cxM*JjCIbh7G@YkCXA@hOOUZ({P?TUcnW%&c#d*Us;1= zWrN?Ad}mN%GPwxH?6U9~x6YJB-8c9mwH0%am8I!Z*rWH$9_=X*t^=mkXZ;gdS)osg zh6JDiN_-Z~HU#?&={@?Ai4CU|9>77IAz5FhjtS^Vyssn?kg@2T9+ozW`L5^{s$k=O zV+mGcX76JDM$lsHJHEe3xS0p(A@mZd*oWj}o+VzK?8hWqopZH={)FidUjCd+Q}l0C z@ig2`My`I+PZ4`Sf{`fb=5tTyBcsrtEClq-Uik~}A!UuLMQmZef}229Q|wy($u&Hg zz4Dh#(u=guvmNzwsMpJ}5<}wwV2VAx3`S}Y>{G6W)k%CuX~CW$0+*H_y&*$TwawTq zO{!<(Y9#@ErTiO`74P~^kyjuFc^6}j%fAK5RWhAvP2VN%{)o@9hL9SvR6?d$_bN!l z|M*qOn~Cb2%RI1>MW?lV2F@;-QTF`C;p0rqgz!k9#yHM_{VsM%T62qMD6fx2OxMNX z+;LgUP?jXeCVyLCL&4?n9BvS&RfJ0KFf)-<>3L>+kV;R4PQq+dA}xyvljxvp;TduJ zBAB-lIlv3~SnAEPPra#iws;`FEs>B5Gt~mYr1UCiGTfq#)?@*a97TWq@oD?^T*;u$ zr*sSl%1O$;1fPQF{805G5H^)~D^ll(xxDFOE)01}9WWBDGC+F|iz=*H8cjN(09u|w zjV^RLdPU&z-cM0D@VJtRsi0_qJ?7;k4IB+l8Gsjn3mD25k|xCkT=3wTJem;7IDZcE zgK)ygvm+bm2U?jDXm54fRW4|><2FoSWFZE&BIQWGVF05G6`-g*{OEvdGta^3E!JlM zKp~8#LyRHtLLgOR?6+Q_rx_UBCGhfDk~EaYY1nK8iO|q?z7ziwlVNVqT^ZPOSl!`0 zkM5+?#V*`sTVLk>0&yU?4^VlFxS^}W2i^|c;3Xa42{Gch!P9qrjpw1fZA&xZ)8NwT@E#Yy8@6(r1+2FDsomFWW>7p8OIMHF)x62QM3vFFSdua9?(H(H`%m z$#)qqI`dsZIc(+tNt6nKh_$eNotH;Ap#e1hmJKboUE|5wrJ93;G4=Te95@}loZ!h zaU9D%^>wK%-+^g;*Zu0-gZwqr%2Tw{J{Xl%S+D0mUBThnC=SyJBa)_Q0VOIBkJXAV zmPQs1^E{{Fit-MBJY+ad9S<7=+G2uOYdeU{S!nF=g>Lg9!`{DK>tCn67i@hSRdXWS zct*c_q94GX_zidWtPSf@2?J4On*Bpg#b39A<4K);TU@h~Pw~qr(rEoK?I`{xf z%yKvha?Kx5Oum6HASsh?T%adml0ps@;|1$yV?_NnmC>=h4mz)5&@8WeoYzO(*N1s^ zEJ2Og543oSu^Eg|-*hSvn?Whj>y)tE*EZ*M)P4Pe^D6ph$-JJ|&G^7!N2hP%?vW>w zvQ98fLth_n<>5joqauz}Zs9Xwi1-Vcu%z&l`3u%Cc;(JejFiMI2xng5BLK)$;~IL4 zmQ3~bJeQ!cZuncFXVL}n!e};Y*KUY6HJY8-&DwR>#n(2Y3c4=7il4j<@rnj(dOsw3 z1KB=ZZ{p55KASI-*J9A5b^2GtrW&o5gr zotl<&f%EfrA#=a`Fl*+~zWYP@8Ol2dtfv#a4neU?G6_u~pFfy{xP!_vu!c2|c09k2 zHtURfk+yR-E9FuU-}O(>Liwfw$IFrfhSm-)SBmc*0jsAy72tgi8NvDXOOQ39UKgw{ zeTz?JVP^twt~10wrG~d#He5AUX?Rc&M+xL}KG_Sq zMRnAG{oRL9*CiR$^?43;9gvgy1xp-rp_o#rKZSN3)GzJYi_Rebl2~7E(x$DS10fqB zNVV@iLODQV_jMl#8uC>@Lq2~nXncWM(y9jX(m~@L7@V^~LmMfTBjUKY;NKSLd0wd* zW1*oy&j62&QRtf^HHx%=jtkuN3Hcs=Jv5|0C8N}zl4S|>+|6HyED;X{&?ESf*ZLKI zuSk+Bw2m=v>t_wD_J?9y-8^?oSx#+HX>+owS%ZGTY<2D*1%1il%N$`hNzSaEETYd35o zEj-$>bSe0}KR&&isw&V){Ht?WOZE0~sdl19@euTsBq{Q7xR)oVWxRqU^2;MDW0&Lp z+825;{;s@Sh`cAGyi>R@y8?5L)#mQqeAMrLRBFLF)UG~xK!%G(*&*wmx<@iRB@BxG zlp>-OZKNwQPj5i$Jh59+M3vet+E1i1?wWE`CoVL*RU1L0R$T?u&(k~=wk|moaHk_Q z932a^oHo89YbU2wKfj@`$ZQY*o4-jY7_W&|nbnU11&H9=s}vMmhpV)z)-M{k+pEU0 ziO|U4l^SpC3xPfHBjDTjL!%H0*agK_3zZ@?vG^Tuihsx+4W|P|;u>d3W&i59A>F)&Fv%!1-1CbqHp&$2Qfw1+c!BmBg@uS`^Dwg_Ie z3}8jr*_91&v9pk%B$jl)FBS&dXSPU^w`(d(tSj#;XaLiW;wVcgm1CF-%hr#^`F^jRtnv0vg;7F}mB7GUQoqUaz% zk%zVMwZe*~WX29RkS<+_wpg>MTj^Dd{TqivAmp?r+yP!ssx!cr-M#}&TffO{dm_fl zlAwM|NzmlqyrTls#QeEgf)|W~Gbkc=e{hGysFDz!(H@|i+(Bb*^mK%r+xwNH=+2Qmi4LkgAw@9MM z*1n)G_@=zHRC)O0;m^0X@@jn@ByHzPT}_GWq=k^mhK=e~iRs2UlE;`7dLfo?A5tH* zD4wrEXlMiB=%Be}&|61#Z131-NkNx>TC-Brd_(6QLpA&yIiM zo3~ID0&-GgL!IzfL|8)JpU>K}jil>q<-K~>`!;3p6pcgc{`fVGxDQcLZ%#@zIq^#* z(_u$InllF*EO87yEm^lu)lFfLkRn>#KH3)-PIv9;Qfn&@ONEf#KK0a*oPD_L?)7}l z@I?03m}^RW-NW+f3i;EU7uctlVpL}-CjHNQuc03=`zpZ>}k#%Ts(tOq>)CvK-(dmOKz@cuz!r3Au zJ$0n1%Kc_vydT0c*}q5Ao*zkj?n`^_PkVkM`Rw$G%jfjLKYYQ9)_J|n}mg0=7(wD;g zSc{0Cn1Ki8JtK7=8tZ;WRwZ<>zM&I=hWTig815OXiK5y4D-R|Y4S4*sdVd>>7%RLw zD!;OMgNibmX_=oZ})MG({ArE-2!nw!3 z@umrmqrr%k_;3;f>~7kN{mKjyYD#}g&(#WTHSC9eW$J4+DOOtxbs z*d_Fl-Ca(QgI4V6=4+?t&--KN$kXle#L>AIMC3Cj8P~%k)2lQ9&_)(^cle})tC$K-wgTD{pxU`Y;;veV1fOqt{d{HJ>t_(!zM{ShC?<-n zTlF->mKrj?jKo}g8yyyFkoY+e5R;6&XqK>yez9jvpQGa~4}l4wqiM=1bP_bJt_1ai z`Q;2tW|xVu_DEtz3g@{eGv!|L7zcX~H^nYsI=q~ajm{#z$0EKU_Nm{;_ro*p4wYK# z(`yAE1E@PMFlu}`(;AFhy*Zq_cqS_XeEDl}gIBWSMb4WeN7oAxdy(}reaS4MPI+8-pNs`vYZVK-VFlp^ zUiA^@6t_qIluI8*TGs#WLy%Kx*qGT6FNqn`ev7Qtpu43`=03`bWW}dx=ctFF+(&r7 zpY`dJha^a`?;6bQUMzpC@6j2QM&tnGgF`tF3D|tu{~i8QJ~oo`1VH0=?3|W|sfHUV zG@6*NEtNM<=cR6{fDKbDhpAWF;q$~N6D&!5-2gj^ghngTm5v>lcpwhbwF!tjuRK>O zjZbC9CYx)M#WO+AnN)vc)Z6rQ-|xl0=eyqn8KG7qVZWPz0|~Jw2|^udydZZG%q9&=$c~8z! zpjU}#k=ph9$+mwvV1R3=cfS|E)@jP?Hr3ltQ`u)V^<}pyK6=13{)qJj+0ltYW0dMF zs*_fc_QK7OE;$J zDIhj?mIN_9!E+S>7QBG4UrCDH3WyK@%5r8r8f-brELat=_Bi6krrY&2ERI36xyXkB z9T~p}!~v(UB*+Mo2@56R$V5VX=HlE3nDLlzaqj){d-0(9%}T85n zHQd9+rsJGV(zmC&h0vbPDfZC1;)HEyP>WCzzDUbHvB7|9`3lsWaG^VkgujvtjeY}J zJloL^gos_1n5}}o(vTdRyL-e76Q|BPmbk-5?CWu%Oym@9GVdRq zuuPn`oXf|cY3MtHt~Pte;m0EUhzwP(ym3Y9DBq2x&-2$!7blKb2JeG=ieKOs=^m5| z#$4+KP$9e@$x38j)vwHNt%^HD5}IV=^N7yyOe`GOgTwagZZC;lIPclmHQMmpSWp|z zkL6z<|70{bu`HUMSQ^bslyfX_R1e=2yW)EJc5c+0xESAydR}vRFz#*U_%B~q;x+OD zdpz-Tz=#&TY)a%rbJlBl*-6CRkhlQPVRHO*f}R3+)6%h*RZ$(S2nxnAfCWkYv@XxwSpA5$-9Te zcg9wBpc(I0_YD{WBK`O|8-hAb987&1plM#3=V=P`piv}f&u~J1!oITCnvKHd-q&Iy zQh9tbDt$Fe#u2fCNmMjzwl{Vof3Jwock61TE=ZYXZg}^In>)!8P*F#lLmS6ibDKkN zwyot4@i=0YZkf5MGVvcJs@%k>sZRC&m0M3N*~sq!F{hFEuM_8MVPAIQUAoI!Aj(-P z(?b%T#6fOOZE(bvctZ!-2KEe!;^I!KXReydvzRGijQ5h$o#2gpQxgoaA$od`R40CL zUmuq1RGe!s2yh)xhgaX%W-(Njcg~?Jm0x?%UV<8l#AF?V!A}DDX9IAT=7DMXbAEg3wS?{5)>q z3u8FrNNVfnR0!f@4*$1RO<<7^s;VB0blm)V!4Y*tB!VA+BOsKVLM|8@NYCt7>kJD) z{iebkt4{wX!B_UH^LELvwy)UquGp;a5{$KJ<0DzAi}iK&#w{iOt`5&9Y2oL_zsGaT zaF%hk@a8QZpJ1e`MI-?B6c%JeP}yof!E>^-eT9(fdG*9aN)XyO;%mI~i2X{_oqHj1 zCMIDS!G{YaR7i~?Uws@90F|(m>9nvMs8G1&oxbfeaZq55!E`BD`6CZa0xAHCtcHKH z_+L&=@$g$fvjcyR3;bPo)a&84219gA-?6<43%(aL)O!rXd;%!M#iG8^!dK+R@z{l? zDaqIqU(4w7!X5wnfGjo`?J z%g8(01Ro(munQK|!aa(O`+4enf%Q(QtcM&Icuq3k8qBZCVP#yU9UX5pm*jQZ9$vTm%#~BU`ll1Pr$wXXNn@NS)*wN| z*a6>F4m_G-)6Bc2J!2YfEwcEgB$mm!b#~ov-*9#`yG}Nnxz=y}IZMb?eDDq%5bg;s zNYt@<1Z(MWnei&Wz08}e7!OxmcG%)o>*l`r(_SJ$9K5GCtRl6{Raw&Smso9?V&$7} z#z6taNV@`9CA^TJSG zkYCFHH!!vjKtf8YU>Wovg*)o-6OxHLXeE&ER`FA{?9GU<4m(1(pM1UC^mVmwEO^bGqS&LN`N(ce`CdM;xP z_>rZEdN<9(N$GzuXmOT*OI0!m$p$_O8LQzOw5Pu*S{%FF`aQNLa9MiwXzu|8+_@AA z^!$Op?rAMj^5I|eZtc&^nWu7!O#=PUj-HAP3-2Y>m!wsHZ)PE+@>DO5ypq%w^EJGi zck7F3Iq`HFi@DWGjqQL#;^r2)7-%g^%SVSfw>&YZ8etTyE2$0gwAd<2Wl~4^f!HN( z4l$if%p%8o_;^Z=TbdJBTZwxeXDTPd{-7}$yC530UQ({0z}}*LX=8T_E=#|0GzFHt z!Mf&6FFLU_MD7Y26<3i2O10IoFz}*kvf02x`FFAQn}_mtIv|jQr*v^Ziqrkr!~s}e z4S408oIBuf^PV%A`KaG|9T!$Yi%(+IV)iVf2FksLuSIxmm@T)nV1L77Z*lRg`xbPt zuKQMy@m84eR+RBpobgtY@m8AgHb3KSLB`v{jJL%ZZ%Z=XDl*h zkWmw!&o5vDn`?uXxblYtn!-)MmO_XGWb8=(s!yb-e^=4c*Dj6UOyj~?INOqA&AJ)X zp$@jS!J)hj*CYDMU&CMPerY$8k@%nuD@`it#{Yi93aO(FNC?#k4$zb2jU&eUD>kEo zDGf0mQm3SW8D4AbM){A!vy7f%d`zeH&SJf@M9y2MtHZ~vWbuLGsHeu-Ek|ogj;avP zaqIKoJg6JxDjpM6Z!n3neB||2u_3s2DJA+&h;e*Txfk^0cQo1K@)sin6B5jc)y9?0weVK&Omjvjd zF`C-0YM0~J1#o;D=WBTxtK$3pMe4VvMhC;7~ z^7d15D8Dar9R)7Z3qt)V5p<~w!kafavTxv#`=Cm;B~oXF2ULg6-()`Q?yQFHjzFKx-INaBiKp-tTxH|sHAuw0lhA~axt`c{27xYvrnlRe8fo#VgJ7DGCj}bH5TRnX>$h{ta{_n# zB@-$u$9_te$vYx_OQD6nU&>(152nrv*c&-3%6|zJSLu!2AiJwdLsG)P6g$h=u|QHQ zy&m=B8)ffVzmra9o*`#0v$F-Ize?3GnH{8*K8dT(&%iP+ageW1PVL%{)D_*%6gZ~}sw3a?FzK3NJyMJZYdun8)RgLxQlnygDq%@RGb#HgvzBNax?N$`lrXa zk1d$gYfKZ3E&PVoKYr?sStsX?HrTa=n?Ul?LK46JX|?BkbnQj|x7w$CbnV6ex7ufX zbnU_aTkXD&uD#&Q+LfL-NtwhBUwGm)*$AaBzRw+Oe_sn zmvi!}2@+0}&i}K@PA*%ME#-+Y$hHyh?RZ2$cQc6!MF#f2t<+0MQ6la!4n0e%`#<*4 zm+T{&kFB~cscyl?Rb6$KRJZVB zt1errTl}$Ar(kf&$5x$BdROtWRTq@%R(@>NDY&cpi0UBOqlMO=9f1fzv)mM$?@EaN z^L6-8lvhLf4?U4$wD1sR zy*+k&uGl|JT+nlb+J6mmnUcD8zM4rYI9W0*Pi{IROP{Y9s%!6{Lv=xKxuli#S+RK- z=ep-8H$^+%_`wfEQTGG0{`K1C9~fB>-MCnRKo9gTq7*kB%=qiZm>66o zHjz=gd6XzA5*Ce|#cob@52J%{tKrdReU)!m)sRTGDJm{VDcmQ$ zzZoqr>ccDrBK3Z0%&T(8=gbjR9lVH}wn?l@mzJ$EiCgEy03v#) z7$&t;)d(ZvxBhxg4*EcG9PS&e^kZb7xPZ2M`#YyK?B+T57l zCgC5?XcP9_Va`*HFISD1{Er(?zXx3Yjw_d?yUp`Vfz{)`Xh_8QJ&NkWr#H0l`HFCER@sq@?}M_$>qxmV!4A^ zS&6)>`V-#WP}XkDX~x~OU=%bMr#!*i#KIsS(ID8sVF{^3xu(P^4W;2Ua&rko<~e=t*!8rV!+^b zv+wU^`V##qypEg|kcC%X!;i6RAtOX+Bw$^n!LVDNWZz;ImIZu-^ zpUX*Ui0-hy`6Bgz=o~``{F>~y64SY9mlD~B-sLE#l#mR{&yKMwC_YG^1&@mkebSG_ zVuBhv&LwhzYi~;&o?+4A8efsc!`J7$2+~)rza(Pqdqp6ZaLh(dG^-Aox$%`HYT?@Q zjVX~j#DK*TfrQi+6T(i}@yz|75O#fwq`Uv{glHAI z=ws^WaN$%${X+AwL0tRTpGgXbm1)L6OV;RrXF1x0%p(n>&zeQgVOJ@cB8XZWt)?tF zT^L|&rK#pOVyemJN5F$00Xu$VHTaRG;YZdf;x_ifoNSLeiceyzD)uAi5zl^XdVHE4 zf7mg?fYfU*;?u-Au+TZBgUXA2f-6Z@2d1lz=+mvX3bnT}xH%maO~`^FAh3m$OHXBo z8@W4Xm)HA=Y8L33Ddbu_=tdy7@kUx-;DjL1Br^G(Q+f{UD3CN?bKt21&APv`%pR8e zye9`L9M3e|+VsTg?dov^;I#x#)!w>p;O)FA&q}&pO<2)F%kaa zYUnqUyMH5rn(^zCW82(cD#4ObFTl0nn#$~o3b6+)vlH92jsj2Yg64se%Dg+LcfEZb z7M6y;lFl5Sa!9gEGw~ZLY$EZll;_TTi9bD_`0bsC61fN8w!`B+hj*Ofi#I;?&S(2i zdl?2fs>fETn$Nkjdi?NvRQIfmjZu^xUcuZ1dXF=m&UA2SVg1^HMqzUFFO!a|Qo1B& zRvYmvb)^sAp8Ybh9bt66bmw$bcOt}L1<7Q+YXR1K>z58AopYBpwTH=pn{cmL6I zr1eYu*sryJLFHM4^PxO4*K#AgS zCO&jM+f}){AepT)aZWVnGg{>>>YP6yteob!^jd~VlsqZ3U^ir?uAiIpGF!->4~wURE< z!=dmATJR)Rs3C{dXwk$mEgwDF530*;vJ&R2{zRbnThz_S1pK58e6POTSrtWBf5Xk-W2quuyJO`7sB)6$v(%#I|n_ z?3uiLdG~405!`K*b)UBJRMI{#^y>D{1om9j{l4dQlF#pc-{#Ihd!Tz@QYpLst;snr zEMK0;{mFmRiSE-~%=~sNQcI}CNA?T&g;3s}%hV(SR4HBL1?xc~ro7r}CT~6ni)~hA z-5IRtIUGMNwCTJ7`CFklW^1yT;O%n_{Ukm0&#||6vn}NiOl8Zga^8T-F^WUSZ?5s13+(A#~W6l z_i4c(;Wr;Jj^u27IO;LpTR(k_nC0yyt-0kJgY6gMNs92q)xqiEg3wstjyY7)?hE}q zaL4&PX*VCC@IDbvoL~m~YNPp$b(lmCh5PcU*Xhafu8tGKa^K_+59I8f{;JlWs~z?v zrjcsLFX`&|!^P+{!Y8@C5#~BGak3p(E_V2c)^VhQ8{qG?e%%P~pMrCRjr-!QOe~Q^ z!@lVV@WL1#JfDDY*gvKE`FNu?mfc!w?y!69Hry<{UZsbRbS+tI$3u6nS)A4N)`G6n z!T6`l>U*r~Z3z1Ck({B3ynPXcYuSNi^Ik+IH1Am)SyVqU@5NobF^f|y+618w5A4#EAzn^>xDjOZ~>OjaIrBnt6hMm?{ndjv_0rO1~8 zIY!WpB97wzB=Rf5WLy-#!pJwPeRy|?R4~BV5CicGjC?RhWDT6!U0(q-%P)!-IHHN2 zd}-Owpc?I9;wUR=hEGJIbM%cT2rh5!XQ|NSS&oE6QkvFq6EH|_i+rWA6OOPq+|RT-vF zJe(PA2-KqzfiRBV2{@Z_KmtJS=%^B0aTOx+Ni6Y}}dC+_AE}<}+*Rp;%MkJkGk{TLn zJvwhliS*Uz#Nl4bqNE;w7`wYi#0ZYi&*57lWv|H$>l<&;H*T}u0gsEDsK@wB9|;qG z6l@l&eflmEV79jD9Wgt83v-ao=n2&k=+*gB-VsB1o9)?tk=7C0(K_v9cny3Z&pTUl zd&3drozjuSXC*y8g_L+6SN%>Ty!>6~EZ7X0P4!4xMa!bN96o6&(eu(@tDQPr-{{(VO#VP61}@rv^(f0NlU)@XK&H*&5-=MxA8m+@Tjl=+T-g7496a-2xz zxI7~Vw%0O^?-v5QK)|`Qp;oof-I>_)yq_L-SbwB_}YFlR&Q$zl28z zET49Cwl+2?7SP6~B!_n|c|W+HY`h2b`Z2S9w;F78iya>$!ZYg#ceI6KNOpDDM>7_7-&qU0FLPlZ&Rp0hk_#(0&s>suLbRfBHU9r&@7=?rD$j-QNoJS~FfaoK zjWyM%u||U$4OP+sO&|%OLYxptOp(O4nx?63F_{T;3o$qeWwKn>c2{>#T6@rKYY*C8 zA+|*mumseCsCDDkw%F2krIXsI6ar>D@9%!rS~HVm#CmwI_xt0^b!E*u+|T)RpKnuL z2i@xfY+MQbNVS82lE1yu$VsNs$kpe)rP9deN8hp1$VsNsh-CVIL22aea({11BX5`c zds7;DyWHQK(#YH8{!W!fkZpn=a17|xdB)3FB}S^tGg2@PredTy4bJfW8}Ro)c>k*D5t#lN+xJ}+d%+8-$H z9!xF4(~og};6xXe7@_lwBhOM@RM-Zy7LYXZsXWJYv<+d{g=gqXa^zUuXB<8P`i95~ zrMUzPGjUSTxbcdG-H$*{q;MFOUf*J5fSy(;fyri2VXHcjm>gDTQtj zd;DqCo;FAW_(oxw#zDpxAgQ zL1h6sPGC%<;HUy%DTLGB9@)u`fA-b8FbI1OP`B0fcT z$Y3bhI><7xVn&>7)U#3V8(7GG3hI#R!a zl8UkL*=LGlHE{EjDd76oWVG>BEoeEY+NgQnH)=bRC%TC%oBUU?dw2|-yZA++KG4ox zkpP}^9-EW4t6K4*L_<1jyulBQkgwff(n;V&E7%^6OygXpTYYeJ3YHJRMJ}$Lz zh(98sikN-H&lJ7@@pjr1tsqw~MfS&QYVm*E;RTDK(if>p6L32oHd=F-fC&V3xb>9O z$B|YXQ}cDrVN&1>LtkZpBY6ZNtU)CQFV#}R5!F1YOi04p>wrWhTL=z9e!26qeGz8K4Z zGjq_P?IuzSrvsbb!slg3D@J_X_wph#y0l|zWb|s^EvLhTij21Sy632yR$sSQ-PCtX zkBna1?rt4xofE5yjNaT)yRe%q+Lxi^L2wUW_ZrD~qp>Oek4Jy}dtY}okFCzu>1Gx5 zwX=*bQwz4sQ-oR6a}l5G>u}KZTiiDeoz2tHKg3>nR7juzhMMwXp~i!+J4bE}x$S}3 zMUYg(Ea2}vu&WlbiK0^CtT3DdUl)Csi`AMW9_0$`;Oo?(@erVAvay>gFMf3*aG)?T zeLY(wn-7ahQOj8!#hjUO@|1aN_kz*cS?{V$$u zU73c&#=a}IvgJqPa?2;h7F5Z0rA^Rxoe*_-cCOz6h!ewB!Ld#UVF?{(>ZTkJt(mP% z9W}I}bNjz?&}zJ3<+7Z!vQGk#`4RpcNnb_l7k-*5wrxNO&~$eVLQ(;{!4 zEbjTT!*|I6P){R|qo&6E*R{nK2VkU5R^`x59BXF&+LI^(_0I|#-@w3q=##u}b+yhJ zx`2H#$&WX8ONRNFap|$~8>T(Ie`S_*!s-3GOz(Z)mg((9z>eG-7C<_aaa70SDEZYY)=cxDfe2169*{R3jiaIYUGn$o=mhqTy+oyJxM=OhHU9@r@ z$SHYJsS!CbQzuNgIx$TWdT8O<&4ZOWr%MhfY@9(cvHUbp*#b4+f)brNN-33T!N&U2 zhZymu#@MG(yleYHpTqg)&Ff(5c-lV8i*+q+Q*jQL$IJFLFhK^(#96(gYpFD#A*X98 zs)=Pq(S~`ErRAPoW%D!m^+y{#T}#Wm0Qdj^JbL-bXc-Ya`12g^v`vr?B>2|!g3pZ} z)`XXe<()d?Y|0Vj5cE^4&J*2}Y|#$VIqvctLE|+L(F9`OH~TY;-AQ-L*v*xLPZ(PV zYg*4Nez7jP<{&}Ca2zI-^juasPO-#|iJZko4G&``ukECgHHK`(!o3%E8+(YCiHc&u z?;bW$D>X`LpgHuLP`g=Upvhl0Z?~a&hFGbfp9bNNW;^aPqk1}lz*tiQz z6eF*v#XE2K072C$wgRT$p-BI+)+{+GWlc&=yvC0)?i9C4!-1Vl3wvlL4U0s;KAf@> zBqPLhEj4VN5{lHlw=(T1OO= zQLr3Au=j&t=S8b0A$QDfOHt*!%94FU9Zsk^{o)0pfZM@JPYyqZpYudoq*q^U{JW2Z zfqyecXgaR|H7;WMDfN^Inun(5)Oy>l3B>mQu?^H**rRGQcn9VnN6O0ch(enoa=rJ% z)}maQ9Jt=M?3BrY=l#mAB16>Xz3*Fg%H-gEvs0!FHL+_~C0?jL%8^6VlSmz^hGOy8GG%cU!~p3u5PF$F+o zFc-JA94|sWUv! z%(TkYOwE74W>&c}j(Okgl*$tZT18?@g-G;$u%)V0YyCt9JZX$OI`Kz3i3_MzUJZg{ z;bifSBIB$0Z*9O$YU9u<*n&3xHIbuu$%MRyS_saP#KaM@Wec}ZP#iEG<34Qc!DB8b zcFv>1D7-2Xk9W@1zxAjH!L;e<&1eIKyYzDZ!LQd4DrT?oHBK!OOus4;a{+rZ3N}rv zllIkp7Ym+O_*KF4BIluN z>=RVD)cU@1{P&N6OvV=RJ;it2M}(D_V>;|u zBg7;Df95FKcT~~0(7xdcOS1(2tTG1^#=Iw^VszG;G5JE=Kpd{dgTf%^i`V>OQH*G2 zjMf`>!jqb$_3{7Iv@cbBcmmVDYal&OXxewu1k*mDRc+J0cG;%;blIlg7t_8s?)v{t z`~E**+Q(=LXMLm?pU!70EP`03DyZqOaU1dnj?P=Zq4p3Z&Qffj9C_KpOA%OsI&jbr zBWW-k#D5D6Zup!fG23gLsWRBG#$KXh2`vi1mQdbaV-MR84l1#E*naQv8 z%1ri(?^%*ah_O7};cls*EhENbst)0GqKG23wECmde0?p_%jp%vrfaBLPzZ$-D4c*^ z5VYVLk@+47{2@e$IFrRUXycgL&e$0gLCc*~N_c|8fEpnHm@Spur7Uw2!eX;+`j_vQ zLCC~bV}lEGx}4HzrjQSH+9lBtQR!E>qALfyxT#L)gKd%o^npO1W`Y73y+kV?@J1K8 z;!*jYnm`OaYihJjhj?#uWrVSoqLH{EjYgt^SBh(JS%lNV7VNia9Z_qpT_wXZvz|be z)vnfQ5CBcF^UN4-#eWa{+Se`Gw2@Jd@3v{&1O~pc`=D#JQ|v zs10;5GMXPcFEV<*2uTV}gAx-c`Z?E74FEXtR3TP|$_1lTLE-eW%t1F#4q$xS{E_1q z`)(Eg)sf>Roh^~$%R(QE91n(;MUJn;ZsT}exC4y|6FPQny-t86C`MO}`7%D|FoF;W zw>;FiQ86Z#iz>r+%X3uql77qGFjbbh9?RpVL~c&0C+bcPd`y*|w4|h`G0FOoH0?hG zJNuw0it7vh{u~CjM8SSGXm(`$veHu0na-WjFbsJrBLC~!@H~onuD>4umaO9Q%v8_r zKbWC%Yyia9Q%bf&pMsk<=%!m8LznTK=u`DZq*;kKpu&zJk!H0{9omYV!fbqet5k8& zjRXLn%hG(a?$QI1{x^q8RQ{xrfs$?>+C$c6-SGu2_1<1ozpG(YYvyX_&fuV1%)-Rs zyqBpyN84=h$fT`v2{$-mvEfeif2O4p!U)W=tu1`ElFq}r4D zV8&1$U`c}6v=1W&QN(c6h2~YEd3e3Z=&sZrlI;jjirOPGw4$l9=~vvywuJgDs2j9O zFt;goin(!VQ8fc_dp}yo0$CxF2e)WBU+Eerg;tV#zbR9e2$O>qBQhn8E8kGqF#GnX zxFQHvg~~9(KdCBoZdGW8s#A3HK>$T~gWk+xAqUyZ*C0j~Wu!ejMVQQs4>6C0Fx)T{ zdtOeh?#-e~6Z?(m28i@|uW>6$P?vqw)G+-)vM(Zu$Wg|2V8G2ksx%7r8U^NKAhPiw z-f1Mj1o@m~zWfkE4wM3R3|0(FLzI1;NL8vromHW>s!&5!sIV&JPiex?EqtbH0#9_5 z>1FYrf$&F;K7DqDftWhBMpJ$X>$MVTpi)ez7;SZl!ZMnYV#?68vBa$T&q`K}!W5e# z?g{SWWF+WYcr^A4I?(si3M2LquL=pL{R4g}%qflr^@?Gsc>zL7G}i6YTaF4&isC)> zQ57E5+|A6`m-?MD4WiSC$#l1x2h$rkYVi1`KPvsY;`IlNE51pWhw}4kjSq8&U5W7Z zSKHpJ6hA0(d8W2EoMqg>b`+ap9AOHgAw4B>`PA^CRAqz6FhL|3ccors( zrxWol(L8-Uw@TmbHDCkDAWl==s$zay6T+#|g<2vTN2Z29VZCe_Rl`|pO|J8+!bo$* z&}6_V^{r}xgfG-Ks|oMNH>QOwt?wxyRmGonJBI>t{;sNh7W#+Bj( z2H7;GIBwiP^4JV2o^8)hTAvYqMdq$;XQW9Q5w57bW79aaJVE0}f~{OZo5TMwVCTd) z?UH)7BH|r29{;+cJ5%#uFmc%E-}FZ;tvDMqLsKKiGeUxkc(tHQFTWibDvZDcwvy4) z|A<5?3w;0|X0A3S82so`mvf+moZ4-(gakNFGLeXi;W_cgnFyE`Wpm!iF+l9QUFDa@ z9plwU#0SRM3Sy@QuOsUUWJ_4`Uykz-_)Yjh5ZM|o^Icm94myLzwO=+Duei)d(JMo? z7~=GDhrfFSY(?f$FYdReu7x^f)d|H(Aaj&UL~tp*rcu|r>j@!<jv3eo~ zl-UpG!T_KjlvEk_8AO@K+I`>^r);U}KJF*mHH1Ei9ZPOdSQ&`U_y}s#4l^Ho)kuGy zA^DOISR#Ej@CxoFBp=mp$h(R9GOmg>)}UmXnOtmUa=r|Tl}&}W)!W2|a#S+8I2f<9 zb8Awqy=2?~P~k|4QaK2nR{Tt_YubV{Iq`8#Yf_E$_3RPg_#~&Sc?Io zM=CN(^Ai{aHWbdLwycSVUArpc8GO&;)9rkc z3<;H`?(u%I2F?(4WI{!deU187xma6Msk&P^lSssXP!yNMY!oj^VGkjOuhU(n`;EW| z`^7q^1Zk=m`%Hgs(D==*oRvgJ!mq`sV&<2XoD`Z{QtRr-EhgL@q;auxL|@=(&1zWm z`n{3<+(50X&1i}j8($vk;L&zh={S4{I+!>mJ;)I{W{O*<%%Jfx z5;h1AFX~oauAooQEpRDud4dh6V#)}8SLq2HsP9;8R{y}dOsxKPs%%b@@<%bD9Bl|) zSmF+yR#NGrjG3BuQ`Gy#(m5DLcNZp=ep|Gtbr> z0i=5+uUjASAb@d7fWZed@J*Ev=i6%Vo0VQ&o+ENC0eP-Cg9+4*0KUL5nyLo!Z;IbM~+O72vWLu zMoDWJJuUUxv|u}gI_^vS53gX%IVf@BGOVrjvH@Kry7W6k1^8uMliler#JscKUl!QW z<_Rp%-f)DkxZl-g2hkSxV`^23A{bd@7@vm1K`w^AlhMd+vI^l_8vhGl8SL3K?654H?~Bp^%5-Be6WI`G-h=MBxyhO&2`5NZ;|+%nQ5M!q2IXU}fr+pJ}Ch z@=FWE#Q=zPirGcVDIuIcA*#UDT10|n45+E9m}Nl`4UU2WKbeZe&F9EJ++1=Q8*@jY zzK%dWc>9T8Djq?q57(%l<28~b*S9ST-x!fy*{GDa9WXJKB=SVJaY8X!&=X%SGXf=X z<2Rxq80Oe{OyR&&v__Zn4~!f9C$tio(k@>_X-kg>}fM zu-n2O?iTF9J`7CkVFmz-Pig#ykJg+(p|ZHYaCd0x((v@Yvu@hcu(R*l2v>XD^dIGi zAe}Z1dm*kBW{5|^EwD+u;Y4(=XmPlNB_Z~OT`O8{B4xB^3;Yjop1PW5&C_i9q^>dk z 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%0DrK7Ogu zYh+#<*A+5ML*~o~$i#&rlaxTnv^}Ro#`kDsWVZ2pi(Vu1_1LbE8G)!5u`-J=op5ZH zJ}G2ntQ?uz7j(#+(g`vh{Fb5D$eh+CG7IZfWbS$-JTgl&A|P|xOFCro@pdFOJGXC; zS#VZYE3-mlv)6wY9+^ij3PENu-!q$D)*(}aw<95Q1;6a*^~%(p*%dO526bh=e>fa6 zDeUd^5M-h_n=N=%hs?`(I}$QC@T-nqBlBuXSIF$$tRj;V0hxZG$b82|^S(_wWWM@s zWMm%WcND!w=09h2h0M8yDl!W(op1smEiGha@;IB#106Kcd~zqq{KPLLdX3DeE|Gcu zN)?$q9}17mbr*&pGoB;Ux>biv9^Q_`X6N(`GV{lDwKAV*$Ta>YJTkw#AOsl;M`rq) zI%M2~TQJjG)2RpZp2{8QqwJZP49kzIu4P%M`ozNWvTX=tzzE;Va9H8*f)BSy`GF68 zDohK!0AHqw6h`l*Ru4?`Yq6yR7ry>YS7)M?UmgieZY5mKUg zVgmG7Lnpfbm^;E2&o{FRq!@fG!zKVZ_ z@+ItUijEfj(|g`m@fD(G#%QrIbbS?ff*QuYimz%)zi31s(pT|djEEigRr8cSQCI^7 z%i-F+T2SlqR7ov6ynAF?M zm3X@cofdhc(TS&l3SqN42LQ89dccWQhB2bL%(>mwa%U$Db#^dAB|65jY3GX`jAx+m z6Xwy(o?}3x&36Ll5j}ivUY*CA0lP&8f1!=C9f=>}xF3e>ib(CFVe^uy6o{Ku?IfPu zdd?`20oI)z?r3Qz{hpX%jK0Yx-L}A%vM4hnFvAB?k{K>jF6E7bsLIMb&J5FlYSDRt zCYwdJ0_sA)ky=htlg(ND4Q~O*NEsaHr%C=L-Ac!$pHlg_ah1!B&Lvxvuz+PhO)fbu zP3lB)jKZutTj=mTSZS|Bk{lC*7dpAfQNfDGhZh@F@QxyF{CJ9ushF$~8!5*wHpID- zU+B$h&%?=b<`7;3t%TB$;2WguNUU~lK@lAn)o_xxyPK#3^7E~T@Q(JD4sjuljjS zcc;GX-hb+}cMkA7YNx5c&1X9D_9&ZrCvtdd&KlH1nmV3d^h6`-u0LI)p&H!zfeKO& zkRUw&6d?lW7E6|pIu9}n;Z%ZDQhbYY+aEz^SbK zbm+NJvEEyAymJ>n$Etn_RYD}mM2jSu?Ti{zhipSNeBH0V@1j-uA4z?A#jd8l%tr;d zMyM6YHv3j-M?_0*f+r_I$xZa+Br3T{o}45lH`$YutmLM6a#ECBRB;=pGZKj71M?8_W0$)8~2&|?Kyu}iF>6f%4uHWMq^%F#Wvt3Gr_;i#NImR zsNWMe9haEsic?a#4OeIVc!-Za^d@y%`s`#YM!x;1gbe%Q$pM?an8Y(B0p_3ro+fY+w}77lF%P;}hq9KZ;q+DW_;8u{ z+Z0?gz`0xEM768c=u^!GpG}6BHLA({D9(qbxH^6)*y17Mo3iaBlFDJTALiHl{DN>W zWQ7%q+8z{Cg`yUSbRbEB0P$!Dl^knEj!mCM$Ey@1o~(*sIwX*Jo4~Bve5OE8RB~(u zN->l&dyxk*1T;TSRtLLo93~BOEE}zZI>bE7J!Mu;wp~mY*K*`mpzhPrWOAMUt-t-Y zel%Geyc(G>B)b!VuNfm-CJ#Le|+UWN{7X3yq_*VHGE%-J8^Choize_0Q6g( zLDYn47!9Hq@r}3ziJ$|&^S!89;k!j=5LI#=_zEb21!E#vLpL4~sxY^PI`27Oc^HrC zdd`!&pwp4f=sEujqmJBZ&-s9F;jR?vTS!1}H}=-^o%}PZW1~<9&onX=$YhBo~h?{ zz*$2>?0_@R;C4VUN%&AZU>_IxrWg4i%aiyMY6skKd#D}oCy=pwb^xv9|9~B!^j7s> zpPN>fleRuLy)GwxeeOkdITx+Z&8W-CSf4woE@#sE+>7gSE?%EIxh`k&`rIjXIaAi> zX4a8;Ftsjc>iXPibve`4=T5K7nJx^5^|@JfIa$YUHjI>J!

!*5ROvDq+EnhgMY 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

*VU-dz01Ds-iJGl+Act-bqleH>Y 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 z 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 z zlcU8Rz#C*~8?32exFz+!BRe~zn6u`G7m=g4g%5Lh zKKkE13))B@w^?Ln;o&A6+~TA=fx-SmQ+kO(Y(dOLsgTO;G257(k1!uyE5n?IufkI$L%zX*Y6;jf3q?4zv4rX$`;WPP}Pgv&T{t^ z)0IF}C!#A;xZ|r4+OmF3Mx)T|3WiHK5*(4d*5C9PaX7o>Bc_Aw6Sa|^1mj@(Q&D7} zfWNvz$ND#$WWv!+_!87d&Cs2##4n_q+ctXCBlM^b*viRA>;}2M^n0nLEQ8-iy1f-bnpZngVY^tixYBnUjqH9fg?u^?jU=gjqwR+ep2BB zw$ZH~VS7P%vCPP_~e}$@ZcqIL$p1e~?=<(xs7MSLQA>0Bxq8slg-2#P6!Maz`_6J=+ zX5uU@#_C9)z)|GMX<+5waI?@RFlpfv=;P*~1ft83JZgQrBZ|I$aD8dM7VBg@U6FUk zWhRouU2#?L2YeS)xSv#huVw~1E!oirk{M0p?j7L_D0~4&p-Y{{tvZ|NPk;JX6bXlm z4CKR-^r$wsp4A|hnxbqF@zf72a1;)K)0lm}Vbg3KdjNZ-=fwjme+F(D$%XSpG3gXo zA2pwdu@9y5g^01`WX2<43KzNiOn6}ABGQVH8Ea7an-Xmuxy@{x^ubstoiH|WYuQh1 zr~OLg{lQG{<8HD<6o?A(R{^xFKE8o%PzrU&AATr(zyy5Z=hDfhk|(!;864sMvWvvz zU3Rh3;xb=2k-N$iGG+y~6Y3M4ZOED3nVW`^Hz$xe=Q2N~a(mu3V)Y2!!9_GUcSXb= z0Y_k#1%u$ev3zI`N`mOyY!=i&mC}^x?TE^yA8gquVt5)GZU1E)|IWQ(!f7KBZC907 zUNRp_FPU-DYfv~1{t8cY7o}S+a0;87P+FjtABui_A^CH!yj3p)_vxj@aZ~gh{iF+~ z@W9*)lk^8UGcAtG62rp%r43WrQ1azMv?S}8qEDl07fvsUOk%?KGR@zmJ6n_!aCTWu zz7&p^X>Kkh(SO)v%sSG`r5~*AEV=ivS2|t(GkSw$$2Q!O^{Jn;@x=@KMfRKG{wKGx zz5mL4XB<^$)_>)`bA@;Q0cVu~kNRBHpJLL5=l|B3o-dXVFR{l)LYtHrFSRbB2K-^Q z;0+q&%yLhVBd_4#BJ$*a|JEFFXO6frN8Fdw=3h7e^&^-5xBT~(gLTJC?0XiPCmUxT zK0WeZClEP-$O%MFAaVkc6NsEZ max items in stack then items will be send in required amount stacks. All stacks amount in mail limited to 12.'), ('server info',0,'Syntax: .server info\r\n\r\nDisplay server version and the number of connected players.'), ('server idleshutdown',3,'Syntax: .server idleshutdown #delay|cancel\r\n\r\nShut the server down after #delay seconds if no active connections are present (no players) or cancel the restart/shutdown if cancel value is used.'), ('server idlerestart',3,'Syntax: .server idlerestart #delay|cancel\r\n\r\nRestart the server after #delay seconds if no active connections are present (no players) or cancel the restart/shutdown if cancel value is used.'), @@ -2137,6 +2138,8 @@ INSERT INTO `mangos_string` VALUES (49,'You must be at least level %u to enter.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (50,'You must be at least level %u and have item %s to enter.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (51,'Hello! Ready for some training?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(52,'Invaid item count (%u) for item %u',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(53,'Mail can\'t have more %u item stacks',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (100,'Global notify: ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (101,'Map: %u (%s) Zone: %u (%s) Area: %u (%s)\nX: %f Y: %f Z: %f Orientation: %f\ngrid[%u,%u]cell[%u,%u] InstanceID: %u\n ZoneX: %f ZoneY: %f\nGroundZ: %f FloorZ: %f Have height data (Map: %u VMap: %u)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (102,'%s is already being teleported.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), @@ -2339,6 +2342,10 @@ INSERT INTO `mangos_string` VALUES (329,' %s (GUID %u)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (330,'No players found!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (331,'Extended item cost %u not exist',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(332,'GM mode is ON',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(333,'GM mode is OFF',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(334,'GM Chat Badge is ON',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(335,'GM Chat Badge is OFF',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (400,'|cffff0000[System Message]:|rScripts reloaded',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (401,'You change security level of %s to %i.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (402,'%s changed your security level to %i.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), @@ -2578,22 +2585,37 @@ INSERT INTO `mangos_string` VALUES (710,'Away from Keyboard',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (711,'Your group is too large for this battleground. Please regroup to join.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (712,'Your group is too large for this arena. Please regroup to join.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(713,'Your group has members not in your arena team. Please regroup to join.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(714,'Your group does not have enough players to join this match.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(715,'The Gold Team wins!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(716,'The Green Team wins!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(717, 'There aren\'t enough players in this battleground. It will end soon unless some more players join to balance the fight.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(718, 'Your group has an offline member. Please remove him before joining.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(719, 'Your group has players from the opposing faction. You can\'t join the battleground as a group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(720, 'Your group has players from different battleground brakets. You can\'t join as group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(721, 'Someone in your party is already in this battleground queue. (S)he must leave it before joining as group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(722, 'Someone in your party is Deserter. You can\'t join as group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(723, 'Someone in your party is already in three battleground queues. You cannot join as group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(724, 'You cannot teleport to a battleground or arena map.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(725, 'You cannot summon players to a battleground or arena map.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(726, 'You must be in GM mode to teleport to a player in a battleground.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(727, 'You cannot teleport to a battleground from another battleground. Please leave the current battleground first.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), -(728, 'Arena testing turned %s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +(713,'You must be level %u to join an arena team!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(714,'%s is not high enough level to join your team',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(715,'You don\'t meet Battleground level requirements',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(716,'Your arena team is full, %s cannot join it.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(730,'Your group has members not in your arena team. Please regroup to join.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(731,'Your group does not have enough players to join this match.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(732,'The Gold Team wins!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(733,'The Green Team wins!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(734, 'There aren\'t enough players in this battleground. It will end soon unless some more players join to balance the fight.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(735, 'Your group has an offline member. Please remove him before joining.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(736, 'Your group has players from the opposing faction. You can\'t join the battleground as a group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(737, 'Your group has players from different battleground brakets. You can\'t join as group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(738, 'Someone in your party is already in this battleground queue. (S)he must leave it before joining as group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(739, 'Someone in your party is Deserter. You can\'t join as group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(740, 'Someone in your party is already in three battleground queues. You cannot join as group.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(741, 'You cannot teleport to a battleground or arena map.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(742, 'You cannot summon players to a battleground or arena map.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(743, 'You must be in GM mode to teleport to a player in a battleground.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(744, 'You cannot teleport to a battleground from another battleground. Please leave the current battleground first.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(745, 'Arena testing turned %s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(800,'Invalid name',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(801,'You do not have enough gold',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(802,'You do not have enough free slots',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(803,'Your partner does not have enough free bag slots',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(804,'You do not have permission to perform that function',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(805,'Unknown language',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(806,'You don\'t know that language',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(807,'Please provide character name',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(808,'Player %s not found or offline',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(809,'Account for character %s not found',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); + /*!40000 ALTER TABLE `mangos_string` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/10_instantiated_battlegrounds.sql b/sql/updates/10_instantiated_battlegrounds.sql index e32bc3f2824..c3234c3099e 100644 --- a/sql/updates/10_instantiated_battlegrounds.sql +++ b/sql/updates/10_instantiated_battlegrounds.sql @@ -15,24 +15,25 @@ INSERT INTO `command` (`name`, `security`, `help`) VALUES ('debug arena',3,'Syntax: .debug arena\r\n\r\n Toggles arena 1v1 or normal mode.'); -DELETE FROM mangos_string WHERE entry BETWEEN 711 AND 728; +DELETE FROM mangos_string WHERE entry IN (711,712); +DELETE FROM mangos_string WHERE entry BETWEEN 730 AND 745; INSERT INTO mangos_string (entry, content_default) VALUES (711,'Your group is too large for this battleground. Please regroup to join.'), (712,'Your group is too large for this arena. Please regroup to join.'), - (713,'Your group has members not in your arena team. Please regroup to join.'), - (714,'Your group does not have enough players to join this match.'), - (715,'The Gold Team wins!'), - (716,'The Green Team wins!'), - (717, 'There aren\'t enough players in this battleground. It will end soon unless some more players join to balance the fight.'), - (718, 'Your group has an offline member. Please remove him before joining.'), - (719, 'Your group has players from the opposing faction. You can\'t join the battleground as a group.'), - (720, 'Your group has players from different battleground brakets. You can\'t join as group.'), - (721, 'Someone in your party is already in this battleground queue. (S)he must leave it before joining as group.'), - (722, 'Someone in your party is Deserter. You can\'t join as group.'), - (723, 'Someone in your party is already in three battleground queues. You cannot join as group.'), - (724, 'You cannot teleport to a battleground or arena map.'), - (725, 'You cannot summon players to a battleground or arena map.'), - (726, 'You must be in GM mode to teleport to a player in a battleground.'), - (727, 'You cannot teleport to a battleground from another battleground. Please leave the current battleground first.'), - (728, 'Arena testing turned %s'); + (730,'Your group has members not in your arena team. Please regroup to join.'), + (731,'Your group does not have enough players to join this match.'), + (732,'The Gold Team wins!'), + (733,'The Green Team wins!'), + (734, 'There aren\'t enough players in this battleground. It will end soon unless some more players join to balance the fight.'), + (735, 'Your group has an offline member. Please remove him before joining.'), + (736, 'Your group has players from the opposing faction. You can\'t join the battleground as a group.'), + (737, 'Your group has players from different battleground brakets. You can\'t join as group.'), + (738, 'Someone in your party is already in this battleground queue. (S)he must leave it before joining as group.'), + (739, 'Someone in your party is Deserter. You can\'t join as group.'), + (740, 'Someone in your party is already in three battleground queues. You cannot join as group.'), + (741, 'You cannot teleport to a battleground or arena map.'), + (742, 'You cannot summon players to a battleground or arena map.'), + (743, 'You must be in GM mode to teleport to a player in a battleground.'), + (744, 'You cannot teleport to a battleground from another battleground. Please leave the current battleground first.'), + (745, 'Arena testing turned %s'); diff --git a/sql/updates/19_pack.sql b/sql/updates/19_pack.sql deleted file mode 100644 index 3a43f43a955..00000000000 --- a/sql/updates/19_pack.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE `quest_template` ADD COLUMN `Method` tinyint(3) unsigned NOT NULL default '2' AFTER `entry`; -DELETE FROM mangos_string WHERE entry IN (331); -INSERT INTO mangos_string VALUES -(331,'Extended item cost %u not exist',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); diff --git a/src/bindings/scripts/ScriptMgr.cpp b/src/bindings/scripts/ScriptMgr.cpp index e382b62e2e3..11d293ffe3d 100644 --- a/src/bindings/scripts/ScriptMgr.cpp +++ b/src/bindings/scripts/ScriptMgr.cpp @@ -1,2105 +1,2135 @@ -/* Copyright (C) 2006 - 2008 ScriptDev2 - * This program is free software licensed under GPL version 2 - * Please see the included DOCS/LICENSE.TXT for more information */ - -#include "precompiled.h" -#include "Config/Config.h" -#include "ProgressBar.h" -#include "Database/DBCStores.h" -#include "Database/DatabaseMysql.h" -#include "ObjectMgr.h" -#include "scripts/creature/mob_event_ai.h" - -#define _FULLVERSION "TrinityScript" - -#ifndef _TSCRIPTCONFVERSION -# define _TSCRIPTCONFVERSION 2008100201 -#endif //_TSCRIPTCONFVERSION - -#ifndef _TRINITY_SCRIPT_CONFIG -# define _TRINITY_SCRIPT_CONFIG "trinityscript.conf" -#endif //_TRINITY_SCRIPT_CONFIG - -//*** Global data *** -int nrscripts; -Script *m_scripts[MAX_SCRIPTS]; - -// Text Map for Event AI -HM_NAMESPACE::hash_map EventAI_Text_Map; - -// Script Text used as says / yells / text emotes / whispers in scripts. -struct ScriptText -{ - uint32 SoundId; - uint8 Type; - uint32 Language; - std::string Text; -}; - -// Enums used by ScriptText::Type -enum ChatType -{ - CHAT_TYPE_SAY = 0, - CHAT_TYPE_YELL = 1, - CHAT_TYPE_TEXT_EMOTE = 2, - CHAT_TYPE_BOSS_EMOTE = 3, - CHAT_TYPE_WHISPER = 4, - CHAT_TYPE_BOSS_WHISPER = 5, -}; - -HM_NAMESPACE::hash_map Script_TextMap; - -// Localized Text structure for storing locales (for EAI and SD2 scripts). -struct Localized_Text -{ - std::string locale_1; - std::string locale_2; - std::string locale_3; - std::string locale_4; - std::string locale_5; - std::string locale_6; - std::string locale_7; - std::string locale_8; -}; -HM_NAMESPACE::hash_map EventAI_LocalizedTextMap; -HM_NAMESPACE::hash_map Script_LocalizedTextMap; - -//*** End Global data *** - -//*** EventAI data *** -//Event AI structure. Used exclusivly by mob_event_ai.cpp (60 bytes each) -std::list EventAI_Event_List; - -//Event AI summon structure. Used exclusivly by mob_event_ai.cpp. -HM_NAMESPACE::hash_map EventAI_Summon_Map; - -//Event AI error prevention structure. Used at runtime to prevent error log spam of same creature id. -//HM_NAMESPACE::hash_map EventAI_CreatureErrorPreventionList; - -uint32 EAI_ErrorLevel; - -//*** End EventAI data *** - -DatabaseMysql TScriptDB; -Config TScriptConfig; -uint32 Locale; - -void FillSpellSummary(); - -// -- Scripts to be added -- - -// -- Areatrigger -- -extern void AddSC_areatrigger_scripts(); - -// -- Boss -- -extern void AddSC_boss_emeriss(); -extern void AddSC_boss_taerar(); -extern void AddSC_boss_ysondre(); - -// -- Creature -- -extern void AddSC_mob_event(); -extern void AddSC_generic_creature(); - -// -- Custom -- -extern void AddSC_custom_example(); -extern void AddSC_custom_gossip_codebox(); -extern void AddSC_test(); - -// -- GO -- -extern void AddSC_go_scripts(); - -// -- Guard -- -extern void AddSC_guards(); - -// -- Honor -- - -// -- Item -- -extern void AddSC_item_scripts(); -extern void AddSC_item_test(); - -// -- NPC -- -extern void AddSC_npc_professions(); -extern void AddSC_npcs_special(); - -// -- Servers -- - -//-------------------- -//------ ZONE -------- - -//Alterac Mountains -extern void AddSC_alterac_mountains(); - -//Arathi Highlands -//Ashenvale Forest -//Aunchindoun -//--Auchenai Crypts -extern void AddSC_boss_exarch_maladaar(); -//--Mana Tombs -extern void AddSC_boss_nexusprince_shaffar(); -extern void AddSC_boss_pandemonius(); - -//--Sekketh Halls -extern void AddSC_boss_darkweaver_syth(); -extern void AddSC_boss_talon_king_ikiss(); -extern void AddSC_instance_sethekk_halls(); - -//--Shadow Labyrinth -extern void AddSC_boss_ambassador_hellmaw(); -extern void AddSC_boss_blackheart_the_inciter(); -extern void AddSC_boss_grandmaster_vorpil(); -extern void AddSC_boss_murmur(); -extern void AddSC_instance_shadow_labyrinth(); - -//Azshara -extern void AddSC_boss_azuregos(); -extern void AddSC_azshara(); - -//Azuremyst Isle -extern void AddSC_azuremyst_isle(); - -//Badlands -//Barrens -extern void AddSC_the_barrens(); - -//Black Temple -extern void AddSC_black_temple(); -extern void AddSC_boss_illidan(); -extern void AddSC_boss_shade_of_akama(); -extern void AddSC_boss_supremus(); -extern void AddSC_boss_gurtogg_bloodboil(); -extern void AddSC_boss_mother_shahraz(); -extern void AddSC_boss_reliquary_of_souls(); -extern void AddSC_boss_teron_gorefiend(); -extern void AddSC_boss_najentus(); -extern void AddSC_boss_illidari_council(); -extern void AddSC_instance_black_temple(); - -//Blackfathom Depths -//Blackrock Depths -extern void AddSC_blackrock_depths(); -extern void AddSC_boss_ambassador_flamelash(); -extern void AddSC_boss_angerrel(); -extern void AddSC_boss_anubshiah(); -extern void AddSC_boss_doomrel(); -extern void AddSC_boss_doperel(); -extern void AddSC_boss_draganthaurissan(); -extern void AddSC_boss_general_angerforge(); -extern void AddSC_boss_gloomrel(); -extern void AddSC_boss_gorosh_the_dervish(); -extern void AddSC_boss_grizzle(); -extern void AddSC_boss_haterel(); -extern void AddSC_boss_high_interrogator_gerstahn(); -extern void AddSC_boss_magmus(); -extern void AddSC_boss_moira_bronzebeard(); -extern void AddSC_boss_seethrel(); -extern void AddSC_boss_vilerel(); - -//Blackrock Spire -extern void AddSC_boss_drakkisath(); -extern void AddSC_boss_halycon(); -extern void AddSC_boss_highlordomokk(); -extern void AddSC_boss_mothersmolderweb(); -extern void AddSC_boss_overlordwyrmthalak(); -extern void AddSC_boss_shadowvosh(); -extern void AddSC_boss_thebeast(); -extern void AddSC_boss_warmastervoone(); -extern void AddSC_boss_quatermasterzigris(); -extern void AddSC_boss_pyroguard_emberseer(); -extern void AddSC_boss_gyth(); -extern void AddSC_boss_rend_blackhand(); - -//Blackwing lair -extern void AddSC_boss_razorgore(); -extern void AddSC_boss_vael(); -extern void AddSC_boss_broodlord(); -extern void AddSC_boss_firemaw(); -extern void AddSC_boss_ebonroc(); -extern void AddSC_boss_flamegor(); -extern void AddSC_boss_chromaggus(); -extern void AddSC_boss_nefarian(); -extern void AddSC_boss_victor_nefarius(); - -//Blade's Edge Mountains -extern void AddSC_blades_edge_mountains(); - -//Blasted lands -extern void AddSC_boss_kruul(); -extern void AddSC_blasted_lands(); - -//Bloodmyst Isle -extern void AddSC_bloodmyst_isle(); - -//Burning steppes -extern void AddSC_burning_steppes(); - -//Caverns of Time -//--Battle for Mt. Hyjal -extern void AddSC_hyjal(); -extern void AddSC_boss_archimonde(); -extern void AddSC_instance_mount_hyjal(); - -//--Old Hillsbrad -extern void AddSC_boss_captain_skarloc(); -extern void AddSC_boss_epoch_hunter(); -extern void AddSC_boss_lieutenant_drake(); -extern void AddSC_instance_old_hillsbrad(); -extern void AddSC_old_hillsbrad(); - -//--The Dark Portal -extern void AddSC_boss_aeonus(); -extern void AddSC_boss_chrono_lord_deja(); -extern void AddSC_boss_temporus(); - -//Coilfang Resevoir -//--Serpent Shrine Cavern -extern void AddSC_boss_fathomlord_karathress(); -extern void AddSC_boss_hydross_the_unstable(); -extern void AddSC_boss_lady_vashj(); -extern void AddSC_boss_leotheras_the_blind(); -extern void AddSC_boss_morogrim_tidewalker(); -extern void AddSC_instance_serpentshrine_cavern(); - -//--Slave Pens - -//--Steam Vault -extern void AddSC_boss_hydromancer_thespia(); -extern void AddSC_boss_mekgineer_steamrigger(); -extern void AddSC_boss_warlord_kalithresh(); -extern void AddSC_instance_steam_vault(); - -//--Underbog -extern void AddSC_boss_hungarfen(); - -//Darkshore -//Darnassus -//Deadmines -//Deadwind pass -//Desolace -//Dire Maul -//Dun Morogh -extern void AddSC_dun_morogh(); - -//Durotar -//Duskwood -//Dustwallow marsh -extern void AddSC_dustwallow_marsh(); - -//Eversong Woods -extern void AddSC_eversong_woods(); - -//Exodar -//Eastern Plaguelands -extern void AddSC_eastern_plaguelands(); - -//Elwynn Forest -extern void AddSC_elwynn_forest(); - -//Felwood -extern void AddSC_felwood(); - -//Feralas -extern void AddSC_feralas(); - -//Ghostlands -extern void AddSC_ghostlands(); - -//Gnomeregan -//Gruul's Lair -extern void AddSC_boss_gruul(); -extern void AddSC_boss_high_king_maulgar(); -extern void AddSC_instance_gruuls_lair(); - -//Hellfire Citadel -//--Blood Furnace -extern void AddSC_boss_broggok(); -extern void AddSC_boss_kelidan_the_breaker(); -extern void AddSC_boss_the_maker(); - -//--Magtheridon's Lair -extern void AddSC_boss_magtheridon(); -extern void AddSC_instance_magtheridons_lair(); - -//--Shattered Halls -extern void AddSC_boss_grand_warlock_nethekurse(); -extern void AddSC_boss_warbringer_omrogg(); -extern void AddSC_instance_shattered_halls(); - -//--Ramparts -extern void AddSC_boss_watchkeeper_gargolmar(); -extern void AddSC_boss_omor_the_unscarred(); - -//Hellfire Peninsula -extern void AddSC_boss_doomlordkazzak(); -extern void AddSC_hellfire_peninsula(); - -//Hillsbrad Foothills -//Hinterlands -//Ironforge -extern void AddSC_ironforge(); - -//Isle of Quel'Danas -extern void AddSC_isle_of_queldanas(); - -//Karazhan -extern void AddSC_boss_attumen(); -extern void AddSC_boss_curator(); -extern void AddSC_boss_maiden_of_virtue(); -extern void AddSC_boss_shade_of_aran(); -extern void AddSC_boss_malchezaar(); -extern void AddSC_boss_terestian_illhoof(); -extern void AddSC_netherspite_infernal(); -extern void AddSC_boss_moroes(); -extern void AddSC_bosses_opera(); -extern void AddSC_instance_karazhan(); -extern void AddSC_karazhan(); - -//Loch Modan -extern void AddSC_loch_modan(); - -//Lower Blackrock Spire - -// Magister's Terrace -extern void AddSC_boss_felblood_kaelthas(); -extern void AddSC_boss_selin_fireheart(); -extern void AddSC_boss_vexallus(); -extern void AddSC_boss_priestess_delrissa(); -extern void AddSC_instance_magisters_terrace(); - -//Maraudon -extern void AddSC_boss_celebras_the_cursed(); -extern void AddSC_boss_landslide(); -extern void AddSC_boss_noxxion(); -extern void AddSC_boss_ptheradras(); - -//Molten core -extern void AddSC_boss_lucifron(); -extern void AddSC_boss_magmadar(); -extern void AddSC_boss_gehennas(); -extern void AddSC_boss_garr(); -extern void AddSC_boss_baron_geddon(); -extern void AddSC_boss_shazzrah(); -extern void AddSC_boss_golemagg(); -extern void AddSC_boss_sulfuron(); -extern void AddSC_boss_majordomo(); -extern void AddSC_boss_ragnaros(); -extern void AddSC_instance_molten_core(); -extern void AddSC_molten_core(); - -//Moonglade -extern void AddSC_moonglade(); - -//Mulgore -extern void AddSC_mulgore(); - -//Nagrand -extern void AddSC_nagrand(); - -//Naxxramas -extern void AddSC_boss_anubrekhan(); -extern void AddSC_boss_maexxna(); -extern void AddSC_boss_patchwerk(); -extern void AddSC_boss_razuvious(); -extern void AddSC_boss_highlord_mograine(); -extern void AddSC_boss_lady_blaumeux(); -extern void AddSC_boss_sir_zeliek(); -extern void AddSC_boss_thane_korthazz(); -extern void AddSC_boss_kelthuzad(); -extern void AddSC_boss_faerlina(); -extern void AddSC_boss_loatheb(); -extern void AddSC_boss_noth(); -extern void AddSC_boss_gluth(); -extern void AddSC_boss_sapphiron(); - -//Netherstorm -extern void AddSC_netherstorm(); - -//Onyxia's Lair -extern void AddSC_boss_onyxia(); - -//Orgrimmar -extern void AddSC_orgrimmar(); - -//Ragefire Chasm -//Razorfen Downs -extern void AddSC_boss_amnennar_the_coldbringer(); - -//Redridge Mountains -//Ruins of Ahn'Qiraj -//Scarlet Monastery -extern void AddSC_boss_arcanist_doan(); -extern void AddSC_boss_azshir_the_sleepless(); -extern void AddSC_boss_bloodmage_thalnos(); -extern void AddSC_boss_herod(); -extern void AddSC_boss_high_inquisitor_fairbanks(); -extern void AddSC_boss_high_inquisitor_whitemane(); -extern void AddSC_boss_houndmaster_loksey(); -extern void AddSC_boss_interrogator_vishas(); -extern void AddSC_boss_scarlet_commander_mograine(); -extern void AddSC_boss_scorn(); - -//Scholomance -extern void AddSC_boss_darkmaster_gandling(); -extern void AddSC_boss_death_knight_darkreaver(); -extern void AddSC_boss_theolenkrastinov(); -extern void AddSC_boss_illuciabarov(); -extern void AddSC_boss_instructormalicia(); -extern void AddSC_boss_jandicebarov(); -extern void AddSC_boss_kormok(); -extern void AddSC_boss_lordalexeibarov(); -extern void AddSC_boss_lorekeeperpolkelt(); -extern void AddSC_boss_rasfrost(); -extern void AddSC_boss_theravenian(); -extern void AddSC_boss_vectus(); -extern void AddSC_instance_scholomance(); - -//Searing gorge -extern void AddSC_searing_gorge(); - -//Shadowfang keep -extern void AddSC_shadowfang_keep(); -extern void AddSC_instance_shadowfang_keep(); - -//Shadowmoon Valley -extern void AddSC_boss_doomwalker(); -extern void AddSC_shadowmoon_valley(); - -//Shattrath -extern void AddSC_shattrath_city(); - -//Silithus -extern void AddSC_silithus(); - -//Silvermoon -extern void AddSC_silvermoon_city(); - -//Silverpine forest -extern void AddSC_silverpine_forest(); - -//Stockade -//Stonetalon mountains -extern void AddSC_stonetalon_mountains(); - -//Stormwind City -extern void AddSC_stormwind_city(); - -//Stranglethorn Vale -extern void AddSC_stranglethorn_vale(); - -//Stratholme -extern void AddSC_boss_magistrate_barthilas(); -extern void AddSC_boss_maleki_the_pallid(); -extern void AddSC_boss_nerubenkan(); -extern void AddSC_boss_cannon_master_willey(); -extern void AddSC_boss_baroness_anastari(); -extern void AddSC_boss_ramstein_the_gorger(); -extern void AddSC_boss_timmy_the_cruel(); -extern void AddSC_boss_postmaster_malown(); -extern void AddSC_boss_baron_rivendare(); -extern void AddSC_boss_dathrohan_balnazzar(); -extern void AddSC_boss_order_of_silver_hand(); -extern void AddSC_instance_stratholme(); -extern void AddSC_stratholme(); - -//Sunken Temple -//Tanaris -extern void AddSC_tanaris(); - -//Teldrassil -//Tempest Keep -//--Arcatraz -extern void AddSC_arcatraz(); -extern void AddSC_boss_harbinger_skyriss(); -extern void AddSC_instance_arcatraz(); - -//--Botanica -extern void AddSC_boss_high_botanist_freywinn(); -extern void AddSC_boss_laj(); -extern void AddSC_boss_warp_splinter(); - -//--The Eye -extern void AddSC_boss_kaelthas(); -extern void AddSC_boss_void_reaver(); -extern void AddSC_boss_high_astromancer_solarian(); -extern void AddSC_instance_the_eye(); -extern void AddSC_the_eye(); - -//--The Mechanar -extern void AddSC_boss_gatewatcher_iron_hand(); -extern void AddSC_boss_nethermancer_sepethrea(); - -//Temple of ahn'qiraj -extern void AddSC_boss_cthun(); -extern void AddSC_boss_fankriss(); -extern void AddSC_boss_huhuran(); -extern void AddSC_bug_trio(); -extern void AddSC_boss_sartura(); -extern void AddSC_boss_skeram(); -extern void AddSC_boss_twinemperors(); -extern void AddSC_mob_anubisath_sentinel(); -extern void AddSC_instance_temple_of_ahnqiraj(); - -//Terokkar Forest -extern void AddSC_terokkar_forest(); - -//Thousand Needles -//Thunder Bluff -extern void AddSC_thunder_bluff(); - -//Tirisfal Glades -extern void AddSC_tirisfal_glades(); - -//Uldaman -extern void AddSC_boss_ironaya(); -extern void AddSC_uldaman(); - -//Undercity -extern void AddSC_undercity(); - -//Un'Goro Crater -//Upper blackrock spire -//Wailing caverns - -//Western plaguelands -extern void AddSC_western_plaguelands(); - -//Westfall -//Wetlands -//Winterspring -extern void AddSC_winterspring(); - -//Zangarmarsh -extern void AddSC_zangarmarsh(); - -//Zul'Farrak -//Zul'Gurub -extern void AddSC_boss_jeklik(); -extern void AddSC_boss_venoxis(); -extern void AddSC_boss_marli(); -extern void AddSC_boss_mandokir(); -extern void AddSC_boss_gahzranka(); -extern void AddSC_boss_thekal(); -extern void AddSC_boss_arlokk(); -extern void AddSC_boss_jindo(); -extern void AddSC_boss_hakkar(); -extern void AddSC_boss_grilek(); -extern void AddSC_boss_hazzarah(); -extern void AddSC_boss_renataki(); -extern void AddSC_boss_wushoolay(); -extern void AddSC_instance_zulgurub(); -//Zul'Aman -extern void AddSC_boss_janalai(); -extern void AddSC_boss_nalorakk(); -extern void AddSC_instance_zulaman(); -extern void AddSC_zulaman(); - -// ------------------- -void LoadDatabase() -{ - //Get db string from file - char const* dbstring = NULL; - if(!TScriptConfig.GetString("TScriptDatabaseInfo", &dbstring)) - error_log("TSCR: Missing Trinity Script Database Info in configuration file"); - - //Initilize connection to DB - if(!dbstring || !TScriptDB.Initialize(dbstring)) - error_db_log("TSCR: Unable to connect to Database"); - else - { - //***Preform all DB queries here*** - QueryResult *result; - - //Get Version information - result = TScriptDB.PQuery("SELECT `version`" - "FROM `script_db_version`"); - - if (result) - { - Field *fields = result->Fetch(); - outstring_log(" "); - outstring_log("TSCR: Database version is: %s", fields[0].GetString()); - outstring_log(" "); - delete result; - - }else error_db_log("TSCR: Missing script_db_version information."); - - // Drop existing Event AI Localized Text hash map - EventAI_LocalizedTextMap.clear(); - - // Gather EventAI Localized Texts - result = TScriptDB.PQuery("SELECT `id`,`locale_1`,`locale_2`,`locale_3`,`locale_4`,`locale_5`,`locale_6`,`locale_7`,`locale_8`" - "FROM `eventai_localized_texts`"); - - if(result) - { - outstring_log("TSCR: Loading EAI Localized Texts...."); - barGoLink bar(result->GetRowCount()); - uint32 count = 0; - - do - { - Localized_Text temp; - bar.step(); - - Field *fields = result->Fetch(); - - uint32 i = fields[0].GetInt32(); - - temp.locale_1 = fields[1].GetString(); - temp.locale_2 = fields[2].GetString(); - temp.locale_3 = fields[3].GetString(); - temp.locale_4 = fields[4].GetString(); - temp.locale_5 = fields[5].GetString(); - temp.locale_6 = fields[6].GetString(); - temp.locale_7 = fields[7].GetString(); - temp.locale_8 = fields[8].GetString(); - - EventAI_LocalizedTextMap[i] = temp; - ++count; - - }while(result->NextRow()); - - delete result; - - outstring_log(""); - outstring_log("TSCR: Loaded %u EventAI Localized Texts", count); - }else outstring_log("TSCR: WARNING >> Loaded 0 EventAI Localized Texts. Database table `eventai_localized_texts` is empty"); - - // Drop Existing Script Localized Text Hash Map - Script_LocalizedTextMap.clear(); - - // Gather Script Localized Texts - result = TScriptDB.PQuery("SELECT `id`,`locale_1`,`locale_2`,`locale_3`,`locale_4`,`locale_5`,`locale_6`,`locale_7`,`locale_8`" - "FROM `script_localized_texts`"); - - if(result) - { - outstring_log("TSCR: Loading Script Localized Texts...."); - barGoLink bar(result->GetRowCount()); - uint32 count = 0; - - do - { - Localized_Text temp; - bar.step(); - - Field *fields = result->Fetch(); - - uint32 i = fields[0].GetInt32(); - - temp.locale_1 = fields[1].GetString(); - temp.locale_2 = fields[2].GetString(); - temp.locale_3 = fields[3].GetString(); - temp.locale_4 = fields[4].GetString(); - temp.locale_5 = fields[5].GetString(); - temp.locale_6 = fields[6].GetString(); - temp.locale_7 = fields[7].GetString(); - temp.locale_8 = fields[8].GetString(); - - Script_LocalizedTextMap[i] = temp; - ++count; - - }while(result->NextRow()); - - delete result; - - outstring_log(""); - outstring_log("TSCR: Loaded %u Script Localized Texts", count); - }else outstring_log("TSCR: WARNING >> Loaded 0 Script Localized Texts. Database table `script_localized_texts` is empty"); - - //Drop existing EventAI Text hash map - EventAI_Text_Map.clear(); - - //Gather EventAI Text Entries - result = TScriptDB.PQuery("SELECT `id`,`text` FROM `eventai_texts`"); - - if (result) - { - outstring_log( "TSCR: Loading EventAI_Texts..."); - barGoLink bar(result->GetRowCount()); - uint32 Count = 0; - - do - { - bar.step(); - Field *fields = result->Fetch(); - - uint32 i = fields[0].GetInt32(); - - std::string text = fields[1].GetString(); - - if (!strlen(text.c_str())) - error_db_log("TSCR: EventAI text %u is empty", i); - - EventAI_Text_Map[i] = text; - ++Count; - - }while (result->NextRow()); - - delete result; - - outstring_log(""); - outstring_log("TSCR: >> Loaded %u EventAI_Texts", Count); - - }else outstring_log("TSCR: WARNING >> Loaded 0 EventAI_Texts. DB table `EventAI_Texts` is empty."); - - //Gather event data - result = TScriptDB.PQuery("SELECT `id`,`position_x`,`position_y`,`position_z`,`orientation`,`spawntimesecs`" - "FROM `eventai_summons`"); - - //Drop Existing EventSummon Map - EventAI_Summon_Map.clear(); - - if (result) - { - outstring_log( "TSCR: Loading EventAI_Summons..."); - barGoLink bar(result->GetRowCount()); - uint32 Count = 0; - - do - { - bar.step(); - Field *fields = result->Fetch(); - - EventAI_Summon temp; - - uint32 i = fields[0].GetUInt32(); - temp.position_x = fields[1].GetFloat(); - temp.position_y = fields[2].GetFloat(); - temp.position_z = fields[3].GetFloat(); - temp.orientation = fields[4].GetFloat(); - temp.SpawnTimeSecs = fields[5].GetUInt32(); - - //Add to map - EventAI_Summon_Map[i] = temp; - ++Count; - - }while (result->NextRow()); - - delete result; - outstring_log(""); - outstring_log("TSCR: >> Loaded %u EventAI_Summons", Count); - - }else outstring_log("TSCR: WARNING >> Loaded 0 EventAI_Summons. DB table `EventAI_Summons` is empty."); - - //Gather event data - result = TScriptDB.PQuery("SELECT `id`,`creature_id`,`event_type`,`event_inverse_phase_mask`,`event_chance`,`event_flags`,`event_param1`,`event_param2`,`event_param3`,`event_param4`,`action1_type`,`action1_param1`,`action1_param2`,`action1_param3`,`action2_type`,`action2_param1`,`action2_param2`,`action2_param3`,`action3_type`,`action3_param1`,`action3_param2`,`action3_param3`" - "FROM `eventai_scripts`"); - - //Drop Existing EventAI List - EventAI_Event_List.clear(); - - if (result) - { - outstring_log( "TSCR: Loading EventAI_Scripts..."); - barGoLink bar(result->GetRowCount()); - uint32 Count = 0; - - do - { - bar.step(); - Field *fields = result->Fetch(); - - EventAI_Event temp; - - temp.event_id = fields[0].GetUInt32(); - uint32 i = temp.event_id; - temp.creature_id = fields[1].GetUInt32(); - temp.event_type = fields[2].GetUInt16(); - temp.event_inverse_phase_mask = fields[3].GetUInt32(); - temp.event_chance = fields[4].GetUInt8(); - temp.event_flags = fields[5].GetUInt8(); - temp.event_param1 = fields[6].GetUInt32(); - temp.event_param2 = fields[7].GetUInt32(); - temp.event_param3 = fields[8].GetUInt32(); - temp.event_param4 = fields[9].GetUInt32(); - - //Report any errors in event - if (temp.event_type >= EVENT_T_END) - error_db_log("TSCR: Event %u has incorrect event type. Maybe DB requires updated version of SD2.", i); - - //No chance of this event occuring - if (temp.event_chance == 0) - error_db_log("TSCR: Event %u has 0 percent chance. Event will never trigger!", i); - //Chance above 100, force it to be 100 - if (temp.event_chance > 100) - { - error_db_log("TSCR: Creature %u are using event %u with more than 100 percent chance. Adjusting to 100 percent.", temp.creature_id, i); - temp.event_chance = 100; - } - - //Individual event checks - switch (temp.event_type) - { - case EVENT_T_HP: - case EVENT_T_MANA: - case EVENT_T_TARGET_HP: - { - if (temp.event_param2 > 100) - error_db_log("TSCR: Creature %u are using percentage event(%u) with param2 (MinPercent) > 100. Event will never trigger! ", temp.creature_id, i); - - if (temp.event_param1 <= temp.event_param2) - error_db_log("TSCR: Creature %u are using percentage event(%u) with param1 <= param2 (MaxPercent <= MinPercent). Event will never trigger! ", temp.creature_id, i); - - if (temp.event_flags & EFLAG_REPEATABLE && !temp.event_param3 && !temp.event_param4) - { - error_db_log("TSCR: Creature %u has param3 and param4=0 (RepeatMin/RepeatMax) but cannot be repeatable without timers. Removing EFLAG_REPEATABLE for event %u.", temp.creature_id, i); - temp.event_flags &= ~EFLAG_REPEATABLE; - } - } - break; - - case EVENT_T_SPELLHIT: - { - if (temp.event_param1) - { - SpellEntry const* pSpell = GetSpellStore()->LookupEntry(temp.event_param1); - if (!pSpell) - { - error_db_log("TSCR: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.event_param1, i); - continue; - } - - if (temp.event_param2_s != -1 && temp.event_param2 != pSpell->SchoolMask) - error_db_log("TSCR: Creature %u has param1(spellId %u) but param2 is not -1 and not equal to spell's school mask. Event %u can never trigger.", temp.creature_id, temp.event_param1, i); - } - - //TODO: fix this system with SPELL_SCHOOL_MASK. Current complicate things, using int32(-1) instead of just 0 - //SPELL_SCHOOL_MASK_NONE = 0 and does not exist, thus it can not ever trigger or be used in SpellHit() - if (temp.event_param2_s != -1 && temp.event_param2_s > SPELL_SCHOOL_MASK_ALL) - error_db_log("TSCR: Creature %u is using invalid SpellSchoolMask(%u) defined in event %u.", temp.creature_id, temp.event_param2, i); - - if (temp.event_param4 < temp.event_param3) - error_db_log("TSCR: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); - } - break; - - case EVENT_T_RANGE: - case EVENT_T_OOC_LOS: - case EVENT_T_FRIENDLY_HP: - case EVENT_T_FRIENDLY_IS_CC: - case EVENT_T_FRIENDLY_MISSING_BUFF: - { - if (temp.event_param4 < temp.event_param3) - error_db_log("TSCR: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); - } - break; - - case EVENT_T_TIMER: - case EVENT_T_TIMER_OOC: - { - if (temp.event_param2 < temp.event_param1) - error_db_log("TSCR: Creature %u are using timed event(%u) with param2 < param1 (InitialMax < InitialMin). Event will never repeat.", temp.creature_id, i); - - if (temp.event_param4 < temp.event_param3) - error_db_log("TSCR: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); - } - break; - - case EVENT_T_KILL: - case EVENT_T_TARGET_CASTING: - { - if (temp.event_param2 < temp.event_param1) - error_db_log("TSCR: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); - } - break; - - case EVENT_T_AGGRO: - case EVENT_T_DEATH: - case EVENT_T_EVADE: - case EVENT_T_SPAWNED: - { - if (temp.event_flags & EFLAG_REPEATABLE) - { - error_db_log("TSCR: Creature %u has EFLAG_REPEATABLE set. Event can never be repeatable. Removing flag for event %u.", temp.creature_id, i); - temp.event_flags &= ~EFLAG_REPEATABLE; - } - } - break; - }; - - for (uint32 j = 0; j < MAX_ACTIONS; j++) - { - temp.action[j].type = fields[10+(j*4)].GetUInt16(); - temp.action[j].param1 = fields[11+(j*4)].GetUInt32(); - temp.action[j].param2 = fields[12+(j*4)].GetUInt32(); - temp.action[j].param3 = fields[13+(j*4)].GetUInt32(); - - //Report any errors in actions - switch (temp.action[j].type) - { - case ACTION_T_SAY: - case ACTION_T_YELL: - case ACTION_T_TEXTEMOTE: - if (GetEventAIText(temp.action[j].param1) == DEFAULT_TEXT) - error_db_log("TSCR: Event %u Action %u refrences missing Localized_Text entry", i, j+1); - break; - - case ACTION_T_SOUND: - if (!GetSoundEntriesStore()->LookupEntry(temp.action[j].param1)) - error_db_log("TSCR: Event %u Action %u uses non-existant SoundID %u.", i, j+1, temp.action[j].param1); - break; - - case ACTION_T_RANDOM_SAY: - case ACTION_T_RANDOM_YELL: - case ACTION_T_RANDOM_TEXTEMOTE: - if ((temp.action[j].param1 != 0xffffffff && GetEventAIText(temp.action[j].param1) == DEFAULT_TEXT) || - (temp.action[j].param2 != 0xffffffff && GetEventAIText(temp.action[j].param2) == DEFAULT_TEXT) || - (temp.action[j].param3 != 0xffffffff && GetEventAIText(temp.action[j].param3) == DEFAULT_TEXT)) - error_db_log("TSCR: Event %u Action %u refrences missing Localized_Text entry", i, j+1); - break; - - case ACTION_T_CAST: - { - if (!GetSpellStore()->LookupEntry(temp.action[j].param1)) - error_db_log("TSCR: Event %u Action %u uses non-existant SpellID %u.", i, j+1, temp.action[j].param1); - - if (temp.action[j].param2 >= TARGET_T_END) - error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); - } - break; - - case ACTION_T_REMOVEAURASFROMSPELL: - { - if (!GetSpellStore()->LookupEntry(temp.action[j].param2)) - error_db_log("TSCR: Event %u Action %u uses non-existant SpellID %u.", i, j+1, temp.action[j].param2); - - if (temp.action[j].param1 >= TARGET_T_END) - error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); - } - break; - - case ACTION_T_CASTCREATUREGO: - { - if (!GetSpellStore()->LookupEntry(temp.action[j].param2)) - error_db_log("TSCR: Event %u Action %u uses non-existant SpellID %u.", i, j+1, temp.action[j].param2); - - if (temp.action[j].param3 >= TARGET_T_END) - error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); - } - break; - - //2nd param target - case ACTION_T_SUMMON_ID: - { - if (EventAI_Summon_Map.find(temp.action[j].param3) == EventAI_Summon_Map.end()) - error_db_log("TSCR: Event %u Action %u summons missing EventAI_Summon %u", i, j+1, temp.action[j].param3); - - if (temp.action[j].param2 >= TARGET_T_END) - error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); - } - break; - - case ACTION_T_SUMMON: - case ACTION_T_THREAT_SINGLE_PCT: - case ACTION_T_QUEST_EVENT: - case ACTION_T_SET_UNIT_FLAG: - case ACTION_T_REMOVE_UNIT_FLAG: - case ACTION_T_SET_INST_DATA64: - if (temp.action[j].param2 >= TARGET_T_END) - error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); - break; - - //3rd param target - case ACTION_T_SET_UNIT_FIELD: - if (temp.action[j].param3 >= TARGET_T_END) - error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); - break; - - case ACTION_T_SET_PHASE: - if (temp.action[j].param1 > 31) - error_db_log("TSCR: Event %u Action %u attempts to set phase > 31. Phase mask cannot be used past phase 31.", i, j+1); - break; - - case ACTION_T_INC_PHASE: - if (!temp.action[j].param1) - error_db_log("TSCR: Event %u Action %u is incrementing phase by 0. Was this intended?", i, j+1); - break; - - case ACTION_T_KILLED_MONSTER: - if (temp.event_type != EVENT_T_DEATH) - outstring_log("TSCR WARNING: Event %u Action %u calling ACTION_T_KILLED_MONSTER outside of EVENT_T_DEATH", i, j+1); - break; - - case ACTION_T_SET_INST_DATA: - if (temp.action[j].param2 > 3) - error_db_log("TSCR: Event %u Action %u attempts to set instance data above encounter state 3. Custom case?", i, j+1); - break; - - default: - break; - } - - if (temp.action[j].type >= ACTION_T_END) - error_db_log("TSCR: Event %u Action %u has incorrect action type. Maybe DB requires updated version of SD2.", i, j+1); - } - - //Add to list - EventAI_Event_List.push_back(temp); - ++Count; - - }while (result->NextRow()); - - delete result; - outstring_log(""); - outstring_log("TSCR: >> Loaded %u EventAI_Events", Count); - - }else outstring_log("TSCR: WARNING >> Loaded 0 EventAI_Scripts. DB table `EventAI_Scripts` is empty."); - - // Gather Script Text - result = TScriptDB.PQuery("SELECT `id`, `sound`, `type`, `language`, `text`" - "FROM `script_texts`;"); - - // Drop Existing Script Text Map - Script_TextMap.clear(); - - if(result) - { - outstring_log("TSCR: Loading Script Text..."); - barGoLink bar(result->GetRowCount()); - uint32 count = 0; - - do - { - bar.step(); - Field* fields = result->Fetch(); - ScriptText temp; - - uint32 i = fields[0].GetInt32(); - temp.SoundId = fields[1].GetInt32(); - temp.Type = fields[2].GetInt32(); - temp.Language = fields[3].GetInt32(); - temp.Text = fields[4].GetString(); - - if (temp.SoundId) - { - if (!GetSoundEntriesStore()->LookupEntry(temp.SoundId)) - error_db_log("TSCR: Id %u in table script_texts has soundid %u but sound does not exist.",i,temp.SoundId); - } - - if(!strlen(temp.Text.c_str())) - error_db_log("TSCR: Id %u in table script_texts has no text.", i); - - Script_TextMap[i] = temp; - ++count; - - }while(result->NextRow()); - - delete result; - - outstring_log(""); - outstring_log("TSCR: Loaded %u Script Texts", count); - - }else outstring_log("TSCR WARNING >> Loaded 0 Script Texts. Database table `script_texts` is empty."); - - //Free database thread and resources - TScriptDB.HaltDelayThread(); - - //***End DB queries*** - } -} - -struct TSpellSummary { - uint8 Targets; // set of enum SelectTarget - uint8 Effects; // set of enum SelectEffect -}extern *SpellSummary; - -MANGOS_DLL_EXPORT -void ScriptsFree() -{ - // Free Spell Summary - delete []SpellSummary; - - // Free resources before library unload - for(int i=0;i 8) - { - Locale = 0; - error_log("TSCR: Locale set to invalid language id. Defaulting to 0."); - } - - outstring_log("TSCR: Using locale %u", Locale); - outstring_log(""); - - EAI_ErrorLevel = TScriptConfig.GetIntDefault("EAIErrorLevel", 1); - - switch (EAI_ErrorLevel) - { - case 0: - outstring_log("TSCR: EventAI Error Reporting level set to 0 (Startup Errors only)"); - break; - - case 1: - outstring_log("TSCR: EventAI Error Reporting level set to 1 (Startup errors and Runtime event errors)"); - break; - - case 2: - outstring_log("TSCR: EventAI Error Reporting level set to 2 (Startup errors, Runtime event errors, and Creation errors)"); - break; - - default: - outstring_log("TSCR: Unknown EventAI Error Reporting level. Defaulting to 1 (Startup errors and Runtime event errors)"); - EAI_ErrorLevel = 1; - break; - } - outstring_log(""); - - //Load database (must be called after TScriptConfig.SetSource) - LoadDatabase(); - - nrscripts = 0; - for(int i=0;i::iterator i = EventAI_LocalizedTextMap.find(entry); - - if (i == EventAI_LocalizedTextMap.end()) - { - error_log("TSCR: EventAI Localized Text %u not found", entry); - return DEFAULT_TEXT; - } - - switch (Locale) - { - case 1: - temp = (*i).second.locale_1.c_str(); - break; - - case 2: - temp = (*i).second.locale_2.c_str(); - break; - - case 3: - temp = (*i).second.locale_3.c_str(); - break; - - case 4: - temp = (*i).second.locale_4.c_str(); - break; - - case 5: - temp = (*i).second.locale_5.c_str(); - break; - - case 6: - temp = (*i).second.locale_6.c_str(); - break; - - case 7: - temp = (*i).second.locale_7.c_str(); - break; - - case 8: - temp = (*i).second.locale_8.c_str(); - break; - }; - - if (strlen(temp)) - return temp; - - return DEFAULT_TEXT; -} - -const char* GetScriptLocalizedText(uint32 entry) -{ - const char* temp = NULL; - - HM_NAMESPACE::hash_map::iterator i = Script_LocalizedTextMap.find(entry); - - if (i == Script_LocalizedTextMap.end()) - { - error_log("TSCR: Script Localized Text %u not found", entry); - return DEFAULT_TEXT; - } - - switch (Locale) - { - case 1: - temp = (*i).second.locale_1.c_str(); - break; - - case 2: - temp = (*i).second.locale_2.c_str(); - break; - - case 3: - temp = (*i).second.locale_3.c_str(); - break; - - case 4: - temp = (*i).second.locale_4.c_str(); - break; - - case 5: - temp = (*i).second.locale_5.c_str(); - break; - - case 6: - temp = (*i).second.locale_6.c_str(); - break; - - case 7: - temp = (*i).second.locale_7.c_str(); - break; - - case 8: - temp = (*i).second.locale_8.c_str(); - break; - }; - - if (strlen(temp)) - return temp; - - return DEFAULT_TEXT; -} - -const char* GetEventAIText(uint32 entry) -{ - if(entry == 0xffffffff) - error_log("TSCR: Entry = -1, GetEventAIText should not be called in this case."); - - const char* str = NULL; - - HM_NAMESPACE::hash_map::iterator itr = EventAI_Text_Map.find(entry); - if(itr == EventAI_Text_Map.end()) - { - error_log("TSCR: Unable to find EventAI Text %u", entry); - return DEFAULT_TEXT; - } - - str = (*itr).second.c_str(); - - if(strlen(str)) - return str; - - if(strlen((*itr).second.c_str())) - return (*itr).second.c_str(); - - return DEFAULT_TEXT; -} - -void ProcessScriptText(uint32 id, WorldObject* pSource, Unit* target) -{ - if (!pSource) - { - error_log("TSCR: ProcessScriptText invalid Source pointer."); - return; - } - - HM_NAMESPACE::hash_map::iterator i = Script_TextMap.find(id); - - if (i == Script_TextMap.end()) - { - error_log("TSCR: ProcessScriptText could not find id %u.",id); - return; - } - - if((*i).second.SoundId) - { - if(GetSoundEntriesStore()->LookupEntry((*i).second.SoundId)) - { - pSource->SendPlaySound((*i).second.SoundId, false); - } - else - error_log("TSCR: ProcessScriptText id %u tried to process invalid soundid %u.",id,(*i).second.SoundId); - } - - switch((*i).second.Type) - { - case CHAT_TYPE_SAY: - pSource->MonsterSay((*i).second.Text.c_str(), (*i).second.Language, target ? target->GetGUID() : 0); - break; - - case CHAT_TYPE_YELL: - pSource->MonsterYell((*i).second.Text.c_str(), (*i).second.Language, target ? target->GetGUID() : 0); - break; - - case CHAT_TYPE_TEXT_EMOTE: - pSource->MonsterTextEmote((*i).second.Text.c_str(), target ? target->GetGUID() : 0); - break; - - case CHAT_TYPE_BOSS_EMOTE: - pSource->MonsterTextEmote((*i).second.Text.c_str(), target ? target->GetGUID() : 0, true); - break; - - case CHAT_TYPE_WHISPER: - { - if (target && target->GetTypeId() == TYPEID_PLAYER) - pSource->MonsterWhisper((*i).second.Text.c_str(), target->GetGUID()); - else error_log("TSCR: ProcessScriptText id %u cannot whisper without target unit (TYPEID_PLAYER).", id); - }break; - - case CHAT_TYPE_BOSS_WHISPER: - { - if (target && target->GetTypeId() == TYPEID_PLAYER) - pSource->MonsterWhisper((*i).second.Text.c_str(), target->GetGUID(), true); - else error_log("TSCR: ProcessScriptText id %u cannot whisper without target unit (TYPEID_PLAYER).", id); - }break; - } -} - -Script* GetScriptByName(std::string Name) -{ - if(Name.empty()) - return NULL; - - for(int i=0;iName == Name ) - return m_scripts[i]; - } - return NULL; -} - -//******************************** -//*** Functions to be Exported *** - -MANGOS_DLL_EXPORT -bool GossipHello ( Player * player, Creature *_Creature ) -{ - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - if(!tmpscript || !tmpscript->pGossipHello) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pGossipHello(player,_Creature); -} - -MANGOS_DLL_EXPORT -bool GossipSelect( Player *player, Creature *_Creature, uint32 sender, uint32 action ) -{ - debug_log("TSCR: Gossip selection, sender: %d, action: %d",sender, action); - - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - if(!tmpscript || !tmpscript->pGossipSelect) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pGossipSelect(player,_Creature,sender,action); -} - -MANGOS_DLL_EXPORT -bool GossipSelectWithCode( Player *player, Creature *_Creature, uint32 sender, uint32 action, const char* sCode ) -{ - debug_log("TSCR: Gossip selection with code, sender: %d, action: %d",sender, action); - - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - if(!tmpscript || !tmpscript->pGossipSelectWithCode) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pGossipSelectWithCode(player,_Creature,sender,action,sCode); -} - -MANGOS_DLL_EXPORT -bool QuestAccept( Player *player, Creature *_Creature, Quest const *_Quest ) -{ - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - if(!tmpscript || !tmpscript->pQuestAccept) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pQuestAccept(player,_Creature,_Quest); -} - -MANGOS_DLL_EXPORT -bool QuestSelect( Player *player, Creature *_Creature, Quest const *_Quest ) -{ - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - if(!tmpscript || !tmpscript->pQuestSelect) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pQuestSelect(player,_Creature,_Quest); -} - -MANGOS_DLL_EXPORT -bool QuestComplete( Player *player, Creature *_Creature, Quest const *_Quest ) -{ - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - if(!tmpscript || !tmpscript->pQuestComplete) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pQuestComplete(player,_Creature,_Quest); -} - -MANGOS_DLL_EXPORT -bool ChooseReward( Player *player, Creature *_Creature, Quest const *_Quest, uint32 opt ) -{ - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - if(!tmpscript || !tmpscript->pChooseReward) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pChooseReward(player,_Creature,_Quest,opt); -} - -MANGOS_DLL_EXPORT -uint32 NPCDialogStatus( Player *player, Creature *_Creature ) -{ - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - if(!tmpscript || !tmpscript->pNPCDialogStatus) return 100; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pNPCDialogStatus(player,_Creature); -} - -MANGOS_DLL_EXPORT -uint32 GODialogStatus( Player *player, GameObject *_GO ) -{ - Script *tmpscript = GetScriptByName(_GO->GetGOInfo()->ScriptName); - if(!tmpscript || !tmpscript->pGODialogStatus) return 100; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pGODialogStatus(player,_GO); -} - -MANGOS_DLL_EXPORT -bool ItemHello( Player *player, Item *_Item, Quest const *_Quest ) -{ - Script *tmpscript = GetScriptByName(_Item->GetProto()->ScriptName); - if(!tmpscript || !tmpscript->pItemHello) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pItemHello(player,_Item,_Quest); -} - -MANGOS_DLL_EXPORT -bool ItemQuestAccept( Player *player, Item *_Item, Quest const *_Quest ) -{ - Script *tmpscript = GetScriptByName(_Item->GetProto()->ScriptName); - if(!tmpscript || !tmpscript->pItemQuestAccept) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pItemQuestAccept(player,_Item,_Quest); -} - -MANGOS_DLL_EXPORT -bool GOHello( Player *player, GameObject *_GO ) -{ - Script *tmpscript = GetScriptByName(_GO->GetGOInfo()->ScriptName); - if(!tmpscript || !tmpscript->pGOHello) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pGOHello(player,_GO); -} - -MANGOS_DLL_EXPORT -bool GOQuestAccept( Player *player, GameObject *_GO, Quest const *_Quest ) -{ - Script *tmpscript = GetScriptByName(_GO->GetGOInfo()->ScriptName); - if(!tmpscript || !tmpscript->pGOQuestAccept) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pGOQuestAccept(player,_GO,_Quest); -} - -MANGOS_DLL_EXPORT -bool GOChooseReward( Player *player, GameObject *_GO, Quest const *_Quest, uint32 opt ) -{ - Script *tmpscript = GetScriptByName(_GO->GetGOInfo()->ScriptName); - if(!tmpscript || !tmpscript->pGOChooseReward) return false; - - player->PlayerTalkClass->ClearMenus(); - return tmpscript->pGOChooseReward(player,_GO,_Quest,opt); -} - -MANGOS_DLL_EXPORT -bool AreaTrigger( Player *player, AreaTriggerEntry * atEntry) -{ - Script *tmpscript = NULL; - - tmpscript = GetScriptByName(GetAreaTriggerScriptNameById(atEntry->id)); - if(!tmpscript || !tmpscript->pAreaTrigger) return false; - - return tmpscript->pAreaTrigger(player, atEntry); -} - -MANGOS_DLL_EXPORT -CreatureAI* GetAI(Creature *_Creature) -{ - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - - if(!tmpscript || !tmpscript->GetAI) return NULL; - return tmpscript->GetAI(_Creature); -} - -MANGOS_DLL_EXPORT -bool ItemUse( Player *player, Item* _Item, SpellCastTargets const& targets) -{ - Script *tmpscript = GetScriptByName(_Item->GetProto()->ScriptName); - if(!tmpscript || !tmpscript->pItemUse) return false; - - return tmpscript->pItemUse(player,_Item,targets); -} - -MANGOS_DLL_EXPORT -bool ReceiveEmote( Player *player, Creature *_Creature, uint32 emote ) -{ - Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); - if(!tmpscript || !tmpscript->pReceiveEmote) return false; - - return tmpscript->pReceiveEmote(player, _Creature, emote); -} - -MANGOS_DLL_EXPORT -InstanceData* CreateInstanceData(Map *map) -{ - Script *tmpscript = NULL; - - if(!map->IsDungeon()) return false; - - tmpscript = GetScriptByName(((InstanceMap*)map)->GetScript()); - if(!tmpscript || !tmpscript->GetInstanceData) return false; - - return tmpscript->GetInstanceData(map); -} +/* Copyright (C) 2006 - 2008 TrinityScript + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#include "precompiled.h" +#include "Config/Config.h" +#include "Database/DatabaseEnv.h" +#include "Database/DBCStores.h" +#include "ObjectMgr.h" +#include "ProgressBar.h" +#include "scripts/creature/mob_event_ai.h" + +#define _FULLVERSION "TrinityScript" + +#ifndef _TSCRIPTCONFVERSION +# define _TSCRIPTCONFVERSION 2008100201 +#endif //_TSCRIPTCONFVERSION + +#ifndef _TRINITY_SCRIPT_CONFIG +# define _TRINITY_SCRIPT_CONFIG "trinityscript.conf" +#endif //_TRINITY_SCRIPT_CONFIG + +//*** Global data *** +int nrscripts; +Script *m_scripts[MAX_SCRIPTS]; + +DatabaseType TScriptDB; +Config TScriptConfig; +uint32 Locale; + +// String text additional data, used in TextMap +struct StringTextData +{ + uint32 SoundId; + uint8 Type; + uint32 Language; +}; + +// Enums used by StringTextData::Type +enum ChatType +{ + CHAT_TYPE_SAY = 0, + CHAT_TYPE_YELL = 1, + CHAT_TYPE_TEXT_EMOTE = 2, + CHAT_TYPE_BOSS_EMOTE = 3, + CHAT_TYPE_WHISPER = 4, + CHAT_TYPE_BOSS_WHISPER = 5, +}; + +#define TEXT_SOURCE_RANGE -100000 //the amount of entries each text source has available + +// Text Maps +HM_NAMESPACE::hash_map EventAI_Text_Map; +HM_NAMESPACE::hash_map TextMap; + +// Localized Text structure for storing locales (for EAI and SD2 scripts). +struct Localized_Text +{ + std::string locale_1; + std::string locale_2; + std::string locale_3; + std::string locale_4; + std::string locale_5; + std::string locale_6; + std::string locale_7; + std::string locale_8; +}; +//*** End Global data *** + +//*** EventAI data *** +HM_NAMESPACE::hash_map EventAI_LocalizedTextMap; + +//Event AI structure. Used exclusivly by mob_event_ai.cpp (60 bytes each) +std::list EventAI_Event_List; + +//Event AI summon structure. Used exclusivly by mob_event_ai.cpp. +HM_NAMESPACE::hash_map EventAI_Summon_Map; + +//Event AI error prevention structure. Used at runtime to prevent error log spam of same creature id. +//HM_NAMESPACE::hash_map EventAI_CreatureErrorPreventionList; + +uint32 EAI_ErrorLevel; +//*** End EventAI data *** + +void FillSpellSummary(); + +// -- Scripts to be added -- + +// -- Areatrigger -- +extern void AddSC_areatrigger_scripts(); + +// -- Boss -- +extern void AddSC_boss_emeriss(); +extern void AddSC_boss_taerar(); +extern void AddSC_boss_ysondre(); + +// -- Creature -- +extern void AddSC_mob_event(); +extern void AddSC_generic_creature(); + +// -- Custom -- +extern void AddSC_custom_example(); +extern void AddSC_custom_gossip_codebox(); +extern void AddSC_test(); + +// -- GO -- +extern void AddSC_go_scripts(); + +// -- Guard -- +extern void AddSC_guards(); + +// -- Honor -- + +// -- Item -- +extern void AddSC_item_scripts(); +extern void AddSC_item_test(); + +// -- NPC -- +extern void AddSC_npc_professions(); +extern void AddSC_npcs_special(); + +// -- Servers -- + +//-------------------- +//------ ZONE -------- + +//Alterac Mountains +extern void AddSC_alterac_mountains(); + +//Arathi Highlands +//Ashenvale Forest +//Aunchindoun +//--Auchenai Crypts +extern void AddSC_boss_exarch_maladaar(); +//--Mana Tombs +extern void AddSC_boss_nexusprince_shaffar(); +extern void AddSC_boss_pandemonius(); + +//--Sekketh Halls +extern void AddSC_boss_darkweaver_syth(); +extern void AddSC_boss_talon_king_ikiss(); +extern void AddSC_instance_sethekk_halls(); + +//--Shadow Labyrinth +extern void AddSC_boss_ambassador_hellmaw(); +extern void AddSC_boss_blackheart_the_inciter(); +extern void AddSC_boss_grandmaster_vorpil(); +extern void AddSC_boss_murmur(); +extern void AddSC_instance_shadow_labyrinth(); + +//Azshara +extern void AddSC_boss_azuregos(); +extern void AddSC_azshara(); + +//Azuremyst Isle +extern void AddSC_azuremyst_isle(); + +//Badlands +//Barrens +extern void AddSC_the_barrens(); + +//Black Temple +extern void AddSC_black_temple(); +extern void AddSC_boss_illidan(); +extern void AddSC_boss_shade_of_akama(); +extern void AddSC_boss_supremus(); +extern void AddSC_boss_gurtogg_bloodboil(); +extern void AddSC_boss_mother_shahraz(); +extern void AddSC_boss_reliquary_of_souls(); +extern void AddSC_boss_teron_gorefiend(); +extern void AddSC_boss_najentus(); +extern void AddSC_boss_illidari_council(); +extern void AddSC_instance_black_temple(); + +//Blackfathom Depths +//Blackrock Depths +extern void AddSC_blackrock_depths(); +extern void AddSC_boss_ambassador_flamelash(); +extern void AddSC_boss_angerrel(); +extern void AddSC_boss_anubshiah(); +extern void AddSC_boss_doomrel(); +extern void AddSC_boss_doperel(); +extern void AddSC_boss_draganthaurissan(); +extern void AddSC_boss_general_angerforge(); +extern void AddSC_boss_gloomrel(); +extern void AddSC_boss_gorosh_the_dervish(); +extern void AddSC_boss_grizzle(); +extern void AddSC_boss_haterel(); +extern void AddSC_boss_high_interrogator_gerstahn(); +extern void AddSC_boss_magmus(); +extern void AddSC_boss_moira_bronzebeard(); +extern void AddSC_boss_seethrel(); +extern void AddSC_boss_vilerel(); + +//Blackrock Spire +extern void AddSC_boss_drakkisath(); +extern void AddSC_boss_halycon(); +extern void AddSC_boss_highlordomokk(); +extern void AddSC_boss_mothersmolderweb(); +extern void AddSC_boss_overlordwyrmthalak(); +extern void AddSC_boss_shadowvosh(); +extern void AddSC_boss_thebeast(); +extern void AddSC_boss_warmastervoone(); +extern void AddSC_boss_quatermasterzigris(); +extern void AddSC_boss_pyroguard_emberseer(); +extern void AddSC_boss_gyth(); +extern void AddSC_boss_rend_blackhand(); + +//Blackwing lair +extern void AddSC_boss_razorgore(); +extern void AddSC_boss_vael(); +extern void AddSC_boss_broodlord(); +extern void AddSC_boss_firemaw(); +extern void AddSC_boss_ebonroc(); +extern void AddSC_boss_flamegor(); +extern void AddSC_boss_chromaggus(); +extern void AddSC_boss_nefarian(); +extern void AddSC_boss_victor_nefarius(); + +//Blade's Edge Mountains +extern void AddSC_blades_edge_mountains(); + +//Blasted lands +extern void AddSC_boss_kruul(); +extern void AddSC_blasted_lands(); + +//Bloodmyst Isle +extern void AddSC_bloodmyst_isle(); + +//Burning steppes +extern void AddSC_burning_steppes(); + +//Caverns of Time +//--Battle for Mt. Hyjal +extern void AddSC_hyjal(); +extern void AddSC_boss_archimonde(); +extern void AddSC_instance_mount_hyjal(); + +//--Old Hillsbrad +extern void AddSC_boss_captain_skarloc(); +extern void AddSC_boss_epoch_hunter(); +extern void AddSC_boss_lieutenant_drake(); +extern void AddSC_instance_old_hillsbrad(); +extern void AddSC_old_hillsbrad(); + +//--The Dark Portal +extern void AddSC_boss_aeonus(); +extern void AddSC_boss_chrono_lord_deja(); +extern void AddSC_boss_temporus(); + +//Coilfang Resevoir +//--Serpent Shrine Cavern +extern void AddSC_boss_fathomlord_karathress(); +extern void AddSC_boss_hydross_the_unstable(); +extern void AddSC_boss_lady_vashj(); +extern void AddSC_boss_leotheras_the_blind(); +extern void AddSC_boss_morogrim_tidewalker(); +extern void AddSC_instance_serpentshrine_cavern(); + +//--Slave Pens + +//--Steam Vault +extern void AddSC_boss_hydromancer_thespia(); +extern void AddSC_boss_mekgineer_steamrigger(); +extern void AddSC_boss_warlord_kalithresh(); +extern void AddSC_instance_steam_vault(); + +//--Underbog +extern void AddSC_boss_hungarfen(); + +//Darkshore +//Darnassus +//Deadmines +//Deadwind pass +//Desolace +//Dire Maul +//Dun Morogh +extern void AddSC_dun_morogh(); + +//Durotar +//Duskwood +//Dustwallow marsh +extern void AddSC_dustwallow_marsh(); + +//Eversong Woods +extern void AddSC_eversong_woods(); + +//Exodar +//Eastern Plaguelands +extern void AddSC_eastern_plaguelands(); + +//Elwynn Forest +extern void AddSC_elwynn_forest(); + +//Felwood +extern void AddSC_felwood(); + +//Feralas +extern void AddSC_feralas(); + +//Ghostlands +extern void AddSC_ghostlands(); + +//Gnomeregan +//Gruul's Lair +extern void AddSC_boss_gruul(); +extern void AddSC_boss_high_king_maulgar(); +extern void AddSC_instance_gruuls_lair(); + +//Hellfire Citadel +//--Blood Furnace +extern void AddSC_boss_broggok(); +extern void AddSC_boss_kelidan_the_breaker(); +extern void AddSC_boss_the_maker(); + +//--Magtheridon's Lair +extern void AddSC_boss_magtheridon(); +extern void AddSC_instance_magtheridons_lair(); + +//--Shattered Halls +extern void AddSC_boss_grand_warlock_nethekurse(); +extern void AddSC_boss_warbringer_omrogg(); +extern void AddSC_instance_shattered_halls(); + +//--Ramparts +extern void AddSC_boss_watchkeeper_gargolmar(); +extern void AddSC_boss_omor_the_unscarred(); + +//Hellfire Peninsula +extern void AddSC_boss_doomlordkazzak(); +extern void AddSC_hellfire_peninsula(); + +//Hillsbrad Foothills +//Hinterlands +//Ironforge +extern void AddSC_ironforge(); + +//Isle of Quel'Danas +extern void AddSC_isle_of_queldanas(); + +//Karazhan +extern void AddSC_boss_attumen(); +extern void AddSC_boss_curator(); +extern void AddSC_boss_maiden_of_virtue(); +extern void AddSC_boss_shade_of_aran(); +extern void AddSC_boss_malchezaar(); +extern void AddSC_boss_terestian_illhoof(); +extern void AddSC_netherspite_infernal(); +extern void AddSC_boss_moroes(); +extern void AddSC_bosses_opera(); +extern void AddSC_instance_karazhan(); +extern void AddSC_karazhan(); + +//Loch Modan +extern void AddSC_loch_modan(); + +//Lower Blackrock Spire + +// Magister's Terrace +extern void AddSC_boss_felblood_kaelthas(); +extern void AddSC_boss_selin_fireheart(); +extern void AddSC_boss_vexallus(); +extern void AddSC_boss_priestess_delrissa(); +extern void AddSC_instance_magisters_terrace(); + +//Maraudon +extern void AddSC_boss_celebras_the_cursed(); +extern void AddSC_boss_landslide(); +extern void AddSC_boss_noxxion(); +extern void AddSC_boss_ptheradras(); + +//Molten core +extern void AddSC_boss_lucifron(); +extern void AddSC_boss_magmadar(); +extern void AddSC_boss_gehennas(); +extern void AddSC_boss_garr(); +extern void AddSC_boss_baron_geddon(); +extern void AddSC_boss_shazzrah(); +extern void AddSC_boss_golemagg(); +extern void AddSC_boss_sulfuron(); +extern void AddSC_boss_majordomo(); +extern void AddSC_boss_ragnaros(); +extern void AddSC_instance_molten_core(); +extern void AddSC_molten_core(); + +//Moonglade +extern void AddSC_moonglade(); + +//Mulgore +extern void AddSC_mulgore(); + +//Nagrand +extern void AddSC_nagrand(); + +//Naxxramas +extern void AddSC_boss_anubrekhan(); +extern void AddSC_boss_maexxna(); +extern void AddSC_boss_patchwerk(); +extern void AddSC_boss_razuvious(); +extern void AddSC_boss_highlord_mograine(); +extern void AddSC_boss_lady_blaumeux(); +extern void AddSC_boss_sir_zeliek(); +extern void AddSC_boss_thane_korthazz(); +extern void AddSC_boss_kelthuzad(); +extern void AddSC_boss_faerlina(); +extern void AddSC_boss_loatheb(); +extern void AddSC_boss_noth(); +extern void AddSC_boss_gluth(); +extern void AddSC_boss_sapphiron(); + +//Netherstorm +extern void AddSC_netherstorm(); + +//Onyxia's Lair +extern void AddSC_boss_onyxia(); + +//Orgrimmar +extern void AddSC_orgrimmar(); + +//Ragefire Chasm +//Razorfen Downs +extern void AddSC_boss_amnennar_the_coldbringer(); + +//Redridge Mountains +//Ruins of Ahn'Qiraj +//Scarlet Monastery +extern void AddSC_boss_arcanist_doan(); +extern void AddSC_boss_azshir_the_sleepless(); +extern void AddSC_boss_bloodmage_thalnos(); +extern void AddSC_boss_herod(); +extern void AddSC_boss_high_inquisitor_fairbanks(); +extern void AddSC_boss_high_inquisitor_whitemane(); +extern void AddSC_boss_houndmaster_loksey(); +extern void AddSC_boss_interrogator_vishas(); +extern void AddSC_boss_scarlet_commander_mograine(); +extern void AddSC_boss_scorn(); + +//Scholomance +extern void AddSC_boss_darkmaster_gandling(); +extern void AddSC_boss_death_knight_darkreaver(); +extern void AddSC_boss_theolenkrastinov(); +extern void AddSC_boss_illuciabarov(); +extern void AddSC_boss_instructormalicia(); +extern void AddSC_boss_jandicebarov(); +extern void AddSC_boss_kormok(); +extern void AddSC_boss_lordalexeibarov(); +extern void AddSC_boss_lorekeeperpolkelt(); +extern void AddSC_boss_rasfrost(); +extern void AddSC_boss_theravenian(); +extern void AddSC_boss_vectus(); +extern void AddSC_instance_scholomance(); + +//Searing gorge +extern void AddSC_searing_gorge(); + +//Shadowfang keep +extern void AddSC_shadowfang_keep(); +extern void AddSC_instance_shadowfang_keep(); + +//Shadowmoon Valley +extern void AddSC_boss_doomwalker(); +extern void AddSC_shadowmoon_valley(); + +//Shattrath +extern void AddSC_shattrath_city(); + +//Silithus +extern void AddSC_silithus(); + +//Silvermoon +extern void AddSC_silvermoon_city(); + +//Silverpine forest +extern void AddSC_silverpine_forest(); + +//Stockade +//Stonetalon mountains +extern void AddSC_stonetalon_mountains(); + +//Stormwind City +extern void AddSC_stormwind_city(); + +//Stranglethorn Vale +extern void AddSC_stranglethorn_vale(); + +//Stratholme +extern void AddSC_boss_magistrate_barthilas(); +extern void AddSC_boss_maleki_the_pallid(); +extern void AddSC_boss_nerubenkan(); +extern void AddSC_boss_cannon_master_willey(); +extern void AddSC_boss_baroness_anastari(); +extern void AddSC_boss_ramstein_the_gorger(); +extern void AddSC_boss_timmy_the_cruel(); +extern void AddSC_boss_postmaster_malown(); +extern void AddSC_boss_baron_rivendare(); +extern void AddSC_boss_dathrohan_balnazzar(); +extern void AddSC_boss_order_of_silver_hand(); +extern void AddSC_instance_stratholme(); +extern void AddSC_stratholme(); + +//Sunken Temple +//Tanaris +extern void AddSC_tanaris(); + +//Teldrassil +//Tempest Keep +//--Arcatraz +extern void AddSC_arcatraz(); +extern void AddSC_boss_harbinger_skyriss(); +extern void AddSC_instance_arcatraz(); + +//--Botanica +extern void AddSC_boss_high_botanist_freywinn(); +extern void AddSC_boss_laj(); +extern void AddSC_boss_warp_splinter(); + +//--The Eye +extern void AddSC_boss_kaelthas(); +extern void AddSC_boss_void_reaver(); +extern void AddSC_boss_high_astromancer_solarian(); +extern void AddSC_instance_the_eye(); +extern void AddSC_the_eye(); + +//--The Mechanar +extern void AddSC_boss_gatewatcher_iron_hand(); +extern void AddSC_boss_nethermancer_sepethrea(); + +//Temple of ahn'qiraj +extern void AddSC_boss_cthun(); +extern void AddSC_boss_fankriss(); +extern void AddSC_boss_huhuran(); +extern void AddSC_bug_trio(); +extern void AddSC_boss_sartura(); +extern void AddSC_boss_skeram(); +extern void AddSC_boss_twinemperors(); +extern void AddSC_mob_anubisath_sentinel(); +extern void AddSC_instance_temple_of_ahnqiraj(); + +//Terokkar Forest +extern void AddSC_terokkar_forest(); + +//Thousand Needles +//Thunder Bluff +extern void AddSC_thunder_bluff(); + +//Tirisfal Glades +extern void AddSC_tirisfal_glades(); + +//Uldaman +extern void AddSC_boss_ironaya(); +extern void AddSC_uldaman(); + +//Undercity +extern void AddSC_undercity(); + +//Un'Goro Crater +//Upper blackrock spire +//Wailing caverns + +//Western plaguelands +extern void AddSC_western_plaguelands(); + +//Westfall +//Wetlands +//Winterspring +extern void AddSC_winterspring(); + +//Zangarmarsh +extern void AddSC_zangarmarsh(); + +//Zul'Farrak +//Zul'Gurub +extern void AddSC_boss_jeklik(); +extern void AddSC_boss_venoxis(); +extern void AddSC_boss_marli(); +extern void AddSC_boss_mandokir(); +extern void AddSC_boss_gahzranka(); +extern void AddSC_boss_thekal(); +extern void AddSC_boss_arlokk(); +extern void AddSC_boss_jindo(); +extern void AddSC_boss_hakkar(); +extern void AddSC_boss_grilek(); +extern void AddSC_boss_hazzarah(); +extern void AddSC_boss_renataki(); +extern void AddSC_boss_wushoolay(); +extern void AddSC_instance_zulgurub(); +//Zul'Aman +extern void AddSC_boss_janalai(); +extern void AddSC_boss_nalorakk(); +extern void AddSC_instance_zulaman(); +extern void AddSC_zulaman(); + +// ------------------- +void LoadDatabase() +{ + //Get db string from file + char const* dbstring = NULL; + + if( !TScriptConfig.GetString("TScriptDatabaseInfo", &dbstring) ) + { + error_log("TSCR: Missing Trinity Script database info from configuration file. Load database aborted."); + return; + } + + //Initialize connection to DB + if( dbstring && TScriptDB.Initialize(dbstring) ) + outstring_log("TSCR: TrinityScript database: %s",dbstring); + else + { + error_log("TSCR: Unable to connect to Database. Load database aborted."); + return; + } + + //***Preform all DB queries here*** + QueryResult *result; + + //Get Version information + result = TScriptDB.PQuery("SELECT version FROM script_db_version"); + + if (result) + { + Field *fields = result->Fetch(); + outstring_log("TSCR: Database version is: %s", fields[0].GetString()); + outstring_log(""); + delete result; + + }else + { + error_log("TSCR: Missing `script_db_version` information."); + outstring_log(""); + } + + // Drop Existing Text Map, only done once and we are ready to add data from multiple sources. + TextMap.clear(); + + //TODO: Add load from eventai_texts here + + // Load Script Text + outstring_log("TSCR: Loading Script Texts..."); + LoadMangosStrings(TScriptDB,"script_texts",TEXT_SOURCE_RANGE,(TEXT_SOURCE_RANGE*2)+1); + + // Gather Additional data from Script Texts + result = TScriptDB.PQuery("SELECT entry, sound, type, language FROM script_texts"); + + outstring_log("TSCR: Loading Script Texts additional data..."); + if (result) + { + barGoLink bar(result->GetRowCount()); + uint32 count = 0; + + do + { + bar.step(); + Field* fields = result->Fetch(); + StringTextData temp; + + int32 i = fields[0].GetInt32(); + temp.SoundId = fields[1].GetInt32(); + temp.Type = fields[2].GetInt32(); + temp.Language = fields[3].GetInt32(); + + if (i >= 0) + { + error_db_log("TSCR: Entry %i in table `script_texts` is not a negative value.",i); + continue; + } + + if (i > TEXT_SOURCE_RANGE || i <= TEXT_SOURCE_RANGE*2) + { + error_db_log("TSCR: Entry %i in table `script_texts` is out of accepted entry range for table.",i); + continue; + } + + if (temp.SoundId) + { + if (!GetSoundEntriesStore()->LookupEntry(temp.SoundId)) + error_db_log("TSCR: Entry %i in table `script_texts` has soundId %u but sound does not exist.",i,temp.SoundId); + } + + if (!GetLanguageDescByID(temp.Language)) + error_db_log("TSCR: Entry %i in table `script_texts` using Language %u but Language does not exist.",i,temp.Language); + + if (temp.Type > CHAT_TYPE_BOSS_WHISPER) + error_db_log("TSCR: Entry %i in table `script_texts` has Type %u but this Chat Type does not exist.",i,temp.Type); + + TextMap[i] = temp; + ++count; + } while (result->NextRow()); + + delete result; + + outstring_log(""); + outstring_log(">> TSCR: Loaded %u additional Script Texts data.", count); + }else + { + barGoLink bar(1); + bar.step(); + outstring_log(""); + outstring_log(">> Loaded 0 additional Script Texts data. DB table `script_texts` is empty."); + } + + // Load Custom Text + outstring_log("TSCR: Loading Custom Texts..."); + LoadMangosStrings(TScriptDB,"custom_texts",TEXT_SOURCE_RANGE*2,(TEXT_SOURCE_RANGE*3)+1); + + // Gather Additional data from Custom Texts + result = TScriptDB.PQuery("SELECT entry, sound, type, language FROM custom_texts"); + + outstring_log("TSCR: Loading Custom Texts additional data..."); + if (result) + { + barGoLink bar(result->GetRowCount()); + uint32 count = 0; + + do + { + bar.step(); + Field* fields = result->Fetch(); + StringTextData temp; + + int32 i = fields[0].GetInt32(); + temp.SoundId = fields[1].GetInt32(); + temp.Type = fields[2].GetInt32(); + temp.Language = fields[3].GetInt32(); + + if (i >= 0) + { + error_db_log("TSCR: Entry %i in table `custom_texts` is not a negative value.",i); + continue; + } + + if (i > TEXT_SOURCE_RANGE*2 || i <= TEXT_SOURCE_RANGE*3) + { + error_db_log("TSCR: Entry %i in table `custom_texts` is out of accepted entry range for table.",i); + continue; + } + + if (temp.SoundId) + { + if (!GetSoundEntriesStore()->LookupEntry(temp.SoundId)) + error_db_log("TSCR: Entry %i in table `custom_texts` has soundId %u but sound does not exist.",i,temp.SoundId); + } + + if (!GetLanguageDescByID(temp.Language)) + error_db_log("TSCR: Entry %i in table `custom_texts` using Language %u but Language does not exist.",i,temp.Language); + + if (temp.Type > CHAT_TYPE_BOSS_WHISPER) + error_db_log("TSCR: Entry %i in table `custom_texts` has Type %u but this Chat Type does not exist.",i,temp.Type); + + TextMap[i] = temp; + ++count; + } while (result->NextRow()); + + delete result; + + outstring_log(""); + outstring_log(">> Loaded %u additional Custom Texts data.", count); + }else + { + barGoLink bar(1); + bar.step(); + outstring_log(""); + outstring_log(">> Loaded 0 additional Custom Texts data. DB table `custom_texts` is empty."); + } + + // Drop existing Event AI Localized Text hash map + EventAI_LocalizedTextMap.clear(); + + // Gather EventAI Localized Texts + result = TScriptDB.PQuery("SELECT id, locale_1, locale_2, locale_3, locale_4, locale_5, locale_6, locale_7, locale_8 " + "FROM eventai_localized_texts"); + + outstring_log("TSCR: Loading EventAI Localized Texts..."); + if(result) + { + barGoLink bar(result->GetRowCount()); + uint32 count = 0; + + do + { + Localized_Text temp; + bar.step(); + + Field *fields = result->Fetch(); + + uint32 i = fields[0].GetInt32(); + + temp.locale_1 = fields[1].GetString(); + temp.locale_2 = fields[2].GetString(); + temp.locale_3 = fields[3].GetString(); + temp.locale_4 = fields[4].GetString(); + temp.locale_5 = fields[5].GetString(); + temp.locale_6 = fields[6].GetString(); + temp.locale_7 = fields[7].GetString(); + temp.locale_8 = fields[8].GetString(); + + EventAI_LocalizedTextMap[i] = temp; + ++count; + + }while(result->NextRow()); + + delete result; + + outstring_log(""); + outstring_log(">> Loaded %u EventAI Localized Texts", count); + }else + { + barGoLink bar(1); + bar.step(); + outstring_log(""); + outstring_log(">> Loaded 0 EventAI Localized Texts. DB table `eventai_localized_texts` is empty"); + } + + //Drop existing EventAI Text hash map + EventAI_Text_Map.clear(); + + //Gather EventAI Text Entries + result = TScriptDB.PQuery("SELECT id, text FROM eventai_texts"); + + outstring_log("TSCR: Loading EventAI_Texts..."); + if (result) + { + barGoLink bar(result->GetRowCount()); + uint32 Count = 0; + + do + { + bar.step(); + Field *fields = result->Fetch(); + + uint32 i = fields[0].GetInt32(); + + std::string text = fields[1].GetString(); + + if (!strlen(text.c_str())) + error_db_log("TSCR: EventAI text %u is empty", i); + + EventAI_Text_Map[i] = text; + ++Count; + + }while (result->NextRow()); + + delete result; + + outstring_log(""); + outstring_log(">> Loaded %u EventAI texts", Count); + }else + { + barGoLink bar(1); + bar.step(); + outstring_log(""); + outstring_log(">> Loaded 0 EventAI texts. DB table `eventai_texts` is empty."); + } + + //Gather event data + result = TScriptDB.PQuery("SELECT id, position_x, position_y, position_z, orientation, spawntimesecs FROM eventai_summons"); + + //Drop Existing EventSummon Map + EventAI_Summon_Map.clear(); + + outstring_log("TSCR: Loading EventAI_Summons..."); + if (result) + { + barGoLink bar(result->GetRowCount()); + uint32 Count = 0; + + do + { + bar.step(); + Field *fields = result->Fetch(); + + EventAI_Summon temp; + + uint32 i = fields[0].GetUInt32(); + temp.position_x = fields[1].GetFloat(); + temp.position_y = fields[2].GetFloat(); + temp.position_z = fields[3].GetFloat(); + temp.orientation = fields[4].GetFloat(); + temp.SpawnTimeSecs = fields[5].GetUInt32(); + + //Add to map + EventAI_Summon_Map[i] = temp; + ++Count; + }while (result->NextRow()); + + delete result; + + outstring_log(""); + outstring_log(">> Loaded %u EventAI summon definitions", Count); + }else + { + barGoLink bar(1); + bar.step(); + outstring_log(""); + outstring_log(">> Loaded 0 EventAI Summon definitions. DB table `eventai_summons` is empty."); + } + + //Gather event data + result = TScriptDB.PQuery("SELECT id, creature_id, event_type, event_inverse_phase_mask, event_chance, event_flags, " + "event_param1, event_param2, event_param3, event_param4, " + "action1_type, action1_param1, action1_param2, action1_param3, " + "action2_type, action2_param1, action2_param2, action2_param3, " + "action3_type, action3_param1, action3_param2, action3_param3 " + "FROM eventai_scripts"); + + //Drop Existing EventAI List + EventAI_Event_List.clear(); + + outstring_log("TSCR: Loading EventAI_Scripts..."); + if (result) + { + barGoLink bar(result->GetRowCount()); + uint32 Count = 0; + + do + { + bar.step(); + Field *fields = result->Fetch(); + + EventAI_Event temp; + + temp.event_id = fields[0].GetUInt32(); + uint32 i = temp.event_id; + temp.creature_id = fields[1].GetUInt32(); + temp.event_type = fields[2].GetUInt16(); + temp.event_inverse_phase_mask = fields[3].GetUInt32(); + temp.event_chance = fields[4].GetUInt8(); + temp.event_flags = fields[5].GetUInt8(); + temp.event_param1 = fields[6].GetUInt32(); + temp.event_param2 = fields[7].GetUInt32(); + temp.event_param3 = fields[8].GetUInt32(); + temp.event_param4 = fields[9].GetUInt32(); + + //Report any errors in event + if (temp.event_type >= EVENT_T_END) + error_db_log("TSCR: Event %u has incorrect event type. Maybe DB requires updated version of SD2.", i); + + //No chance of this event occuring + if (temp.event_chance == 0) + error_db_log("TSCR: Event %u has 0 percent chance. Event will never trigger!", i); + + //Chance above 100, force it to be 100 + if (temp.event_chance > 100) + { + error_db_log("TSCR: Creature %u are using event %u with more than 100 percent chance. Adjusting to 100 percent.", temp.creature_id, i); + temp.event_chance = 100; + } + + //Individual event checks + switch (temp.event_type) + { + case EVENT_T_HP: + case EVENT_T_MANA: + case EVENT_T_TARGET_HP: + { + if (temp.event_param2 > 100) + error_db_log("TSCR: Creature %u are using percentage event(%u) with param2 (MinPercent) > 100. Event will never trigger! ", temp.creature_id, i); + + if (temp.event_param1 <= temp.event_param2) + error_db_log("TSCR: Creature %u are using percentage event(%u) with param1 <= param2 (MaxPercent <= MinPercent). Event will never trigger! ", temp.creature_id, i); + + if (temp.event_flags & EFLAG_REPEATABLE && !temp.event_param3 && !temp.event_param4) + { + error_db_log("TSCR: Creature %u has param3 and param4=0 (RepeatMin/RepeatMax) but cannot be repeatable without timers. Removing EFLAG_REPEATABLE for event %u.", temp.creature_id, i); + temp.event_flags &= ~EFLAG_REPEATABLE; + } + } + break; + + case EVENT_T_SPELLHIT: + { + if (temp.event_param1) + { + SpellEntry const* pSpell = GetSpellStore()->LookupEntry(temp.event_param1); + if (!pSpell) + { + error_db_log("TSCR: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.event_param1, i); + continue; + } + + if (temp.event_param2_s != -1 && temp.event_param2 != pSpell->SchoolMask) + error_db_log("TSCR: Creature %u has param1(spellId %u) but param2 is not -1 and not equal to spell's school mask. Event %u can never trigger.", temp.creature_id, temp.event_param1, i); + } + + //TODO: fix this system with SPELL_SCHOOL_MASK. Current complicate things, using int32(-1) instead of just 0 + //SPELL_SCHOOL_MASK_NONE = 0 and does not exist, thus it can not ever trigger or be used in SpellHit() + if (temp.event_param2_s != -1 && temp.event_param2_s > SPELL_SCHOOL_MASK_ALL) + error_db_log("TSCR: Creature %u is using invalid SpellSchoolMask(%u) defined in event %u.", temp.creature_id, temp.event_param2, i); + + if (temp.event_param4 < temp.event_param3) + error_db_log("TSCR: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + } + break; + + case EVENT_T_RANGE: + case EVENT_T_OOC_LOS: + case EVENT_T_FRIENDLY_HP: + case EVENT_T_FRIENDLY_IS_CC: + case EVENT_T_FRIENDLY_MISSING_BUFF: + { + if (temp.event_param4 < temp.event_param3) + error_db_log("TSCR: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + } + break; + + case EVENT_T_TIMER: + case EVENT_T_TIMER_OOC: + { + if (temp.event_param2 < temp.event_param1) + error_db_log("TSCR: Creature %u are using timed event(%u) with param2 < param1 (InitialMax < InitialMin). Event will never repeat.", temp.creature_id, i); + + if (temp.event_param4 < temp.event_param3) + error_db_log("TSCR: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + } + break; + + case EVENT_T_KILL: + case EVENT_T_TARGET_CASTING: + { + if (temp.event_param2 < temp.event_param1) + error_db_log("TSCR: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + } + break; + + case EVENT_T_AGGRO: + case EVENT_T_DEATH: + case EVENT_T_EVADE: + case EVENT_T_SPAWNED: + { + if (temp.event_flags & EFLAG_REPEATABLE) + { + error_db_log("TSCR: Creature %u has EFLAG_REPEATABLE set. Event can never be repeatable. Removing flag for event %u.", temp.creature_id, i); + temp.event_flags &= ~EFLAG_REPEATABLE; + } + } + break; + } + + for (uint32 j = 0; j < MAX_ACTIONS; j++) + { + temp.action[j].type = fields[10+(j*4)].GetUInt16(); + temp.action[j].param1 = fields[11+(j*4)].GetUInt32(); + temp.action[j].param2 = fields[12+(j*4)].GetUInt32(); + temp.action[j].param3 = fields[13+(j*4)].GetUInt32(); + + //Report any errors in actions + switch (temp.action[j].type) + { + case ACTION_T_SAY: + case ACTION_T_YELL: + case ACTION_T_TEXTEMOTE: + if (GetEventAIText(temp.action[j].param1) == DEFAULT_TEXT) + error_db_log("TSCR: Event %u Action %u refrences missing Localized_Text entry", i, j+1); + break; + + case ACTION_T_SOUND: + if (!GetSoundEntriesStore()->LookupEntry(temp.action[j].param1)) + error_db_log("TSCR: Event %u Action %u uses non-existant SoundID %u.", i, j+1, temp.action[j].param1); + break; + + case ACTION_T_RANDOM_SAY: + case ACTION_T_RANDOM_YELL: + case ACTION_T_RANDOM_TEXTEMOTE: + if ((temp.action[j].param1 != 0xffffffff && GetEventAIText(temp.action[j].param1) == DEFAULT_TEXT) || + (temp.action[j].param2 != 0xffffffff && GetEventAIText(temp.action[j].param2) == DEFAULT_TEXT) || + (temp.action[j].param3 != 0xffffffff && GetEventAIText(temp.action[j].param3) == DEFAULT_TEXT)) + error_db_log("TSCR: Event %u Action %u refrences missing Localized_Text entry", i, j+1); + break; + + case ACTION_T_CAST: + { + if (!GetSpellStore()->LookupEntry(temp.action[j].param1)) + error_db_log("TSCR: Event %u Action %u uses non-existant SpellID %u.", i, j+1, temp.action[j].param1); + + if (temp.action[j].param2 >= TARGET_T_END) + error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); + } + break; + + case ACTION_T_REMOVEAURASFROMSPELL: + { + if (!GetSpellStore()->LookupEntry(temp.action[j].param2)) + error_db_log("TSCR: Event %u Action %u uses non-existant SpellID %u.", i, j+1, temp.action[j].param2); + + if (temp.action[j].param1 >= TARGET_T_END) + error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); + } + break; + + case ACTION_T_CASTCREATUREGO: + { + if (!GetSpellStore()->LookupEntry(temp.action[j].param2)) + error_db_log("TSCR: Event %u Action %u uses non-existant SpellID %u.", i, j+1, temp.action[j].param2); + + if (temp.action[j].param3 >= TARGET_T_END) + error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); + } + break; + + //2nd param target + case ACTION_T_SUMMON_ID: + { + if (EventAI_Summon_Map.find(temp.action[j].param3) == EventAI_Summon_Map.end()) + error_db_log("TSCR: Event %u Action %u summons missing EventAI_Summon %u", i, j+1, temp.action[j].param3); + + if (temp.action[j].param2 >= TARGET_T_END) + error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); + } + break; + + case ACTION_T_SUMMON: + case ACTION_T_THREAT_SINGLE_PCT: + case ACTION_T_QUEST_EVENT: + case ACTION_T_SET_UNIT_FLAG: + case ACTION_T_REMOVE_UNIT_FLAG: + case ACTION_T_SET_INST_DATA64: + if (temp.action[j].param2 >= TARGET_T_END) + error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); + break; + + //3rd param target + case ACTION_T_SET_UNIT_FIELD: + if (temp.action[j].param3 >= TARGET_T_END) + error_db_log("TSCR: Event %u Action %u uses incorrect Target type", i, j+1); + break; + + case ACTION_T_SET_PHASE: + if (temp.action[j].param1 > 31) + error_db_log("TSCR: Event %u Action %u attempts to set phase > 31. Phase mask cannot be used past phase 31.", i, j+1); + break; + + case ACTION_T_INC_PHASE: + if (!temp.action[j].param1) + error_db_log("TSCR: Event %u Action %u is incrementing phase by 0. Was this intended?", i, j+1); + break; + + case ACTION_T_KILLED_MONSTER: + if (temp.event_type != EVENT_T_DEATH) + outstring_log("SD2 WARNING: Event %u Action %u calling ACTION_T_KILLED_MONSTER outside of EVENT_T_DEATH", i, j+1); + break; + + case ACTION_T_SET_INST_DATA: + if (temp.action[j].param2 > 3) + error_db_log("TSCR: Event %u Action %u attempts to set instance data above encounter state 3. Custom case?", i, j+1); + break; + + default: + if (temp.action[j].type >= ACTION_T_END) + error_db_log("TSCR: Event %u Action %u has incorrect action type. Maybe DB requires updated version of SD2.", i, j+1); + break; + } + } + + //Add to list + EventAI_Event_List.push_back(temp); + ++Count; + } while (result->NextRow()); + + delete result; + + outstring_log(""); + outstring_log(">> Loaded %u EventAI scripts", Count); + }else + { + barGoLink bar(1); + bar.step(); + outstring_log(""); + outstring_log(">> Loaded 0 EventAI scripts. DB table `eventai_scripts` is empty."); + } + + //Free database thread and resources + TScriptDB.HaltDelayThread(); + +} + +struct TSpellSummary { + uint8 Targets; // set of enum SelectTarget + uint8 Effects; // set of enum SelectEffect +}extern *SpellSummary; + +MANGOS_DLL_EXPORT +void ScriptsFree() +{ + // Free Spell Summary + delete []SpellSummary; + + // Free resources before library unload + for(int i=0;i 8) + { + Locale = 0; + error_log("TSCR: Locale set to invalid language id. Defaulting to 0."); + } + + outstring_log("TSCR: Using locale %u", Locale); + + EAI_ErrorLevel = TScriptConfig.GetIntDefault("EAIErrorLevel", 1); + + switch (EAI_ErrorLevel) + { + case 0: + outstring_log("TSCR: EventAI Error Reporting level set to 0 (Startup Errors only)"); + break; + case 1: + outstring_log("TSCR: EventAI Error Reporting level set to 1 (Startup errors and Runtime event errors)"); + break; + case 2: + outstring_log("TSCR: EventAI Error Reporting level set to 2 (Startup errors, Runtime event errors, and Creation errors)"); + break; + default: + outstring_log("TSCR: Unknown EventAI Error Reporting level. Defaulting to 1 (Startup errors and Runtime event errors)"); + EAI_ErrorLevel = 1; + break; + } + + outstring_log(""); + + //Load database (must be called after TScriptConfig.SetSource). In case it failed, no need to even try load. + if (CanLoadDB) + LoadDatabase(); + + outstring_log("TSCR: Loading C++ scripts"); + barGoLink bar(1); + bar.step(); + outstring_log(""); + + nrscripts = 0; + for(int i=0;i::iterator i = EventAI_LocalizedTextMap.find(entry); + + if (i == EventAI_LocalizedTextMap.end()) + { + error_log("TSCR: EventAI Localized Text %u not found", entry); + return DEFAULT_TEXT; + } + + switch (Locale) + { + case 1: + temp = (*i).second.locale_1.c_str(); + break; + + case 2: + temp = (*i).second.locale_2.c_str(); + break; + + case 3: + temp = (*i).second.locale_3.c_str(); + break; + + case 4: + temp = (*i).second.locale_4.c_str(); + break; + + case 5: + temp = (*i).second.locale_5.c_str(); + break; + + case 6: + temp = (*i).second.locale_6.c_str(); + break; + + case 7: + temp = (*i).second.locale_7.c_str(); + break; + + case 8: + temp = (*i).second.locale_8.c_str(); + break; + }; + + if (strlen(temp)) + return temp; + + return DEFAULT_TEXT; +} + +const char* GetEventAIText(uint32 entry) +{ + if(entry == 0xffffffff) + error_log("TSCR: Entry = -1, GetEventAIText should not be called in this case."); + + const char* str = NULL; + + HM_NAMESPACE::hash_map::iterator itr = EventAI_Text_Map.find(entry); + if(itr == EventAI_Text_Map.end()) + { + error_log("TSCR: Unable to find EventAI Text %u", entry); + return DEFAULT_TEXT; + } + + str = (*itr).second.c_str(); + + if(strlen(str)) + return str; + + if(strlen((*itr).second.c_str())) + return (*itr).second.c_str(); + + return DEFAULT_TEXT; +} + +void DoScriptText(int32 textEntry, WorldObject* pSource, Unit* target) +{ + if (!pSource) + { + error_log("TSCR: DoScriptText entry %i, invalid Source pointer.",textEntry); + return; + } + + if (textEntry >= 0) + { + error_log("TSCR: DoScriptText attempts to process entry %i, but entry must be negative.",textEntry); + return; + } + + HM_NAMESPACE::hash_map::iterator i = TextMap.find(textEntry); + + if (i == TextMap.end()) + { + error_log("TSCR: DoScriptText could not find text entry %i.",textEntry); + return; + } + + if((*i).second.SoundId) + { + if( GetSoundEntriesStore()->LookupEntry((*i).second.SoundId) ) + { + pSource->SendPlaySound((*i).second.SoundId, false); + } + else + error_log("TSCR: DoScriptText entry %i tried to process invalid sound id %u.",textEntry,(*i).second.SoundId); + } + + switch((*i).second.Type) + { + case CHAT_TYPE_SAY: + pSource->MonsterSay(textEntry, (*i).second.Language, target ? target->GetGUID() : 0); + break; + case CHAT_TYPE_YELL: + pSource->MonsterYell(textEntry, (*i).second.Language, target ? target->GetGUID() : 0); + break; + case CHAT_TYPE_TEXT_EMOTE: + pSource->MonsterTextEmote(textEntry, target ? target->GetGUID() : 0); + break; + case CHAT_TYPE_BOSS_EMOTE: + pSource->MonsterTextEmote(textEntry, target ? target->GetGUID() : 0, true); + break; + case CHAT_TYPE_WHISPER: + { + if (target && target->GetTypeId() == TYPEID_PLAYER) + pSource->MonsterWhisper(textEntry, target->GetGUID()); + else error_log("TSCR: DoScriptText entry %i cannot whisper without target unit (TYPEID_PLAYER).", textEntry); + }break; + case CHAT_TYPE_BOSS_WHISPER: + { + if (target && target->GetTypeId() == TYPEID_PLAYER) + pSource->MonsterWhisper(textEntry, target->GetGUID(), true); + else error_log("TSCR: DoScriptText entry %i cannot whisper without target unit (TYPEID_PLAYER).", textEntry); + }break; + } +} + +Script* GetScriptByName(std::string Name) +{ + if(Name.empty()) + return NULL; + + for(int i=0;iName == Name ) + return m_scripts[i]; + } + return NULL; +} + +//******************************** +//*** Functions to be Exported *** + +MANGOS_DLL_EXPORT +bool GossipHello ( Player * player, Creature *_Creature ) +{ + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + if(!tmpscript || !tmpscript->pGossipHello) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pGossipHello(player,_Creature); +} + +MANGOS_DLL_EXPORT +bool GossipSelect( Player *player, Creature *_Creature, uint32 sender, uint32 action ) +{ + debug_log("TSCR: Gossip selection, sender: %d, action: %d",sender, action); + + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + if(!tmpscript || !tmpscript->pGossipSelect) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pGossipSelect(player,_Creature,sender,action); +} + +MANGOS_DLL_EXPORT +bool GossipSelectWithCode( Player *player, Creature *_Creature, uint32 sender, uint32 action, const char* sCode ) +{ + debug_log("TSCR: Gossip selection with code, sender: %d, action: %d",sender, action); + + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + if(!tmpscript || !tmpscript->pGossipSelectWithCode) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pGossipSelectWithCode(player,_Creature,sender,action,sCode); +} + +MANGOS_DLL_EXPORT +bool QuestAccept( Player *player, Creature *_Creature, Quest const *_Quest ) +{ + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + if(!tmpscript || !tmpscript->pQuestAccept) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pQuestAccept(player,_Creature,_Quest); +} + +MANGOS_DLL_EXPORT +bool QuestSelect( Player *player, Creature *_Creature, Quest const *_Quest ) +{ + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + if(!tmpscript || !tmpscript->pQuestSelect) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pQuestSelect(player,_Creature,_Quest); +} + +MANGOS_DLL_EXPORT +bool QuestComplete( Player *player, Creature *_Creature, Quest const *_Quest ) +{ + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + if(!tmpscript || !tmpscript->pQuestComplete) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pQuestComplete(player,_Creature,_Quest); +} + +MANGOS_DLL_EXPORT +bool ChooseReward( Player *player, Creature *_Creature, Quest const *_Quest, uint32 opt ) +{ + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + if(!tmpscript || !tmpscript->pChooseReward) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pChooseReward(player,_Creature,_Quest,opt); +} + +MANGOS_DLL_EXPORT +uint32 NPCDialogStatus( Player *player, Creature *_Creature ) +{ + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + if(!tmpscript || !tmpscript->pNPCDialogStatus) return 100; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pNPCDialogStatus(player,_Creature); +} + +MANGOS_DLL_EXPORT +uint32 GODialogStatus( Player *player, GameObject *_GO ) +{ + Script *tmpscript = GetScriptByName(_GO->GetGOInfo()->ScriptName); + if(!tmpscript || !tmpscript->pGODialogStatus) return 100; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pGODialogStatus(player,_GO); +} + +MANGOS_DLL_EXPORT +bool ItemHello( Player *player, Item *_Item, Quest const *_Quest ) +{ + Script *tmpscript = GetScriptByName(_Item->GetProto()->ScriptName); + if(!tmpscript || !tmpscript->pItemHello) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pItemHello(player,_Item,_Quest); +} + +MANGOS_DLL_EXPORT +bool ItemQuestAccept( Player *player, Item *_Item, Quest const *_Quest ) +{ + Script *tmpscript = GetScriptByName(_Item->GetProto()->ScriptName); + if(!tmpscript || !tmpscript->pItemQuestAccept) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pItemQuestAccept(player,_Item,_Quest); +} + +MANGOS_DLL_EXPORT +bool GOHello( Player *player, GameObject *_GO ) +{ + Script *tmpscript = GetScriptByName(_GO->GetGOInfo()->ScriptName); + if(!tmpscript || !tmpscript->pGOHello) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pGOHello(player,_GO); +} + +MANGOS_DLL_EXPORT +bool GOQuestAccept( Player *player, GameObject *_GO, Quest const *_Quest ) +{ + Script *tmpscript = GetScriptByName(_GO->GetGOInfo()->ScriptName); + if(!tmpscript || !tmpscript->pGOQuestAccept) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pGOQuestAccept(player,_GO,_Quest); +} + +MANGOS_DLL_EXPORT +bool GOChooseReward( Player *player, GameObject *_GO, Quest const *_Quest, uint32 opt ) +{ + Script *tmpscript = GetScriptByName(_GO->GetGOInfo()->ScriptName); + if(!tmpscript || !tmpscript->pGOChooseReward) return false; + + player->PlayerTalkClass->ClearMenus(); + return tmpscript->pGOChooseReward(player,_GO,_Quest,opt); +} + +MANGOS_DLL_EXPORT +bool AreaTrigger( Player *player, AreaTriggerEntry * atEntry) +{ + Script *tmpscript = NULL; + + tmpscript = GetScriptByName(GetAreaTriggerScriptNameById(atEntry->id)); + if(!tmpscript || !tmpscript->pAreaTrigger) return false; + + return tmpscript->pAreaTrigger(player, atEntry); +} + +MANGOS_DLL_EXPORT +CreatureAI* GetAI(Creature *_Creature) +{ + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + + if(!tmpscript || !tmpscript->GetAI) return NULL; + return tmpscript->GetAI(_Creature); +} + +MANGOS_DLL_EXPORT +bool ItemUse( Player *player, Item* _Item, SpellCastTargets const& targets) +{ + Script *tmpscript = GetScriptByName(_Item->GetProto()->ScriptName); + if(!tmpscript || !tmpscript->pItemUse) return false; + + return tmpscript->pItemUse(player,_Item,targets); +} + +MANGOS_DLL_EXPORT +bool ReceiveEmote( Player *player, Creature *_Creature, uint32 emote ) +{ + Script *tmpscript = GetScriptByName(_Creature->GetScriptName()); + if(!tmpscript || !tmpscript->pReceiveEmote) return false; + + return tmpscript->pReceiveEmote(player, _Creature, emote); +} + +MANGOS_DLL_EXPORT +InstanceData* CreateInstanceData(Map *map) +{ + Script *tmpscript = NULL; + + if(!map->IsDungeon()) return false; + + tmpscript = GetScriptByName(((InstanceMap*)map)->GetScript()); + if(!tmpscript || !tmpscript->GetInstanceData) return false; + + return tmpscript->GetInstanceData(map); +} diff --git a/src/bindings/scripts/ScriptMgr.h b/src/bindings/scripts/ScriptMgr.h index a0f541e23b0..8ff1ee3cfdd 100644 --- a/src/bindings/scripts/ScriptMgr.h +++ b/src/bindings/scripts/ScriptMgr.h @@ -1,94 +1,88 @@ -/* Copyright (C) 2006 - 2008 ScriptDev2 -* This program is free software licensed under GPL version 2 -* Please see the included DOCS/LICENSE.TXT for more information */ - -#ifndef SCRIPTMGR_H -#define SCRIPTMGR_H - -#include "Common.h" -#include "Platform/CompilerDefs.h" -#include "Database/DBCStructure.h" - -class Player; -class Creature; -class CreatureAI; -class InstanceData; -class Quest; -class Item; -class GameObject; -class SpellCastTargets; -class Map; -class Unit; -class WorldObject; - -#define MAX_SCRIPTS 1000 //72 bytes each (approx 71kb) - -//MAX visible range (size of grid) -#define VISIBLE_RANGE (166.0f) - -#define DEFAULT_TEXT "" - -// -struct Script -{ - Script() : -pGossipHello(NULL), pQuestAccept(NULL), pGossipSelect(NULL), pGossipSelectWithCode(NULL), -pQuestSelect(NULL), pQuestComplete(NULL), pNPCDialogStatus(NULL), pGODialogStatus(NULL), pChooseReward(NULL), -pItemHello(NULL), pGOHello(NULL), pAreaTrigger(NULL), pItemQuestAccept(NULL), pGOQuestAccept(NULL), -pGOChooseReward(NULL),pReceiveEmote(NULL),pItemUse(NULL), GetAI(NULL), GetInstanceData(NULL) -{} - -std::string Name; - -// -- Quest/gossip Methods to be scripted -- -bool (*pGossipHello )(Player*, Creature*); -bool (*pQuestAccept )(Player*, Creature*, Quest const* ); -bool (*pGossipSelect )(Player*, Creature*, uint32 , uint32 ); -bool (*pGossipSelectWithCode)(Player*, Creature*, uint32 , uint32 , const char* ); -bool (*pQuestSelect )(Player*, Creature*, Quest const* ); -bool (*pQuestComplete )(Player*, Creature*, Quest const* ); -uint32 (*pNPCDialogStatus )(Player*, Creature* ); -uint32 (*pGODialogStatus )(Player *player, GameObject * _GO ); -bool (*pChooseReward )(Player*, Creature*, Quest const*, uint32 ); -bool (*pItemHello )(Player*, Item*, Quest const* ); -bool (*pGOHello )(Player*, GameObject* ); -bool (*pAreaTrigger )(Player*, AreaTriggerEntry* ); -bool (*pItemQuestAccept )(Player*, Item *, Quest const* ); -bool (*pGOQuestAccept )(Player*, GameObject*, Quest const* ); -bool (*pGOChooseReward )(Player*, GameObject*_GO, Quest const*, uint32 ); -bool (*pReceiveEmote )(Player*, Creature*, uint32 ); -bool (*pItemUse )(Player*, Item*, SpellCastTargets const& ); - -CreatureAI* (*GetAI)(Creature*); -InstanceData* (*GetInstanceData)(Map*); -// ----------------------------------------- -}; - -extern int nrscripts; -extern Script *m_scripts[MAX_SCRIPTS]; - -// Localized Text function -const char* GetEventAILocalizedText(uint32 entry); -const char* GetScriptLocalizedText(uint32 entry); - -//EventAI text function -const char* GetEventAIText(uint32 entry); // TODO: Locales - -// Script Text function -void ProcessScriptText(uint32 id, WorldObject* pSource, Unit* target = NULL); // TODO: Locales - -#if COMPILER == COMPILER_GNU -#define FUNC_PTR(name,callconvention,returntype,parameters) typedef returntype(*name)parameters __attribute__ ((callconvention)); -#else -#define FUNC_PTR(name, callconvention, returntype, parameters) typedef returntype(callconvention *name)parameters; -#endif - -#ifdef WIN32 - #define MANGOS_DLL_EXPORT extern "C" __declspec(dllexport) -#elif defined( __GNUC__ ) - #define MANGOS_DLL_EXPORT extern "C" -#else - #define MANGOS_DLL_EXPORT extern "C" export -#endif - -#endif +/* Copyright (C) 2006 - 2008 ScriptDev2 + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SCRIPTMGR_H +#define SCRIPTMGR_H + +#include "Common.h" +#include "Platform/CompilerDefs.h" +#include "Database/DBCStructure.h" + +class Player; +class Creature; +class CreatureAI; +class InstanceData; +class Quest; +class Item; +class GameObject; +class SpellCastTargets; +class Map; +class Unit; +class WorldObject; + +#define MAX_SCRIPTS 1000 //72 bytes each (approx 71kb) +#define VISIBLE_RANGE (166.0f) //MAX visible range (size of grid) +#define DEFAULT_TEXT "" + +struct Script +{ + Script() : +pGossipHello(NULL), pQuestAccept(NULL), pGossipSelect(NULL), pGossipSelectWithCode(NULL), +pQuestSelect(NULL), pQuestComplete(NULL), pNPCDialogStatus(NULL), pGODialogStatus(NULL), pChooseReward(NULL), +pItemHello(NULL), pGOHello(NULL), pAreaTrigger(NULL), pItemQuestAccept(NULL), pGOQuestAccept(NULL), +pGOChooseReward(NULL),pReceiveEmote(NULL),pItemUse(NULL), GetAI(NULL), GetInstanceData(NULL) +{} + +std::string Name; + +// Quest/gossip Methods to be scripted +bool (*pGossipHello )(Player*, Creature*); +bool (*pQuestAccept )(Player*, Creature*, Quest const* ); +bool (*pGossipSelect )(Player*, Creature*, uint32 , uint32 ); +bool (*pGossipSelectWithCode)(Player*, Creature*, uint32 , uint32 , const char* ); +bool (*pQuestSelect )(Player*, Creature*, Quest const* ); +bool (*pQuestComplete )(Player*, Creature*, Quest const* ); +uint32 (*pNPCDialogStatus )(Player*, Creature* ); +uint32 (*pGODialogStatus )(Player *player, GameObject * _GO ); +bool (*pChooseReward )(Player*, Creature*, Quest const*, uint32 ); +bool (*pItemHello )(Player*, Item*, Quest const* ); +bool (*pGOHello )(Player*, GameObject* ); +bool (*pAreaTrigger )(Player*, AreaTriggerEntry* ); +bool (*pItemQuestAccept )(Player*, Item *, Quest const* ); +bool (*pGOQuestAccept )(Player*, GameObject*, Quest const* ); +bool (*pGOChooseReward )(Player*, GameObject*_GO, Quest const*, uint32 ); +bool (*pReceiveEmote )(Player*, Creature*, uint32 ); +bool (*pItemUse )(Player*, Item*, SpellCastTargets const& ); + +CreatureAI* (*GetAI)(Creature*); +InstanceData* (*GetInstanceData)(Map*); +}; + +extern int nrscripts; +extern Script *m_scripts[MAX_SCRIPTS]; + +// Localized Text function +const char* GetEventAILocalizedText(uint32 entry); + +//EventAI text function +const char* GetEventAIText(uint32 entry); // TODO: Locales + +//Generic scripting text function +void DoScriptText(int32 textEntry, WorldObject* pSource, Unit* target = NULL); + +#if COMPILER == COMPILER_GNU +#define FUNC_PTR(name,callconvention,returntype,parameters) typedef returntype(*name)parameters __attribute__ ((callconvention)); +#else +#define FUNC_PTR(name, callconvention, returntype, parameters) typedef returntype(callconvention *name)parameters; +#endif + +#ifdef WIN32 + #define MANGOS_DLL_EXPORT extern "C" __declspec(dllexport) +#elif defined( __GNUC__ ) + #define MANGOS_DLL_EXPORT extern "C" +#else + #define MANGOS_DLL_EXPORT extern "C" export +#endif + +#endif diff --git a/src/bindings/scripts/scripts/zone/karazhan/boss_prince_malchezaar.cpp b/src/bindings/scripts/scripts/zone/karazhan/boss_prince_malchezaar.cpp index 757745d7a15..40111e7c35c 100644 --- a/src/bindings/scripts/scripts/zone/karazhan/boss_prince_malchezaar.cpp +++ b/src/bindings/scripts/scripts/zone/karazhan/boss_prince_malchezaar.cpp @@ -399,7 +399,7 @@ struct MANGOS_DLL_DECL boss_malchezaarAI : public ScriptedAI EnfeebleResetTimer=0; }else EnfeebleResetTimer -= diff; - if(m_creature->hasUnitState(UNIT_STAT_STUNDED)) //While shifting to phase 2 malchezaar stuns himself + if(m_creature->hasUnitState(UNIT_STAT_STUNNED)) //While shifting to phase 2 malchezaar stuns himself return; if(m_creature->GetUInt64Value(UNIT_FIELD_TARGET)!=m_creature->getVictim()->GetGUID()) diff --git a/src/bindings/scripts/scripts/zone/magisters_terrace/boss_priestess_delrissa.cpp b/src/bindings/scripts/scripts/zone/magisters_terrace/boss_priestess_delrissa.cpp index a37069542cc..531fe4077aa 100644 --- a/src/bindings/scripts/scripts/zone/magisters_terrace/boss_priestess_delrissa.cpp +++ b/src/bindings/scripts/scripts/zone/magisters_terrace/boss_priestess_delrissa.cpp @@ -1002,7 +1002,7 @@ struct MANGOS_DLL_DECL boss_garaxxasAI : public boss_priestess_guestAI Freezing_Trap_Timer = 30000; }else Freezing_Trap_Timer -= diff; - if(!m_creature->getVictim()->hasUnitState(UNIT_STAT_STUNDED | UNIT_STAT_ROOT | UNIT_STAT_CONFUSED | UNIT_STAT_DISTRACTED)) + if(!m_creature->getVictim()->hasUnitState(UNIT_STAT_STUNNED | UNIT_STAT_ROOT | UNIT_STAT_CONFUSED | UNIT_STAT_DISTRACTED)) DoMeleeAttackIfReady(); }else { diff --git a/src/bindings/scripts/scripts/zone/magisters_terrace/boss_selin_fireheart.cpp b/src/bindings/scripts/scripts/zone/magisters_terrace/boss_selin_fireheart.cpp index 6f1da25f220..96f8a52ff4f 100644 --- a/src/bindings/scripts/scripts/zone/magisters_terrace/boss_selin_fireheart.cpp +++ b/src/bindings/scripts/scripts/zone/magisters_terrace/boss_selin_fireheart.cpp @@ -16,7 +16,7 @@ /* ScriptData SDName: Boss_Selin_Fireheart -SD%Complete: 99 +SD%Complete: 90 SDComment: Heroic and Normal Support. Needs further testing. SDCategory: Magister's Terrace EndScriptData */ @@ -73,7 +73,7 @@ struct MANGOS_DLL_DECL boss_selin_fireheartAI : public ScriptedAI for(uint8 i = 0; i < size; ++i) { uint64 guid = pInstance->GetData64(DATA_FEL_CRYSTAL); - outstring_log("Selin: Adding Fel Crystal %u to list", guid); + debug_log("SD2: Selin: Adding Fel Crystal %u to list", guid); Crystals.push_back(guid); } } @@ -116,9 +116,9 @@ struct MANGOS_DLL_DECL boss_selin_fireheartAI : public ScriptedAI } GameObject* Door = GameObject::GetGameObject(*m_creature, pInstance->GetData64(DATA_SELIN_ENCOUNTER_DOOR)); - if(Door) - Door->SetGoState(0); // Close the door. Open it only in JustDied. - + if( Door ) + Door->SetGoState(0); // Open the big encounter door. Close it in Aggro and open it only in JustDied(and here) + // Small door opened after event are expected to be closed by default // Set Inst data for encounter pInstance->SetData(DATA_SELIN_EVENT, NOT_STARTED); }else error_log(ERROR_INST_DATA); @@ -160,10 +160,11 @@ struct MANGOS_DLL_DECL boss_selin_fireheartAI : public ScriptedAI } } } - if(CrystalChosen) + if( CrystalChosen ) { DoYell(SAY_ENERGY, LANG_UNIVERSAL, NULL); DoPlaySoundToSet(m_creature, SOUND_ENERGY); + CrystalChosen->CastSpell(CrystalChosen, SPELL_FEL_CRYSTAL_COSMETIC, true); float x, y, z; // coords that we move to, close to the crystal. @@ -185,7 +186,7 @@ struct MANGOS_DLL_DECL boss_selin_fireheartAI : public ScriptedAI { //Creature* pCrystal = ((Creature*)Unit::GetUnit(*m_creature, FelCrystals[i])); Creature* pCrystal = ((Creature*)Unit::GetUnit(*m_creature, *itr)); - if(pCrystal && pCrystal->isAlive()) + if( pCrystal && pCrystal->isAlive()) pCrystal->DealDamage(pCrystal, pCrystal->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); } } @@ -194,6 +195,13 @@ struct MANGOS_DLL_DECL boss_selin_fireheartAI : public ScriptedAI { DoYell(SAY_AGGRO, LANG_UNIVERSAL, NULL); DoPlaySoundToSet(m_creature, SOUND_AGGRO); + + if( pInstance ) + { + GameObject* EncounterDoor = GameObject::GetGameObject(*m_creature, pInstance->GetData64(DATA_SELIN_ENCOUNTER_DOOR)); + if( EncounterDoor ) + EncounterDoor->SetGoState(1); //Close the encounter door, open it in JustDied/Reset + } } void KilledUnit(Unit* victim) @@ -245,9 +253,14 @@ struct MANGOS_DLL_DECL boss_selin_fireheartAI : public ScriptedAI } pInstance->SetData(DATA_SELIN_EVENT, DONE); // Encounter complete! + GameObject* EncounterDoor = GameObject::GetGameObject((*m_creature), pInstance->GetData64(DATA_SELIN_ENCOUNTER_DOOR)); - if(EncounterDoor) - EncounterDoor->SetGoState(1); // Open the door + if( EncounterDoor ) + EncounterDoor->SetGoState(0); // Open the encounter door + + GameObject* ContinueDoor = GameObject::GetGameObject(*m_creature, pInstance->GetData64(DATA_SELIN_DOOR)); + if( ContinueDoor ) + ContinueDoor->SetGoState(0); // Open the door leading further in ShatterRemainingCrystals(); } @@ -302,23 +315,27 @@ struct MANGOS_DLL_DECL boss_selin_fireheartAI : public ScriptedAI }else { - if(IsDraining) - if(EmpowerTimer < diff) + if( IsDraining ) { - IsDraining = false; - DrainingCrystal = false; - DoYell(SAY_EMPOWERED, LANG_UNIVERSAL, NULL); - DoPlaySoundToSet(m_creature, SOUND_EMPOWERED); - Unit* CrystalChosen = Unit::GetUnit(*m_creature, CrystalGUID); - if(CrystalChosen && CrystalChosen->isAlive()) - // Use Deal Damage to kill it, not setDeathState. - CrystalChosen->DealDamage(CrystalChosen, CrystalChosen->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); + if( EmpowerTimer < diff ) + { + IsDraining = false; + DrainingCrystal = false; - CrystalGUID = 0; + DoYell(SAY_EMPOWERED, LANG_UNIVERSAL, NULL); + DoPlaySoundToSet(m_creature, SOUND_EMPOWERED); - m_creature->GetMotionMaster()->Clear(); - m_creature->GetMotionMaster()->MoveChase(m_creature->getVictim()); - }else EmpowerTimer -= diff; + Unit* CrystalChosen = Unit::GetUnit(*m_creature, CrystalGUID); + if( CrystalChosen && CrystalChosen->isAlive() ) + // Use Deal Damage to kill it, not setDeathState. + CrystalChosen->DealDamage(CrystalChosen, CrystalChosen->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); + + CrystalGUID = 0; + + m_creature->GetMotionMaster()->Clear(); + m_creature->GetMotionMaster()->MoveChase(m_creature->getVictim()); + }else EmpowerTimer -= diff; + } } DoMeleeAttackIfReady(); // No need to check if we are draining crystal here, as the spell has a stun. diff --git a/src/bindings/scripts/scripts/zone/magisters_terrace/instance_magisters_terrace.cpp b/src/bindings/scripts/scripts/zone/magisters_terrace/instance_magisters_terrace.cpp index b0d3e90f645..49303a06a2a 100644 --- a/src/bindings/scripts/scripts/zone/magisters_terrace/instance_magisters_terrace.cpp +++ b/src/bindings/scripts/scripts/zone/magisters_terrace/instance_magisters_terrace.cpp @@ -16,7 +16,7 @@ /* ScriptData SDName: Instance_Magisters_Terrace -SD%Complete: 100 +SD%Complete: 60 SDComment: Designed only for Selin Fireheart SDCategory: Magister's Terrace EndScriptData */ @@ -118,15 +118,24 @@ struct MANGOS_DLL_DECL instance_magisters_terrace : public ScriptedInstance { switch(entry) { - case 24723: - SelinGUID = creature->GetGUID(); - break; - case 24560: - DelrissaGUID = creature->GetGUID(); - break; - case 24722: - FelCrystals.push_back(creature->GetGUID()); - break; + case 24723: SelinGUID = creature->GetGUID(); break; + case 24560: DelrissaGUID = creature->GetGUID(); break; + case 24722: FelCrystals.push_back(creature->GetGUID()); break; + } + } + + void OnObjectCreate(GameObject* go) + { + switch(go->GetEntry()) + { + case 187896: VexallusDoorGUID = go->GetGUID(); break; + //SunwellRaid Gate 02 + case 187979: SelinDoorGUID = go->GetGUID(); break; + //Assembly Chamber Door + case 188065: SelinEncounterDoorGUID = go->GetGUID(); break; + case 187770: DelrissaDoorGUID = go->GetGUID(); break; + case 188165: KaelStatue[0] = go->GetGUID(); break; + case 188166: KaelStatue[1] = go->GetGUID(); break; } } @@ -164,19 +173,6 @@ struct MANGOS_DLL_DECL instance_magisters_terrace : public ScriptedInstance } return 0; } - - void OnObjectCreate(GameObject* go) - { - switch(go->GetEntry()) - { - case 187896: VexallusDoorGUID = go->GetGUID(); break; - case 187979: SelinDoorGUID = go->GetGUID(); break; - case 188118: SelinEncounterDoorGUID = go->GetGUID(); break; - case 187770: DelrissaDoorGUID = go->GetGUID(); break; - case 188165: KaelStatue[0] = go->GetGUID(); break; - case 188166: KaelStatue[1] = go->GetGUID(); break; - } - } }; InstanceData* GetInstanceData_instance_magisters_terrace(Map* map) diff --git a/src/bindings/scripts/scripts/zone/temple_of_ahnqiraj/boss_twinemperors.cpp b/src/bindings/scripts/scripts/zone/temple_of_ahnqiraj/boss_twinemperors.cpp index e7bd93f5461..b2565f0e79a 100644 --- a/src/bindings/scripts/scripts/zone/temple_of_ahnqiraj/boss_twinemperors.cpp +++ b/src/bindings/scripts/scripts/zone/temple_of_ahnqiraj/boss_twinemperors.cpp @@ -87,7 +87,7 @@ struct MANGOS_DLL_DECL boss_twinemperorsAI : public ScriptedAI AfterTeleportTimer = 0; Abuse_Bug_Timer = 10000 + rand()%7000; BugsTimer = 2000; - m_creature->clearUnitState(UNIT_STAT_STUNDED); + m_creature->clearUnitState(UNIT_STAT_STUNNED); DontYellWhenDead = false; EnrageTimer = 15*60000; } @@ -290,7 +290,7 @@ struct MANGOS_DLL_DECL boss_twinemperorsAI : public ScriptedAI DoStopAttack(); DoResetThreat(); DoCast(m_creature, SPELL_TWIN_TELEPORT_VISUAL); - m_creature->addUnitState(UNIT_STAT_STUNDED); + m_creature->addUnitState(UNIT_STAT_STUNNED); AfterTeleport = true; AfterTeleportTimer = 2000; tspellcasted = false; @@ -302,9 +302,9 @@ struct MANGOS_DLL_DECL boss_twinemperorsAI : public ScriptedAI { if (!tspellcasted) { - m_creature->clearUnitState(UNIT_STAT_STUNDED); + m_creature->clearUnitState(UNIT_STAT_STUNNED); DoCast(m_creature, SPELL_TWIN_TELEPORT); - m_creature->addUnitState(UNIT_STAT_STUNDED); + m_creature->addUnitState(UNIT_STAT_STUNNED); } tspellcasted = true; @@ -312,7 +312,7 @@ struct MANGOS_DLL_DECL boss_twinemperorsAI : public ScriptedAI if (AfterTeleportTimer < diff) { AfterTeleport = false; - m_creature->clearUnitState(UNIT_STAT_STUNDED); + m_creature->clearUnitState(UNIT_STAT_STUNNED); Unit *nearu = PickNearestPlayer(); //DoYell(nearu->GetName(), LANG_UNIVERSAL, 0); AttackStart(nearu); diff --git a/src/bindings/scripts/sql/scriptdev2_structure.sql b/src/bindings/scripts/sql/scriptdev2_structure.sql index 66ae5ac6cbf..122deb53357 100644 --- a/src/bindings/scripts/sql/scriptdev2_structure.sql +++ b/src/bindings/scripts/sql/scriptdev2_structure.sql @@ -73,31 +73,41 @@ PRIMARY KEY (`id`) DROP TABLE IF EXISTS `script_texts`; CREATE TABLE `script_texts` ( -`id` int(11) unsigned NOT NULL auto_increment COMMENT 'Identifier', -`sound` int(11) unsigned NOT NULL default '0', -`type` int(11) unsigned NOT NULL default '0', -`language` int(11) unsigned NOT NULL default '0', -`text` varchar(255) NOT NULL default '', -`comment` varchar(255) NOT NULL default '', -PRIMARY KEY (`id`) + `entry` mediumint(8) NOT NULL, + `content_default` text NOT NULL, + `content_loc1` text, + `content_loc2` text, + `content_loc3` text, + `content_loc4` text, + `content_loc5` text, + `content_loc6` text, + `content_loc7` text, + `content_loc8` text, + `sound` mediumint(8) unsigned NOT NULL default '0', + `type` tinyint unsigned NOT NULL default '0', + `language` tinyint unsigned NOT NULL default '0', + `comment` text, + PRIMARY KEY (`entry`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Script Texts'; - -DROP TABLE IF EXISTS `script_localized_texts`; -CREATE TABLE `script_localized_texts` ( -`id` int(11) unsigned NOT NULL auto_increment COMMENT 'Identifier', -`locale_1` varchar(255) NOT NULL default '', -`locale_2` varchar(255) NOT NULL default '', -`locale_3` varchar(255) NOT NULL default '', -`locale_4` varchar(255) NOT NULL default '', -`locale_5` varchar(255) NOT NULL default '', -`locale_6` varchar(255) NOT NULL default '', -`locale_7` varchar(255) NOT NULL default '', -`locale_8` varchar(255) NOT NULL default '', -`comment` varchar(255) NOT NULL default '' COMMENT 'Text Comment', -PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Localized Script Text'; - +DROP TABLE IF EXISTS `custom_texts`; +CREATE TABLE `custom_texts` ( + `entry` mediumint(8) NOT NULL, + `content_default` text NOT NULL, + `content_loc1` text, + `content_loc2` text, + `content_loc3` text, + `content_loc4` text, + `content_loc5` text, + `content_loc6` text, + `content_loc7` text, + `content_loc8` text, + `sound` mediumint(8) unsigned NOT NULL default '0', + `type` tinyint unsigned NOT NULL default '0', + `language` tinyint unsigned NOT NULL default '0', + `comment` text, + PRIMARY KEY (`entry`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Custom Texts'; DROP TABLE IF EXISTS `script_db_version`; CREATE TABLE `script_db_version` ( diff --git a/src/game/AggressorAI.cpp b/src/game/AggressorAI.cpp index a4ef9e39f47..58604183660 100644 --- a/src/game/AggressorAI.cpp +++ b/src/game/AggressorAI.cpp @@ -47,7 +47,7 @@ AggressorAI::MoveInLineOfSight(Unit *u) if( !i_creature.canFly() && i_creature.GetDistanceZ(u) > CREATURE_Z_ATTACK_RANGE ) return; - if( !i_creature.getVictim() && !i_creature.hasUnitState(UNIT_STAT_STUNDED) && u->isTargetableForAttack() && + if( !i_creature.getVictim() && !i_creature.hasUnitState(UNIT_STAT_STUNNED) && u->isTargetableForAttack() && ( i_creature.IsHostileTo( u ) /*|| u->getVictim() && i_creature.IsFriendlyTo( u->getVictim() )*/ ) && u->isInAccessablePlaceFor(&i_creature) ) { diff --git a/src/game/ArenaTeamHandler.cpp b/src/game/ArenaTeamHandler.cpp index 33786e93532..58293cb96cb 100644 --- a/src/game/ArenaTeamHandler.cpp +++ b/src/game/ArenaTeamHandler.cpp @@ -1,462 +1,463 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "WorldSession.h" -#include "WorldPacket.h" -#include "Log.h" -#include "Database/DatabaseEnv.h" -#include "Player.h" -#include "ObjectMgr.h" -#include "ArenaTeam.h" -#include "World.h" -#include "SocialMgr.h" - -void WorldSession::HandleInspectArenaStatsOpcode(WorldPacket & recv_data) -{ - sLog.outDebug("MSG_INSPECT_ARENA_TEAMS"); - //recv_data.hexlike(); - - CHECK_PACKET_SIZE(recv_data, 8); - - uint64 guid; - recv_data >> guid; - sLog.outDebug("Inspect Arena stats " I64FMTD, guid); - - if(Player *plr = objmgr.GetPlayer(guid)) - { - for (uint8 i = 0; i < MAX_ARENA_SLOT; i++) - { - if(uint32 a_id = plr->GetArenaTeamId(i)) - { - if(ArenaTeam *at = objmgr.GetArenaTeamById(a_id)) - at->InspectStats(this, plr->GetGUID()); - } - } - } -} - -void WorldSession::HandleArenaTeamQueryOpcode(WorldPacket & recv_data) -{ - sLog.outDebug( "WORLD: Received CMSG_ARENA_TEAM_QUERY" ); - //recv_data.hexlike(); - - CHECK_PACKET_SIZE(recv_data, 4); - - uint32 ArenaTeamId; - recv_data >> ArenaTeamId; - - ArenaTeam *arenateam = objmgr.GetArenaTeamById(ArenaTeamId); - if(!arenateam) // arena team not found - return; - - arenateam->Query(this); - arenateam->Stats(this); -} - -void WorldSession::HandleArenaTeamRosterOpcode(WorldPacket & recv_data) -{ - sLog.outDebug( "WORLD: Received CMSG_ARENA_TEAM_ROSTER" ); - //recv_data.hexlike(); - - CHECK_PACKET_SIZE(recv_data, 4); - - uint32 ArenaTeamId; // arena team id - recv_data >> ArenaTeamId; - - ArenaTeam *arenateam = objmgr.GetArenaTeamById(ArenaTeamId); - if(!arenateam) - return; - - arenateam->Roster(this); -} - -void WorldSession::HandleArenaTeamAddMemberOpcode(WorldPacket & recv_data) -{ - sLog.outDebug("CMSG_ARENA_TEAM_ADD_MEMBER"); - //recv_data.hexlike(); - - CHECK_PACKET_SIZE(recv_data, 4+1); - - uint32 ArenaTeamId; // arena team id - std::string Invitedname; - - Player * player = NULL; - - recv_data >> ArenaTeamId >> Invitedname; - - if(!Invitedname.empty()) - { - if(!normalizePlayerName(Invitedname)) - return; - - player = ObjectAccessor::Instance().FindPlayerByName(Invitedname.c_str()); - } - - if(!player) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", Invitedname, ERR_ARENA_TEAM_PLAYER_NOT_FOUND_S); - return; - } - - if(player->getLevel() < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) - { - //SendArenaTeamCommandResult(ARENA_TEAM_INVITE_SS,"",Invitedname,ARENA_TEAM_PLAYER_NOT_FOUND_S); - // can't find related opcode - SendNotification("%s is not high enough level to join your team", player->GetName()); - return; - } - - ArenaTeam *arenateam = objmgr.GetArenaTeamById(ArenaTeamId); - if(!arenateam) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PLAYER_NOT_IN_TEAM); - return; - } - - // OK result but not send invite - if(player->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow())) - return; - - if (!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && player->GetTeam() != GetPlayer()->GetTeam()) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", "", ERR_ARENA_TEAM_NOT_ALLIED); - return; - } - - if(player->GetArenaTeamId(arenateam->GetSlot())) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, player->GetName(), "", ERR_ALREADY_IN_ARENA_TEAM_S); - return; - } - - if(player->GetArenaTeamIdInvited()) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, player->GetName(), "", ERR_ALREADY_INVITED_TO_ARENA_TEAM_S); - return; - } - - if(arenateam->GetMembersSize() >= arenateam->GetType() * 2) - { - // should send an "arena team is full" or the likes message, I just don't know the proper values so... ERR_INTERNAL -// SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", "", ERR_ARENA_TEAM_INTERNAL); - SendNotification("Your arena team is full, %s cannot join it.", player->GetName()); - return; - } - - sLog.outDebug("Player %s Invited %s to Join his ArenaTeam", GetPlayer()->GetName(), Invitedname.c_str()); - - player->SetArenaTeamIdInvited(arenateam->GetId()); - - WorldPacket data(SMSG_ARENA_TEAM_INVITE, (8+10)); - data << GetPlayer()->GetName(); - data << arenateam->GetName(); - player->GetSession()->SendPacket(&data); - - sLog.outDebug("WORLD: Sent SMSG_ARENA_TEAM_INVITE"); -} - -void WorldSession::HandleArenaTeamInviteAcceptOpcode(WorldPacket & /*recv_data*/) -{ - sLog.outDebug("CMSG_ARENA_TEAM_INVITE_ACCEPT"); // empty opcode - - ArenaTeam *at = objmgr.GetArenaTeamById(_player->GetArenaTeamIdInvited()); - if(!at) - { - // arena team not exist - return; - } - - if(_player->GetArenaTeamId(at->GetSlot())) - { - // already in arena team that size - return; - } - - // not let enemies sign petition - if (!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && _player->GetTeam() != objmgr.GetPlayerTeamByGUID(at->GetCaptain())) - return; - - if(!at->AddMember(_player->GetGUID())) - return; - - // event - WorldPacket data; - BuildArenaTeamEventPacket(&data, ERR_ARENA_TEAM_JOIN_SS, 2, _player->GetName(), at->GetName(), ""); - at->BroadcastPacket(&data); -} - -void WorldSession::HandleArenaTeamInviteDeclineOpcode(WorldPacket & /*recv_data*/) -{ - sLog.outDebug("CMSG_ARENA_TEAM_INVITE_DECLINE"); // empty opcode - - _player->SetArenaTeamIdInvited(0); // no more invited -} - -void WorldSession::HandleArenaTeamLeaveOpcode(WorldPacket & recv_data) -{ - sLog.outDebug("CMSG_ARENA_TEAM_LEAVE"); - //recv_data.hexlike(); - - CHECK_PACKET_SIZE(recv_data, 4); - - uint32 ArenaTeamId; // arena team id - recv_data >> ArenaTeamId; - - ArenaTeam *at = objmgr.GetArenaTeamById(ArenaTeamId); - if(!at) - { - // send command result - return; - } - if(_player->GetGUID() == at->GetCaptain() && at->GetMembersSize() > 1) - { - // check for correctness - SendArenaTeamCommandResult(ERR_ARENA_TEAM_QUIT_S, "", "", ERR_ARENA_TEAM_LEADER_LEAVE_S); - return; - } - // arena team has only one member (=captain) - if(_player->GetGUID() == at->GetCaptain()) - { - at->Disband(this); - delete at; - return; - } - - at->DelMember(_player->GetGUID()); - - // event - WorldPacket data; - BuildArenaTeamEventPacket(&data, ERR_ARENA_TEAM_LEAVE_SS, 2, _player->GetName(), at->GetName(), ""); - at->BroadcastPacket(&data); - - //SendArenaTeamCommandResult(ERR_ARENA_TEAM_QUIT_S, at->GetName(), "", 0); -} - -void WorldSession::HandleArenaTeamDisbandOpcode(WorldPacket & recv_data) -{ - sLog.outDebug("CMSG_ARENA_TEAM_DISBAND"); - //recv_data.hexlike(); - - CHECK_PACKET_SIZE(recv_data, 4); - - uint32 ArenaTeamId; // arena team id - recv_data >> ArenaTeamId; - - ArenaTeam *at = objmgr.GetArenaTeamById(ArenaTeamId); - if(!at) - { - // arena team not found - return; - } - - if(at->GetCaptain() != _player->GetGUID()) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PERMISSIONS); - return; - } - - at->Disband(this); - delete at; -} - -void WorldSession::HandleArenaTeamRemoveFromTeamOpcode(WorldPacket & recv_data) -{ - sLog.outDebug("CMSG_ARENA_TEAM_REMOVE_FROM_TEAM"); - //recv_data.hexlike(); - - CHECK_PACKET_SIZE(recv_data, 4+1); - - uint32 ArenaTeamId; - std::string name; - - recv_data >> ArenaTeamId; - recv_data >> name; - - ArenaTeam *at = objmgr.GetArenaTeamById(ArenaTeamId); - if(!at) - { - // arena team not found - return; - } - - uint64 guid = objmgr.GetPlayerGUIDByName(name); - if(!guid) - { - // player guid not found - return; - } - - if(at->GetCaptain() == guid) - { - // unsure - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PERMISSIONS); - return; - } - - if(at->GetCaptain() != _player->GetGUID()) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PERMISSIONS); - return; - } - - if(at->GetCaptain() == guid) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_LEADER_LEAVE_S); - return; - } - - at->DelMember(guid); - - // event - WorldPacket data; - BuildArenaTeamEventPacket(&data, ERR_ARENA_TEAM_REMOVE_SSS, 3, name, at->GetName(), _player->GetName()); - at->BroadcastPacket(&data); -} - -void WorldSession::HandleArenaTeamPromoteToCaptainOpcode(WorldPacket & recv_data) -{ - sLog.outDebug("CMSG_ARENA_TEAM_PROMOTE_TO_CAPTAIN"); - //recv_data.hexlike(); - - CHECK_PACKET_SIZE(recv_data, 4+1); - - uint32 ArenaTeamId; - std::string name; - - recv_data >> ArenaTeamId; - recv_data >> name; - - ArenaTeam *at = objmgr.GetArenaTeamById(ArenaTeamId); - if(!at) - { - // arena team not found - return; - } - - uint64 guid = objmgr.GetPlayerGUIDByName(name); - if(!guid) - { - // player guid not found - return; - } - - if(at->GetCaptain() == guid) - { - // target player already captain - return; - } - - if(at->GetCaptain() != _player->GetGUID()) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PERMISSIONS); - return; - } - - at->SetCaptain(guid); - - // event - WorldPacket data; - BuildArenaTeamEventPacket(&data, ERR_ARENA_TEAM_LEADER_CHANGED_SSS, 3, _player->GetName(), name, at->GetName()); - at->BroadcastPacket(&data); -} - -void WorldSession::SendArenaTeamCommandResult(uint32 unk1, std::string str1, std::string str2, uint32 unk3) -{ - WorldPacket data(SMSG_ARENA_TEAM_COMMAND_RESULT, 4+str1.length()+1+str2.length()+1+4); - data << unk1; - data << str1; - data << str2; - data << unk3; - SendPacket(&data); -} - -void WorldSession::BuildArenaTeamEventPacket(WorldPacket *data, uint8 eventid, uint8 str_count, std::string str1, std::string str2, std::string str3) -{ - data->Initialize(SMSG_ARENA_TEAM_EVENT, 1+1+1); - *data << eventid; - *data << str_count; - switch(str_count) - { - case 1: - *data << str1; - break; - case 2: - *data << str1; - *data << str2; - break; - case 3: - *data << str1; - *data << str2; - *data << str3; - break; - default: - sLog.outError("Unhandled str_count %u in SendArenaTeamEvent()", str_count); - return; - } -} - -void WorldSession::SendNotInArenaTeamPacket(uint8 type) -{ - WorldPacket data(SMSG_ARENA_ERROR, 4+1); // 886 - You are not in a %uv%u arena team - uint32 unk = 0; - data << uint32(unk); // unk(0) - if(!unk) - data << uint8(type); // team type (2=2v2,3=3v3,5=5v5), can be used for custom types... - SendPacket(&data); -} - -/* -+ERR_ARENA_NO_TEAM_II "You are not in a %dv%d arena team" - -+ERR_ARENA_TEAM_CREATE_S "%s created. To disband, use /teamdisband [2v2, 3v3, 5v5]." -+ERR_ARENA_TEAM_INVITE_SS "You have invited %s to join %s" -+ERR_ARENA_TEAM_QUIT_S "You are no longer a member of %s" -ERR_ARENA_TEAM_FOUNDER_S "Congratulations, you are a founding member of %s! To leave, use /teamquit [2v2, 3v3, 5v5]." - -+ERR_ARENA_TEAM_INTERNAL "Internal arena team error" -+ERR_ALREADY_IN_ARENA_TEAM "You are already in an arena team of that size" -+ERR_ALREADY_IN_ARENA_TEAM_S "%s is already in an arena team of that size" -+ERR_INVITED_TO_ARENA_TEAM "You have already been invited into an arena team" -+ERR_ALREADY_INVITED_TO_ARENA_TEAM_S "%s has already been invited to an arena team" -+ERR_ARENA_TEAM_NAME_INVALID "That name contains invalid characters, please enter a new name" -+ERR_ARENA_TEAM_NAME_EXISTS_S "There is already an arena team named \"%s\"" -+ERR_ARENA_TEAM_LEADER_LEAVE_S "You must promote a new team captain using /teamcaptain before leaving the team" -+ERR_ARENA_TEAM_PERMISSIONS "You don't have permission to do that" -+ERR_ARENA_TEAM_PLAYER_NOT_IN_TEAM "You are not in an arena team of that size" -+ERR_ARENA_TEAM_PLAYER_NOT_IN_TEAM_SS "%s is not in %s" -+ERR_ARENA_TEAM_PLAYER_NOT_FOUND_S "\"%s\" not found" -+ERR_ARENA_TEAM_NOT_ALLIED "You cannot invite players from the opposing alliance" - -+ERR_ARENA_TEAM_JOIN_SS "%s has joined %s" -+ERR_ARENA_TEAM_YOU_JOIN_S "You have joined %s. To leave, use /teamquit [2v2, 3v3, 5v5]." - -+ERR_ARENA_TEAM_LEAVE_SS "%s has left %s" - -+ERR_ARENA_TEAM_LEADER_IS_SS "%s is the captain of %s" -+ERR_ARENA_TEAM_LEADER_CHANGED_SSS "%s has made %s the new captain of %s" - -+ERR_ARENA_TEAM_REMOVE_SSS "%s has been kicked out of %s by %s" - -+ERR_ARENA_TEAM_DISBANDED_S "%s has disbanded %s" - -ERR_ARENA_TEAM_TARGET_TOO_LOW_S "%s is not high enough level to join your team" - -ERR_ARENA_TEAM_TOO_MANY_MEMBERS_S "%s is full" - -ERR_ARENA_TEAM_LEVEL_TOO_LOW_I "You must be level %d to form an arena team" -*/ +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "WorldSession.h" +#include "WorldPacket.h" +#include "Log.h" +#include "Database/DatabaseEnv.h" +#include "Player.h" +#include "ObjectMgr.h" +#include "ArenaTeam.h" +#include "World.h" +#include "SocialMgr.h" +#include "Language.h" + +void WorldSession::HandleInspectArenaStatsOpcode(WorldPacket & recv_data) +{ + sLog.outDebug("MSG_INSPECT_ARENA_TEAMS"); + //recv_data.hexlike(); + + CHECK_PACKET_SIZE(recv_data, 8); + + uint64 guid; + recv_data >> guid; + sLog.outDebug("Inspect Arena stats " I64FMTD, guid); + + if(Player *plr = objmgr.GetPlayer(guid)) + { + for (uint8 i = 0; i < MAX_ARENA_SLOT; i++) + { + if(uint32 a_id = plr->GetArenaTeamId(i)) + { + if(ArenaTeam *at = objmgr.GetArenaTeamById(a_id)) + at->InspectStats(this, plr->GetGUID()); + } + } + } +} + +void WorldSession::HandleArenaTeamQueryOpcode(WorldPacket & recv_data) +{ + sLog.outDebug( "WORLD: Received CMSG_ARENA_TEAM_QUERY" ); + //recv_data.hexlike(); + + CHECK_PACKET_SIZE(recv_data, 4); + + uint32 ArenaTeamId; + recv_data >> ArenaTeamId; + + ArenaTeam *arenateam = objmgr.GetArenaTeamById(ArenaTeamId); + if(!arenateam) // arena team not found + return; + + arenateam->Query(this); + arenateam->Stats(this); +} + +void WorldSession::HandleArenaTeamRosterOpcode(WorldPacket & recv_data) +{ + sLog.outDebug( "WORLD: Received CMSG_ARENA_TEAM_ROSTER" ); + //recv_data.hexlike(); + + CHECK_PACKET_SIZE(recv_data, 4); + + uint32 ArenaTeamId; // arena team id + recv_data >> ArenaTeamId; + + ArenaTeam *arenateam = objmgr.GetArenaTeamById(ArenaTeamId); + if(!arenateam) + return; + + arenateam->Roster(this); +} + +void WorldSession::HandleArenaTeamAddMemberOpcode(WorldPacket & recv_data) +{ + sLog.outDebug("CMSG_ARENA_TEAM_ADD_MEMBER"); + //recv_data.hexlike(); + + CHECK_PACKET_SIZE(recv_data, 4+1); + + uint32 ArenaTeamId; // arena team id + std::string Invitedname; + + Player * player = NULL; + + recv_data >> ArenaTeamId >> Invitedname; + + if(!Invitedname.empty()) + { + if(!normalizePlayerName(Invitedname)) + return; + + player = ObjectAccessor::Instance().FindPlayerByName(Invitedname.c_str()); + } + + if(!player) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", Invitedname, ERR_ARENA_TEAM_PLAYER_NOT_FOUND_S); + return; + } + + if(player->getLevel() < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + { + //SendArenaTeamCommandResult(ARENA_TEAM_INVITE_SS,"",Invitedname,ARENA_TEAM_PLAYER_NOT_FOUND_S); + // can't find related opcode + SendNotification(LANG_HIS_ARENA_LEVEL_REQ_ERROR, player->GetName()); + return; + } + + ArenaTeam *arenateam = objmgr.GetArenaTeamById(ArenaTeamId); + if(!arenateam) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PLAYER_NOT_IN_TEAM); + return; + } + + // OK result but not send invite + if(player->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow())) + return; + + if (!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && player->GetTeam() != GetPlayer()->GetTeam()) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", "", ERR_ARENA_TEAM_NOT_ALLIED); + return; + } + + if(player->GetArenaTeamId(arenateam->GetSlot())) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, player->GetName(), "", ERR_ALREADY_IN_ARENA_TEAM_S); + return; + } + + if(player->GetArenaTeamIdInvited()) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, player->GetName(), "", ERR_ALREADY_INVITED_TO_ARENA_TEAM_S); + return; + } + + if(arenateam->GetMembersSize() >= arenateam->GetType() * 2) + { + // should send an "arena team is full" or the likes message, I just don't know the proper values so... ERR_INTERNAL +// SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", "", ERR_ARENA_TEAM_INTERNAL); + SendNotification(LANG_YOUR_ARENA_TEAM_FULL, player->GetName()); + return; + } + + sLog.outDebug("Player %s Invited %s to Join his ArenaTeam", GetPlayer()->GetName(), Invitedname.c_str()); + + player->SetArenaTeamIdInvited(arenateam->GetId()); + + WorldPacket data(SMSG_ARENA_TEAM_INVITE, (8+10)); + data << GetPlayer()->GetName(); + data << arenateam->GetName(); + player->GetSession()->SendPacket(&data); + + sLog.outDebug("WORLD: Sent SMSG_ARENA_TEAM_INVITE"); +} + +void WorldSession::HandleArenaTeamInviteAcceptOpcode(WorldPacket & /*recv_data*/) +{ + sLog.outDebug("CMSG_ARENA_TEAM_INVITE_ACCEPT"); // empty opcode + + ArenaTeam *at = objmgr.GetArenaTeamById(_player->GetArenaTeamIdInvited()); + if(!at) + { + // arena team not exist + return; + } + + if(_player->GetArenaTeamId(at->GetSlot())) + { + // already in arena team that size + return; + } + + // not let enemies sign petition + if (!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && _player->GetTeam() != objmgr.GetPlayerTeamByGUID(at->GetCaptain())) + return; + + if(!at->AddMember(_player->GetGUID())) + return; + + // event + WorldPacket data; + BuildArenaTeamEventPacket(&data, ERR_ARENA_TEAM_JOIN_SS, 2, _player->GetName(), at->GetName(), ""); + at->BroadcastPacket(&data); +} + +void WorldSession::HandleArenaTeamInviteDeclineOpcode(WorldPacket & /*recv_data*/) +{ + sLog.outDebug("CMSG_ARENA_TEAM_INVITE_DECLINE"); // empty opcode + + _player->SetArenaTeamIdInvited(0); // no more invited +} + +void WorldSession::HandleArenaTeamLeaveOpcode(WorldPacket & recv_data) +{ + sLog.outDebug("CMSG_ARENA_TEAM_LEAVE"); + //recv_data.hexlike(); + + CHECK_PACKET_SIZE(recv_data, 4); + + uint32 ArenaTeamId; // arena team id + recv_data >> ArenaTeamId; + + ArenaTeam *at = objmgr.GetArenaTeamById(ArenaTeamId); + if(!at) + { + // send command result + return; + } + if(_player->GetGUID() == at->GetCaptain() && at->GetMembersSize() > 1) + { + // check for correctness + SendArenaTeamCommandResult(ERR_ARENA_TEAM_QUIT_S, "", "", ERR_ARENA_TEAM_LEADER_LEAVE_S); + return; + } + // arena team has only one member (=captain) + if(_player->GetGUID() == at->GetCaptain()) + { + at->Disband(this); + delete at; + return; + } + + at->DelMember(_player->GetGUID()); + + // event + WorldPacket data; + BuildArenaTeamEventPacket(&data, ERR_ARENA_TEAM_LEAVE_SS, 2, _player->GetName(), at->GetName(), ""); + at->BroadcastPacket(&data); + + //SendArenaTeamCommandResult(ERR_ARENA_TEAM_QUIT_S, at->GetName(), "", 0); +} + +void WorldSession::HandleArenaTeamDisbandOpcode(WorldPacket & recv_data) +{ + sLog.outDebug("CMSG_ARENA_TEAM_DISBAND"); + //recv_data.hexlike(); + + CHECK_PACKET_SIZE(recv_data, 4); + + uint32 ArenaTeamId; // arena team id + recv_data >> ArenaTeamId; + + ArenaTeam *at = objmgr.GetArenaTeamById(ArenaTeamId); + if(!at) + { + // arena team not found + return; + } + + if(at->GetCaptain() != _player->GetGUID()) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PERMISSIONS); + return; + } + + at->Disband(this); + delete at; +} + +void WorldSession::HandleArenaTeamRemoveFromTeamOpcode(WorldPacket & recv_data) +{ + sLog.outDebug("CMSG_ARENA_TEAM_REMOVE_FROM_TEAM"); + //recv_data.hexlike(); + + CHECK_PACKET_SIZE(recv_data, 4+1); + + uint32 ArenaTeamId; + std::string name; + + recv_data >> ArenaTeamId; + recv_data >> name; + + ArenaTeam *at = objmgr.GetArenaTeamById(ArenaTeamId); + if(!at) + { + // arena team not found + return; + } + + uint64 guid = objmgr.GetPlayerGUIDByName(name); + if(!guid) + { + // player guid not found + return; + } + + if(at->GetCaptain() == guid) + { + // unsure + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PERMISSIONS); + return; + } + + if(at->GetCaptain() != _player->GetGUID()) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PERMISSIONS); + return; + } + + if(at->GetCaptain() == guid) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_LEADER_LEAVE_S); + return; + } + + at->DelMember(guid); + + // event + WorldPacket data; + BuildArenaTeamEventPacket(&data, ERR_ARENA_TEAM_REMOVE_SSS, 3, name, at->GetName(), _player->GetName()); + at->BroadcastPacket(&data); +} + +void WorldSession::HandleArenaTeamPromoteToCaptainOpcode(WorldPacket & recv_data) +{ + sLog.outDebug("CMSG_ARENA_TEAM_PROMOTE_TO_CAPTAIN"); + //recv_data.hexlike(); + + CHECK_PACKET_SIZE(recv_data, 4+1); + + uint32 ArenaTeamId; + std::string name; + + recv_data >> ArenaTeamId; + recv_data >> name; + + ArenaTeam *at = objmgr.GetArenaTeamById(ArenaTeamId); + if(!at) + { + // arena team not found + return; + } + + uint64 guid = objmgr.GetPlayerGUIDByName(name); + if(!guid) + { + // player guid not found + return; + } + + if(at->GetCaptain() == guid) + { + // target player already captain + return; + } + + if(at->GetCaptain() != _player->GetGUID()) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", "", ERR_ARENA_TEAM_PERMISSIONS); + return; + } + + at->SetCaptain(guid); + + // event + WorldPacket data; + BuildArenaTeamEventPacket(&data, ERR_ARENA_TEAM_LEADER_CHANGED_SSS, 3, _player->GetName(), name, at->GetName()); + at->BroadcastPacket(&data); +} + +void WorldSession::SendArenaTeamCommandResult(uint32 unk1, std::string str1, std::string str2, uint32 unk3) +{ + WorldPacket data(SMSG_ARENA_TEAM_COMMAND_RESULT, 4+str1.length()+1+str2.length()+1+4); + data << unk1; + data << str1; + data << str2; + data << unk3; + SendPacket(&data); +} + +void WorldSession::BuildArenaTeamEventPacket(WorldPacket *data, uint8 eventid, uint8 str_count, std::string str1, std::string str2, std::string str3) +{ + data->Initialize(SMSG_ARENA_TEAM_EVENT, 1+1+1); + *data << eventid; + *data << str_count; + switch(str_count) + { + case 1: + *data << str1; + break; + case 2: + *data << str1; + *data << str2; + break; + case 3: + *data << str1; + *data << str2; + *data << str3; + break; + default: + sLog.outError("Unhandled str_count %u in SendArenaTeamEvent()", str_count); + return; + } +} + +void WorldSession::SendNotInArenaTeamPacket(uint8 type) +{ + WorldPacket data(SMSG_ARENA_ERROR, 4+1); // 886 - You are not in a %uv%u arena team + uint32 unk = 0; + data << uint32(unk); // unk(0) + if(!unk) + data << uint8(type); // team type (2=2v2,3=3v3,5=5v5), can be used for custom types... + SendPacket(&data); +} + +/* ++ERR_ARENA_NO_TEAM_II "You are not in a %dv%d arena team" + ++ERR_ARENA_TEAM_CREATE_S "%s created. To disband, use /teamdisband [2v2, 3v3, 5v5]." ++ERR_ARENA_TEAM_INVITE_SS "You have invited %s to join %s" ++ERR_ARENA_TEAM_QUIT_S "You are no longer a member of %s" +ERR_ARENA_TEAM_FOUNDER_S "Congratulations, you are a founding member of %s! To leave, use /teamquit [2v2, 3v3, 5v5]." + ++ERR_ARENA_TEAM_INTERNAL "Internal arena team error" ++ERR_ALREADY_IN_ARENA_TEAM "You are already in an arena team of that size" ++ERR_ALREADY_IN_ARENA_TEAM_S "%s is already in an arena team of that size" ++ERR_INVITED_TO_ARENA_TEAM "You have already been invited into an arena team" ++ERR_ALREADY_INVITED_TO_ARENA_TEAM_S "%s has already been invited to an arena team" ++ERR_ARENA_TEAM_NAME_INVALID "That name contains invalid characters, please enter a new name" ++ERR_ARENA_TEAM_NAME_EXISTS_S "There is already an arena team named \"%s\"" ++ERR_ARENA_TEAM_LEADER_LEAVE_S "You must promote a new team captain using /teamcaptain before leaving the team" ++ERR_ARENA_TEAM_PERMISSIONS "You don't have permission to do that" ++ERR_ARENA_TEAM_PLAYER_NOT_IN_TEAM "You are not in an arena team of that size" ++ERR_ARENA_TEAM_PLAYER_NOT_IN_TEAM_SS "%s is not in %s" ++ERR_ARENA_TEAM_PLAYER_NOT_FOUND_S "\"%s\" not found" ++ERR_ARENA_TEAM_NOT_ALLIED "You cannot invite players from the opposing alliance" + ++ERR_ARENA_TEAM_JOIN_SS "%s has joined %s" ++ERR_ARENA_TEAM_YOU_JOIN_S "You have joined %s. To leave, use /teamquit [2v2, 3v3, 5v5]." + ++ERR_ARENA_TEAM_LEAVE_SS "%s has left %s" + ++ERR_ARENA_TEAM_LEADER_IS_SS "%s is the captain of %s" ++ERR_ARENA_TEAM_LEADER_CHANGED_SSS "%s has made %s the new captain of %s" + ++ERR_ARENA_TEAM_REMOVE_SSS "%s has been kicked out of %s by %s" + ++ERR_ARENA_TEAM_DISBANDED_S "%s has disbanded %s" + +ERR_ARENA_TEAM_TARGET_TOO_LOW_S "%s is not high enough level to join your team" + +ERR_ARENA_TEAM_TOO_MANY_MEMBERS_S "%s is full" + +ERR_ARENA_TEAM_LEVEL_TOO_LOW_I "You must be level %d to form an arena team" +*/ diff --git a/src/game/BattleGroundHandler.cpp b/src/game/BattleGroundHandler.cpp index 486f828fbda..eb9a593decf 100644 --- a/src/game/BattleGroundHandler.cpp +++ b/src/game/BattleGroundHandler.cpp @@ -1,952 +1,952 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "WorldPacket.h" -#include "Opcodes.h" -#include "Log.h" -#include "Player.h" -#include "ObjectMgr.h" -#include "WorldSession.h" -#include "MapManager.h" -#include "ObjectAccessor.h" -#include "Object.h" -#include "Chat.h" -#include "Language.h" -#include "BattleGroundMgr.h" -#include "BattleGroundWS.h" -#include "BattleGround.h" -#include "ArenaTeam.h" - -void WorldSession::HandleBattleGroundHelloOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - uint64 guid; - recv_data >> guid; - sLog.outDebug( "WORLD: Recvd CMSG_BATTLEMASTER_HELLO Message from: " I64FMT, guid); - - Creature *unit = ObjectAccessor::GetCreature(*_player, guid); - if(!unit) - return; - - if(!unit->isBattleMaster()) // it's not battlemaster - return; - - // Stop the npc if moving - unit->StopMoving(); - - uint32 bgTypeId = objmgr.GetBattleMasterBG(unit->GetEntry()); - - if(!_player->GetBGAccessByLevel(bgTypeId)) - { - // temp, must be gossip message... - SendNotification("You don't meet Battleground level requirements"); - return; - } - - SendBattlegGroundList(guid, bgTypeId); -} - -void WorldSession::SendBattlegGroundList( uint64 guid, uint32 bgTypeId ) -{ - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundListPacket(&data, guid, _player, bgTypeId); - SendPacket( &data ); -} - -void WorldSession::HandleBattleGroundJoinOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 8+4+4+1); - - uint64 guid; - uint32 bgTypeId; - uint32 instanceId; - uint8 joinAsGroup; - Group * grp; - - recv_data >> guid; // battlemaster guid - recv_data >> bgTypeId; // battleground type id (DBC id) - recv_data >> instanceId; // instance id, 0 if First Available selected - recv_data >> joinAsGroup; // join as group - - if(bgTypeId >= MAX_BATTLEGROUND_TYPES) - { - sLog.outError("Battleground: invalid bgtype received. possible cheater? player guid %u",_player->GetGUIDLow()); - return; - } - - sLog.outDebug( "WORLD: Recvd CMSG_BATTLEMASTER_JOIN Message from: " I64FMT, guid); - - // can do this, since it's battleground, not arena - uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bgTypeId, 0); - - // ignore if we already in BG or BG queue - if(_player->InBattleGround()) - return; - - Creature *unit = ObjectAccessor::GetCreature(*_player, guid); - if(!unit) - return; - - if(!unit->isBattleMaster()) // it's not battlemaster - return; - - // get bg instance or bg template if instance not found - BattleGround * bg = 0; - if(instanceId) - BattleGround *bg = sBattleGroundMgr.GetBattleGround(instanceId); - - if(!bg && !(bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId))) - { - sLog.outError("Battleground: no available bg / template found"); - return; - } - - // check queueing conditions - if(!joinAsGroup) - { - // check Deserter debuff - if( !_player->CanJoinToBattleground() ) - { - WorldPacket data(SMSG_GROUP_JOINED_BATTLEGROUND, 4); - data << (uint32) 0xFFFFFFFE; - _player->GetSession()->SendPacket(&data); - return; - } - // check if already in queue - if (_player->GetBattleGroundQueueIndex(bgQueueTypeId) < PLAYER_MAX_BATTLEGROUND_QUEUES) - //player is already in this queue - return; - // check if has free queue slots - if(!_player->HasFreeBattleGroundQueueId()) - return; - } - else - { - grp = _player->GetGroup(); - // no group found, error - if(!grp) - return; - uint32 err = grp->CanJoinBattleGroundQueue(bgTypeId, bgQueueTypeId, 0, bg->GetMaxPlayersPerTeam(), false, 0); - switch(err) - { - // TODO: add error-based feedback to players in all cases - case BG_JOIN_ERR_GROUP_TOO_MANY: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_TOO_LARGE), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_OFFLINE_MEMBER: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_OFFLINE_MEMBER), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_MIXED_FACTION: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MIXED_FACTION), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_MIXED_LEVELS: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MIXED_LEVELS), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_GROUP_MEMBER_ALREADY_IN_QUEUE: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_ALREADY_IN_QUEUE), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_GROUP_DESERTER: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_DESERTER), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_ALL_QUEUES_USED: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_NO_FREE_QUEUE_SLOTS), NULL); - SendPacket(&data); - } - return; - break; - // all ok, can join - case BG_JOIN_ERR_OK: - break; - // these aren't possible outcomes in bgs - case BG_JOIN_ERR_GROUP_NOT_ENOUGH: - case BG_JOIN_ERR_MIXED_ARENATEAM: - return; - break; - // not the above? shouldn't happen, don't let join - default: - return; - break; - }; - } - - // if we're here, then the conditions to join a bg are met. We can proceed in joining. - - // _player->GetGroup() was already checked, grp is already initialized - if(joinAsGroup /* && _player->GetGroup()*/) - { - sLog.outDebug("Battleground: the following players are joining as group:"); - GroupQueueInfo * ginfo = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddGroup(_player, bgTypeId, 0, false, 0); - for(GroupReference *itr = grp->GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *member = itr->getSource(); - if(!member) continue; // this should never happen - - uint32 queueSlot = member->AddBattleGroundQueueId(bgQueueTypeId); // add to queue - - // store entry point coords (same as leader entry point) - member->SetBattleGroundEntryPoint(_player->GetMapId(),_player->GetPositionX(),_player->GetPositionY(),_player->GetPositionZ(),_player->GetOrientation()); - - WorldPacket data; - // send status packet (in queue) - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, member->GetTeam(), queueSlot, STATUS_WAIT_QUEUE, 0, 0); - member->GetSession()->SendPacket(&data); - sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(&data, bgTypeId); - member->GetSession()->SendPacket(&data); - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddPlayer(member, ginfo); - sLog.outDebug("Battleground: player joined queue for bg queue type %u bg type %u: GUID %u, NAME %s",bgQueueTypeId,bgTypeId,member->GetGUIDLow(), member->GetName()); - } - sLog.outDebug("Battleground: group end"); - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId, _player->GetBattleGroundQueueIdFromLevel()); - } - else - { - // already checked if queueSlot is valid, now just get it - uint32 queueSlot = _player->AddBattleGroundQueueId(bgQueueTypeId); - // store entry point coords - _player->SetBattleGroundEntryPoint(_player->GetMapId(),_player->GetPositionX(),_player->GetPositionY(),_player->GetPositionZ(),_player->GetOrientation()); - - WorldPacket data; - // send status packet (in queue) - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_WAIT_QUEUE, 0, 0); - SendPacket(&data); - - GroupQueueInfo * ginfo = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddGroup(_player, bgTypeId, 0, false, 0); - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddPlayer(_player, ginfo); - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId, _player->GetBattleGroundQueueIdFromLevel()); - sLog.outDebug("Battleground: player joined queue for bg queue type %u bg type %u: GUID %u, NAME %s",bgQueueTypeId,bgTypeId,_player->GetGUIDLow(), _player->GetName()); - } -} - -void WorldSession::HandleBattleGroundPlayerPositionsOpcode( WorldPacket & /*recv_data*/ ) -{ - // empty opcode - sLog.outDebug("WORLD: Recvd MSG_BATTLEGROUND_PLAYER_POSITIONS Message"); - - BattleGround *bg = _player->GetBattleGround(); - if(!bg) // can't be received if player not in battleground - return; - - if(bg->GetTypeID() == BATTLEGROUND_WS) - { - uint32 count1 = 0; - uint32 count2 = 0; - - Player *ap = objmgr.GetPlayer(((BattleGroundWS*)bg)->GetAllianceFlagPickerGUID()); - if(ap) ++count2; - - Player *hp = objmgr.GetPlayer(((BattleGroundWS*)bg)->GetHordeFlagPickerGUID()); - if(hp) ++count2; - - WorldPacket data(MSG_BATTLEGROUND_PLAYER_POSITIONS, (4+4+16*count1+16*count2)); - data << count1; // alliance flag holders count - /*for(uint8 i = 0; i < count1; i++) - { - data << uint64(0); // guid - data << (float)0; // x - data << (float)0; // y - }*/ - data << count2; // horde flag holders count - if(ap) - { - data << (uint64)ap->GetGUID(); - data << (float)ap->GetPositionX(); - data << (float)ap->GetPositionY(); - } - if(hp) - { - data << (uint64)hp->GetGUID(); - data << (float)hp->GetPositionX(); - data << (float)hp->GetPositionY(); - } - - SendPacket(&data); - } -} - -void WorldSession::HandleBattleGroundPVPlogdataOpcode( WorldPacket & /*recv_data*/ ) -{ - sLog.outDebug( "WORLD: Recvd MSG_PVP_LOG_DATA Message"); - - BattleGround *bg = _player->GetBattleGround(); - if(!bg) - return; - - WorldPacket data; - sBattleGroundMgr.BuildPvpLogDataPacket(&data, bg); - SendPacket(&data); - - sLog.outDebug( "WORLD: Sent MSG_PVP_LOG_DATA Message"); -} - -void WorldSession::HandleBattleGroundListOpcode( WorldPacket &recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 4); - - sLog.outDebug( "WORLD: Recvd CMSG_BATTLEFIELD_LIST Message"); - - uint32 bgTypeId; - recv_data >> bgTypeId; // id from DBC - - if(bgTypeId >= MAX_BATTLEGROUND_TYPES) - { - sLog.outError("Battleground: invalid bgtype received."); - return; - } - - BattlemasterListEntry const* bl = sBattlemasterListStore.LookupEntry(bgTypeId); - - if(!bl) - return; - - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundListPacket(&data, _player->GetGUID(), _player, bgTypeId); - SendPacket( &data ); -} - -void WorldSession::HandleBattleGroundPlayerPortOpcode( WorldPacket &recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 1+1+4+2+1); - - sLog.outDebug( "WORLD: Recvd CMSG_BATTLEFIELD_PORT Message"); - - uint8 type; // arenatype if arena - uint8 unk2; // unk, can be 0x0 (may be if was invited?) and 0x1 - uint32 instanceId; - uint32 bgTypeId; // type id from dbc - uint16 unk; // 0x1F90 constant? - uint8 action; // enter battle 0x1, leave queue 0x0 - - recv_data >> type >> unk2 >> bgTypeId >> unk >> action; - - if(bgTypeId >= MAX_BATTLEGROUND_TYPES) - { - sLog.outError("Battleground: invalid bgtype received."); - // update battleground slots for the player to fix his UI and sent data. - // this is a HACK, I don't know why the client starts sending invalid packets in the first place. - // it usually happens with extremely high latency (if debugging / stepping in the code for example) - if(_player->InBattleGroundQueue()) - { - // update all queues, send invitation info if player is invited, queue info if queued - for (uint32 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) - { - uint32 queue_id = _player->GetBattleGroundQueueId(i); - if(!queue_id) - continue; - BattleGroundQueue::QueuedPlayersMap::iterator itrPlayerStatus = sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); - // if the player is not in queue, contine - if(itrPlayerStatus == sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end()) - continue; - - // no group information, this should never happen - if(!itrPlayerStatus->second.GroupInfo) - continue; - - BattleGround * bg = NULL; - - // get possibly needed data from groupinfo - bgTypeId = itrPlayerStatus->second.GroupInfo->BgTypeId; - uint8 arenatype = itrPlayerStatus->second.GroupInfo->ArenaType; - uint8 israted = itrPlayerStatus->second.GroupInfo->IsRated; - uint8 status = 0; - - - if(!itrPlayerStatus->second.GroupInfo->IsInvitedToBGInstanceGUID) - { - // not invited to bg, get template - bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); - status = STATUS_WAIT_QUEUE; - } - else - { - // get the bg we're invited to - BattleGround * bg = sBattleGroundMgr.GetBattleGround(itrPlayerStatus->second.GroupInfo->IsInvitedToBGInstanceGUID); - status = STATUS_WAIT_JOIN; - } - - // if bg not found, then continue - if(!bg) - continue; - - // don't invite if already in the instance - if(_player->InBattleGround() && _player->GetBattleGround() && _player->GetBattleGround()->GetInstanceID() == bg->GetInstanceID()) - continue; - - // re - invite player with proper data - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, itrPlayerStatus->second.GroupInfo->Team?itrPlayerStatus->second.GroupInfo->Team:_player->GetTeam(), i, status, INVITE_ACCEPT_WAIT_TIME, 0, arenatype, israted); - SendPacket(&data); - } - } - return; - } - - uint32 bgQueueTypeId = 0; - // get the bg what we were invited to - BattleGroundQueue::QueuedPlayersMap::iterator itrPlayerStatus; - bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bgTypeId,type); - itrPlayerStatus = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); - - if(itrPlayerStatus == sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end()) - { - sLog.outError("Battleground: itrplayerstatus not found."); - return; - } - instanceId = itrPlayerStatus->second.GroupInfo->IsInvitedToBGInstanceGUID; - - // if action == 1, then instanceId is _required_ - if(!instanceId && action == 1) - { - sLog.outError("Battleground: instance not found."); - return; - } - - BattleGround *bg = sBattleGroundMgr.GetBattleGround(instanceId); - - // bg template might and must be used in case of leaving queue, when instance is not created yet - if(!bg && action == 0) - bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); - - if(!bg) - { - sLog.outError("Battleground: bg not found."); - return; - } - - bgTypeId = bg->GetTypeID(); - - if(_player->InBattleGroundQueue()) - { - uint32 queueSlot = 0; - uint32 team = 0; - uint32 arenatype = 0; - uint32 israted = 0; - uint32 rating = 0; - // get the team info from the queue - BattleGroundQueue::QueuedPlayersMap::iterator pitr = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); - if(pitr !=sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end() - && pitr->second.GroupInfo ) - { - team = pitr->second.GroupInfo->Team; - arenatype = pitr->second.GroupInfo->ArenaType; - israted = pitr->second.GroupInfo->IsRated; - rating = pitr->second.GroupInfo->ArenaTeamRating; - } - else - { - sLog.outError("Battleground: Invalid player queue info!"); - return; - } - WorldPacket data; - switch(action) - { - case 1: // port to battleground - if(!_player->IsInvitedForBattleGroundQueueType(bgQueueTypeId)) - return; // cheating? - // resurrect the player - if(!_player->isAlive()) - { - _player->ResurrectPlayer(1.0f,false); - _player->SpawnCorpseBones(); - } - // stop taxi flight at port - if(_player->isInFlight()) - { - _player->GetMotionMaster()->MovementExpired(); - _player->m_taxi.ClearTaxiDestinations(); - } - _player->RemoveFromGroup(); - queueSlot = _player->GetBattleGroundQueueIndex(bgQueueTypeId); - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_IN_PROGRESS, 0, bg->GetStartTime()); - _player->GetSession()->SendPacket(&data); - // remove battleground queue status from BGmgr - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].RemovePlayer(_player->GetGUID(), false); - // this is still needed here if battleground "jumping" shouldn't add deserter debuff - // also this required to prevent stuck at old battleground after SetBattleGroundId set to new - if( BattleGround *currentBg = _player->GetBattleGround() ) - currentBg->RemovePlayerAtLeave(_player->GetGUID(), false, true); - - // set the destination instance id - _player->SetBattleGroundId(bg->GetInstanceID()); - // set the destination team - _player->SetBGTeam(team); - // bg->HandleBeforeTeleportToBattleGround(_player); - sBattleGroundMgr.SendToBattleGround(_player, instanceId); - // add only in HandleMoveWorldPortAck() - // bg->AddPlayer(_player,team); - sLog.outDebug("Battleground: player %s (%u) joined battle for bg %u, bgtype %u, queue type %u.",_player->GetName(),_player->GetGUIDLow(),bg->GetInstanceID(),bg->GetTypeID(),bgQueueTypeId); - break; - case 0: // leave queue - queueSlot = _player->GetBattleGroundQueueIndex(bgQueueTypeId); - _player->RemoveBattleGroundQueueId(bgQueueTypeId); // must be called this way, because if you move this call to queue->removeplayer, it causes bugs - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_NONE, 0, 0); - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].RemovePlayer(_player->GetGUID(), true); - // player left queue, we should update it, maybe now his group fits in - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId,_player->GetBattleGroundQueueIdFromLevel(),arenatype,israted,rating); - SendPacket(&data); - sLog.outDebug("Battleground: player %s (%u) left queue for bgtype %u, queue type %u.",_player->GetName(),_player->GetGUIDLow(),bg->GetTypeID(),bgQueueTypeId); - break; - default: - sLog.outError("Battleground port: unknown action %u", action); - break; - } - } -} - -void WorldSession::HandleBattleGroundLeaveOpcode( WorldPacket & /*recv_data*/ ) -{ - //CHECK_PACKET_SIZE(recv_data, 1+1+4+2); - - sLog.outDebug( "WORLD: Recvd CMSG_LEAVE_BATTLEFIELD Message"); - - //uint8 unk1, unk2; - //uint32 bgTypeId; // id from DBC - //uint16 unk3; - - //recv_data >> unk1 >> unk2 >> bgTypeId >> unk3; - no used currently - - //if(bgTypeId >= MAX_BATTLEGROUND_TYPES) // cheating? but not important in this case - // return; - - // not allow leave battleground in combat - if(_player->isInCombat()) - if(BattleGround* bg = _player->GetBattleGround()) - if(bg->GetStatus() != STATUS_WAIT_LEAVE) - return; - - _player->LeaveBattleground(); -} - -void WorldSession::HandleBattlefieldStatusOpcode( WorldPacket & /*recv_data*/ ) -{ - // empty opcode - sLog.outDebug( "WORLD: Battleground status" ); - - WorldPacket data; - - // TODO: we must put player back to battleground in case disconnect (< 5 minutes offline time) or teleport player on login(!) from battleground map to entry point - if(_player->InBattleGround()) - { - BattleGround *bg = _player->GetBattleGround(); - if(bg) - { - uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bg->GetTypeID(), bg->GetArenaType()); - uint32 queueSlot = _player->GetBattleGroundQueueIndex(bgQueueTypeId); - if((bg->GetStatus() <= STATUS_IN_PROGRESS)) - { - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_IN_PROGRESS, 0, bg->GetStartTime()); - SendPacket(&data); - } - for (uint32 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) - { - uint32 queue_id = _player->GetBattleGroundQueueId(i); // battlegroundqueueid stores the type id, not the instance id, so this is definitely wrong - uint8 arenatype = sBattleGroundMgr.BGArenaType(queue_id); - uint8 isRated = 0; - if (i == queueSlot || !queue_id) // we need to get the instance ids - continue; - BattleGroundQueue::QueuedPlayersMap::iterator itrPlayerStatus = sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); - if(itrPlayerStatus == sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end()) - continue; - if(itrPlayerStatus->second.GroupInfo) - { - arenatype = itrPlayerStatus->second.GroupInfo->ArenaType; - isRated = itrPlayerStatus->second.GroupInfo->IsRated; - } - BattleGround *bg2 = sBattleGroundMgr.GetBattleGroundTemplate(sBattleGroundMgr.BGTemplateId(queue_id)); // try this - if(bg2) - { - //in this call is small bug, this call should be filled by player's waiting time in queue - //this call nulls all timers for client : - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg2, _player->GetTeam(), i, STATUS_WAIT_QUEUE, 0, 0,arenatype,isRated); - SendPacket(&data); - } - } - } - } - else - { - // we should update all queues? .. i'm not sure if this code is correct - for (uint32 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) - { - uint32 queue_id = _player->GetBattleGroundQueueId(i); - if(!queue_id) - continue; - uint32 bgTypeId = sBattleGroundMgr.BGTemplateId(queue_id); - uint8 arenatype = sBattleGroundMgr.BGArenaType(queue_id); - uint8 isRated = 0; - BattleGround *bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); - BattleGroundQueue::QueuedPlayersMap::iterator itrPlayerStatus = sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); - if(itrPlayerStatus == sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end()) - continue; - if(itrPlayerStatus->second.GroupInfo) - { - arenatype = itrPlayerStatus->second.GroupInfo->ArenaType; - isRated = itrPlayerStatus->second.GroupInfo->IsRated; - } - if(bg && queue_id) - { - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), i, STATUS_WAIT_QUEUE, 0, 0, arenatype, isRated); - SendPacket(&data); - } - } - } -/* else // not sure if it needed... - { - for (uint32 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) - { - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, NULL, _player->GetTeam(),i , STATUS_NONE, 0, 0); - SendPacket(&data); - } - }*/ -} - -void WorldSession::HandleAreaSpiritHealerQueryOpcode( WorldPacket & recv_data ) -{ - sLog.outDebug("WORLD: CMSG_AREA_SPIRIT_HEALER_QUERY"); - - CHECK_PACKET_SIZE(recv_data, 8); - - BattleGround *bg = _player->GetBattleGround(); - if(!bg) - return; - - uint64 guid; - recv_data >> guid; - - Creature *unit = ObjectAccessor::GetCreature(*_player, guid); - if(!unit) - return; - - if(!unit->isSpiritService()) // it's not spirit service - return; - - sBattleGroundMgr.SendAreaSpiritHealerQueryOpcode(_player, bg, guid); -} - -void WorldSession::HandleAreaSpiritHealerQueueOpcode( WorldPacket & recv_data ) -{ - sLog.outDebug("WORLD: CMSG_AREA_SPIRIT_HEALER_QUEUE"); - - CHECK_PACKET_SIZE(recv_data, 8); - - BattleGround *bg = _player->GetBattleGround(); - if(!bg) - return; - - uint64 guid; - recv_data >> guid; - - Creature *unit = ObjectAccessor::GetCreature(*_player, guid); - if(!unit) - return; - - if(!unit->isSpiritService()) // it's not spirit service - return; - - bg->AddPlayerToResurrectQueue(guid, _player->GetGUID()); -} - -void WorldSession::HandleBattleGroundArenaJoin( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 8+1+1+1); - - sLog.outDebug("WORLD: CMSG_BATTLEMASTER_JOIN_ARENA"); - recv_data.hexlike(); - - // ignore if we already in BG or BG queue - if(_player->InBattleGround()) - return; - - uint64 guid; // arena Battlemaster guid - uint8 type; // 2v2, 3v3 or 5v5 - uint8 asGroup; // asGroup - uint8 isRated; // isRated - Group * grp; - - recv_data >> guid >> type >> asGroup >> isRated; - - Creature *unit = ObjectAccessor::GetCreature(*_player, guid); - if(!unit) - return; - - if(!unit->isBattleMaster()) // it's not battle master - return; - - uint8 arenatype = 0; - uint32 arenaRating = 0; - - switch(type) - { - case 0: - arenatype = ARENA_TYPE_2v2; - break; - case 1: - arenatype = ARENA_TYPE_3v3; - break; - case 2: - arenatype = ARENA_TYPE_5v5; - break; - default: - sLog.outError("Unknown arena type %u at HandleBattleGroundArenaJoin()", type); - return; - } - - //check existance - BattleGround* bg = NULL; - if( !(bg = sBattleGroundMgr.GetBattleGroundTemplate(BATTLEGROUND_AA)) ) - { - sLog.outError("Battleground: template bg (all arenas) not found"); - return; - } - - uint8 bgTypeId = bg->GetTypeID(); - uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bgTypeId, arenatype); - - // check queueing conditions - if(!asGroup) - { - // check if already in queue - if (_player->GetBattleGroundQueueIndex(bgQueueTypeId) < PLAYER_MAX_BATTLEGROUND_QUEUES) - //player is already in this queue - return; - // check if has free queue slots - if(!_player->HasFreeBattleGroundQueueId()) - return; - } - else - { - grp = _player->GetGroup(); - // no group found, error - if(!grp) - return; - uint32 err = grp->CanJoinBattleGroundQueue(bgTypeId, bgQueueTypeId, arenatype, arenatype, (bool)isRated, type); - switch(err) - { - // TODO: add error-based feedback to players in all cases - case BG_JOIN_ERR_GROUP_TOO_MANY: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_ARENA_GROUP_TOO_LARGE), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_GROUP_NOT_ENOUGH: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_ARENA_NOT_ENOUGH_PLAYERS), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_MIXED_ARENATEAM: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_ARENA_YOUR_TEAM_ONLY), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_OFFLINE_MEMBER: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_OFFLINE_MEMBER), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_MIXED_FACTION: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MIXED_FACTION), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_MIXED_LEVELS: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MIXED_LEVELS), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_GROUP_MEMBER_ALREADY_IN_QUEUE: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_ALREADY_IN_QUEUE), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_GROUP_DESERTER: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_DESERTER), NULL); - SendPacket(&data); - } - return; - break; - case BG_JOIN_ERR_ALL_QUEUES_USED: - { - WorldPacket data; - ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_NO_FREE_QUEUE_SLOTS), NULL); - SendPacket(&data); - } - return; - break; - // all ok, can join - case BG_JOIN_ERR_OK: - break; - // not the above? shouldn't happen, don't let join - default: - return; - break; - }; - } - - uint32 ateamId = 0; - - if(isRated) - { - ateamId = _player->GetArenaTeamId(type); - // check real arenateam existence only here (if it was moved to group->CanJoin .. () then we would ahve to get it twice) - ArenaTeam * at = objmgr.GetArenaTeamById(ateamId); - if(!at) - { - _player->GetSession()->SendNotInArenaTeamPacket(arenatype); - return; - } - // get the team rating for queueing - arenaRating = at->GetRating(); - // the arenateam id must match for everyone in the group - // get the personal ratings for queueing - uint32 avg_pers_rating = 0; - for(GroupReference *itr = grp->GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *member = itr->getSource(); - - // calc avg personal rating - avg_pers_rating += member->GetUInt32Value(PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + (type*6) + 5); - } - - if( arenatype ) - avg_pers_rating /= arenatype; - - // if avg personal rating is more than 150 points below the team’s rating, the team will be queued against an opponent matching or similar to the average personal rating - if(avg_pers_rating + 150 < arenaRating) - arenaRating = avg_pers_rating; - } - - if(asGroup) - { - GroupQueueInfo * ginfo = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddGroup(_player, bgTypeId, arenatype, isRated, arenaRating, ateamId); - sLog.outDebug("Battleground: arena join as group start"); - if(isRated) - sLog.outDebug("Battleground: arena team id %u, leader %s queued with rating %u for type %u",_player->GetArenaTeamId(type),_player->GetName(),arenaRating,arenatype); - for(GroupReference *itr = grp->GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *member = itr->getSource(); - if(!member) continue; - - uint32 queueSlot = member->AddBattleGroundQueueId(bgQueueTypeId);// add to queue - - // store entry point coords (same as leader entry point) - member->SetBattleGroundEntryPoint(_player->GetMapId(),_player->GetPositionX(),_player->GetPositionY(),_player->GetPositionZ(),_player->GetOrientation()); - - WorldPacket data; - // send status packet (in queue) - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, member->GetTeam(), queueSlot, STATUS_WAIT_QUEUE, 0, 0, arenatype, isRated); - member->GetSession()->SendPacket(&data); - sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(&data, bgTypeId); - member->GetSession()->SendPacket(&data); - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddPlayer(member, ginfo); - sLog.outDebug("Battleground: player joined queue for arena as group bg queue type %u bg type %u: GUID %u, NAME %s",bgQueueTypeId,bgTypeId,member->GetGUIDLow(), member->GetName()); - } - sLog.outDebug("Battleground: arena join as group end"); - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId, _player->GetBattleGroundQueueIdFromLevel(), arenatype, isRated, arenaRating); - } - else - { - uint32 queueSlot = _player->AddBattleGroundQueueId(bgQueueTypeId); - - // store entry point coords - _player->SetBattleGroundEntryPoint(_player->GetMapId(),_player->GetPositionX(),_player->GetPositionY(),_player->GetPositionZ(),_player->GetOrientation()); - - WorldPacket data; - // send status packet (in queue) - sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_WAIT_QUEUE, 0, 0, arenatype, isRated); - SendPacket(&data); - GroupQueueInfo * ginfo = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddGroup(_player, bgTypeId, arenatype, isRated, arenaRating); - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddPlayer(_player, ginfo); - sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId, _player->GetBattleGroundQueueIdFromLevel(), arenatype, isRated, arenaRating); - sLog.outDebug("Battleground: player joined queue for arena, skirmish, bg queue type %u bg type %u: GUID %u, NAME %s",bgQueueTypeId,bgTypeId,_player->GetGUIDLow(), _player->GetName()); - } -} - -void WorldSession::HandleBattleGroundReportAFK( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - uint64 playerGuid; - recv_data >> playerGuid; - Player *reportedPlayer = objmgr.GetPlayer(playerGuid); - - if(!reportedPlayer) - { - sLog.outDebug("WorldSession::HandleBattleGroundReportAFK: player not found"); - return; - } - - sLog.outDebug("WorldSession::HandleBattleGroundReportAFK: %s reported %s", _player->GetName(), reportedPlayer->GetName()); - - reportedPlayer->ReportedAfkBy(_player); -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "WorldPacket.h" +#include "Opcodes.h" +#include "Log.h" +#include "Player.h" +#include "ObjectMgr.h" +#include "WorldSession.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "Object.h" +#include "Chat.h" +#include "Language.h" +#include "BattleGroundMgr.h" +#include "BattleGroundWS.h" +#include "BattleGround.h" +#include "ArenaTeam.h" + +void WorldSession::HandleBattleGroundHelloOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + uint64 guid; + recv_data >> guid; + sLog.outDebug( "WORLD: Recvd CMSG_BATTLEMASTER_HELLO Message from: " I64FMT, guid); + + Creature *unit = ObjectAccessor::GetCreature(*_player, guid); + if(!unit) + return; + + if(!unit->isBattleMaster()) // it's not battlemaster + return; + + // Stop the npc if moving + unit->StopMoving(); + + uint32 bgTypeId = objmgr.GetBattleMasterBG(unit->GetEntry()); + + if(!_player->GetBGAccessByLevel(bgTypeId)) + { + // temp, must be gossip message... + SendNotification(LANG_YOUR_BG_LEVEL_REQ_ERROR); + return; + } + + SendBattlegGroundList(guid, bgTypeId); +} + +void WorldSession::SendBattlegGroundList( uint64 guid, uint32 bgTypeId ) +{ + WorldPacket data; + sBattleGroundMgr.BuildBattleGroundListPacket(&data, guid, _player, bgTypeId); + SendPacket( &data ); +} + +void WorldSession::HandleBattleGroundJoinOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 8+4+4+1); + + uint64 guid; + uint32 bgTypeId; + uint32 instanceId; + uint8 joinAsGroup; + Group * grp; + + recv_data >> guid; // battlemaster guid + recv_data >> bgTypeId; // battleground type id (DBC id) + recv_data >> instanceId; // instance id, 0 if First Available selected + recv_data >> joinAsGroup; // join as group + + if(bgTypeId >= MAX_BATTLEGROUND_TYPES) + { + sLog.outError("Battleground: invalid bgtype received. possible cheater? player guid %u",_player->GetGUIDLow()); + return; + } + + sLog.outDebug( "WORLD: Recvd CMSG_BATTLEMASTER_JOIN Message from: " I64FMT, guid); + + // can do this, since it's battleground, not arena + uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bgTypeId, 0); + + // ignore if we already in BG or BG queue + if(_player->InBattleGround()) + return; + + Creature *unit = ObjectAccessor::GetCreature(*_player, guid); + if(!unit) + return; + + if(!unit->isBattleMaster()) // it's not battlemaster + return; + + // get bg instance or bg template if instance not found + BattleGround * bg = 0; + if(instanceId) + BattleGround *bg = sBattleGroundMgr.GetBattleGround(instanceId); + + if(!bg && !(bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId))) + { + sLog.outError("Battleground: no available bg / template found"); + return; + } + + // check queueing conditions + if(!joinAsGroup) + { + // check Deserter debuff + if( !_player->CanJoinToBattleground() ) + { + WorldPacket data(SMSG_GROUP_JOINED_BATTLEGROUND, 4); + data << (uint32) 0xFFFFFFFE; + _player->GetSession()->SendPacket(&data); + return; + } + // check if already in queue + if (_player->GetBattleGroundQueueIndex(bgQueueTypeId) < PLAYER_MAX_BATTLEGROUND_QUEUES) + //player is already in this queue + return; + // check if has free queue slots + if(!_player->HasFreeBattleGroundQueueId()) + return; + } + else + { + grp = _player->GetGroup(); + // no group found, error + if(!grp) + return; + uint32 err = grp->CanJoinBattleGroundQueue(bgTypeId, bgQueueTypeId, 0, bg->GetMaxPlayersPerTeam(), false, 0); + switch(err) + { + // TODO: add error-based feedback to players in all cases + case BG_JOIN_ERR_GROUP_TOO_MANY: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_TOO_LARGE), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_OFFLINE_MEMBER: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_OFFLINE_MEMBER), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_MIXED_FACTION: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MIXED_FACTION), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_MIXED_LEVELS: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MIXED_LEVELS), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_GROUP_MEMBER_ALREADY_IN_QUEUE: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_ALREADY_IN_QUEUE), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_GROUP_DESERTER: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_DESERTER), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_ALL_QUEUES_USED: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_NO_FREE_QUEUE_SLOTS), NULL); + SendPacket(&data); + } + return; + break; + // all ok, can join + case BG_JOIN_ERR_OK: + break; + // these aren't possible outcomes in bgs + case BG_JOIN_ERR_GROUP_NOT_ENOUGH: + case BG_JOIN_ERR_MIXED_ARENATEAM: + return; + break; + // not the above? shouldn't happen, don't let join + default: + return; + break; + }; + } + + // if we're here, then the conditions to join a bg are met. We can proceed in joining. + + // _player->GetGroup() was already checked, grp is already initialized + if(joinAsGroup /* && _player->GetGroup()*/) + { + sLog.outDebug("Battleground: the following players are joining as group:"); + GroupQueueInfo * ginfo = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddGroup(_player, bgTypeId, 0, false, 0); + for(GroupReference *itr = grp->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *member = itr->getSource(); + if(!member) continue; // this should never happen + + uint32 queueSlot = member->AddBattleGroundQueueId(bgQueueTypeId); // add to queue + + // store entry point coords (same as leader entry point) + member->SetBattleGroundEntryPoint(_player->GetMapId(),_player->GetPositionX(),_player->GetPositionY(),_player->GetPositionZ(),_player->GetOrientation()); + + WorldPacket data; + // send status packet (in queue) + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, member->GetTeam(), queueSlot, STATUS_WAIT_QUEUE, 0, 0); + member->GetSession()->SendPacket(&data); + sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(&data, bgTypeId); + member->GetSession()->SendPacket(&data); + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddPlayer(member, ginfo); + sLog.outDebug("Battleground: player joined queue for bg queue type %u bg type %u: GUID %u, NAME %s",bgQueueTypeId,bgTypeId,member->GetGUIDLow(), member->GetName()); + } + sLog.outDebug("Battleground: group end"); + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId, _player->GetBattleGroundQueueIdFromLevel()); + } + else + { + // already checked if queueSlot is valid, now just get it + uint32 queueSlot = _player->AddBattleGroundQueueId(bgQueueTypeId); + // store entry point coords + _player->SetBattleGroundEntryPoint(_player->GetMapId(),_player->GetPositionX(),_player->GetPositionY(),_player->GetPositionZ(),_player->GetOrientation()); + + WorldPacket data; + // send status packet (in queue) + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_WAIT_QUEUE, 0, 0); + SendPacket(&data); + + GroupQueueInfo * ginfo = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddGroup(_player, bgTypeId, 0, false, 0); + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddPlayer(_player, ginfo); + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId, _player->GetBattleGroundQueueIdFromLevel()); + sLog.outDebug("Battleground: player joined queue for bg queue type %u bg type %u: GUID %u, NAME %s",bgQueueTypeId,bgTypeId,_player->GetGUIDLow(), _player->GetName()); + } +} + +void WorldSession::HandleBattleGroundPlayerPositionsOpcode( WorldPacket & /*recv_data*/ ) +{ + // empty opcode + sLog.outDebug("WORLD: Recvd MSG_BATTLEGROUND_PLAYER_POSITIONS Message"); + + BattleGround *bg = _player->GetBattleGround(); + if(!bg) // can't be received if player not in battleground + return; + + if(bg->GetTypeID() == BATTLEGROUND_WS) + { + uint32 count1 = 0; + uint32 count2 = 0; + + Player *ap = objmgr.GetPlayer(((BattleGroundWS*)bg)->GetAllianceFlagPickerGUID()); + if(ap) ++count2; + + Player *hp = objmgr.GetPlayer(((BattleGroundWS*)bg)->GetHordeFlagPickerGUID()); + if(hp) ++count2; + + WorldPacket data(MSG_BATTLEGROUND_PLAYER_POSITIONS, (4+4+16*count1+16*count2)); + data << count1; // alliance flag holders count + /*for(uint8 i = 0; i < count1; i++) + { + data << uint64(0); // guid + data << (float)0; // x + data << (float)0; // y + }*/ + data << count2; // horde flag holders count + if(ap) + { + data << (uint64)ap->GetGUID(); + data << (float)ap->GetPositionX(); + data << (float)ap->GetPositionY(); + } + if(hp) + { + data << (uint64)hp->GetGUID(); + data << (float)hp->GetPositionX(); + data << (float)hp->GetPositionY(); + } + + SendPacket(&data); + } +} + +void WorldSession::HandleBattleGroundPVPlogdataOpcode( WorldPacket & /*recv_data*/ ) +{ + sLog.outDebug( "WORLD: Recvd MSG_PVP_LOG_DATA Message"); + + BattleGround *bg = _player->GetBattleGround(); + if(!bg) + return; + + WorldPacket data; + sBattleGroundMgr.BuildPvpLogDataPacket(&data, bg); + SendPacket(&data); + + sLog.outDebug( "WORLD: Sent MSG_PVP_LOG_DATA Message"); +} + +void WorldSession::HandleBattleGroundListOpcode( WorldPacket &recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 4); + + sLog.outDebug( "WORLD: Recvd CMSG_BATTLEFIELD_LIST Message"); + + uint32 bgTypeId; + recv_data >> bgTypeId; // id from DBC + + if(bgTypeId >= MAX_BATTLEGROUND_TYPES) + { + sLog.outError("Battleground: invalid bgtype received."); + return; + } + + BattlemasterListEntry const* bl = sBattlemasterListStore.LookupEntry(bgTypeId); + + if(!bl) + return; + + WorldPacket data; + sBattleGroundMgr.BuildBattleGroundListPacket(&data, _player->GetGUID(), _player, bgTypeId); + SendPacket( &data ); +} + +void WorldSession::HandleBattleGroundPlayerPortOpcode( WorldPacket &recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 1+1+4+2+1); + + sLog.outDebug( "WORLD: Recvd CMSG_BATTLEFIELD_PORT Message"); + + uint8 type; // arenatype if arena + uint8 unk2; // unk, can be 0x0 (may be if was invited?) and 0x1 + uint32 instanceId; + uint32 bgTypeId; // type id from dbc + uint16 unk; // 0x1F90 constant? + uint8 action; // enter battle 0x1, leave queue 0x0 + + recv_data >> type >> unk2 >> bgTypeId >> unk >> action; + + if(bgTypeId >= MAX_BATTLEGROUND_TYPES) + { + sLog.outError("Battleground: invalid bgtype received."); + // update battleground slots for the player to fix his UI and sent data. + // this is a HACK, I don't know why the client starts sending invalid packets in the first place. + // it usually happens with extremely high latency (if debugging / stepping in the code for example) + if(_player->InBattleGroundQueue()) + { + // update all queues, send invitation info if player is invited, queue info if queued + for (uint32 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) + { + uint32 queue_id = _player->GetBattleGroundQueueId(i); + if(!queue_id) + continue; + BattleGroundQueue::QueuedPlayersMap::iterator itrPlayerStatus = sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); + // if the player is not in queue, contine + if(itrPlayerStatus == sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end()) + continue; + + // no group information, this should never happen + if(!itrPlayerStatus->second.GroupInfo) + continue; + + BattleGround * bg = NULL; + + // get possibly needed data from groupinfo + bgTypeId = itrPlayerStatus->second.GroupInfo->BgTypeId; + uint8 arenatype = itrPlayerStatus->second.GroupInfo->ArenaType; + uint8 israted = itrPlayerStatus->second.GroupInfo->IsRated; + uint8 status = 0; + + + if(!itrPlayerStatus->second.GroupInfo->IsInvitedToBGInstanceGUID) + { + // not invited to bg, get template + bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + status = STATUS_WAIT_QUEUE; + } + else + { + // get the bg we're invited to + BattleGround * bg = sBattleGroundMgr.GetBattleGround(itrPlayerStatus->second.GroupInfo->IsInvitedToBGInstanceGUID); + status = STATUS_WAIT_JOIN; + } + + // if bg not found, then continue + if(!bg) + continue; + + // don't invite if already in the instance + if(_player->InBattleGround() && _player->GetBattleGround() && _player->GetBattleGround()->GetInstanceID() == bg->GetInstanceID()) + continue; + + // re - invite player with proper data + WorldPacket data; + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, itrPlayerStatus->second.GroupInfo->Team?itrPlayerStatus->second.GroupInfo->Team:_player->GetTeam(), i, status, INVITE_ACCEPT_WAIT_TIME, 0, arenatype, israted); + SendPacket(&data); + } + } + return; + } + + uint32 bgQueueTypeId = 0; + // get the bg what we were invited to + BattleGroundQueue::QueuedPlayersMap::iterator itrPlayerStatus; + bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bgTypeId,type); + itrPlayerStatus = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); + + if(itrPlayerStatus == sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end()) + { + sLog.outError("Battleground: itrplayerstatus not found."); + return; + } + instanceId = itrPlayerStatus->second.GroupInfo->IsInvitedToBGInstanceGUID; + + // if action == 1, then instanceId is _required_ + if(!instanceId && action == 1) + { + sLog.outError("Battleground: instance not found."); + return; + } + + BattleGround *bg = sBattleGroundMgr.GetBattleGround(instanceId); + + // bg template might and must be used in case of leaving queue, when instance is not created yet + if(!bg && action == 0) + bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + + if(!bg) + { + sLog.outError("Battleground: bg not found."); + return; + } + + bgTypeId = bg->GetTypeID(); + + if(_player->InBattleGroundQueue()) + { + uint32 queueSlot = 0; + uint32 team = 0; + uint32 arenatype = 0; + uint32 israted = 0; + uint32 rating = 0; + // get the team info from the queue + BattleGroundQueue::QueuedPlayersMap::iterator pitr = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); + if(pitr !=sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end() + && pitr->second.GroupInfo ) + { + team = pitr->second.GroupInfo->Team; + arenatype = pitr->second.GroupInfo->ArenaType; + israted = pitr->second.GroupInfo->IsRated; + rating = pitr->second.GroupInfo->ArenaTeamRating; + } + else + { + sLog.outError("Battleground: Invalid player queue info!"); + return; + } + WorldPacket data; + switch(action) + { + case 1: // port to battleground + if(!_player->IsInvitedForBattleGroundQueueType(bgQueueTypeId)) + return; // cheating? + // resurrect the player + if(!_player->isAlive()) + { + _player->ResurrectPlayer(1.0f,false); + _player->SpawnCorpseBones(); + } + // stop taxi flight at port + if(_player->isInFlight()) + { + _player->GetMotionMaster()->MovementExpired(); + _player->m_taxi.ClearTaxiDestinations(); + } + _player->RemoveFromGroup(); + queueSlot = _player->GetBattleGroundQueueIndex(bgQueueTypeId); + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_IN_PROGRESS, 0, bg->GetStartTime()); + _player->GetSession()->SendPacket(&data); + // remove battleground queue status from BGmgr + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].RemovePlayer(_player->GetGUID(), false); + // this is still needed here if battleground "jumping" shouldn't add deserter debuff + // also this required to prevent stuck at old battleground after SetBattleGroundId set to new + if( BattleGround *currentBg = _player->GetBattleGround() ) + currentBg->RemovePlayerAtLeave(_player->GetGUID(), false, true); + + // set the destination instance id + _player->SetBattleGroundId(bg->GetInstanceID()); + // set the destination team + _player->SetBGTeam(team); + // bg->HandleBeforeTeleportToBattleGround(_player); + sBattleGroundMgr.SendToBattleGround(_player, instanceId); + // add only in HandleMoveWorldPortAck() + // bg->AddPlayer(_player,team); + sLog.outDebug("Battleground: player %s (%u) joined battle for bg %u, bgtype %u, queue type %u.",_player->GetName(),_player->GetGUIDLow(),bg->GetInstanceID(),bg->GetTypeID(),bgQueueTypeId); + break; + case 0: // leave queue + queueSlot = _player->GetBattleGroundQueueIndex(bgQueueTypeId); + _player->RemoveBattleGroundQueueId(bgQueueTypeId); // must be called this way, because if you move this call to queue->removeplayer, it causes bugs + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_NONE, 0, 0); + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].RemovePlayer(_player->GetGUID(), true); + // player left queue, we should update it, maybe now his group fits in + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId,_player->GetBattleGroundQueueIdFromLevel(),arenatype,israted,rating); + SendPacket(&data); + sLog.outDebug("Battleground: player %s (%u) left queue for bgtype %u, queue type %u.",_player->GetName(),_player->GetGUIDLow(),bg->GetTypeID(),bgQueueTypeId); + break; + default: + sLog.outError("Battleground port: unknown action %u", action); + break; + } + } +} + +void WorldSession::HandleBattleGroundLeaveOpcode( WorldPacket & /*recv_data*/ ) +{ + //CHECK_PACKET_SIZE(recv_data, 1+1+4+2); + + sLog.outDebug( "WORLD: Recvd CMSG_LEAVE_BATTLEFIELD Message"); + + //uint8 unk1, unk2; + //uint32 bgTypeId; // id from DBC + //uint16 unk3; + + //recv_data >> unk1 >> unk2 >> bgTypeId >> unk3; - no used currently + + //if(bgTypeId >= MAX_BATTLEGROUND_TYPES) // cheating? but not important in this case + // return; + + // not allow leave battleground in combat + if(_player->isInCombat()) + if(BattleGround* bg = _player->GetBattleGround()) + if(bg->GetStatus() != STATUS_WAIT_LEAVE) + return; + + _player->LeaveBattleground(); +} + +void WorldSession::HandleBattlefieldStatusOpcode( WorldPacket & /*recv_data*/ ) +{ + // empty opcode + sLog.outDebug( "WORLD: Battleground status" ); + + WorldPacket data; + + // TODO: we must put player back to battleground in case disconnect (< 5 minutes offline time) or teleport player on login(!) from battleground map to entry point + if(_player->InBattleGround()) + { + BattleGround *bg = _player->GetBattleGround(); + if(bg) + { + uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bg->GetTypeID(), bg->GetArenaType()); + uint32 queueSlot = _player->GetBattleGroundQueueIndex(bgQueueTypeId); + if((bg->GetStatus() <= STATUS_IN_PROGRESS)) + { + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_IN_PROGRESS, 0, bg->GetStartTime()); + SendPacket(&data); + } + for (uint32 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) + { + uint32 queue_id = _player->GetBattleGroundQueueId(i); // battlegroundqueueid stores the type id, not the instance id, so this is definitely wrong + uint8 arenatype = sBattleGroundMgr.BGArenaType(queue_id); + uint8 isRated = 0; + if (i == queueSlot || !queue_id) // we need to get the instance ids + continue; + BattleGroundQueue::QueuedPlayersMap::iterator itrPlayerStatus = sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); + if(itrPlayerStatus == sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end()) + continue; + if(itrPlayerStatus->second.GroupInfo) + { + arenatype = itrPlayerStatus->second.GroupInfo->ArenaType; + isRated = itrPlayerStatus->second.GroupInfo->IsRated; + } + BattleGround *bg2 = sBattleGroundMgr.GetBattleGroundTemplate(sBattleGroundMgr.BGTemplateId(queue_id)); // try this + if(bg2) + { + //in this call is small bug, this call should be filled by player's waiting time in queue + //this call nulls all timers for client : + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg2, _player->GetTeam(), i, STATUS_WAIT_QUEUE, 0, 0,arenatype,isRated); + SendPacket(&data); + } + } + } + } + else + { + // we should update all queues? .. i'm not sure if this code is correct + for (uint32 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) + { + uint32 queue_id = _player->GetBattleGroundQueueId(i); + if(!queue_id) + continue; + uint32 bgTypeId = sBattleGroundMgr.BGTemplateId(queue_id); + uint8 arenatype = sBattleGroundMgr.BGArenaType(queue_id); + uint8 isRated = 0; + BattleGround *bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + BattleGroundQueue::QueuedPlayersMap::iterator itrPlayerStatus = sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].find(_player->GetGUID()); + if(itrPlayerStatus == sBattleGroundMgr.m_BattleGroundQueues[queue_id].m_QueuedPlayers[_player->GetBattleGroundQueueIdFromLevel()].end()) + continue; + if(itrPlayerStatus->second.GroupInfo) + { + arenatype = itrPlayerStatus->second.GroupInfo->ArenaType; + isRated = itrPlayerStatus->second.GroupInfo->IsRated; + } + if(bg && queue_id) + { + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), i, STATUS_WAIT_QUEUE, 0, 0, arenatype, isRated); + SendPacket(&data); + } + } + } +/* else // not sure if it needed... + { + for (uint32 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) + { + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, NULL, _player->GetTeam(),i , STATUS_NONE, 0, 0); + SendPacket(&data); + } + }*/ +} + +void WorldSession::HandleAreaSpiritHealerQueryOpcode( WorldPacket & recv_data ) +{ + sLog.outDebug("WORLD: CMSG_AREA_SPIRIT_HEALER_QUERY"); + + CHECK_PACKET_SIZE(recv_data, 8); + + BattleGround *bg = _player->GetBattleGround(); + if(!bg) + return; + + uint64 guid; + recv_data >> guid; + + Creature *unit = ObjectAccessor::GetCreature(*_player, guid); + if(!unit) + return; + + if(!unit->isSpiritService()) // it's not spirit service + return; + + sBattleGroundMgr.SendAreaSpiritHealerQueryOpcode(_player, bg, guid); +} + +void WorldSession::HandleAreaSpiritHealerQueueOpcode( WorldPacket & recv_data ) +{ + sLog.outDebug("WORLD: CMSG_AREA_SPIRIT_HEALER_QUEUE"); + + CHECK_PACKET_SIZE(recv_data, 8); + + BattleGround *bg = _player->GetBattleGround(); + if(!bg) + return; + + uint64 guid; + recv_data >> guid; + + Creature *unit = ObjectAccessor::GetCreature(*_player, guid); + if(!unit) + return; + + if(!unit->isSpiritService()) // it's not spirit service + return; + + bg->AddPlayerToResurrectQueue(guid, _player->GetGUID()); +} + +void WorldSession::HandleBattleGroundArenaJoin( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 8+1+1+1); + + sLog.outDebug("WORLD: CMSG_BATTLEMASTER_JOIN_ARENA"); + recv_data.hexlike(); + + // ignore if we already in BG or BG queue + if(_player->InBattleGround()) + return; + + uint64 guid; // arena Battlemaster guid + uint8 type; // 2v2, 3v3 or 5v5 + uint8 asGroup; // asGroup + uint8 isRated; // isRated + Group * grp; + + recv_data >> guid >> type >> asGroup >> isRated; + + Creature *unit = ObjectAccessor::GetCreature(*_player, guid); + if(!unit) + return; + + if(!unit->isBattleMaster()) // it's not battle master + return; + + uint8 arenatype = 0; + uint32 arenaRating = 0; + + switch(type) + { + case 0: + arenatype = ARENA_TYPE_2v2; + break; + case 1: + arenatype = ARENA_TYPE_3v3; + break; + case 2: + arenatype = ARENA_TYPE_5v5; + break; + default: + sLog.outError("Unknown arena type %u at HandleBattleGroundArenaJoin()", type); + return; + } + + //check existance + BattleGround* bg = NULL; + if( !(bg = sBattleGroundMgr.GetBattleGroundTemplate(BATTLEGROUND_AA)) ) + { + sLog.outError("Battleground: template bg (all arenas) not found"); + return; + } + + uint8 bgTypeId = bg->GetTypeID(); + uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bgTypeId, arenatype); + + // check queueing conditions + if(!asGroup) + { + // check if already in queue + if (_player->GetBattleGroundQueueIndex(bgQueueTypeId) < PLAYER_MAX_BATTLEGROUND_QUEUES) + //player is already in this queue + return; + // check if has free queue slots + if(!_player->HasFreeBattleGroundQueueId()) + return; + } + else + { + grp = _player->GetGroup(); + // no group found, error + if(!grp) + return; + uint32 err = grp->CanJoinBattleGroundQueue(bgTypeId, bgQueueTypeId, arenatype, arenatype, (bool)isRated, type); + switch(err) + { + // TODO: add error-based feedback to players in all cases + case BG_JOIN_ERR_GROUP_TOO_MANY: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_ARENA_GROUP_TOO_LARGE), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_GROUP_NOT_ENOUGH: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_ARENA_NOT_ENOUGH_PLAYERS), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_MIXED_ARENATEAM: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_ARENA_YOUR_TEAM_ONLY), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_OFFLINE_MEMBER: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_OFFLINE_MEMBER), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_MIXED_FACTION: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MIXED_FACTION), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_MIXED_LEVELS: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MIXED_LEVELS), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_GROUP_MEMBER_ALREADY_IN_QUEUE: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_ALREADY_IN_QUEUE), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_GROUP_DESERTER: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_DESERTER), NULL); + SendPacket(&data); + } + return; + break; + case BG_JOIN_ERR_ALL_QUEUES_USED: + { + WorldPacket data; + ChatHandler::FillMessageData(&data, NULL, CHAT_MSG_BG_SYSTEM_NEUTRAL, LANG_UNIVERSAL, NULL, 0, GetMangosString(LANG_BG_GROUP_MEMBER_NO_FREE_QUEUE_SLOTS), NULL); + SendPacket(&data); + } + return; + break; + // all ok, can join + case BG_JOIN_ERR_OK: + break; + // not the above? shouldn't happen, don't let join + default: + return; + break; + }; + } + + uint32 ateamId = 0; + + if(isRated) + { + ateamId = _player->GetArenaTeamId(type); + // check real arenateam existence only here (if it was moved to group->CanJoin .. () then we would ahve to get it twice) + ArenaTeam * at = objmgr.GetArenaTeamById(ateamId); + if(!at) + { + _player->GetSession()->SendNotInArenaTeamPacket(arenatype); + return; + } + // get the team rating for queueing + arenaRating = at->GetRating(); + // the arenateam id must match for everyone in the group + // get the personal ratings for queueing + uint32 avg_pers_rating = 0; + for(GroupReference *itr = grp->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *member = itr->getSource(); + + // calc avg personal rating + avg_pers_rating += member->GetUInt32Value(PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + (type*6) + 5); + } + + if( arenatype ) + avg_pers_rating /= arenatype; + + // if avg personal rating is more than 150 points below the team’s rating, the team will be queued against an opponent matching or similar to the average personal rating + if(avg_pers_rating + 150 < arenaRating) + arenaRating = avg_pers_rating; + } + + if(asGroup) + { + GroupQueueInfo * ginfo = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddGroup(_player, bgTypeId, arenatype, isRated, arenaRating, ateamId); + sLog.outDebug("Battleground: arena join as group start"); + if(isRated) + sLog.outDebug("Battleground: arena team id %u, leader %s queued with rating %u for type %u",_player->GetArenaTeamId(type),_player->GetName(),arenaRating,arenatype); + for(GroupReference *itr = grp->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *member = itr->getSource(); + if(!member) continue; + + uint32 queueSlot = member->AddBattleGroundQueueId(bgQueueTypeId);// add to queue + + // store entry point coords (same as leader entry point) + member->SetBattleGroundEntryPoint(_player->GetMapId(),_player->GetPositionX(),_player->GetPositionY(),_player->GetPositionZ(),_player->GetOrientation()); + + WorldPacket data; + // send status packet (in queue) + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, member->GetTeam(), queueSlot, STATUS_WAIT_QUEUE, 0, 0, arenatype, isRated); + member->GetSession()->SendPacket(&data); + sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(&data, bgTypeId); + member->GetSession()->SendPacket(&data); + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddPlayer(member, ginfo); + sLog.outDebug("Battleground: player joined queue for arena as group bg queue type %u bg type %u: GUID %u, NAME %s",bgQueueTypeId,bgTypeId,member->GetGUIDLow(), member->GetName()); + } + sLog.outDebug("Battleground: arena join as group end"); + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId, _player->GetBattleGroundQueueIdFromLevel(), arenatype, isRated, arenaRating); + } + else + { + uint32 queueSlot = _player->AddBattleGroundQueueId(bgQueueTypeId); + + // store entry point coords + _player->SetBattleGroundEntryPoint(_player->GetMapId(),_player->GetPositionX(),_player->GetPositionY(),_player->GetPositionZ(),_player->GetOrientation()); + + WorldPacket data; + // send status packet (in queue) + sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, _player->GetTeam(), queueSlot, STATUS_WAIT_QUEUE, 0, 0, arenatype, isRated); + SendPacket(&data); + GroupQueueInfo * ginfo = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddGroup(_player, bgTypeId, arenatype, isRated, arenaRating); + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].AddPlayer(_player, ginfo); + sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgTypeId, _player->GetBattleGroundQueueIdFromLevel(), arenatype, isRated, arenaRating); + sLog.outDebug("Battleground: player joined queue for arena, skirmish, bg queue type %u bg type %u: GUID %u, NAME %s",bgQueueTypeId,bgTypeId,_player->GetGUIDLow(), _player->GetName()); + } +} + +void WorldSession::HandleBattleGroundReportAFK( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + uint64 playerGuid; + recv_data >> playerGuid; + Player *reportedPlayer = objmgr.GetPlayer(playerGuid); + + if(!reportedPlayer) + { + sLog.outDebug("WorldSession::HandleBattleGroundReportAFK: player not found"); + return; + } + + sLog.outDebug("WorldSession::HandleBattleGroundReportAFK: %s reported %s", _player->GetName(), reportedPlayer->GetName()); + + reportedPlayer->ReportedAfkBy(_player); +} diff --git a/src/game/CharacterHandler.cpp b/src/game/CharacterHandler.cpp index 929a0543f0b..f6dc47e7b60 100644 --- a/src/game/CharacterHandler.cpp +++ b/src/game/CharacterHandler.cpp @@ -1,1065 +1,1066 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "Database/DatabaseEnv.h" -#include "WorldPacket.h" -#include "SharedDefines.h" -#include "WorldSession.h" -#include "Opcodes.h" -#include "Log.h" -#include "World.h" -#include "ObjectMgr.h" -#include "Player.h" -#include "Guild.h" -#include "UpdateMask.h" -#include "Auth/md5.h" -#include "MapManager.h" -#include "ObjectAccessor.h" -#include "Group.h" -#include "Database/DatabaseImpl.h" -#include "PlayerDump.h" -#include "SocialMgr.h" -#include "Util.h" - -class LoginQueryHolder : public SqlQueryHolder -{ - private: - uint32 m_accountId; - uint64 m_guid; - public: - LoginQueryHolder(uint32 accountId, uint64 guid) - : m_accountId(accountId), m_guid(guid) { } - uint64 GetGuid() const { return m_guid; } - uint32 GetAccountId() const { return m_accountId; } - bool Initialize(); -}; - -bool LoginQueryHolder::Initialize() -{ - SetSize(MAX_PLAYER_LOGIN_QUERY); - - bool res = true; - - // NOTE: all fields in `characters` must be read to prevent lost character data at next save in case wrong DB structure. - // !!! NOTE: including unused `zone`,`online` - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADFROM, "SELECT guid, account, data, name, race, class, position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, trans_x, trans_y, trans_z, trans_o, transguid, gmstate, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeon_difficulty FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGROUP, "SELECT leaderGuid FROM group_member WHERE memberGuid ='%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES, "SELECT id, permanent, map, difficulty, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = '%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADAURAS, "SELECT caster_guid,spell,effect_index,amount,maxduration,remaintime,remaincharges FROM character_aura WHERE guid = '%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSPELLS, "SELECT spell,slot,active,disabled FROM character_spell WHERE guid = '%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS, "SELECT quest,status,rewarded,explored,timer,mobcount1,mobcount2,mobcount3,mobcount4,itemcount1,itemcount2,itemcount3,itemcount4 FROM character_queststatus WHERE guid = '%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS,"SELECT quest,time FROM character_queststatus_daily WHERE guid = '%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADTUTORIALS, "SELECT tut0,tut1,tut2,tut3,tut4,tut5,tut6,tut7 FROM character_tutorial WHERE account = '%u' AND realmid = '%u'", GetAccountId(), realmID); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADREPUTATION, "SELECT faction,standing,flags FROM character_reputation WHERE guid = '%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADINVENTORY, "SELECT data,bag,slot,item,item_template FROM character_inventory JOIN item_instance ON character_inventory.item = item_instance.guid WHERE character_inventory.guid = '%u' ORDER BY bag,slot", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADACTIONS, "SELECT button,action,type,misc FROM character_action WHERE guid = '%u' ORDER BY button", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADMAILCOUNT, "SELECT COUNT(id) FROM mail WHERE receiver = '%u' AND (checked & 1)=0 AND deliver_time <= '" I64FMTD "'", GUID_LOPART(m_guid),(uint64)time(NULL)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADMAILDATE, "SELECT MIN(deliver_time) FROM mail WHERE receiver = '%u' AND (checked & 1)=0", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSOCIALLIST, "SELECT friend,flags,note FROM character_social WHERE guid = '%u' LIMIT 255", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADHOMEBIND, "SELECT map,zone,position_x,position_y,position_z FROM character_homebind WHERE guid = '%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSPELLCOOLDOWNS, "SELECT spell,item,time FROM character_spell_cooldown WHERE guid = '%u'", GUID_LOPART(m_guid)); - if(sWorld.getConfig(CONFIG_DECLINED_NAMES_USED)) - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADDECLINEDNAMES, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_declinedname WHERE guid = '%u'",GUID_LOPART(m_guid)); - // in other case still be dummy query - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGUILD, "SELECT guildid,rank FROM guild_member WHERE guid = '%u'", GUID_LOPART(m_guid)); - - return res; -} - -// don't call WorldSession directly -// it may get deleted before the query callbacks get executed -// instead pass an account id to this handler -class CharacterHandler -{ - public: - void HandleCharEnumCallback(QueryResult * result, uint32 account) - { - WorldSession * session = sWorld.FindSession(account); - if(!session) - { - delete result; - return; - } - session->HandleCharEnum(result); - } - void HandlePlayerLoginCallback(QueryResult * /*dummy*/, SqlQueryHolder * holder) - { - if (!holder) return; - WorldSession *session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId()); - if(!session) - { - delete holder; - return; - } - session->HandlePlayerLogin((LoginQueryHolder*)holder); - } -} chrHandler; - -void WorldSession::HandleCharEnum(QueryResult * result) -{ - // keys can be non cleared if player open realm list and close it by 'cancel' - loginDatabase.PExecute("UPDATE account SET v = '0', s = '0' WHERE id = '%u'", GetAccountId()); - - WorldPacket data(SMSG_CHAR_ENUM, 100); // we guess size - - uint8 num = 0; - - data << num; - - if( result ) - { - Player *plr = new Player(this); - do - { - sLog.outDetail("Loading char guid %u from account %u.",(*result)[0].GetUInt32(),GetAccountId()); - - if(plr->MinimalLoadFromDB( result, (*result)[0].GetUInt32() )) - { - plr->BuildEnumData( result, &data ); - ++num; - } - } - while( result->NextRow() ); - - delete plr; - delete result; - } - - data.put(0, num); - - SendPacket( &data ); -} - -void WorldSession::HandleCharEnumOpcode( WorldPacket & /*recv_data*/ ) -{ - /// get all the data necessary for loading all characters (along with their pets) on the account - CharacterDatabase.AsyncPQuery(&chrHandler, &CharacterHandler::HandleCharEnumCallback, GetAccountId(), - !sWorld.getConfig(CONFIG_DECLINED_NAMES_USED) ? - // ------- Query Without Declined Names -------- - // 0 1 2 3 4 5 6 7 8 - "SELECT characters.data, characters.name, characters.position_x, characters.position_y, characters.position_z, characters.map, characters.totaltime, characters.leveltime, characters.at_login, " - // 9 10 11 - "character_pet.entry, character_pet.modelid, character_pet.level " - "FROM characters LEFT JOIN character_pet ON characters.guid=character_pet.owner AND character_pet.slot='0' " - "WHERE characters.account = '%u' ORDER BY characters.guid" - : - // --------- Query With Declined Names --------- - // 0 1 2 3 4 5 6 7 8 - "SELECT characters.data, characters.name, characters.position_x, characters.position_y, characters.position_z, characters.map, characters.totaltime, characters.leveltime, characters.at_login, " - // 9 10 11 12 - "character_pet.entry, character_pet.modelid, character_pet.level, genitive " - "FROM characters LEFT JOIN character_pet ON characters.guid = character_pet.owner AND character_pet.slot='0' " - "LEFT JOIN character_declinedname ON characters.guid = character_declinedname.guid " - "WHERE characters.account = '%u' ORDER BY characters.guid", - GetAccountId()); -} - -void WorldSession::HandleCharCreateOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,1+1+1+1+1+1+1+1+1+1); - - std::string name; - uint8 race_,class_; - bool pTbc = this->IsTBC() && sWorld.getConfig(CONFIG_EXPANSION) > 0; - recv_data >> name; - - // recheck with known string size - CHECK_PACKET_SIZE(recv_data,(name.size()+1)+1+1+1+1+1+1+1+1+1); - - recv_data >> race_; - recv_data >> class_; - - WorldPacket data(SMSG_CHAR_CREATE, 1); // returned with diff.values in all cases - - if(GetSecurity() == SEC_PLAYER) - { - if(uint32 mask = sWorld.getConfig(CONFIG_CHARACTERS_CREATING_DISABLED)) - { - bool disabled = false; - - uint32 team = Player::TeamForRace(race_); - switch(team) - { - case ALLIANCE: disabled = mask & (1<<0); break; - case HORDE: disabled = mask & (1<<1); break; - } - - if(disabled) - { - data << (uint8)CHAR_CREATE_DISABLED; - SendPacket( &data ); - return; - } - } - } - - if (!sChrClassesStore.LookupEntry(class_)|| - !sChrRacesStore.LookupEntry(race_)) - { - data << (uint8)CHAR_CREATE_FAILED; - SendPacket( &data ); - sLog.outError("Class: %u or Race %u not found in DBC (Wrong DBC files?) or Cheater?", class_, race_); - return; - } - - // prevent character creating Expansion race without Expansion account - if (!pTbc&&(race_>RACE_TROLL)) - { - data << (uint8)CHAR_CREATE_EXPANSION; - sLog.outError("No Expansion Account:[%d] but tried to Create TBC character",GetAccountId()); - SendPacket( &data ); - return; - } - - // prevent character creating with invalid name - if(!normalizePlayerName(name)) - { - data << (uint8)CHAR_NAME_INVALID_CHARACTER; - SendPacket( &data ); - sLog.outError("Account:[%d] but tried to Create character with empty [name] ",GetAccountId()); - return; - } - - // check name limitations - if(!ObjectMgr::IsValidName(name,true)) - { - data << (uint8)CHAR_NAME_INVALID_CHARACTER; - SendPacket( &data ); - return; - } - - if(GetSecurity() == SEC_PLAYER && objmgr.IsReservedName(name)) - { - data << (uint8)CHAR_NAME_RESERVED; - SendPacket( &data ); - return; - } - - if(objmgr.GetPlayerGUIDByName(name)) - { - data << (uint8)CHAR_CREATE_NAME_IN_USE; - SendPacket( &data ); - return; - } - - QueryResult *resultacct = loginDatabase.PQuery("SELECT SUM(numchars) FROM realmcharacters WHERE acctid = '%d'", GetAccountId()); - if ( resultacct ) - { - Field *fields=resultacct->Fetch(); - uint32 acctcharcount = fields[0].GetUInt32(); - delete resultacct; - - if (acctcharcount >= sWorld.getConfig(CONFIG_CHARACTERS_PER_ACCOUNT)) - { - data << (uint8)CHAR_CREATE_ACCOUNT_LIMIT; - SendPacket( &data ); - return; - } - } - - QueryResult *result = CharacterDatabase.PQuery("SELECT COUNT(guid) FROM characters WHERE account = '%d'", GetAccountId()); - uint8 charcount = 0; - if ( result ) - { - Field *fields=result->Fetch(); - charcount = fields[0].GetUInt8(); - delete result; - - if (charcount >= sWorld.getConfig(CONFIG_CHARACTERS_PER_REALM)) - { - data << (uint8)CHAR_CREATE_SERVER_LIMIT; - SendPacket( &data ); - return; - } - } - - bool AllowTwoSideAccounts = !sWorld.IsPvPRealm() || sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS) || GetSecurity() > SEC_PLAYER; - uint32 skipCinematics = sWorld.getConfig(CONFIG_SKIP_CINEMATICS); - - bool have_same_race = false; - if(!AllowTwoSideAccounts || skipCinematics == 1) - { - QueryResult *result2 = CharacterDatabase.PQuery("SELECT DISTINCT race FROM characters WHERE account = '%u' %s", GetAccountId(),skipCinematics == 1 ? "" : "LIMIT 1"); - if(result2) - { - uint32 team_= Player::TeamForRace(race_); - - Field* field = result2->Fetch(); - uint8 race = field[0].GetUInt32(); - - // need to check team only for first character - // TODO: what to if account already has characters of both races? - if (!AllowTwoSideAccounts) - { - uint32 team=0; - if(race > 0) - team = Player::TeamForRace(race); - - if(team != team_) - { - data << (uint8)CHAR_CREATE_PVP_TEAMS_VIOLATION; - SendPacket( &data ); - delete result2; - return; - } - } - - if (skipCinematics == 1) - { - // TODO: check if cinematic already shown? (already logged in?; cinematic field) - while (race_ != race && result2->NextRow()) - { - field = result2->Fetch(); - race = field[0].GetUInt32(); - } - have_same_race = race_ == race; - } - delete result2; - } - } - - // extract other data required for player creating - uint8 gender, skin, face, hairStyle, hairColor, facialHair, outfitId; - recv_data >> gender >> skin >> face; - recv_data >> hairStyle >> hairColor >> facialHair >> outfitId; - - Player * pNewChar = new Player(this); - if(!pNewChar->Create( objmgr.GenerateLowGuid(HIGHGUID_PLAYER), name, race_, class_, gender, skin, face, hairStyle, hairColor, facialHair, outfitId )) - { - // Player not create (race/class problem?) - delete pNewChar; - - data << (uint8)CHAR_CREATE_ERROR; - SendPacket( &data ); - - return; - } - - if(have_same_race && skipCinematics == 1 || skipCinematics == 2) - pNewChar->setCinematic(1); // not show intro - - // Player created, save it now - pNewChar->SaveToDB(); - charcount+=1; - - loginDatabase.PExecute("DELETE FROM realmcharacters WHERE acctid= '%d' AND realmid = '%d'", GetAccountId(), realmID); - loginDatabase.PExecute("INSERT INTO realmcharacters (numchars, acctid, realmid) VALUES (%u, %u, %u)", charcount, GetAccountId(), realmID); - - delete pNewChar; // created only to call SaveToDB() - - data << (uint8)CHAR_CREATE_SUCCESS; - SendPacket( &data ); - - std::string IP_str = GetRemoteAddress().c_str(); - sLog.outBasic("Account: %d (IP: %s) Create Character:[%s]",GetAccountId(),IP_str.c_str(),name.c_str()); - sLog.outChar("Account: %d (IP: %s) Create Character:[%s]",GetAccountId(),IP_str.c_str(),name.c_str()); -} - -void WorldSession::HandleCharDeleteOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,8); - - uint64 guid; - recv_data >> guid; - - // can't delete loaded character - if(objmgr.GetPlayer(guid)) - return; - - uint32 accountId = 0; - std::string name; - - // is guild leader - if(objmgr.GetGuildByLeader(guid)) - { - WorldPacket data(SMSG_CHAR_DELETE, 1); - data << (uint8)CHAR_DELETE_FAILED_GUILD_LEADER; - SendPacket( &data ); - return; - } - - // is arena team captain - if(objmgr.GetArenaTeamByCapitan(guid)) - { - WorldPacket data(SMSG_CHAR_DELETE, 1); - data << (uint8)CHAR_DELETE_FAILED_ARENA_CAPTAIN; - SendPacket( &data ); - return; - } - - QueryResult *result = CharacterDatabase.PQuery("SELECT account,name FROM characters WHERE guid='%u'", GUID_LOPART(guid)); - if(result) - { - Field *fields = result->Fetch(); - accountId = fields[0].GetUInt32(); - name = fields[1].GetCppString(); - delete result; - } - - // prevent deleting other players' characters using cheating tools - if(accountId != GetAccountId()) - return; - - std::string IP_str = GetRemoteAddress(); - sLog.outBasic("Account: %d (IP: %s) Delete Character:[%s] (guid:%u)",GetAccountId(),IP_str.c_str(),name.c_str(),GUID_LOPART(guid)); - sLog.outChar("Account: %d (IP: %s) Delete Character:[%s] (guid: %u)",GetAccountId(),IP_str.c_str(),name.c_str(),GUID_LOPART(guid)); - - if(sLog.IsOutCharDump()) // optimize GetPlayerDump call - { - std::string dump = PlayerDumpWriter().GetDump(GUID_LOPART(guid)); - sLog.outCharDump(dump.c_str(),GetAccountId(),GUID_LOPART(guid),name.c_str()); - } - - Player::DeleteFromDB(guid, GetAccountId()); - - WorldPacket data(SMSG_CHAR_DELETE, 1); - data << (uint8)CHAR_DELETE_SUCCESS; - SendPacket( &data ); -} - -void WorldSession::HandlePlayerLoginOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,8); - - m_playerLoading = true; - uint64 playerGuid = 0; - - DEBUG_LOG( "WORLD: Recvd Player Logon Message" ); - - recv_data >> playerGuid; - - LoginQueryHolder *holder = new LoginQueryHolder(GetAccountId(), playerGuid); - if(!holder->Initialize()) - { - delete holder; // delete all unprocessed queries - m_playerLoading = false; - return; - } - - CharacterDatabase.DelayQueryHolder(&chrHandler, &CharacterHandler::HandlePlayerLoginCallback, holder); -} - -void WorldSession::HandlePlayerLogin(LoginQueryHolder * holder) -{ - uint64 playerGuid = holder->GetGuid(); - - Player* pCurrChar = new Player(this); - pCurrChar->GetMotionMaster()->Initialize(); - - // "GetAccountId()==db stored account id" checked in LoadFromDB (prevent login not own character using cheating tools) - if(!pCurrChar->LoadFromDB(GUID_LOPART(playerGuid), holder)) - { - KickPlayer(); // disconnect client, player no set to session and it will not deleted or saved at kick - delete pCurrChar; // delete it manually - delete holder; // delete all unprocessed queries - m_playerLoading = false; - return; - } - - SetPlayer(pCurrChar); - - pCurrChar->SendDungeonDifficulty(false); - - WorldPacket data( SMSG_LOGIN_VERIFY_WORLD, 20 ); - data << pCurrChar->GetMapId(); - data << pCurrChar->GetPositionX(); - data << pCurrChar->GetPositionY(); - data << pCurrChar->GetPositionZ(); - data << pCurrChar->GetOrientation(); - SendPacket(&data); - - data.Initialize( SMSG_ACCOUNT_DATA_TIMES, 128 ); - for(int i = 0; i < 32; i++) - data << uint32(0); - SendPacket(&data); - - data.Initialize(SMSG_FEATURE_SYSTEM_STATUS, 2); // added in 2.2.0 - data << uint8(2); // unknown value - data << uint8(0); // enable(1)/disable(0) voice chat interface in client - SendPacket(&data); - - // Send MOTD - { - data.Initialize(SMSG_MOTD, 50); // new in 2.0.1 - data << (uint32)0; - - uint32 linecount=0; - std::string str_motd = sWorld.GetMotd(); - std::string::size_type pos, nextpos; - - pos = 0; - while ( (nextpos= str_motd.find('@',pos)) != std::string::npos ) - { - if (nextpos != pos) - { - data << str_motd.substr(pos,nextpos-pos); - ++linecount; - } - pos = nextpos+1; - } - - if (posGetGuildId() != 0) - { - Guild* guild = objmgr.GetGuildById(pCurrChar->GetGuildId()); - if(guild) - { - data.Initialize(SMSG_GUILD_EVENT, (2+guild->GetMOTD().size()+1)); - data << (uint8)GE_MOTD; - data << (uint8)1; - data << guild->GetMOTD(); - SendPacket(&data); - DEBUG_LOG( "WORLD: Sent guild-motd (SMSG_GUILD_EVENT)" ); - - data.Initialize(SMSG_GUILD_EVENT, (5+10)); // we guess size - data<<(uint8)GE_SIGNED_ON; - data<<(uint8)1; - data<GetName(); - data<GetGUID(); - guild->BroadcastPacket(&data); - DEBUG_LOG( "WORLD: Sent guild-signed-on (SMSG_GUILD_EVENT)" ); - - // Increment online members of the guild - guild->IncOnlineMemberCount(); - } - else - { - // remove wrong guild data - sLog.outError("Player %s (GUID: %u) marked as member not existed guild (id: %u), removing guild membership for player.",pCurrChar->GetName(),pCurrChar->GetGUIDLow(),pCurrChar->GetGuildId()); - pCurrChar->SetUInt32Value(PLAYER_GUILDID,0); - pCurrChar->SetUInt32ValueInDB(PLAYER_GUILDID,0,pCurrChar->GetGUID()); - } - } - - if(!pCurrChar->isAlive()) - pCurrChar->SendCorpseReclaimDelay(true); - - pCurrChar->SendInitialPacketsBeforeAddToMap(); - - //Show cinematic at the first time that player login - if( !pCurrChar->getCinematic() ) - { - pCurrChar->setCinematic(1); - - ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(pCurrChar->getRace()); - if(rEntry) - { - data.Initialize( SMSG_TRIGGER_CINEMATIC,4 ); - data << uint32(rEntry->startmovie); - SendPacket( &data ); - } - } - - //QueryResult *result = CharacterDatabase.PQuery("SELECT guildid,rank FROM guild_member WHERE guid = '%u'",pCurrChar->GetGUIDLow()); - QueryResult *resultGuild = holder->GetResult(PLAYER_LOGIN_QUERY_LOADGUILD); - - if(resultGuild) - { - Field *fields = resultGuild->Fetch(); - pCurrChar->SetInGuild(fields[0].GetUInt32()); - pCurrChar->SetRank(fields[1].GetUInt32()); - delete resultGuild; - } - else if(pCurrChar->GetGuildId()) // clear guild related fields in case wrong data about non existed membership - { - pCurrChar->SetInGuild(0); - pCurrChar->SetRank(0); - } - - if (!MapManager::Instance().GetMap(pCurrChar->GetMapId(), pCurrChar)->Add(pCurrChar)) - { - AreaTrigger const* at = objmgr.GetGoBackTrigger(pCurrChar->GetMapId()); - if(at) - pCurrChar->TeleportTo(at->target_mapId, at->target_X, at->target_Y, at->target_Z, pCurrChar->GetOrientation()); - else - pCurrChar->TeleportTo(pCurrChar->m_homebindMapId, pCurrChar->m_homebindX, pCurrChar->m_homebindY, pCurrChar->m_homebindZ, pCurrChar->GetOrientation()); - } - - ObjectAccessor::Instance().AddObject(pCurrChar); - //sLog.outDebug("Player %s added to Map.",pCurrChar->GetName()); - pCurrChar->GetSocial()->SendSocialList(); - - pCurrChar->SendInitialPacketsAfterAddToMap(); - - CharacterDatabase.PExecute("UPDATE characters SET online = 1 WHERE guid = '%u'", pCurrChar->GetGUIDLow()); - loginDatabase.PExecute("UPDATE account SET online = 1 WHERE id = '%u'", GetAccountId()); - pCurrChar->SetInGameTime( getMSTime() ); - - // announce group about member online (must be after add to player list to receive announce to self) - if(Group *group = pCurrChar->GetGroup()) - { - //pCurrChar->groupInfo.group->SendInit(this); // useless - group->SendUpdate(); - } - - // friend status - sSocialMgr.SendFriendStatus(pCurrChar, FRIEND_ONLINE, pCurrChar->GetGUIDLow(), "", true); - - // Place character in world (and load zone) before some object loading - pCurrChar->LoadCorpse(); - - // setting Ghost+speed if dead - //if ( pCurrChar->m_deathState == DEAD ) - if (pCurrChar->m_deathState != ALIVE) - { - // not blizz like, we must correctly save and load player instead... - if(pCurrChar->getRace() == RACE_NIGHTELF) - pCurrChar->CastSpell(pCurrChar, 20584, true, 0);// auras SPELL_AURA_INCREASE_SPEED(+speed in wisp form), SPELL_AURA_INCREASE_SWIM_SPEED(+swim speed in wisp form), SPELL_AURA_TRANSFORM (to wisp form) - pCurrChar->CastSpell(pCurrChar, 8326, true, 0); // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?) - - //pCurrChar->SetUInt32Value(UNIT_FIELD_AURA+41, 8326); - //pCurrChar->SetUInt32Value(UNIT_FIELD_AURA+42, 20584); - //pCurrChar->SetUInt32Value(UNIT_FIELD_AURAFLAGS+6, 238); - //pCurrChar->SetUInt32Value(UNIT_FIELD_AURALEVELS+11, 514); - //pCurrChar->SetUInt32Value(UNIT_FIELD_AURAAPPLICATIONS+11, 65535); - //pCurrChar->SetUInt32Value(UNIT_FIELD_DISPLAYID, 1825); - //if (pCurrChar->getRace() == RACE_NIGHTELF) - //{ - // pCurrChar->SetSpeed(MOVE_RUN, 1.5f*1.2f, true); - // pCurrChar->SetSpeed(MOVE_SWIM, 1.5f*1.2f, true); - //} - //else - //{ - // pCurrChar->SetSpeed(MOVE_RUN, 1.5f, true); - // pCurrChar->SetSpeed(MOVE_SWIM, 1.5f, true); - //} - pCurrChar->SetMovement(MOVE_WATER_WALK); - } - - if(uint32 sourceNode = pCurrChar->m_taxi.GetTaxiSource()) - { - - sLog.outDebug( "WORLD: Restart character %u taxi flight", pCurrChar->GetGUIDLow() ); - - uint32 MountId = objmgr.GetTaxiMount(sourceNode, pCurrChar->GetTeam()); - uint32 path = pCurrChar->m_taxi.GetCurrentTaxiPath(); - - // search appropriate start path node - uint32 startNode = 0; - - TaxiPathNodeList const& nodeList = sTaxiPathNodesByPath[path]; - - float distPrev = MAP_SIZE*MAP_SIZE; - float distNext = - (nodeList[0].x-pCurrChar->GetPositionX())*(nodeList[0].x-pCurrChar->GetPositionX())+ - (nodeList[0].y-pCurrChar->GetPositionY())*(nodeList[0].y-pCurrChar->GetPositionY())+ - (nodeList[0].z-pCurrChar->GetPositionZ())*(nodeList[0].z-pCurrChar->GetPositionZ()); - - for(uint32 i = 1; i < nodeList.size(); ++i) - { - TaxiPathNode const& node = nodeList[i]; - TaxiPathNode const& prevNode = nodeList[i-1]; - - // skip nodes at another map - if(node.mapid != pCurrChar->GetMapId()) - continue; - - distPrev = distNext; - - distNext = - (node.x-pCurrChar->GetPositionX())*(node.x-pCurrChar->GetPositionX())+ - (node.y-pCurrChar->GetPositionY())*(node.y-pCurrChar->GetPositionY())+ - (node.z-pCurrChar->GetPositionZ())*(node.z-pCurrChar->GetPositionZ()); - - float distNodes = - (node.x-prevNode.x)*(node.x-prevNode.x)+ - (node.y-prevNode.y)*(node.y-prevNode.y)+ - (node.z-prevNode.z)*(node.z-prevNode.z); - - if(distNext + distPrev < distNodes) - { - startNode = i; - break; - } - } - - SendDoFlight( MountId, path, startNode ); - } - - // Load pet if any and player is alive and not in taxi flight - if(pCurrChar->isAlive() && pCurrChar->m_taxi.GetTaxiSource()==0) - pCurrChar->LoadPet(); - - // Set FFA PvP for non GM in non-rest mode - if(sWorld.IsFFAPvPRealm() && !pCurrChar->isGameMaster() && !pCurrChar->HasFlag(PLAYER_FLAGS,PLAYER_FLAGS_RESTING) ) - pCurrChar->SetFlag(PLAYER_FLAGS,PLAYER_FLAGS_FFA_PVP); - - if(pCurrChar->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP)) - pCurrChar->SetContestedPvP(); - - // Apply at_login requests - if(pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_SPELLS)) - { - pCurrChar->resetSpells(); - SendNotification("Spells has been reset."); - } - - if(pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_TALENTS)) - { - pCurrChar->resetTalents(true); - SendNotification("Talents has been reset."); - } - - // show time before shutdown if shutdown planned. - if(sWorld.IsShutdowning()) - sWorld.ShutdownMsg(true,pCurrChar); - - if(pCurrChar->isGameMaster()) - SendNotification("GM mode is ON"); - - std::string IP_str = GetRemoteAddress(); - sLog.outChar("Account: %d (IP: %s) Login Character:[%s] (guid:%u)",GetAccountId(),IP_str.c_str(),pCurrChar->GetName() ,pCurrChar->GetGUID()); - - m_playerLoading = false; - delete holder; -} - -void WorldSession::HandleSetFactionAtWar( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,4+1); - - DEBUG_LOG( "WORLD: Received CMSG_SET_FACTION_ATWAR" ); - - uint32 repListID; - uint8 flag; - - recv_data >> repListID; - recv_data >> flag; - - FactionStateList::iterator itr = GetPlayer()->m_factions.find(repListID); - if (itr == GetPlayer()->m_factions.end()) - return; - - // always invisible or hidden faction can't change war state - if(itr->second.Flags & (FACTION_FLAG_INVISIBLE_FORCED|FACTION_FLAG_HIDDEN) ) - return; - - GetPlayer()->SetFactionAtWar(&itr->second,flag); -} - -//I think this function is never used :/ I dunno, but i guess this opcode not exists -void WorldSession::HandleSetFactionCheat( WorldPacket & /*recv_data*/ ) -{ - //CHECK_PACKET_SIZE(recv_data,4+4); - - //sLog.outDebug("WORLD SESSION: HandleSetFactionCheat"); - /* - uint32 FactionID; - uint32 Standing; - - recv_data >> FactionID; - recv_data >> Standing; - - std::list::iterator itr; - - for(itr = GetPlayer()->factions.begin(); itr != GetPlayer()->factions.end(); ++itr) - { - if(itr->ReputationListID == FactionID) - { - itr->Standing += Standing; - itr->Flags = (itr->Flags | 1); - break; - } - } - */ - GetPlayer()->UpdateReputation(); -} - -void WorldSession::HandleMeetingStoneInfo( WorldPacket & /*recv_data*/ ) -{ - DEBUG_LOG( "WORLD: Received CMSG_MEETING_STONE_INFO" ); - - WorldPacket data(SMSG_MEETINGSTONE_SETQUEUE, 5); - data << uint32(0) << uint8(6); - SendPacket(&data); -} - -void WorldSession::HandleTutorialFlag( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,4); - - uint32 iFlag; - recv_data >> iFlag; - - uint32 wInt = (iFlag / 32); - if (wInt >= 8) - { - //sLog.outError("CHEATER? Account:[%d] Guid[%u] tried to send wrong CMSG_TUTORIAL_FLAG", GetAccountId(),GetGUID()); - return; - } - uint32 rInt = (iFlag % 32); - - uint32 tutflag = GetPlayer()->GetTutorialInt( wInt ); - tutflag |= (1 << rInt); - GetPlayer()->SetTutorialInt( wInt, tutflag ); - - //sLog.outDebug("Received Tutorial Flag Set {%u}.", iFlag); -} - -void WorldSession::HandleTutorialClear( WorldPacket & /*recv_data*/ ) -{ - for ( uint32 iI = 0; iI < 8; iI++) - GetPlayer()->SetTutorialInt( iI, 0xFFFFFFFF ); -} - -void WorldSession::HandleTutorialReset( WorldPacket & /*recv_data*/ ) -{ - for ( uint32 iI = 0; iI < 8; iI++) - GetPlayer()->SetTutorialInt( iI, 0x00000000 ); -} - -void WorldSession::HandleSetWatchedFactionIndexOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data,4); - - DEBUG_LOG("WORLD: Received CMSG_SET_WATCHED_FACTION"); - uint32 fact; - recv_data >> fact; - GetPlayer()->SetUInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fact); -} - -void WorldSession::HandleSetWatchedFactionInactiveOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data,4+1); - - DEBUG_LOG("WORLD: Received CMSG_SET_FACTION_INACTIVE"); - uint32 replistid; - uint8 inactive; - recv_data >> replistid >> inactive; - - FactionStateList::iterator itr = _player->m_factions.find(replistid); - if (itr == _player->m_factions.end()) - return; - - _player->SetFactionInactive(&itr->second, inactive); -} - -void WorldSession::HandleToggleHelmOpcode( WorldPacket & /*recv_data*/ ) -{ - DEBUG_LOG("CMSG_TOGGLE_HELM for %s", _player->GetName()); - _player->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_HELM); -} - -void WorldSession::HandleToggleCloakOpcode( WorldPacket & /*recv_data*/ ) -{ - DEBUG_LOG("CMSG_TOGGLE_CLOAK for %s", _player->GetName()); - _player->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_CLOAK); -} - -void WorldSession::HandleChangePlayerNameOpcode(WorldPacket& recv_data) -{ - CHECK_PACKET_SIZE(recv_data,8+1); - - uint64 guid; - std::string newname; - std::string oldname; - - CHECK_PACKET_SIZE(recv_data, 8+1); - - recv_data >> guid; - recv_data >> newname; - - QueryResult *result = CharacterDatabase.PQuery("SELECT at_login FROM characters WHERE guid ='%u'", GUID_LOPART(guid)); - if (result) - { - uint32 at_loginFlags; - Field *fields = result->Fetch(); - at_loginFlags = fields[0].GetUInt32(); - delete result; - - if (!(at_loginFlags & AT_LOGIN_RENAME)) - { - WorldPacket data(SMSG_CHAR_RENAME, 1); - data << (uint8)CHAR_CREATE_ERROR; - SendPacket( &data ); - return; - } - } - else - { - WorldPacket data(SMSG_CHAR_RENAME, 1); - data << (uint8)CHAR_CREATE_ERROR; - SendPacket( &data ); - return; - } - - if(!objmgr.GetPlayerNameByGUID(guid, oldname)) // character not exist, because we have no name for this guid - { - WorldPacket data(SMSG_CHAR_RENAME, 1); - data << (uint8)CHAR_LOGIN_NO_CHARACTER; - SendPacket( &data ); - return; - } - - // prevent character rename to invalid name - if(!normalizePlayerName(newname)) - { - WorldPacket data(SMSG_CHAR_RENAME, 1); - data << (uint8)CHAR_NAME_NO_NAME; - SendPacket( &data ); - return; - } - - if(!ObjectMgr::IsValidName(newname,true)) - { - WorldPacket data(SMSG_CHAR_RENAME, 1); - data << (uint8)CHAR_NAME_INVALID_CHARACTER; - SendPacket( &data ); - return; - } - - // check name limitations - if(GetSecurity() == SEC_PLAYER && objmgr.IsReservedName(newname)) - { - WorldPacket data(SMSG_CHAR_RENAME, 1); - data << (uint8)CHAR_NAME_RESERVED; - SendPacket( &data ); - return; - } - - if(objmgr.GetPlayerGUIDByName(newname)) // character with this name already exist - { - WorldPacket data(SMSG_CHAR_RENAME, 1); - data << (uint8)CHAR_CREATE_ERROR; - SendPacket( &data ); - return; - } - - if(newname == oldname) // checked by client - { - WorldPacket data(SMSG_CHAR_RENAME, 1); - data << (uint8)CHAR_NAME_FAILURE; - SendPacket( &data ); - return; - } - - // we have to check character at_login_flag & AT_LOGIN_RENAME also (fake packets hehe) - - CharacterDatabase.escape_string(newname); - CharacterDatabase.PExecute("UPDATE characters set name = '%s', at_login = at_login & ~ %u WHERE guid ='%u'", newname.c_str(), uint32(AT_LOGIN_RENAME),GUID_LOPART(guid)); - CharacterDatabase.PExecute("DELETE FROM character_declinedname WHERE guid ='%u'", GUID_LOPART(guid)); - - std::string IP_str = GetRemoteAddress(); - sLog.outChar("Account: %d (IP: %s) Character:[%s] (guid:%u) Changed name to: %s",GetAccountId(),IP_str.c_str(),oldname.c_str(),GUID_LOPART(guid),newname.c_str()); - - WorldPacket data(SMSG_CHAR_RENAME,1+8+(newname.size()+1)); - data << (uint8)RESPONSE_SUCCESS; - data << guid; - data << newname; - SendPacket(&data); -} - -void WorldSession::HandleDeclinedPlayerNameOpcode(WorldPacket& recv_data) -{ - uint64 guid; - - CHECK_PACKET_SIZE(recv_data, 8+6); - recv_data >> guid; - - // not accept declined names for unsupported languages - std::string name; - if(!objmgr.GetPlayerNameByGUID(guid,name)) - { - WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); - data << (uint32)1; - data << guid; - SendPacket(&data); - return; - } - - std::wstring wname; - if(!Utf8toWStr(name,wname)) - { - WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); - data << (uint32)1; - data << guid; - SendPacket(&data); - return; - } - - if(!isCyrillicCharacter(wname[0])) // name already stored as only single alphabet using - { - WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); - data << (uint32)1; - data << guid; - SendPacket(&data); - return; - } - - std::string name2; - DeclinedName declinedname; - - recv_data >> name2; - - if(name2!=name) // character have different name - { - WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); - data << (uint32)1; - data << guid; - SendPacket(&data); - return; - } - - for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) - { - recv_data >> declinedname.name[i]; - if(!normalizePlayerName(declinedname.name[i])) - { - WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); - data << (uint32)1; - data << guid; - SendPacket(&data); - return; - } - } - - if(!ObjectMgr::CheckDeclinedNames(GetMainPartOfName(wname,0),declinedname)) - { - WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); - data << (uint32)1; - data << guid; - SendPacket(&data); - return; - } - - for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) - CharacterDatabase.escape_string(declinedname.name[i]); - - CharacterDatabase.BeginTransaction(); - CharacterDatabase.PExecute("DELETE FROM character_declinedname WHERE guid = '%u'", GUID_LOPART(guid)); - CharacterDatabase.PExecute("INSERT INTO character_declinedname (guid, genitive, dative, accusative, instrumental, prepositional) VALUES ('%u','%s','%s','%s','%s','%s')", - GUID_LOPART(guid), declinedname.name[0].c_str(),declinedname.name[1].c_str(),declinedname.name[2].c_str(),declinedname.name[3].c_str(),declinedname.name[4].c_str()); - CharacterDatabase.CommitTransaction(); - - WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); - data << (uint32)0; // OK - data << guid; - SendPacket(&data); -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "WorldPacket.h" +#include "SharedDefines.h" +#include "WorldSession.h" +#include "Opcodes.h" +#include "Log.h" +#include "World.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "Guild.h" +#include "UpdateMask.h" +#include "Auth/md5.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "Group.h" +#include "Database/DatabaseImpl.h" +#include "PlayerDump.h" +#include "SocialMgr.h" +#include "Util.h" +#include "Language.h" + +class LoginQueryHolder : public SqlQueryHolder +{ + private: + uint32 m_accountId; + uint64 m_guid; + public: + LoginQueryHolder(uint32 accountId, uint64 guid) + : m_accountId(accountId), m_guid(guid) { } + uint64 GetGuid() const { return m_guid; } + uint32 GetAccountId() const { return m_accountId; } + bool Initialize(); +}; + +bool LoginQueryHolder::Initialize() +{ + SetSize(MAX_PLAYER_LOGIN_QUERY); + + bool res = true; + + // NOTE: all fields in `characters` must be read to prevent lost character data at next save in case wrong DB structure. + // !!! NOTE: including unused `zone`,`online` + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADFROM, "SELECT guid, account, data, name, race, class, position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, trans_x, trans_y, trans_z, trans_o, transguid, gmstate, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeon_difficulty FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGROUP, "SELECT leaderGuid FROM group_member WHERE memberGuid ='%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES, "SELECT id, permanent, map, difficulty, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = '%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADAURAS, "SELECT caster_guid,spell,effect_index,amount,maxduration,remaintime,remaincharges FROM character_aura WHERE guid = '%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSPELLS, "SELECT spell,slot,active,disabled FROM character_spell WHERE guid = '%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS, "SELECT quest,status,rewarded,explored,timer,mobcount1,mobcount2,mobcount3,mobcount4,itemcount1,itemcount2,itemcount3,itemcount4 FROM character_queststatus WHERE guid = '%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS,"SELECT quest,time FROM character_queststatus_daily WHERE guid = '%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADTUTORIALS, "SELECT tut0,tut1,tut2,tut3,tut4,tut5,tut6,tut7 FROM character_tutorial WHERE account = '%u' AND realmid = '%u'", GetAccountId(), realmID); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADREPUTATION, "SELECT faction,standing,flags FROM character_reputation WHERE guid = '%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADINVENTORY, "SELECT data,bag,slot,item,item_template FROM character_inventory JOIN item_instance ON character_inventory.item = item_instance.guid WHERE character_inventory.guid = '%u' ORDER BY bag,slot", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADACTIONS, "SELECT button,action,type,misc FROM character_action WHERE guid = '%u' ORDER BY button", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADMAILCOUNT, "SELECT COUNT(id) FROM mail WHERE receiver = '%u' AND (checked & 1)=0 AND deliver_time <= '" I64FMTD "'", GUID_LOPART(m_guid),(uint64)time(NULL)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADMAILDATE, "SELECT MIN(deliver_time) FROM mail WHERE receiver = '%u' AND (checked & 1)=0", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSOCIALLIST, "SELECT friend,flags,note FROM character_social WHERE guid = '%u' LIMIT 255", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADHOMEBIND, "SELECT map,zone,position_x,position_y,position_z FROM character_homebind WHERE guid = '%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSPELLCOOLDOWNS, "SELECT spell,item,time FROM character_spell_cooldown WHERE guid = '%u'", GUID_LOPART(m_guid)); + if(sWorld.getConfig(CONFIG_DECLINED_NAMES_USED)) + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADDECLINEDNAMES, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_declinedname WHERE guid = '%u'",GUID_LOPART(m_guid)); + // in other case still be dummy query + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGUILD, "SELECT guildid,rank FROM guild_member WHERE guid = '%u'", GUID_LOPART(m_guid)); + + return res; +} + +// don't call WorldSession directly +// it may get deleted before the query callbacks get executed +// instead pass an account id to this handler +class CharacterHandler +{ + public: + void HandleCharEnumCallback(QueryResult * result, uint32 account) + { + WorldSession * session = sWorld.FindSession(account); + if(!session) + { + delete result; + return; + } + session->HandleCharEnum(result); + } + void HandlePlayerLoginCallback(QueryResult * /*dummy*/, SqlQueryHolder * holder) + { + if (!holder) return; + WorldSession *session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId()); + if(!session) + { + delete holder; + return; + } + session->HandlePlayerLogin((LoginQueryHolder*)holder); + } +} chrHandler; + +void WorldSession::HandleCharEnum(QueryResult * result) +{ + // keys can be non cleared if player open realm list and close it by 'cancel' + loginDatabase.PExecute("UPDATE account SET v = '0', s = '0' WHERE id = '%u'", GetAccountId()); + + WorldPacket data(SMSG_CHAR_ENUM, 100); // we guess size + + uint8 num = 0; + + data << num; + + if( result ) + { + Player *plr = new Player(this); + do + { + sLog.outDetail("Loading char guid %u from account %u.",(*result)[0].GetUInt32(),GetAccountId()); + + if(plr->MinimalLoadFromDB( result, (*result)[0].GetUInt32() )) + { + plr->BuildEnumData( result, &data ); + ++num; + } + } + while( result->NextRow() ); + + delete plr; + delete result; + } + + data.put(0, num); + + SendPacket( &data ); +} + +void WorldSession::HandleCharEnumOpcode( WorldPacket & /*recv_data*/ ) +{ + /// get all the data necessary for loading all characters (along with their pets) on the account + CharacterDatabase.AsyncPQuery(&chrHandler, &CharacterHandler::HandleCharEnumCallback, GetAccountId(), + !sWorld.getConfig(CONFIG_DECLINED_NAMES_USED) ? + // ------- Query Without Declined Names -------- + // 0 1 2 3 4 5 6 7 8 + "SELECT characters.data, characters.name, characters.position_x, characters.position_y, characters.position_z, characters.map, characters.totaltime, characters.leveltime, characters.at_login, " + // 9 10 11 + "character_pet.entry, character_pet.modelid, character_pet.level " + "FROM characters LEFT JOIN character_pet ON characters.guid=character_pet.owner AND character_pet.slot='0' " + "WHERE characters.account = '%u' ORDER BY characters.guid" + : + // --------- Query With Declined Names --------- + // 0 1 2 3 4 5 6 7 8 + "SELECT characters.data, characters.name, characters.position_x, characters.position_y, characters.position_z, characters.map, characters.totaltime, characters.leveltime, characters.at_login, " + // 9 10 11 12 + "character_pet.entry, character_pet.modelid, character_pet.level, genitive " + "FROM characters LEFT JOIN character_pet ON characters.guid = character_pet.owner AND character_pet.slot='0' " + "LEFT JOIN character_declinedname ON characters.guid = character_declinedname.guid " + "WHERE characters.account = '%u' ORDER BY characters.guid", + GetAccountId()); +} + +void WorldSession::HandleCharCreateOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,1+1+1+1+1+1+1+1+1+1); + + std::string name; + uint8 race_,class_; + bool pTbc = this->IsTBC() && sWorld.getConfig(CONFIG_EXPANSION) > 0; + recv_data >> name; + + // recheck with known string size + CHECK_PACKET_SIZE(recv_data,(name.size()+1)+1+1+1+1+1+1+1+1+1); + + recv_data >> race_; + recv_data >> class_; + + WorldPacket data(SMSG_CHAR_CREATE, 1); // returned with diff.values in all cases + + if(GetSecurity() == SEC_PLAYER) + { + if(uint32 mask = sWorld.getConfig(CONFIG_CHARACTERS_CREATING_DISABLED)) + { + bool disabled = false; + + uint32 team = Player::TeamForRace(race_); + switch(team) + { + case ALLIANCE: disabled = mask & (1<<0); break; + case HORDE: disabled = mask & (1<<1); break; + } + + if(disabled) + { + data << (uint8)CHAR_CREATE_DISABLED; + SendPacket( &data ); + return; + } + } + } + + if (!sChrClassesStore.LookupEntry(class_)|| + !sChrRacesStore.LookupEntry(race_)) + { + data << (uint8)CHAR_CREATE_FAILED; + SendPacket( &data ); + sLog.outError("Class: %u or Race %u not found in DBC (Wrong DBC files?) or Cheater?", class_, race_); + return; + } + + // prevent character creating Expansion race without Expansion account + if (!pTbc&&(race_>RACE_TROLL)) + { + data << (uint8)CHAR_CREATE_EXPANSION; + sLog.outError("No Expansion Account:[%d] but tried to Create TBC character",GetAccountId()); + SendPacket( &data ); + return; + } + + // prevent character creating with invalid name + if(!normalizePlayerName(name)) + { + data << (uint8)CHAR_NAME_INVALID_CHARACTER; + SendPacket( &data ); + sLog.outError("Account:[%d] but tried to Create character with empty [name] ",GetAccountId()); + return; + } + + // check name limitations + if(!ObjectMgr::IsValidName(name,true)) + { + data << (uint8)CHAR_NAME_INVALID_CHARACTER; + SendPacket( &data ); + return; + } + + if(GetSecurity() == SEC_PLAYER && objmgr.IsReservedName(name)) + { + data << (uint8)CHAR_NAME_RESERVED; + SendPacket( &data ); + return; + } + + if(objmgr.GetPlayerGUIDByName(name)) + { + data << (uint8)CHAR_CREATE_NAME_IN_USE; + SendPacket( &data ); + return; + } + + QueryResult *resultacct = loginDatabase.PQuery("SELECT SUM(numchars) FROM realmcharacters WHERE acctid = '%d'", GetAccountId()); + if ( resultacct ) + { + Field *fields=resultacct->Fetch(); + uint32 acctcharcount = fields[0].GetUInt32(); + delete resultacct; + + if (acctcharcount >= sWorld.getConfig(CONFIG_CHARACTERS_PER_ACCOUNT)) + { + data << (uint8)CHAR_CREATE_ACCOUNT_LIMIT; + SendPacket( &data ); + return; + } + } + + QueryResult *result = CharacterDatabase.PQuery("SELECT COUNT(guid) FROM characters WHERE account = '%d'", GetAccountId()); + uint8 charcount = 0; + if ( result ) + { + Field *fields=result->Fetch(); + charcount = fields[0].GetUInt8(); + delete result; + + if (charcount >= sWorld.getConfig(CONFIG_CHARACTERS_PER_REALM)) + { + data << (uint8)CHAR_CREATE_SERVER_LIMIT; + SendPacket( &data ); + return; + } + } + + bool AllowTwoSideAccounts = !sWorld.IsPvPRealm() || sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS) || GetSecurity() > SEC_PLAYER; + uint32 skipCinematics = sWorld.getConfig(CONFIG_SKIP_CINEMATICS); + + bool have_same_race = false; + if(!AllowTwoSideAccounts || skipCinematics == 1) + { + QueryResult *result2 = CharacterDatabase.PQuery("SELECT DISTINCT race FROM characters WHERE account = '%u' %s", GetAccountId(),skipCinematics == 1 ? "" : "LIMIT 1"); + if(result2) + { + uint32 team_= Player::TeamForRace(race_); + + Field* field = result2->Fetch(); + uint8 race = field[0].GetUInt32(); + + // need to check team only for first character + // TODO: what to if account already has characters of both races? + if (!AllowTwoSideAccounts) + { + uint32 team=0; + if(race > 0) + team = Player::TeamForRace(race); + + if(team != team_) + { + data << (uint8)CHAR_CREATE_PVP_TEAMS_VIOLATION; + SendPacket( &data ); + delete result2; + return; + } + } + + if (skipCinematics == 1) + { + // TODO: check if cinematic already shown? (already logged in?; cinematic field) + while (race_ != race && result2->NextRow()) + { + field = result2->Fetch(); + race = field[0].GetUInt32(); + } + have_same_race = race_ == race; + } + delete result2; + } + } + + // extract other data required for player creating + uint8 gender, skin, face, hairStyle, hairColor, facialHair, outfitId; + recv_data >> gender >> skin >> face; + recv_data >> hairStyle >> hairColor >> facialHair >> outfitId; + + Player * pNewChar = new Player(this); + if(!pNewChar->Create( objmgr.GenerateLowGuid(HIGHGUID_PLAYER), name, race_, class_, gender, skin, face, hairStyle, hairColor, facialHair, outfitId )) + { + // Player not create (race/class problem?) + delete pNewChar; + + data << (uint8)CHAR_CREATE_ERROR; + SendPacket( &data ); + + return; + } + + if(have_same_race && skipCinematics == 1 || skipCinematics == 2) + pNewChar->setCinematic(1); // not show intro + + // Player created, save it now + pNewChar->SaveToDB(); + charcount+=1; + + loginDatabase.PExecute("DELETE FROM realmcharacters WHERE acctid= '%d' AND realmid = '%d'", GetAccountId(), realmID); + loginDatabase.PExecute("INSERT INTO realmcharacters (numchars, acctid, realmid) VALUES (%u, %u, %u)", charcount, GetAccountId(), realmID); + + delete pNewChar; // created only to call SaveToDB() + + data << (uint8)CHAR_CREATE_SUCCESS; + SendPacket( &data ); + + std::string IP_str = GetRemoteAddress().c_str(); + sLog.outBasic("Account: %d (IP: %s) Create Character:[%s]",GetAccountId(),IP_str.c_str(),name.c_str()); + sLog.outChar("Account: %d (IP: %s) Create Character:[%s]",GetAccountId(),IP_str.c_str(),name.c_str()); +} + +void WorldSession::HandleCharDeleteOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,8); + + uint64 guid; + recv_data >> guid; + + // can't delete loaded character + if(objmgr.GetPlayer(guid)) + return; + + uint32 accountId = 0; + std::string name; + + // is guild leader + if(objmgr.GetGuildByLeader(guid)) + { + WorldPacket data(SMSG_CHAR_DELETE, 1); + data << (uint8)CHAR_DELETE_FAILED_GUILD_LEADER; + SendPacket( &data ); + return; + } + + // is arena team captain + if(objmgr.GetArenaTeamByCapitan(guid)) + { + WorldPacket data(SMSG_CHAR_DELETE, 1); + data << (uint8)CHAR_DELETE_FAILED_ARENA_CAPTAIN; + SendPacket( &data ); + return; + } + + QueryResult *result = CharacterDatabase.PQuery("SELECT account,name FROM characters WHERE guid='%u'", GUID_LOPART(guid)); + if(result) + { + Field *fields = result->Fetch(); + accountId = fields[0].GetUInt32(); + name = fields[1].GetCppString(); + delete result; + } + + // prevent deleting other players' characters using cheating tools + if(accountId != GetAccountId()) + return; + + std::string IP_str = GetRemoteAddress(); + sLog.outBasic("Account: %d (IP: %s) Delete Character:[%s] (guid:%u)",GetAccountId(),IP_str.c_str(),name.c_str(),GUID_LOPART(guid)); + sLog.outChar("Account: %d (IP: %s) Delete Character:[%s] (guid: %u)",GetAccountId(),IP_str.c_str(),name.c_str(),GUID_LOPART(guid)); + + if(sLog.IsOutCharDump()) // optimize GetPlayerDump call + { + std::string dump = PlayerDumpWriter().GetDump(GUID_LOPART(guid)); + sLog.outCharDump(dump.c_str(),GetAccountId(),GUID_LOPART(guid),name.c_str()); + } + + Player::DeleteFromDB(guid, GetAccountId()); + + WorldPacket data(SMSG_CHAR_DELETE, 1); + data << (uint8)CHAR_DELETE_SUCCESS; + SendPacket( &data ); +} + +void WorldSession::HandlePlayerLoginOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,8); + + m_playerLoading = true; + uint64 playerGuid = 0; + + DEBUG_LOG( "WORLD: Recvd Player Logon Message" ); + + recv_data >> playerGuid; + + LoginQueryHolder *holder = new LoginQueryHolder(GetAccountId(), playerGuid); + if(!holder->Initialize()) + { + delete holder; // delete all unprocessed queries + m_playerLoading = false; + return; + } + + CharacterDatabase.DelayQueryHolder(&chrHandler, &CharacterHandler::HandlePlayerLoginCallback, holder); +} + +void WorldSession::HandlePlayerLogin(LoginQueryHolder * holder) +{ + uint64 playerGuid = holder->GetGuid(); + + Player* pCurrChar = new Player(this); + pCurrChar->GetMotionMaster()->Initialize(); + + // "GetAccountId()==db stored account id" checked in LoadFromDB (prevent login not own character using cheating tools) + if(!pCurrChar->LoadFromDB(GUID_LOPART(playerGuid), holder)) + { + KickPlayer(); // disconnect client, player no set to session and it will not deleted or saved at kick + delete pCurrChar; // delete it manually + delete holder; // delete all unprocessed queries + m_playerLoading = false; + return; + } + + SetPlayer(pCurrChar); + + pCurrChar->SendDungeonDifficulty(false); + + WorldPacket data( SMSG_LOGIN_VERIFY_WORLD, 20 ); + data << pCurrChar->GetMapId(); + data << pCurrChar->GetPositionX(); + data << pCurrChar->GetPositionY(); + data << pCurrChar->GetPositionZ(); + data << pCurrChar->GetOrientation(); + SendPacket(&data); + + data.Initialize( SMSG_ACCOUNT_DATA_TIMES, 128 ); + for(int i = 0; i < 32; i++) + data << uint32(0); + SendPacket(&data); + + data.Initialize(SMSG_FEATURE_SYSTEM_STATUS, 2); // added in 2.2.0 + data << uint8(2); // unknown value + data << uint8(0); // enable(1)/disable(0) voice chat interface in client + SendPacket(&data); + + // Send MOTD + { + data.Initialize(SMSG_MOTD, 50); // new in 2.0.1 + data << (uint32)0; + + uint32 linecount=0; + std::string str_motd = sWorld.GetMotd(); + std::string::size_type pos, nextpos; + + pos = 0; + while ( (nextpos= str_motd.find('@',pos)) != std::string::npos ) + { + if (nextpos != pos) + { + data << str_motd.substr(pos,nextpos-pos); + ++linecount; + } + pos = nextpos+1; + } + + if (posGetGuildId() != 0) + { + Guild* guild = objmgr.GetGuildById(pCurrChar->GetGuildId()); + if(guild) + { + data.Initialize(SMSG_GUILD_EVENT, (2+guild->GetMOTD().size()+1)); + data << (uint8)GE_MOTD; + data << (uint8)1; + data << guild->GetMOTD(); + SendPacket(&data); + DEBUG_LOG( "WORLD: Sent guild-motd (SMSG_GUILD_EVENT)" ); + + data.Initialize(SMSG_GUILD_EVENT, (5+10)); // we guess size + data<<(uint8)GE_SIGNED_ON; + data<<(uint8)1; + data<GetName(); + data<GetGUID(); + guild->BroadcastPacket(&data); + DEBUG_LOG( "WORLD: Sent guild-signed-on (SMSG_GUILD_EVENT)" ); + + // Increment online members of the guild + guild->IncOnlineMemberCount(); + } + else + { + // remove wrong guild data + sLog.outError("Player %s (GUID: %u) marked as member not existed guild (id: %u), removing guild membership for player.",pCurrChar->GetName(),pCurrChar->GetGUIDLow(),pCurrChar->GetGuildId()); + pCurrChar->SetUInt32Value(PLAYER_GUILDID,0); + pCurrChar->SetUInt32ValueInDB(PLAYER_GUILDID,0,pCurrChar->GetGUID()); + } + } + + if(!pCurrChar->isAlive()) + pCurrChar->SendCorpseReclaimDelay(true); + + pCurrChar->SendInitialPacketsBeforeAddToMap(); + + //Show cinematic at the first time that player login + if( !pCurrChar->getCinematic() ) + { + pCurrChar->setCinematic(1); + + ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(pCurrChar->getRace()); + if(rEntry) + { + data.Initialize( SMSG_TRIGGER_CINEMATIC,4 ); + data << uint32(rEntry->startmovie); + SendPacket( &data ); + } + } + + //QueryResult *result = CharacterDatabase.PQuery("SELECT guildid,rank FROM guild_member WHERE guid = '%u'",pCurrChar->GetGUIDLow()); + QueryResult *resultGuild = holder->GetResult(PLAYER_LOGIN_QUERY_LOADGUILD); + + if(resultGuild) + { + Field *fields = resultGuild->Fetch(); + pCurrChar->SetInGuild(fields[0].GetUInt32()); + pCurrChar->SetRank(fields[1].GetUInt32()); + delete resultGuild; + } + else if(pCurrChar->GetGuildId()) // clear guild related fields in case wrong data about non existed membership + { + pCurrChar->SetInGuild(0); + pCurrChar->SetRank(0); + } + + if (!MapManager::Instance().GetMap(pCurrChar->GetMapId(), pCurrChar)->Add(pCurrChar)) + { + AreaTrigger const* at = objmgr.GetGoBackTrigger(pCurrChar->GetMapId()); + if(at) + pCurrChar->TeleportTo(at->target_mapId, at->target_X, at->target_Y, at->target_Z, pCurrChar->GetOrientation()); + else + pCurrChar->TeleportTo(pCurrChar->m_homebindMapId, pCurrChar->m_homebindX, pCurrChar->m_homebindY, pCurrChar->m_homebindZ, pCurrChar->GetOrientation()); + } + + ObjectAccessor::Instance().AddObject(pCurrChar); + //sLog.outDebug("Player %s added to Map.",pCurrChar->GetName()); + pCurrChar->GetSocial()->SendSocialList(); + + pCurrChar->SendInitialPacketsAfterAddToMap(); + + CharacterDatabase.PExecute("UPDATE characters SET online = 1 WHERE guid = '%u'", pCurrChar->GetGUIDLow()); + loginDatabase.PExecute("UPDATE account SET online = 1 WHERE id = '%u'", GetAccountId()); + pCurrChar->SetInGameTime( getMSTime() ); + + // announce group about member online (must be after add to player list to receive announce to self) + if(Group *group = pCurrChar->GetGroup()) + { + //pCurrChar->groupInfo.group->SendInit(this); // useless + group->SendUpdate(); + } + + // friend status + sSocialMgr.SendFriendStatus(pCurrChar, FRIEND_ONLINE, pCurrChar->GetGUIDLow(), "", true); + + // Place character in world (and load zone) before some object loading + pCurrChar->LoadCorpse(); + + // setting Ghost+speed if dead + //if ( pCurrChar->m_deathState == DEAD ) + if (pCurrChar->m_deathState != ALIVE) + { + // not blizz like, we must correctly save and load player instead... + if(pCurrChar->getRace() == RACE_NIGHTELF) + pCurrChar->CastSpell(pCurrChar, 20584, true, 0);// auras SPELL_AURA_INCREASE_SPEED(+speed in wisp form), SPELL_AURA_INCREASE_SWIM_SPEED(+swim speed in wisp form), SPELL_AURA_TRANSFORM (to wisp form) + pCurrChar->CastSpell(pCurrChar, 8326, true, 0); // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?) + + //pCurrChar->SetUInt32Value(UNIT_FIELD_AURA+41, 8326); + //pCurrChar->SetUInt32Value(UNIT_FIELD_AURA+42, 20584); + //pCurrChar->SetUInt32Value(UNIT_FIELD_AURAFLAGS+6, 238); + //pCurrChar->SetUInt32Value(UNIT_FIELD_AURALEVELS+11, 514); + //pCurrChar->SetUInt32Value(UNIT_FIELD_AURAAPPLICATIONS+11, 65535); + //pCurrChar->SetUInt32Value(UNIT_FIELD_DISPLAYID, 1825); + //if (pCurrChar->getRace() == RACE_NIGHTELF) + //{ + // pCurrChar->SetSpeed(MOVE_RUN, 1.5f*1.2f, true); + // pCurrChar->SetSpeed(MOVE_SWIM, 1.5f*1.2f, true); + //} + //else + //{ + // pCurrChar->SetSpeed(MOVE_RUN, 1.5f, true); + // pCurrChar->SetSpeed(MOVE_SWIM, 1.5f, true); + //} + pCurrChar->SetMovement(MOVE_WATER_WALK); + } + + if(uint32 sourceNode = pCurrChar->m_taxi.GetTaxiSource()) + { + + sLog.outDebug( "WORLD: Restart character %u taxi flight", pCurrChar->GetGUIDLow() ); + + uint32 MountId = objmgr.GetTaxiMount(sourceNode, pCurrChar->GetTeam()); + uint32 path = pCurrChar->m_taxi.GetCurrentTaxiPath(); + + // search appropriate start path node + uint32 startNode = 0; + + TaxiPathNodeList const& nodeList = sTaxiPathNodesByPath[path]; + + float distPrev = MAP_SIZE*MAP_SIZE; + float distNext = + (nodeList[0].x-pCurrChar->GetPositionX())*(nodeList[0].x-pCurrChar->GetPositionX())+ + (nodeList[0].y-pCurrChar->GetPositionY())*(nodeList[0].y-pCurrChar->GetPositionY())+ + (nodeList[0].z-pCurrChar->GetPositionZ())*(nodeList[0].z-pCurrChar->GetPositionZ()); + + for(uint32 i = 1; i < nodeList.size(); ++i) + { + TaxiPathNode const& node = nodeList[i]; + TaxiPathNode const& prevNode = nodeList[i-1]; + + // skip nodes at another map + if(node.mapid != pCurrChar->GetMapId()) + continue; + + distPrev = distNext; + + distNext = + (node.x-pCurrChar->GetPositionX())*(node.x-pCurrChar->GetPositionX())+ + (node.y-pCurrChar->GetPositionY())*(node.y-pCurrChar->GetPositionY())+ + (node.z-pCurrChar->GetPositionZ())*(node.z-pCurrChar->GetPositionZ()); + + float distNodes = + (node.x-prevNode.x)*(node.x-prevNode.x)+ + (node.y-prevNode.y)*(node.y-prevNode.y)+ + (node.z-prevNode.z)*(node.z-prevNode.z); + + if(distNext + distPrev < distNodes) + { + startNode = i; + break; + } + } + + SendDoFlight( MountId, path, startNode ); + } + + // Load pet if any and player is alive and not in taxi flight + if(pCurrChar->isAlive() && pCurrChar->m_taxi.GetTaxiSource()==0) + pCurrChar->LoadPet(); + + // Set FFA PvP for non GM in non-rest mode + if(sWorld.IsFFAPvPRealm() && !pCurrChar->isGameMaster() && !pCurrChar->HasFlag(PLAYER_FLAGS,PLAYER_FLAGS_RESTING) ) + pCurrChar->SetFlag(PLAYER_FLAGS,PLAYER_FLAGS_FFA_PVP); + + if(pCurrChar->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP)) + pCurrChar->SetContestedPvP(); + + // Apply at_login requests + if(pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_SPELLS)) + { + pCurrChar->resetSpells(); + SendNotification(LANG_RESET_SPELLS); + } + + if(pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_TALENTS)) + { + pCurrChar->resetTalents(true); + SendNotification(LANG_RESET_TALENTS); + } + + // show time before shutdown if shutdown planned. + if(sWorld.IsShutdowning()) + sWorld.ShutdownMsg(true,pCurrChar); + + if(pCurrChar->isGameMaster()) + SendNotification(LANG_GM_ON); + + std::string IP_str = GetRemoteAddress(); + sLog.outChar("Account: %d (IP: %s) Login Character:[%s] (guid:%u)",GetAccountId(),IP_str.c_str(),pCurrChar->GetName() ,pCurrChar->GetGUID()); + + m_playerLoading = false; + delete holder; +} + +void WorldSession::HandleSetFactionAtWar( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,4+1); + + DEBUG_LOG( "WORLD: Received CMSG_SET_FACTION_ATWAR" ); + + uint32 repListID; + uint8 flag; + + recv_data >> repListID; + recv_data >> flag; + + FactionStateList::iterator itr = GetPlayer()->m_factions.find(repListID); + if (itr == GetPlayer()->m_factions.end()) + return; + + // always invisible or hidden faction can't change war state + if(itr->second.Flags & (FACTION_FLAG_INVISIBLE_FORCED|FACTION_FLAG_HIDDEN) ) + return; + + GetPlayer()->SetFactionAtWar(&itr->second,flag); +} + +//I think this function is never used :/ I dunno, but i guess this opcode not exists +void WorldSession::HandleSetFactionCheat( WorldPacket & /*recv_data*/ ) +{ + //CHECK_PACKET_SIZE(recv_data,4+4); + + //sLog.outDebug("WORLD SESSION: HandleSetFactionCheat"); + /* + uint32 FactionID; + uint32 Standing; + + recv_data >> FactionID; + recv_data >> Standing; + + std::list::iterator itr; + + for(itr = GetPlayer()->factions.begin(); itr != GetPlayer()->factions.end(); ++itr) + { + if(itr->ReputationListID == FactionID) + { + itr->Standing += Standing; + itr->Flags = (itr->Flags | 1); + break; + } + } + */ + GetPlayer()->UpdateReputation(); +} + +void WorldSession::HandleMeetingStoneInfo( WorldPacket & /*recv_data*/ ) +{ + DEBUG_LOG( "WORLD: Received CMSG_MEETING_STONE_INFO" ); + + WorldPacket data(SMSG_MEETINGSTONE_SETQUEUE, 5); + data << uint32(0) << uint8(6); + SendPacket(&data); +} + +void WorldSession::HandleTutorialFlag( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,4); + + uint32 iFlag; + recv_data >> iFlag; + + uint32 wInt = (iFlag / 32); + if (wInt >= 8) + { + //sLog.outError("CHEATER? Account:[%d] Guid[%u] tried to send wrong CMSG_TUTORIAL_FLAG", GetAccountId(),GetGUID()); + return; + } + uint32 rInt = (iFlag % 32); + + uint32 tutflag = GetPlayer()->GetTutorialInt( wInt ); + tutflag |= (1 << rInt); + GetPlayer()->SetTutorialInt( wInt, tutflag ); + + //sLog.outDebug("Received Tutorial Flag Set {%u}.", iFlag); +} + +void WorldSession::HandleTutorialClear( WorldPacket & /*recv_data*/ ) +{ + for ( uint32 iI = 0; iI < 8; iI++) + GetPlayer()->SetTutorialInt( iI, 0xFFFFFFFF ); +} + +void WorldSession::HandleTutorialReset( WorldPacket & /*recv_data*/ ) +{ + for ( uint32 iI = 0; iI < 8; iI++) + GetPlayer()->SetTutorialInt( iI, 0x00000000 ); +} + +void WorldSession::HandleSetWatchedFactionIndexOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data,4); + + DEBUG_LOG("WORLD: Received CMSG_SET_WATCHED_FACTION"); + uint32 fact; + recv_data >> fact; + GetPlayer()->SetUInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fact); +} + +void WorldSession::HandleSetWatchedFactionInactiveOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data,4+1); + + DEBUG_LOG("WORLD: Received CMSG_SET_FACTION_INACTIVE"); + uint32 replistid; + uint8 inactive; + recv_data >> replistid >> inactive; + + FactionStateList::iterator itr = _player->m_factions.find(replistid); + if (itr == _player->m_factions.end()) + return; + + _player->SetFactionInactive(&itr->second, inactive); +} + +void WorldSession::HandleToggleHelmOpcode( WorldPacket & /*recv_data*/ ) +{ + DEBUG_LOG("CMSG_TOGGLE_HELM for %s", _player->GetName()); + _player->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_HELM); +} + +void WorldSession::HandleToggleCloakOpcode( WorldPacket & /*recv_data*/ ) +{ + DEBUG_LOG("CMSG_TOGGLE_CLOAK for %s", _player->GetName()); + _player->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_CLOAK); +} + +void WorldSession::HandleChangePlayerNameOpcode(WorldPacket& recv_data) +{ + CHECK_PACKET_SIZE(recv_data,8+1); + + uint64 guid; + std::string newname; + std::string oldname; + + CHECK_PACKET_SIZE(recv_data, 8+1); + + recv_data >> guid; + recv_data >> newname; + + QueryResult *result = CharacterDatabase.PQuery("SELECT at_login FROM characters WHERE guid ='%u'", GUID_LOPART(guid)); + if (result) + { + uint32 at_loginFlags; + Field *fields = result->Fetch(); + at_loginFlags = fields[0].GetUInt32(); + delete result; + + if (!(at_loginFlags & AT_LOGIN_RENAME)) + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << (uint8)CHAR_CREATE_ERROR; + SendPacket( &data ); + return; + } + } + else + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << (uint8)CHAR_CREATE_ERROR; + SendPacket( &data ); + return; + } + + if(!objmgr.GetPlayerNameByGUID(guid, oldname)) // character not exist, because we have no name for this guid + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << (uint8)CHAR_LOGIN_NO_CHARACTER; + SendPacket( &data ); + return; + } + + // prevent character rename to invalid name + if(!normalizePlayerName(newname)) + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << (uint8)CHAR_NAME_NO_NAME; + SendPacket( &data ); + return; + } + + if(!ObjectMgr::IsValidName(newname,true)) + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << (uint8)CHAR_NAME_INVALID_CHARACTER; + SendPacket( &data ); + return; + } + + // check name limitations + if(GetSecurity() == SEC_PLAYER && objmgr.IsReservedName(newname)) + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << (uint8)CHAR_NAME_RESERVED; + SendPacket( &data ); + return; + } + + if(objmgr.GetPlayerGUIDByName(newname)) // character with this name already exist + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << (uint8)CHAR_CREATE_ERROR; + SendPacket( &data ); + return; + } + + if(newname == oldname) // checked by client + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << (uint8)CHAR_NAME_FAILURE; + SendPacket( &data ); + return; + } + + // we have to check character at_login_flag & AT_LOGIN_RENAME also (fake packets hehe) + + CharacterDatabase.escape_string(newname); + CharacterDatabase.PExecute("UPDATE characters set name = '%s', at_login = at_login & ~ %u WHERE guid ='%u'", newname.c_str(), uint32(AT_LOGIN_RENAME),GUID_LOPART(guid)); + CharacterDatabase.PExecute("DELETE FROM character_declinedname WHERE guid ='%u'", GUID_LOPART(guid)); + + std::string IP_str = GetRemoteAddress(); + sLog.outChar("Account: %d (IP: %s) Character:[%s] (guid:%u) Changed name to: %s",GetAccountId(),IP_str.c_str(),oldname.c_str(),GUID_LOPART(guid),newname.c_str()); + + WorldPacket data(SMSG_CHAR_RENAME,1+8+(newname.size()+1)); + data << (uint8)RESPONSE_SUCCESS; + data << guid; + data << newname; + SendPacket(&data); +} + +void WorldSession::HandleDeclinedPlayerNameOpcode(WorldPacket& recv_data) +{ + uint64 guid; + + CHECK_PACKET_SIZE(recv_data, 8+6); + recv_data >> guid; + + // not accept declined names for unsupported languages + std::string name; + if(!objmgr.GetPlayerNameByGUID(guid,name)) + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); + data << (uint32)1; + data << guid; + SendPacket(&data); + return; + } + + std::wstring wname; + if(!Utf8toWStr(name,wname)) + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); + data << (uint32)1; + data << guid; + SendPacket(&data); + return; + } + + if(!isCyrillicCharacter(wname[0])) // name already stored as only single alphabet using + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); + data << (uint32)1; + data << guid; + SendPacket(&data); + return; + } + + std::string name2; + DeclinedName declinedname; + + recv_data >> name2; + + if(name2!=name) // character have different name + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); + data << (uint32)1; + data << guid; + SendPacket(&data); + return; + } + + for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) + { + recv_data >> declinedname.name[i]; + if(!normalizePlayerName(declinedname.name[i])) + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); + data << (uint32)1; + data << guid; + SendPacket(&data); + return; + } + } + + if(!ObjectMgr::CheckDeclinedNames(GetMainPartOfName(wname,0),declinedname)) + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); + data << (uint32)1; + data << guid; + SendPacket(&data); + return; + } + + for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) + CharacterDatabase.escape_string(declinedname.name[i]); + + CharacterDatabase.BeginTransaction(); + CharacterDatabase.PExecute("DELETE FROM character_declinedname WHERE guid = '%u'", GUID_LOPART(guid)); + CharacterDatabase.PExecute("INSERT INTO character_declinedname (guid, genitive, dative, accusative, instrumental, prepositional) VALUES ('%u','%s','%s','%s','%s','%s')", + GUID_LOPART(guid), declinedname.name[0].c_str(),declinedname.name[1].c_str(),declinedname.name[2].c_str(),declinedname.name[3].c_str(),declinedname.name[4].c_str()); + CharacterDatabase.CommitTransaction(); + + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT,4+8); + data << (uint32)0; // OK + data << guid; + SendPacket(&data); +} diff --git a/src/game/Chat.cpp b/src/game/Chat.cpp index e85053a2204..e8867c516f4 100644 --- a/src/game/Chat.cpp +++ b/src/game/Chat.cpp @@ -353,6 +353,7 @@ ChatCommand * ChatHandler::getCommandTable() static ChatCommand gmCommandTable[] = { + { "chat", SEC_MODERATOR, &ChatHandler::HandleGMChatCommand, "", NULL }, { "list", SEC_PLAYER, &ChatHandler::HandleGMListCommand, "", NULL }, { "visible", SEC_MODERATOR, &ChatHandler::HandleVisibleCommand, "", NULL }, { "fly", SEC_ADMINISTRATOR, &ChatHandler::HandleFlyModeCommand, "", NULL }, @@ -507,18 +508,29 @@ const char *ChatHandler::GetMangosString(int32 entry) return m_session->GetMangosString(entry); } -bool ChatHandler::hasStringAbbr(const char* s1, const char* s2) +bool ChatHandler::hasStringAbbr(const char* name, const char* part) { - for(;;) + // non "" command + if( *name ) { - if( !*s2 ) - return true; - else if( !*s1 ) + // "" part from non-"" command + if( !*part ) return false; - else if( tolower( *s1 ) != tolower( *s2 ) ) - return false; - ++s1; ++s2; + + for(;;) + { + if( !*part ) + return true; + else if( !*name ) + return false; + else if( tolower( *name ) != tolower( *part ) ) + return false; + ++name; ++part; + } } + // allow with any for "" + + return true; } void ChatHandler::SendSysMessage(const char *str) @@ -594,13 +606,9 @@ bool ChatHandler::ExecuteCommandInTable(ChatCommand *table, const char* text, st while (*text == ' ') ++text; - if(!cmd.length()) - return false; - for(uint32 i = 0; table[i].Name != NULL; i++) { - // allow pass "" command name in table - if(strlen(table[i].Name) && !hasStringAbbr(table[i].Name, cmd.c_str())) + if( !hasStringAbbr(table[i].Name, cmd.c_str()) ) continue; // select subcommand from child commands list @@ -688,8 +696,7 @@ bool ChatHandler::ShowHelpForSubCommands(ChatCommand *table, char const* cmd, ch if(m_session->GetSecurity() < table[i].SecurityLevel) continue; - if(strlen(table[i].Name) && !hasStringAbbr(table[i].Name, subcmd)) - continue; + if( !hasStringAbbr(table[i].Name, subcmd) ) (list += "\n ") += table[i].Name; } @@ -717,7 +724,7 @@ bool ChatHandler::ShowHelpForCommand(ChatCommand *table, const char* cmd) if(m_session->GetSecurity() < table[i].SecurityLevel) continue; - if(strlen(table[i].Name) && !hasStringAbbr(table[i].Name, cmd)) + if( !hasStringAbbr(table[i].Name, cmd) ) continue; // have subcommand diff --git a/src/game/Chat.h b/src/game/Chat.h index 26e3a3a969a..74366659f66 100644 --- a/src/game/Chat.h +++ b/src/game/Chat.h @@ -69,7 +69,7 @@ class ChatHandler int ParseCommands(const char* text); protected: - bool hasStringAbbr(const char* s1, const char* s2); + bool hasStringAbbr(const char* name, const char* part); void SendGlobalSysMessage(const char *str); bool ExecuteCommandInTable(ChatCommand *table, const char* text, std::string fullcommand); @@ -94,6 +94,7 @@ class ChatHandler bool HandleAnnounceCommand(const char* args); bool HandleNotifyCommand(const char* args); bool HandleGMmodeCommand(const char* args); + bool HandleGMChatCommand(const char* args); bool HandleVisibleCommand(const char* args); bool HandleGPSCommand(const char* args); bool HandleTaxiCheatCommand(const char* args); diff --git a/src/game/ChatHandler.cpp b/src/game/ChatHandler.cpp index dd3a711858d..3a3f2d1d1db 100644 --- a/src/game/ChatHandler.cpp +++ b/src/game/ChatHandler.cpp @@ -1,583 +1,583 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "Log.h" -#include "WorldPacket.h" -#include "WorldSession.h" -#include "World.h" -#include "Opcodes.h" -#include "ObjectMgr.h" -#include "Chat.h" -#include "Database/DatabaseEnv.h" -#include "ChannelMgr.h" -#include "Group.h" -#include "Guild.h" -#include "MapManager.h" -#include "ObjectAccessor.h" -#include "ScriptCalls.h" -#include "Player.h" -#include "SpellAuras.h" -#include "Language.h" -#include "Util.h" - -void WorldSession::HandleMessagechatOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,4+4+1); - - uint32 type; - uint32 lang; - - recv_data >> type; - recv_data >> lang; - - if(type >= MAX_CHAT_MSG_TYPE) - { - sLog.outError("CHAT: Wrong message type received: %u", type); - return; - } - - //sLog.outDebug("CHAT: packet received. type %u, lang %u", type, lang ); - - // prevent talking at unknown language (cheating) - LanguageDesc const* langDesc = GetLanguageDescByID(lang); - if(!langDesc) - { - SendNotification("Unknown language"); - return; - } - if(langDesc->skill_id != 0 && !_player->HasSkill(langDesc->skill_id)) - { - // also check SPELL_AURA_COMPREHEND_LANGUAGE (client offers option to speak in that language) - Unit::AuraList const& langAuras = _player->GetAurasByType(SPELL_AURA_COMPREHEND_LANGUAGE); - bool foundAura = false; - for(Unit::AuraList::const_iterator i = langAuras.begin();i != langAuras.end(); ++i) - { - if((*i)->GetModifier()->m_miscvalue == lang) - { - foundAura = true; - break; - } - } - if(!foundAura) - { - SendNotification("You don't know that language"); - return; - } - } - - if(lang == LANG_ADDON) - { - // Disabled addon channel? - if(!sWorld.getConfig(CONFIG_ADDON_CHANNEL)) - return; - } - // LANG_ADDON should not be changed nor be affected by flood control - else - { - // send in universal language if player in .gmon mode (ignore spell effects) - if (_player->isGameMaster()) - lang = LANG_UNIVERSAL; - else - { - // send in universal language in two side iteration allowed mode - if (sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHAT)) - lang = LANG_UNIVERSAL; - else - { - switch(type) - { - case CHAT_MSG_PARTY: - case CHAT_MSG_RAID: - case CHAT_MSG_RAID_LEADER: - case CHAT_MSG_RAID_WARNING: - // allow two side chat at group channel if two side group allowed - if(sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP)) - lang = LANG_UNIVERSAL; - break; - case CHAT_MSG_GUILD: - case CHAT_MSG_OFFICER: - // allow two side chat at guild channel if two side guild allowed - if(sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD)) - lang = LANG_UNIVERSAL; - break; - } - } - - // but overwrite it by SPELL_AURA_MOD_LANGUAGE auras (only single case used) - Unit::AuraList const& ModLangAuras = _player->GetAurasByType(SPELL_AURA_MOD_LANGUAGE); - if(!ModLangAuras.empty()) - lang = ModLangAuras.front()->GetModifier()->m_miscvalue; - } - - if (!_player->CanSpeak()) - { - std::string timeStr = secsToTimeString(m_muteTime - time(NULL)); - SendNotification(GetMangosString(LANG_WAIT_BEFORE_SPEAKING),timeStr.c_str()); - return; - } - - if (type != CHAT_MSG_AFK && type != CHAT_MSG_DND) - GetPlayer()->UpdateSpeakTime(); - } - - switch(type) - { - case CHAT_MSG_SAY: - case CHAT_MSG_EMOTE: - case CHAT_MSG_YELL: - { - std::string msg = ""; - recv_data >> msg; - - if(msg.empty()) - break; - - if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) - break; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - if(type == CHAT_MSG_SAY) - GetPlayer()->Say(msg, lang); - else if(type == CHAT_MSG_EMOTE) - GetPlayer()->TextEmote(msg); - else if(type == CHAT_MSG_YELL) - GetPlayer()->Yell(msg, lang); - } break; - - case CHAT_MSG_WHISPER: - { - std::string to, msg; - recv_data >> to; - CHECK_PACKET_SIZE(recv_data,4+4+(to.size()+1)+1); - recv_data >> msg; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - if(!normalizePlayerName(to)) - { - WorldPacket data(SMSG_CHAT_PLAYER_NOT_FOUND, (to.size()+1)); - data<GetSession()->GetSecurity() : 0; - if(!player || tSecurity == SEC_PLAYER && pSecurity > SEC_PLAYER && !player->isAcceptWhispers()) - { - WorldPacket data(SMSG_CHAT_PLAYER_NOT_FOUND, (to.size()+1)); - data<GetTeam(); - uint32 sideb = player->GetTeam(); - if( sidea != sideb ) - { - WorldPacket data(SMSG_CHAT_PLAYER_NOT_FOUND, (to.size()+1)); - data<Whisper(msg, lang,player->GetGUID()); - } break; - - case CHAT_MSG_PARTY: - { - std::string msg = ""; - recv_data >> msg; - - if(msg.empty()) - break; - - if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) - break; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - Group *group = GetPlayer()->GetGroup(); - if(!group) - return; - - WorldPacket data; - ChatHandler::FillMessageData(&data, this, CHAT_MSG_PARTY, lang, NULL, 0, msg.c_str(),NULL); - group->BroadcastPacket(&data, group->GetMemberGroup(GetPlayer()->GetGUID())); - } - break; - case CHAT_MSG_GUILD: - { - std::string msg = ""; - recv_data >> msg; - - if(msg.empty()) - break; - - if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) - break; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - if (GetPlayer()->GetGuildId()) - { - Guild *guild = objmgr.GetGuildById(GetPlayer()->GetGuildId()); - if (guild) - guild->BroadcastToGuild(this, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL); - } - - break; - } - case CHAT_MSG_OFFICER: - { - std::string msg = ""; - recv_data >> msg; - - if(msg.empty()) - break; - - if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) - break; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - if (GetPlayer()->GetGuildId()) - { - Guild *guild = objmgr.GetGuildById(GetPlayer()->GetGuildId()); - if (guild) - guild->BroadcastToOfficers(this, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL); - } - break; - } - case CHAT_MSG_RAID: - { - std::string msg=""; - recv_data >> msg; - - if(msg.empty()) - break; - - if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) - break; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - Group *group = GetPlayer()->GetGroup(); - if(!group || !group->isRaidGroup()) - return; - - WorldPacket data; - ChatHandler::FillMessageData(&data, this, CHAT_MSG_RAID, lang, "", 0, msg.c_str(),NULL); - group->BroadcastPacket(&data); - } break; - case CHAT_MSG_RAID_LEADER: - { - std::string msg=""; - recv_data >> msg; - - if(msg.empty()) - break; - - if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) - break; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - Group *group = GetPlayer()->GetGroup(); - if(!group || !group->isRaidGroup() || !group->IsLeader(GetPlayer()->GetGUID())) - return; - - WorldPacket data; - ChatHandler::FillMessageData(&data, this, CHAT_MSG_RAID_LEADER, lang, "", 0, msg.c_str(),NULL); - group->BroadcastPacket(&data); - } break; - case CHAT_MSG_RAID_WARNING: - { - std::string msg=""; - recv_data >> msg; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - Group *group = GetPlayer()->GetGroup(); - if(!group || !group->isRaidGroup() || !(group->IsLeader(GetPlayer()->GetGUID()) || group->IsAssistant(GetPlayer()->GetGUID()))) - return; - - WorldPacket data; - ChatHandler::FillMessageData(&data, this, CHAT_MSG_RAID_WARNING, lang, "", 0, msg.c_str(),NULL); - group->BroadcastPacket(&data); - } break; - - case CHAT_MSG_BATTLEGROUND: - { - std::string msg=""; - recv_data >> msg; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - Group *group = GetPlayer()->GetGroup(); - if(!group || !group->isRaidGroup()) - return; - - WorldPacket data; - ChatHandler::FillMessageData(&data, this, CHAT_MSG_BATTLEGROUND, lang, "", 0, msg.c_str(),NULL); - group->BroadcastPacket(&data); - } break; - - case CHAT_MSG_BATTLEGROUND_LEADER: - { - std::string msg=""; - recv_data >> msg; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - Group *group = GetPlayer()->GetGroup(); - if(!group || !group->isRaidGroup() || !group->IsLeader(GetPlayer()->GetGUID())) - return; - - WorldPacket data; - ChatHandler::FillMessageData(&data, this, CHAT_MSG_BATTLEGROUND_LEADER, lang, "", 0, msg.c_str(),NULL); - group->BroadcastPacket(&data); - } break; - - case CHAT_MSG_CHANNEL: - { - std::string channel = "", msg = ""; - recv_data >> channel; - - // recheck - CHECK_PACKET_SIZE(recv_data,4+4+(channel.size()+1)+1); - - recv_data >> msg; - - // strip invisible characters for non-addon messages - if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) - stripLineInvisibleChars(msg); - - if(msg.empty()) - break; - - if(ChannelMgr* cMgr = channelMgr(_player->GetTeam())) - { - if(Channel *chn = cMgr->GetChannel(channel,_player)) - chn->Say(_player->GetGUID(),msg.c_str(),lang); - } - } break; - - case CHAT_MSG_AFK: - { - std::string msg; - recv_data >> msg; - - if((msg.empty() || !_player->isAFK()) && !_player->isInCombat() ) - { - if(!_player->isAFK()) - { - if(msg.empty()) - msg = GetMangosString(LANG_PLAYER_AFK_DEFAULT); - _player->afkMsg = msg; - } - _player->ToggleAFK(); - if(_player->isAFK() && _player->isDND()) - _player->ToggleDND(); - } - } break; - - case CHAT_MSG_DND: - { - std::string msg; - recv_data >> msg; - - if(msg.empty() || !_player->isDND()) - { - if(!_player->isDND()) - { - if(msg.empty()) - msg = GetMangosString(LANG_PLAYER_DND_DEFAULT); - _player->dndMsg = msg; - } - _player->ToggleDND(); - if(_player->isDND() && _player->isAFK()) - _player->ToggleAFK(); - } - } break; - - default: - sLog.outError("CHAT: unknown message type %u, lang: %u", type, lang); - break; - } -} - -void WorldSession::HandleEmoteOpcode( WorldPacket & recv_data ) -{ - if(!GetPlayer()->isAlive()) - return; - CHECK_PACKET_SIZE(recv_data,4); - - uint32 emote; - recv_data >> emote; - GetPlayer()->HandleEmoteCommand(emote); -} - -void WorldSession::HandleTextEmoteOpcode( WorldPacket & recv_data ) -{ - if(!GetPlayer()->isAlive()) - return; - - if (!GetPlayer()->CanSpeak()) - { - std::string timeStr = secsToTimeString(m_muteTime - time(NULL)); - SendNotification(GetMangosString(LANG_WAIT_BEFORE_SPEAKING),timeStr.c_str()); - return; - } - - CHECK_PACKET_SIZE(recv_data,4+4+8); - - uint32 text_emote, emoteNum; - uint64 guid; - - recv_data >> text_emote; - recv_data >> emoteNum; - recv_data >> guid; - - const char *nam = 0; - uint32 namlen = 1; - - Unit* unit = ObjectAccessor::GetUnit(*_player, guid); - Creature *pCreature = dynamic_cast(unit); - if(unit) - { - nam = unit->GetName(); - namlen = (nam ? strlen(nam) : 0) + 1; - } - - EmotesTextEntry const *em = sEmotesTextStore.LookupEntry(text_emote); - if (em) - { - uint32 emote_anim = em->textid; - - WorldPacket data; - - switch(emote_anim) - { - case EMOTE_STATE_SLEEP: - case EMOTE_STATE_SIT: - case EMOTE_STATE_KNEEL: - case EMOTE_ONESHOT_NONE: - break; - default: - GetPlayer()->HandleEmoteCommand(emote_anim); - break; - } - - data.Initialize(SMSG_TEXT_EMOTE, (20+namlen)); - data << GetPlayer()->GetGUID(); - data << (uint32)text_emote; - data << emoteNum; - data << (uint32)namlen; - if( namlen > 1 ) - { - data.append(nam, namlen); - } - else - { - data << (uint8)0x00; - } - - GetPlayer()->SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE),true); - - //Send scripted event call - if (pCreature && Script) - Script->ReceiveEmote(GetPlayer(),pCreature,text_emote); - } -} - -void WorldSession::HandleChatIgnoredOpcode(WorldPacket& recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 8+1); - - uint64 iguid; - uint8 unk; - //sLog.outDebug("WORLD: Received CMSG_CHAT_IGNORED"); - - recv_data >> iguid; - recv_data >> unk; // probably related to spam reporting - - Player *player = objmgr.GetPlayer(iguid); - if(!player || !player->GetSession()) - return; - - WorldPacket data; - ChatHandler::FillMessageData(&data, this, CHAT_MSG_IGNORED, LANG_UNIVERSAL, NULL, GetPlayer()->GetGUID(), GetPlayer()->GetName(),NULL); - player->GetSession()->SendPacket(&data); -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "Log.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "World.h" +#include "Opcodes.h" +#include "ObjectMgr.h" +#include "Chat.h" +#include "Database/DatabaseEnv.h" +#include "ChannelMgr.h" +#include "Group.h" +#include "Guild.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "ScriptCalls.h" +#include "Player.h" +#include "SpellAuras.h" +#include "Language.h" +#include "Util.h" + +void WorldSession::HandleMessagechatOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,4+4+1); + + uint32 type; + uint32 lang; + + recv_data >> type; + recv_data >> lang; + + if(type >= MAX_CHAT_MSG_TYPE) + { + sLog.outError("CHAT: Wrong message type received: %u", type); + return; + } + + //sLog.outDebug("CHAT: packet received. type %u, lang %u", type, lang ); + + // prevent talking at unknown language (cheating) + LanguageDesc const* langDesc = GetLanguageDescByID(lang); + if(!langDesc) + { + SendNotification(LANG_UNKNOWN_LANGUAGE); + return; + } + if(langDesc->skill_id != 0 && !_player->HasSkill(langDesc->skill_id)) + { + // also check SPELL_AURA_COMPREHEND_LANGUAGE (client offers option to speak in that language) + Unit::AuraList const& langAuras = _player->GetAurasByType(SPELL_AURA_COMPREHEND_LANGUAGE); + bool foundAura = false; + for(Unit::AuraList::const_iterator i = langAuras.begin();i != langAuras.end(); ++i) + { + if((*i)->GetModifier()->m_miscvalue == lang) + { + foundAura = true; + break; + } + } + if(!foundAura) + { + SendNotification(LANG_NOT_LEARNED_LANGUAGE); + return; + } + } + + if(lang == LANG_ADDON) + { + // Disabled addon channel? + if(!sWorld.getConfig(CONFIG_ADDON_CHANNEL)) + return; + } + // LANG_ADDON should not be changed nor be affected by flood control + else + { + // send in universal language if player in .gmon mode (ignore spell effects) + if (_player->isGameMaster()) + lang = LANG_UNIVERSAL; + else + { + // send in universal language in two side iteration allowed mode + if (sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHAT)) + lang = LANG_UNIVERSAL; + else + { + switch(type) + { + case CHAT_MSG_PARTY: + case CHAT_MSG_RAID: + case CHAT_MSG_RAID_LEADER: + case CHAT_MSG_RAID_WARNING: + // allow two side chat at group channel if two side group allowed + if(sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP)) + lang = LANG_UNIVERSAL; + break; + case CHAT_MSG_GUILD: + case CHAT_MSG_OFFICER: + // allow two side chat at guild channel if two side guild allowed + if(sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD)) + lang = LANG_UNIVERSAL; + break; + } + } + + // but overwrite it by SPELL_AURA_MOD_LANGUAGE auras (only single case used) + Unit::AuraList const& ModLangAuras = _player->GetAurasByType(SPELL_AURA_MOD_LANGUAGE); + if(!ModLangAuras.empty()) + lang = ModLangAuras.front()->GetModifier()->m_miscvalue; + } + + if (!_player->CanSpeak()) + { + std::string timeStr = secsToTimeString(m_muteTime - time(NULL)); + SendNotification(GetMangosString(LANG_WAIT_BEFORE_SPEAKING),timeStr.c_str()); + return; + } + + if (type != CHAT_MSG_AFK && type != CHAT_MSG_DND) + GetPlayer()->UpdateSpeakTime(); + } + + switch(type) + { + case CHAT_MSG_SAY: + case CHAT_MSG_EMOTE: + case CHAT_MSG_YELL: + { + std::string msg = ""; + recv_data >> msg; + + if(msg.empty()) + break; + + if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) + break; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + if(type == CHAT_MSG_SAY) + GetPlayer()->Say(msg, lang); + else if(type == CHAT_MSG_EMOTE) + GetPlayer()->TextEmote(msg); + else if(type == CHAT_MSG_YELL) + GetPlayer()->Yell(msg, lang); + } break; + + case CHAT_MSG_WHISPER: + { + std::string to, msg; + recv_data >> to; + CHECK_PACKET_SIZE(recv_data,4+4+(to.size()+1)+1); + recv_data >> msg; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + if(!normalizePlayerName(to)) + { + WorldPacket data(SMSG_CHAT_PLAYER_NOT_FOUND, (to.size()+1)); + data<GetSession()->GetSecurity() : 0; + if(!player || tSecurity == SEC_PLAYER && pSecurity > SEC_PLAYER && !player->isAcceptWhispers()) + { + WorldPacket data(SMSG_CHAT_PLAYER_NOT_FOUND, (to.size()+1)); + data<GetTeam(); + uint32 sideb = player->GetTeam(); + if( sidea != sideb ) + { + WorldPacket data(SMSG_CHAT_PLAYER_NOT_FOUND, (to.size()+1)); + data<Whisper(msg, lang,player->GetGUID()); + } break; + + case CHAT_MSG_PARTY: + { + std::string msg = ""; + recv_data >> msg; + + if(msg.empty()) + break; + + if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) + break; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + Group *group = GetPlayer()->GetGroup(); + if(!group) + return; + + WorldPacket data; + ChatHandler::FillMessageData(&data, this, CHAT_MSG_PARTY, lang, NULL, 0, msg.c_str(),NULL); + group->BroadcastPacket(&data, group->GetMemberGroup(GetPlayer()->GetGUID())); + } + break; + case CHAT_MSG_GUILD: + { + std::string msg = ""; + recv_data >> msg; + + if(msg.empty()) + break; + + if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) + break; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + if (GetPlayer()->GetGuildId()) + { + Guild *guild = objmgr.GetGuildById(GetPlayer()->GetGuildId()); + if (guild) + guild->BroadcastToGuild(this, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL); + } + + break; + } + case CHAT_MSG_OFFICER: + { + std::string msg = ""; + recv_data >> msg; + + if(msg.empty()) + break; + + if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) + break; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + if (GetPlayer()->GetGuildId()) + { + Guild *guild = objmgr.GetGuildById(GetPlayer()->GetGuildId()); + if (guild) + guild->BroadcastToOfficers(this, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL); + } + break; + } + case CHAT_MSG_RAID: + { + std::string msg=""; + recv_data >> msg; + + if(msg.empty()) + break; + + if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) + break; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + Group *group = GetPlayer()->GetGroup(); + if(!group || !group->isRaidGroup()) + return; + + WorldPacket data; + ChatHandler::FillMessageData(&data, this, CHAT_MSG_RAID, lang, "", 0, msg.c_str(),NULL); + group->BroadcastPacket(&data); + } break; + case CHAT_MSG_RAID_LEADER: + { + std::string msg=""; + recv_data >> msg; + + if(msg.empty()) + break; + + if (ChatHandler(this).ParseCommands(msg.c_str()) > 0) + break; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + Group *group = GetPlayer()->GetGroup(); + if(!group || !group->isRaidGroup() || !group->IsLeader(GetPlayer()->GetGUID())) + return; + + WorldPacket data; + ChatHandler::FillMessageData(&data, this, CHAT_MSG_RAID_LEADER, lang, "", 0, msg.c_str(),NULL); + group->BroadcastPacket(&data); + } break; + case CHAT_MSG_RAID_WARNING: + { + std::string msg=""; + recv_data >> msg; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + Group *group = GetPlayer()->GetGroup(); + if(!group || !group->isRaidGroup() || !(group->IsLeader(GetPlayer()->GetGUID()) || group->IsAssistant(GetPlayer()->GetGUID()))) + return; + + WorldPacket data; + ChatHandler::FillMessageData(&data, this, CHAT_MSG_RAID_WARNING, lang, "", 0, msg.c_str(),NULL); + group->BroadcastPacket(&data); + } break; + + case CHAT_MSG_BATTLEGROUND: + { + std::string msg=""; + recv_data >> msg; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + Group *group = GetPlayer()->GetGroup(); + if(!group || !group->isRaidGroup()) + return; + + WorldPacket data; + ChatHandler::FillMessageData(&data, this, CHAT_MSG_BATTLEGROUND, lang, "", 0, msg.c_str(),NULL); + group->BroadcastPacket(&data); + } break; + + case CHAT_MSG_BATTLEGROUND_LEADER: + { + std::string msg=""; + recv_data >> msg; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + Group *group = GetPlayer()->GetGroup(); + if(!group || !group->isRaidGroup() || !group->IsLeader(GetPlayer()->GetGUID())) + return; + + WorldPacket data; + ChatHandler::FillMessageData(&data, this, CHAT_MSG_BATTLEGROUND_LEADER, lang, "", 0, msg.c_str(),NULL); + group->BroadcastPacket(&data); + } break; + + case CHAT_MSG_CHANNEL: + { + std::string channel = "", msg = ""; + recv_data >> channel; + + // recheck + CHECK_PACKET_SIZE(recv_data,4+4+(channel.size()+1)+1); + + recv_data >> msg; + + // strip invisible characters for non-addon messages + if (lang != LANG_ADDON && sWorld.getConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) + stripLineInvisibleChars(msg); + + if(msg.empty()) + break; + + if(ChannelMgr* cMgr = channelMgr(_player->GetTeam())) + { + if(Channel *chn = cMgr->GetChannel(channel,_player)) + chn->Say(_player->GetGUID(),msg.c_str(),lang); + } + } break; + + case CHAT_MSG_AFK: + { + std::string msg; + recv_data >> msg; + + if((msg.empty() || !_player->isAFK()) && !_player->isInCombat() ) + { + if(!_player->isAFK()) + { + if(msg.empty()) + msg = GetMangosString(LANG_PLAYER_AFK_DEFAULT); + _player->afkMsg = msg; + } + _player->ToggleAFK(); + if(_player->isAFK() && _player->isDND()) + _player->ToggleDND(); + } + } break; + + case CHAT_MSG_DND: + { + std::string msg; + recv_data >> msg; + + if(msg.empty() || !_player->isDND()) + { + if(!_player->isDND()) + { + if(msg.empty()) + msg = GetMangosString(LANG_PLAYER_DND_DEFAULT); + _player->dndMsg = msg; + } + _player->ToggleDND(); + if(_player->isDND() && _player->isAFK()) + _player->ToggleAFK(); + } + } break; + + default: + sLog.outError("CHAT: unknown message type %u, lang: %u", type, lang); + break; + } +} + +void WorldSession::HandleEmoteOpcode( WorldPacket & recv_data ) +{ + if(!GetPlayer()->isAlive()) + return; + CHECK_PACKET_SIZE(recv_data,4); + + uint32 emote; + recv_data >> emote; + GetPlayer()->HandleEmoteCommand(emote); +} + +void WorldSession::HandleTextEmoteOpcode( WorldPacket & recv_data ) +{ + if(!GetPlayer()->isAlive()) + return; + + if (!GetPlayer()->CanSpeak()) + { + std::string timeStr = secsToTimeString(m_muteTime - time(NULL)); + SendNotification(GetMangosString(LANG_WAIT_BEFORE_SPEAKING),timeStr.c_str()); + return; + } + + CHECK_PACKET_SIZE(recv_data,4+4+8); + + uint32 text_emote, emoteNum; + uint64 guid; + + recv_data >> text_emote; + recv_data >> emoteNum; + recv_data >> guid; + + const char *nam = 0; + uint32 namlen = 1; + + Unit* unit = ObjectAccessor::GetUnit(*_player, guid); + Creature *pCreature = dynamic_cast(unit); + if(unit) + { + nam = unit->GetName(); + namlen = (nam ? strlen(nam) : 0) + 1; + } + + EmotesTextEntry const *em = sEmotesTextStore.LookupEntry(text_emote); + if (em) + { + uint32 emote_anim = em->textid; + + WorldPacket data; + + switch(emote_anim) + { + case EMOTE_STATE_SLEEP: + case EMOTE_STATE_SIT: + case EMOTE_STATE_KNEEL: + case EMOTE_ONESHOT_NONE: + break; + default: + GetPlayer()->HandleEmoteCommand(emote_anim); + break; + } + + data.Initialize(SMSG_TEXT_EMOTE, (20+namlen)); + data << GetPlayer()->GetGUID(); + data << (uint32)text_emote; + data << emoteNum; + data << (uint32)namlen; + if( namlen > 1 ) + { + data.append(nam, namlen); + } + else + { + data << (uint8)0x00; + } + + GetPlayer()->SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE),true); + + //Send scripted event call + if (pCreature && Script) + Script->ReceiveEmote(GetPlayer(),pCreature,text_emote); + } +} + +void WorldSession::HandleChatIgnoredOpcode(WorldPacket& recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 8+1); + + uint64 iguid; + uint8 unk; + //sLog.outDebug("WORLD: Received CMSG_CHAT_IGNORED"); + + recv_data >> iguid; + recv_data >> unk; // probably related to spam reporting + + Player *player = objmgr.GetPlayer(iguid); + if(!player || !player->GetSession()) + return; + + WorldPacket data; + ChatHandler::FillMessageData(&data, this, CHAT_MSG_IGNORED, LANG_UNIVERSAL, NULL, GetPlayer()->GetGUID(), GetPlayer()->GetName(),NULL); + player->GetSession()->SendPacket(&data); +} diff --git a/src/game/ConfusedMovementGenerator.cpp b/src/game/ConfusedMovementGenerator.cpp index f1e7c2c1548..7b4c5b91f71 100644 --- a/src/game/ConfusedMovementGenerator.cpp +++ b/src/game/ConfusedMovementGenerator.cpp @@ -1,155 +1,155 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Creature.h" -#include "MapManager.h" -#include "Opcodes.h" -#include "ConfusedMovementGenerator.h" -#include "DestinationHolderImp.h" - -template -void -ConfusedMovementGenerator::Initialize(T &unit) -{ - const float wander_distance=11; - float x,y,z; - x = unit.GetPositionX(); - y = unit.GetPositionY(); - z = unit.GetPositionZ(); - uint32 mapid=unit.GetMapId(); - - Map const* map = MapManager::Instance().GetBaseMap(mapid); - - i_nextMove = 1; - - bool is_water_ok, is_land_ok; - _InitSpecific(unit, is_water_ok, is_land_ok); - - for(unsigned int idx=0; idx < MAX_CONF_WAYPOINTS+1; ++idx) - { - const float wanderX=wander_distance*rand_norm() - wander_distance/2; - const float wanderY=wander_distance*rand_norm() - wander_distance/2; - - i_waypoints[idx][0] = x + wanderX; - i_waypoints[idx][1] = y + wanderY; - - // prevent invalid coordinates generation - MaNGOS::NormalizeMapCoord(i_waypoints[idx][0]); - MaNGOS::NormalizeMapCoord(i_waypoints[idx][1]); - - bool is_water = map->IsInWater(i_waypoints[idx][0],i_waypoints[idx][1],z); - // if generated wrong path just ignore - if( is_water && !is_water_ok || !is_water && !is_land_ok ) - { - i_waypoints[idx][0] = idx > 0 ? i_waypoints[idx-1][0] : x; - i_waypoints[idx][1] = idx > 0 ? i_waypoints[idx-1][1] : y; - } - unit.UpdateGroundPositionZ(i_waypoints[idx][0],i_waypoints[idx][1],z); - i_waypoints[idx][2] = z; - } - - unit.StopMoving(); - unit.RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - unit.addUnitState(UNIT_STAT_CONFUSED); -} - -template<> -void -ConfusedMovementGenerator::_InitSpecific(Creature &creature, bool &is_water_ok, bool &is_land_ok) -{ - is_water_ok = creature.canSwim(); - is_land_ok = creature.canWalk(); -} - -template<> -void -ConfusedMovementGenerator::_InitSpecific(Player &, bool &is_water_ok, bool &is_land_ok) -{ - is_water_ok = true; - is_land_ok = true; -} - -template -void -ConfusedMovementGenerator::Reset(T &unit) -{ - i_nextMove = 1; - i_nextMoveTime.Reset(0); - i_destinationHolder.ResetUpdate(); - unit.StopMoving(); -} - -template -bool -ConfusedMovementGenerator::Update(T &unit, const uint32 &diff) -{ - if(!&unit) - return true; - - if(unit.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED | UNIT_STAT_DISTRACTED)) - return true; - - if( i_nextMoveTime.Passed() ) - { - // currently moving, update location - Traveller traveller(unit); - if( i_destinationHolder.UpdateTraveller(traveller, diff, false)) - { - if( i_destinationHolder.HasArrived()) - { - // arrived, stop and wait a bit - unit.StopMoving(); - - i_nextMove = urand(1,MAX_CONF_WAYPOINTS); - i_nextMoveTime.Reset(urand(0, 1500-1)); // TODO: check the minimum reset time, should be probably higher - } - } - } - else - { - // waiting for next move - i_nextMoveTime.Update(diff); - if( i_nextMoveTime.Passed() ) - { - // start moving - assert( i_nextMove <= MAX_CONF_WAYPOINTS ); - const float x = i_waypoints[i_nextMove][0]; - const float y = i_waypoints[i_nextMove][1]; - const float z = i_waypoints[i_nextMove][2]; - Traveller traveller(unit); - i_destinationHolder.SetDestination(traveller, x, y, z); - } - } - return true; -} - -template -void -ConfusedMovementGenerator::Finalize(T &unit) -{ - unit.clearUnitState(UNIT_STAT_CONFUSED); -} - -template void ConfusedMovementGenerator::Initialize(Player &player); -template void ConfusedMovementGenerator::Initialize(Creature &creature); -template void ConfusedMovementGenerator::Finalize(Player &player); -template void ConfusedMovementGenerator::Finalize(Creature &creature); -template void ConfusedMovementGenerator::Reset(Player &player); -template void ConfusedMovementGenerator::Reset(Creature &creature); -template bool ConfusedMovementGenerator::Update(Player &player, const uint32 &diff); -template bool ConfusedMovementGenerator::Update(Creature &creature, const uint32 &diff); +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Creature.h" +#include "MapManager.h" +#include "Opcodes.h" +#include "ConfusedMovementGenerator.h" +#include "DestinationHolderImp.h" + +template +void +ConfusedMovementGenerator::Initialize(T &unit) +{ + const float wander_distance=11; + float x,y,z; + x = unit.GetPositionX(); + y = unit.GetPositionY(); + z = unit.GetPositionZ(); + uint32 mapid=unit.GetMapId(); + + Map const* map = MapManager::Instance().GetBaseMap(mapid); + + i_nextMove = 1; + + bool is_water_ok, is_land_ok; + _InitSpecific(unit, is_water_ok, is_land_ok); + + for(unsigned int idx=0; idx < MAX_CONF_WAYPOINTS+1; ++idx) + { + const float wanderX=wander_distance*rand_norm() - wander_distance/2; + const float wanderY=wander_distance*rand_norm() - wander_distance/2; + + i_waypoints[idx][0] = x + wanderX; + i_waypoints[idx][1] = y + wanderY; + + // prevent invalid coordinates generation + MaNGOS::NormalizeMapCoord(i_waypoints[idx][0]); + MaNGOS::NormalizeMapCoord(i_waypoints[idx][1]); + + bool is_water = map->IsInWater(i_waypoints[idx][0],i_waypoints[idx][1],z); + // if generated wrong path just ignore + if( is_water && !is_water_ok || !is_water && !is_land_ok ) + { + i_waypoints[idx][0] = idx > 0 ? i_waypoints[idx-1][0] : x; + i_waypoints[idx][1] = idx > 0 ? i_waypoints[idx-1][1] : y; + } + unit.UpdateGroundPositionZ(i_waypoints[idx][0],i_waypoints[idx][1],z); + i_waypoints[idx][2] = z; + } + + unit.StopMoving(); + unit.RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + unit.addUnitState(UNIT_STAT_CONFUSED); +} + +template<> +void +ConfusedMovementGenerator::_InitSpecific(Creature &creature, bool &is_water_ok, bool &is_land_ok) +{ + is_water_ok = creature.canSwim(); + is_land_ok = creature.canWalk(); +} + +template<> +void +ConfusedMovementGenerator::_InitSpecific(Player &, bool &is_water_ok, bool &is_land_ok) +{ + is_water_ok = true; + is_land_ok = true; +} + +template +void +ConfusedMovementGenerator::Reset(T &unit) +{ + i_nextMove = 1; + i_nextMoveTime.Reset(0); + i_destinationHolder.ResetUpdate(); + unit.StopMoving(); +} + +template +bool +ConfusedMovementGenerator::Update(T &unit, const uint32 &diff) +{ + if(!&unit) + return true; + + if(unit.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED | UNIT_STAT_DISTRACTED)) + return true; + + if( i_nextMoveTime.Passed() ) + { + // currently moving, update location + Traveller traveller(unit); + if( i_destinationHolder.UpdateTraveller(traveller, diff, false)) + { + if( i_destinationHolder.HasArrived()) + { + // arrived, stop and wait a bit + unit.StopMoving(); + + i_nextMove = urand(1,MAX_CONF_WAYPOINTS); + i_nextMoveTime.Reset(urand(0, 1500-1)); // TODO: check the minimum reset time, should be probably higher + } + } + } + else + { + // waiting for next move + i_nextMoveTime.Update(diff); + if( i_nextMoveTime.Passed() ) + { + // start moving + assert( i_nextMove <= MAX_CONF_WAYPOINTS ); + const float x = i_waypoints[i_nextMove][0]; + const float y = i_waypoints[i_nextMove][1]; + const float z = i_waypoints[i_nextMove][2]; + Traveller traveller(unit); + i_destinationHolder.SetDestination(traveller, x, y, z); + } + } + return true; +} + +template +void +ConfusedMovementGenerator::Finalize(T &unit) +{ + unit.clearUnitState(UNIT_STAT_CONFUSED); +} + +template void ConfusedMovementGenerator::Initialize(Player &player); +template void ConfusedMovementGenerator::Initialize(Creature &creature); +template void ConfusedMovementGenerator::Finalize(Player &player); +template void ConfusedMovementGenerator::Finalize(Creature &creature); +template void ConfusedMovementGenerator::Reset(Player &player); +template void ConfusedMovementGenerator::Reset(Creature &creature); +template bool ConfusedMovementGenerator::Update(Player &player, const uint32 &diff); +template bool ConfusedMovementGenerator::Update(Creature &creature, const uint32 &diff); diff --git a/src/game/Creature.cpp b/src/game/Creature.cpp index 555bb4db02a..814b133a1b3 100644 --- a/src/game/Creature.cpp +++ b/src/game/Creature.cpp @@ -75,6 +75,14 @@ bool VendorItemData::RemoveItem( uint32 item_id ) return false; } +size_t VendorItemData::FindItemSlot(uint32 item_id) const +{ + for(size_t i = 0; i < m_items.size(); ++i ) + if(m_items[i]->item==item_id) + return i; + return m_items.size(); +} + VendorItem const* VendorItemData::FindItem(uint32 item_id) const { for(VendorItemList::const_iterator i = m_items.begin(); i != m_items.end(); ++i ) @@ -1910,26 +1918,29 @@ time_t Creature::GetRespawnTimeEx() const void Creature::GetRespawnCoord( float &x, float &y, float &z, float* ori, float* dist ) const { - if(CreatureData const* data = objmgr.GetCreatureData(GetDBTableGUIDLow())) + if (m_DBTableGuid) { - x = data->posX; - y = data->posY; - z = data->posZ; - if(ori) - *ori = data->orientation; - if(dist) - *dist = data->spawndist; - } - else - { - x = GetPositionX(); - y = GetPositionY(); - z = GetPositionZ(); - if(ori) - *ori = GetOrientation(); - if(dist) - *dist = 0; + if (CreatureData const* data = objmgr.GetCreatureData(GetDBTableGUIDLow())) + { + x = data->posX; + y = data->posY; + z = data->posZ; + if(ori) + *ori = data->orientation; + if(dist) + *dist = data->spawndist; + + return; + } } + + x = GetPositionX(); + y = GetPositionY(); + z = GetPositionZ(); + if(ori) + *ori = GetOrientation(); + if(dist) + *dist = 0; } void Creature::AllLootRemovedFromCorpse() diff --git a/src/game/Creature.h b/src/game/Creature.h index 6145b946892..c32ac7327b9 100644 --- a/src/game/Creature.h +++ b/src/game/Creature.h @@ -308,6 +308,7 @@ struct VendorItemData } bool RemoveItem( uint32 item_id ); VendorItem const* FindItem(uint32 item_id) const; + size_t FindItemSlot(uint32 item_id) const; void Clear() { diff --git a/src/game/FleeingMovementGenerator.cpp b/src/game/FleeingMovementGenerator.cpp index 80ecf13922f..97862eac567 100644 --- a/src/game/FleeingMovementGenerator.cpp +++ b/src/game/FleeingMovementGenerator.cpp @@ -1,379 +1,379 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Creature.h" -#include "MapManager.h" -#include "FleeingMovementGenerator.h" -#include "DestinationHolderImp.h" -#include "ObjectAccessor.h" - -#define MIN_QUIET_DISTANCE 28.0f -#define MAX_QUIET_DISTANCE 43.0f - -template -void -FleeingMovementGenerator::_setTargetLocation(T &owner) -{ - if( !&owner ) - return; - - if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED) ) - return; - - if(!_setMoveData(owner)) - return; - - float x, y, z; - if(!_getPoint(owner, x, y, z)) - return; - - owner.addUnitState(UNIT_STAT_FLEEING); - Traveller traveller(owner); - i_destinationHolder.SetDestination(traveller, x, y, z); -} - -template -bool -FleeingMovementGenerator::_getPoint(T &owner, float &x, float &y, float &z) -{ - if(!&owner) - return false; - - x = owner.GetPositionX(); - y = owner.GetPositionY(); - z = owner.GetPositionZ(); - - float temp_x, temp_y, angle; - const Map * _map = MapManager::Instance().GetBaseMap(owner.GetMapId()); - //primitive path-finding - for(uint8 i = 0; i < 18; i++) - { - if(i_only_forward && i > 2) - break; - - float distance = 5.0f; - - switch(i) - { - case 0: - angle = i_cur_angle; - break; - case 1: - angle = i_cur_angle; - distance /= 2; - break; - case 2: - angle = i_cur_angle; - distance /= 4; - break; - case 3: - angle = i_cur_angle + M_PI/4.0f; - break; - case 4: - angle = i_cur_angle - M_PI/4.0f; - break; - case 5: - angle = i_cur_angle + M_PI/4.0f; - distance /= 2; - break; - case 6: - angle = i_cur_angle - M_PI/4.0f; - distance /= 2; - break; - case 7: - angle = i_cur_angle + M_PI/2.0f; - break; - case 8: - angle = i_cur_angle - M_PI/2.0f; - break; - case 9: - angle = i_cur_angle + M_PI/2.0f; - distance /= 2; - break; - case 10: - angle = i_cur_angle - M_PI/2.0f; - distance /= 2; - break; - case 11: - angle = i_cur_angle + M_PI/4.0f; - distance /= 4; - break; - case 12: - angle = i_cur_angle - M_PI/4.0f; - distance /= 4; - break; - case 13: - angle = i_cur_angle + M_PI/2.0f; - distance /= 4; - break; - case 14: - angle = i_cur_angle - M_PI/2.0f; - distance /= 4; - break; - case 15: - angle = i_cur_angle + M_PI*3/4.0f; - distance /= 2; - break; - case 16: - angle = i_cur_angle - M_PI*3/4.0f; - distance /= 2; - break; - case 17: - angle = i_cur_angle + M_PI; - distance /= 2; - break; - } - temp_x = x + distance * cos(angle); - temp_y = y + distance * sin(angle); - MaNGOS::NormalizeMapCoord(temp_x); - MaNGOS::NormalizeMapCoord(temp_y); - if( owner.IsWithinLOS(temp_x,temp_y,z)) - { - bool is_water_now = _map->IsInWater(x,y,z); - - if(is_water_now && _map->IsInWater(temp_x,temp_y,z)) - { - x = temp_x; - y = temp_y; - return true; - } - float new_z = _map->GetHeight(temp_x,temp_y,z,true); - - if(new_z <= INVALID_HEIGHT) - continue; - - bool is_water_next = _map->IsInWater(temp_x,temp_y,new_z); - - if((is_water_now && !is_water_next && !is_land_ok) || (!is_water_now && is_water_next && !is_water_ok)) - continue; - - if( !(new_z - z) || distance / fabs(new_z - z) > 1.0f) - { - float new_z_left = _map->GetHeight(temp_x + 1.0f*cos(angle+M_PI/2),temp_y + 1.0f*sin(angle+M_PI/2),z,true); - float new_z_right = _map->GetHeight(temp_x + 1.0f*cos(angle-M_PI/2),temp_y + 1.0f*sin(angle-M_PI/2),z,true); - if(fabs(new_z_left - new_z) < 1.2f && fabs(new_z_right - new_z) < 1.2f) - { - x = temp_x; - y = temp_y; - z = new_z; - return true; - } - } - } - } - i_to_distance_from_caster = 0.0f; - i_nextCheckTime.Reset( urand(500,1000) ); - return false; -} - -template -bool -FleeingMovementGenerator::_setMoveData(T &owner) -{ - float cur_dist_xyz = owner.GetDistance(i_caster_x, i_caster_y, i_caster_z); - - if(i_to_distance_from_caster > 0.0f) - { - if((i_last_distance_from_caster > i_to_distance_from_caster && cur_dist_xyz < i_to_distance_from_caster) || - // if we reach lower distance - (i_last_distance_from_caster > i_to_distance_from_caster && cur_dist_xyz > i_last_distance_from_caster) || - // if we can't be close - (i_last_distance_from_caster < i_to_distance_from_caster && cur_dist_xyz > i_to_distance_from_caster) || - // if we reach bigger distance - (cur_dist_xyz > MAX_QUIET_DISTANCE) || // if we are too far - (i_last_distance_from_caster > MIN_QUIET_DISTANCE && cur_dist_xyz < MIN_QUIET_DISTANCE) ) - // if we leave 'quiet zone' - { - // we are very far or too close, stopping - i_to_distance_from_caster = 0.0f; - i_nextCheckTime.Reset( urand(500,1000) ); - return false; - } - else - { - // now we are running, continue - i_last_distance_from_caster = cur_dist_xyz; - return true; - } - } - - float cur_dist; - float angle_to_caster; - - Unit * fright = ObjectAccessor::GetUnit(owner, i_frightGUID); - - if(fright) - { - cur_dist = fright->GetDistance(&owner); - if(cur_dist < cur_dist_xyz) - { - i_caster_x = fright->GetPositionX(); - i_caster_y = fright->GetPositionY(); - i_caster_z = fright->GetPositionZ(); - angle_to_caster = fright->GetAngle(&owner); - } - else - { - cur_dist = cur_dist_xyz; - angle_to_caster = owner.GetAngle(i_caster_x, i_caster_y) + M_PI; - } - } - else - { - cur_dist = cur_dist_xyz; - angle_to_caster = owner.GetAngle(i_caster_x, i_caster_y) + M_PI; - } - - // if we too close may use 'path-finding' else just stop - i_only_forward = cur_dist >= MIN_QUIET_DISTANCE/3; - - //get angle and 'distance from caster' to run - float angle; - - if(i_cur_angle == 0.0f && i_last_distance_from_caster == 0.0f) //just started, first time - { - angle = rand_norm()*(1.0f - cur_dist/MIN_QUIET_DISTANCE) * M_PI/3 + rand_norm()*M_PI*2/3; - i_to_distance_from_caster = MIN_QUIET_DISTANCE; - i_only_forward = true; - } - else if(cur_dist < MIN_QUIET_DISTANCE) - { - angle = M_PI/6 + rand_norm()*M_PI*2/3; - i_to_distance_from_caster = cur_dist*2/3 + rand_norm()*(MIN_QUIET_DISTANCE - cur_dist*2/3); - } - else if(cur_dist > MAX_QUIET_DISTANCE) - { - angle = rand_norm()*M_PI/3 + M_PI*2/3; - i_to_distance_from_caster = MIN_QUIET_DISTANCE + 2.5f + rand_norm()*(MAX_QUIET_DISTANCE - MIN_QUIET_DISTANCE - 2.5f); - } - else - { - angle = rand_norm()*M_PI; - i_to_distance_from_caster = MIN_QUIET_DISTANCE + 2.5f + rand_norm()*(MAX_QUIET_DISTANCE - MIN_QUIET_DISTANCE - 2.5f); - } - - int8 sign = rand_norm() > 0.5f ? 1 : -1; - i_cur_angle = sign*angle + angle_to_caster; - - // current distance - i_last_distance_from_caster = cur_dist; - - return true; -} - -template -void -FleeingMovementGenerator::Initialize(T &owner) -{ - if(!&owner) - return; - - Unit * fright = ObjectAccessor::GetUnit(owner, i_frightGUID); - if(!fright) - return; - - _Init(owner); - owner.RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - i_caster_x = fright->GetPositionX(); - i_caster_y = fright->GetPositionY(); - i_caster_z = fright->GetPositionZ(); - i_only_forward = true; - i_cur_angle = 0.0f; - i_last_distance_from_caster = 0.0f; - i_to_distance_from_caster = 0.0f; - _setTargetLocation(owner); -} - -template<> -void -FleeingMovementGenerator::_Init(Creature &owner) -{ - if(!&owner) - return; - owner.SetUInt64Value(UNIT_FIELD_TARGET, 0); - is_water_ok = owner.canSwim(); - is_land_ok = owner.canWalk(); -} - -template<> -void -FleeingMovementGenerator::_Init(Player &) -{ - is_water_ok = true; - is_land_ok = true; -} - -template -void -FleeingMovementGenerator::Finalize(T &owner) -{ - owner.clearUnitState(UNIT_STAT_FLEEING); -} - -template -void -FleeingMovementGenerator::Reset(T &owner) -{ - Initialize(owner); -} - -template -bool -FleeingMovementGenerator::Update(T &owner, const uint32 & time_diff) -{ - if( !&owner || !owner.isAlive() ) - return false; - if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED) ) - return true; - - Traveller traveller(owner); - - i_nextCheckTime.Update(time_diff); - - if( (owner.IsStopped() && !i_destinationHolder.HasArrived()) || !i_destinationHolder.HasDestination() ) - { - _setTargetLocation(owner); - return true; - } - - if (i_destinationHolder.UpdateTraveller(traveller, time_diff, false)) - { - i_destinationHolder.ResetUpdate(50); - if(i_nextCheckTime.Passed() && i_destinationHolder.HasArrived()) - { - _setTargetLocation(owner); - return true; - } - } - return true; -} - -template void FleeingMovementGenerator::Initialize(Player &); -template void FleeingMovementGenerator::Initialize(Creature &); -template bool FleeingMovementGenerator::_setMoveData(Player &); -template bool FleeingMovementGenerator::_setMoveData(Creature &); -template bool FleeingMovementGenerator::_getPoint(Player &, float &, float &, float &); -template bool FleeingMovementGenerator::_getPoint(Creature &, float &, float &, float &); -template void FleeingMovementGenerator::_setTargetLocation(Player &); -template void FleeingMovementGenerator::_setTargetLocation(Creature &); -template void FleeingMovementGenerator::Finalize(Player &); -template void FleeingMovementGenerator::Finalize(Creature &); -template void FleeingMovementGenerator::Reset(Player &); -template void FleeingMovementGenerator::Reset(Creature &); -template bool FleeingMovementGenerator::Update(Player &, const uint32 &); -template bool FleeingMovementGenerator::Update(Creature &, const uint32 &); +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Creature.h" +#include "MapManager.h" +#include "FleeingMovementGenerator.h" +#include "DestinationHolderImp.h" +#include "ObjectAccessor.h" + +#define MIN_QUIET_DISTANCE 28.0f +#define MAX_QUIET_DISTANCE 43.0f + +template +void +FleeingMovementGenerator::_setTargetLocation(T &owner) +{ + if( !&owner ) + return; + + if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED) ) + return; + + if(!_setMoveData(owner)) + return; + + float x, y, z; + if(!_getPoint(owner, x, y, z)) + return; + + owner.addUnitState(UNIT_STAT_FLEEING); + Traveller traveller(owner); + i_destinationHolder.SetDestination(traveller, x, y, z); +} + +template +bool +FleeingMovementGenerator::_getPoint(T &owner, float &x, float &y, float &z) +{ + if(!&owner) + return false; + + x = owner.GetPositionX(); + y = owner.GetPositionY(); + z = owner.GetPositionZ(); + + float temp_x, temp_y, angle; + const Map * _map = MapManager::Instance().GetBaseMap(owner.GetMapId()); + //primitive path-finding + for(uint8 i = 0; i < 18; i++) + { + if(i_only_forward && i > 2) + break; + + float distance = 5.0f; + + switch(i) + { + case 0: + angle = i_cur_angle; + break; + case 1: + angle = i_cur_angle; + distance /= 2; + break; + case 2: + angle = i_cur_angle; + distance /= 4; + break; + case 3: + angle = i_cur_angle + M_PI/4.0f; + break; + case 4: + angle = i_cur_angle - M_PI/4.0f; + break; + case 5: + angle = i_cur_angle + M_PI/4.0f; + distance /= 2; + break; + case 6: + angle = i_cur_angle - M_PI/4.0f; + distance /= 2; + break; + case 7: + angle = i_cur_angle + M_PI/2.0f; + break; + case 8: + angle = i_cur_angle - M_PI/2.0f; + break; + case 9: + angle = i_cur_angle + M_PI/2.0f; + distance /= 2; + break; + case 10: + angle = i_cur_angle - M_PI/2.0f; + distance /= 2; + break; + case 11: + angle = i_cur_angle + M_PI/4.0f; + distance /= 4; + break; + case 12: + angle = i_cur_angle - M_PI/4.0f; + distance /= 4; + break; + case 13: + angle = i_cur_angle + M_PI/2.0f; + distance /= 4; + break; + case 14: + angle = i_cur_angle - M_PI/2.0f; + distance /= 4; + break; + case 15: + angle = i_cur_angle + M_PI*3/4.0f; + distance /= 2; + break; + case 16: + angle = i_cur_angle - M_PI*3/4.0f; + distance /= 2; + break; + case 17: + angle = i_cur_angle + M_PI; + distance /= 2; + break; + } + temp_x = x + distance * cos(angle); + temp_y = y + distance * sin(angle); + MaNGOS::NormalizeMapCoord(temp_x); + MaNGOS::NormalizeMapCoord(temp_y); + if( owner.IsWithinLOS(temp_x,temp_y,z)) + { + bool is_water_now = _map->IsInWater(x,y,z); + + if(is_water_now && _map->IsInWater(temp_x,temp_y,z)) + { + x = temp_x; + y = temp_y; + return true; + } + float new_z = _map->GetHeight(temp_x,temp_y,z,true); + + if(new_z <= INVALID_HEIGHT) + continue; + + bool is_water_next = _map->IsInWater(temp_x,temp_y,new_z); + + if((is_water_now && !is_water_next && !is_land_ok) || (!is_water_now && is_water_next && !is_water_ok)) + continue; + + if( !(new_z - z) || distance / fabs(new_z - z) > 1.0f) + { + float new_z_left = _map->GetHeight(temp_x + 1.0f*cos(angle+M_PI/2),temp_y + 1.0f*sin(angle+M_PI/2),z,true); + float new_z_right = _map->GetHeight(temp_x + 1.0f*cos(angle-M_PI/2),temp_y + 1.0f*sin(angle-M_PI/2),z,true); + if(fabs(new_z_left - new_z) < 1.2f && fabs(new_z_right - new_z) < 1.2f) + { + x = temp_x; + y = temp_y; + z = new_z; + return true; + } + } + } + } + i_to_distance_from_caster = 0.0f; + i_nextCheckTime.Reset( urand(500,1000) ); + return false; +} + +template +bool +FleeingMovementGenerator::_setMoveData(T &owner) +{ + float cur_dist_xyz = owner.GetDistance(i_caster_x, i_caster_y, i_caster_z); + + if(i_to_distance_from_caster > 0.0f) + { + if((i_last_distance_from_caster > i_to_distance_from_caster && cur_dist_xyz < i_to_distance_from_caster) || + // if we reach lower distance + (i_last_distance_from_caster > i_to_distance_from_caster && cur_dist_xyz > i_last_distance_from_caster) || + // if we can't be close + (i_last_distance_from_caster < i_to_distance_from_caster && cur_dist_xyz > i_to_distance_from_caster) || + // if we reach bigger distance + (cur_dist_xyz > MAX_QUIET_DISTANCE) || // if we are too far + (i_last_distance_from_caster > MIN_QUIET_DISTANCE && cur_dist_xyz < MIN_QUIET_DISTANCE) ) + // if we leave 'quiet zone' + { + // we are very far or too close, stopping + i_to_distance_from_caster = 0.0f; + i_nextCheckTime.Reset( urand(500,1000) ); + return false; + } + else + { + // now we are running, continue + i_last_distance_from_caster = cur_dist_xyz; + return true; + } + } + + float cur_dist; + float angle_to_caster; + + Unit * fright = ObjectAccessor::GetUnit(owner, i_frightGUID); + + if(fright) + { + cur_dist = fright->GetDistance(&owner); + if(cur_dist < cur_dist_xyz) + { + i_caster_x = fright->GetPositionX(); + i_caster_y = fright->GetPositionY(); + i_caster_z = fright->GetPositionZ(); + angle_to_caster = fright->GetAngle(&owner); + } + else + { + cur_dist = cur_dist_xyz; + angle_to_caster = owner.GetAngle(i_caster_x, i_caster_y) + M_PI; + } + } + else + { + cur_dist = cur_dist_xyz; + angle_to_caster = owner.GetAngle(i_caster_x, i_caster_y) + M_PI; + } + + // if we too close may use 'path-finding' else just stop + i_only_forward = cur_dist >= MIN_QUIET_DISTANCE/3; + + //get angle and 'distance from caster' to run + float angle; + + if(i_cur_angle == 0.0f && i_last_distance_from_caster == 0.0f) //just started, first time + { + angle = rand_norm()*(1.0f - cur_dist/MIN_QUIET_DISTANCE) * M_PI/3 + rand_norm()*M_PI*2/3; + i_to_distance_from_caster = MIN_QUIET_DISTANCE; + i_only_forward = true; + } + else if(cur_dist < MIN_QUIET_DISTANCE) + { + angle = M_PI/6 + rand_norm()*M_PI*2/3; + i_to_distance_from_caster = cur_dist*2/3 + rand_norm()*(MIN_QUIET_DISTANCE - cur_dist*2/3); + } + else if(cur_dist > MAX_QUIET_DISTANCE) + { + angle = rand_norm()*M_PI/3 + M_PI*2/3; + i_to_distance_from_caster = MIN_QUIET_DISTANCE + 2.5f + rand_norm()*(MAX_QUIET_DISTANCE - MIN_QUIET_DISTANCE - 2.5f); + } + else + { + angle = rand_norm()*M_PI; + i_to_distance_from_caster = MIN_QUIET_DISTANCE + 2.5f + rand_norm()*(MAX_QUIET_DISTANCE - MIN_QUIET_DISTANCE - 2.5f); + } + + int8 sign = rand_norm() > 0.5f ? 1 : -1; + i_cur_angle = sign*angle + angle_to_caster; + + // current distance + i_last_distance_from_caster = cur_dist; + + return true; +} + +template +void +FleeingMovementGenerator::Initialize(T &owner) +{ + if(!&owner) + return; + + Unit * fright = ObjectAccessor::GetUnit(owner, i_frightGUID); + if(!fright) + return; + + _Init(owner); + owner.RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + i_caster_x = fright->GetPositionX(); + i_caster_y = fright->GetPositionY(); + i_caster_z = fright->GetPositionZ(); + i_only_forward = true; + i_cur_angle = 0.0f; + i_last_distance_from_caster = 0.0f; + i_to_distance_from_caster = 0.0f; + _setTargetLocation(owner); +} + +template<> +void +FleeingMovementGenerator::_Init(Creature &owner) +{ + if(!&owner) + return; + owner.SetUInt64Value(UNIT_FIELD_TARGET, 0); + is_water_ok = owner.canSwim(); + is_land_ok = owner.canWalk(); +} + +template<> +void +FleeingMovementGenerator::_Init(Player &) +{ + is_water_ok = true; + is_land_ok = true; +} + +template +void +FleeingMovementGenerator::Finalize(T &owner) +{ + owner.clearUnitState(UNIT_STAT_FLEEING); +} + +template +void +FleeingMovementGenerator::Reset(T &owner) +{ + Initialize(owner); +} + +template +bool +FleeingMovementGenerator::Update(T &owner, const uint32 & time_diff) +{ + if( !&owner || !owner.isAlive() ) + return false; + if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED) ) + return true; + + Traveller traveller(owner); + + i_nextCheckTime.Update(time_diff); + + if( (owner.IsStopped() && !i_destinationHolder.HasArrived()) || !i_destinationHolder.HasDestination() ) + { + _setTargetLocation(owner); + return true; + } + + if (i_destinationHolder.UpdateTraveller(traveller, time_diff, false)) + { + i_destinationHolder.ResetUpdate(50); + if(i_nextCheckTime.Passed() && i_destinationHolder.HasArrived()) + { + _setTargetLocation(owner); + return true; + } + } + return true; +} + +template void FleeingMovementGenerator::Initialize(Player &); +template void FleeingMovementGenerator::Initialize(Creature &); +template bool FleeingMovementGenerator::_setMoveData(Player &); +template bool FleeingMovementGenerator::_setMoveData(Creature &); +template bool FleeingMovementGenerator::_getPoint(Player &, float &, float &, float &); +template bool FleeingMovementGenerator::_getPoint(Creature &, float &, float &, float &); +template void FleeingMovementGenerator::_setTargetLocation(Player &); +template void FleeingMovementGenerator::_setTargetLocation(Creature &); +template void FleeingMovementGenerator::Finalize(Player &); +template void FleeingMovementGenerator::Finalize(Creature &); +template void FleeingMovementGenerator::Reset(Player &); +template void FleeingMovementGenerator::Reset(Creature &); +template bool FleeingMovementGenerator::Update(Player &, const uint32 &); +template bool FleeingMovementGenerator::Update(Creature &, const uint32 &); diff --git a/src/game/GridNotifiers.h b/src/game/GridNotifiers.h index 70d5d7c8928..8eb69b4f6b6 100644 --- a/src/game/GridNotifiers.h +++ b/src/game/GridNotifiers.h @@ -852,7 +852,7 @@ namespace MaNGOS bool operator()(Unit* u) { if(u->isAlive() && u->isInCombat() && !i_obj->IsHostileTo(u) && i_obj->IsWithinDistInMap(u, i_range) && - (u->isFeared() || u->isCharmed() || u->isFrozen() || u->hasUnitState(UNIT_STAT_STUNDED) || u->hasUnitState(UNIT_STAT_STUNDED) || u->hasUnitState(UNIT_STAT_CONFUSED))) + (u->isFeared() || u->isCharmed() || u->isFrozen() || u->hasUnitState(UNIT_STAT_STUNNED) || u->hasUnitState(UNIT_STAT_CONFUSED))) { return true; } diff --git a/src/game/Group.cpp b/src/game/Group.cpp index 1d4850ca82d..273edc135b9 100644 --- a/src/game/Group.cpp +++ b/src/game/Group.cpp @@ -1,1454 +1,1454 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "Opcodes.h" -#include "WorldPacket.h" -#include "WorldSession.h" -#include "Player.h" -#include "World.h" -#include "ObjectMgr.h" -#include "Group.h" -#include "ObjectAccessor.h" -#include "BattleGround.h" -#include "MapManager.h" -#include "InstanceSaveMgr.h" -#include "MapInstanced.h" -#include "Util.h" - -Group::Group() -{ - m_leaderGuid = 0; - m_mainTank = 0; - m_mainAssistant = 0; - m_groupType = (GroupType)0; - m_bgGroup = NULL; - m_lootMethod = (LootMethod)0; - m_looterGuid = 0; - m_lootThreshold = ITEM_QUALITY_UNCOMMON; - - for(int i=0; iGetBgRaid(ALLIANCE) == this) m_bgGroup->SetBgRaid(ALLIANCE, NULL); - else if(m_bgGroup->GetBgRaid(HORDE) == this) m_bgGroup->SetBgRaid(HORDE, NULL); - else sLog.outError("Group::~Group: battleground group is not linked to the correct battleground."); - } - Rolls::iterator itr; - while(!RollId.empty()) - { - itr = RollId.begin(); - Roll *r = *itr; - RollId.erase(itr); - delete(r); - } - - // it is undefined whether objectmgr (which stores the groups) or instancesavemgr - // will be unloaded first so we must be prepared for both cases - // this may unload some instance saves - for(uint8 i = 0; i < TOTAL_DIFFICULTIES; i++) - for(BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) - itr->second.save->RemoveGroup(this); -} - -bool Group::Create(const uint64 &guid, const char * name) -{ - m_leaderGuid = guid; - m_leaderName = name; - - m_groupType = isBGGroup() ? GROUPTYPE_RAID : GROUPTYPE_NORMAL; - m_lootMethod = GROUP_LOOT; - m_lootThreshold = ITEM_QUALITY_UNCOMMON; - m_looterGuid = guid; - - m_difficulty = DIFFICULTY_NORMAL; - if(!isBGGroup()) - { - Player *leader = objmgr.GetPlayer(guid); - if(leader) m_difficulty = leader->GetDifficulty(); - - Player::ConvertInstancesToGroup(leader, this, guid); - - // store group in database - CharacterDatabase.BeginTransaction(); - CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid ='%u'", GUID_LOPART(m_leaderGuid)); - CharacterDatabase.PExecute("DELETE FROM group_member WHERE leaderGuid ='%u'", GUID_LOPART(m_leaderGuid)); - CharacterDatabase.PExecute("INSERT INTO groups(leaderGuid,mainTank,mainAssistant,lootMethod,looterGuid,lootThreshold,icon1,icon2,icon3,icon4,icon5,icon6,icon7,icon8,isRaid,difficulty) " - "VALUES('%u','%u','%u','%u','%u','%u','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','%u','%u')", - GUID_LOPART(m_leaderGuid), GUID_LOPART(m_mainTank), GUID_LOPART(m_mainAssistant), uint32(m_lootMethod), - GUID_LOPART(m_looterGuid), uint32(m_lootThreshold), m_targetIcons[0], m_targetIcons[1], m_targetIcons[2], m_targetIcons[3], m_targetIcons[4], m_targetIcons[5], m_targetIcons[6], m_targetIcons[7], isRaidGroup(), m_difficulty); - } - - if(!AddMember(guid, name)) - return false; - - if(!isBGGroup()) CharacterDatabase.CommitTransaction(); - - return true; -} - -bool Group::LoadGroupFromDB(const uint64 &leaderGuid, QueryResult *result, bool loadMembers) -{ - if(isBGGroup()) - return false; - - bool external = true; - if(!result) - { - external = false; - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 - result = CharacterDatabase.PQuery("SELECT mainTank, mainAssistant, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, isRaid, difficulty FROM groups WHERE leaderGuid ='%u'", GUID_LOPART(leaderGuid)); - if(!result) - return false; - } - - m_leaderGuid = leaderGuid; - - // group leader not exist - if(!objmgr.GetPlayerNameByGUID(m_leaderGuid, m_leaderName)) - { - if(!external) delete result; - return false; - } - - m_groupType = (*result)[13].GetBool() ? GROUPTYPE_RAID : GROUPTYPE_NORMAL; - m_difficulty = (*result)[14].GetUInt8(); - m_mainTank = (*result)[0].GetUInt64(); - m_mainAssistant = (*result)[1].GetUInt64(); - m_lootMethod = (LootMethod)(*result)[2].GetUInt8(); - m_looterGuid = MAKE_NEW_GUID((*result)[3].GetUInt32(), 0, HIGHGUID_PLAYER); - m_lootThreshold = (ItemQualities)(*result)[4].GetUInt16(); - - for(int i=0; iNextRow() ); - delete result; - // group too small - if(GetMembersCount() < 2) - return false; - } - - return true; -} - -bool Group::LoadMemberFromDB(uint32 guidLow, uint8 subgroup, bool assistant) -{ - MemberSlot member; - member.guid = MAKE_NEW_GUID(guidLow, 0, HIGHGUID_PLAYER); - - // skip non-existed member - if(!objmgr.GetPlayerNameByGUID(member.guid, member.name)) - return false; - - member.group = subgroup; - member.assistant = assistant; - m_memberSlots.push_back(member); - return true; -} - -bool Group::AddInvite(Player *player) -{ - if(!player || player->GetGroupInvite() || player->GetGroup()) - return false; - - RemoveInvite(player); - - m_invitees.insert(player->GetGUID()); - - player->SetGroupInvite(this); - - return true; -} - -bool Group::AddLeaderInvite(Player *player) -{ - if(!AddInvite(player)) - return false; - - m_leaderGuid = player->GetGUID(); - m_leaderName = player->GetName(); - return true; -} - -uint32 Group::RemoveInvite(Player *player) -{ - for(InvitesList::iterator itr=m_invitees.begin(); itr!=m_invitees.end(); ++itr) - { - if((*itr) == player->GetGUID()) - { - m_invitees.erase(itr); - break; - } - } - - player->SetGroupInvite(NULL); - return GetMembersCount(); -} - -void Group::RemoveAllInvites() -{ - for(InvitesList::iterator itr=m_invitees.begin(); itr!=m_invitees.end(); ++itr) - { - Player *invitee = objmgr.GetPlayer(*itr); - if(invitee) - invitee->SetGroupInvite(NULL); - } - m_invitees.clear(); -} - -bool Group::AddMember(const uint64 &guid, const char* name) -{ - if(!_addMember(guid, name)) - return false; - SendUpdate(); - - Player *player = objmgr.GetPlayer(guid); - if(player) - { - if(!IsLeader(player->GetGUID()) && !isBGGroup()) - { - // reset the new member's instances, unless he is currently in one of them - // including raid/heroic instances that they are not permanently bound to! - player->ResetInstances(INSTANCE_RESET_GROUP_JOIN); - - if(player->getLevel() >= LEVELREQUIREMENT_HEROIC && player->GetDifficulty() != GetDifficulty() ) - { - player->SetDifficulty(m_difficulty); - player->SendDungeonDifficulty(true); - } - } - player->SetGroupUpdateFlag(GROUP_UPDATE_FULL); - UpdatePlayerOutOfRange(player); - } - - return true; -} - -uint32 Group::RemoveMember(const uint64 &guid, const uint8 &method) -{ - // remove member and change leader (if need) only if strong more 2 members _before_ member remove - if(GetMembersCount() > (isBGGroup() ? 1 : 2)) // in BG group case allow 1 members group - { - bool leaderChanged = _removeMember(guid); - - Player *player = objmgr.GetPlayer( guid ); - if (player) - { - WorldPacket data; - - if(method == 1) - { - data.Initialize( SMSG_GROUP_UNINVITE, 0 ); - player->GetSession()->SendPacket( &data ); - } - - data.Initialize(SMSG_GROUP_LIST, 24); - data << uint64(0) << uint64(0) << uint64(0); - player->GetSession()->SendPacket(&data); - - _homebindIfInstance(player); - } - - if(leaderChanged) - { - WorldPacket data(SMSG_GROUP_SET_LEADER, (m_memberSlots.front().name.size()+1)); - data << m_memberSlots.front().name; - BroadcastPacket(&data); - } - - SendUpdate(); - } - // if group before remove <= 2 disband it - else - Disband(true); - - return m_memberSlots.size(); -} - -void Group::ChangeLeader(const uint64 &guid) -{ - member_citerator slot = _getMemberCSlot(guid); - - if(slot==m_memberSlots.end()) - return; - - _setLeader(guid); - - WorldPacket data(SMSG_GROUP_SET_LEADER, slot->name.size()+1); - data << slot->name; - BroadcastPacket(&data); - SendUpdate(); -} - -void Group::Disband(bool hideDestroy) -{ - Player *player; - - for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) - { - player = objmgr.GetPlayer(citr->guid); - if(!player) - continue; - - player->SetGroup(NULL); - - if(!player->GetSession()) - continue; - - WorldPacket data; - if(!hideDestroy) - { - data.Initialize(SMSG_GROUP_DESTROYED, 0); - player->GetSession()->SendPacket(&data); - } - - data.Initialize(SMSG_GROUP_LIST, 24); - data << uint64(0) << uint64(0) << uint64(0); - player->GetSession()->SendPacket(&data); - - _homebindIfInstance(player); - } - RollId.clear(); - m_memberSlots.clear(); - - RemoveAllInvites(); - - if(!isBGGroup()) - { - CharacterDatabase.BeginTransaction(); - CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid='%u'", GUID_LOPART(m_leaderGuid)); - CharacterDatabase.PExecute("DELETE FROM group_member WHERE leaderGuid='%u'", GUID_LOPART(m_leaderGuid)); - CharacterDatabase.CommitTransaction(); - ResetInstances(INSTANCE_RESET_GROUP_DISBAND, NULL); - } - - m_leaderGuid = 0; - m_leaderName = ""; -} - -/*********************************************************/ -/*** LOOT SYSTEM ***/ -/*********************************************************/ - -void Group::SendLootStartRoll(uint32 CountDown, const Roll &r) -{ - WorldPacket data(SMSG_LOOT_START_ROLL, (8+4+4+4+4+4)); - data << uint64(r.itemGUID); // guid of rolled item - data << uint32(r.totalPlayersRolling); // maybe the number of players rolling for it??? - data << uint32(r.itemid); // the itemEntryId for the item that shall be rolled for - data << uint32(r.itemRandomSuffix); // randomSuffix - data << uint32(r.itemRandomPropId); // item random property ID - data << uint32(CountDown); // the countdown time to choose "need" or "greed" - - for (Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr!=r.playerVote.end(); ++itr) - { - Player *p = objmgr.GetPlayer(itr->first); - if(!p || !p->GetSession()) - continue; - - if(itr->second != NOT_VALID) - p->GetSession()->SendPacket( &data ); - } -} - -void Group::SendLootRoll(uint64 SourceGuid, uint64 TargetGuid, uint8 RollNumber, uint8 RollType, const Roll &r) -{ - WorldPacket data(SMSG_LOOT_ROLL, (8+4+8+4+4+4+1+1)); - data << uint64(SourceGuid); // guid of the item rolled - data << uint32(0); // unknown, maybe amount of players - data << uint64(TargetGuid); - data << uint32(r.itemid); // the itemEntryId for the item that shall be rolled for - data << uint32(r.itemRandomSuffix); // randomSuffix - data << uint32(r.itemRandomPropId); // Item random property ID - data << uint8(RollNumber); // 0: "Need for: [item name]" > 127: "you passed on: [item name]" Roll number - data << uint8(RollType); // 0: "Need for: [item name]" 0: "You have selected need for [item name] 1: need roll 2: greed roll - data << uint8(0); // 2.4.0 - - for( Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr!=r.playerVote.end(); ++itr) - { - Player *p = objmgr.GetPlayer(itr->first); - if(!p || !p->GetSession()) - continue; - - if(itr->second != NOT_VALID) - p->GetSession()->SendPacket( &data ); - } -} - -void Group::SendLootRollWon(uint64 SourceGuid, uint64 TargetGuid, uint8 RollNumber, uint8 RollType, const Roll &r) -{ - WorldPacket data(SMSG_LOOT_ROLL_WON, (8+4+4+4+4+8+1+1)); - data << uint64(SourceGuid); // guid of the item rolled - data << uint32(0); // unknown, maybe amount of players - data << uint32(r.itemid); // the itemEntryId for the item that shall be rolled for - data << uint32(r.itemRandomSuffix); // randomSuffix - data << uint32(r.itemRandomPropId); // Item random property - data << uint64(TargetGuid); // guid of the player who won. - data << uint8(RollNumber); // rollnumber realted to SMSG_LOOT_ROLL - data << uint8(RollType); // Rolltype related to SMSG_LOOT_ROLL - - for( Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr!=r.playerVote.end(); ++itr) - { - Player *p = objmgr.GetPlayer(itr->first); - if(!p || !p->GetSession()) - continue; - - if(itr->second != NOT_VALID) - p->GetSession()->SendPacket( &data ); - } -} - -void Group::SendLootAllPassed(uint32 NumberOfPlayers, const Roll &r) -{ - WorldPacket data(SMSG_LOOT_ALL_PASSED, (8+4+4+4+4)); - data << uint64(r.itemGUID); // Guid of the item rolled - data << uint32(NumberOfPlayers); // The number of players rolling for it??? - data << uint32(r.itemid); // The itemEntryId for the item that shall be rolled for - data << uint32(r.itemRandomPropId); // Item random property ID - data << uint32(r.itemRandomSuffix); // Item random suffix ID - - for( Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr!=r.playerVote.end(); ++itr) - { - Player *p = objmgr.GetPlayer(itr->first); - if(!p || !p->GetSession()) - continue; - - if(itr->second != NOT_VALID) - p->GetSession()->SendPacket( &data ); - } -} - -void Group::GroupLoot(uint64 playerGUID, Loot *loot, Creature *creature) -{ - std::vector::iterator i; - ItemPrototype const *item; - uint8 itemSlot = 0; - Player *player = objmgr.GetPlayer(playerGUID); - Group *group = player->GetGroup(); - - for (i=loot->items.begin(); i != loot->items.end(); ++i, ++itemSlot) - { - item = objmgr.GetItemPrototype(i->itemid); - if (!item) - { - //sLog.outDebug("Group::GroupLoot: missing item prototype for item with id: %d", i->itemid); - continue; - } - - //roll for over-threshold item if it's one-player loot - if (item->Quality >= uint32(m_lootThreshold) && !i->freeforall) - { - uint64 newitemGUID = MAKE_NEW_GUID(objmgr.GenerateLowGuid(HIGHGUID_ITEM),0,HIGHGUID_ITEM); - Roll* r=new Roll(newitemGUID,*i); - - //a vector is filled with only near party members - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *member = itr->getSource(); - if(!member || !member->GetSession()) - continue; - if ( i->AllowedForPlayer(member) ) - { - if (member->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) - { - r->playerVote[member->GetGUID()] = NOT_EMITED_YET; - ++r->totalPlayersRolling; - } - } - } - - r->setLoot(loot); - r->itemSlot = itemSlot; - - group->SendLootStartRoll(60000, *r); - - loot->items[itemSlot].is_blocked = true; - creature->m_groupLootTimer = 60000; - creature->lootingGroupLeaderGUID = GetLeaderGUID(); - - RollId.push_back(r); - } - else - i->is_underthreshold=1; - - } -} - -void Group::NeedBeforeGreed(uint64 playerGUID, Loot *loot, Creature *creature) -{ - ItemPrototype const *item; - Player *player = objmgr.GetPlayer(playerGUID); - Group *group = player->GetGroup(); - - uint8 itemSlot = 0; - for(std::vector::iterator i=loot->items.begin(); i != loot->items.end(); ++i, ++itemSlot) - { - item = objmgr.GetItemPrototype(i->itemid); - - //only roll for one-player items, not for ones everyone can get - if (item->Quality >= uint32(m_lootThreshold) && !i->freeforall) - { - uint64 newitemGUID = MAKE_NEW_GUID(objmgr.GenerateLowGuid(HIGHGUID_ITEM),0,HIGHGUID_ITEM); - Roll* r=new Roll(newitemGUID,*i); - - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *playerToRoll = itr->getSource(); - if(!playerToRoll || !playerToRoll->GetSession()) - continue; - - if (playerToRoll->CanUseItem(item) && i->AllowedForPlayer(playerToRoll) ) - { - if (playerToRoll->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) - { - r->playerVote[playerToRoll->GetGUID()] = NOT_EMITED_YET; - ++r->totalPlayersRolling; - } - } - } - - if (r->totalPlayersRolling > 0) - { - r->setLoot(loot); - r->itemSlot = itemSlot; - - group->SendLootStartRoll(60000, *r); - - loot->items[itemSlot].is_blocked = true; - - RollId.push_back(r); - } - else - { - delete r; - } - } - else - i->is_underthreshold=1; - } -} - -void Group::MasterLoot(uint64 playerGUID, Loot* /*loot*/, Creature *creature) -{ - Player *player = objmgr.GetPlayer(playerGUID); - if(!player) - return; - - sLog.outDebug("Group::MasterLoot (SMSG_LOOT_MASTER_LIST, 330) player = [%s].", player->GetName()); - - uint32 real_count = 0; - - WorldPacket data(SMSG_LOOT_MASTER_LIST, 330); - data << (uint8)GetMembersCount(); - - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *looter = itr->getSource(); - if (!looter->IsInWorld()) - continue; - - if (looter->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) - { - data << looter->GetGUID(); - ++real_count; - } - } - - data.put(0,real_count); - - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *looter = itr->getSource(); - if (looter->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) - looter->GetSession()->SendPacket(&data); - } -} - -void Group::CountRollVote(uint64 playerGUID, uint64 Guid, uint32 NumberOfPlayers, uint8 Choise) -{ - Rolls::iterator rollI = GetRoll(Guid); - if (rollI == RollId.end()) - return; - Roll* roll = *rollI; - - Roll::PlayerVote::iterator itr = roll->playerVote.find(playerGUID); - // this condition means that player joins to the party after roll begins - if (itr == roll->playerVote.end()) - return; - - if (roll->getLoot()) - if (roll->getLoot()->items.empty()) - return; - - switch (Choise) - { - case 0: //Player choose pass - { - SendLootRoll(0, playerGUID, 128, 128, *roll); - ++roll->totalPass; - itr->second = PASS; - } - break; - case 1: //player choose Need - { - SendLootRoll(0, playerGUID, 1, 1, *roll); - ++roll->totalNeed; - itr->second = NEED; - } - break; - case 2: //player choose Greed - { - SendLootRoll(0, playerGUID, 2, 2, *roll); - ++roll->totalGreed; - itr->second = GREED; - } - break; - } - if (roll->totalPass + roll->totalGreed + roll->totalNeed >= roll->totalPlayersRolling) - { - CountTheRoll(rollI, NumberOfPlayers); - } -} - -//called when roll timer expires -void Group::EndRoll() -{ - Rolls::iterator itr; - while(!RollId.empty()) - { - //need more testing here, if rolls disappear - itr = RollId.begin(); - CountTheRoll(itr, GetMembersCount()); //i don't have to edit player votes, who didn't vote ... he will pass - } -} - -void Group::CountTheRoll(Rolls::iterator rollI, uint32 NumberOfPlayers) -{ - Roll* roll = *rollI; - if(!roll->isValid()) // is loot already deleted ? - { - RollId.erase(rollI); - delete roll; - return; - } - //end of the roll - if (roll->totalNeed > 0) - { - if(!roll->playerVote.empty()) - { - uint8 maxresul = 0; - uint64 maxguid = (*roll->playerVote.begin()).first; - Player *player; - - for( Roll::PlayerVote::const_iterator itr=roll->playerVote.begin(); itr!=roll->playerVote.end(); ++itr) - { - if (itr->second != NEED) - continue; - - uint8 randomN = urand(1, 99); - SendLootRoll(0, itr->first, randomN, 1, *roll); - if (maxresul < randomN) - { - maxguid = itr->first; - maxresul = randomN; - } - } - SendLootRollWon(0, maxguid, maxresul, 1, *roll); - player = objmgr.GetPlayer(maxguid); - - if(player && player->GetSession()) - { - ItemPosCountVec dest; - LootItem *item = &(roll->getLoot()->items[roll->itemSlot]); - uint8 msg = player->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, roll->itemid, item->count ); - if ( msg == EQUIP_ERR_OK ) - { - item->is_looted = true; - roll->getLoot()->NotifyItemRemoved(roll->itemSlot); - --roll->getLoot()->unlootedCount; - player->StoreNewItem( dest, roll->itemid, true, item->randomPropertyId); - } - else - { - item->is_blocked = false; - player->SendEquipError( msg, NULL, NULL ); - } - } - } - } - else if (roll->totalGreed > 0) - { - if(!roll->playerVote.empty()) - { - uint8 maxresul = 0; - uint64 maxguid = (*roll->playerVote.begin()).first; - Player *player; - - Roll::PlayerVote::iterator itr; - for (itr=roll->playerVote.begin(); itr!=roll->playerVote.end(); ++itr) - { - if (itr->second != GREED) - continue; - - uint8 randomN = urand(1, 99); - SendLootRoll(0, itr->first, randomN, 2, *roll); - if (maxresul < randomN) - { - maxguid = itr->first; - maxresul = randomN; - } - } - SendLootRollWon(0, maxguid, maxresul, 2, *roll); - player = objmgr.GetPlayer(maxguid); - - if(player && player->GetSession()) - { - ItemPosCountVec dest; - LootItem *item = &(roll->getLoot()->items[roll->itemSlot]); - uint8 msg = player->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, roll->itemid, item->count ); - if ( msg == EQUIP_ERR_OK ) - { - item->is_looted = true; - roll->getLoot()->NotifyItemRemoved(roll->itemSlot); - --roll->getLoot()->unlootedCount; - player->StoreNewItem( dest, roll->itemid, true, item->randomPropertyId); - } - else - { - item->is_blocked = false; - player->SendEquipError( msg, NULL, NULL ); - } - } - } - } - else - { - SendLootAllPassed(NumberOfPlayers, *roll); - LootItem *item = &(roll->getLoot()->items[roll->itemSlot]); - if(item) item->is_blocked = false; - } - RollId.erase(rollI); - delete roll; -} - -void Group::SetTargetIcon(uint8 id, uint64 guid) -{ - if(id >= TARGETICONCOUNT) - return; - - // clean other icons - if( guid != 0 ) - for(int i=0; inext()) - { - Player* member = itr->getSource(); - if(!member || !member->isAlive()) // only for alive - continue; - - if(!member->IsAtGroupRewardDistance(victim)) // at req. distance - continue; - - ++count; - sum_level += member->getLevel(); - if(!member_with_max_level || member_with_max_level->getLevel() < member->getLevel()) - member_with_max_level = member; - } -} - -void Group::SendTargetIconList(WorldSession *session) -{ - if(!session) - return; - - WorldPacket data(MSG_RAID_TARGET_UPDATE, (1+TARGETICONCOUNT*9)); - data << (uint8)1; - - for(int i=0; iSendPacket(&data); -} - -void Group::SendUpdate() -{ - Player *player; - - for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) - { - player = objmgr.GetPlayer(citr->guid); - if(!player || !player->GetSession()) - continue; - // guess size - WorldPacket data(SMSG_GROUP_LIST, (1+1+1+1+8+4+GetMembersCount()*20)); - data << (uint8)m_groupType; // group type - data << (uint8)(isBGGroup() ? 1 : 0); // 2.0.x, isBattleGroundGroup? - data << (uint8)(citr->group); // groupid - data << (uint8)(citr->assistant?0x01:0); // 0x2 main assist, 0x4 main tank - data << uint64(0x50000000FFFFFFFELL); // related to voice chat? - data << uint32(GetMembersCount()-1); - for(member_citerator citr2 = m_memberSlots.begin(); citr2 != m_memberSlots.end(); ++citr2) - { - if(citr->guid == citr2->guid) - continue; - - data << citr2->name; - data << (uint64)citr2->guid; - // online-state - data << (uint8)(objmgr.GetPlayer(citr2->guid) ? 1 : 0); - data << (uint8)(citr2->group); // groupid - data << (uint8)(citr2->assistant?0x01:0); // 0x2 main assist, 0x4 main tank - } - - data << uint64(m_leaderGuid); // leader guid - if(GetMembersCount()-1) - { - data << (uint8)m_lootMethod; // loot method - data << (uint64)m_looterGuid; // looter guid - data << (uint8)m_lootThreshold; // loot threshold - data << (uint8)m_difficulty; // Heroic Mod Group - - } - player->GetSession()->SendPacket( &data ); - } -} - -void Group::UpdatePlayerOutOfRange(Player* pPlayer) -{ - if(!pPlayer) - return; - - Player *player; - WorldPacket data; - pPlayer->GetSession()->BuildPartyMemberStatsChangedPacket(pPlayer, &data); - - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - player = itr->getSource(); - if (player && player != pPlayer && !pPlayer->isVisibleFor(player)) - player->GetSession()->SendPacket(&data); - } -} - -void Group::BroadcastPacket(WorldPacket *packet, int group, uint64 ignore) -{ - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *pl = itr->getSource(); - if(!pl || (ignore != 0 && pl->GetGUID() == ignore)) - continue; - - if (pl->GetSession() && (group==-1 || itr->getSubGroup()==group)) - pl->GetSession()->SendPacket(packet); - } -} - -void Group::BroadcastReadyCheck(WorldPacket *packet) -{ - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *pl = itr->getSource(); - if(pl && pl->GetSession()) - if(IsLeader(pl->GetGUID()) || IsAssistant(pl->GetGUID())) - pl->GetSession()->SendPacket(packet); - } -} - -void Group::OfflineReadyCheck() -{ - for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) - { - Player *pl = objmgr.GetPlayer(citr->guid); - if (!pl || !pl->GetSession()) - { - WorldPacket data(MSG_RAID_READY_CHECK_CONFIRM, 9); - data << citr->guid; - data << (uint8)0; - BroadcastReadyCheck(&data); - } - } -} - -bool Group::_addMember(const uint64 &guid, const char* name, bool isAssistant) -{ - // get first not-full group - uint8 groupid = 0; - std::vector temp(MAXRAIDSIZE/MAXGROUPSIZE); - for(member_citerator itr = m_memberSlots.begin(); itr != m_memberSlots.end(); ++itr) - { - if (itr->group >= temp.size()) continue; - ++temp[itr->group]; - if(temp[groupid] >= MAXGROUPSIZE) - ++groupid; - } - - return _addMember(guid, name, isAssistant, groupid); -} - -bool Group::_addMember(const uint64 &guid, const char* name, bool isAssistant, uint8 group) -{ - if(IsFull()) - return false; - - if(!guid) - return false; - - Player *player = objmgr.GetPlayer(guid); - - MemberSlot member; - member.guid = guid; - member.name = name; - member.group = group; - member.assistant = isAssistant; - m_memberSlots.push_back(member); - - if(player) - { - player->SetGroupInvite(NULL); - player->SetGroup(this, group); - // if the same group invites the player back, cancel the homebind timer - InstanceGroupBind *bind = GetBoundInstance(player->GetMapId(), player->GetDifficulty()); - if(bind && bind->save->GetInstanceId() == player->GetInstanceId()) - player->m_InstanceValid = true; - } - - if(!isRaidGroup()) // reset targetIcons for non-raid-groups - { - for(int i=0; iSetGroup(NULL); - } - - _removeRolls(guid); - - member_witerator slot = _getMemberWSlot(guid); - if (slot != m_memberSlots.end()) - m_memberSlots.erase(slot); - - if(!isBGGroup()) - CharacterDatabase.PExecute("DELETE FROM group_member WHERE memberGuid='%u'", GUID_LOPART(guid)); - - if(m_leaderGuid == guid) // leader was removed - { - if(GetMembersCount() > 0) - _setLeader(m_memberSlots.front().guid); - return true; - } - - return false; -} - -void Group::_setLeader(const uint64 &guid) -{ - member_citerator slot = _getMemberCSlot(guid); - if(slot==m_memberSlots.end()) - return; - - if(!isBGGroup()) - { - // TODO: set a time limit to have this function run rarely cause it can be slow - CharacterDatabase.BeginTransaction(); - - // update the group's bound instances when changing leaders - - // remove all permanent binds from the group - // in the DB also remove solo binds that will be replaced with permbinds - // from the new leader - CharacterDatabase.PExecute( - "DELETE FROM group_instance WHERE leaderguid='%u' AND (permanent = 1 OR " - "instance IN (SELECT instance FROM character_instance WHERE guid = '%u')" - ")", GUID_LOPART(m_leaderGuid), GUID_LOPART(slot->guid) - ); - - Player *player = objmgr.GetPlayer(slot->guid); - if(player) - { - for(uint8 i = 0; i < TOTAL_DIFFICULTIES; i++) - { - for(BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end();) - { - if(itr->second.perm) - { - itr->second.save->RemoveGroup(this); - m_boundInstances[i].erase(itr++); - } - else - ++itr; - } - } - } - - // update the group's solo binds to the new leader - CharacterDatabase.PExecute("UPDATE group_instance SET leaderGuid='%u' WHERE leaderGuid = '%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_leaderGuid)); - - // copy the permanent binds from the new leader to the group - // overwriting the solo binds with permanent ones if necessary - // in the DB those have been deleted already - Player::ConvertInstancesToGroup(player, this, slot->guid); - - // update the group leader - CharacterDatabase.PExecute("UPDATE groups SET leaderGuid='%u' WHERE leaderGuid='%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_leaderGuid)); - CharacterDatabase.PExecute("UPDATE group_member SET leaderGuid='%u' WHERE leaderGuid='%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_leaderGuid)); - CharacterDatabase.CommitTransaction(); - } - - m_leaderGuid = slot->guid; - m_leaderName = slot->name; -} - -void Group::_removeRolls(const uint64 &guid) -{ - for (Rolls::iterator it = RollId.begin(); it < RollId.end(); it++) - { - Roll* roll = *it; - Roll::PlayerVote::iterator itr2 = roll->playerVote.find(guid); - if(itr2 == roll->playerVote.end()) - continue; - - if (itr2->second == GREED) --roll->totalGreed; - if (itr2->second == NEED) --roll->totalNeed; - if (itr2->second == PASS) --roll->totalPass; - if (itr2->second != NOT_VALID) --roll->totalPlayersRolling; - - roll->playerVote.erase(itr2); - - CountRollVote(guid, roll->itemGUID, GetMembersCount()-1, 3); - } -} - -void Group::_convertToRaid() -{ - m_groupType = GROUPTYPE_RAID; - - if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE groups SET isRaid = 1 WHERE leaderGuid='%u'", GUID_LOPART(m_leaderGuid)); -} - -bool Group::_setMembersGroup(const uint64 &guid, const uint8 &group) -{ - member_witerator slot = _getMemberWSlot(guid); - if(slot==m_memberSlots.end()) - return false; - - slot->group = group; - if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE group_member SET subgroup='%u' WHERE memberGuid='%u'", group, GUID_LOPART(guid)); - return true; -} - -bool Group::_setAssistantFlag(const uint64 &guid, const bool &state) -{ - member_witerator slot = _getMemberWSlot(guid); - if(slot==m_memberSlots.end()) - return false; - - slot->assistant = state; - if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE group_member SET assistant='%u' WHERE memberGuid='%u'", (state==true)?1:0, GUID_LOPART(guid)); - return true; -} - -bool Group::_setMainTank(const uint64 &guid) -{ - member_citerator slot = _getMemberCSlot(guid); - if(slot==m_memberSlots.end()) - return false; - - if(m_mainAssistant == guid) - _setMainAssistant(0); - m_mainTank = guid; - if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE groups SET mainTank='%u' WHERE leaderGuid='%u'", GUID_LOPART(m_mainTank), GUID_LOPART(m_leaderGuid)); - return true; -} - -bool Group::_setMainAssistant(const uint64 &guid) -{ - member_witerator slot = _getMemberWSlot(guid); - if(slot==m_memberSlots.end()) - return false; - - if(m_mainTank == guid) - _setMainTank(0); - m_mainAssistant = guid; - if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE groups SET mainAssistant='%u' WHERE leaderGuid='%u'", GUID_LOPART(m_mainAssistant), GUID_LOPART(m_leaderGuid)); - return true; -} - -bool Group::SameSubGroup(Player const* member1, Player const* member2) const -{ - if(!member1 || !member2) return false; - if (member1->GetGroup() != this || member2->GetGroup() != this) return false; - else return member1->GetSubGroup() == member2->GetSubGroup(); -} - -// allows setting subgroup for offline members -void Group::ChangeMembersGroup(const uint64 &guid, const uint8 &group) -{ - if(!isRaidGroup()) - return; - Player *player = objmgr.GetPlayer(guid); - if (!player) - { - if(_setMembersGroup(guid, group)) - SendUpdate(); - } - else ChangeMembersGroup(player, group); -} - -// only for online members -void Group::ChangeMembersGroup(Player *player, const uint8 &group) -{ - if(!player || !isRaidGroup()) - return; - if(_setMembersGroup(player->GetGUID(), group)) - { - player->GetGroupRef().setSubGroup(group); - SendUpdate(); - } -} - -void Group::UpdateLooterGuid( Creature* creature, bool ifneed ) -{ - switch (GetLootMethod()) - { - case MASTER_LOOT: - case FREE_FOR_ALL: - return; - default: - // round robin style looting applies for all low - // quality items in each loot method except free for all and master loot - break; - } - - member_citerator guid_itr = _getMemberCSlot(GetLooterGuid()); - if(guid_itr != m_memberSlots.end()) - { - if(ifneed) - { - // not update if only update if need and ok - Player* looter = ObjectAccessor::FindPlayer(guid_itr->guid); - if(looter && looter->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) - return; - } - ++guid_itr; - } - - // search next after current - if(guid_itr != m_memberSlots.end()) - { - for(member_citerator itr = guid_itr; itr != m_memberSlots.end(); ++itr) - { - if(Player* pl = ObjectAccessor::FindPlayer(itr->guid)) - { - if (pl->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) - { - bool refresh = pl->GetLootGUID()==creature->GetGUID(); - - //if(refresh) // update loot for new looter - // pl->GetSession()->DoLootRelease(pl->GetLootGUID()); - SetLooterGuid(pl->GetGUID()); - SendUpdate(); - if(refresh) // update loot for new looter - pl->SendLoot(creature->GetGUID(),LOOT_CORPSE); - return; - } - } - } - } - - // search from start - for(member_citerator itr = m_memberSlots.begin(); itr != guid_itr; ++itr) - { - if(Player* pl = ObjectAccessor::FindPlayer(itr->guid)) - { - if (pl->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) - { - bool refresh = pl->GetLootGUID()==creature->GetGUID(); - - //if(refresh) // update loot for new looter - // pl->GetSession()->DoLootRelease(pl->GetLootGUID()); - SetLooterGuid(pl->GetGUID()); - SendUpdate(); - if(refresh) // update loot for new looter - pl->SendLoot(creature->GetGUID(),LOOT_CORPSE); - return; - } - } - } - - SetLooterGuid(0); - SendUpdate(); -} - -uint32 Group::CanJoinBattleGroundQueue(uint32 bgTypeId, uint32 bgQueueType, uint32 MinPlayerCount, uint32 MaxPlayerCount, bool isRated, uint32 arenaSlot) -{ - // check for min / max count - uint32 memberscount = GetMembersCount(); - if(memberscount < MinPlayerCount) - return BG_JOIN_ERR_GROUP_NOT_ENOUGH; - if(memberscount > MaxPlayerCount) - return BG_JOIN_ERR_GROUP_TOO_MANY; - - // get a player as reference, to compare other players' stats to (arena team id, queue id based on level, etc.) - Player * reference = GetFirstMember()->getSource(); - // no reference found, can't join this way - if(!reference) - return BG_JOIN_ERR_OFFLINE_MEMBER; - - uint32 bgQueueId = reference->GetBattleGroundQueueIdFromLevel(); - uint32 arenaTeamId = reference->GetArenaTeamId(arenaSlot); - uint32 team = reference->GetTeam(); - - // check every member of the group to be able to join - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *member = itr->getSource(); - // offline member? don't let join - if(!member) - return BG_JOIN_ERR_OFFLINE_MEMBER; - // don't allow cross-faction join as group - if(member->GetTeam() != team) - return BG_JOIN_ERR_MIXED_FACTION; - // not in the same battleground level braket, don't let join - if(member->GetBattleGroundQueueIdFromLevel() != bgQueueId) - return BG_JOIN_ERR_MIXED_LEVELS; - // don't let join rated matches if the arena team id doesn't match - if(isRated && member->GetArenaTeamId(arenaSlot) != arenaTeamId) - return BG_JOIN_ERR_MIXED_ARENATEAM; - // don't let join if someone from the group is already in that bg queue - if(member->InBattleGroundQueueForBattleGroundQueueType(bgQueueType)) - return BG_JOIN_ERR_GROUP_MEMBER_ALREADY_IN_QUEUE; - // check for deserter debuff in case not arena queue - if(bgTypeId != BATTLEGROUND_AA && !member->CanJoinToBattleground()) - return BG_JOIN_ERR_GROUP_DESERTER; - // check if member can join any more battleground queues - if(!member->HasFreeBattleGroundQueueId()) - return BG_JOIN_ERR_ALL_QUEUES_USED; - } - return BG_JOIN_ERR_OK; -} - -//=================================================== -//============== Roll =============================== -//=================================================== - -void Roll::targetObjectBuildLink() -{ - // called from link() - this->getTarget()->addLootValidatorRef(this); -} - -void Group::SetDifficulty(uint8 difficulty) -{ - m_difficulty = difficulty; - if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE groups SET difficulty = %u WHERE leaderGuid ='%u'", m_difficulty, GUID_LOPART(m_leaderGuid)); - - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *player = itr->getSource(); - if(!player->GetSession() || player->getLevel() < LEVELREQUIREMENT_HEROIC) - continue; - player->SetDifficulty(difficulty); - player->SendDungeonDifficulty(true); - } -} - -bool Group::InCombatToInstance(uint32 instanceId) -{ - for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player *pPlayer = itr->getSource(); - if(pPlayer->getAttackers().size() && pPlayer->GetInstanceId() == instanceId) - return true; - } - return false; -} - -void Group::ResetInstances(uint8 method, Player* SendMsgTo) -{ - if(isBGGroup()) - return; - - // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_DISBAND - - // we assume that when the difficulty changes, all instances that can be reset will be - uint8 dif = GetDifficulty(); - - for(BoundInstancesMap::iterator itr = m_boundInstances[dif].begin(); itr != m_boundInstances[dif].end();) - { - InstanceSave *p = itr->second.save; - const MapEntry *entry = sMapStore.LookupEntry(itr->first); - if(!entry || (!p->CanReset() && method != INSTANCE_RESET_GROUP_DISBAND)) - { - ++itr; - continue; - } - - if(method == INSTANCE_RESET_ALL) - { - // the "reset all instances" method can only reset normal maps - if(dif == DIFFICULTY_HEROIC || entry->map_type == MAP_RAID) - { - ++itr; - continue; - } - } - - bool isEmpty = true; - // if the map is loaded, reset it - Map *map = MapManager::Instance().FindMap(p->GetMapId(), p->GetInstanceId()); - if(map && map->IsDungeon()) - isEmpty = ((InstanceMap*)map)->Reset(method); - - if(SendMsgTo) - { - if(isEmpty) SendMsgTo->SendResetInstanceSuccess(p->GetMapId()); - else SendMsgTo->SendResetInstanceFailed(0, p->GetMapId()); - } - - if(isEmpty || method == INSTANCE_RESET_GROUP_DISBAND || method == INSTANCE_RESET_CHANGE_DIFFICULTY) - { - // do not reset the instance, just unbind if others are permanently bound to it - if(p->CanReset()) p->DeleteFromDB(); - else CharacterDatabase.PExecute("DELETE FROM group_instance WHERE instance = '%u'", p->GetInstanceId()); - // i don't know for sure if hash_map iterators - m_boundInstances[dif].erase(itr); - itr = m_boundInstances[dif].begin(); - // this unloads the instance save unless online players are bound to it - // (eg. permanent binds or GM solo binds) - p->RemoveGroup(this); - } - else - ++itr; - } -} - -InstanceGroupBind* Group::GetBoundInstance(uint32 mapid, uint8 difficulty) -{ - // some instances only have one difficulty - const MapEntry* entry = sMapStore.LookupEntry(mapid); - if(!entry || !entry->SupportsHeroicMode()) difficulty = DIFFICULTY_NORMAL; - - BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid); - if(itr != m_boundInstances[difficulty].end()) - return &itr->second; - else - return NULL; -} - -InstanceGroupBind* Group::BindToInstance(InstanceSave *save, bool permanent, bool load) -{ - if(save && !isBGGroup()) - { - InstanceGroupBind& bind = m_boundInstances[save->GetDifficulty()][save->GetMapId()]; - if(bind.save) - { - // when a boss is killed or when copying the players's binds to the group - if(permanent != bind.perm || save != bind.save) - if(!load) CharacterDatabase.PExecute("UPDATE group_instance SET instance = '%u', permanent = '%u' WHERE leaderGuid = '%u' AND instance = '%u'", save->GetInstanceId(), permanent, GUID_LOPART(GetLeaderGUID()), bind.save->GetInstanceId()); - } - else - if(!load) CharacterDatabase.PExecute("INSERT INTO group_instance (leaderGuid, instance, permanent) VALUES ('%u', '%u', '%u')", GUID_LOPART(GetLeaderGUID()), save->GetInstanceId(), permanent); - - if(bind.save != save) - { - if(bind.save) bind.save->RemoveGroup(this); - save->AddGroup(this); - } - - bind.save = save; - bind.perm = permanent; - if(!load) sLog.outDebug("Group::BindToInstance: %d is now bound to map %d, instance %d, difficulty %d", GUID_LOPART(GetLeaderGUID()), save->GetMapId(), save->GetInstanceId(), save->GetDifficulty()); - return &bind; - } - else - return NULL; -} - -void Group::UnbindInstance(uint32 mapid, uint8 difficulty, bool unload) -{ - BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid); - if(itr != m_boundInstances[difficulty].end()) - { - if(!unload) CharacterDatabase.PExecute("DELETE FROM group_instance WHERE leaderGuid = '%u' AND instance = '%u'", GUID_LOPART(GetLeaderGUID()), itr->second.save->GetInstanceId()); - itr->second.save->RemoveGroup(this); // save can become invalid - m_boundInstances[difficulty].erase(itr); - } -} - -void Group::_homebindIfInstance(Player *player) -{ - if(player && !player->isGameMaster() && sMapStore.LookupEntry(player->GetMapId())->IsDungeon()) - { - // leaving the group in an instance, the homebind timer is started - // unless the player is permanently saved to the instance - InstanceSave *save = sInstanceSaveManager.GetInstanceSave(player->GetInstanceId()); - InstancePlayerBind *playerBind = save ? player->GetBoundInstance(save->GetMapId(), save->GetDifficulty()) : NULL; - if(!playerBind || !playerBind->perm) - player->m_InstanceValid = false; - } -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "Opcodes.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "Player.h" +#include "World.h" +#include "ObjectMgr.h" +#include "Group.h" +#include "ObjectAccessor.h" +#include "BattleGround.h" +#include "MapManager.h" +#include "InstanceSaveMgr.h" +#include "MapInstanced.h" +#include "Util.h" + +Group::Group() +{ + m_leaderGuid = 0; + m_mainTank = 0; + m_mainAssistant = 0; + m_groupType = (GroupType)0; + m_bgGroup = NULL; + m_lootMethod = (LootMethod)0; + m_looterGuid = 0; + m_lootThreshold = ITEM_QUALITY_UNCOMMON; + + for(int i=0; iGetBgRaid(ALLIANCE) == this) m_bgGroup->SetBgRaid(ALLIANCE, NULL); + else if(m_bgGroup->GetBgRaid(HORDE) == this) m_bgGroup->SetBgRaid(HORDE, NULL); + else sLog.outError("Group::~Group: battleground group is not linked to the correct battleground."); + } + Rolls::iterator itr; + while(!RollId.empty()) + { + itr = RollId.begin(); + Roll *r = *itr; + RollId.erase(itr); + delete(r); + } + + // it is undefined whether objectmgr (which stores the groups) or instancesavemgr + // will be unloaded first so we must be prepared for both cases + // this may unload some instance saves + for(uint8 i = 0; i < TOTAL_DIFFICULTIES; i++) + for(BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) + itr->second.save->RemoveGroup(this); +} + +bool Group::Create(const uint64 &guid, const char * name) +{ + m_leaderGuid = guid; + m_leaderName = name; + + m_groupType = isBGGroup() ? GROUPTYPE_RAID : GROUPTYPE_NORMAL; + m_lootMethod = GROUP_LOOT; + m_lootThreshold = ITEM_QUALITY_UNCOMMON; + m_looterGuid = guid; + + m_difficulty = DIFFICULTY_NORMAL; + if(!isBGGroup()) + { + Player *leader = objmgr.GetPlayer(guid); + if(leader) m_difficulty = leader->GetDifficulty(); + + Player::ConvertInstancesToGroup(leader, this, guid); + + // store group in database + CharacterDatabase.BeginTransaction(); + CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid ='%u'", GUID_LOPART(m_leaderGuid)); + CharacterDatabase.PExecute("DELETE FROM group_member WHERE leaderGuid ='%u'", GUID_LOPART(m_leaderGuid)); + CharacterDatabase.PExecute("INSERT INTO groups(leaderGuid,mainTank,mainAssistant,lootMethod,looterGuid,lootThreshold,icon1,icon2,icon3,icon4,icon5,icon6,icon7,icon8,isRaid,difficulty) " + "VALUES('%u','%u','%u','%u','%u','%u','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','" I64FMTD "','%u','%u')", + GUID_LOPART(m_leaderGuid), GUID_LOPART(m_mainTank), GUID_LOPART(m_mainAssistant), uint32(m_lootMethod), + GUID_LOPART(m_looterGuid), uint32(m_lootThreshold), m_targetIcons[0], m_targetIcons[1], m_targetIcons[2], m_targetIcons[3], m_targetIcons[4], m_targetIcons[5], m_targetIcons[6], m_targetIcons[7], isRaidGroup(), m_difficulty); + } + + if(!AddMember(guid, name)) + return false; + + if(!isBGGroup()) CharacterDatabase.CommitTransaction(); + + return true; +} + +bool Group::LoadGroupFromDB(const uint64 &leaderGuid, QueryResult *result, bool loadMembers) +{ + if(isBGGroup()) + return false; + + bool external = true; + if(!result) + { + external = false; + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + result = CharacterDatabase.PQuery("SELECT mainTank, mainAssistant, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, isRaid, difficulty FROM groups WHERE leaderGuid ='%u'", GUID_LOPART(leaderGuid)); + if(!result) + return false; + } + + m_leaderGuid = leaderGuid; + + // group leader not exist + if(!objmgr.GetPlayerNameByGUID(m_leaderGuid, m_leaderName)) + { + if(!external) delete result; + return false; + } + + m_groupType = (*result)[13].GetBool() ? GROUPTYPE_RAID : GROUPTYPE_NORMAL; + m_difficulty = (*result)[14].GetUInt8(); + m_mainTank = (*result)[0].GetUInt64(); + m_mainAssistant = (*result)[1].GetUInt64(); + m_lootMethod = (LootMethod)(*result)[2].GetUInt8(); + m_looterGuid = MAKE_NEW_GUID((*result)[3].GetUInt32(), 0, HIGHGUID_PLAYER); + m_lootThreshold = (ItemQualities)(*result)[4].GetUInt16(); + + for(int i=0; iNextRow() ); + delete result; + // group too small + if(GetMembersCount() < 2) + return false; + } + + return true; +} + +bool Group::LoadMemberFromDB(uint32 guidLow, uint8 subgroup, bool assistant) +{ + MemberSlot member; + member.guid = MAKE_NEW_GUID(guidLow, 0, HIGHGUID_PLAYER); + + // skip non-existed member + if(!objmgr.GetPlayerNameByGUID(member.guid, member.name)) + return false; + + member.group = subgroup; + member.assistant = assistant; + m_memberSlots.push_back(member); + return true; +} + +bool Group::AddInvite(Player *player) +{ + if(!player || player->GetGroupInvite() || player->GetGroup()) + return false; + + RemoveInvite(player); + + m_invitees.insert(player->GetGUID()); + + player->SetGroupInvite(this); + + return true; +} + +bool Group::AddLeaderInvite(Player *player) +{ + if(!AddInvite(player)) + return false; + + m_leaderGuid = player->GetGUID(); + m_leaderName = player->GetName(); + return true; +} + +uint32 Group::RemoveInvite(Player *player) +{ + for(InvitesList::iterator itr=m_invitees.begin(); itr!=m_invitees.end(); ++itr) + { + if((*itr) == player->GetGUID()) + { + m_invitees.erase(itr); + break; + } + } + + player->SetGroupInvite(NULL); + return GetMembersCount(); +} + +void Group::RemoveAllInvites() +{ + for(InvitesList::iterator itr=m_invitees.begin(); itr!=m_invitees.end(); ++itr) + { + Player *invitee = objmgr.GetPlayer(*itr); + if(invitee) + invitee->SetGroupInvite(NULL); + } + m_invitees.clear(); +} + +bool Group::AddMember(const uint64 &guid, const char* name) +{ + if(!_addMember(guid, name)) + return false; + SendUpdate(); + + Player *player = objmgr.GetPlayer(guid); + if(player) + { + if(!IsLeader(player->GetGUID()) && !isBGGroup()) + { + // reset the new member's instances, unless he is currently in one of them + // including raid/heroic instances that they are not permanently bound to! + player->ResetInstances(INSTANCE_RESET_GROUP_JOIN); + + if(player->getLevel() >= LEVELREQUIREMENT_HEROIC && player->GetDifficulty() != GetDifficulty() ) + { + player->SetDifficulty(m_difficulty); + player->SendDungeonDifficulty(true); + } + } + player->SetGroupUpdateFlag(GROUP_UPDATE_FULL); + UpdatePlayerOutOfRange(player); + } + + return true; +} + +uint32 Group::RemoveMember(const uint64 &guid, const uint8 &method) +{ + // remove member and change leader (if need) only if strong more 2 members _before_ member remove + if(GetMembersCount() > (isBGGroup() ? 1 : 2)) // in BG group case allow 1 members group + { + bool leaderChanged = _removeMember(guid); + + Player *player = objmgr.GetPlayer( guid ); + if (player) + { + WorldPacket data; + + if(method == 1) + { + data.Initialize( SMSG_GROUP_UNINVITE, 0 ); + player->GetSession()->SendPacket( &data ); + } + + data.Initialize(SMSG_GROUP_LIST, 24); + data << uint64(0) << uint64(0) << uint64(0); + player->GetSession()->SendPacket(&data); + + _homebindIfInstance(player); + } + + if(leaderChanged) + { + WorldPacket data(SMSG_GROUP_SET_LEADER, (m_memberSlots.front().name.size()+1)); + data << m_memberSlots.front().name; + BroadcastPacket(&data); + } + + SendUpdate(); + } + // if group before remove <= 2 disband it + else + Disband(true); + + return m_memberSlots.size(); +} + +void Group::ChangeLeader(const uint64 &guid) +{ + member_citerator slot = _getMemberCSlot(guid); + + if(slot==m_memberSlots.end()) + return; + + _setLeader(guid); + + WorldPacket data(SMSG_GROUP_SET_LEADER, slot->name.size()+1); + data << slot->name; + BroadcastPacket(&data); + SendUpdate(); +} + +void Group::Disband(bool hideDestroy) +{ + Player *player; + + for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) + { + player = objmgr.GetPlayer(citr->guid); + if(!player) + continue; + + player->SetGroup(NULL); + + if(!player->GetSession()) + continue; + + WorldPacket data; + if(!hideDestroy) + { + data.Initialize(SMSG_GROUP_DESTROYED, 0); + player->GetSession()->SendPacket(&data); + } + + data.Initialize(SMSG_GROUP_LIST, 24); + data << uint64(0) << uint64(0) << uint64(0); + player->GetSession()->SendPacket(&data); + + _homebindIfInstance(player); + } + RollId.clear(); + m_memberSlots.clear(); + + RemoveAllInvites(); + + if(!isBGGroup()) + { + CharacterDatabase.BeginTransaction(); + CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid='%u'", GUID_LOPART(m_leaderGuid)); + CharacterDatabase.PExecute("DELETE FROM group_member WHERE leaderGuid='%u'", GUID_LOPART(m_leaderGuid)); + CharacterDatabase.CommitTransaction(); + ResetInstances(INSTANCE_RESET_GROUP_DISBAND, NULL); + } + + m_leaderGuid = 0; + m_leaderName = ""; +} + +/*********************************************************/ +/*** LOOT SYSTEM ***/ +/*********************************************************/ + +void Group::SendLootStartRoll(uint32 CountDown, const Roll &r) +{ + WorldPacket data(SMSG_LOOT_START_ROLL, (8+4+4+4+4+4)); + data << uint64(r.itemGUID); // guid of rolled item + data << uint32(r.totalPlayersRolling); // maybe the number of players rolling for it??? + data << uint32(r.itemid); // the itemEntryId for the item that shall be rolled for + data << uint32(r.itemRandomSuffix); // randomSuffix + data << uint32(r.itemRandomPropId); // item random property ID + data << uint32(CountDown); // the countdown time to choose "need" or "greed" + + for (Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr!=r.playerVote.end(); ++itr) + { + Player *p = objmgr.GetPlayer(itr->first); + if(!p || !p->GetSession()) + continue; + + if(itr->second != NOT_VALID) + p->GetSession()->SendPacket( &data ); + } +} + +void Group::SendLootRoll(uint64 SourceGuid, uint64 TargetGuid, uint8 RollNumber, uint8 RollType, const Roll &r) +{ + WorldPacket data(SMSG_LOOT_ROLL, (8+4+8+4+4+4+1+1)); + data << uint64(SourceGuid); // guid of the item rolled + data << uint32(0); // unknown, maybe amount of players + data << uint64(TargetGuid); + data << uint32(r.itemid); // the itemEntryId for the item that shall be rolled for + data << uint32(r.itemRandomSuffix); // randomSuffix + data << uint32(r.itemRandomPropId); // Item random property ID + data << uint8(RollNumber); // 0: "Need for: [item name]" > 127: "you passed on: [item name]" Roll number + data << uint8(RollType); // 0: "Need for: [item name]" 0: "You have selected need for [item name] 1: need roll 2: greed roll + data << uint8(0); // 2.4.0 + + for( Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr!=r.playerVote.end(); ++itr) + { + Player *p = objmgr.GetPlayer(itr->first); + if(!p || !p->GetSession()) + continue; + + if(itr->second != NOT_VALID) + p->GetSession()->SendPacket( &data ); + } +} + +void Group::SendLootRollWon(uint64 SourceGuid, uint64 TargetGuid, uint8 RollNumber, uint8 RollType, const Roll &r) +{ + WorldPacket data(SMSG_LOOT_ROLL_WON, (8+4+4+4+4+8+1+1)); + data << uint64(SourceGuid); // guid of the item rolled + data << uint32(0); // unknown, maybe amount of players + data << uint32(r.itemid); // the itemEntryId for the item that shall be rolled for + data << uint32(r.itemRandomSuffix); // randomSuffix + data << uint32(r.itemRandomPropId); // Item random property + data << uint64(TargetGuid); // guid of the player who won. + data << uint8(RollNumber); // rollnumber realted to SMSG_LOOT_ROLL + data << uint8(RollType); // Rolltype related to SMSG_LOOT_ROLL + + for( Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr!=r.playerVote.end(); ++itr) + { + Player *p = objmgr.GetPlayer(itr->first); + if(!p || !p->GetSession()) + continue; + + if(itr->second != NOT_VALID) + p->GetSession()->SendPacket( &data ); + } +} + +void Group::SendLootAllPassed(uint32 NumberOfPlayers, const Roll &r) +{ + WorldPacket data(SMSG_LOOT_ALL_PASSED, (8+4+4+4+4)); + data << uint64(r.itemGUID); // Guid of the item rolled + data << uint32(NumberOfPlayers); // The number of players rolling for it??? + data << uint32(r.itemid); // The itemEntryId for the item that shall be rolled for + data << uint32(r.itemRandomPropId); // Item random property ID + data << uint32(r.itemRandomSuffix); // Item random suffix ID + + for( Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr!=r.playerVote.end(); ++itr) + { + Player *p = objmgr.GetPlayer(itr->first); + if(!p || !p->GetSession()) + continue; + + if(itr->second != NOT_VALID) + p->GetSession()->SendPacket( &data ); + } +} + +void Group::GroupLoot(uint64 playerGUID, Loot *loot, Creature *creature) +{ + std::vector::iterator i; + ItemPrototype const *item; + uint8 itemSlot = 0; + Player *player = objmgr.GetPlayer(playerGUID); + Group *group = player->GetGroup(); + + for (i=loot->items.begin(); i != loot->items.end(); ++i, ++itemSlot) + { + item = objmgr.GetItemPrototype(i->itemid); + if (!item) + { + //sLog.outDebug("Group::GroupLoot: missing item prototype for item with id: %d", i->itemid); + continue; + } + + //roll for over-threshold item if it's one-player loot + if (item->Quality >= uint32(m_lootThreshold) && !i->freeforall) + { + uint64 newitemGUID = MAKE_NEW_GUID(objmgr.GenerateLowGuid(HIGHGUID_ITEM),0,HIGHGUID_ITEM); + Roll* r=new Roll(newitemGUID,*i); + + //a vector is filled with only near party members + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *member = itr->getSource(); + if(!member || !member->GetSession()) + continue; + if ( i->AllowedForPlayer(member) ) + { + if (member->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) + { + r->playerVote[member->GetGUID()] = NOT_EMITED_YET; + ++r->totalPlayersRolling; + } + } + } + + r->setLoot(loot); + r->itemSlot = itemSlot; + + group->SendLootStartRoll(60000, *r); + + loot->items[itemSlot].is_blocked = true; + creature->m_groupLootTimer = 60000; + creature->lootingGroupLeaderGUID = GetLeaderGUID(); + + RollId.push_back(r); + } + else + i->is_underthreshold=1; + + } +} + +void Group::NeedBeforeGreed(uint64 playerGUID, Loot *loot, Creature *creature) +{ + ItemPrototype const *item; + Player *player = objmgr.GetPlayer(playerGUID); + Group *group = player->GetGroup(); + + uint8 itemSlot = 0; + for(std::vector::iterator i=loot->items.begin(); i != loot->items.end(); ++i, ++itemSlot) + { + item = objmgr.GetItemPrototype(i->itemid); + + //only roll for one-player items, not for ones everyone can get + if (item->Quality >= uint32(m_lootThreshold) && !i->freeforall) + { + uint64 newitemGUID = MAKE_NEW_GUID(objmgr.GenerateLowGuid(HIGHGUID_ITEM),0,HIGHGUID_ITEM); + Roll* r=new Roll(newitemGUID,*i); + + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *playerToRoll = itr->getSource(); + if(!playerToRoll || !playerToRoll->GetSession()) + continue; + + if (playerToRoll->CanUseItem(item) && i->AllowedForPlayer(playerToRoll) ) + { + if (playerToRoll->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) + { + r->playerVote[playerToRoll->GetGUID()] = NOT_EMITED_YET; + ++r->totalPlayersRolling; + } + } + } + + if (r->totalPlayersRolling > 0) + { + r->setLoot(loot); + r->itemSlot = itemSlot; + + group->SendLootStartRoll(60000, *r); + + loot->items[itemSlot].is_blocked = true; + + RollId.push_back(r); + } + else + { + delete r; + } + } + else + i->is_underthreshold=1; + } +} + +void Group::MasterLoot(uint64 playerGUID, Loot* /*loot*/, Creature *creature) +{ + Player *player = objmgr.GetPlayer(playerGUID); + if(!player) + return; + + sLog.outDebug("Group::MasterLoot (SMSG_LOOT_MASTER_LIST, 330) player = [%s].", player->GetName()); + + uint32 real_count = 0; + + WorldPacket data(SMSG_LOOT_MASTER_LIST, 330); + data << (uint8)GetMembersCount(); + + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *looter = itr->getSource(); + if (!looter->IsInWorld()) + continue; + + if (looter->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) + { + data << looter->GetGUID(); + ++real_count; + } + } + + data.put(0,real_count); + + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *looter = itr->getSource(); + if (looter->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) + looter->GetSession()->SendPacket(&data); + } +} + +void Group::CountRollVote(uint64 playerGUID, uint64 Guid, uint32 NumberOfPlayers, uint8 Choise) +{ + Rolls::iterator rollI = GetRoll(Guid); + if (rollI == RollId.end()) + return; + Roll* roll = *rollI; + + Roll::PlayerVote::iterator itr = roll->playerVote.find(playerGUID); + // this condition means that player joins to the party after roll begins + if (itr == roll->playerVote.end()) + return; + + if (roll->getLoot()) + if (roll->getLoot()->items.empty()) + return; + + switch (Choise) + { + case 0: //Player choose pass + { + SendLootRoll(0, playerGUID, 128, 128, *roll); + ++roll->totalPass; + itr->second = PASS; + } + break; + case 1: //player choose Need + { + SendLootRoll(0, playerGUID, 0, 0, *roll); + ++roll->totalNeed; + itr->second = NEED; + } + break; + case 2: //player choose Greed + { + SendLootRoll(0, playerGUID, 128, 2, *roll); + ++roll->totalGreed; + itr->second = GREED; + } + break; + } + if (roll->totalPass + roll->totalGreed + roll->totalNeed >= roll->totalPlayersRolling) + { + CountTheRoll(rollI, NumberOfPlayers); + } +} + +//called when roll timer expires +void Group::EndRoll() +{ + Rolls::iterator itr; + while(!RollId.empty()) + { + //need more testing here, if rolls disappear + itr = RollId.begin(); + CountTheRoll(itr, GetMembersCount()); //i don't have to edit player votes, who didn't vote ... he will pass + } +} + +void Group::CountTheRoll(Rolls::iterator rollI, uint32 NumberOfPlayers) +{ + Roll* roll = *rollI; + if(!roll->isValid()) // is loot already deleted ? + { + RollId.erase(rollI); + delete roll; + return; + } + //end of the roll + if (roll->totalNeed > 0) + { + if(!roll->playerVote.empty()) + { + uint8 maxresul = 0; + uint64 maxguid = (*roll->playerVote.begin()).first; + Player *player; + + for( Roll::PlayerVote::const_iterator itr=roll->playerVote.begin(); itr!=roll->playerVote.end(); ++itr) + { + if (itr->second != NEED) + continue; + + uint8 randomN = urand(1, 99); + SendLootRoll(0, itr->first, randomN, 1, *roll); + if (maxresul < randomN) + { + maxguid = itr->first; + maxresul = randomN; + } + } + SendLootRollWon(0, maxguid, maxresul, 1, *roll); + player = objmgr.GetPlayer(maxguid); + + if(player && player->GetSession()) + { + ItemPosCountVec dest; + LootItem *item = &(roll->getLoot()->items[roll->itemSlot]); + uint8 msg = player->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, roll->itemid, item->count ); + if ( msg == EQUIP_ERR_OK ) + { + item->is_looted = true; + roll->getLoot()->NotifyItemRemoved(roll->itemSlot); + --roll->getLoot()->unlootedCount; + player->StoreNewItem( dest, roll->itemid, true, item->randomPropertyId); + } + else + { + item->is_blocked = false; + player->SendEquipError( msg, NULL, NULL ); + } + } + } + } + else if (roll->totalGreed > 0) + { + if(!roll->playerVote.empty()) + { + uint8 maxresul = 0; + uint64 maxguid = (*roll->playerVote.begin()).first; + Player *player; + + Roll::PlayerVote::iterator itr; + for (itr=roll->playerVote.begin(); itr!=roll->playerVote.end(); ++itr) + { + if (itr->second != GREED) + continue; + + uint8 randomN = urand(1, 99); + SendLootRoll(0, itr->first, randomN, 2, *roll); + if (maxresul < randomN) + { + maxguid = itr->first; + maxresul = randomN; + } + } + SendLootRollWon(0, maxguid, maxresul, 2, *roll); + player = objmgr.GetPlayer(maxguid); + + if(player && player->GetSession()) + { + ItemPosCountVec dest; + LootItem *item = &(roll->getLoot()->items[roll->itemSlot]); + uint8 msg = player->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, roll->itemid, item->count ); + if ( msg == EQUIP_ERR_OK ) + { + item->is_looted = true; + roll->getLoot()->NotifyItemRemoved(roll->itemSlot); + --roll->getLoot()->unlootedCount; + player->StoreNewItem( dest, roll->itemid, true, item->randomPropertyId); + } + else + { + item->is_blocked = false; + player->SendEquipError( msg, NULL, NULL ); + } + } + } + } + else + { + SendLootAllPassed(NumberOfPlayers, *roll); + LootItem *item = &(roll->getLoot()->items[roll->itemSlot]); + if(item) item->is_blocked = false; + } + RollId.erase(rollI); + delete roll; +} + +void Group::SetTargetIcon(uint8 id, uint64 guid) +{ + if(id >= TARGETICONCOUNT) + return; + + // clean other icons + if( guid != 0 ) + for(int i=0; inext()) + { + Player* member = itr->getSource(); + if(!member || !member->isAlive()) // only for alive + continue; + + if(!member->IsAtGroupRewardDistance(victim)) // at req. distance + continue; + + ++count; + sum_level += member->getLevel(); + if(!member_with_max_level || member_with_max_level->getLevel() < member->getLevel()) + member_with_max_level = member; + } +} + +void Group::SendTargetIconList(WorldSession *session) +{ + if(!session) + return; + + WorldPacket data(MSG_RAID_TARGET_UPDATE, (1+TARGETICONCOUNT*9)); + data << (uint8)1; + + for(int i=0; iSendPacket(&data); +} + +void Group::SendUpdate() +{ + Player *player; + + for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) + { + player = objmgr.GetPlayer(citr->guid); + if(!player || !player->GetSession()) + continue; + // guess size + WorldPacket data(SMSG_GROUP_LIST, (1+1+1+1+8+4+GetMembersCount()*20)); + data << (uint8)m_groupType; // group type + data << (uint8)(isBGGroup() ? 1 : 0); // 2.0.x, isBattleGroundGroup? + data << (uint8)(citr->group); // groupid + data << (uint8)(citr->assistant?0x01:0); // 0x2 main assist, 0x4 main tank + data << uint64(0x50000000FFFFFFFELL); // related to voice chat? + data << uint32(GetMembersCount()-1); + for(member_citerator citr2 = m_memberSlots.begin(); citr2 != m_memberSlots.end(); ++citr2) + { + if(citr->guid == citr2->guid) + continue; + + data << citr2->name; + data << (uint64)citr2->guid; + // online-state + data << (uint8)(objmgr.GetPlayer(citr2->guid) ? 1 : 0); + data << (uint8)(citr2->group); // groupid + data << (uint8)(citr2->assistant?0x01:0); // 0x2 main assist, 0x4 main tank + } + + data << uint64(m_leaderGuid); // leader guid + if(GetMembersCount()-1) + { + data << (uint8)m_lootMethod; // loot method + data << (uint64)m_looterGuid; // looter guid + data << (uint8)m_lootThreshold; // loot threshold + data << (uint8)m_difficulty; // Heroic Mod Group + + } + player->GetSession()->SendPacket( &data ); + } +} + +void Group::UpdatePlayerOutOfRange(Player* pPlayer) +{ + if(!pPlayer) + return; + + Player *player; + WorldPacket data; + pPlayer->GetSession()->BuildPartyMemberStatsChangedPacket(pPlayer, &data); + + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + player = itr->getSource(); + if (player && player != pPlayer && !pPlayer->isVisibleFor(player)) + player->GetSession()->SendPacket(&data); + } +} + +void Group::BroadcastPacket(WorldPacket *packet, int group, uint64 ignore) +{ + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *pl = itr->getSource(); + if(!pl || (ignore != 0 && pl->GetGUID() == ignore)) + continue; + + if (pl->GetSession() && (group==-1 || itr->getSubGroup()==group)) + pl->GetSession()->SendPacket(packet); + } +} + +void Group::BroadcastReadyCheck(WorldPacket *packet) +{ + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *pl = itr->getSource(); + if(pl && pl->GetSession()) + if(IsLeader(pl->GetGUID()) || IsAssistant(pl->GetGUID())) + pl->GetSession()->SendPacket(packet); + } +} + +void Group::OfflineReadyCheck() +{ + for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) + { + Player *pl = objmgr.GetPlayer(citr->guid); + if (!pl || !pl->GetSession()) + { + WorldPacket data(MSG_RAID_READY_CHECK_CONFIRM, 9); + data << citr->guid; + data << (uint8)0; + BroadcastReadyCheck(&data); + } + } +} + +bool Group::_addMember(const uint64 &guid, const char* name, bool isAssistant) +{ + // get first not-full group + uint8 groupid = 0; + std::vector temp(MAXRAIDSIZE/MAXGROUPSIZE); + for(member_citerator itr = m_memberSlots.begin(); itr != m_memberSlots.end(); ++itr) + { + if (itr->group >= temp.size()) continue; + ++temp[itr->group]; + if(temp[groupid] >= MAXGROUPSIZE) + ++groupid; + } + + return _addMember(guid, name, isAssistant, groupid); +} + +bool Group::_addMember(const uint64 &guid, const char* name, bool isAssistant, uint8 group) +{ + if(IsFull()) + return false; + + if(!guid) + return false; + + Player *player = objmgr.GetPlayer(guid); + + MemberSlot member; + member.guid = guid; + member.name = name; + member.group = group; + member.assistant = isAssistant; + m_memberSlots.push_back(member); + + if(player) + { + player->SetGroupInvite(NULL); + player->SetGroup(this, group); + // if the same group invites the player back, cancel the homebind timer + InstanceGroupBind *bind = GetBoundInstance(player->GetMapId(), player->GetDifficulty()); + if(bind && bind->save->GetInstanceId() == player->GetInstanceId()) + player->m_InstanceValid = true; + } + + if(!isRaidGroup()) // reset targetIcons for non-raid-groups + { + for(int i=0; iSetGroup(NULL); + } + + _removeRolls(guid); + + member_witerator slot = _getMemberWSlot(guid); + if (slot != m_memberSlots.end()) + m_memberSlots.erase(slot); + + if(!isBGGroup()) + CharacterDatabase.PExecute("DELETE FROM group_member WHERE memberGuid='%u'", GUID_LOPART(guid)); + + if(m_leaderGuid == guid) // leader was removed + { + if(GetMembersCount() > 0) + _setLeader(m_memberSlots.front().guid); + return true; + } + + return false; +} + +void Group::_setLeader(const uint64 &guid) +{ + member_citerator slot = _getMemberCSlot(guid); + if(slot==m_memberSlots.end()) + return; + + if(!isBGGroup()) + { + // TODO: set a time limit to have this function run rarely cause it can be slow + CharacterDatabase.BeginTransaction(); + + // update the group's bound instances when changing leaders + + // remove all permanent binds from the group + // in the DB also remove solo binds that will be replaced with permbinds + // from the new leader + CharacterDatabase.PExecute( + "DELETE FROM group_instance WHERE leaderguid='%u' AND (permanent = 1 OR " + "instance IN (SELECT instance FROM character_instance WHERE guid = '%u')" + ")", GUID_LOPART(m_leaderGuid), GUID_LOPART(slot->guid) + ); + + Player *player = objmgr.GetPlayer(slot->guid); + if(player) + { + for(uint8 i = 0; i < TOTAL_DIFFICULTIES; i++) + { + for(BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end();) + { + if(itr->second.perm) + { + itr->second.save->RemoveGroup(this); + m_boundInstances[i].erase(itr++); + } + else + ++itr; + } + } + } + + // update the group's solo binds to the new leader + CharacterDatabase.PExecute("UPDATE group_instance SET leaderGuid='%u' WHERE leaderGuid = '%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_leaderGuid)); + + // copy the permanent binds from the new leader to the group + // overwriting the solo binds with permanent ones if necessary + // in the DB those have been deleted already + Player::ConvertInstancesToGroup(player, this, slot->guid); + + // update the group leader + CharacterDatabase.PExecute("UPDATE groups SET leaderGuid='%u' WHERE leaderGuid='%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_leaderGuid)); + CharacterDatabase.PExecute("UPDATE group_member SET leaderGuid='%u' WHERE leaderGuid='%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_leaderGuid)); + CharacterDatabase.CommitTransaction(); + } + + m_leaderGuid = slot->guid; + m_leaderName = slot->name; +} + +void Group::_removeRolls(const uint64 &guid) +{ + for (Rolls::iterator it = RollId.begin(); it < RollId.end(); it++) + { + Roll* roll = *it; + Roll::PlayerVote::iterator itr2 = roll->playerVote.find(guid); + if(itr2 == roll->playerVote.end()) + continue; + + if (itr2->second == GREED) --roll->totalGreed; + if (itr2->second == NEED) --roll->totalNeed; + if (itr2->second == PASS) --roll->totalPass; + if (itr2->second != NOT_VALID) --roll->totalPlayersRolling; + + roll->playerVote.erase(itr2); + + CountRollVote(guid, roll->itemGUID, GetMembersCount()-1, 3); + } +} + +void Group::_convertToRaid() +{ + m_groupType = GROUPTYPE_RAID; + + if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE groups SET isRaid = 1 WHERE leaderGuid='%u'", GUID_LOPART(m_leaderGuid)); +} + +bool Group::_setMembersGroup(const uint64 &guid, const uint8 &group) +{ + member_witerator slot = _getMemberWSlot(guid); + if(slot==m_memberSlots.end()) + return false; + + slot->group = group; + if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE group_member SET subgroup='%u' WHERE memberGuid='%u'", group, GUID_LOPART(guid)); + return true; +} + +bool Group::_setAssistantFlag(const uint64 &guid, const bool &state) +{ + member_witerator slot = _getMemberWSlot(guid); + if(slot==m_memberSlots.end()) + return false; + + slot->assistant = state; + if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE group_member SET assistant='%u' WHERE memberGuid='%u'", (state==true)?1:0, GUID_LOPART(guid)); + return true; +} + +bool Group::_setMainTank(const uint64 &guid) +{ + member_citerator slot = _getMemberCSlot(guid); + if(slot==m_memberSlots.end()) + return false; + + if(m_mainAssistant == guid) + _setMainAssistant(0); + m_mainTank = guid; + if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE groups SET mainTank='%u' WHERE leaderGuid='%u'", GUID_LOPART(m_mainTank), GUID_LOPART(m_leaderGuid)); + return true; +} + +bool Group::_setMainAssistant(const uint64 &guid) +{ + member_witerator slot = _getMemberWSlot(guid); + if(slot==m_memberSlots.end()) + return false; + + if(m_mainTank == guid) + _setMainTank(0); + m_mainAssistant = guid; + if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE groups SET mainAssistant='%u' WHERE leaderGuid='%u'", GUID_LOPART(m_mainAssistant), GUID_LOPART(m_leaderGuid)); + return true; +} + +bool Group::SameSubGroup(Player const* member1, Player const* member2) const +{ + if(!member1 || !member2) return false; + if (member1->GetGroup() != this || member2->GetGroup() != this) return false; + else return member1->GetSubGroup() == member2->GetSubGroup(); +} + +// allows setting subgroup for offline members +void Group::ChangeMembersGroup(const uint64 &guid, const uint8 &group) +{ + if(!isRaidGroup()) + return; + Player *player = objmgr.GetPlayer(guid); + if (!player) + { + if(_setMembersGroup(guid, group)) + SendUpdate(); + } + else ChangeMembersGroup(player, group); +} + +// only for online members +void Group::ChangeMembersGroup(Player *player, const uint8 &group) +{ + if(!player || !isRaidGroup()) + return; + if(_setMembersGroup(player->GetGUID(), group)) + { + player->GetGroupRef().setSubGroup(group); + SendUpdate(); + } +} + +void Group::UpdateLooterGuid( Creature* creature, bool ifneed ) +{ + switch (GetLootMethod()) + { + case MASTER_LOOT: + case FREE_FOR_ALL: + return; + default: + // round robin style looting applies for all low + // quality items in each loot method except free for all and master loot + break; + } + + member_citerator guid_itr = _getMemberCSlot(GetLooterGuid()); + if(guid_itr != m_memberSlots.end()) + { + if(ifneed) + { + // not update if only update if need and ok + Player* looter = ObjectAccessor::FindPlayer(guid_itr->guid); + if(looter && looter->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) + return; + } + ++guid_itr; + } + + // search next after current + if(guid_itr != m_memberSlots.end()) + { + for(member_citerator itr = guid_itr; itr != m_memberSlots.end(); ++itr) + { + if(Player* pl = ObjectAccessor::FindPlayer(itr->guid)) + { + if (pl->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) + { + bool refresh = pl->GetLootGUID()==creature->GetGUID(); + + //if(refresh) // update loot for new looter + // pl->GetSession()->DoLootRelease(pl->GetLootGUID()); + SetLooterGuid(pl->GetGUID()); + SendUpdate(); + if(refresh) // update loot for new looter + pl->SendLoot(creature->GetGUID(),LOOT_CORPSE); + return; + } + } + } + } + + // search from start + for(member_citerator itr = m_memberSlots.begin(); itr != guid_itr; ++itr) + { + if(Player* pl = ObjectAccessor::FindPlayer(itr->guid)) + { + if (pl->GetDistance2d(creature) < sWorld.getConfig(CONFIG_GROUP_XP_DISTANCE)) + { + bool refresh = pl->GetLootGUID()==creature->GetGUID(); + + //if(refresh) // update loot for new looter + // pl->GetSession()->DoLootRelease(pl->GetLootGUID()); + SetLooterGuid(pl->GetGUID()); + SendUpdate(); + if(refresh) // update loot for new looter + pl->SendLoot(creature->GetGUID(),LOOT_CORPSE); + return; + } + } + } + + SetLooterGuid(0); + SendUpdate(); +} + +uint32 Group::CanJoinBattleGroundQueue(uint32 bgTypeId, uint32 bgQueueType, uint32 MinPlayerCount, uint32 MaxPlayerCount, bool isRated, uint32 arenaSlot) +{ + // check for min / max count + uint32 memberscount = GetMembersCount(); + if(memberscount < MinPlayerCount) + return BG_JOIN_ERR_GROUP_NOT_ENOUGH; + if(memberscount > MaxPlayerCount) + return BG_JOIN_ERR_GROUP_TOO_MANY; + + // get a player as reference, to compare other players' stats to (arena team id, queue id based on level, etc.) + Player * reference = GetFirstMember()->getSource(); + // no reference found, can't join this way + if(!reference) + return BG_JOIN_ERR_OFFLINE_MEMBER; + + uint32 bgQueueId = reference->GetBattleGroundQueueIdFromLevel(); + uint32 arenaTeamId = reference->GetArenaTeamId(arenaSlot); + uint32 team = reference->GetTeam(); + + // check every member of the group to be able to join + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *member = itr->getSource(); + // offline member? don't let join + if(!member) + return BG_JOIN_ERR_OFFLINE_MEMBER; + // don't allow cross-faction join as group + if(member->GetTeam() != team) + return BG_JOIN_ERR_MIXED_FACTION; + // not in the same battleground level braket, don't let join + if(member->GetBattleGroundQueueIdFromLevel() != bgQueueId) + return BG_JOIN_ERR_MIXED_LEVELS; + // don't let join rated matches if the arena team id doesn't match + if(isRated && member->GetArenaTeamId(arenaSlot) != arenaTeamId) + return BG_JOIN_ERR_MIXED_ARENATEAM; + // don't let join if someone from the group is already in that bg queue + if(member->InBattleGroundQueueForBattleGroundQueueType(bgQueueType)) + return BG_JOIN_ERR_GROUP_MEMBER_ALREADY_IN_QUEUE; + // check for deserter debuff in case not arena queue + if(bgTypeId != BATTLEGROUND_AA && !member->CanJoinToBattleground()) + return BG_JOIN_ERR_GROUP_DESERTER; + // check if member can join any more battleground queues + if(!member->HasFreeBattleGroundQueueId()) + return BG_JOIN_ERR_ALL_QUEUES_USED; + } + return BG_JOIN_ERR_OK; +} + +//=================================================== +//============== Roll =============================== +//=================================================== + +void Roll::targetObjectBuildLink() +{ + // called from link() + this->getTarget()->addLootValidatorRef(this); +} + +void Group::SetDifficulty(uint8 difficulty) +{ + m_difficulty = difficulty; + if(!isBGGroup()) CharacterDatabase.PExecute("UPDATE groups SET difficulty = %u WHERE leaderGuid ='%u'", m_difficulty, GUID_LOPART(m_leaderGuid)); + + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *player = itr->getSource(); + if(!player->GetSession() || player->getLevel() < LEVELREQUIREMENT_HEROIC) + continue; + player->SetDifficulty(difficulty); + player->SendDungeonDifficulty(true); + } +} + +bool Group::InCombatToInstance(uint32 instanceId) +{ + for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player *pPlayer = itr->getSource(); + if(pPlayer->getAttackers().size() && pPlayer->GetInstanceId() == instanceId) + return true; + } + return false; +} + +void Group::ResetInstances(uint8 method, Player* SendMsgTo) +{ + if(isBGGroup()) + return; + + // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_DISBAND + + // we assume that when the difficulty changes, all instances that can be reset will be + uint8 dif = GetDifficulty(); + + for(BoundInstancesMap::iterator itr = m_boundInstances[dif].begin(); itr != m_boundInstances[dif].end();) + { + InstanceSave *p = itr->second.save; + const MapEntry *entry = sMapStore.LookupEntry(itr->first); + if(!entry || (!p->CanReset() && method != INSTANCE_RESET_GROUP_DISBAND)) + { + ++itr; + continue; + } + + if(method == INSTANCE_RESET_ALL) + { + // the "reset all instances" method can only reset normal maps + if(dif == DIFFICULTY_HEROIC || entry->map_type == MAP_RAID) + { + ++itr; + continue; + } + } + + bool isEmpty = true; + // if the map is loaded, reset it + Map *map = MapManager::Instance().FindMap(p->GetMapId(), p->GetInstanceId()); + if(map && map->IsDungeon()) + isEmpty = ((InstanceMap*)map)->Reset(method); + + if(SendMsgTo) + { + if(isEmpty) SendMsgTo->SendResetInstanceSuccess(p->GetMapId()); + else SendMsgTo->SendResetInstanceFailed(0, p->GetMapId()); + } + + if(isEmpty || method == INSTANCE_RESET_GROUP_DISBAND || method == INSTANCE_RESET_CHANGE_DIFFICULTY) + { + // do not reset the instance, just unbind if others are permanently bound to it + if(p->CanReset()) p->DeleteFromDB(); + else CharacterDatabase.PExecute("DELETE FROM group_instance WHERE instance = '%u'", p->GetInstanceId()); + // i don't know for sure if hash_map iterators + m_boundInstances[dif].erase(itr); + itr = m_boundInstances[dif].begin(); + // this unloads the instance save unless online players are bound to it + // (eg. permanent binds or GM solo binds) + p->RemoveGroup(this); + } + else + ++itr; + } +} + +InstanceGroupBind* Group::GetBoundInstance(uint32 mapid, uint8 difficulty) +{ + // some instances only have one difficulty + const MapEntry* entry = sMapStore.LookupEntry(mapid); + if(!entry || !entry->SupportsHeroicMode()) difficulty = DIFFICULTY_NORMAL; + + BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid); + if(itr != m_boundInstances[difficulty].end()) + return &itr->second; + else + return NULL; +} + +InstanceGroupBind* Group::BindToInstance(InstanceSave *save, bool permanent, bool load) +{ + if(save && !isBGGroup()) + { + InstanceGroupBind& bind = m_boundInstances[save->GetDifficulty()][save->GetMapId()]; + if(bind.save) + { + // when a boss is killed or when copying the players's binds to the group + if(permanent != bind.perm || save != bind.save) + if(!load) CharacterDatabase.PExecute("UPDATE group_instance SET instance = '%u', permanent = '%u' WHERE leaderGuid = '%u' AND instance = '%u'", save->GetInstanceId(), permanent, GUID_LOPART(GetLeaderGUID()), bind.save->GetInstanceId()); + } + else + if(!load) CharacterDatabase.PExecute("INSERT INTO group_instance (leaderGuid, instance, permanent) VALUES ('%u', '%u', '%u')", GUID_LOPART(GetLeaderGUID()), save->GetInstanceId(), permanent); + + if(bind.save != save) + { + if(bind.save) bind.save->RemoveGroup(this); + save->AddGroup(this); + } + + bind.save = save; + bind.perm = permanent; + if(!load) sLog.outDebug("Group::BindToInstance: %d is now bound to map %d, instance %d, difficulty %d", GUID_LOPART(GetLeaderGUID()), save->GetMapId(), save->GetInstanceId(), save->GetDifficulty()); + return &bind; + } + else + return NULL; +} + +void Group::UnbindInstance(uint32 mapid, uint8 difficulty, bool unload) +{ + BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid); + if(itr != m_boundInstances[difficulty].end()) + { + if(!unload) CharacterDatabase.PExecute("DELETE FROM group_instance WHERE leaderGuid = '%u' AND instance = '%u'", GUID_LOPART(GetLeaderGUID()), itr->second.save->GetInstanceId()); + itr->second.save->RemoveGroup(this); // save can become invalid + m_boundInstances[difficulty].erase(itr); + } +} + +void Group::_homebindIfInstance(Player *player) +{ + if(player && !player->isGameMaster() && sMapStore.LookupEntry(player->GetMapId())->IsDungeon()) + { + // leaving the group in an instance, the homebind timer is started + // unless the player is permanently saved to the instance + InstanceSave *save = sInstanceSaveManager.GetInstanceSave(player->GetInstanceId()); + InstancePlayerBind *playerBind = save ? player->GetBoundInstance(save->GetMapId(), save->GetDifficulty()) : NULL; + if(!playerBind || !playerBind->perm) + player->m_InstanceValid = false; + } +} diff --git a/src/game/HomeMovementGenerator.cpp b/src/game/HomeMovementGenerator.cpp index 281909b68de..79f312d3b8b 100644 --- a/src/game/HomeMovementGenerator.cpp +++ b/src/game/HomeMovementGenerator.cpp @@ -1,86 +1,86 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "HomeMovementGenerator.h" -#include "Creature.h" -#include "Traveller.h" -#include "MapManager.h" -#include "ObjectAccessor.h" -#include "DestinationHolderImp.h" -#include "ObjectMgr.h" -#include "WorldPacket.h" - -void -HomeMovementGenerator::Initialize(Creature & owner) -{ - owner.RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - _setTargetLocation(owner); -} - -void -HomeMovementGenerator::Reset(Creature &) -{ -} - -void -HomeMovementGenerator::_setTargetLocation(Creature & owner) -{ - if( !&owner ) - return; - - if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED | UNIT_STAT_DISTRACTED) ) - return; - - float x, y, z; - owner.GetRespawnCoord(x, y, z); - - CreatureTraveller traveller(owner); - - uint32 travel_time = i_destinationHolder.SetDestination(traveller, x, y, z); - modifyTravelTime(travel_time); - owner.clearUnitState(UNIT_STAT_ALL_STATE); -} - -bool -HomeMovementGenerator::Update(Creature &owner, const uint32& time_diff) -{ - CreatureTraveller traveller( owner); - i_destinationHolder.UpdateTraveller(traveller, time_diff, false); - - if (time_diff > i_travel_timer) - { - owner.AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - - // restore orientation of not moving creature at returning to home - if(owner.GetDefaultMovementType()==IDLE_MOTION_TYPE) - { - if(CreatureData const* data = objmgr.GetCreatureData(owner.GetDBTableGUIDLow())) - { - owner.SetOrientation(data->orientation); - WorldPacket packet; - owner.BuildHeartBeatMsg(&packet); - owner.SendMessageToSet(&packet, false); - } - } - return false; - } - - i_travel_timer -= time_diff; - - return true; -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "HomeMovementGenerator.h" +#include "Creature.h" +#include "Traveller.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "DestinationHolderImp.h" +#include "ObjectMgr.h" +#include "WorldPacket.h" + +void +HomeMovementGenerator::Initialize(Creature & owner) +{ + owner.RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + _setTargetLocation(owner); +} + +void +HomeMovementGenerator::Reset(Creature &) +{ +} + +void +HomeMovementGenerator::_setTargetLocation(Creature & owner) +{ + if( !&owner ) + return; + + if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED | UNIT_STAT_DISTRACTED) ) + return; + + float x, y, z; + owner.GetRespawnCoord(x, y, z); + + CreatureTraveller traveller(owner); + + uint32 travel_time = i_destinationHolder.SetDestination(traveller, x, y, z); + modifyTravelTime(travel_time); + owner.clearUnitState(UNIT_STAT_ALL_STATE); +} + +bool +HomeMovementGenerator::Update(Creature &owner, const uint32& time_diff) +{ + CreatureTraveller traveller( owner); + i_destinationHolder.UpdateTraveller(traveller, time_diff, false); + + if (time_diff > i_travel_timer) + { + owner.AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + + // restore orientation of not moving creature at returning to home + if(owner.GetDefaultMovementType()==IDLE_MOTION_TYPE) + { + if(CreatureData const* data = objmgr.GetCreatureData(owner.GetDBTableGUIDLow())) + { + owner.SetOrientation(data->orientation); + WorldPacket packet; + owner.BuildHeartBeatMsg(&packet); + owner.SendMessageToSet(&packet, false); + } + } + return false; + } + + i_travel_timer -= time_diff; + + return true; +} diff --git a/src/game/Language.h b/src/game/Language.h index dcd76059e2c..fc4f4039e51 100644 --- a/src/game/Language.h +++ b/src/game/Language.h @@ -75,7 +75,9 @@ enum MangosStrings LANG_LEVEL_MINREQUIRED = 49, LANG_LEVEL_MINREQUIRED_AND_ITEM = 50, LANG_NPC_TAINER_HELLO = 51, - // Room for more level 0 + LANG_COMMAND_INVALID_ITEM_COUNT = 52, + LANG_COMMAND_MAIL_ITEMS_LIMIT = 53, + // Room for more level 0 54-99 not used // level 1 chat LANG_GLOBAL_NOTIFY = 100, @@ -159,7 +161,7 @@ enum MangosStrings LANG_MAIL_SENT = 169, LANG_SOUND_NOT_EXIST = 170, - // Room for more level 1 + // Room for more level 1 171-199 not used // level 2 chat LANG_NO_SELECTION = 200, @@ -304,8 +306,11 @@ enum MangosStrings LANG_LOOKUP_PLAYER_CHARACTER = 329, LANG_NO_PLAYERS_FOUND = 330, LANG_EXTENDED_COST_NOT_EXIST = 331, - - // Room for more level 2 + LANG_GM_ON = 332, + LANG_GM_OFF = 333, + LANG_GM_CHAT_ON = 334, + LANG_GM_CHAT_OFF = 335, + // Room for more level 2 336-399 not used // level 3 chat LANG_SCRIPTS_RELOADED = 400, @@ -610,79 +615,49 @@ enum MangosStrings LANG_BG_GROUP_TOO_LARGE = 711, LANG_ARENA_GROUP_TOO_LARGE = 712, - LANG_ARENA_YOUR_TEAM_ONLY = 713, - LANG_ARENA_NOT_ENOUGH_PLAYERS = 714, - LANG_ARENA_GOLD_WINS = 715, - LANG_ARENA_GREEN_WINS = 716, - LANG_BATTLEGROUND_PREMATURE_FINISH_WARNING = 717, - LANG_BG_GROUP_OFFLINE_MEMBER = 718, - LANG_BG_GROUP_MIXED_FACTION = 719, - LANG_BG_GROUP_MIXED_LEVELS = 720, - LANG_BG_GROUP_MEMBER_ALREADY_IN_QUEUE = 721, - LANG_BG_GROUP_MEMBER_DESERTER = 722, - LANG_BG_GROUP_MEMBER_NO_FREE_QUEUE_SLOTS = 723, + LANG_YOUR_ARENA_LEVEL_REQ_ERROR = 713, + LANG_HIS_ARENA_LEVEL_REQ_ERROR = 714, + LANG_YOUR_BG_LEVEL_REQ_ERROR = 715, + LANG_YOUR_ARENA_TEAM_FULL = 716, + // Room for BG/ARENA 717-799 not used - LANG_CANNOT_TELE_TO_BG = 724, - LANG_CANNOT_SUMMON_TO_BG = 725, - LANG_CANNOT_GO_TO_BG_GM = 726, - LANG_CANNOT_GO_TO_BG_FROM_BG = 727, + LANG_ARENA_YOUR_TEAM_ONLY = 730, + LANG_ARENA_NOT_ENOUGH_PLAYERS = 731, + LANG_ARENA_GOLD_WINS = 732, + LANG_ARENA_GREEN_WINS = 733, + LANG_BATTLEGROUND_PREMATURE_FINISH_WARNING = 734, + LANG_BG_GROUP_OFFLINE_MEMBER = 735, + LANG_BG_GROUP_MIXED_FACTION = 736, + LANG_BG_GROUP_MIXED_LEVELS = 737, + LANG_BG_GROUP_MEMBER_ALREADY_IN_QUEUE = 738, + LANG_BG_GROUP_MEMBER_DESERTER = 739, + LANG_BG_GROUP_MEMBER_NO_FREE_QUEUE_SLOTS = 740, - LANG_ARENA_TESTING = 728 + LANG_CANNOT_TELE_TO_BG = 741, + LANG_CANNOT_SUMMON_TO_BG = 742, + LANG_CANNOT_GO_TO_BG_GM = 743, + LANG_CANNOT_GO_TO_BG_FROM_BG = 744, + LANG_ARENA_TESTING = 745, + + // in game strings + LANG_PET_INVALID_NAME = 800, + LANG_NOT_ENOUGH_GOLD = 801, + LANG_NOT_FREE_TRADE_SLOTS = 802, + LANG_NOT_PARTNER_FREE_TRADE_SLOTS = 803, + LANG_YOU_NOT_HAVE_PERMISSION = 804, + LANG_UNKNOWN_LANGUAGE = 805, + LANG_NOT_LEARNED_LANGUAGE = 806, + LANG_NEED_CHARACTER_NAME = 807, + LANG_PLAYER_NOT_EXIST_OR_OFFLINE = 808, + LANG_ACCOUNT_FOR_PLAYER_NOT_FOUND = 809, + // Room for in-game strings 810-999 not used + + // FREE IDS 1000-9999 + + // Use for not-in-svn patches 10000-10999 + // Use for custom patches 11000-11999 + + // NOT RESERVED IDS 12000- }; #endif - -/* NOT USED VALUES -// alliance ranks -#define LANG_ALI_PRIVATE "Private " -#define LANG_ALI_CORPORAL "Corporal " -#define LANG_ALI_SERGEANT "Sergeant " -#define LANG_ALI_MASTER_SERGEANT "Master Sergeant " -#define LANG_ALI_SERGEANT_MAJOR "Sergeant Major " -#define LANG_ALI_KNIGHT "Knight " -#define LANG_ALI_KNIGHT_LIEUTENANT "Knight-Lieutenant " -#define LANG_ALI_KNIGHT_CAPTAIN "Knight-Captain " -#define LANG_ALI_KNIGHT_CHAMPION "Knight-Champion " -#define LANG_ALI_LIEUTENANT_COMMANDER "Lieutenant Commander " -#define LANG_ALI_COMMANDER "Commander " -#define LANG_ALI_MARSHAL "Marshal " -#define LANG_ALI_FIELD_MARSHAL "Field Marshal " -#define LANG_ALI_GRAND_MARSHAL "Grand Marshal " -#define LANG_ALI_GAME_MASTER "Game Master " - -// horde ranks -#define LANG_HRD_SCOUT "Scout " -#define LANG_HRD_GRUNT "Grunt " -#define LANG_HRD_SERGEANT "Sergeant " -#define LANG_HRD_SENIOR_SERGEANT "Senior Sergeant " -#define LANG_HRD_FIRST_SERGEANT "First Sergeant " -#define LANG_HRD_STONE_GUARD "Stone Guard " -#define LANG_HRD_BLOOD_GUARD "Blood Guard " -#define LANG_HRD_LEGIONNARE "Legionnaire " -#define LANG_HRD_CENTURION "Centurion " -#define LANG_HRD_CHAMPION "Champion " -#define LANG_HRD_LIEUTENANT_GENERAL "Lieutenant General " -#define LANG_HRD_GENERAL "General " -#define LANG_HRD_WARLORD "Warlord " -#define LANG_HRD_HIGH_WARLORD "High Warlord " -#define LANG_HRD_GAME_MASTER "Game Master " - -#define LANG_NO_RANK "No rank " -#define LANG_RANK "%s (Rank %u)" -#define LANG_HONOR_TODAY "Today: [Honorable kills: |c0000ff00%u|r] [Dishonorable kills: |c00ff0000%u|r]" -#define LANG_HONOR_YESTERDAY "Yesterday: [Kills: |c0000ff00%u|r] [Honor: %u]" -#define LANG_HONOR_THIS_WEEK "This week: [Kills: |c0000ff00%u|r] [Honor: %u]" -#define LANG_HONOR_LAST_WEEK "Last week: [Kills: |c0000ff00%u|r] [Honor: %u] [Standing: %u]" -#define LANG_HONOR_LIFE "Lifetime: [Honorable kills: |c0000ff00%u|r] [Dishonorable kills: |c00ff0000%u|r] [Highest rank %u: %s]" - -// level 2 -#define LANG_ADD_OBJ "AddObject at Chat.cpp" //log -#define LANG_DEMORPHED "Demorphed %s" //log - -// level 3 -#define LANG_SPAWNING_SPIRIT_HEAL "Spawning spirit healers\n" -#define LANG_NO_SPIRIT_HEAL_DB "No spirit healers in database, exiting." - -#define LANG_ADD_OBJ_LV3 "AddObject at Level3.cpp line 1176" - -*/ diff --git a/src/game/Level1.cpp b/src/game/Level1.cpp index c1bca8b8b40..19dec5500bc 100644 --- a/src/game/Level1.cpp +++ b/src/game/Level1.cpp @@ -147,9 +147,11 @@ bool ChatHandler::HandleGMmodeCommand(const char* args) { if(!*args) { - SendSysMessage(LANG_USE_BOL); - SetSentErrorMessage(true); - return false; + if(m_session->GetPlayer()->isGameMaster()) + m_session->SendNotification(LANG_GM_ON); + else + m_session->SendNotification(LANG_GM_OFF); + return true; } std::string argstr = (char*)args; @@ -157,7 +159,7 @@ bool ChatHandler::HandleGMmodeCommand(const char* args) if (argstr == "on") { m_session->GetPlayer()->SetGameMaster(true); - m_session->SendNotification("GM mode is ON"); + m_session->SendNotification(LANG_GM_ON); #ifdef _DEBUG_VMAPS VMAP::IVMapManager *vMapManager = VMAP::VMapFactory::createOrGetVMapManager(); vMapManager->processCommand("stoplog"); @@ -168,7 +170,7 @@ bool ChatHandler::HandleGMmodeCommand(const char* args) if (argstr == "off") { m_session->GetPlayer()->SetGameMaster(false); - m_session->SendNotification("GM mode is OFF"); + m_session->SendNotification(LANG_GM_OFF); #ifdef _DEBUG_VMAPS VMAP::IVMapManager *vMapManager = VMAP::VMapFactory::createOrGetVMapManager(); vMapManager->processCommand("startlog"); @@ -181,6 +183,40 @@ bool ChatHandler::HandleGMmodeCommand(const char* args) return false; } +// Enables or disables hiding of the staff badge +bool ChatHandler::HandleGMChatCommand(const char* args) +{ + if(!*args) + { + if(m_session->GetPlayer()->isGMChat()) + m_session->SendNotification(LANG_GM_CHAT_ON); + else + m_session->SendNotification(LANG_GM_CHAT_OFF); + return true; + } + + std::string argstr = (char*)args; + + if (argstr == "on") + { + m_session->GetPlayer()->SetGMChat(true); + m_session->SendNotification(LANG_GM_CHAT_ON); + return true; + } + + if (argstr == "off") + { + m_session->GetPlayer()->SetGMChat(false); + m_session->SendNotification(LANG_GM_CHAT_OFF); + return true; + } + + SendSysMessage(LANG_USE_BOL); + SetSentErrorMessage(true); + return false; +} + + //Enable\Dissable Invisible mode bool ChatHandler::HandleVisibleCommand(const char* args) { @@ -195,13 +231,13 @@ bool ChatHandler::HandleVisibleCommand(const char* args) if (argstr == "on") { m_session->GetPlayer()->SetGMVisible(true); - m_session->SendNotification(GetMangosString(LANG_INVISIBLE_VISIBLE)); + m_session->SendNotification(LANG_INVISIBLE_VISIBLE); return true; } if (argstr == "off") { - m_session->SendNotification(GetMangosString(LANG_INVISIBLE_INVISIBLE)); + m_session->SendNotification(LANG_INVISIBLE_INVISIBLE); m_session->GetPlayer()->SetGMVisible(false); return true; } @@ -1823,19 +1859,107 @@ bool ChatHandler::HandleSendMailCommand(const char* args) if(!*args) return false; + // format: name "subject text" "mail text" item1[:count1] item2[:count2] ... item12[:count12] + char* pName = strtok((char*)args, " "); - char* msgSubject = strtok(NULL, " "); - char* msgText = strtok(NULL, ""); + if(!pName) + return false; + + char* tail1 = strtok(NULL, ""); + if(!tail1) + return false; + + char* msgSubject; + if(*tail1=='"') + msgSubject = strtok(tail1+1, "\""); + else + { + char* space = strtok(tail1, "\""); + if(!space) + return false; + msgSubject = strtok(NULL, "\""); + } + + if (!msgSubject) + return false; + + char* tail2 = strtok(NULL, ""); + if(!tail2) + return false; + + char* msgText; + if(*tail2=='"') + msgText = strtok(tail2+1, "\""); + else + { + char* space = strtok(tail2, "\""); + if(!space) + return false; + msgText = strtok(NULL, "\""); + } if (!msgText) return false; // pName, msgSubject, msgText isn't NUL after prev. check - std::string name = pName; std::string subject = msgSubject; std::string text = msgText; + // extract items + typedef std::pair ItemPair; + typedef std::list< ItemPair > ItemPairs; + ItemPairs items; + + // get all tail string + char* tail = strtok(NULL, ""); + + // get from tail next item str + while(char* itemStr = strtok(tail, " ")) + { + // and get new tail + tail = strtok(NULL, ""); + + // parse item str + char* itemIdStr = strtok(itemStr, ":"); + char* itemCountStr = strtok(NULL, " "); + + uint32 item_id = atoi(itemIdStr); + if(!item_id) + return false; + + ItemPrototype const* item_proto = objmgr.GetItemPrototype(item_id); + if(!item_proto) + { + PSendSysMessage(LANG_COMMAND_ITEMIDINVALID, item_id); + SetSentErrorMessage(true); + return false; + } + + uint32 item_count = itemCountStr ? atoi(itemCountStr) : 1; + if(item_count < 1 || item_proto->MaxCount && item_count > item_proto->MaxCount) + { + PSendSysMessage(LANG_COMMAND_INVALID_ITEM_COUNT, item_count,item_id); + SetSentErrorMessage(true); + return false; + } + + while(item_count > item_proto->Stackable) + { + items.push_back(ItemPair(item_id,item_proto->Stackable)); + item_count -= item_proto->Stackable; + } + + items.push_back(ItemPair(item_id,item_count)); + + if(items.size() > MAX_MAIL_ITEMS) + { + PSendSysMessage(LANG_COMMAND_MAIL_ITEMS_LIMIT, MAX_MAIL_ITEMS); + SetSentErrorMessage(true); + return false; + } + } + if(!normalizePlayerName(name)) { SendSysMessage(LANG_PLAYER_NOT_FOUND); @@ -1844,9 +1968,12 @@ bool ChatHandler::HandleSendMailCommand(const char* args) } uint64 receiver_guid = objmgr.GetPlayerGUIDByName(name); - if(!receiver_guid) + { + SendSysMessage(LANG_PLAYER_NOT_FOUND); + SetSentErrorMessage(true); return false; + } uint32 mailId = objmgr.GenerateMailID(); uint32 sender_guidlo = m_session->GetPlayer()->GetGUIDLow(); @@ -1860,7 +1987,19 @@ bool ChatHandler::HandleSendMailCommand(const char* args) Player *receiver = objmgr.GetPlayer(receiver_guid); - WorldSession::SendMailTo(receiver,messagetype, stationery, sender_guidlo, GUID_LOPART(receiver_guid), subject, itemTextId, NULL, 0, 0, MAIL_CHECK_MASK_NONE); + // fill mail + MailItemsInfo mi; // item list preparing + + for(ItemPairs::const_iterator itr = items.begin(); itr != items.end(); ++itr) + { + if(Item* item = Item::CreateItem(itr->first,itr->second,m_session->GetPlayer())) + { + item->SaveToDB(); // save for prevent lost at next mail load, if send fail then item will deleted + mi.AddItem(item->GetGUIDLow(), item->GetEntry(), item); + } + } + + WorldSession::SendMailTo(receiver,messagetype, stationery, sender_guidlo, GUID_LOPART(receiver_guid), subject, itemTextId, &mi, 0, 0, MAIL_CHECK_MASK_NONE); PSendSysMessage(LANG_MAIL_SENT, name.c_str()); return true; diff --git a/src/game/MiscHandler.cpp b/src/game/MiscHandler.cpp index 20d556fb9aa..cb9a7cd673f 100644 --- a/src/game/MiscHandler.cpp +++ b/src/game/MiscHandler.cpp @@ -1,1765 +1,1759 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "Language.h" -#include "Database/DatabaseEnv.h" -#include "WorldPacket.h" -#include "Opcodes.h" -#include "Log.h" -#include "Player.h" -#include "World.h" -#include "ObjectMgr.h" -#include "WorldSession.h" -#include "Auth/BigNumber.h" -#include "Auth/Sha1.h" -#include "UpdateData.h" -#include "LootMgr.h" -#include "Chat.h" -#include "ScriptCalls.h" -#include -#include "MapManager.h" -#include "ObjectAccessor.h" -#include "Object.h" -#include "BattleGround.h" -#include "SpellAuras.h" -#include "Pet.h" -#include "SocialMgr.h" - -void WorldSession::HandleRepopRequestOpcode( WorldPacket & /*recv_data*/ ) -{ - sLog.outDebug( "WORLD: Recvd CMSG_REPOP_REQUEST Message" ); - - if(GetPlayer()->isAlive()||GetPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) - return; - - // the world update order is sessions, players, creatures - // the netcode runs in parallel with all of these - // creatures can kill players - // so if the server is lagging enough the player can - // release spirit after he's killed but before he is updated - if(GetPlayer()->getDeathState() == JUST_DIED) - { - sLog.outDebug("HandleRepopRequestOpcode: got request after player %s(%d) was killed and before he was updated", GetPlayer()->GetName(), GetPlayer()->GetGUIDLow()); - GetPlayer()->KillPlayer(); - } - - //this is spirit release confirm? - GetPlayer()->RemovePet(NULL,PET_SAVE_NOT_IN_SLOT, true); - GetPlayer()->BuildPlayerRepop(); - GetPlayer()->RepopAtGraveyard(); -} - -void WorldSession::HandleWhoOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,4+4+1+1+4+4+4+4); - - sLog.outDebug( "WORLD: Recvd CMSG_WHO Message" ); - //recv_data.hexlike(); - - uint32 clientcount = 0; - - uint32 level_min, level_max, racemask, classmask, zones_count, str_count; - uint32 zoneids[10]; // 10 is client limit - std::string player_name, guild_name; - - recv_data >> level_min; // maximal player level, default 0 - recv_data >> level_max; // minimal player level, default 100 - recv_data >> player_name; // player name, case sensitive... - - // recheck - CHECK_PACKET_SIZE(recv_data,4+4+(player_name.size()+1)+1+4+4+4+4); - - recv_data >> guild_name; // guild name, case sensitive... - - // recheck - CHECK_PACKET_SIZE(recv_data,4+4+(player_name.size()+1)+(guild_name.size()+1)+4+4+4+4); - - recv_data >> racemask; // race mask - recv_data >> classmask; // class mask - recv_data >> zones_count; // zones count, client limit=10 (2.0.10) - - if(zones_count > 10) - return; // can't be received from real client or broken packet - - // recheck - CHECK_PACKET_SIZE(recv_data,4+4+(player_name.size()+1)+(guild_name.size()+1)+4+4+4+(4*zones_count)+4); - - for(uint32 i = 0; i < zones_count; i++) - { - uint32 temp; - recv_data >> temp; // zone id, 0 if zone is unknown... - zoneids[i] = temp; - sLog.outDebug("Zone %u: %u", i, zoneids[i]); - } - - recv_data >> str_count; // user entered strings count, client limit=4 (checked on 2.0.10) - - if(str_count > 4) - return; // can't be received from real client or broken packet - - // recheck - CHECK_PACKET_SIZE(recv_data,4+4+(player_name.size()+1)+(guild_name.size()+1)+4+4+4+(4*zones_count)+4+(1*str_count)); - - sLog.outDebug("Minlvl %u, maxlvl %u, name %s, guild %s, racemask %u, classmask %u, zones %u, strings %u", level_min, level_max, player_name.c_str(), guild_name.c_str(), racemask, classmask, zones_count, str_count); - - std::wstring str[4]; // 4 is client limit - for(uint32 i = 0; i < str_count; i++) - { - // recheck (have one more byte) - CHECK_PACKET_SIZE(recv_data,recv_data.rpos()); - - std::string temp; - recv_data >> temp; // user entered string, it used as universal search pattern(guild+player name)? - - if(!Utf8toWStr(temp,str[i])) - continue; - - wstrToLower(str[i]); - - sLog.outDebug("String %u: %s", i, str[i].c_str()); - } - - std::wstring wplayer_name; - std::wstring wguild_name; - if(!(Utf8toWStr(player_name, wplayer_name) && Utf8toWStr(guild_name, wguild_name))) - return; - wstrToLower(wplayer_name); - wstrToLower(wguild_name); - - // client send in case not set max level value 100 but mangos support 255 max level, - // update it to show GMs with characters after 100 level - if(level_max >= 100) - level_max = 255; - - uint32 team = _player->GetTeam(); - uint32 security = GetSecurity(); - bool allowTwoSideWhoList = sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_WHO_LIST); - bool gmInWhoList = sWorld.getConfig(CONFIG_GM_IN_WHO_LIST); - - WorldPacket data( SMSG_WHO, 50 ); // guess size - data << clientcount; // clientcount place holder - data << clientcount; // clientcount place holder - - //TODO: Guard Player map - HashMapHolder::MapType& m = ObjectAccessor::Instance().GetPlayers(); - for(HashMapHolder::MapType::iterator itr = m.begin(); itr != m.end(); ++itr) - { - if (security == SEC_PLAYER) - { - // player can see member of other team only if CONFIG_ALLOW_TWO_SIDE_WHO_LIST - if (itr->second->GetTeam() != team && !allowTwoSideWhoList ) - continue; - - // player can see MODERATOR, GAME MASTER, ADMINISTRATOR only if CONFIG_GM_IN_WHO_LIST - if ((itr->second->GetSession()->GetSecurity() > SEC_PLAYER && !gmInWhoList)) - continue; - } - - // check if target is globally visible for player - if (!(itr->second->IsVisibleGloballyFor(_player))) - continue; - - // check if target's level is in level range - uint32 lvl = itr->second->getLevel(); - if (lvl < level_min || lvl > level_max) - continue; - - // check if class matches classmask - uint32 class_ = itr->second->getClass(); - if (!(classmask & (1 << class_))) - continue; - - // check if race matches racemask - uint32 race = itr->second->getRace(); - if (!(racemask & (1 << race))) - continue; - - uint32 pzoneid = itr->second->GetZoneId(); - - bool z_show = true; - for(uint32 i = 0; i < zones_count; i++) - { - if(zoneids[i] == pzoneid) - { - z_show = true; - break; - } - - z_show = false; - } - if (!z_show) - continue; - - std::string pname = itr->second->GetName(); - std::wstring wpname; - if(!Utf8toWStr(pname,wpname)) - continue; - wstrToLower(wpname); - - if (!(wplayer_name.empty() || wpname.find(wplayer_name) != std::wstring::npos)) - continue; - - std::string gname = objmgr.GetGuildNameById(itr->second->GetGuildId()); - std::wstring wgname; - if(!Utf8toWStr(gname,wgname)) - continue; - wstrToLower(wgname); - - if (!(wguild_name.empty() || wgname.find(wguild_name) != std::wstring::npos)) - continue; - - std::string aname; - if(AreaTableEntry const* areaEntry = GetAreaEntryByAreaID(itr->second->GetZoneId())) - aname = areaEntry->area_name[GetSessionDbcLocale()]; - - bool s_show = true; - for(uint32 i = 0; i < str_count; i++) - { - if (!str[i].empty()) - { - if (wgname.find(str[i]) != std::wstring::npos || - wpname.find(str[i]) != std::wstring::npos || - Utf8FitTo(aname, str[i]) ) - { - s_show = true; - break; - } - s_show = false; - } - } - if (!s_show) - continue; - - data << pname; // player name - data << gname; // guild name - data << uint32( lvl ); // player level - data << uint32( class_ ); // player class - data << uint32( race ); // player race - data << uint8(0); // new 2.4.0 - data << uint32( pzoneid ); // player zone id - - // 49 is maximum player count sent to client - if ((++clientcount) == 49) - break; - } - - data.put( 0, clientcount ); //insert right count - data.put( sizeof(uint32), clientcount ); //insert right count - - SendPacket(&data); - sLog.outDebug( "WORLD: Send SMSG_WHO Message" ); -} - -void WorldSession::HandleLogoutRequestOpcode( WorldPacket & /*recv_data*/ ) -{ - sLog.outDebug( "WORLD: Recvd CMSG_LOGOUT_REQUEST Message, security - %u", GetSecurity() ); - - if (uint64 lguid = GetPlayer()->GetLootGUID()) - DoLootRelease(lguid); - - //instant logout for admins, gm's, mod's - if( GetSecurity() > SEC_PLAYER ) - { - LogoutPlayer(true); - return; - } - - //Can not logout if... - if( GetPlayer()->isInCombat() || //...is in combat - GetPlayer()->duel || //...is in Duel - //...is jumping ...is falling - GetPlayer()->HasUnitMovementFlag(MOVEMENTFLAG_JUMPING | MOVEMENTFLAG_FALLING)) - { - WorldPacket data( SMSG_LOGOUT_RESPONSE, (2+4) ) ; - data << (uint8)0xC; - data << uint32(0); - data << uint8(0); - SendPacket( &data ); - LogoutRequest(0); - return; - } - - //instant logout in taverns/cities or on taxi - if(GetPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || GetPlayer()->isInFlight()) - { - LogoutPlayer(true); - return; - } - - // not set flags if player can't free move to prevent lost state at logout cancel - if(GetPlayer()->CanFreeMove()) - { - GetPlayer()->SetStandState(PLAYER_STATE_SIT); - - WorldPacket data( SMSG_FORCE_MOVE_ROOT, (8+4) ); // guess size - data.append(GetPlayer()->GetPackGUID()); - data << (uint32)2; - SendPacket( &data ); - GetPlayer()->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_ROTATE); - } - - WorldPacket data( SMSG_LOGOUT_RESPONSE, 5 ); - data << uint32(0); - data << uint8(0); - SendPacket( &data ); - LogoutRequest(time(NULL)); -} - -void WorldSession::HandlePlayerLogoutOpcode( WorldPacket & /*recv_data*/ ) -{ - sLog.outDebug( "WORLD: Recvd CMSG_PLAYER_LOGOUT Message" ); -} - -void WorldSession::HandleLogoutCancelOpcode( WorldPacket & /*recv_data*/ ) -{ - sLog.outDebug( "WORLD: Recvd CMSG_LOGOUT_CANCEL Message" ); - - LogoutRequest(0); - - WorldPacket data( SMSG_LOGOUT_CANCEL_ACK, 0 ); - SendPacket( &data ); - - // not remove flags if can't free move - its not set in Logout request code. - if(GetPlayer()->CanFreeMove()) - { - //!we can move again - data.Initialize( SMSG_FORCE_MOVE_UNROOT, 8 ); // guess size - data.append(GetPlayer()->GetPackGUID()); - data << uint32(0); - SendPacket( &data ); - - //! Stand Up - GetPlayer()->SetStandState(PLAYER_STATE_NONE); - - //! DISABLE_ROTATE - GetPlayer()->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_ROTATE); - } - - sLog.outDebug( "WORLD: sent SMSG_LOGOUT_CANCEL_ACK Message" ); -} - -void WorldSession::SendGMTicketGetTicket(uint32 status, char const* text) -{ - int len = text ? strlen(text) : 0; - WorldPacket data( SMSG_GMTICKET_GETTICKET, (4+len+1+4+2+4+4) ); - data << uint32(status); // standard 0x0A, 0x06 if text present - if(status == 6) - { - data << text; // ticket text - data << uint8(0x7); // ticket category - data << float(0); // time from ticket creation? - data << float(0); // const - data << float(0); // const - data << uint8(0); // const - data << uint8(0); // const - } - SendPacket( &data ); -} - -void WorldSession::HandleGMTicketGetTicketOpcode( WorldPacket & /*recv_data*/ ) -{ - WorldPacket data( SMSG_QUERY_TIME_RESPONSE, 4+4 ); - data << (uint32)time(NULL); - data << (uint32)0; - SendPacket( &data ); - - uint64 guid; - Field *fields; - guid = GetPlayer()->GetGUID(); - - QueryResult *result = CharacterDatabase.PQuery("SELECT COUNT(ticket_id) FROM character_ticket WHERE guid = '%u'", GUID_LOPART(guid)); - - if (result) - { - int cnt; - fields = result->Fetch(); - cnt = fields[0].GetUInt32(); - delete result; - - if ( cnt > 0 ) - { - QueryResult *result2 = CharacterDatabase.PQuery("SELECT ticket_text FROM character_ticket WHERE guid = '%u'", GUID_LOPART(guid)); - if(result2) - { - Field *fields2 = result2->Fetch(); - SendGMTicketGetTicket(0x06,fields2[0].GetString()); - delete result2; - } - } - else - SendGMTicketGetTicket(0x0A,0); - } -} - -void WorldSession::HandleGMTicketUpdateTextOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,1); - - std::string ticketText; - recv_data >> ticketText; - - CharacterDatabase.escape_string(ticketText); - CharacterDatabase.PExecute("UPDATE character_ticket SET ticket_text = '%s' WHERE guid = '%u'", ticketText.c_str(), _player->GetGUIDLow()); -} - -void WorldSession::HandleGMTicketDeleteOpcode( WorldPacket & /*recv_data*/ ) -{ - uint32 guid = GetPlayer()->GetGUIDLow(); - - CharacterDatabase.PExecute("DELETE FROM character_ticket WHERE guid = '%u' LIMIT 1",guid); - - WorldPacket data( SMSG_GMTICKET_DELETETICKET, 4 ); - data << uint32(9); - SendPacket( &data ); - - SendGMTicketGetTicket(0x0A, 0); -} - -void WorldSession::HandleGMTicketCreateOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 4*4+1+2*4); - - uint32 map; - float x, y, z; - std::string ticketText = ""; - uint32 unk1, unk2; - - recv_data >> map >> x >> y >> z; // last check 2.4.3 - recv_data >> ticketText; - - // recheck - CHECK_PACKET_SIZE(recv_data,4*4+(ticketText.size()+1)+2*4); - - recv_data >> unk1 >> unk2; - // note: the packet might contain more data, but the exact structure of that is unknown - - sLog.outDebug("TicketCreate: map %u, x %f, y %f, z %f, text %s, unk1 %u, unk2 %u", map, x, y, z, ticketText.c_str(), unk1, unk2); - - CharacterDatabase.escape_string(ticketText); - - QueryResult *result = CharacterDatabase.PQuery("SELECT COUNT(*) FROM character_ticket WHERE guid = '%u'", _player->GetGUIDLow()); - - if (result) - { - int cnt; - Field *fields = result->Fetch(); - cnt = fields[0].GetUInt32(); - delete result; - - if ( cnt > 0 ) - { - WorldPacket data( SMSG_GMTICKET_CREATE, 4 ); - data << uint32(1); - SendPacket( &data ); - } - else - { - CharacterDatabase.PExecute("INSERT INTO character_ticket (guid,ticket_text) VALUES ('%u', '%s')", _player->GetGUIDLow(), ticketText.c_str()); - - WorldPacket data( SMSG_QUERY_TIME_RESPONSE, 4+4 ); - data << (uint32)time(NULL); - data << (uint32)0; - SendPacket( &data ); - - data.Initialize( SMSG_GMTICKET_CREATE, 4 ); - data << uint32(2); - SendPacket( &data ); - DEBUG_LOG("update the ticket\n"); - - //TODO: Guard player map - HashMapHolder::MapType &m = ObjectAccessor::Instance().GetPlayers(); - for(HashMapHolder::MapType::iterator itr = m.begin(); itr != m.end(); ++itr) - { - if(itr->second->GetSession()->GetSecurity() >= SEC_GAMEMASTER && itr->second->isAcceptTickets()) - ChatHandler(itr->second).PSendSysMessage(LANG_COMMAND_TICKETNEW,GetPlayer()->GetName()); - } - } - } -} - -void WorldSession::HandleGMTicketSystemStatusOpcode( WorldPacket & /*recv_data*/ ) -{ - WorldPacket data( SMSG_GMTICKET_SYSTEMSTATUS,4 ); - data << uint32(1); // we can also disable ticket system by sending 0 value - - SendPacket( &data ); -} - -void WorldSession::HandleGMSurveySubmit( WorldPacket & recv_data) -{ - // GM survey is shown after SMSG_GM_TICKET_STATUS_UPDATE with status = 3 - CHECK_PACKET_SIZE(recv_data,4+4); - uint32 x; - recv_data >> x; // answer range? (6 = 0-5?) - sLog.outDebug("SURVEY: X = %u", x); - - uint8 result[10]; - memset(result, 0, sizeof(result)); - for( int i = 0; i < 10; ++i) - { - CHECK_PACKET_SIZE(recv_data,recv_data.rpos()+4); - uint32 questionID; - recv_data >> questionID; // GMSurveyQuestions.dbc - if (!questionID) - break; - - CHECK_PACKET_SIZE(recv_data,recv_data.rpos()+1+1); - uint8 value; - std::string unk_text; - recv_data >> value; // answer - recv_data >> unk_text; // always empty? - - result[i] = value; - sLog.outDebug("SURVEY: ID %u, value %u, text %s", questionID, value, unk_text.c_str()); - } - - CHECK_PACKET_SIZE(recv_data,recv_data.rpos()+1); - std::string comment; - recv_data >> comment; // addional comment - sLog.outDebug("SURVEY: comment %s", comment.c_str()); - - // TODO: chart this data in some way -} - -void WorldSession::HandleTogglePvP( WorldPacket & recv_data ) -{ - // this opcode can be used in two ways: Either set explicit new status or toggle old status - if(recv_data.size() == 1) - { - bool newPvPStatus; - recv_data >> newPvPStatus; - GetPlayer()->ApplyModFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP, newPvPStatus); - } - else - { - GetPlayer()->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP); - } - - if(GetPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP)) - { - if(!GetPlayer()->IsPvP() || GetPlayer()->pvpInfo.endTimer != 0) - GetPlayer()->UpdatePvP(true, true); - } - else - { - if(!GetPlayer()->pvpInfo.inHostileArea && GetPlayer()->IsPvP()) - GetPlayer()->pvpInfo.endTimer = time(NULL); // start toggle-off - } -} - -void WorldSession::HandleZoneUpdateOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,4); - - uint32 newZone; - recv_data >> newZone; - - sLog.outDetail("WORLD: Recvd ZONE_UPDATE: %u", newZone); - - if(newZone != _player->GetZoneId()) - GetPlayer()->SendInitWorldStates(); // only if really enters to new zone, not just area change, works strange... - - GetPlayer()->UpdateZone(newZone); -} - -void WorldSession::HandleSetTargetOpcode( WorldPacket & recv_data ) -{ - // When this packet send? - CHECK_PACKET_SIZE(recv_data,8); - - uint64 guid ; - recv_data >> guid; - - _player->SetUInt32Value(UNIT_FIELD_TARGET,guid); - - // update reputation list if need - Unit* unit = ObjectAccessor::GetUnit(*_player, guid ); - if(!unit) - return; - - _player->SetFactionVisibleForFactionTemplateId(unit->getFaction()); -} - -void WorldSession::HandleSetSelectionOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,8); - - uint64 guid; - recv_data >> guid; - - _player->SetSelection(guid); - - // update reputation list if need - Unit* unit = ObjectAccessor::GetUnit(*_player, guid ); - if(!unit) - return; - - _player->SetFactionVisibleForFactionTemplateId(unit->getFaction()); -} - -void WorldSession::HandleStandStateChangeOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,1); - - sLog.outDebug( "WORLD: Received CMSG_STAND_STATE_CHANGE" ); - uint8 animstate; - recv_data >> animstate; - - _player->SetStandState(animstate); -} - -void WorldSession::HandleFriendListOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 4); - sLog.outDebug( "WORLD: Received CMSG_CONTACT_LIST" ); - uint32 unk; - recv_data >> unk; - sLog.outDebug("unk value is %u", unk); - _player->GetSocial()->SendSocialList(); -} - -void WorldSession::HandleAddFriendOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 1+1); - - sLog.outDebug( "WORLD: Received CMSG_ADD_FRIEND" ); - - std::string friendName = GetMangosString(LANG_FRIEND_IGNORE_UNKNOWN); - std::string friendNote; - FriendsResult friendResult = FRIEND_NOT_FOUND; - Player *pFriend = NULL; - uint64 friendGuid = 0; - - recv_data >> friendName; - - // recheck - CHECK_PACKET_SIZE(recv_data, (friendName.size()+1)+1); - - recv_data >> friendNote; - - if(!normalizePlayerName(friendName)) - return; - - CharacterDatabase.escape_string(friendName); // prevent SQL injection - normal name don't must changed by this call - - sLog.outDebug( "WORLD: %s asked to add friend : '%s'", - GetPlayer()->GetName(), friendName.c_str() ); - - friendGuid = objmgr.GetPlayerGUIDByName(friendName); - - if(friendGuid) - { - pFriend = ObjectAccessor::FindPlayer(friendGuid); - if(pFriend==GetPlayer()) - friendResult = FRIEND_SELF; - else if(GetPlayer()->GetTeam()!=objmgr.GetPlayerTeamByGUID(friendGuid) && !sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_ADD_FRIEND) && GetSecurity() < SEC_MODERATOR) - friendResult = FRIEND_ENEMY; - else if(GetPlayer()->GetSocial()->HasFriend(GUID_LOPART(friendGuid))) - friendResult = FRIEND_ALREADY; - } - - if (friendGuid && friendResult==FRIEND_NOT_FOUND) - { - if( pFriend && pFriend->IsInWorld() && pFriend->IsVisibleGloballyFor(GetPlayer())) - friendResult = FRIEND_ADDED_ONLINE; - else - friendResult = FRIEND_ADDED_OFFLINE; - - if(!_player->GetSocial()->AddToSocialList(GUID_LOPART(friendGuid), false)) - { - friendResult = FRIEND_LIST_FULL; - sLog.outDebug( "WORLD: %s's friend list is full.", GetPlayer()->GetName()); - } - - _player->GetSocial()->SetFriendNote(GUID_LOPART(friendGuid), friendNote); - - sLog.outDebug( "WORLD: %s Guid found '%u'.", friendName.c_str(), GUID_LOPART(friendGuid)); - } - else if(friendResult==FRIEND_ALREADY) - { - sLog.outDebug( "WORLD: %s Guid Already a Friend.", friendName.c_str() ); - } - else if(friendResult==FRIEND_SELF) - { - sLog.outDebug( "WORLD: %s Guid can't add himself.", friendName.c_str() ); - } - else - { - sLog.outDebug( "WORLD: %s Guid not found.", friendName.c_str() ); - } - - sSocialMgr.SendFriendStatus(GetPlayer(), friendResult, GUID_LOPART(friendGuid), friendName, false); - - sLog.outDebug( "WORLD: Sent (SMSG_FRIEND_STATUS)" ); -} - -void WorldSession::HandleDelFriendOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - uint64 FriendGUID; - - sLog.outDebug( "WORLD: Received CMSG_DEL_FRIEND" ); - - recv_data >> FriendGUID; - - _player->GetSocial()->RemoveFromSocialList(GUID_LOPART(FriendGUID), false); - - sSocialMgr.SendFriendStatus(GetPlayer(), FRIEND_REMOVED, GUID_LOPART(FriendGUID), "", false); - - sLog.outDebug( "WORLD: Sent motd (SMSG_FRIEND_STATUS)" ); -} - -void WorldSession::HandleAddIgnoreOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,1); - - sLog.outDebug( "WORLD: Received CMSG_ADD_IGNORE" ); - - std::string IgnoreName = GetMangosString(LANG_FRIEND_IGNORE_UNKNOWN); - FriendsResult ignoreResult = FRIEND_IGNORE_NOT_FOUND; - uint64 IgnoreGuid = 0; - - recv_data >> IgnoreName; - - if(!normalizePlayerName(IgnoreName)) - return; - - CharacterDatabase.escape_string(IgnoreName); // prevent SQL injection - normal name don't must changed by this call - - sLog.outDebug( "WORLD: %s asked to Ignore: '%s'", - GetPlayer()->GetName(), IgnoreName.c_str() ); - - IgnoreGuid = objmgr.GetPlayerGUIDByName(IgnoreName); - - if(IgnoreGuid) - { - if(IgnoreGuid==GetPlayer()->GetGUID()) - ignoreResult = FRIEND_IGNORE_SELF; - else - { - if( GetPlayer()->GetSocial()->HasIgnore(GUID_LOPART(IgnoreGuid)) ) - ignoreResult = FRIEND_IGNORE_ALREADY; - } - } - - if (IgnoreGuid && ignoreResult == FRIEND_IGNORE_NOT_FOUND) - { - ignoreResult = FRIEND_IGNORE_ADDED; - - _player->GetSocial()->AddToSocialList(GUID_LOPART(IgnoreGuid), true); - } - else if(ignoreResult==FRIEND_IGNORE_ALREADY) - { - sLog.outDebug( "WORLD: %s Guid Already Ignored.", IgnoreName.c_str() ); - } - else if(ignoreResult==FRIEND_IGNORE_SELF) - { - sLog.outDebug( "WORLD: %s Guid can't add himself.", IgnoreName.c_str() ); - } - else - { - sLog.outDebug( "WORLD: %s Guid not found.", IgnoreName.c_str() ); - } - - sSocialMgr.SendFriendStatus(GetPlayer(), ignoreResult, GUID_LOPART(IgnoreGuid), "", false); - - sLog.outDebug( "WORLD: Sent (SMSG_FRIEND_STATUS)" ); -} - -void WorldSession::HandleDelIgnoreOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - uint64 IgnoreGUID; - - sLog.outDebug( "WORLD: Received CMSG_DEL_IGNORE" ); - - recv_data >> IgnoreGUID; - - _player->GetSocial()->RemoveFromSocialList(GUID_LOPART(IgnoreGUID), true); - - sSocialMgr.SendFriendStatus(GetPlayer(), FRIEND_IGNORE_REMOVED, GUID_LOPART(IgnoreGUID), "", false); - - sLog.outDebug( "WORLD: Sent motd (SMSG_FRIEND_STATUS)" ); -} - -void WorldSession::HandleSetFriendNoteOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 8+1); - uint64 guid; - std::string note; - recv_data >> guid >> note; - _player->GetSocial()->SetFriendNote(guid, note); -} - -void WorldSession::HandleBugOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,4+4+1+4+1); - - uint32 suggestion, contentlen; - std::string content; - uint32 typelen; - std::string type; - - recv_data >> suggestion >> contentlen >> content; - - //recheck - CHECK_PACKET_SIZE(recv_data,4+4+(content.size()+1)+4+1); - - recv_data >> typelen >> type; - - if( suggestion == 0 ) - sLog.outDebug( "WORLD: Received CMSG_BUG [Bug Report]" ); - else - sLog.outDebug( "WORLD: Received CMSG_BUG [Suggestion]" ); - - sLog.outDebug( type.c_str( ) ); - sLog.outDebug( content.c_str( ) ); - - CharacterDatabase.escape_string(type); - CharacterDatabase.escape_string(content); - CharacterDatabase.PExecute ("INSERT INTO bugreport (type,content) VALUES('%s', '%s')", type.c_str( ), content.c_str( )); -} - -void WorldSession::HandleCorpseReclaimOpcode(WorldPacket &recv_data) -{ - CHECK_PACKET_SIZE(recv_data,8); - - sLog.outDetail("WORLD: Received CMSG_RECLAIM_CORPSE"); - if (GetPlayer()->isAlive()) - return; - - if (BattleGround * bg = _player->GetBattleGround()) - if(bg->isArena()) - return; - - // body not released yet - if(!GetPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) - return; - - Corpse *corpse = GetPlayer()->GetCorpse(); - - if (!corpse ) - return; - - // prevent resurrect before 30-sec delay after body release not finished - if(corpse->GetGhostTime() + GetPlayer()->GetCorpseReclaimDelay(corpse->GetType()==CORPSE_RESURRECTABLE_PVP) > time(NULL)) - return; - - float dist = corpse->GetDistance2d(GetPlayer()); - sLog.outDebug("Corpse 2D Distance: \t%f",dist); - if (dist > CORPSE_RECLAIM_RADIUS) - return; - - uint64 guid; - recv_data >> guid; - - // resurrect - GetPlayer()->ResurrectPlayer(GetPlayer()->InBattleGround() ? 1.0f : 0.5f); - - // spawn bones - GetPlayer()->SpawnCorpseBones(); - - GetPlayer()->SaveToDB(); -} - -void WorldSession::HandleResurrectResponseOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data,8+1); - - sLog.outDetail("WORLD: Received CMSG_RESURRECT_RESPONSE"); - - if(GetPlayer()->isAlive()) - return; - - uint64 guid; - uint8 status; - recv_data >> guid; - recv_data >> status; - - if(status == 0) - { - GetPlayer()->clearResurrectRequestData(); // reject - return; - } - - if(!GetPlayer()->isRessurectRequestedBy(guid)) - return; - - GetPlayer()->ResurectUsingRequestData(); - GetPlayer()->SaveToDB(); -} - -void WorldSession::HandleAreaTriggerOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data,4); - - sLog.outDebug("WORLD: Received CMSG_AREATRIGGER"); - - uint32 Trigger_ID; - - recv_data >> Trigger_ID; - sLog.outDebug("Trigger ID:%u",Trigger_ID); - - if(GetPlayer()->isInFlight()) - { - sLog.outDebug("Player '%s' (GUID: %u) in flight, ignore Area Trigger ID:%u",GetPlayer()->GetName(),GetPlayer()->GetGUIDLow(), Trigger_ID); - return; - } - - AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID); - if(!atEntry) - { - sLog.outDebug("Player '%s' (GUID: %u) send unknown (by DBC) Area Trigger ID:%u",GetPlayer()->GetName(),GetPlayer()->GetGUIDLow(), Trigger_ID); - return; - } - - if (GetPlayer()->GetMapId()!=atEntry->mapid) - { - sLog.outDebug("Player '%s' (GUID: %u) too far (trigger map: %u player map: %u), ignore Area Trigger ID: %u", GetPlayer()->GetName(), atEntry->mapid, GetPlayer()->GetMapId(), GetPlayer()->GetGUIDLow(), Trigger_ID); - return; - } - - // delta is safe radius - const float delta = 5.0f; - // check if player in the range of areatrigger - Player* pl = GetPlayer(); - - if (atEntry->radius > 0) - { - // if we have radius check it - float dist = pl->GetDistance(atEntry->x,atEntry->y,atEntry->z); - if(dist > atEntry->radius + delta) - { - sLog.outDebug("Player '%s' (GUID: %u) too far (radius: %f distance: %f), ignore Area Trigger ID: %u", - pl->GetName(), pl->GetGUIDLow(), atEntry->radius, dist, Trigger_ID); - return; - } - } - else - { - // we have only extent - float dx = pl->GetPositionX() - atEntry->x; - float dy = pl->GetPositionY() - atEntry->y; - float dz = pl->GetPositionZ() - atEntry->z; - double es = sin(atEntry->box_orientation); - double ec = cos(atEntry->box_orientation); - // calc rotated vector based on extent axis - double rotateDx = dx*ec - dy*es; - double rotateDy = dx*es + dy*ec; - - if( (fabs(rotateDx) > atEntry->box_x/2 + delta) || - (fabs(rotateDy) > atEntry->box_y/2 + delta) || - (fabs(dz) > atEntry->box_z/2 + delta) ) - { - sLog.outDebug("Player '%s' (GUID: %u) too far (1/2 box X: %f 1/2 box Y: %u 1/2 box Z: %u rotate dX: %f rotate dY: %f dZ:%f), ignore Area Trigger ID: %u", - pl->GetName(), pl->GetGUIDLow(), atEntry->box_x/2, atEntry->box_y/2, atEntry->box_z/2, rotateDx, rotateDy, dz, Trigger_ID); - return; - } - } - - if(Script->scriptAreaTrigger(GetPlayer(), atEntry)) - return; - - uint32 quest_id = objmgr.GetQuestForAreaTrigger( Trigger_ID ); - if( quest_id && GetPlayer()->isAlive() && GetPlayer()->IsActiveQuest(quest_id) ) - { - Quest const* pQuest = objmgr.GetQuestTemplate(quest_id); - if( pQuest ) - { - if(GetPlayer()->GetQuestStatus(quest_id) == QUEST_STATUS_INCOMPLETE) - GetPlayer()->AreaExploredOrEventHappens( quest_id ); - } - } - - if(objmgr.IsTavernAreaTrigger(Trigger_ID)) - { - // set resting flag we are in the inn - GetPlayer()->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING); - GetPlayer()->InnEnter(time(NULL), atEntry->mapid, atEntry->x, atEntry->y, atEntry->z); - GetPlayer()->SetRestType(REST_TYPE_IN_TAVERN); - - if(sWorld.IsFFAPvPRealm()) - GetPlayer()->RemoveFlag(PLAYER_FLAGS,PLAYER_FLAGS_FFA_PVP); - - return; - } - - if(GetPlayer()->InBattleGround()) - { - BattleGround* bg = GetPlayer()->GetBattleGround(); - if(bg) - if(bg->GetStatus() == STATUS_IN_PROGRESS) - bg->HandleAreaTrigger(GetPlayer(), Trigger_ID); - - return; - } - - // NULL if all values default (non teleport trigger) - AreaTrigger const* at = objmgr.GetAreaTrigger(Trigger_ID); - if(!at) - return; - - if(!GetPlayer()->isGameMaster()) - { - uint32 missingLevel = 0; - if(GetPlayer()->getLevel() < at->requiredLevel && !sWorld.getConfig(CONFIG_INSTANCE_IGNORE_LEVEL)) - missingLevel = at->requiredLevel; - - // must have one or the other, report the first one that's missing - uint32 missingItem = 0; - if(at->requiredItem) - { - if(!GetPlayer()->HasItemCount(at->requiredItem, 1) && - (!at->requiredItem2 || !GetPlayer()->HasItemCount(at->requiredItem2, 1))) - missingItem = at->requiredItem; - } - else if(at->requiredItem2 && !GetPlayer()->HasItemCount(at->requiredItem2, 1)) - missingItem = at->requiredItem2; - - uint32 missingKey = 0; - if(GetPlayer()->GetDifficulty() == DIFFICULTY_HEROIC) - { - if(at->heroicKey) - { - if(!GetPlayer()->HasItemCount(at->heroicKey, 1) && - (!at->heroicKey2 || !GetPlayer()->HasItemCount(at->heroicKey2, 1))) - missingKey = at->heroicKey; - } - else if(at->heroicKey2 && !GetPlayer()->HasItemCount(at->heroicKey2, 1)) - missingKey = at->heroicKey2; - } - - uint32 missingQuest = 0; - if(at->requiredQuest && !GetPlayer()->GetQuestRewardStatus(at->requiredQuest)) - missingQuest = at->requiredQuest; - - if(missingLevel || missingItem || missingKey || missingQuest) - { - // TODO: all this is probably wrong - if(missingItem) - SendAreaTriggerMessage(GetMangosString(LANG_LEVEL_MINREQUIRED_AND_ITEM), at->requiredLevel, objmgr.GetItemPrototype(missingItem)->Name1); - else if(missingKey) - GetPlayer()->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_DIFFICULTY2); - else if(missingQuest) - SendAreaTriggerMessage(at->requiredFailedText.c_str()); - else if(missingLevel) - SendAreaTriggerMessage(GetMangosString(LANG_LEVEL_MINREQUIRED), missingLevel); - return; - } - } - - GetPlayer()->TeleportTo(at->target_mapId,at->target_X,at->target_Y,at->target_Z,at->target_Orientation,TELE_TO_NOT_LEAVE_TRANSPORT); -} - -void WorldSession::HandleUpdateAccountData(WorldPacket &/*recv_data*/) -{ - sLog.outDetail("WORLD: Received CMSG_UPDATE_ACCOUNT_DATA"); - //recv_data.hexlike(); -} - -void WorldSession::HandleRequestAccountData(WorldPacket& /*recv_data*/) -{ - sLog.outDetail("WORLD: Received CMSG_REQUEST_ACCOUNT_DATA"); - //recv_data.hexlike(); -} - -void WorldSession::HandleSetActionButtonOpcode(WorldPacket& recv_data) -{ - CHECK_PACKET_SIZE(recv_data,1+2+1+1); - - sLog.outDebug( "WORLD: Received CMSG_SET_ACTION_BUTTON" ); - uint8 button, misc, type; - uint16 action; - recv_data >> button >> action >> misc >> type; - sLog.outDetail( "BUTTON: %u ACTION: %u TYPE: %u MISC: %u", button, action, type, misc ); - if(action==0) - { - sLog.outDetail( "MISC: Remove action from button %u", button ); - - GetPlayer()->removeActionButton(button); - } - else - { - if(type==ACTION_BUTTON_MACRO || type==ACTION_BUTTON_CMACRO) - { - sLog.outDetail( "MISC: Added Macro %u into button %u", action, button ); - GetPlayer()->addActionButton(button,action,type,misc); - } - else if(type==ACTION_BUTTON_SPELL) - { - sLog.outDetail( "MISC: Added Action %u into button %u", action, button ); - GetPlayer()->addActionButton(button,action,type,misc); - } - else if(type==ACTION_BUTTON_ITEM) - { - sLog.outDetail( "MISC: Added Item %u into button %u", action, button ); - GetPlayer()->addActionButton(button,action,type,misc); - } - else - sLog.outError( "MISC: Unknown action button type %u for action %u into button %u", type, action, button ); - } -} - -void WorldSession::HandleCompleteCinema( WorldPacket & /*recv_data*/ ) -{ - DEBUG_LOG( "WORLD: Player is watching cinema" ); -} - -void WorldSession::HandleNextCinematicCamera( WorldPacket & /*recv_data*/ ) -{ - DEBUG_LOG( "WORLD: Which movie to play" ); -} - -void WorldSession::HandleMoveTimeSkippedOpcode( WorldPacket & /*recv_data*/ ) -{ - /* WorldSession::Update( getMSTime() );*/ - DEBUG_LOG( "WORLD: Time Lag/Synchronization Resent/Update" ); - - /* - CHECK_PACKET_SIZE(recv_data,8+4); - uint64 guid; - uint32 time_skipped; - recv_data >> guid; - recv_data >> time_skipped; - sLog.outDebug( "WORLD: CMSG_MOVE_TIME_SKIPPED" ); - - /// TODO - must be need use in mangos - We substract server Lags to move time ( AntiLags ) - for exmaple - GetPlayer()->ModifyLastMoveTime( -int32(time_skipped) ); - */ -} - -void WorldSession::HandleFeatherFallAck(WorldPacket &/*recv_data*/) -{ - DEBUG_LOG("WORLD: CMSG_MOVE_FEATHER_FALL_ACK"); -} - -void WorldSession::HandleMoveUnRootAck(WorldPacket&/* recv_data*/) -{ - /* - CHECK_PACKET_SIZE(recv_data,8+8+4+4+4+4+4); - - sLog.outDebug( "WORLD: CMSG_FORCE_MOVE_UNROOT_ACK" ); - recv_data.hexlike(); - uint64 guid; - uint64 unknown1; - uint32 unknown2; - float PositionX; - float PositionY; - float PositionZ; - float Orientation; - - recv_data >> guid; - recv_data >> unknown1; - recv_data >> unknown2; - recv_data >> PositionX; - recv_data >> PositionY; - recv_data >> PositionZ; - recv_data >> Orientation; - - // TODO for later may be we can use for anticheat - DEBUG_LOG("Guid " I64FMTD,guid); - DEBUG_LOG("unknown1 " I64FMTD,unknown1); - DEBUG_LOG("unknown2 %u",unknown2); - DEBUG_LOG("X %f",PositionX); - DEBUG_LOG("Y %f",PositionY); - DEBUG_LOG("Z %f",PositionZ); - DEBUG_LOG("O %f",Orientation); - */ -} - -void WorldSession::HandleMoveRootAck(WorldPacket&/* recv_data*/) -{ - /* - CHECK_PACKET_SIZE(recv_data,8+8+4+4+4+4+4); - - sLog.outDebug( "WORLD: CMSG_FORCE_MOVE_ROOT_ACK" ); - recv_data.hexlike(); - uint64 guid; - uint64 unknown1; - uint32 unknown2; - float PositionX; - float PositionY; - float PositionZ; - float Orientation; - - recv_data >> guid; - recv_data >> unknown1; - recv_data >> unknown2; - recv_data >> PositionX; - recv_data >> PositionY; - recv_data >> PositionZ; - recv_data >> Orientation; - - // for later may be we can use for anticheat - DEBUG_LOG("Guid " I64FMTD,guid); - DEBUG_LOG("unknown1 " I64FMTD,unknown1); - DEBUG_LOG("unknown1 %u",unknown2); - DEBUG_LOG("X %f",PositionX); - DEBUG_LOG("Y %f",PositionY); - DEBUG_LOG("Z %f",PositionZ); - DEBUG_LOG("O %f",Orientation); - */ -} - -void WorldSession::HandleMoveTeleportAck(WorldPacket&/* recv_data*/) -{ - /* - CHECK_PACKET_SIZE(recv_data,8+4); - - sLog.outDebug("MSG_MOVE_TELEPORT_ACK"); - uint64 guid; - uint32 flags, time; - - recv_data >> guid; - recv_data >> flags >> time; - DEBUG_LOG("Guid " I64FMTD,guid); - DEBUG_LOG("Flags %u, time %u",flags, time/1000); - */ -} - -void WorldSession::HandleSetActionBar(WorldPacket& recv_data) -{ - CHECK_PACKET_SIZE(recv_data,1); - - uint8 ActionBar; - - recv_data >> ActionBar; - - if(!GetPlayer()) // ignore until not logged (check needed because STATUS_AUTHED) - { - if(ActionBar!=0) - sLog.outError("WorldSession::HandleSetActionBar in not logged state with value: %u, ignored",uint32(ActionBar)); - return; - } - - GetPlayer()->SetByteValue(PLAYER_FIELD_BYTES, 2, ActionBar); -} - -void WorldSession::HandleWardenDataOpcode(WorldPacket& /*recv_data*/) -{ - /* - CHECK_PACKET_SIZE(recv_data,1); - - uint8 tmp; - recv_data >> tmp; - sLog.outDebug("Received opcode CMSG_WARDEN_DATA, not resolve.uint8 = %u",tmp); - */ -} - -void WorldSession::HandlePlayedTime(WorldPacket& /*recv_data*/) -{ - uint32 TotalTimePlayed = GetPlayer()->GetTotalPlayedTime(); - uint32 LevelPlayedTime = GetPlayer()->GetLevelPlayedTime(); - - WorldPacket data(SMSG_PLAYED_TIME, 8); - data << TotalTimePlayed; - data << LevelPlayedTime; - SendPacket(&data); -} - -void WorldSession::HandleInspectOpcode(WorldPacket& recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - uint64 guid; - recv_data >> guid; - DEBUG_LOG("Inspected guid is " I64FMTD, guid); - - _player->SetSelection(guid); - - Player *plr = objmgr.GetPlayer(guid); - if(!plr) // wrong player - return; - - uint32 talent_points = 0x3D; - uint32 guid_size = plr->GetPackGUID().size(); - WorldPacket data(SMSG_INSPECT_TALENT, 4+talent_points); - data.append(plr->GetPackGUID()); - data << uint32(talent_points); - - // fill by 0 talents array - for(uint32 i = 0; i < talent_points; ++i) - data << uint8(0); - - if(sWorld.getConfig(CONFIG_TALENTS_INSPECTING) || _player->isGameMaster()) - { - // find class talent tabs (all players have 3 talent tabs) - uint32 const* talentTabIds = GetTalentTabPages(plr->getClass()); - - uint32 talentTabPos = 0; // pos of first talent rank in tab including all prev tabs - for(uint32 i = 0; i < 3; ++i) - { - uint32 talentTabId = talentTabIds[i]; - - // fill by real data - for(uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) - { - TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); - if(!talentInfo) - continue; - - // skip another tab talents - if(talentInfo->TalentTab != talentTabId) - continue; - - // find talent rank - uint32 curtalent_maxrank = 0; - for(uint32 k = 5; k > 0; --k) - { - if(talentInfo->RankID[k-1] && plr->HasSpell(talentInfo->RankID[k-1])) - { - curtalent_maxrank = k; - break; - } - } - - // not learned talent - if(!curtalent_maxrank) - continue; - - // 1 rank talent bit index - uint32 curtalent_index = talentTabPos + GetTalentInspectBitPosInTab(talentId); - - uint32 curtalent_rank_index = curtalent_index+curtalent_maxrank-1; - - // slot/offset in 7-bit bytes - uint32 curtalent_rank_slot7 = curtalent_rank_index / 7; - uint32 curtalent_rank_offset7 = curtalent_rank_index % 7; - - // rank pos with skipped 8 bit - uint32 curtalent_rank_index2 = curtalent_rank_slot7 * 8 + curtalent_rank_offset7; - - // slot/offset in 8-bit bytes with skipped high bit - uint32 curtalent_rank_slot = curtalent_rank_index2 / 8; - uint32 curtalent_rank_offset = curtalent_rank_index2 % 8; - - // apply mask - uint32 val = data.read(guid_size + 4 + curtalent_rank_slot); - val |= (1 << curtalent_rank_offset); - data.put(guid_size + 4 + curtalent_rank_slot, val & 0xFF); - } - - talentTabPos += GetTalentTabInspectBitSize(talentTabId); - } - } - - SendPacket(&data); -} - -void WorldSession::HandleInspectHonorStatsOpcode(WorldPacket& recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - uint64 guid; - recv_data >> guid; - - Player *player = objmgr.GetPlayer(guid); - - if(!player) - { - sLog.outError("InspectHonorStats: WTF, player not found..."); - return; - } - - WorldPacket data(MSG_INSPECT_HONOR_STATS, 8+1+4*4); - data << uint64(player->GetGUID()); - data << uint8(player->GetUInt32Value(PLAYER_FIELD_HONOR_CURRENCY)); - data << uint32(player->GetUInt32Value(PLAYER_FIELD_KILLS)); - data << uint32(player->GetUInt32Value(PLAYER_FIELD_TODAY_CONTRIBUTION)); - data << uint32(player->GetUInt32Value(PLAYER_FIELD_YESTERDAY_CONTRIBUTION)); - data << uint32(player->GetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS)); - SendPacket(&data); -} - -void WorldSession::HandleWorldTeleportOpcode(WorldPacket& recv_data) -{ - CHECK_PACKET_SIZE(recv_data,4+4+4+4+4+4); - - // write in client console: worldport 469 452 6454 2536 180 or /console worldport 469 452 6454 2536 180 - // Received opcode CMSG_WORLD_TELEPORT - // Time is ***, map=469, x=452.000000, y=6454.000000, z=2536.000000, orient=3.141593 - - //sLog.outDebug("Received opcode CMSG_WORLD_TELEPORT"); - - if(GetPlayer()->isInFlight()) - { - sLog.outDebug("Player '%s' (GUID: %u) in flight, ignore worldport command.",GetPlayer()->GetName(),GetPlayer()->GetGUIDLow()); - return; - } - - uint32 time; - uint32 mapid; - float PositionX; - float PositionY; - float PositionZ; - float Orientation; - - recv_data >> time; // time in m.sec. - recv_data >> mapid; - recv_data >> PositionX; - recv_data >> PositionY; - recv_data >> PositionZ; - recv_data >> Orientation; // o (3.141593 = 180 degrees) - DEBUG_LOG("Time %u sec, map=%u, x=%f, y=%f, z=%f, orient=%f", time/1000, mapid, PositionX, PositionY, PositionZ, Orientation); - - if (GetSecurity() >= SEC_ADMINISTRATOR) - GetPlayer()->TeleportTo(mapid,PositionX,PositionY,PositionZ,Orientation); - else - SendNotification("You do not have permission to perform that function"); - sLog.outDebug("Received worldport command from player %s", GetPlayer()->GetName()); -} - -void WorldSession::HandleWhoisOpcode(WorldPacket& recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 1); - - sLog.outDebug("Received opcode CMSG_WHOIS"); - std::string charname, acc, email, lastip, msg; - recv_data >> charname; - - if (GetSecurity() < SEC_ADMINISTRATOR) - { - SendNotification("You do not have permission to perform that function"); - return; - } - - if(charname.empty()) - { - SendNotification("Please provide character name"); - return; - } - - uint32 accid; - Field *fields; - - Player *plr = objmgr.GetPlayer(charname.c_str()); - - if(plr) - accid = plr->GetSession()->GetAccountId(); - else - { - SendNotification("Player %s not found or offline", charname.c_str()); - return; - } - - if(!accid) - { - SendNotification("Account for character %s not found", charname.c_str()); - return; - } - - QueryResult *result = loginDatabase.PQuery("SELECT username,email,last_ip FROM account WHERE id=%u", accid); - if(result) - { - fields = result->Fetch(); - acc = fields[0].GetCppString(); - if(acc.empty()) - acc = "Unknown"; - email = fields[1].GetCppString(); - if(email.empty()) - email = "Unknown"; - lastip = fields[2].GetCppString(); - if(lastip.empty()) - lastip = "Unknown"; - msg = charname + "'s " + "account is " + acc + ", e-mail: " + email + ", last ip: " + lastip; - - WorldPacket data(SMSG_WHOIS, msg.size()+1); - data << msg; - _player->GetSession()->SendPacket(&data); - } - else - SendNotification("Account for character %s not found", charname.c_str()); - - delete result; - sLog.outDebug("Received whois command from player %s for character %s", GetPlayer()->GetName(), charname.c_str()); -} - -void WorldSession::HandleReportSpamOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 1+8); - sLog.outDebug("WORLD: CMSG_REPORT_SPAM"); - recv_data.hexlike(); - - uint8 spam_type; // 0 - mail, 1 - chat - uint64 spammer_guid; - uint32 unk1, unk2, unk3, unk4 = 0; - std::string description = ""; - recv_data >> spam_type; // unk 0x01 const, may be spam type (mail/chat) - recv_data >> spammer_guid; // player guid - switch(spam_type) - { - case 0: - CHECK_PACKET_SIZE(recv_data, recv_data.rpos()+4+4+4); - recv_data >> unk1; // const 0 - recv_data >> unk2; // probably mail id - recv_data >> unk3; // const 0 - break; - case 1: - CHECK_PACKET_SIZE(recv_data, recv_data.rpos()+4+4+4+4+1); - recv_data >> unk1; // probably language - recv_data >> unk2; // message type? - recv_data >> unk3; // probably channel id - recv_data >> unk4; // unk random value - recv_data >> description; // spam description string (messagetype, channel name, player name, message) - break; - } - - // NOTE: all chat messages from this spammer automatically ignored by spam reporter until logout in case chat spam. - // if it's mail spam - ALL mails from this spammer automatically removed by client - - // Complaint Received message - WorldPacket data(SMSG_COMPLAIN_RESULT, 1); - data << uint8(0); - SendPacket(&data); - - sLog.outDebug("REPORT SPAM: type %u, guid %u, unk1 %u, unk2 %u, unk3 %u, unk4 %u, message %s", spam_type, GUID_LOPART(spammer_guid), unk1, unk2, unk3, unk4, description.c_str()); -} - -void WorldSession::HandleRealmStateRequestOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 4); - - sLog.outDebug("CMSG_REALM_SPLIT"); - - uint32 unk; - std::string split_date = "01/01/01"; - recv_data >> unk; - - WorldPacket data(SMSG_REALM_SPLIT, 4+4+split_date.size()+1); - data << unk; - data << uint32(0x00000000); // realm split state - // split states: - // 0x0 realm normal - // 0x1 realm split - // 0x2 realm split pending - data << split_date; - SendPacket(&data); - //sLog.outDebug("response sent %u", unk); -} - -void WorldSession::HandleFarSightOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 1); - - sLog.outDebug("WORLD: CMSG_FAR_SIGHT"); - //recv_data.hexlike(); - - uint8 unk; - recv_data >> unk; - - switch(unk) - { - case 0: - //WorldPacket data(SMSG_CLEAR_FAR_SIGHT_IMMEDIATE, 0) - //SendPacket(&data); - //_player->SetUInt64Value(PLAYER_FARSIGHT, 0); - sLog.outDebug("Removed FarSight from player %u", _player->GetGUIDLow()); - break; - case 1: - sLog.outDebug("Added FarSight " I64FMTD " to player %u", _player->GetUInt64Value(PLAYER_FARSIGHT), _player->GetGUIDLow()); - break; - } -} - -void WorldSession::HandleChooseTitleOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 4); - - sLog.outDebug("CMSG_SET_TITLE"); - - int32 title; - recv_data >> title; - - // -1 at none - if(title > 0 && title < 64) - { - if(!GetPlayer()->HasFlag64(PLAYER__FIELD_KNOWN_TITLES,uint64(1) << title)) - return; - } - else - title = 0; - - GetPlayer()->SetUInt32Value(PLAYER_CHOSEN_TITLE, title); -} - -void WorldSession::HandleAllowMoveAckOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 4+4); - - sLog.outDebug("CMSG_ALLOW_MOVE_ACK"); - - uint32 counter, time_; - recv_data >> counter >> time_; - - // time_ seems always more than getMSTime() - uint32 diff = getMSTimeDiff(getMSTime(),time_); - - sLog.outDebug("response sent: counter %u, time %u (HEX: %X), ms. time %u, diff %u", counter, time_, time_, getMSTime(), diff); -} - -void WorldSession::HandleResetInstancesOpcode( WorldPacket & /*recv_data*/ ) -{ - sLog.outDebug("WORLD: CMSG_RESET_INSTANCES"); - Group *pGroup = _player->GetGroup(); - if(pGroup) - { - if(pGroup->IsLeader(_player->GetGUID())) - pGroup->ResetInstances(INSTANCE_RESET_ALL, _player); - } - else - _player->ResetInstances(INSTANCE_RESET_ALL); -} - -void WorldSession::HandleDungeonDifficultyOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 4); - - sLog.outDebug("MSG_SET_DUNGEON_DIFFICULTY"); - - uint32 mode; - recv_data >> mode; - - if(mode == _player->GetDifficulty()) - return; - - if(mode > DIFFICULTY_HEROIC) - { - sLog.outError("WorldSession::HandleDungeonDifficultyOpcode: player %d sent an invalid instance mode %d!", _player->GetGUIDLow(), mode); - return; - } - - // cannot reset while in an instance - Map *map = _player->GetMap(); - if(map && map->IsDungeon()) - { - sLog.outError("WorldSession::HandleDungeonDifficultyOpcode: player %d tried to reset the instance while inside!", _player->GetGUIDLow()); - return; - } - - if(_player->getLevel() < LEVELREQUIREMENT_HEROIC) - return; - Group *pGroup = _player->GetGroup(); - if(pGroup) - { - if(pGroup->IsLeader(_player->GetGUID())) - { - // the difficulty is set even if the instances can't be reset - //_player->SendDungeonDifficulty(true); - pGroup->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY, _player); - pGroup->SetDifficulty(mode); - } - } - else - { - _player->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY); - _player->SetDifficulty(mode); - } -} - -void WorldSession::HandleNewUnknownOpcode( WorldPacket & recv_data ) -{ - sLog.outDebug("New Unknown Opcode %u", recv_data.GetOpcode()); - recv_data.hexlike(); - /* - New Unknown Opcode 837 - STORAGE_SIZE: 60 - 02 00 00 00 00 00 00 00 | 00 00 00 00 01 20 00 00 - 89 EB 33 01 71 5C 24 C4 | 15 03 35 45 74 47 8B 42 - BA B8 1B 40 00 00 00 00 | 00 00 00 00 77 66 42 BF - 23 91 26 3F 00 00 60 41 | 00 00 00 00 - - New Unknown Opcode 837 - STORAGE_SIZE: 44 - 02 00 00 00 00 00 00 00 | 00 00 00 00 00 00 80 00 - 7B 80 34 01 84 EA 2B C4 | 5F A1 36 45 C9 39 1C 42 - BA B8 1B 40 CE 06 00 00 | 00 00 80 3F - */ -} - -void WorldSession::HandleDismountOpcode( WorldPacket & /*recv_data*/ ) -{ - sLog.outDebug("WORLD: CMSG_CANCEL_MOUNT_AURA"); - //recv_data.hexlike(); - - //If player is not mounted, so go out :) - if (!_player->IsMounted()) // not blizz like; no any messages on blizz - { - ChatHandler(this).SendSysMessage(LANG_CHAR_NON_MOUNTED); - return; - } - - if(_player->isInFlight()) // not blizz like; no any messages on blizz - { - ChatHandler(this).SendSysMessage(LANG_YOU_IN_FLIGHT); - return; - } - - _player->Unmount(); - _player->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); -} - -void WorldSession::HandleMoveFlyModeChangeAckOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 8+4+4); - - // fly mode on/off - sLog.outDebug("WORLD: CMSG_MOVE_SET_CAN_FLY_ACK"); - //recv_data.hexlike(); - - uint64 guid; - uint32 unk; - uint32 flags; - - recv_data >> guid >> unk >> flags; - - _player->SetUnitMovementFlags(flags); - /* - on: - 25 00 00 00 00 00 00 00 | 00 00 00 00 00 00 80 00 - 85 4E A9 01 19 BA 7A C3 | 42 0D 70 44 44 B0 A8 42 - 78 15 94 40 39 03 00 00 | 00 00 80 3F - off: - 25 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 - 10 FD A9 01 19 BA 7A C3 | 42 0D 70 44 44 B0 A8 42 - 78 15 94 40 39 03 00 00 | 00 00 00 00 - */ -} - -void WorldSession::HandleRequestPetInfoOpcode( WorldPacket & /*recv_data */) -{ - /* - sLog.outDebug("WORLD: CMSG_REQUEST_PET_INFO"); - recv_data.hexlike(); - */ -} - -void WorldSession::HandleSetTaxiBenchmarkOpcode( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data, 1); - - uint8 mode; - recv_data >> mode; - - sLog.outDebug("Client used \"/timetest %d\" command", mode); -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "Language.h" +#include "Database/DatabaseEnv.h" +#include "WorldPacket.h" +#include "Opcodes.h" +#include "Log.h" +#include "Player.h" +#include "World.h" +#include "ObjectMgr.h" +#include "WorldSession.h" +#include "Auth/BigNumber.h" +#include "Auth/Sha1.h" +#include "UpdateData.h" +#include "LootMgr.h" +#include "Chat.h" +#include "ScriptCalls.h" +#include +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "Object.h" +#include "BattleGround.h" +#include "SpellAuras.h" +#include "Pet.h" +#include "SocialMgr.h" + +void WorldSession::HandleRepopRequestOpcode( WorldPacket & /*recv_data*/ ) +{ + sLog.outDebug( "WORLD: Recvd CMSG_REPOP_REQUEST Message" ); + + if(GetPlayer()->isAlive()||GetPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + return; + + // the world update order is sessions, players, creatures + // the netcode runs in parallel with all of these + // creatures can kill players + // so if the server is lagging enough the player can + // release spirit after he's killed but before he is updated + if(GetPlayer()->getDeathState() == JUST_DIED) + { + sLog.outDebug("HandleRepopRequestOpcode: got request after player %s(%d) was killed and before he was updated", GetPlayer()->GetName(), GetPlayer()->GetGUIDLow()); + GetPlayer()->KillPlayer(); + } + + //this is spirit release confirm? + GetPlayer()->RemovePet(NULL,PET_SAVE_NOT_IN_SLOT, true); + GetPlayer()->BuildPlayerRepop(); + GetPlayer()->RepopAtGraveyard(); +} + +void WorldSession::HandleWhoOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,4+4+1+1+4+4+4+4); + + sLog.outDebug( "WORLD: Recvd CMSG_WHO Message" ); + //recv_data.hexlike(); + + uint32 clientcount = 0; + + uint32 level_min, level_max, racemask, classmask, zones_count, str_count; + uint32 zoneids[10]; // 10 is client limit + std::string player_name, guild_name; + + recv_data >> level_min; // maximal player level, default 0 + recv_data >> level_max; // minimal player level, default 100 + recv_data >> player_name; // player name, case sensitive... + + // recheck + CHECK_PACKET_SIZE(recv_data,4+4+(player_name.size()+1)+1+4+4+4+4); + + recv_data >> guild_name; // guild name, case sensitive... + + // recheck + CHECK_PACKET_SIZE(recv_data,4+4+(player_name.size()+1)+(guild_name.size()+1)+4+4+4+4); + + recv_data >> racemask; // race mask + recv_data >> classmask; // class mask + recv_data >> zones_count; // zones count, client limit=10 (2.0.10) + + if(zones_count > 10) + return; // can't be received from real client or broken packet + + // recheck + CHECK_PACKET_SIZE(recv_data,4+4+(player_name.size()+1)+(guild_name.size()+1)+4+4+4+(4*zones_count)+4); + + for(uint32 i = 0; i < zones_count; i++) + { + uint32 temp; + recv_data >> temp; // zone id, 0 if zone is unknown... + zoneids[i] = temp; + sLog.outDebug("Zone %u: %u", i, zoneids[i]); + } + + recv_data >> str_count; // user entered strings count, client limit=4 (checked on 2.0.10) + + if(str_count > 4) + return; // can't be received from real client or broken packet + + // recheck + CHECK_PACKET_SIZE(recv_data,4+4+(player_name.size()+1)+(guild_name.size()+1)+4+4+4+(4*zones_count)+4+(1*str_count)); + + sLog.outDebug("Minlvl %u, maxlvl %u, name %s, guild %s, racemask %u, classmask %u, zones %u, strings %u", level_min, level_max, player_name.c_str(), guild_name.c_str(), racemask, classmask, zones_count, str_count); + + std::wstring str[4]; // 4 is client limit + for(uint32 i = 0; i < str_count; i++) + { + // recheck (have one more byte) + CHECK_PACKET_SIZE(recv_data,recv_data.rpos()); + + std::string temp; + recv_data >> temp; // user entered string, it used as universal search pattern(guild+player name)? + + if(!Utf8toWStr(temp,str[i])) + continue; + + wstrToLower(str[i]); + + sLog.outDebug("String %u: %s", i, str[i].c_str()); + } + + std::wstring wplayer_name; + std::wstring wguild_name; + if(!(Utf8toWStr(player_name, wplayer_name) && Utf8toWStr(guild_name, wguild_name))) + return; + wstrToLower(wplayer_name); + wstrToLower(wguild_name); + + // client send in case not set max level value 100 but mangos support 255 max level, + // update it to show GMs with characters after 100 level + if(level_max >= 100) + level_max = 255; + + uint32 team = _player->GetTeam(); + uint32 security = GetSecurity(); + bool allowTwoSideWhoList = sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_WHO_LIST); + bool gmInWhoList = sWorld.getConfig(CONFIG_GM_IN_WHO_LIST); + + WorldPacket data( SMSG_WHO, 50 ); // guess size + data << clientcount; // clientcount place holder + data << clientcount; // clientcount place holder + + //TODO: Guard Player map + HashMapHolder::MapType& m = ObjectAccessor::Instance().GetPlayers(); + for(HashMapHolder::MapType::iterator itr = m.begin(); itr != m.end(); ++itr) + { + if (security == SEC_PLAYER) + { + // player can see member of other team only if CONFIG_ALLOW_TWO_SIDE_WHO_LIST + if (itr->second->GetTeam() != team && !allowTwoSideWhoList ) + continue; + + // player can see MODERATOR, GAME MASTER, ADMINISTRATOR only if CONFIG_GM_IN_WHO_LIST + if ((itr->second->GetSession()->GetSecurity() > SEC_PLAYER && !gmInWhoList)) + continue; + } + + // check if target is globally visible for player + if (!(itr->second->IsVisibleGloballyFor(_player))) + continue; + + // check if target's level is in level range + uint32 lvl = itr->second->getLevel(); + if (lvl < level_min || lvl > level_max) + continue; + + // check if class matches classmask + uint32 class_ = itr->second->getClass(); + if (!(classmask & (1 << class_))) + continue; + + // check if race matches racemask + uint32 race = itr->second->getRace(); + if (!(racemask & (1 << race))) + continue; + + uint32 pzoneid = itr->second->GetZoneId(); + + bool z_show = true; + for(uint32 i = 0; i < zones_count; i++) + { + if(zoneids[i] == pzoneid) + { + z_show = true; + break; + } + + z_show = false; + } + if (!z_show) + continue; + + std::string pname = itr->second->GetName(); + std::wstring wpname; + if(!Utf8toWStr(pname,wpname)) + continue; + wstrToLower(wpname); + + if (!(wplayer_name.empty() || wpname.find(wplayer_name) != std::wstring::npos)) + continue; + + std::string gname = objmgr.GetGuildNameById(itr->second->GetGuildId()); + std::wstring wgname; + if(!Utf8toWStr(gname,wgname)) + continue; + wstrToLower(wgname); + + if (!(wguild_name.empty() || wgname.find(wguild_name) != std::wstring::npos)) + continue; + + std::string aname; + if(AreaTableEntry const* areaEntry = GetAreaEntryByAreaID(itr->second->GetZoneId())) + aname = areaEntry->area_name[GetSessionDbcLocale()]; + + bool s_show = true; + for(uint32 i = 0; i < str_count; i++) + { + if (!str[i].empty()) + { + if (wgname.find(str[i]) != std::wstring::npos || + wpname.find(str[i]) != std::wstring::npos || + Utf8FitTo(aname, str[i]) ) + { + s_show = true; + break; + } + s_show = false; + } + } + if (!s_show) + continue; + + data << pname; // player name + data << gname; // guild name + data << uint32( lvl ); // player level + data << uint32( class_ ); // player class + data << uint32( race ); // player race + data << uint8(0); // new 2.4.0 + data << uint32( pzoneid ); // player zone id + + // 49 is maximum player count sent to client + if ((++clientcount) == 49) + break; + } + + data.put( 0, clientcount ); //insert right count + data.put( sizeof(uint32), clientcount ); //insert right count + + SendPacket(&data); + sLog.outDebug( "WORLD: Send SMSG_WHO Message" ); +} + +void WorldSession::HandleLogoutRequestOpcode( WorldPacket & /*recv_data*/ ) +{ + sLog.outDebug( "WORLD: Recvd CMSG_LOGOUT_REQUEST Message, security - %u", GetSecurity() ); + + if (uint64 lguid = GetPlayer()->GetLootGUID()) + DoLootRelease(lguid); + + //instant logout for admins, gm's, mod's + if( GetSecurity() > SEC_PLAYER ) + { + LogoutPlayer(true); + return; + } + + //Can not logout if... + if( GetPlayer()->isInCombat() || //...is in combat + GetPlayer()->duel || //...is in Duel + //...is jumping ...is falling + GetPlayer()->HasUnitMovementFlag(MOVEMENTFLAG_JUMPING | MOVEMENTFLAG_FALLING)) + { + WorldPacket data( SMSG_LOGOUT_RESPONSE, (2+4) ) ; + data << (uint8)0xC; + data << uint32(0); + data << uint8(0); + SendPacket( &data ); + LogoutRequest(0); + return; + } + + //instant logout in taverns/cities or on taxi + if(GetPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || GetPlayer()->isInFlight()) + { + LogoutPlayer(true); + return; + } + + // not set flags if player can't free move to prevent lost state at logout cancel + if(GetPlayer()->CanFreeMove()) + { + GetPlayer()->SetStandState(PLAYER_STATE_SIT); + + WorldPacket data( SMSG_FORCE_MOVE_ROOT, (8+4) ); // guess size + data.append(GetPlayer()->GetPackGUID()); + data << (uint32)2; + SendPacket( &data ); + GetPlayer()->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_ROTATE); + } + + WorldPacket data( SMSG_LOGOUT_RESPONSE, 5 ); + data << uint32(0); + data << uint8(0); + SendPacket( &data ); + LogoutRequest(time(NULL)); +} + +void WorldSession::HandlePlayerLogoutOpcode( WorldPacket & /*recv_data*/ ) +{ + sLog.outDebug( "WORLD: Recvd CMSG_PLAYER_LOGOUT Message" ); +} + +void WorldSession::HandleLogoutCancelOpcode( WorldPacket & /*recv_data*/ ) +{ + sLog.outDebug( "WORLD: Recvd CMSG_LOGOUT_CANCEL Message" ); + + LogoutRequest(0); + + WorldPacket data( SMSG_LOGOUT_CANCEL_ACK, 0 ); + SendPacket( &data ); + + // not remove flags if can't free move - its not set in Logout request code. + if(GetPlayer()->CanFreeMove()) + { + //!we can move again + data.Initialize( SMSG_FORCE_MOVE_UNROOT, 8 ); // guess size + data.append(GetPlayer()->GetPackGUID()); + data << uint32(0); + SendPacket( &data ); + + //! Stand Up + GetPlayer()->SetStandState(PLAYER_STATE_NONE); + + //! DISABLE_ROTATE + GetPlayer()->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_ROTATE); + } + + sLog.outDebug( "WORLD: sent SMSG_LOGOUT_CANCEL_ACK Message" ); +} + +void WorldSession::SendGMTicketGetTicket(uint32 status, char const* text) +{ + int len = text ? strlen(text) : 0; + WorldPacket data( SMSG_GMTICKET_GETTICKET, (4+len+1+4+2+4+4) ); + data << uint32(status); // standard 0x0A, 0x06 if text present + if(status == 6) + { + data << text; // ticket text + data << uint8(0x7); // ticket category + data << float(0); // time from ticket creation? + data << float(0); // const + data << float(0); // const + data << uint8(0); // const + data << uint8(0); // const + } + SendPacket( &data ); +} + +void WorldSession::HandleGMTicketGetTicketOpcode( WorldPacket & /*recv_data*/ ) +{ + WorldPacket data( SMSG_QUERY_TIME_RESPONSE, 4+4 ); + data << (uint32)time(NULL); + data << (uint32)0; + SendPacket( &data ); + + uint64 guid; + Field *fields; + guid = GetPlayer()->GetGUID(); + + QueryResult *result = CharacterDatabase.PQuery("SELECT COUNT(ticket_id) FROM character_ticket WHERE guid = '%u'", GUID_LOPART(guid)); + + if (result) + { + int cnt; + fields = result->Fetch(); + cnt = fields[0].GetUInt32(); + delete result; + + if ( cnt > 0 ) + { + QueryResult *result2 = CharacterDatabase.PQuery("SELECT ticket_text FROM character_ticket WHERE guid = '%u'", GUID_LOPART(guid)); + if(result2) + { + Field *fields2 = result2->Fetch(); + SendGMTicketGetTicket(0x06,fields2[0].GetString()); + delete result2; + } + } + else + SendGMTicketGetTicket(0x0A,0); + } +} + +void WorldSession::HandleGMTicketUpdateTextOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,1); + + std::string ticketText; + recv_data >> ticketText; + + CharacterDatabase.escape_string(ticketText); + CharacterDatabase.PExecute("UPDATE character_ticket SET ticket_text = '%s' WHERE guid = '%u'", ticketText.c_str(), _player->GetGUIDLow()); +} + +void WorldSession::HandleGMTicketDeleteOpcode( WorldPacket & /*recv_data*/ ) +{ + uint32 guid = GetPlayer()->GetGUIDLow(); + + CharacterDatabase.PExecute("DELETE FROM character_ticket WHERE guid = '%u' LIMIT 1",guid); + + WorldPacket data( SMSG_GMTICKET_DELETETICKET, 4 ); + data << uint32(9); + SendPacket( &data ); + + SendGMTicketGetTicket(0x0A, 0); +} + +void WorldSession::HandleGMTicketCreateOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 4*4+1+2*4); + + uint32 map; + float x, y, z; + std::string ticketText = ""; + uint32 unk1, unk2; + + recv_data >> map >> x >> y >> z; // last check 2.4.3 + recv_data >> ticketText; + + // recheck + CHECK_PACKET_SIZE(recv_data,4*4+(ticketText.size()+1)+2*4); + + recv_data >> unk1 >> unk2; + // note: the packet might contain more data, but the exact structure of that is unknown + + sLog.outDebug("TicketCreate: map %u, x %f, y %f, z %f, text %s, unk1 %u, unk2 %u", map, x, y, z, ticketText.c_str(), unk1, unk2); + + CharacterDatabase.escape_string(ticketText); + + QueryResult *result = CharacterDatabase.PQuery("SELECT COUNT(*) FROM character_ticket WHERE guid = '%u'", _player->GetGUIDLow()); + + if (result) + { + int cnt; + Field *fields = result->Fetch(); + cnt = fields[0].GetUInt32(); + delete result; + + if ( cnt > 0 ) + { + WorldPacket data( SMSG_GMTICKET_CREATE, 4 ); + data << uint32(1); + SendPacket( &data ); + } + else + { + CharacterDatabase.PExecute("INSERT INTO character_ticket (guid,ticket_text) VALUES ('%u', '%s')", _player->GetGUIDLow(), ticketText.c_str()); + + WorldPacket data( SMSG_QUERY_TIME_RESPONSE, 4+4 ); + data << (uint32)time(NULL); + data << (uint32)0; + SendPacket( &data ); + + data.Initialize( SMSG_GMTICKET_CREATE, 4 ); + data << uint32(2); + SendPacket( &data ); + DEBUG_LOG("update the ticket\n"); + + //TODO: Guard player map + HashMapHolder::MapType &m = ObjectAccessor::Instance().GetPlayers(); + for(HashMapHolder::MapType::iterator itr = m.begin(); itr != m.end(); ++itr) + { + if(itr->second->GetSession()->GetSecurity() >= SEC_GAMEMASTER && itr->second->isAcceptTickets()) + ChatHandler(itr->second).PSendSysMessage(LANG_COMMAND_TICKETNEW,GetPlayer()->GetName()); + } + } + } +} + +void WorldSession::HandleGMTicketSystemStatusOpcode( WorldPacket & /*recv_data*/ ) +{ + WorldPacket data( SMSG_GMTICKET_SYSTEMSTATUS,4 ); + data << uint32(1); // we can also disable ticket system by sending 0 value + + SendPacket( &data ); +} + +void WorldSession::HandleGMSurveySubmit( WorldPacket & recv_data) +{ + // GM survey is shown after SMSG_GM_TICKET_STATUS_UPDATE with status = 3 + CHECK_PACKET_SIZE(recv_data,4+4); + uint32 x; + recv_data >> x; // answer range? (6 = 0-5?) + sLog.outDebug("SURVEY: X = %u", x); + + uint8 result[10]; + memset(result, 0, sizeof(result)); + for( int i = 0; i < 10; ++i) + { + CHECK_PACKET_SIZE(recv_data,recv_data.rpos()+4); + uint32 questionID; + recv_data >> questionID; // GMSurveyQuestions.dbc + if (!questionID) + break; + + CHECK_PACKET_SIZE(recv_data,recv_data.rpos()+1+1); + uint8 value; + std::string unk_text; + recv_data >> value; // answer + recv_data >> unk_text; // always empty? + + result[i] = value; + sLog.outDebug("SURVEY: ID %u, value %u, text %s", questionID, value, unk_text.c_str()); + } + + CHECK_PACKET_SIZE(recv_data,recv_data.rpos()+1); + std::string comment; + recv_data >> comment; // addional comment + sLog.outDebug("SURVEY: comment %s", comment.c_str()); + + // TODO: chart this data in some way +} + +void WorldSession::HandleTogglePvP( WorldPacket & recv_data ) +{ + // this opcode can be used in two ways: Either set explicit new status or toggle old status + if(recv_data.size() == 1) + { + bool newPvPStatus; + recv_data >> newPvPStatus; + GetPlayer()->ApplyModFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP, newPvPStatus); + } + else + { + GetPlayer()->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP); + } + + if(GetPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP)) + { + if(!GetPlayer()->IsPvP() || GetPlayer()->pvpInfo.endTimer != 0) + GetPlayer()->UpdatePvP(true, true); + } + else + { + if(!GetPlayer()->pvpInfo.inHostileArea && GetPlayer()->IsPvP()) + GetPlayer()->pvpInfo.endTimer = time(NULL); // start toggle-off + } +} + +void WorldSession::HandleZoneUpdateOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,4); + + uint32 newZone; + recv_data >> newZone; + + sLog.outDetail("WORLD: Recvd ZONE_UPDATE: %u", newZone); + + if(newZone != _player->GetZoneId()) + GetPlayer()->SendInitWorldStates(); // only if really enters to new zone, not just area change, works strange... + + GetPlayer()->UpdateZone(newZone); +} + +void WorldSession::HandleSetTargetOpcode( WorldPacket & recv_data ) +{ + // When this packet send? + CHECK_PACKET_SIZE(recv_data,8); + + uint64 guid ; + recv_data >> guid; + + _player->SetUInt32Value(UNIT_FIELD_TARGET,guid); + + // update reputation list if need + Unit* unit = ObjectAccessor::GetUnit(*_player, guid ); + if(!unit) + return; + + _player->SetFactionVisibleForFactionTemplateId(unit->getFaction()); +} + +void WorldSession::HandleSetSelectionOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,8); + + uint64 guid; + recv_data >> guid; + + _player->SetSelection(guid); + + // update reputation list if need + Unit* unit = ObjectAccessor::GetUnit(*_player, guid ); + if(!unit) + return; + + _player->SetFactionVisibleForFactionTemplateId(unit->getFaction()); +} + +void WorldSession::HandleStandStateChangeOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,1); + + sLog.outDebug( "WORLD: Received CMSG_STAND_STATE_CHANGE" ); + uint8 animstate; + recv_data >> animstate; + + _player->SetStandState(animstate); +} + +void WorldSession::HandleFriendListOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 4); + sLog.outDebug( "WORLD: Received CMSG_CONTACT_LIST" ); + uint32 unk; + recv_data >> unk; + sLog.outDebug("unk value is %u", unk); + _player->GetSocial()->SendSocialList(); +} + +void WorldSession::HandleAddFriendOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 1+1); + + sLog.outDebug( "WORLD: Received CMSG_ADD_FRIEND" ); + + std::string friendName = GetMangosString(LANG_FRIEND_IGNORE_UNKNOWN); + std::string friendNote; + FriendsResult friendResult = FRIEND_NOT_FOUND; + Player *pFriend = NULL; + uint64 friendGuid = 0; + + recv_data >> friendName; + + // recheck + CHECK_PACKET_SIZE(recv_data, (friendName.size()+1)+1); + + recv_data >> friendNote; + + if(!normalizePlayerName(friendName)) + return; + + CharacterDatabase.escape_string(friendName); // prevent SQL injection - normal name don't must changed by this call + + sLog.outDebug( "WORLD: %s asked to add friend : '%s'", + GetPlayer()->GetName(), friendName.c_str() ); + + friendGuid = objmgr.GetPlayerGUIDByName(friendName); + + if(friendGuid) + { + pFriend = ObjectAccessor::FindPlayer(friendGuid); + if(pFriend==GetPlayer()) + friendResult = FRIEND_SELF; + else if(GetPlayer()->GetTeam()!=objmgr.GetPlayerTeamByGUID(friendGuid) && !sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_ADD_FRIEND) && GetSecurity() < SEC_MODERATOR) + friendResult = FRIEND_ENEMY; + else if(GetPlayer()->GetSocial()->HasFriend(GUID_LOPART(friendGuid))) + friendResult = FRIEND_ALREADY; + } + + if (friendGuid && friendResult==FRIEND_NOT_FOUND) + { + if( pFriend && pFriend->IsInWorld() && pFriend->IsVisibleGloballyFor(GetPlayer())) + friendResult = FRIEND_ADDED_ONLINE; + else + friendResult = FRIEND_ADDED_OFFLINE; + + if(!_player->GetSocial()->AddToSocialList(GUID_LOPART(friendGuid), false)) + { + friendResult = FRIEND_LIST_FULL; + sLog.outDebug( "WORLD: %s's friend list is full.", GetPlayer()->GetName()); + } + + _player->GetSocial()->SetFriendNote(GUID_LOPART(friendGuid), friendNote); + + sLog.outDebug( "WORLD: %s Guid found '%u'.", friendName.c_str(), GUID_LOPART(friendGuid)); + } + else if(friendResult==FRIEND_ALREADY) + { + sLog.outDebug( "WORLD: %s Guid Already a Friend.", friendName.c_str() ); + } + else if(friendResult==FRIEND_SELF) + { + sLog.outDebug( "WORLD: %s Guid can't add himself.", friendName.c_str() ); + } + else + { + sLog.outDebug( "WORLD: %s Guid not found.", friendName.c_str() ); + } + + sSocialMgr.SendFriendStatus(GetPlayer(), friendResult, GUID_LOPART(friendGuid), friendName, false); + + sLog.outDebug( "WORLD: Sent (SMSG_FRIEND_STATUS)" ); +} + +void WorldSession::HandleDelFriendOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + uint64 FriendGUID; + + sLog.outDebug( "WORLD: Received CMSG_DEL_FRIEND" ); + + recv_data >> FriendGUID; + + _player->GetSocial()->RemoveFromSocialList(GUID_LOPART(FriendGUID), false); + + sSocialMgr.SendFriendStatus(GetPlayer(), FRIEND_REMOVED, GUID_LOPART(FriendGUID), "", false); + + sLog.outDebug( "WORLD: Sent motd (SMSG_FRIEND_STATUS)" ); +} + +void WorldSession::HandleAddIgnoreOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,1); + + sLog.outDebug( "WORLD: Received CMSG_ADD_IGNORE" ); + + std::string IgnoreName = GetMangosString(LANG_FRIEND_IGNORE_UNKNOWN); + FriendsResult ignoreResult = FRIEND_IGNORE_NOT_FOUND; + uint64 IgnoreGuid = 0; + + recv_data >> IgnoreName; + + if(!normalizePlayerName(IgnoreName)) + return; + + CharacterDatabase.escape_string(IgnoreName); // prevent SQL injection - normal name don't must changed by this call + + sLog.outDebug( "WORLD: %s asked to Ignore: '%s'", + GetPlayer()->GetName(), IgnoreName.c_str() ); + + IgnoreGuid = objmgr.GetPlayerGUIDByName(IgnoreName); + + if(IgnoreGuid) + { + if(IgnoreGuid==GetPlayer()->GetGUID()) + ignoreResult = FRIEND_IGNORE_SELF; + else + { + if( GetPlayer()->GetSocial()->HasIgnore(GUID_LOPART(IgnoreGuid)) ) + ignoreResult = FRIEND_IGNORE_ALREADY; + } + } + + if (IgnoreGuid && ignoreResult == FRIEND_IGNORE_NOT_FOUND) + { + ignoreResult = FRIEND_IGNORE_ADDED; + + _player->GetSocial()->AddToSocialList(GUID_LOPART(IgnoreGuid), true); + } + else if(ignoreResult==FRIEND_IGNORE_ALREADY) + { + sLog.outDebug( "WORLD: %s Guid Already Ignored.", IgnoreName.c_str() ); + } + else if(ignoreResult==FRIEND_IGNORE_SELF) + { + sLog.outDebug( "WORLD: %s Guid can't add himself.", IgnoreName.c_str() ); + } + else + { + sLog.outDebug( "WORLD: %s Guid not found.", IgnoreName.c_str() ); + } + + sSocialMgr.SendFriendStatus(GetPlayer(), ignoreResult, GUID_LOPART(IgnoreGuid), "", false); + + sLog.outDebug( "WORLD: Sent (SMSG_FRIEND_STATUS)" ); +} + +void WorldSession::HandleDelIgnoreOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + uint64 IgnoreGUID; + + sLog.outDebug( "WORLD: Received CMSG_DEL_IGNORE" ); + + recv_data >> IgnoreGUID; + + _player->GetSocial()->RemoveFromSocialList(GUID_LOPART(IgnoreGUID), true); + + sSocialMgr.SendFriendStatus(GetPlayer(), FRIEND_IGNORE_REMOVED, GUID_LOPART(IgnoreGUID), "", false); + + sLog.outDebug( "WORLD: Sent motd (SMSG_FRIEND_STATUS)" ); +} + +void WorldSession::HandleSetFriendNoteOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 8+1); + uint64 guid; + std::string note; + recv_data >> guid >> note; + _player->GetSocial()->SetFriendNote(guid, note); +} + +void WorldSession::HandleBugOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,4+4+1+4+1); + + uint32 suggestion, contentlen; + std::string content; + uint32 typelen; + std::string type; + + recv_data >> suggestion >> contentlen >> content; + + //recheck + CHECK_PACKET_SIZE(recv_data,4+4+(content.size()+1)+4+1); + + recv_data >> typelen >> type; + + if( suggestion == 0 ) + sLog.outDebug( "WORLD: Received CMSG_BUG [Bug Report]" ); + else + sLog.outDebug( "WORLD: Received CMSG_BUG [Suggestion]" ); + + sLog.outDebug( type.c_str( ) ); + sLog.outDebug( content.c_str( ) ); + + CharacterDatabase.escape_string(type); + CharacterDatabase.escape_string(content); + CharacterDatabase.PExecute ("INSERT INTO bugreport (type,content) VALUES('%s', '%s')", type.c_str( ), content.c_str( )); +} + +void WorldSession::HandleCorpseReclaimOpcode(WorldPacket &recv_data) +{ + CHECK_PACKET_SIZE(recv_data,8); + + sLog.outDetail("WORLD: Received CMSG_RECLAIM_CORPSE"); + if (GetPlayer()->isAlive()) + return; + + if (BattleGround * bg = _player->GetBattleGround()) + if(bg->isArena()) + return; + + // body not released yet + if(!GetPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + return; + + Corpse *corpse = GetPlayer()->GetCorpse(); + + if (!corpse ) + return; + + // prevent resurrect before 30-sec delay after body release not finished + if(corpse->GetGhostTime() + GetPlayer()->GetCorpseReclaimDelay(corpse->GetType()==CORPSE_RESURRECTABLE_PVP) > time(NULL)) + return; + + float dist = corpse->GetDistance2d(GetPlayer()); + sLog.outDebug("Corpse 2D Distance: \t%f",dist); + if (dist > CORPSE_RECLAIM_RADIUS) + return; + + uint64 guid; + recv_data >> guid; + + // resurrect + GetPlayer()->ResurrectPlayer(GetPlayer()->InBattleGround() ? 1.0f : 0.5f); + + // spawn bones + GetPlayer()->SpawnCorpseBones(); + + GetPlayer()->SaveToDB(); +} + +void WorldSession::HandleResurrectResponseOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data,8+1); + + sLog.outDetail("WORLD: Received CMSG_RESURRECT_RESPONSE"); + + if(GetPlayer()->isAlive()) + return; + + uint64 guid; + uint8 status; + recv_data >> guid; + recv_data >> status; + + if(status == 0) + { + GetPlayer()->clearResurrectRequestData(); // reject + return; + } + + if(!GetPlayer()->isRessurectRequestedBy(guid)) + return; + + GetPlayer()->ResurectUsingRequestData(); + GetPlayer()->SaveToDB(); +} + +void WorldSession::HandleAreaTriggerOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data,4); + + sLog.outDebug("WORLD: Received CMSG_AREATRIGGER"); + + uint32 Trigger_ID; + + recv_data >> Trigger_ID; + sLog.outDebug("Trigger ID:%u",Trigger_ID); + + if(GetPlayer()->isInFlight()) + { + sLog.outDebug("Player '%s' (GUID: %u) in flight, ignore Area Trigger ID:%u",GetPlayer()->GetName(),GetPlayer()->GetGUIDLow(), Trigger_ID); + return; + } + + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID); + if(!atEntry) + { + sLog.outDebug("Player '%s' (GUID: %u) send unknown (by DBC) Area Trigger ID:%u",GetPlayer()->GetName(),GetPlayer()->GetGUIDLow(), Trigger_ID); + return; + } + + if (GetPlayer()->GetMapId()!=atEntry->mapid) + { + sLog.outDebug("Player '%s' (GUID: %u) too far (trigger map: %u player map: %u), ignore Area Trigger ID: %u", GetPlayer()->GetName(), atEntry->mapid, GetPlayer()->GetMapId(), GetPlayer()->GetGUIDLow(), Trigger_ID); + return; + } + + // delta is safe radius + const float delta = 5.0f; + // check if player in the range of areatrigger + Player* pl = GetPlayer(); + + if (atEntry->radius > 0) + { + // if we have radius check it + float dist = pl->GetDistance(atEntry->x,atEntry->y,atEntry->z); + if(dist > atEntry->radius + delta) + { + sLog.outDebug("Player '%s' (GUID: %u) too far (radius: %f distance: %f), ignore Area Trigger ID: %u", + pl->GetName(), pl->GetGUIDLow(), atEntry->radius, dist, Trigger_ID); + return; + } + } + else + { + // we have only extent + float dx = pl->GetPositionX() - atEntry->x; + float dy = pl->GetPositionY() - atEntry->y; + float dz = pl->GetPositionZ() - atEntry->z; + double es = sin(atEntry->box_orientation); + double ec = cos(atEntry->box_orientation); + // calc rotated vector based on extent axis + double rotateDx = dx*ec - dy*es; + double rotateDy = dx*es + dy*ec; + + if( (fabs(rotateDx) > atEntry->box_x/2 + delta) || + (fabs(rotateDy) > atEntry->box_y/2 + delta) || + (fabs(dz) > atEntry->box_z/2 + delta) ) + { + sLog.outDebug("Player '%s' (GUID: %u) too far (1/2 box X: %f 1/2 box Y: %u 1/2 box Z: %u rotate dX: %f rotate dY: %f dZ:%f), ignore Area Trigger ID: %u", + pl->GetName(), pl->GetGUIDLow(), atEntry->box_x/2, atEntry->box_y/2, atEntry->box_z/2, rotateDx, rotateDy, dz, Trigger_ID); + return; + } + } + + if(Script->scriptAreaTrigger(GetPlayer(), atEntry)) + return; + + uint32 quest_id = objmgr.GetQuestForAreaTrigger( Trigger_ID ); + if( quest_id && GetPlayer()->isAlive() && GetPlayer()->IsActiveQuest(quest_id) ) + { + Quest const* pQuest = objmgr.GetQuestTemplate(quest_id); + if( pQuest ) + { + if(GetPlayer()->GetQuestStatus(quest_id) == QUEST_STATUS_INCOMPLETE) + GetPlayer()->AreaExploredOrEventHappens( quest_id ); + } + } + + if(objmgr.IsTavernAreaTrigger(Trigger_ID)) + { + // set resting flag we are in the inn + GetPlayer()->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING); + GetPlayer()->InnEnter(time(NULL), atEntry->mapid, atEntry->x, atEntry->y, atEntry->z); + GetPlayer()->SetRestType(REST_TYPE_IN_TAVERN); + + if(sWorld.IsFFAPvPRealm()) + GetPlayer()->RemoveFlag(PLAYER_FLAGS,PLAYER_FLAGS_FFA_PVP); + + return; + } + + if(GetPlayer()->InBattleGround()) + { + BattleGround* bg = GetPlayer()->GetBattleGround(); + if(bg) + if(bg->GetStatus() == STATUS_IN_PROGRESS) + bg->HandleAreaTrigger(GetPlayer(), Trigger_ID); + + return; + } + + // NULL if all values default (non teleport trigger) + AreaTrigger const* at = objmgr.GetAreaTrigger(Trigger_ID); + if(!at) + return; + + if(!GetPlayer()->isGameMaster()) + { + uint32 missingLevel = 0; + if(GetPlayer()->getLevel() < at->requiredLevel && !sWorld.getConfig(CONFIG_INSTANCE_IGNORE_LEVEL)) + missingLevel = at->requiredLevel; + + // must have one or the other, report the first one that's missing + uint32 missingItem = 0; + if(at->requiredItem) + { + if(!GetPlayer()->HasItemCount(at->requiredItem, 1) && + (!at->requiredItem2 || !GetPlayer()->HasItemCount(at->requiredItem2, 1))) + missingItem = at->requiredItem; + } + else if(at->requiredItem2 && !GetPlayer()->HasItemCount(at->requiredItem2, 1)) + missingItem = at->requiredItem2; + + uint32 missingKey = 0; + if(GetPlayer()->GetDifficulty() == DIFFICULTY_HEROIC) + { + if(at->heroicKey) + { + if(!GetPlayer()->HasItemCount(at->heroicKey, 1) && + (!at->heroicKey2 || !GetPlayer()->HasItemCount(at->heroicKey2, 1))) + missingKey = at->heroicKey; + } + else if(at->heroicKey2 && !GetPlayer()->HasItemCount(at->heroicKey2, 1)) + missingKey = at->heroicKey2; + } + + uint32 missingQuest = 0; + if(at->requiredQuest && !GetPlayer()->GetQuestRewardStatus(at->requiredQuest)) + missingQuest = at->requiredQuest; + + if(missingLevel || missingItem || missingKey || missingQuest) + { + // TODO: all this is probably wrong + if(missingItem) + SendAreaTriggerMessage(GetMangosString(LANG_LEVEL_MINREQUIRED_AND_ITEM), at->requiredLevel, objmgr.GetItemPrototype(missingItem)->Name1); + else if(missingKey) + GetPlayer()->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_DIFFICULTY2); + else if(missingQuest) + SendAreaTriggerMessage(at->requiredFailedText.c_str()); + else if(missingLevel) + SendAreaTriggerMessage(GetMangosString(LANG_LEVEL_MINREQUIRED), missingLevel); + return; + } + } + + GetPlayer()->TeleportTo(at->target_mapId,at->target_X,at->target_Y,at->target_Z,at->target_Orientation,TELE_TO_NOT_LEAVE_TRANSPORT); +} + +void WorldSession::HandleUpdateAccountData(WorldPacket &/*recv_data*/) +{ + sLog.outDetail("WORLD: Received CMSG_UPDATE_ACCOUNT_DATA"); + //recv_data.hexlike(); +} + +void WorldSession::HandleRequestAccountData(WorldPacket& /*recv_data*/) +{ + sLog.outDetail("WORLD: Received CMSG_REQUEST_ACCOUNT_DATA"); + //recv_data.hexlike(); +} + +void WorldSession::HandleSetActionButtonOpcode(WorldPacket& recv_data) +{ + CHECK_PACKET_SIZE(recv_data,1+2+1+1); + + sLog.outDebug( "WORLD: Received CMSG_SET_ACTION_BUTTON" ); + uint8 button, misc, type; + uint16 action; + recv_data >> button >> action >> misc >> type; + sLog.outDetail( "BUTTON: %u ACTION: %u TYPE: %u MISC: %u", button, action, type, misc ); + if(action==0) + { + sLog.outDetail( "MISC: Remove action from button %u", button ); + + GetPlayer()->removeActionButton(button); + } + else + { + if(type==ACTION_BUTTON_MACRO || type==ACTION_BUTTON_CMACRO) + { + sLog.outDetail( "MISC: Added Macro %u into button %u", action, button ); + GetPlayer()->addActionButton(button,action,type,misc); + } + else if(type==ACTION_BUTTON_SPELL) + { + sLog.outDetail( "MISC: Added Action %u into button %u", action, button ); + GetPlayer()->addActionButton(button,action,type,misc); + } + else if(type==ACTION_BUTTON_ITEM) + { + sLog.outDetail( "MISC: Added Item %u into button %u", action, button ); + GetPlayer()->addActionButton(button,action,type,misc); + } + else + sLog.outError( "MISC: Unknown action button type %u for action %u into button %u", type, action, button ); + } +} + +void WorldSession::HandleCompleteCinema( WorldPacket & /*recv_data*/ ) +{ + DEBUG_LOG( "WORLD: Player is watching cinema" ); +} + +void WorldSession::HandleNextCinematicCamera( WorldPacket & /*recv_data*/ ) +{ + DEBUG_LOG( "WORLD: Which movie to play" ); +} + +void WorldSession::HandleMoveTimeSkippedOpcode( WorldPacket & /*recv_data*/ ) +{ + /* WorldSession::Update( getMSTime() );*/ + DEBUG_LOG( "WORLD: Time Lag/Synchronization Resent/Update" ); + + /* + CHECK_PACKET_SIZE(recv_data,8+4); + uint64 guid; + uint32 time_skipped; + recv_data >> guid; + recv_data >> time_skipped; + sLog.outDebug( "WORLD: CMSG_MOVE_TIME_SKIPPED" ); + + /// TODO + must be need use in mangos + We substract server Lags to move time ( AntiLags ) + for exmaple + GetPlayer()->ModifyLastMoveTime( -int32(time_skipped) ); + */ +} + +void WorldSession::HandleFeatherFallAck(WorldPacket &/*recv_data*/) +{ + DEBUG_LOG("WORLD: CMSG_MOVE_FEATHER_FALL_ACK"); +} + +void WorldSession::HandleMoveUnRootAck(WorldPacket&/* recv_data*/) +{ + /* + CHECK_PACKET_SIZE(recv_data,8+8+4+4+4+4+4); + + sLog.outDebug( "WORLD: CMSG_FORCE_MOVE_UNROOT_ACK" ); + recv_data.hexlike(); + uint64 guid; + uint64 unknown1; + uint32 unknown2; + float PositionX; + float PositionY; + float PositionZ; + float Orientation; + + recv_data >> guid; + recv_data >> unknown1; + recv_data >> unknown2; + recv_data >> PositionX; + recv_data >> PositionY; + recv_data >> PositionZ; + recv_data >> Orientation; + + // TODO for later may be we can use for anticheat + DEBUG_LOG("Guid " I64FMTD,guid); + DEBUG_LOG("unknown1 " I64FMTD,unknown1); + DEBUG_LOG("unknown2 %u",unknown2); + DEBUG_LOG("X %f",PositionX); + DEBUG_LOG("Y %f",PositionY); + DEBUG_LOG("Z %f",PositionZ); + DEBUG_LOG("O %f",Orientation); + */ +} + +void WorldSession::HandleMoveRootAck(WorldPacket&/* recv_data*/) +{ + /* + CHECK_PACKET_SIZE(recv_data,8+8+4+4+4+4+4); + + sLog.outDebug( "WORLD: CMSG_FORCE_MOVE_ROOT_ACK" ); + recv_data.hexlike(); + uint64 guid; + uint64 unknown1; + uint32 unknown2; + float PositionX; + float PositionY; + float PositionZ; + float Orientation; + + recv_data >> guid; + recv_data >> unknown1; + recv_data >> unknown2; + recv_data >> PositionX; + recv_data >> PositionY; + recv_data >> PositionZ; + recv_data >> Orientation; + + // for later may be we can use for anticheat + DEBUG_LOG("Guid " I64FMTD,guid); + DEBUG_LOG("unknown1 " I64FMTD,unknown1); + DEBUG_LOG("unknown1 %u",unknown2); + DEBUG_LOG("X %f",PositionX); + DEBUG_LOG("Y %f",PositionY); + DEBUG_LOG("Z %f",PositionZ); + DEBUG_LOG("O %f",Orientation); + */ +} + +void WorldSession::HandleMoveTeleportAck(WorldPacket&/* recv_data*/) +{ + /* + CHECK_PACKET_SIZE(recv_data,8+4); + + sLog.outDebug("MSG_MOVE_TELEPORT_ACK"); + uint64 guid; + uint32 flags, time; + + recv_data >> guid; + recv_data >> flags >> time; + DEBUG_LOG("Guid " I64FMTD,guid); + DEBUG_LOG("Flags %u, time %u",flags, time/1000); + */ +} + +void WorldSession::HandleSetActionBar(WorldPacket& recv_data) +{ + CHECK_PACKET_SIZE(recv_data,1); + + uint8 ActionBar; + + recv_data >> ActionBar; + + if(!GetPlayer()) // ignore until not logged (check needed because STATUS_AUTHED) + { + if(ActionBar!=0) + sLog.outError("WorldSession::HandleSetActionBar in not logged state with value: %u, ignored",uint32(ActionBar)); + return; + } + + GetPlayer()->SetByteValue(PLAYER_FIELD_BYTES, 2, ActionBar); +} + +void WorldSession::HandleWardenDataOpcode(WorldPacket& /*recv_data*/) +{ + /* + CHECK_PACKET_SIZE(recv_data,1); + + uint8 tmp; + recv_data >> tmp; + sLog.outDebug("Received opcode CMSG_WARDEN_DATA, not resolve.uint8 = %u",tmp); + */ +} + +void WorldSession::HandlePlayedTime(WorldPacket& /*recv_data*/) +{ + uint32 TotalTimePlayed = GetPlayer()->GetTotalPlayedTime(); + uint32 LevelPlayedTime = GetPlayer()->GetLevelPlayedTime(); + + WorldPacket data(SMSG_PLAYED_TIME, 8); + data << TotalTimePlayed; + data << LevelPlayedTime; + SendPacket(&data); +} + +void WorldSession::HandleInspectOpcode(WorldPacket& recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + uint64 guid; + recv_data >> guid; + DEBUG_LOG("Inspected guid is " I64FMTD, guid); + + _player->SetSelection(guid); + + Player *plr = objmgr.GetPlayer(guid); + if(!plr) // wrong player + return; + + uint32 talent_points = 0x3D; + uint32 guid_size = plr->GetPackGUID().size(); + WorldPacket data(SMSG_INSPECT_TALENT, 4+talent_points); + data.append(plr->GetPackGUID()); + data << uint32(talent_points); + + // fill by 0 talents array + for(uint32 i = 0; i < talent_points; ++i) + data << uint8(0); + + if(sWorld.getConfig(CONFIG_TALENTS_INSPECTING) || _player->isGameMaster()) + { + // find class talent tabs (all players have 3 talent tabs) + uint32 const* talentTabIds = GetTalentTabPages(plr->getClass()); + + uint32 talentTabPos = 0; // pos of first talent rank in tab including all prev tabs + for(uint32 i = 0; i < 3; ++i) + { + uint32 talentTabId = talentTabIds[i]; + + // fill by real data + for(uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) + { + TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); + if(!talentInfo) + continue; + + // skip another tab talents + if(talentInfo->TalentTab != talentTabId) + continue; + + // find talent rank + uint32 curtalent_maxrank = 0; + for(uint32 k = 5; k > 0; --k) + { + if(talentInfo->RankID[k-1] && plr->HasSpell(talentInfo->RankID[k-1])) + { + curtalent_maxrank = k; + break; + } + } + + // not learned talent + if(!curtalent_maxrank) + continue; + + // 1 rank talent bit index + uint32 curtalent_index = talentTabPos + GetTalentInspectBitPosInTab(talentId); + + uint32 curtalent_rank_index = curtalent_index+curtalent_maxrank-1; + + // slot/offset in 7-bit bytes + uint32 curtalent_rank_slot7 = curtalent_rank_index / 7; + uint32 curtalent_rank_offset7 = curtalent_rank_index % 7; + + // rank pos with skipped 8 bit + uint32 curtalent_rank_index2 = curtalent_rank_slot7 * 8 + curtalent_rank_offset7; + + // slot/offset in 8-bit bytes with skipped high bit + uint32 curtalent_rank_slot = curtalent_rank_index2 / 8; + uint32 curtalent_rank_offset = curtalent_rank_index2 % 8; + + // apply mask + uint32 val = data.read(guid_size + 4 + curtalent_rank_slot); + val |= (1 << curtalent_rank_offset); + data.put(guid_size + 4 + curtalent_rank_slot, val & 0xFF); + } + + talentTabPos += GetTalentTabInspectBitSize(talentTabId); + } + } + + SendPacket(&data); +} + +void WorldSession::HandleInspectHonorStatsOpcode(WorldPacket& recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + uint64 guid; + recv_data >> guid; + + Player *player = objmgr.GetPlayer(guid); + + if(!player) + { + sLog.outError("InspectHonorStats: WTF, player not found..."); + return; + } + + WorldPacket data(MSG_INSPECT_HONOR_STATS, 8+1+4*4); + data << uint64(player->GetGUID()); + data << uint8(player->GetUInt32Value(PLAYER_FIELD_HONOR_CURRENCY)); + data << uint32(player->GetUInt32Value(PLAYER_FIELD_KILLS)); + data << uint32(player->GetUInt32Value(PLAYER_FIELD_TODAY_CONTRIBUTION)); + data << uint32(player->GetUInt32Value(PLAYER_FIELD_YESTERDAY_CONTRIBUTION)); + data << uint32(player->GetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS)); + SendPacket(&data); +} + +void WorldSession::HandleWorldTeleportOpcode(WorldPacket& recv_data) +{ + CHECK_PACKET_SIZE(recv_data,4+4+4+4+4+4); + + // write in client console: worldport 469 452 6454 2536 180 or /console worldport 469 452 6454 2536 180 + // Received opcode CMSG_WORLD_TELEPORT + // Time is ***, map=469, x=452.000000, y=6454.000000, z=2536.000000, orient=3.141593 + + //sLog.outDebug("Received opcode CMSG_WORLD_TELEPORT"); + + if(GetPlayer()->isInFlight()) + { + sLog.outDebug("Player '%s' (GUID: %u) in flight, ignore worldport command.",GetPlayer()->GetName(),GetPlayer()->GetGUIDLow()); + return; + } + + uint32 time; + uint32 mapid; + float PositionX; + float PositionY; + float PositionZ; + float Orientation; + + recv_data >> time; // time in m.sec. + recv_data >> mapid; + recv_data >> PositionX; + recv_data >> PositionY; + recv_data >> PositionZ; + recv_data >> Orientation; // o (3.141593 = 180 degrees) + DEBUG_LOG("Time %u sec, map=%u, x=%f, y=%f, z=%f, orient=%f", time/1000, mapid, PositionX, PositionY, PositionZ, Orientation); + + if (GetSecurity() >= SEC_ADMINISTRATOR) + GetPlayer()->TeleportTo(mapid,PositionX,PositionY,PositionZ,Orientation); + else + SendNotification(LANG_YOU_NOT_HAVE_PERMISSION); + sLog.outDebug("Received worldport command from player %s", GetPlayer()->GetName()); +} + +void WorldSession::HandleWhoisOpcode(WorldPacket& recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 1); + + sLog.outDebug("Received opcode CMSG_WHOIS"); + std::string charname; + recv_data >> charname; + + if (GetSecurity() < SEC_ADMINISTRATOR) + { + SendNotification(LANG_YOU_NOT_HAVE_PERMISSION); + return; + } + + if(charname.empty()) + { + SendNotification(LANG_NEED_CHARACTER_NAME); + return; + } + + Player *plr = objmgr.GetPlayer(charname.c_str()); + + if(!plr) + { + SendNotification(LANG_PLAYER_NOT_EXIST_OR_OFFLINE, charname.c_str()); + return; + } + + uint32 accid = plr->GetSession()->GetAccountId(); + + QueryResult *result = loginDatabase.PQuery("SELECT username,email,last_ip FROM account WHERE id=%u", accid); + if(!result) + { + SendNotification(LANG_ACCOUNT_FOR_PLAYER_NOT_FOUND, charname.c_str()); + return; + } + + Field *fields = result->Fetch(); + std::string acc = fields[0].GetCppString(); + if(acc.empty()) + acc = "Unknown"; + std::string email = fields[1].GetCppString(); + if(email.empty()) + email = "Unknown"; + std::string lastip = fields[2].GetCppString(); + if(lastip.empty()) + lastip = "Unknown"; + + std::string msg = charname + "'s " + "account is " + acc + ", e-mail: " + email + ", last ip: " + lastip; + + WorldPacket data(SMSG_WHOIS, msg.size()+1); + data << msg; + _player->GetSession()->SendPacket(&data); + + delete result; + + sLog.outDebug("Received whois command from player %s for character %s", GetPlayer()->GetName(), charname.c_str()); +} + +void WorldSession::HandleReportSpamOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 1+8); + sLog.outDebug("WORLD: CMSG_REPORT_SPAM"); + recv_data.hexlike(); + + uint8 spam_type; // 0 - mail, 1 - chat + uint64 spammer_guid; + uint32 unk1, unk2, unk3, unk4 = 0; + std::string description = ""; + recv_data >> spam_type; // unk 0x01 const, may be spam type (mail/chat) + recv_data >> spammer_guid; // player guid + switch(spam_type) + { + case 0: + CHECK_PACKET_SIZE(recv_data, recv_data.rpos()+4+4+4); + recv_data >> unk1; // const 0 + recv_data >> unk2; // probably mail id + recv_data >> unk3; // const 0 + break; + case 1: + CHECK_PACKET_SIZE(recv_data, recv_data.rpos()+4+4+4+4+1); + recv_data >> unk1; // probably language + recv_data >> unk2; // message type? + recv_data >> unk3; // probably channel id + recv_data >> unk4; // unk random value + recv_data >> description; // spam description string (messagetype, channel name, player name, message) + break; + } + + // NOTE: all chat messages from this spammer automatically ignored by spam reporter until logout in case chat spam. + // if it's mail spam - ALL mails from this spammer automatically removed by client + + // Complaint Received message + WorldPacket data(SMSG_COMPLAIN_RESULT, 1); + data << uint8(0); + SendPacket(&data); + + sLog.outDebug("REPORT SPAM: type %u, guid %u, unk1 %u, unk2 %u, unk3 %u, unk4 %u, message %s", spam_type, GUID_LOPART(spammer_guid), unk1, unk2, unk3, unk4, description.c_str()); +} + +void WorldSession::HandleRealmStateRequestOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 4); + + sLog.outDebug("CMSG_REALM_SPLIT"); + + uint32 unk; + std::string split_date = "01/01/01"; + recv_data >> unk; + + WorldPacket data(SMSG_REALM_SPLIT, 4+4+split_date.size()+1); + data << unk; + data << uint32(0x00000000); // realm split state + // split states: + // 0x0 realm normal + // 0x1 realm split + // 0x2 realm split pending + data << split_date; + SendPacket(&data); + //sLog.outDebug("response sent %u", unk); +} + +void WorldSession::HandleFarSightOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 1); + + sLog.outDebug("WORLD: CMSG_FAR_SIGHT"); + //recv_data.hexlike(); + + uint8 unk; + recv_data >> unk; + + switch(unk) + { + case 0: + //WorldPacket data(SMSG_CLEAR_FAR_SIGHT_IMMEDIATE, 0) + //SendPacket(&data); + //_player->SetUInt64Value(PLAYER_FARSIGHT, 0); + sLog.outDebug("Removed FarSight from player %u", _player->GetGUIDLow()); + break; + case 1: + sLog.outDebug("Added FarSight " I64FMTD " to player %u", _player->GetUInt64Value(PLAYER_FARSIGHT), _player->GetGUIDLow()); + break; + } +} + +void WorldSession::HandleChooseTitleOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 4); + + sLog.outDebug("CMSG_SET_TITLE"); + + int32 title; + recv_data >> title; + + // -1 at none + if(title > 0 && title < 64) + { + if(!GetPlayer()->HasFlag64(PLAYER__FIELD_KNOWN_TITLES,uint64(1) << title)) + return; + } + else + title = 0; + + GetPlayer()->SetUInt32Value(PLAYER_CHOSEN_TITLE, title); +} + +void WorldSession::HandleAllowMoveAckOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 4+4); + + sLog.outDebug("CMSG_ALLOW_MOVE_ACK"); + + uint32 counter, time_; + recv_data >> counter >> time_; + + // time_ seems always more than getMSTime() + uint32 diff = getMSTimeDiff(getMSTime(),time_); + + sLog.outDebug("response sent: counter %u, time %u (HEX: %X), ms. time %u, diff %u", counter, time_, time_, getMSTime(), diff); +} + +void WorldSession::HandleResetInstancesOpcode( WorldPacket & /*recv_data*/ ) +{ + sLog.outDebug("WORLD: CMSG_RESET_INSTANCES"); + Group *pGroup = _player->GetGroup(); + if(pGroup) + { + if(pGroup->IsLeader(_player->GetGUID())) + pGroup->ResetInstances(INSTANCE_RESET_ALL, _player); + } + else + _player->ResetInstances(INSTANCE_RESET_ALL); +} + +void WorldSession::HandleDungeonDifficultyOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 4); + + sLog.outDebug("MSG_SET_DUNGEON_DIFFICULTY"); + + uint32 mode; + recv_data >> mode; + + if(mode == _player->GetDifficulty()) + return; + + if(mode > DIFFICULTY_HEROIC) + { + sLog.outError("WorldSession::HandleDungeonDifficultyOpcode: player %d sent an invalid instance mode %d!", _player->GetGUIDLow(), mode); + return; + } + + // cannot reset while in an instance + Map *map = _player->GetMap(); + if(map && map->IsDungeon()) + { + sLog.outError("WorldSession::HandleDungeonDifficultyOpcode: player %d tried to reset the instance while inside!", _player->GetGUIDLow()); + return; + } + + if(_player->getLevel() < LEVELREQUIREMENT_HEROIC) + return; + Group *pGroup = _player->GetGroup(); + if(pGroup) + { + if(pGroup->IsLeader(_player->GetGUID())) + { + // the difficulty is set even if the instances can't be reset + //_player->SendDungeonDifficulty(true); + pGroup->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY, _player); + pGroup->SetDifficulty(mode); + } + } + else + { + _player->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY); + _player->SetDifficulty(mode); + } +} + +void WorldSession::HandleNewUnknownOpcode( WorldPacket & recv_data ) +{ + sLog.outDebug("New Unknown Opcode %u", recv_data.GetOpcode()); + recv_data.hexlike(); + /* + New Unknown Opcode 837 + STORAGE_SIZE: 60 + 02 00 00 00 00 00 00 00 | 00 00 00 00 01 20 00 00 + 89 EB 33 01 71 5C 24 C4 | 15 03 35 45 74 47 8B 42 + BA B8 1B 40 00 00 00 00 | 00 00 00 00 77 66 42 BF + 23 91 26 3F 00 00 60 41 | 00 00 00 00 + + New Unknown Opcode 837 + STORAGE_SIZE: 44 + 02 00 00 00 00 00 00 00 | 00 00 00 00 00 00 80 00 + 7B 80 34 01 84 EA 2B C4 | 5F A1 36 45 C9 39 1C 42 + BA B8 1B 40 CE 06 00 00 | 00 00 80 3F + */ +} + +void WorldSession::HandleDismountOpcode( WorldPacket & /*recv_data*/ ) +{ + sLog.outDebug("WORLD: CMSG_CANCEL_MOUNT_AURA"); + //recv_data.hexlike(); + + //If player is not mounted, so go out :) + if (!_player->IsMounted()) // not blizz like; no any messages on blizz + { + ChatHandler(this).SendSysMessage(LANG_CHAR_NON_MOUNTED); + return; + } + + if(_player->isInFlight()) // not blizz like; no any messages on blizz + { + ChatHandler(this).SendSysMessage(LANG_YOU_IN_FLIGHT); + return; + } + + _player->Unmount(); + _player->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); +} + +void WorldSession::HandleMoveFlyModeChangeAckOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 8+4+4); + + // fly mode on/off + sLog.outDebug("WORLD: CMSG_MOVE_SET_CAN_FLY_ACK"); + //recv_data.hexlike(); + + uint64 guid; + uint32 unk; + uint32 flags; + + recv_data >> guid >> unk >> flags; + + _player->SetUnitMovementFlags(flags); + /* + on: + 25 00 00 00 00 00 00 00 | 00 00 00 00 00 00 80 00 + 85 4E A9 01 19 BA 7A C3 | 42 0D 70 44 44 B0 A8 42 + 78 15 94 40 39 03 00 00 | 00 00 80 3F + off: + 25 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 + 10 FD A9 01 19 BA 7A C3 | 42 0D 70 44 44 B0 A8 42 + 78 15 94 40 39 03 00 00 | 00 00 00 00 + */ +} + +void WorldSession::HandleRequestPetInfoOpcode( WorldPacket & /*recv_data */) +{ + /* + sLog.outDebug("WORLD: CMSG_REQUEST_PET_INFO"); + recv_data.hexlike(); + */ +} + +void WorldSession::HandleSetTaxiBenchmarkOpcode( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data, 1); + + uint8 mode; + recv_data >> mode; + + sLog.outDebug("Client used \"/timetest %d\" command", mode); +} diff --git a/src/game/MotionMaster.cpp b/src/game/MotionMaster.cpp index cf910bf50b2..2a5cc05e986 100644 --- a/src/game/MotionMaster.cpp +++ b/src/game/MotionMaster.cpp @@ -1,342 +1,342 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "MotionMaster.h" -#include "CreatureAISelector.h" -#include "Creature.h" -#include "Traveller.h" - -#include "ConfusedMovementGenerator.h" -#include "FleeingMovementGenerator.h" -#include "HomeMovementGenerator.h" -#include "IdleMovementGenerator.h" -#include "PointMovementGenerator.h" -#include "TargetedMovementGenerator.h" -#include "WaypointMovementGenerator.h" - -#include - -inline bool isStatic(MovementGenerator *mv) -{ - return (mv == &si_idleMovement); -} - -void -MotionMaster::Initialize() -{ - // clear ALL movement generators (including default) - while(!empty()) - { - MovementGenerator *curr = top(); - curr->Finalize(*i_owner); - pop(); - if( !isStatic( curr ) ) - delete curr; - } - - // set new default movement generator - if(i_owner->GetTypeId() == TYPEID_UNIT) - { - MovementGenerator* movement = FactorySelector::selectMovementGenerator((Creature*)i_owner); - push( movement == NULL ? &si_idleMovement : movement ); - top()->Initialize(*i_owner); - } - else - push(&si_idleMovement); -} - -MotionMaster::~MotionMaster() -{ - // clear ALL movement generators (including default) - while(!empty()) - { - MovementGenerator *curr = top(); - curr->Finalize(*i_owner); - pop(); - if( !isStatic( curr ) ) - delete curr; - } -} - -void -MotionMaster::UpdateMotion(const uint32 &diff) -{ - if( i_owner->hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED) ) - return; - assert( !empty() ); - if (!top()->Update(*i_owner, diff)) - MovementExpired(); -} - -void -MotionMaster::Clear(bool reset) -{ - while( !empty() && size() > 1 ) - { - MovementGenerator *curr = top(); - curr->Finalize(*i_owner); - pop(); - if( !isStatic( curr ) ) - delete curr; - } - - if (reset) - { - assert( !empty() ); - top()->Reset(*i_owner); - } -} - -void -MotionMaster::MovementExpired(bool reset) -{ - if( empty() || size() == 1 ) - return; - - MovementGenerator *curr = top(); - curr->Finalize(*i_owner); - pop(); - - if( !isStatic(curr) ) - delete curr; - - assert( !empty() ); - while( !empty() && top()->GetMovementGeneratorType() == TARGETED_MOTION_TYPE ) - { - // Should check if target is still valid? If not valid it will crash. - curr = top(); - curr->Finalize(*i_owner); - pop(); - delete curr; - } - if( empty() ) - Initialize(); - if (reset) top()->Reset(*i_owner); -} - -void MotionMaster::MoveIdle() -{ - if( empty() || !isStatic( top() ) ) - push( &si_idleMovement ); -} - -void -MotionMaster::MoveTargetedHome() -{ - if(i_owner->hasUnitState(UNIT_STAT_FLEEING)) - return; - - Clear(false); - - if(i_owner->GetTypeId()==TYPEID_UNIT && !((Creature*)i_owner)->GetCharmerOrOwnerGUID()) - { - DEBUG_LOG("Creature (Entry: %u GUID: %u) targeted home", i_owner->GetEntry(), i_owner->GetGUIDLow()); - Mutate(new HomeMovementGenerator()); - } - else if(i_owner->GetTypeId()==TYPEID_UNIT && ((Creature*)i_owner)->GetCharmerOrOwnerGUID()) - { - sLog.outError("Pet or controlled creature (Entry: %u GUID: %u) attempt targeted home", - i_owner->GetEntry(), i_owner->GetGUIDLow() ); - } - else - { - sLog.outError("Player (GUID: %u) attempt targeted home", i_owner->GetGUIDLow() ); - } -} - -void -MotionMaster::MoveConfused() -{ - if(i_owner->GetTypeId()==TYPEID_PLAYER) - { - DEBUG_LOG("Player (GUID: %u) move confused", i_owner->GetGUIDLow() ); - Mutate(new ConfusedMovementGenerator()); - } - else - { - DEBUG_LOG("Creature (Entry: %u GUID: %u) move confused", - i_owner->GetEntry(), i_owner->GetGUIDLow() ); - Mutate(new ConfusedMovementGenerator()); - } -} - -void -MotionMaster::MoveChase(Unit* target, float dist, float angle) -{ - // ignore movement request if target not exist - if(!target) - return; - - i_owner->clearUnitState(UNIT_STAT_FOLLOW); - if(i_owner->GetTypeId()==TYPEID_PLAYER) - { - DEBUG_LOG("Player (GUID: %u) chase to %s (GUID: %u)", - target->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", - target->GetTypeId()==TYPEID_PLAYER ? i_owner->GetGUIDLow() : ((Creature*)i_owner)->GetDBTableGUIDLow() ); - Mutate(new TargetedMovementGenerator(*target,dist,angle)); - } - else - { - DEBUG_LOG("Creature (Entry: %u GUID: %u) chase to %s (GUID: %u)", - i_owner->GetEntry(), i_owner->GetGUIDLow(), - target->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", - target->GetTypeId()==TYPEID_PLAYER ? target->GetGUIDLow() : ((Creature*)target)->GetDBTableGUIDLow() ); - Mutate(new TargetedMovementGenerator(*target,dist,angle)); - } -} - -void -MotionMaster::MoveFollow(Unit* target, float dist, float angle) -{ - Clear(); - - // ignore movement request if target not exist - if(!target) - return; - - i_owner->addUnitState(UNIT_STAT_FOLLOW); - if(i_owner->GetTypeId()==TYPEID_PLAYER) - { - DEBUG_LOG("Player (GUID: %u) follow to %s (GUID: %u)", i_owner->GetGUIDLow(), - target->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", - target->GetTypeId()==TYPEID_PLAYER ? i_owner->GetGUIDLow() : ((Creature*)i_owner)->GetDBTableGUIDLow() ); - Mutate(new TargetedMovementGenerator(*target,dist,angle)); - } - else - { - DEBUG_LOG("Creature (Entry: %u GUID: %u) follow to %s (GUID: %u)", - i_owner->GetEntry(), i_owner->GetGUIDLow(), - target->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", - target->GetTypeId()==TYPEID_PLAYER ? target->GetGUIDLow() : ((Creature*)target)->GetDBTableGUIDLow() ); - Mutate(new TargetedMovementGenerator(*target,dist,angle)); - } -} - -void -MotionMaster::MovePoint(uint32 id, float x, float y, float z) -{ - if(i_owner->GetTypeId()==TYPEID_PLAYER) - { - DEBUG_LOG("Player (GUID: %u) targeted point (Id: %u X: %f Y: %f Z: %f)", i_owner->GetGUIDLow(), id, x, y, z ); - Mutate(new PointMovementGenerator(id,x,y,z)); - } - else - { - DEBUG_LOG("Creature (Entry: %u GUID: %u) targeted point (ID: %u X: %f Y: %f Z: %f)", - i_owner->GetEntry(), i_owner->GetGUIDLow(), id, x, y, z ); - Mutate(new PointMovementGenerator(id,x,y,z)); - } -} - -void -MotionMaster::MoveFleeing(Unit* enemy) -{ - if(!enemy) - return; - - if(i_owner->GetTypeId()==TYPEID_PLAYER) - { - DEBUG_LOG("Player (GUID: %u) flee from %s (GUID: %u)", i_owner->GetGUIDLow(), - enemy->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", - enemy->GetTypeId()==TYPEID_PLAYER ? enemy->GetGUIDLow() : ((Creature*)enemy)->GetDBTableGUIDLow() ); - Mutate(new FleeingMovementGenerator(enemy->GetGUID())); - } - else - { - DEBUG_LOG("Creature (Entry: %u GUID: %u) flee from %s (GUID: %u)", - i_owner->GetEntry(), i_owner->GetGUIDLow(), - enemy->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", - enemy->GetTypeId()==TYPEID_PLAYER ? enemy->GetGUIDLow() : ((Creature*)enemy)->GetDBTableGUIDLow() ); - Mutate(new FleeingMovementGenerator(enemy->GetGUID())); - } -} - -void -MotionMaster::MoveTaxiFlight(uint32 path, uint32 pathnode) -{ - if(i_owner->GetTypeId()==TYPEID_PLAYER) - { - DEBUG_LOG("Player (GUID: %u) taxi to (Path %u node %u)", i_owner->GetGUIDLow(), path, pathnode); - FlightPathMovementGenerator* mgen = new FlightPathMovementGenerator(path,pathnode); - Mutate(mgen); - } - else - { - sLog.outError("Creature (Entry: %u GUID: %u) attempt taxi to (Path %u node %u)", - i_owner->GetEntry(), i_owner->GetGUIDLow(), path, pathnode ); - } -} - -void -MotionMaster::MoveDistract(uint32 timer) -{ - if(i_owner->GetTypeId()==TYPEID_PLAYER) - { - DEBUG_LOG("Player (GUID: %u) distracted (timer: %u)", i_owner->GetGUIDLow(), timer); - } - else - { - DEBUG_LOG("Creature (Entry: %u GUID: %u) (timer: %u)", - i_owner->GetEntry(), i_owner->GetGUIDLow(), timer); - } - - DistractMovementGenerator* mgen = new DistractMovementGenerator(timer); - Mutate(mgen); -} - -void MotionMaster::Mutate(MovementGenerator *m) -{ - if (!empty()) - { - switch(top()->GetMovementGeneratorType()) - { - // HomeMovement is not that important, delete it if meanwhile a new comes - case HOME_MOTION_TYPE: - // DistractMovement interrupted by any other movement - case DISTRACT_MOTION_TYPE: - MovementExpired(false); - } - } - m->Initialize(*i_owner); - push(m); -} - -void MotionMaster::propagateSpeedChange() -{ - Impl::container_type::iterator it = Impl::c.begin(); - for ( ;it != end(); ++it) - { - (*it)->unitSpeedChanged(); - } -} - -MovementGeneratorType MotionMaster::GetCurrentMovementGeneratorType() const -{ - if(empty()) - return IDLE_MOTION_TYPE; - - return top()->GetMovementGeneratorType(); -} - -bool MotionMaster::GetDestination(float &x, float &y, float &z) -{ - if(empty()) - return false; - - return top()->GetDestination(x,y,z); -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "MotionMaster.h" +#include "CreatureAISelector.h" +#include "Creature.h" +#include "Traveller.h" + +#include "ConfusedMovementGenerator.h" +#include "FleeingMovementGenerator.h" +#include "HomeMovementGenerator.h" +#include "IdleMovementGenerator.h" +#include "PointMovementGenerator.h" +#include "TargetedMovementGenerator.h" +#include "WaypointMovementGenerator.h" + +#include + +inline bool isStatic(MovementGenerator *mv) +{ + return (mv == &si_idleMovement); +} + +void +MotionMaster::Initialize() +{ + // clear ALL movement generators (including default) + while(!empty()) + { + MovementGenerator *curr = top(); + curr->Finalize(*i_owner); + pop(); + if( !isStatic( curr ) ) + delete curr; + } + + // set new default movement generator + if(i_owner->GetTypeId() == TYPEID_UNIT) + { + MovementGenerator* movement = FactorySelector::selectMovementGenerator((Creature*)i_owner); + push( movement == NULL ? &si_idleMovement : movement ); + top()->Initialize(*i_owner); + } + else + push(&si_idleMovement); +} + +MotionMaster::~MotionMaster() +{ + // clear ALL movement generators (including default) + while(!empty()) + { + MovementGenerator *curr = top(); + curr->Finalize(*i_owner); + pop(); + if( !isStatic( curr ) ) + delete curr; + } +} + +void +MotionMaster::UpdateMotion(const uint32 &diff) +{ + if( i_owner->hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED) ) + return; + assert( !empty() ); + if (!top()->Update(*i_owner, diff)) + MovementExpired(); +} + +void +MotionMaster::Clear(bool reset) +{ + while( !empty() && size() > 1 ) + { + MovementGenerator *curr = top(); + curr->Finalize(*i_owner); + pop(); + if( !isStatic( curr ) ) + delete curr; + } + + if (reset) + { + assert( !empty() ); + top()->Reset(*i_owner); + } +} + +void +MotionMaster::MovementExpired(bool reset) +{ + if( empty() || size() == 1 ) + return; + + MovementGenerator *curr = top(); + curr->Finalize(*i_owner); + pop(); + + if( !isStatic(curr) ) + delete curr; + + assert( !empty() ); + while( !empty() && top()->GetMovementGeneratorType() == TARGETED_MOTION_TYPE ) + { + // Should check if target is still valid? If not valid it will crash. + curr = top(); + curr->Finalize(*i_owner); + pop(); + delete curr; + } + if( empty() ) + Initialize(); + if (reset) top()->Reset(*i_owner); +} + +void MotionMaster::MoveIdle() +{ + if( empty() || !isStatic( top() ) ) + push( &si_idleMovement ); +} + +void +MotionMaster::MoveTargetedHome() +{ + if(i_owner->hasUnitState(UNIT_STAT_FLEEING)) + return; + + Clear(false); + + if(i_owner->GetTypeId()==TYPEID_UNIT && !((Creature*)i_owner)->GetCharmerOrOwnerGUID()) + { + DEBUG_LOG("Creature (Entry: %u GUID: %u) targeted home", i_owner->GetEntry(), i_owner->GetGUIDLow()); + Mutate(new HomeMovementGenerator()); + } + else if(i_owner->GetTypeId()==TYPEID_UNIT && ((Creature*)i_owner)->GetCharmerOrOwnerGUID()) + { + sLog.outError("Pet or controlled creature (Entry: %u GUID: %u) attempt targeted home", + i_owner->GetEntry(), i_owner->GetGUIDLow() ); + } + else + { + sLog.outError("Player (GUID: %u) attempt targeted home", i_owner->GetGUIDLow() ); + } +} + +void +MotionMaster::MoveConfused() +{ + if(i_owner->GetTypeId()==TYPEID_PLAYER) + { + DEBUG_LOG("Player (GUID: %u) move confused", i_owner->GetGUIDLow() ); + Mutate(new ConfusedMovementGenerator()); + } + else + { + DEBUG_LOG("Creature (Entry: %u GUID: %u) move confused", + i_owner->GetEntry(), i_owner->GetGUIDLow() ); + Mutate(new ConfusedMovementGenerator()); + } +} + +void +MotionMaster::MoveChase(Unit* target, float dist, float angle) +{ + // ignore movement request if target not exist + if(!target) + return; + + i_owner->clearUnitState(UNIT_STAT_FOLLOW); + if(i_owner->GetTypeId()==TYPEID_PLAYER) + { + DEBUG_LOG("Player (GUID: %u) chase to %s (GUID: %u)", + target->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", + target->GetTypeId()==TYPEID_PLAYER ? i_owner->GetGUIDLow() : ((Creature*)i_owner)->GetDBTableGUIDLow() ); + Mutate(new TargetedMovementGenerator(*target,dist,angle)); + } + else + { + DEBUG_LOG("Creature (Entry: %u GUID: %u) chase to %s (GUID: %u)", + i_owner->GetEntry(), i_owner->GetGUIDLow(), + target->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", + target->GetTypeId()==TYPEID_PLAYER ? target->GetGUIDLow() : ((Creature*)target)->GetDBTableGUIDLow() ); + Mutate(new TargetedMovementGenerator(*target,dist,angle)); + } +} + +void +MotionMaster::MoveFollow(Unit* target, float dist, float angle) +{ + Clear(); + + // ignore movement request if target not exist + if(!target) + return; + + i_owner->addUnitState(UNIT_STAT_FOLLOW); + if(i_owner->GetTypeId()==TYPEID_PLAYER) + { + DEBUG_LOG("Player (GUID: %u) follow to %s (GUID: %u)", i_owner->GetGUIDLow(), + target->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", + target->GetTypeId()==TYPEID_PLAYER ? i_owner->GetGUIDLow() : ((Creature*)i_owner)->GetDBTableGUIDLow() ); + Mutate(new TargetedMovementGenerator(*target,dist,angle)); + } + else + { + DEBUG_LOG("Creature (Entry: %u GUID: %u) follow to %s (GUID: %u)", + i_owner->GetEntry(), i_owner->GetGUIDLow(), + target->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", + target->GetTypeId()==TYPEID_PLAYER ? target->GetGUIDLow() : ((Creature*)target)->GetDBTableGUIDLow() ); + Mutate(new TargetedMovementGenerator(*target,dist,angle)); + } +} + +void +MotionMaster::MovePoint(uint32 id, float x, float y, float z) +{ + if(i_owner->GetTypeId()==TYPEID_PLAYER) + { + DEBUG_LOG("Player (GUID: %u) targeted point (Id: %u X: %f Y: %f Z: %f)", i_owner->GetGUIDLow(), id, x, y, z ); + Mutate(new PointMovementGenerator(id,x,y,z)); + } + else + { + DEBUG_LOG("Creature (Entry: %u GUID: %u) targeted point (ID: %u X: %f Y: %f Z: %f)", + i_owner->GetEntry(), i_owner->GetGUIDLow(), id, x, y, z ); + Mutate(new PointMovementGenerator(id,x,y,z)); + } +} + +void +MotionMaster::MoveFleeing(Unit* enemy) +{ + if(!enemy) + return; + + if(i_owner->GetTypeId()==TYPEID_PLAYER) + { + DEBUG_LOG("Player (GUID: %u) flee from %s (GUID: %u)", i_owner->GetGUIDLow(), + enemy->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", + enemy->GetTypeId()==TYPEID_PLAYER ? enemy->GetGUIDLow() : ((Creature*)enemy)->GetDBTableGUIDLow() ); + Mutate(new FleeingMovementGenerator(enemy->GetGUID())); + } + else + { + DEBUG_LOG("Creature (Entry: %u GUID: %u) flee from %s (GUID: %u)", + i_owner->GetEntry(), i_owner->GetGUIDLow(), + enemy->GetTypeId()==TYPEID_PLAYER ? "player" : "creature", + enemy->GetTypeId()==TYPEID_PLAYER ? enemy->GetGUIDLow() : ((Creature*)enemy)->GetDBTableGUIDLow() ); + Mutate(new FleeingMovementGenerator(enemy->GetGUID())); + } +} + +void +MotionMaster::MoveTaxiFlight(uint32 path, uint32 pathnode) +{ + if(i_owner->GetTypeId()==TYPEID_PLAYER) + { + DEBUG_LOG("Player (GUID: %u) taxi to (Path %u node %u)", i_owner->GetGUIDLow(), path, pathnode); + FlightPathMovementGenerator* mgen = new FlightPathMovementGenerator(path,pathnode); + Mutate(mgen); + } + else + { + sLog.outError("Creature (Entry: %u GUID: %u) attempt taxi to (Path %u node %u)", + i_owner->GetEntry(), i_owner->GetGUIDLow(), path, pathnode ); + } +} + +void +MotionMaster::MoveDistract(uint32 timer) +{ + if(i_owner->GetTypeId()==TYPEID_PLAYER) + { + DEBUG_LOG("Player (GUID: %u) distracted (timer: %u)", i_owner->GetGUIDLow(), timer); + } + else + { + DEBUG_LOG("Creature (Entry: %u GUID: %u) (timer: %u)", + i_owner->GetEntry(), i_owner->GetGUIDLow(), timer); + } + + DistractMovementGenerator* mgen = new DistractMovementGenerator(timer); + Mutate(mgen); +} + +void MotionMaster::Mutate(MovementGenerator *m) +{ + if (!empty()) + { + switch(top()->GetMovementGeneratorType()) + { + // HomeMovement is not that important, delete it if meanwhile a new comes + case HOME_MOTION_TYPE: + // DistractMovement interrupted by any other movement + case DISTRACT_MOTION_TYPE: + MovementExpired(false); + } + } + m->Initialize(*i_owner); + push(m); +} + +void MotionMaster::propagateSpeedChange() +{ + Impl::container_type::iterator it = Impl::c.begin(); + for ( ;it != end(); ++it) + { + (*it)->unitSpeedChanged(); + } +} + +MovementGeneratorType MotionMaster::GetCurrentMovementGeneratorType() const +{ + if(empty()) + return IDLE_MOTION_TYPE; + + return top()->GetMovementGeneratorType(); +} + +bool MotionMaster::GetDestination(float &x, float &y, float &z) +{ + if(empty()) + return false; + + return top()->GetDestination(x,y,z); +} diff --git a/src/game/Object.cpp b/src/game/Object.cpp index 7266d5612e7..1b690902023 100644 --- a/src/game/Object.cpp +++ b/src/game/Object.cpp @@ -1,1447 +1,1447 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "SharedDefines.h" -#include "WorldPacket.h" -#include "Opcodes.h" -#include "Log.h" -#include "World.h" -#include "Object.h" -#include "Creature.h" -#include "Player.h" -#include "ObjectMgr.h" -#include "WorldSession.h" -#include "UpdateData.h" -#include "UpdateMask.h" -#include "Util.h" -#include "MapManager.h" -#include "ObjectAccessor.h" -#include "Log.h" -#include "Transports.h" -#include "TargetedMovementGenerator.h" -#include "WaypointMovementGenerator.h" -#include "VMapFactory.h" -#include "CellImpl.h" -#include "GridNotifiers.h" -#include "GridNotifiersImpl.h" - -#include "TemporarySummon.h" - -uint32 GuidHigh2TypeId(uint32 guid_hi) -{ - switch(guid_hi) - { - case HIGHGUID_ITEM: return TYPEID_ITEM; - //case HIGHGUID_CONTAINER: return TYPEID_CONTAINER; HIGHGUID_CONTAINER==HIGHGUID_ITEM currently - case HIGHGUID_UNIT: return TYPEID_UNIT; - case HIGHGUID_PET: return TYPEID_UNIT; - case HIGHGUID_PLAYER: return TYPEID_PLAYER; - case HIGHGUID_GAMEOBJECT: return TYPEID_GAMEOBJECT; - case HIGHGUID_DYNAMICOBJECT:return TYPEID_DYNAMICOBJECT; - case HIGHGUID_CORPSE: return TYPEID_CORPSE; - case HIGHGUID_MO_TRANSPORT: return TYPEID_GAMEOBJECT; - } - return 10; // unknown -} - -Object::Object( ) -{ - m_objectTypeId = TYPEID_OBJECT; - m_objectType = TYPEMASK_OBJECT; - - m_uint32Values = 0; - m_uint32Values_mirror = 0; - m_valuesCount = 0; - - m_inWorld = false; - m_objectUpdated = false; - - m_PackGUID.clear(); - m_PackGUID.appendPackGUID(0); -} - -Object::~Object( ) -{ - if(m_objectUpdated) - ObjectAccessor::Instance().RemoveUpdateObject(this); - - if(m_uint32Values) - { - if(IsInWorld()) - { - ///- Do NOT call RemoveFromWorld here, if the object is a player it will crash - sLog.outError("Object::~Object - guid="I64FMTD", typeid=%d deleted but still in world!!", GetGUID(), GetTypeId()); - //assert(0); - } - - //DEBUG_LOG("Object desctr 1 check (%p)",(void*)this); - delete [] m_uint32Values; - delete [] m_uint32Values_mirror; - //DEBUG_LOG("Object desctr 2 check (%p)",(void*)this); - } -} - -void Object::_InitValues() -{ - m_uint32Values = new uint32[ m_valuesCount ]; - memset(m_uint32Values, 0, m_valuesCount*sizeof(uint32)); - - m_uint32Values_mirror = new uint32[ m_valuesCount ]; - memset(m_uint32Values_mirror, 0, m_valuesCount*sizeof(uint32)); - - m_objectUpdated = false; -} - -void Object::_Create( uint32 guidlow, uint32 entry, HighGuid guidhigh ) -{ - if(!m_uint32Values) _InitValues(); - - uint64 guid = MAKE_NEW_GUID(guidlow, entry, guidhigh); // required more changes to make it working - SetUInt64Value( OBJECT_FIELD_GUID, guid ); - SetUInt32Value( OBJECT_FIELD_TYPE, m_objectType ); - m_PackGUID.clear(); - m_PackGUID.appendPackGUID(GetGUID()); -} - -void Object::BuildMovementUpdateBlock(UpdateData * data, uint32 flags ) const -{ - ByteBuffer buf(500); - - buf << uint8( UPDATETYPE_MOVEMENT ); - buf << GetGUID(); - - _BuildMovementUpdate(&buf, flags, 0x00000000); - - data->AddUpdateBlock(buf); -} - -void Object::BuildCreateUpdateBlockForPlayer(UpdateData *data, Player *target) const -{ - if(!target) - { - return; - } - - uint8 updatetype = UPDATETYPE_CREATE_OBJECT; - uint8 flags = m_updateFlag; - uint32 flags2 = 0; - - /** lower flag1 **/ - if(target == this) // building packet for oneself - { - flags |= UPDATEFLAG_SELF; - - /*** temporary reverted - until real source of stack corruption will not found - updatetype = UPDATETYPE_CREATE_OBJECT2; - ****/ - } - - if(flags & UPDATEFLAG_HASPOSITION) - { - // UPDATETYPE_CREATE_OBJECT2 dynamic objects, corpses... - if(isType(TYPEMASK_DYNAMICOBJECT) || isType(TYPEMASK_CORPSE) || isType(TYPEMASK_PLAYER)) - updatetype = UPDATETYPE_CREATE_OBJECT2; - - // UPDATETYPE_CREATE_OBJECT2 for pets... - if(target->GetPetGUID() == GetGUID()) - updatetype = UPDATETYPE_CREATE_OBJECT2; - - // UPDATETYPE_CREATE_OBJECT2 for some gameobject types... - if(isType(TYPEMASK_GAMEOBJECT)) - { - switch(((GameObject*)this)->GetGoType()) - { - case GAMEOBJECT_TYPE_TRAP: - case GAMEOBJECT_TYPE_DUEL_ARBITER: - case GAMEOBJECT_TYPE_FLAGSTAND: - case GAMEOBJECT_TYPE_FLAGDROP: - updatetype = UPDATETYPE_CREATE_OBJECT2; - break; - case GAMEOBJECT_TYPE_TRANSPORT: - flags |= UPDATEFLAG_TRANSPORT; - break; - } - } - } - - //sLog.outDebug("BuildCreateUpdate: update-type: %u, object-type: %u got flags: %X, flags2: %X", updatetype, m_objectTypeId, flags, flags2); - - ByteBuffer buf(500); - buf << (uint8)updatetype; - //buf.append(GetPackGUID()); //client crashes when using this - buf << (uint8)0xFF << GetGUID(); - buf << (uint8)m_objectTypeId; - - _BuildMovementUpdate(&buf, flags, flags2); - - UpdateMask updateMask; - updateMask.SetCount( m_valuesCount ); - _SetCreateBits( &updateMask, target ); - _BuildValuesUpdate(updatetype, &buf, &updateMask, target ); - data->AddUpdateBlock(buf); -} - -void Object::BuildUpdate(UpdateDataMapType &update_players) -{ - ObjectAccessor::_buildUpdateObject(this,update_players); - ClearUpdateMask(true); -} - -void Object::SendUpdateToPlayer(Player* player) -{ - // send update to another players - SendUpdateObjectToAllExcept(player); - - // send create update to player - UpdateData upd; - WorldPacket packet; - - upd.Clear(); - BuildCreateUpdateBlockForPlayer(&upd, player); - upd.BuildPacket(&packet); - player->GetSession()->SendPacket(&packet); - - // now object updated/(create updated) -} - -void Object::BuildValuesUpdateBlockForPlayer(UpdateData *data, Player *target) const -{ - ByteBuffer buf(500); - - buf << (uint8) UPDATETYPE_VALUES; - //buf.append(GetPackGUID()); //client crashes when using this. but not have crash in debug mode - buf << (uint8)0xFF; - buf << GetGUID(); - - UpdateMask updateMask; - updateMask.SetCount( m_valuesCount ); - - _SetUpdateBits( &updateMask, target ); - _BuildValuesUpdate(UPDATETYPE_VALUES, &buf, &updateMask, target ); - - data->AddUpdateBlock(buf); -} - -void Object::BuildOutOfRangeUpdateBlock(UpdateData * data) const -{ - data->AddOutOfRangeGUID(GetGUID()); -} - -void Object::DestroyForPlayer(Player *target) const -{ - ASSERT(target); - - WorldPacket data(SMSG_DESTROY_OBJECT, 8); - data << GetGUID(); - target->GetSession()->SendPacket( &data ); -} - -void Object::_BuildMovementUpdate(ByteBuffer * data, uint8 flags, uint32 flags2 ) const -{ - *data << (uint8)flags; // update flags - - // 0x20 - if (flags & UPDATEFLAG_LIVING) - { - switch(GetTypeId()) - { - case TYPEID_UNIT: - { - flags2 = ((Unit*)this)->GetUnitMovementFlags(); - } - break; - case TYPEID_PLAYER: - { - flags2 = ((Player*)this)->GetUnitMovementFlags(); - - if(((Player*)this)->GetTransport()) - flags2 |= MOVEMENTFLAG_ONTRANSPORT; - else - flags2 &= ~MOVEMENTFLAG_ONTRANSPORT; - - // remove unknown, unused etc flags for now - flags2 &= ~MOVEMENTFLAG_SPLINE2; // will be set manually - - if(((Player*)this)->isInFlight()) - { - WPAssert(((Player*)this)->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE); - flags2 = (MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_SPLINE2); - } - } - break; - } - - *data << uint32(flags2); // movement flags - *data << uint8(0); // unk 2.3.0 - *data << uint32(getMSTime()); // time (in milliseconds) - } - - // 0x40 - if (flags & UPDATEFLAG_HASPOSITION) - { - // 0x02 - if(flags & UPDATEFLAG_TRANSPORT && ((GameObject*)this)->GetGoType() == GAMEOBJECT_TYPE_MO_TRANSPORT) - { - *data << (float)0; - *data << (float)0; - *data << (float)0; - *data << ((WorldObject *)this)->GetOrientation(); - } - else - { - *data << ((WorldObject *)this)->GetPositionX(); - *data << ((WorldObject *)this)->GetPositionY(); - *data << ((WorldObject *)this)->GetPositionZ(); - *data << ((WorldObject *)this)->GetOrientation(); - } - } - - // 0x20 - if(flags & UPDATEFLAG_LIVING) - { - // 0x00000200 - if(flags2 & MOVEMENTFLAG_ONTRANSPORT) - { - if(GetTypeId() == TYPEID_PLAYER) - { - *data << (uint64)((Player*)this)->GetTransport()->GetGUID(); - *data << (float)((Player*)this)->GetTransOffsetX(); - *data << (float)((Player*)this)->GetTransOffsetY(); - *data << (float)((Player*)this)->GetTransOffsetZ(); - *data << (float)((Player*)this)->GetTransOffsetO(); - *data << (uint32)((Player*)this)->GetTransTime(); - } - //MaNGOS currently not have support for other than player on transport - } - - // 0x02200000 - if(flags2 & (MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_FLYING2)) - { - if(GetTypeId() == TYPEID_PLAYER) - *data << (float)((Player*)this)->m_movementInfo.s_pitch; - else - *data << (float)0; // is't part of movement packet, we must store and send it... - } - - if(GetTypeId() == TYPEID_PLAYER) - *data << (uint32)((Player*)this)->m_movementInfo.fallTime; - else - *data << (uint32)0; // last fall time - - // 0x00001000 - if(flags2 & MOVEMENTFLAG_JUMPING) - { - if(GetTypeId() == TYPEID_PLAYER) - { - *data << (float)((Player*)this)->m_movementInfo.j_unk; - *data << (float)((Player*)this)->m_movementInfo.j_sinAngle; - *data << (float)((Player*)this)->m_movementInfo.j_cosAngle; - *data << (float)((Player*)this)->m_movementInfo.j_xyspeed; - } - else - { - *data << (float)0; - *data << (float)0; - *data << (float)0; - *data << (float)0; - } - } - - // 0x04000000 - if(flags2 & MOVEMENTFLAG_SPLINE) - { - if(GetTypeId() == TYPEID_PLAYER) - *data << (float)((Player*)this)->m_movementInfo.u_unk1; - else - *data << (float)0; - } - - *data << ((Unit*)this)->GetSpeed( MOVE_WALK ); - *data << ((Unit*)this)->GetSpeed( MOVE_RUN ); - *data << ((Unit*)this)->GetSpeed( MOVE_SWIMBACK ); - *data << ((Unit*)this)->GetSpeed( MOVE_SWIM ); - *data << ((Unit*)this)->GetSpeed( MOVE_WALKBACK ); - *data << ((Unit*)this)->GetSpeed( MOVE_FLY ); - *data << ((Unit*)this)->GetSpeed( MOVE_FLYBACK ); - *data << ((Unit*)this)->GetSpeed( MOVE_TURN ); - - // 0x08000000 - if(flags2 & MOVEMENTFLAG_SPLINE2) - { - if(GetTypeId() != TYPEID_PLAYER) - { - sLog.outDebug("_BuildMovementUpdate: MOVEMENTFLAG_SPLINE2 for non-player"); - return; - } - - if(!((Player*)this)->isInFlight()) - { - sLog.outDebug("_BuildMovementUpdate: MOVEMENTFLAG_SPLINE2 but not in flight"); - return; - } - - WPAssert(((Player*)this)->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE); - - FlightPathMovementGenerator *fmg = (FlightPathMovementGenerator*)(((Player*)this)->GetMotionMaster()->top()); - - uint32 flags3 = 0x00000300; - - *data << uint32(flags3); // splines flag? - - if(flags3 & 0x10000) // probably x,y,z coords there - { - *data << (float)0; - *data << (float)0; - *data << (float)0; - } - - if(flags3 & 0x20000) // probably guid there - { - *data << uint64(0); - } - - if(flags3 & 0x40000) // may be orientation - { - *data << (float)0; - } - - Path &path = fmg->GetPath(); - - float x, y, z; - ((Player*)this)->GetPosition(x, y, z); - - uint32 inflighttime = uint32(path.GetPassedLength(fmg->GetCurrentNode(), x, y, z) * 32); - uint32 traveltime = uint32(path.GetTotalLength() * 32); - - *data << uint32(inflighttime); // passed move time? - *data << uint32(traveltime); // full move time? - *data << uint32(0); // ticks count? - - uint32 poscount = uint32(path.Size()); - - *data << uint32(poscount); // points count - - for(uint32 i = 0; i < poscount; ++i) - { - *data << path.GetNodes()[i].x; - *data << path.GetNodes()[i].y; - *data << path.GetNodes()[i].z; - } - - /*for(uint32 i = 0; i < poscount; i++) - { - // path points - *data << (float)0; - *data << (float)0; - *data << (float)0; - }*/ - - *data << path.GetNodes()[poscount-1].x; - *data << path.GetNodes()[poscount-1].y; - *data << path.GetNodes()[poscount-1].z; - - // target position (path end) - /**data << ((Unit*)this)->GetPositionX(); - *data << ((Unit*)this)->GetPositionY(); - *data << ((Unit*)this)->GetPositionZ();*/ - } - } - - // 0x8 - if(flags & UPDATEFLAG_LOWGUID) - { - switch(GetTypeId()) - { - case TYPEID_OBJECT: - case TYPEID_ITEM: - case TYPEID_CONTAINER: - case TYPEID_GAMEOBJECT: - case TYPEID_DYNAMICOBJECT: - case TYPEID_CORPSE: - *data << uint32(GetGUIDLow()); // GetGUIDLow() - break; - case TYPEID_UNIT: - *data << uint32(0x0000000B); // unk, can be 0xB or 0xC - break; - case TYPEID_PLAYER: - if(flags & UPDATEFLAG_SELF) - *data << uint32(0x00000015); // unk, can be 0x15 or 0x22 - else - *data << uint32(0x00000008); // unk, can be 0x7 or 0x8 - break; - default: - *data << uint32(0x00000000); // unk - break; - } - } - - // 0x10 - if(flags & UPDATEFLAG_HIGHGUID) - { - switch(GetTypeId()) - { - case TYPEID_OBJECT: - case TYPEID_ITEM: - case TYPEID_CONTAINER: - case TYPEID_GAMEOBJECT: - case TYPEID_DYNAMICOBJECT: - case TYPEID_CORPSE: - *data << uint32(GetGUIDHigh()); // GetGUIDHigh() - break; - default: - *data << uint32(0x00000000); // unk - break; - } - } - - // 0x4 - if(flags & UPDATEFLAG_FULLGUID) - { - *data << uint8(0); // packed guid (probably target guid) - } - - // 0x2 - if(flags & UPDATEFLAG_TRANSPORT) - { - *data << uint32(getMSTime()); // ms time - } -} - -void Object::_BuildValuesUpdate(uint8 updatetype, ByteBuffer * data, UpdateMask *updateMask, Player *target) const -{ - if(!target) - return; - - bool IsActivateToQuest = false; - if (updatetype == UPDATETYPE_CREATE_OBJECT || updatetype == UPDATETYPE_CREATE_OBJECT2) - { - if (isType(TYPEMASK_GAMEOBJECT) && !((GameObject*)this)->IsTransport()) - { - if ( ((GameObject*)this)->ActivateToQuest(target) || target->isGameMaster()) - { - IsActivateToQuest = true; - updateMask->SetBit(GAMEOBJECT_DYN_FLAGS); - } - } - } - else //case UPDATETYPE_VALUES - { - if (isType(TYPEMASK_GAMEOBJECT) && !((GameObject*)this)->IsTransport()) - { - if ( ((GameObject*)this)->ActivateToQuest(target) || target->isGameMaster()) - { - IsActivateToQuest = true; - } - updateMask->SetBit(GAMEOBJECT_DYN_FLAGS); - updateMask->SetBit(GAMEOBJECT_ANIMPROGRESS); - } - } - - WPAssert(updateMask && updateMask->GetCount() == m_valuesCount); - - *data << (uint8)updateMask->GetBlockCount(); - data->append( updateMask->GetMask(), updateMask->GetLength() ); - - // 2 specialized loops for speed optimization in non-unit case - if(isType(TYPEMASK_UNIT)) // unit (creature/player) case - { - for( uint16 index = 0; index < m_valuesCount; index ++ ) - { - if( updateMask->GetBit( index ) ) - { - // remove custom flag before send - if( index == UNIT_NPC_FLAGS ) - *data << uint32(m_uint32Values[ index ] & ~UNIT_NPC_FLAG_GUARD); - // FIXME: Some values at server stored in float format but must be sent to client in uint32 format - else if(index >= UNIT_FIELD_BASEATTACKTIME && index <= UNIT_FIELD_RANGEDATTACKTIME) - { - // convert from float to uint32 and send - *data << uint32(m_floatValues[ index ] < 0 ? 0 : m_floatValues[ index ]); - } - // there are some float values which may be negative or can't get negative due to other checks - else if(index >= UNIT_FIELD_NEGSTAT0 && index <= UNIT_FIELD_NEGSTAT4 || - index >= UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + 6) || - index >= UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + 6) || - index >= UNIT_FIELD_POSSTAT0 && index <= UNIT_FIELD_POSSTAT4) - { - *data << uint32(m_floatValues[ index ]); - } - // Gamemasters should be always able to select units - remove not selectable flag - else if(index == UNIT_FIELD_FLAGS && target->isGameMaster()) - { - *data << (m_uint32Values[ index ] & ~UNIT_FLAG_NOT_SELECTABLE); - } - // hide lootable animation for unallowed players - else if(index == UNIT_DYNAMIC_FLAGS && GetTypeId() == TYPEID_UNIT) - { - if(!target->isAllowedToLoot((Creature*)this)) - *data << (m_uint32Values[ index ] & ~UNIT_DYNFLAG_LOOTABLE); - else - *data << (m_uint32Values[ index ] & ~UNIT_DYNFLAG_OTHER_TAGGER); - } - else - { - // send in current format (float as float, uint32 as uint32) - *data << m_uint32Values[ index ]; - } - } - } - } - else if(isType(TYPEMASK_GAMEOBJECT)) // gameobject case - { - for( uint16 index = 0; index < m_valuesCount; index ++ ) - { - if( updateMask->GetBit( index ) ) - { - // send in current format (float as float, uint32 as uint32) - if ( index == GAMEOBJECT_DYN_FLAGS ) - { - if(IsActivateToQuest ) - { - switch(((GameObject*)this)->GetGoType()) - { - case GAMEOBJECT_TYPE_CHEST: - *data << uint32(9); // enable quest object. Represent 9, but 1 for client before 2.3.0 - break; - case GAMEOBJECT_TYPE_GOOBER: - *data << uint32(1); - break; - default: - *data << uint32(0); //unknown. not happen. - break; - } - } - else - *data << uint32(0); // disable quest object - } - else - *data << m_uint32Values[ index ]; // other cases - } - } - } - else // other objects case (no special index checks) - { - for( uint16 index = 0; index < m_valuesCount; index ++ ) - { - if( updateMask->GetBit( index ) ) - { - // send in current format (float as float, uint32 as uint32) - *data << m_uint32Values[ index ]; - } - } - } -} - -void Object::ClearUpdateMask(bool remove) -{ - for( uint16 index = 0; index < m_valuesCount; index ++ ) - { - if(m_uint32Values_mirror[index]!= m_uint32Values[index]) - m_uint32Values_mirror[index] = m_uint32Values[index]; - } - if(m_objectUpdated) - { - if(remove) - ObjectAccessor::Instance().RemoveUpdateObject(this); - m_objectUpdated = false; - } -} - -// Send current value fields changes to all viewers -void Object::SendUpdateObjectToAllExcept(Player* exceptPlayer) -{ - // changes will be send in create packet - if(!IsInWorld()) - return; - - // nothing do - if(!m_objectUpdated) - return; - - ObjectAccessor::UpdateObject(this,exceptPlayer); -} - -bool Object::LoadValues(const char* data) -{ - if(!m_uint32Values) _InitValues(); - - Tokens tokens = StrSplit(data, " "); - - if(tokens.size() != m_valuesCount) - return false; - - Tokens::iterator iter; - int index; - for (iter = tokens.begin(), index = 0; index < m_valuesCount; ++iter, ++index) - { - m_uint32Values[index] = atol((*iter).c_str()); - } - - return true; -} - -void Object::_SetUpdateBits(UpdateMask *updateMask, Player* /*target*/) const -{ - for( uint16 index = 0; index < m_valuesCount; index ++ ) - { - if(m_uint32Values_mirror[index]!= m_uint32Values[index]) - updateMask->SetBit(index); - } -} - -void Object::_SetCreateBits(UpdateMask *updateMask, Player* /*target*/) const -{ - for( uint16 index = 0; index < m_valuesCount; index++ ) - { - if(GetUInt32Value(index) != 0) - updateMask->SetBit(index); - } -} - -void Object::SetInt32Value( uint16 index, int32 value ) -{ - ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); - - if(m_int32Values[ index ] != value) - { - m_int32Values[ index ] = value; - - if(m_inWorld) - { - if(!m_objectUpdated) - { - ObjectAccessor::Instance().AddUpdateObject(this); - m_objectUpdated = true; - } - } - } -} - -void Object::SetUInt32Value( uint16 index, uint32 value ) -{ - ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); - - if(m_uint32Values[ index ] != value) - { - m_uint32Values[ index ] = value; - - if(m_inWorld) - { - if(!m_objectUpdated) - { - ObjectAccessor::Instance().AddUpdateObject(this); - m_objectUpdated = true; - } - } - } -} - -void Object::SetUInt64Value( uint16 index, const uint64 &value ) -{ - ASSERT( index + 1 < m_valuesCount || PrintIndexError( index , true ) ); - if(*((uint64*)&(m_uint32Values[ index ])) != value) - { - m_uint32Values[ index ] = *((uint32*)&value); - m_uint32Values[ index + 1 ] = *(((uint32*)&value) + 1); - - if(m_inWorld) - { - if(!m_objectUpdated) - { - ObjectAccessor::Instance().AddUpdateObject(this); - m_objectUpdated = true; - } - } - } -} - -void Object::SetFloatValue( uint16 index, float value ) -{ - ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); - - if(m_floatValues[ index ] != value) - { - m_floatValues[ index ] = value; - - if(m_inWorld) - { - if(!m_objectUpdated) - { - ObjectAccessor::Instance().AddUpdateObject(this); - m_objectUpdated = true; - } - } - } -} - -void Object::SetByteValue( uint16 index, uint8 offset, uint8 value ) -{ - ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); - - if(offset > 4) - { - sLog.outError("Object::SetByteValue: wrong offset %u", offset); - return; - } - - if(uint8(m_uint32Values[ index ] >> (offset * 8)) != value) - { - m_uint32Values[ index ] &= ~uint32(uint32(0xFF) << (offset * 8)); - m_uint32Values[ index ] |= uint32(uint32(value) << (offset * 8)); - - if(m_inWorld) - { - if(!m_objectUpdated) - { - ObjectAccessor::Instance().AddUpdateObject(this); - m_objectUpdated = true; - } - } - } -} - -void Object::SetUInt16Value( uint16 index, uint8 offset, uint16 value ) -{ - ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); - - if(offset > 2) - { - sLog.outError("Object::SetUInt16Value: wrong offset %u", offset); - return; - } - - if(uint8(m_uint32Values[ index ] >> (offset * 16)) != value) - { - m_uint32Values[ index ] &= ~uint32(uint32(0xFFFF) << (offset * 16)); - m_uint32Values[ index ] |= uint32(uint32(value) << (offset * 16)); - - if(m_inWorld) - { - if(!m_objectUpdated) - { - ObjectAccessor::Instance().AddUpdateObject(this); - m_objectUpdated = true; - } - } - } -} - -void Object::SetStatFloatValue( uint16 index, float value) -{ - if(value < 0) - value = 0.0f; - - SetFloatValue(index, value); -} - -void Object::SetStatInt32Value( uint16 index, int32 value) -{ - if(value < 0) - value = 0; - - SetUInt32Value(index, uint32(value)); -} - -void Object::ApplyModUInt32Value(uint16 index, int32 val, bool apply) -{ - int32 cur = GetUInt32Value(index); - cur += (apply ? val : -val); - if(cur < 0) - cur = 0; - SetUInt32Value(index,cur); -} - -void Object::ApplyModInt32Value(uint16 index, int32 val, bool apply) -{ - int32 cur = GetInt32Value(index); - cur += (apply ? val : -val); - SetInt32Value(index,cur); -} - -void Object::ApplyModSignedFloatValue(uint16 index, float val, bool apply) -{ - float cur = GetFloatValue(index); - cur += (apply ? val : -val); - SetFloatValue(index,cur); -} - -void Object::ApplyModPositiveFloatValue(uint16 index, float val, bool apply) -{ - float cur = GetFloatValue(index); - cur += (apply ? val : -val); - if(cur < 0) - cur = 0; - SetFloatValue(index,cur); -} - -void Object::SetFlag( uint16 index, uint32 newFlag ) -{ - ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); - uint32 oldval = m_uint32Values[ index ]; - uint32 newval = oldval | newFlag; - - if(oldval != newval) - { - m_uint32Values[ index ] = newval; - - if(m_inWorld) - { - if(!m_objectUpdated) - { - ObjectAccessor::Instance().AddUpdateObject(this); - m_objectUpdated = true; - } - } - } -} - -void Object::RemoveFlag( uint16 index, uint32 oldFlag ) -{ - ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); - uint32 oldval = m_uint32Values[ index ]; - uint32 newval = oldval & ~oldFlag; - - if(oldval != newval) - { - m_uint32Values[ index ] = newval; - - if(m_inWorld) - { - if(!m_objectUpdated) - { - ObjectAccessor::Instance().AddUpdateObject(this); - m_objectUpdated = true; - } - } - } -} - -bool Object::PrintIndexError(uint32 index, bool set) const -{ - sLog.outError("ERROR: Attempt %s non-existed value field: %u (count: %u) for object typeid: %u type mask: %u",(set ? "set value to" : "get value from"),index,m_valuesCount,GetTypeId(),m_objectType); - - // assert must fail after function call - return false; -} - -WorldObject::WorldObject() -{ - m_positionX = 0.0f; - m_positionY = 0.0f; - m_positionZ = 0.0f; - m_orientation = 0.0f; - - m_mapId = 0; - m_InstanceId = 0; - - m_name = ""; - - mSemaphoreTeleport = false; -} - -void WorldObject::_Create( uint32 guidlow, HighGuid guidhigh, uint32 mapid ) -{ - Object::_Create(guidlow, 0, guidhigh); - - m_mapId = mapid; -} - -uint32 WorldObject::GetZoneId() const -{ - return MapManager::Instance().GetBaseMap(m_mapId)->GetZoneId(m_positionX,m_positionY); -} - -uint32 WorldObject::GetAreaId() const -{ - return MapManager::Instance().GetBaseMap(m_mapId)->GetAreaId(m_positionX,m_positionY); -} - -InstanceData* WorldObject::GetInstanceData() -{ - Map *map = MapManager::Instance().GetMap(m_mapId, this); - return map->IsDungeon() ? ((InstanceMap*)map)->GetInstanceData() : NULL; -} - - //slow -float WorldObject::GetDistance(const WorldObject* obj) const -{ - float dx = GetPositionX() - obj->GetPositionX(); - float dy = GetPositionY() - obj->GetPositionY(); - float dz = GetPositionZ() - obj->GetPositionZ(); - float sizefactor = GetObjectSize() + obj->GetObjectSize(); - float dist = sqrt((dx*dx) + (dy*dy) + (dz*dz)) - sizefactor; - return ( dist > 0 ? dist : 0); -} - -float WorldObject::GetDistance2d(float x, float y) const -{ - float dx = GetPositionX() - x; - float dy = GetPositionY() - y; - float sizefactor = GetObjectSize(); - float dist = sqrt((dx*dx) + (dy*dy)) - sizefactor; - return ( dist > 0 ? dist : 0); -} - -float WorldObject::GetDistance(const float x, const float y, const float z) const -{ - float dx = GetPositionX() - x; - float dy = GetPositionY() - y; - float dz = GetPositionZ() - z; - float sizefactor = GetObjectSize(); - float dist = sqrt((dx*dx) + (dy*dy) + (dz*dz)) - sizefactor; - return ( dist > 0 ? dist : 0); -} - -float WorldObject::GetDistance2d(const WorldObject* obj) const -{ - float dx = GetPositionX() - obj->GetPositionX(); - float dy = GetPositionY() - obj->GetPositionY(); - float sizefactor = GetObjectSize() + obj->GetObjectSize(); - float dist = sqrt((dx*dx) + (dy*dy)) - sizefactor; - return ( dist > 0 ? dist : 0); -} - -float WorldObject::GetDistanceZ(const WorldObject* obj) const -{ - float dz = fabs(GetPositionZ() - obj->GetPositionZ()); - float sizefactor = GetObjectSize() + obj->GetObjectSize(); - float dist = dz - sizefactor; - return ( dist > 0 ? dist : 0); -} - -bool WorldObject::IsWithinDistInMap(const WorldObject* obj, const float dist2compare) const -{ - if (!obj || !IsInMap(obj)) return false; - - float dx = GetPositionX() - obj->GetPositionX(); - float dy = GetPositionY() - obj->GetPositionY(); - float dz = GetPositionZ() - obj->GetPositionZ(); - float distsq = dx*dx + dy*dy + dz*dz; - float sizefactor = GetObjectSize() + obj->GetObjectSize(); - float maxdist = dist2compare + sizefactor; - - return distsq < maxdist * maxdist; -} - -bool WorldObject::IsWithinLOSInMap(const WorldObject* obj) const -{ - if (!IsInMap(obj)) return false; - float ox,oy,oz; - obj->GetPosition(ox,oy,oz); - return(IsWithinLOS(ox, oy, oz )); -} - -bool WorldObject::IsWithinLOS(const float ox, const float oy, const float oz ) const -{ - float x,y,z; - GetPosition(x,y,z); - VMAP::IVMapManager *vMapManager = VMAP::VMapFactory::createOrGetVMapManager(); - return vMapManager->isInLineOfSight(GetMapId(), x, y, z+2.0f, ox, oy, oz+2.0f); -} - -float WorldObject::GetAngle(const WorldObject* obj) const -{ - if(!obj) return 0; - return GetAngle( obj->GetPositionX(), obj->GetPositionY() ); -} - -// Return angle in range 0..2*pi -float WorldObject::GetAngle( const float x, const float y ) const -{ - float dx = x - GetPositionX(); - float dy = y - GetPositionY(); - - float ang = atan2(dy, dx); - ang = (ang >= 0) ? ang : 2 * M_PI + ang; - return ang; -} - -bool WorldObject::HasInArc(const float arcangle, const WorldObject* obj) const -{ - float arc = arcangle; - - // move arc to range 0.. 2*pi - while( arc >= 2.0f * M_PI ) - arc -= 2.0f * M_PI; - while( arc < 0 ) - arc += 2.0f * M_PI; - - float angle = GetAngle( obj ); - angle -= m_orientation; - - // move angle to range -pi ... +pi - while( angle > M_PI) - angle -= 2.0f * M_PI; - while(angle < -M_PI) - angle += 2.0f * M_PI; - - float lborder = -1 * (arc/2.0f); // in range -pi..0 - float rborder = (arc/2.0f); // in range 0..pi - return (( angle >= lborder ) && ( angle <= rborder )); -} - -void WorldObject::GetRandomPoint( float x, float y, float z, float distance, float &rand_x, float &rand_y, float &rand_z) const -{ - if(distance==0) - { - rand_x = x; - rand_y = y; - rand_z = z; - return; - } - - // angle to face `obj` to `this` - float angle = rand_norm()*2*M_PI; - float new_dist = rand_norm()*distance; - - rand_x = x + new_dist * cos(angle); - rand_y = y + new_dist * sin(angle); - rand_z = z; - - MaNGOS::NormalizeMapCoord(rand_x); - MaNGOS::NormalizeMapCoord(rand_y); - UpdateGroundPositionZ(rand_x,rand_y,rand_z); // update to LOS height if available -} - -void WorldObject::UpdateGroundPositionZ(float x, float y, float &z) const -{ - float new_z = MapManager::Instance().GetBaseMap(GetMapId())->GetHeight(x,y,z,true); - if(new_z > INVALID_HEIGHT) - z = new_z+ 0.05f; // just to be sure that we are not a few pixel under the surface -} - -bool WorldObject::IsPositionValid() const -{ - return MaNGOS::IsValidMapCoord(m_positionX,m_positionY,m_positionZ,m_orientation); -} - -void WorldObject::MonsterSay(const char* text, uint32 language, uint64 TargetGuid) -{ - WorldPacket data(SMSG_MESSAGECHAT, 200); - BuildMonsterChat(&data,CHAT_MSG_MONSTER_SAY,text,language,GetName(),TargetGuid); - SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_SAY),true); -} - -void WorldObject::MonsterYell(const char* text, uint32 language, uint64 TargetGuid) -{ - WorldPacket data(SMSG_MESSAGECHAT, 200); - BuildMonsterChat(&data,CHAT_MSG_MONSTER_YELL,text,language,GetName(),TargetGuid); - SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_YELL),true); -} - -void WorldObject::MonsterTextEmote(const char* text, uint64 TargetGuid, bool IsBossEmote) -{ - WorldPacket data(SMSG_MESSAGECHAT, 200); - BuildMonsterChat(&data,IsBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE,text,LANG_UNIVERSAL,GetName(),TargetGuid); - SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE),true); -} - -void WorldObject::MonsterWhisper(const char* text, uint64 receiver, bool IsBossWhisper) -{ - Player *player = objmgr.GetPlayer(receiver); - if(!player || !player->GetSession()) - return; - - WorldPacket data(SMSG_MESSAGECHAT, 200); - BuildMonsterChat(&data,IsBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER,text,LANG_UNIVERSAL,GetName(),receiver); - - player->GetSession()->SendPacket(&data); -} - -void WorldObject::SendPlaySound(uint32 Sound, bool OnlySelf) -{ - WorldPacket data(SMSG_PLAY_SOUND, 4); - data << Sound; - if (OnlySelf && GetTypeId() == TYPEID_PLAYER ) - ((Player*)this)->GetSession()->SendPacket( &data ); - else - SendMessageToSet( &data, true ); // ToSelf ignored in this case -} - -namespace MaNGOS -{ - class MessageChatLocaleCacheDo - { - public: - MessageChatLocaleCacheDo(WorldObject const& obj, ChatMsg msgtype, int32 textId, uint32 language, uint64 targetGUID, float dist) - : i_object(obj), i_msgtype(msgtype), i_textId(textId), i_language(language), - i_targetGUID(targetGUID), i_dist(dist) - { - } - - ~MessageChatLocaleCacheDo() - { - for(int i = 0; i < i_data_cache.size(); ++i) - delete i_data_cache[i]; - } - - void operator()(Player* p) - { - // skip far away players - if(p->GetDistance(&i_object) > i_dist) - return; - - uint32 loc_idx = p->GetSession()->GetSessionDbLocaleIndex(); - uint32 cache_idx = loc_idx+1; - WorldPacket* data; - - // create if not cached yet - if(i_data_cache.size() < cache_idx+1 || !i_data_cache[cache_idx]) - { - if(i_data_cache.size() < cache_idx+1) - i_data_cache.resize(cache_idx+1); - - char const* text = objmgr.GetMangosString(i_textId,loc_idx); - - data = new WorldPacket(SMSG_MESSAGECHAT, 200); - - // TODO: i_object.GetName() also must be localized? - i_object.BuildMonsterChat(data,i_msgtype,text,i_language,i_object.GetName(),i_targetGUID); - - i_data_cache[cache_idx] = data; - } - else - data = i_data_cache[cache_idx]; - - p->SendDirectMessage(data); - } - - private: - WorldObject const& i_object; - ChatMsg i_msgtype; - int32 i_textId; - uint32 i_language; - uint64 i_targetGUID; - float i_dist; - std::vector i_data_cache; // 0 = default, i => i-1 locale index - }; -} // namespace MaNGOS - -void WorldObject::MonsterSay(int32 textId, uint32 language, uint64 TargetGuid) -{ - CellPair p = MaNGOS::ComputeCellPair(GetPositionX(), GetPositionY()); - - Cell cell(p); - cell.data.Part.reserved = ALL_DISTRICT; - cell.SetNoCreate(); - - MaNGOS::MessageChatLocaleCacheDo say_do(*this, CHAT_MSG_MONSTER_SAY, textId,language,TargetGuid,sWorld.getConfig(CONFIG_LISTEN_RANGE_SAY)); - MaNGOS::PlayerWorker say_worker(say_do); - TypeContainerVisitor, WorldTypeMapContainer > message(say_worker); - CellLock cell_lock(cell, p); - cell_lock->Visit(cell_lock, message, *GetMap()); -} - -void WorldObject::MonsterYell(int32 textId, uint32 language, uint64 TargetGuid) -{ - CellPair p = MaNGOS::ComputeCellPair(GetPositionX(), GetPositionY()); - - Cell cell(p); - cell.data.Part.reserved = ALL_DISTRICT; - cell.SetNoCreate(); - - MaNGOS::MessageChatLocaleCacheDo say_do(*this, CHAT_MSG_MONSTER_YELL, textId,language,TargetGuid,sWorld.getConfig(CONFIG_LISTEN_RANGE_YELL)); - MaNGOS::PlayerWorker say_worker(say_do); - TypeContainerVisitor, WorldTypeMapContainer > message(say_worker); - CellLock cell_lock(cell, p); - cell_lock->Visit(cell_lock, message, *GetMap()); -} - -void WorldObject::MonsterTextEmote(int32 textId, uint64 TargetGuid, bool IsBossEmote) -{ - CellPair p = MaNGOS::ComputeCellPair(GetPositionX(), GetPositionY()); - - Cell cell(p); - cell.data.Part.reserved = ALL_DISTRICT; - cell.SetNoCreate(); - - MaNGOS::MessageChatLocaleCacheDo say_do(*this, IsBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, textId,LANG_UNIVERSAL,TargetGuid,sWorld.getConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE)); - MaNGOS::PlayerWorker say_worker(say_do); - TypeContainerVisitor, WorldTypeMapContainer > message(say_worker); - CellLock cell_lock(cell, p); - cell_lock->Visit(cell_lock, message, *GetMap()); -} - -void WorldObject::MonsterWhisper(int32 textId, uint64 receiver, bool IsBossWhisper) -{ - Player *player = objmgr.GetPlayer(receiver); - if(!player || !player->GetSession()) - return; - - uint32 loc_idx = player->GetSession()->GetSessionDbLocaleIndex(); - char const* text = objmgr.GetMangosString(textId,loc_idx); - - WorldPacket data(SMSG_MESSAGECHAT, 200); - BuildMonsterChat(&data,IsBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER,text,LANG_UNIVERSAL,GetName(),receiver); - - player->GetSession()->SendPacket(&data); -} - -void WorldObject::BuildMonsterChat(WorldPacket *data, uint8 msgtype, char const* text, uint32 language, char const* name, uint64 targetGuid) const -{ - bool pre = (msgtype==CHAT_MSG_MONSTER_EMOTE || msgtype==CHAT_MSG_RAID_BOSS_EMOTE); - - *data << (uint8)msgtype; - *data << (uint32)language; - *data << (uint64)GetGUID(); - *data << (uint32)0; //2.1.0 - *data << (uint32)(strlen(name)+1); - *data << name; - *data << (uint64)targetGuid; //Unit Target - if( targetGuid && !IS_PLAYER_GUID(targetGuid) ) - { - *data << (uint32)1; // target name length - *data << (uint8)0; // target name - } - *data << (uint32)(strlen(text)+1+(pre?3:0)); - if(pre) - data->append("%s ",3); - *data << text; - *data << (uint8)0; // ChatTag -} - -void WorldObject::BuildHeartBeatMsg(WorldPacket *data) const -{ - //Heartbeat message cannot be used for non-units - if (!isType(TYPEMASK_UNIT)) - return; - - data->Initialize(MSG_MOVE_HEARTBEAT, 32); - data->append(GetPackGUID()); - *data << uint32(((Unit*)this)->GetUnitMovementFlags()); // movement flags - *data << uint8(0); // 2.3.0 - *data << getMSTime(); // time - *data << m_positionX; - *data << m_positionY; - *data << m_positionZ; - *data << m_orientation; - *data << uint32(0); -} - -void WorldObject::BuildTeleportAckMsg(WorldPacket *data, float x, float y, float z, float ang) const -{ - //TeleportAck message cannot be used for non-units - if (!isType(TYPEMASK_UNIT)) - return; - - data->Initialize(MSG_MOVE_TELEPORT_ACK, 41); - data->append(GetPackGUID()); - *data << uint32(0); // this value increments every time - *data << uint32(((Unit*)this)->GetUnitMovementFlags()); // movement flags - *data << uint8(0); // 2.3.0 - *data << getMSTime(); // time - *data << x; - *data << y; - *data << z; - *data << ang; - *data << uint32(0); -} - -void WorldObject::SendMessageToSet(WorldPacket *data, bool /*bToSelf*/) -{ - MapManager::Instance().GetMap(m_mapId, this)->MessageBroadcast(this, data); -} - -void WorldObject::SendMessageToSetInRange(WorldPacket *data, float dist, bool /*bToSelf*/) -{ - MapManager::Instance().GetMap(m_mapId, this)->MessageDistBroadcast(this, data, dist); -} - -void WorldObject::SendObjectDeSpawnAnim(uint64 guid) -{ - WorldPacket data(SMSG_GAMEOBJECT_DESPAWN_ANIM, 8); - data << guid; - SendMessageToSet(&data, true); -} - -Map* WorldObject::GetMap() const -{ - return MapManager::Instance().GetMap(GetMapId(), this); -} - -Map const* WorldObject::GetBaseMap() const -{ - return MapManager::Instance().GetBaseMap(GetMapId()); -} - -void WorldObject::AddObjectToRemoveList() -{ - Map* map = GetMap(); - if(!map) - { - sLog.outError("Object (TypeId: %u Entry: %u GUID: %u) at attempt add to move list not have valid map (Id: %u).",GetTypeId(),GetEntry(),GetGUIDLow(),GetMapId()); - return; - } - - map->AddObjectToRemoveList(this); -} - -Creature* WorldObject::SummonCreature(uint32 id, float x, float y, float z, float ang,TempSummonType spwtype,uint32 despwtime) -{ - TemporarySummon* pCreature = new TemporarySummon(GetGUID()); - - pCreature->SetInstanceId(GetInstanceId()); - uint32 team = 0; - if (GetTypeId()==TYPEID_PLAYER) - team = ((Player*)this)->GetTeam(); - - if (!pCreature->Create(objmgr.GenerateLowGuid(HIGHGUID_UNIT), GetMap(), id, team)) - { - delete pCreature; - return NULL; - } - - if (x == 0.0f && y == 0.0f && z == 0.0f) - GetClosePoint(x, y, z, pCreature->GetObjectSize()); - - pCreature->Relocate(x, y, z, ang); - - if(!pCreature->IsPositionValid()) - { - sLog.outError("ERROR: Creature (guidlow %d, entry %d) not summoned. Suggested coordinates isn't valid (X: %f Y: %f)",pCreature->GetGUIDLow(),pCreature->GetEntry(),pCreature->GetPositionX(),pCreature->GetPositionY()); - delete pCreature; - return NULL; - } - - pCreature->Summon(spwtype, despwtime); - - if(GetTypeId()==TYPEID_UNIT && ((Creature*)this)->AI()) - ((Creature*)this)->AI()->JustSummoned(pCreature); - - //return the creature therewith the summoner has access to it - return pCreature; -} - -void WorldObject::GetNearPoint2D(float &x, float &y, float distance2d, float absAngle ) const -{ - x = GetPositionX() + (GetObjectSize() + distance2d) * cos(absAngle); - y = GetPositionY() + (GetObjectSize() + distance2d) * sin(absAngle); - - MaNGOS::NormalizeMapCoord(x); - MaNGOS::NormalizeMapCoord(y); -} - -void WorldObject::GetNearPoint(WorldObject const* searcher, float &x, float &y, float &z, float searcher_size, float distance2d, float absAngle ) const -{ - GetNearPoint2D(x,y,distance2d+searcher_size,absAngle); - - z = GetPositionZ(); - - UpdateGroundPositionZ(x,y,z); -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "SharedDefines.h" +#include "WorldPacket.h" +#include "Opcodes.h" +#include "Log.h" +#include "World.h" +#include "Object.h" +#include "Creature.h" +#include "Player.h" +#include "ObjectMgr.h" +#include "WorldSession.h" +#include "UpdateData.h" +#include "UpdateMask.h" +#include "Util.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "Log.h" +#include "Transports.h" +#include "TargetedMovementGenerator.h" +#include "WaypointMovementGenerator.h" +#include "VMapFactory.h" +#include "CellImpl.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" + +#include "TemporarySummon.h" + +uint32 GuidHigh2TypeId(uint32 guid_hi) +{ + switch(guid_hi) + { + case HIGHGUID_ITEM: return TYPEID_ITEM; + //case HIGHGUID_CONTAINER: return TYPEID_CONTAINER; HIGHGUID_CONTAINER==HIGHGUID_ITEM currently + case HIGHGUID_UNIT: return TYPEID_UNIT; + case HIGHGUID_PET: return TYPEID_UNIT; + case HIGHGUID_PLAYER: return TYPEID_PLAYER; + case HIGHGUID_GAMEOBJECT: return TYPEID_GAMEOBJECT; + case HIGHGUID_DYNAMICOBJECT:return TYPEID_DYNAMICOBJECT; + case HIGHGUID_CORPSE: return TYPEID_CORPSE; + case HIGHGUID_MO_TRANSPORT: return TYPEID_GAMEOBJECT; + } + return 10; // unknown +} + +Object::Object( ) +{ + m_objectTypeId = TYPEID_OBJECT; + m_objectType = TYPEMASK_OBJECT; + + m_uint32Values = 0; + m_uint32Values_mirror = 0; + m_valuesCount = 0; + + m_inWorld = false; + m_objectUpdated = false; + + m_PackGUID.clear(); + m_PackGUID.appendPackGUID(0); +} + +Object::~Object( ) +{ + if(m_objectUpdated) + ObjectAccessor::Instance().RemoveUpdateObject(this); + + if(m_uint32Values) + { + if(IsInWorld()) + { + ///- Do NOT call RemoveFromWorld here, if the object is a player it will crash + sLog.outError("Object::~Object - guid="I64FMTD", typeid=%d deleted but still in world!!", GetGUID(), GetTypeId()); + //assert(0); + } + + //DEBUG_LOG("Object desctr 1 check (%p)",(void*)this); + delete [] m_uint32Values; + delete [] m_uint32Values_mirror; + //DEBUG_LOG("Object desctr 2 check (%p)",(void*)this); + } +} + +void Object::_InitValues() +{ + m_uint32Values = new uint32[ m_valuesCount ]; + memset(m_uint32Values, 0, m_valuesCount*sizeof(uint32)); + + m_uint32Values_mirror = new uint32[ m_valuesCount ]; + memset(m_uint32Values_mirror, 0, m_valuesCount*sizeof(uint32)); + + m_objectUpdated = false; +} + +void Object::_Create( uint32 guidlow, uint32 entry, HighGuid guidhigh ) +{ + if(!m_uint32Values) _InitValues(); + + uint64 guid = MAKE_NEW_GUID(guidlow, entry, guidhigh); // required more changes to make it working + SetUInt64Value( OBJECT_FIELD_GUID, guid ); + SetUInt32Value( OBJECT_FIELD_TYPE, m_objectType ); + m_PackGUID.clear(); + m_PackGUID.appendPackGUID(GetGUID()); +} + +void Object::BuildMovementUpdateBlock(UpdateData * data, uint32 flags ) const +{ + ByteBuffer buf(500); + + buf << uint8( UPDATETYPE_MOVEMENT ); + buf << GetGUID(); + + _BuildMovementUpdate(&buf, flags, 0x00000000); + + data->AddUpdateBlock(buf); +} + +void Object::BuildCreateUpdateBlockForPlayer(UpdateData *data, Player *target) const +{ + if(!target) + { + return; + } + + uint8 updatetype = UPDATETYPE_CREATE_OBJECT; + uint8 flags = m_updateFlag; + uint32 flags2 = 0; + + /** lower flag1 **/ + if(target == this) // building packet for oneself + { + flags |= UPDATEFLAG_SELF; + + /*** temporary reverted - until real source of stack corruption will not found + updatetype = UPDATETYPE_CREATE_OBJECT2; + ****/ + } + + if(flags & UPDATEFLAG_HASPOSITION) + { + // UPDATETYPE_CREATE_OBJECT2 dynamic objects, corpses... + if(isType(TYPEMASK_DYNAMICOBJECT) || isType(TYPEMASK_CORPSE) || isType(TYPEMASK_PLAYER)) + updatetype = UPDATETYPE_CREATE_OBJECT2; + + // UPDATETYPE_CREATE_OBJECT2 for pets... + if(target->GetPetGUID() == GetGUID()) + updatetype = UPDATETYPE_CREATE_OBJECT2; + + // UPDATETYPE_CREATE_OBJECT2 for some gameobject types... + if(isType(TYPEMASK_GAMEOBJECT)) + { + switch(((GameObject*)this)->GetGoType()) + { + case GAMEOBJECT_TYPE_TRAP: + case GAMEOBJECT_TYPE_DUEL_ARBITER: + case GAMEOBJECT_TYPE_FLAGSTAND: + case GAMEOBJECT_TYPE_FLAGDROP: + updatetype = UPDATETYPE_CREATE_OBJECT2; + break; + case GAMEOBJECT_TYPE_TRANSPORT: + flags |= UPDATEFLAG_TRANSPORT; + break; + } + } + } + + //sLog.outDebug("BuildCreateUpdate: update-type: %u, object-type: %u got flags: %X, flags2: %X", updatetype, m_objectTypeId, flags, flags2); + + ByteBuffer buf(500); + buf << (uint8)updatetype; + //buf.append(GetPackGUID()); //client crashes when using this + buf << (uint8)0xFF << GetGUID(); + buf << (uint8)m_objectTypeId; + + _BuildMovementUpdate(&buf, flags, flags2); + + UpdateMask updateMask; + updateMask.SetCount( m_valuesCount ); + _SetCreateBits( &updateMask, target ); + _BuildValuesUpdate(updatetype, &buf, &updateMask, target ); + data->AddUpdateBlock(buf); +} + +void Object::BuildUpdate(UpdateDataMapType &update_players) +{ + ObjectAccessor::_buildUpdateObject(this,update_players); + ClearUpdateMask(true); +} + +void Object::SendUpdateToPlayer(Player* player) +{ + // send update to another players + SendUpdateObjectToAllExcept(player); + + // send create update to player + UpdateData upd; + WorldPacket packet; + + upd.Clear(); + BuildCreateUpdateBlockForPlayer(&upd, player); + upd.BuildPacket(&packet); + player->GetSession()->SendPacket(&packet); + + // now object updated/(create updated) +} + +void Object::BuildValuesUpdateBlockForPlayer(UpdateData *data, Player *target) const +{ + ByteBuffer buf(500); + + buf << (uint8) UPDATETYPE_VALUES; + //buf.append(GetPackGUID()); //client crashes when using this. but not have crash in debug mode + buf << (uint8)0xFF; + buf << GetGUID(); + + UpdateMask updateMask; + updateMask.SetCount( m_valuesCount ); + + _SetUpdateBits( &updateMask, target ); + _BuildValuesUpdate(UPDATETYPE_VALUES, &buf, &updateMask, target ); + + data->AddUpdateBlock(buf); +} + +void Object::BuildOutOfRangeUpdateBlock(UpdateData * data) const +{ + data->AddOutOfRangeGUID(GetGUID()); +} + +void Object::DestroyForPlayer(Player *target) const +{ + ASSERT(target); + + WorldPacket data(SMSG_DESTROY_OBJECT, 8); + data << GetGUID(); + target->GetSession()->SendPacket( &data ); +} + +void Object::_BuildMovementUpdate(ByteBuffer * data, uint8 flags, uint32 flags2 ) const +{ + *data << (uint8)flags; // update flags + + // 0x20 + if (flags & UPDATEFLAG_LIVING) + { + switch(GetTypeId()) + { + case TYPEID_UNIT: + { + flags2 = ((Unit*)this)->GetUnitMovementFlags(); + } + break; + case TYPEID_PLAYER: + { + flags2 = ((Player*)this)->GetUnitMovementFlags(); + + if(((Player*)this)->GetTransport()) + flags2 |= MOVEMENTFLAG_ONTRANSPORT; + else + flags2 &= ~MOVEMENTFLAG_ONTRANSPORT; + + // remove unknown, unused etc flags for now + flags2 &= ~MOVEMENTFLAG_SPLINE2; // will be set manually + + if(((Player*)this)->isInFlight()) + { + WPAssert(((Player*)this)->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE); + flags2 = (MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_SPLINE2); + } + } + break; + } + + *data << uint32(flags2); // movement flags + *data << uint8(0); // unk 2.3.0 + *data << uint32(getMSTime()); // time (in milliseconds) + } + + // 0x40 + if (flags & UPDATEFLAG_HASPOSITION) + { + // 0x02 + if(flags & UPDATEFLAG_TRANSPORT && ((GameObject*)this)->GetGoType() == GAMEOBJECT_TYPE_MO_TRANSPORT) + { + *data << (float)0; + *data << (float)0; + *data << (float)0; + *data << ((WorldObject *)this)->GetOrientation(); + } + else + { + *data << ((WorldObject *)this)->GetPositionX(); + *data << ((WorldObject *)this)->GetPositionY(); + *data << ((WorldObject *)this)->GetPositionZ(); + *data << ((WorldObject *)this)->GetOrientation(); + } + } + + // 0x20 + if(flags & UPDATEFLAG_LIVING) + { + // 0x00000200 + if(flags2 & MOVEMENTFLAG_ONTRANSPORT) + { + if(GetTypeId() == TYPEID_PLAYER) + { + *data << (uint64)((Player*)this)->GetTransport()->GetGUID(); + *data << (float)((Player*)this)->GetTransOffsetX(); + *data << (float)((Player*)this)->GetTransOffsetY(); + *data << (float)((Player*)this)->GetTransOffsetZ(); + *data << (float)((Player*)this)->GetTransOffsetO(); + *data << (uint32)((Player*)this)->GetTransTime(); + } + //MaNGOS currently not have support for other than player on transport + } + + // 0x02200000 + if(flags2 & (MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_FLYING2)) + { + if(GetTypeId() == TYPEID_PLAYER) + *data << (float)((Player*)this)->m_movementInfo.s_pitch; + else + *data << (float)0; // is't part of movement packet, we must store and send it... + } + + if(GetTypeId() == TYPEID_PLAYER) + *data << (uint32)((Player*)this)->m_movementInfo.fallTime; + else + *data << (uint32)0; // last fall time + + // 0x00001000 + if(flags2 & MOVEMENTFLAG_JUMPING) + { + if(GetTypeId() == TYPEID_PLAYER) + { + *data << (float)((Player*)this)->m_movementInfo.j_unk; + *data << (float)((Player*)this)->m_movementInfo.j_sinAngle; + *data << (float)((Player*)this)->m_movementInfo.j_cosAngle; + *data << (float)((Player*)this)->m_movementInfo.j_xyspeed; + } + else + { + *data << (float)0; + *data << (float)0; + *data << (float)0; + *data << (float)0; + } + } + + // 0x04000000 + if(flags2 & MOVEMENTFLAG_SPLINE) + { + if(GetTypeId() == TYPEID_PLAYER) + *data << (float)((Player*)this)->m_movementInfo.u_unk1; + else + *data << (float)0; + } + + *data << ((Unit*)this)->GetSpeed( MOVE_WALK ); + *data << ((Unit*)this)->GetSpeed( MOVE_RUN ); + *data << ((Unit*)this)->GetSpeed( MOVE_SWIMBACK ); + *data << ((Unit*)this)->GetSpeed( MOVE_SWIM ); + *data << ((Unit*)this)->GetSpeed( MOVE_WALKBACK ); + *data << ((Unit*)this)->GetSpeed( MOVE_FLY ); + *data << ((Unit*)this)->GetSpeed( MOVE_FLYBACK ); + *data << ((Unit*)this)->GetSpeed( MOVE_TURN ); + + // 0x08000000 + if(flags2 & MOVEMENTFLAG_SPLINE2) + { + if(GetTypeId() != TYPEID_PLAYER) + { + sLog.outDebug("_BuildMovementUpdate: MOVEMENTFLAG_SPLINE2 for non-player"); + return; + } + + if(!((Player*)this)->isInFlight()) + { + sLog.outDebug("_BuildMovementUpdate: MOVEMENTFLAG_SPLINE2 but not in flight"); + return; + } + + WPAssert(((Player*)this)->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE); + + FlightPathMovementGenerator *fmg = (FlightPathMovementGenerator*)(((Player*)this)->GetMotionMaster()->top()); + + uint32 flags3 = 0x00000300; + + *data << uint32(flags3); // splines flag? + + if(flags3 & 0x10000) // probably x,y,z coords there + { + *data << (float)0; + *data << (float)0; + *data << (float)0; + } + + if(flags3 & 0x20000) // probably guid there + { + *data << uint64(0); + } + + if(flags3 & 0x40000) // may be orientation + { + *data << (float)0; + } + + Path &path = fmg->GetPath(); + + float x, y, z; + ((Player*)this)->GetPosition(x, y, z); + + uint32 inflighttime = uint32(path.GetPassedLength(fmg->GetCurrentNode(), x, y, z) * 32); + uint32 traveltime = uint32(path.GetTotalLength() * 32); + + *data << uint32(inflighttime); // passed move time? + *data << uint32(traveltime); // full move time? + *data << uint32(0); // ticks count? + + uint32 poscount = uint32(path.Size()); + + *data << uint32(poscount); // points count + + for(uint32 i = 0; i < poscount; ++i) + { + *data << path.GetNodes()[i].x; + *data << path.GetNodes()[i].y; + *data << path.GetNodes()[i].z; + } + + /*for(uint32 i = 0; i < poscount; i++) + { + // path points + *data << (float)0; + *data << (float)0; + *data << (float)0; + }*/ + + *data << path.GetNodes()[poscount-1].x; + *data << path.GetNodes()[poscount-1].y; + *data << path.GetNodes()[poscount-1].z; + + // target position (path end) + /**data << ((Unit*)this)->GetPositionX(); + *data << ((Unit*)this)->GetPositionY(); + *data << ((Unit*)this)->GetPositionZ();*/ + } + } + + // 0x8 + if(flags & UPDATEFLAG_LOWGUID) + { + switch(GetTypeId()) + { + case TYPEID_OBJECT: + case TYPEID_ITEM: + case TYPEID_CONTAINER: + case TYPEID_GAMEOBJECT: + case TYPEID_DYNAMICOBJECT: + case TYPEID_CORPSE: + *data << uint32(GetGUIDLow()); // GetGUIDLow() + break; + case TYPEID_UNIT: + *data << uint32(0x0000000B); // unk, can be 0xB or 0xC + break; + case TYPEID_PLAYER: + if(flags & UPDATEFLAG_SELF) + *data << uint32(0x00000015); // unk, can be 0x15 or 0x22 + else + *data << uint32(0x00000008); // unk, can be 0x7 or 0x8 + break; + default: + *data << uint32(0x00000000); // unk + break; + } + } + + // 0x10 + if(flags & UPDATEFLAG_HIGHGUID) + { + switch(GetTypeId()) + { + case TYPEID_OBJECT: + case TYPEID_ITEM: + case TYPEID_CONTAINER: + case TYPEID_GAMEOBJECT: + case TYPEID_DYNAMICOBJECT: + case TYPEID_CORPSE: + *data << uint32(GetGUIDHigh()); // GetGUIDHigh() + break; + default: + *data << uint32(0x00000000); // unk + break; + } + } + + // 0x4 + if(flags & UPDATEFLAG_FULLGUID) + { + *data << uint8(0); // packed guid (probably target guid) + } + + // 0x2 + if(flags & UPDATEFLAG_TRANSPORT) + { + *data << uint32(getMSTime()); // ms time + } +} + +void Object::_BuildValuesUpdate(uint8 updatetype, ByteBuffer * data, UpdateMask *updateMask, Player *target) const +{ + if(!target) + return; + + bool IsActivateToQuest = false; + if (updatetype == UPDATETYPE_CREATE_OBJECT || updatetype == UPDATETYPE_CREATE_OBJECT2) + { + if (isType(TYPEMASK_GAMEOBJECT) && !((GameObject*)this)->IsTransport()) + { + if ( ((GameObject*)this)->ActivateToQuest(target) || target->isGameMaster()) + { + IsActivateToQuest = true; + updateMask->SetBit(GAMEOBJECT_DYN_FLAGS); + } + } + } + else //case UPDATETYPE_VALUES + { + if (isType(TYPEMASK_GAMEOBJECT) && !((GameObject*)this)->IsTransport()) + { + if ( ((GameObject*)this)->ActivateToQuest(target) || target->isGameMaster()) + { + IsActivateToQuest = true; + } + updateMask->SetBit(GAMEOBJECT_DYN_FLAGS); + updateMask->SetBit(GAMEOBJECT_ANIMPROGRESS); + } + } + + WPAssert(updateMask && updateMask->GetCount() == m_valuesCount); + + *data << (uint8)updateMask->GetBlockCount(); + data->append( updateMask->GetMask(), updateMask->GetLength() ); + + // 2 specialized loops for speed optimization in non-unit case + if(isType(TYPEMASK_UNIT)) // unit (creature/player) case + { + for( uint16 index = 0; index < m_valuesCount; index ++ ) + { + if( updateMask->GetBit( index ) ) + { + // remove custom flag before send + if( index == UNIT_NPC_FLAGS ) + *data << uint32(m_uint32Values[ index ] & ~UNIT_NPC_FLAG_GUARD); + // FIXME: Some values at server stored in float format but must be sent to client in uint32 format + else if(index >= UNIT_FIELD_BASEATTACKTIME && index <= UNIT_FIELD_RANGEDATTACKTIME) + { + // convert from float to uint32 and send + *data << uint32(m_floatValues[ index ] < 0 ? 0 : m_floatValues[ index ]); + } + // there are some float values which may be negative or can't get negative due to other checks + else if(index >= UNIT_FIELD_NEGSTAT0 && index <= UNIT_FIELD_NEGSTAT4 || + index >= UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + 6) || + index >= UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + 6) || + index >= UNIT_FIELD_POSSTAT0 && index <= UNIT_FIELD_POSSTAT4) + { + *data << uint32(m_floatValues[ index ]); + } + // Gamemasters should be always able to select units - remove not selectable flag + else if(index == UNIT_FIELD_FLAGS && target->isGameMaster()) + { + *data << (m_uint32Values[ index ] & ~UNIT_FLAG_NOT_SELECTABLE); + } + // hide lootable animation for unallowed players + else if(index == UNIT_DYNAMIC_FLAGS && GetTypeId() == TYPEID_UNIT) + { + if(!target->isAllowedToLoot((Creature*)this)) + *data << (m_uint32Values[ index ] & ~UNIT_DYNFLAG_LOOTABLE); + else + *data << (m_uint32Values[ index ] & ~UNIT_DYNFLAG_OTHER_TAGGER); + } + else + { + // send in current format (float as float, uint32 as uint32) + *data << m_uint32Values[ index ]; + } + } + } + } + else if(isType(TYPEMASK_GAMEOBJECT)) // gameobject case + { + for( uint16 index = 0; index < m_valuesCount; index ++ ) + { + if( updateMask->GetBit( index ) ) + { + // send in current format (float as float, uint32 as uint32) + if ( index == GAMEOBJECT_DYN_FLAGS ) + { + if(IsActivateToQuest ) + { + switch(((GameObject*)this)->GetGoType()) + { + case GAMEOBJECT_TYPE_CHEST: + *data << uint32(9); // enable quest object. Represent 9, but 1 for client before 2.3.0 + break; + case GAMEOBJECT_TYPE_GOOBER: + *data << uint32(1); + break; + default: + *data << uint32(0); //unknown. not happen. + break; + } + } + else + *data << uint32(0); // disable quest object + } + else + *data << m_uint32Values[ index ]; // other cases + } + } + } + else // other objects case (no special index checks) + { + for( uint16 index = 0; index < m_valuesCount; index ++ ) + { + if( updateMask->GetBit( index ) ) + { + // send in current format (float as float, uint32 as uint32) + *data << m_uint32Values[ index ]; + } + } + } +} + +void Object::ClearUpdateMask(bool remove) +{ + for( uint16 index = 0; index < m_valuesCount; index ++ ) + { + if(m_uint32Values_mirror[index]!= m_uint32Values[index]) + m_uint32Values_mirror[index] = m_uint32Values[index]; + } + if(m_objectUpdated) + { + if(remove) + ObjectAccessor::Instance().RemoveUpdateObject(this); + m_objectUpdated = false; + } +} + +// Send current value fields changes to all viewers +void Object::SendUpdateObjectToAllExcept(Player* exceptPlayer) +{ + // changes will be send in create packet + if(!IsInWorld()) + return; + + // nothing do + if(!m_objectUpdated) + return; + + ObjectAccessor::UpdateObject(this,exceptPlayer); +} + +bool Object::LoadValues(const char* data) +{ + if(!m_uint32Values) _InitValues(); + + Tokens tokens = StrSplit(data, " "); + + if(tokens.size() != m_valuesCount) + return false; + + Tokens::iterator iter; + int index; + for (iter = tokens.begin(), index = 0; index < m_valuesCount; ++iter, ++index) + { + m_uint32Values[index] = atol((*iter).c_str()); + } + + return true; +} + +void Object::_SetUpdateBits(UpdateMask *updateMask, Player* /*target*/) const +{ + for( uint16 index = 0; index < m_valuesCount; index ++ ) + { + if(m_uint32Values_mirror[index]!= m_uint32Values[index]) + updateMask->SetBit(index); + } +} + +void Object::_SetCreateBits(UpdateMask *updateMask, Player* /*target*/) const +{ + for( uint16 index = 0; index < m_valuesCount; index++ ) + { + if(GetUInt32Value(index) != 0) + updateMask->SetBit(index); + } +} + +void Object::SetInt32Value( uint16 index, int32 value ) +{ + ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); + + if(m_int32Values[ index ] != value) + { + m_int32Values[ index ] = value; + + if(m_inWorld) + { + if(!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetUInt32Value( uint16 index, uint32 value ) +{ + ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); + + if(m_uint32Values[ index ] != value) + { + m_uint32Values[ index ] = value; + + if(m_inWorld) + { + if(!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetUInt64Value( uint16 index, const uint64 &value ) +{ + ASSERT( index + 1 < m_valuesCount || PrintIndexError( index , true ) ); + if(*((uint64*)&(m_uint32Values[ index ])) != value) + { + m_uint32Values[ index ] = *((uint32*)&value); + m_uint32Values[ index + 1 ] = *(((uint32*)&value) + 1); + + if(m_inWorld) + { + if(!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetFloatValue( uint16 index, float value ) +{ + ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); + + if(m_floatValues[ index ] != value) + { + m_floatValues[ index ] = value; + + if(m_inWorld) + { + if(!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetByteValue( uint16 index, uint8 offset, uint8 value ) +{ + ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); + + if(offset > 4) + { + sLog.outError("Object::SetByteValue: wrong offset %u", offset); + return; + } + + if(uint8(m_uint32Values[ index ] >> (offset * 8)) != value) + { + m_uint32Values[ index ] &= ~uint32(uint32(0xFF) << (offset * 8)); + m_uint32Values[ index ] |= uint32(uint32(value) << (offset * 8)); + + if(m_inWorld) + { + if(!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetUInt16Value( uint16 index, uint8 offset, uint16 value ) +{ + ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); + + if(offset > 2) + { + sLog.outError("Object::SetUInt16Value: wrong offset %u", offset); + return; + } + + if(uint8(m_uint32Values[ index ] >> (offset * 16)) != value) + { + m_uint32Values[ index ] &= ~uint32(uint32(0xFFFF) << (offset * 16)); + m_uint32Values[ index ] |= uint32(uint32(value) << (offset * 16)); + + if(m_inWorld) + { + if(!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetStatFloatValue( uint16 index, float value) +{ + if(value < 0) + value = 0.0f; + + SetFloatValue(index, value); +} + +void Object::SetStatInt32Value( uint16 index, int32 value) +{ + if(value < 0) + value = 0; + + SetUInt32Value(index, uint32(value)); +} + +void Object::ApplyModUInt32Value(uint16 index, int32 val, bool apply) +{ + int32 cur = GetUInt32Value(index); + cur += (apply ? val : -val); + if(cur < 0) + cur = 0; + SetUInt32Value(index,cur); +} + +void Object::ApplyModInt32Value(uint16 index, int32 val, bool apply) +{ + int32 cur = GetInt32Value(index); + cur += (apply ? val : -val); + SetInt32Value(index,cur); +} + +void Object::ApplyModSignedFloatValue(uint16 index, float val, bool apply) +{ + float cur = GetFloatValue(index); + cur += (apply ? val : -val); + SetFloatValue(index,cur); +} + +void Object::ApplyModPositiveFloatValue(uint16 index, float val, bool apply) +{ + float cur = GetFloatValue(index); + cur += (apply ? val : -val); + if(cur < 0) + cur = 0; + SetFloatValue(index,cur); +} + +void Object::SetFlag( uint16 index, uint32 newFlag ) +{ + ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); + uint32 oldval = m_uint32Values[ index ]; + uint32 newval = oldval | newFlag; + + if(oldval != newval) + { + m_uint32Values[ index ] = newval; + + if(m_inWorld) + { + if(!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::RemoveFlag( uint16 index, uint32 oldFlag ) +{ + ASSERT( index < m_valuesCount || PrintIndexError( index , true ) ); + uint32 oldval = m_uint32Values[ index ]; + uint32 newval = oldval & ~oldFlag; + + if(oldval != newval) + { + m_uint32Values[ index ] = newval; + + if(m_inWorld) + { + if(!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +bool Object::PrintIndexError(uint32 index, bool set) const +{ + sLog.outError("ERROR: Attempt %s non-existed value field: %u (count: %u) for object typeid: %u type mask: %u",(set ? "set value to" : "get value from"),index,m_valuesCount,GetTypeId(),m_objectType); + + // assert must fail after function call + return false; +} + +WorldObject::WorldObject() +{ + m_positionX = 0.0f; + m_positionY = 0.0f; + m_positionZ = 0.0f; + m_orientation = 0.0f; + + m_mapId = 0; + m_InstanceId = 0; + + m_name = ""; + + mSemaphoreTeleport = false; +} + +void WorldObject::_Create( uint32 guidlow, HighGuid guidhigh, uint32 mapid ) +{ + Object::_Create(guidlow, 0, guidhigh); + + m_mapId = mapid; +} + +uint32 WorldObject::GetZoneId() const +{ + return MapManager::Instance().GetBaseMap(m_mapId)->GetZoneId(m_positionX,m_positionY); +} + +uint32 WorldObject::GetAreaId() const +{ + return MapManager::Instance().GetBaseMap(m_mapId)->GetAreaId(m_positionX,m_positionY); +} + +InstanceData* WorldObject::GetInstanceData() +{ + Map *map = MapManager::Instance().GetMap(m_mapId, this); + return map->IsDungeon() ? ((InstanceMap*)map)->GetInstanceData() : NULL; +} + + //slow +float WorldObject::GetDistance(const WorldObject* obj) const +{ + float dx = GetPositionX() - obj->GetPositionX(); + float dy = GetPositionY() - obj->GetPositionY(); + float dz = GetPositionZ() - obj->GetPositionZ(); + float sizefactor = GetObjectSize() + obj->GetObjectSize(); + float dist = sqrt((dx*dx) + (dy*dy) + (dz*dz)) - sizefactor; + return ( dist > 0 ? dist : 0); +} + +float WorldObject::GetDistance2d(float x, float y) const +{ + float dx = GetPositionX() - x; + float dy = GetPositionY() - y; + float sizefactor = GetObjectSize(); + float dist = sqrt((dx*dx) + (dy*dy)) - sizefactor; + return ( dist > 0 ? dist : 0); +} + +float WorldObject::GetDistance(const float x, const float y, const float z) const +{ + float dx = GetPositionX() - x; + float dy = GetPositionY() - y; + float dz = GetPositionZ() - z; + float sizefactor = GetObjectSize(); + float dist = sqrt((dx*dx) + (dy*dy) + (dz*dz)) - sizefactor; + return ( dist > 0 ? dist : 0); +} + +float WorldObject::GetDistance2d(const WorldObject* obj) const +{ + float dx = GetPositionX() - obj->GetPositionX(); + float dy = GetPositionY() - obj->GetPositionY(); + float sizefactor = GetObjectSize() + obj->GetObjectSize(); + float dist = sqrt((dx*dx) + (dy*dy)) - sizefactor; + return ( dist > 0 ? dist : 0); +} + +float WorldObject::GetDistanceZ(const WorldObject* obj) const +{ + float dz = fabs(GetPositionZ() - obj->GetPositionZ()); + float sizefactor = GetObjectSize() + obj->GetObjectSize(); + float dist = dz - sizefactor; + return ( dist > 0 ? dist : 0); +} + +bool WorldObject::IsWithinDistInMap(const WorldObject* obj, const float dist2compare) const +{ + if (!obj || !IsInMap(obj)) return false; + + float dx = GetPositionX() - obj->GetPositionX(); + float dy = GetPositionY() - obj->GetPositionY(); + float dz = GetPositionZ() - obj->GetPositionZ(); + float distsq = dx*dx + dy*dy + dz*dz; + float sizefactor = GetObjectSize() + obj->GetObjectSize(); + float maxdist = dist2compare + sizefactor; + + return distsq < maxdist * maxdist; +} + +bool WorldObject::IsWithinLOSInMap(const WorldObject* obj) const +{ + if (!IsInMap(obj)) return false; + float ox,oy,oz; + obj->GetPosition(ox,oy,oz); + return(IsWithinLOS(ox, oy, oz )); +} + +bool WorldObject::IsWithinLOS(const float ox, const float oy, const float oz ) const +{ + float x,y,z; + GetPosition(x,y,z); + VMAP::IVMapManager *vMapManager = VMAP::VMapFactory::createOrGetVMapManager(); + return vMapManager->isInLineOfSight(GetMapId(), x, y, z+2.0f, ox, oy, oz+2.0f); +} + +float WorldObject::GetAngle(const WorldObject* obj) const +{ + if(!obj) return 0; + return GetAngle( obj->GetPositionX(), obj->GetPositionY() ); +} + +// Return angle in range 0..2*pi +float WorldObject::GetAngle( const float x, const float y ) const +{ + float dx = x - GetPositionX(); + float dy = y - GetPositionY(); + + float ang = atan2(dy, dx); + ang = (ang >= 0) ? ang : 2 * M_PI + ang; + return ang; +} + +bool WorldObject::HasInArc(const float arcangle, const WorldObject* obj) const +{ + float arc = arcangle; + + // move arc to range 0.. 2*pi + while( arc >= 2.0f * M_PI ) + arc -= 2.0f * M_PI; + while( arc < 0 ) + arc += 2.0f * M_PI; + + float angle = GetAngle( obj ); + angle -= m_orientation; + + // move angle to range -pi ... +pi + while( angle > M_PI) + angle -= 2.0f * M_PI; + while(angle < -M_PI) + angle += 2.0f * M_PI; + + float lborder = -1 * (arc/2.0f); // in range -pi..0 + float rborder = (arc/2.0f); // in range 0..pi + return (( angle >= lborder ) && ( angle <= rborder )); +} + +void WorldObject::GetRandomPoint( float x, float y, float z, float distance, float &rand_x, float &rand_y, float &rand_z) const +{ + if(distance==0) + { + rand_x = x; + rand_y = y; + rand_z = z; + return; + } + + // angle to face `obj` to `this` + float angle = rand_norm()*2*M_PI; + float new_dist = rand_norm()*distance; + + rand_x = x + new_dist * cos(angle); + rand_y = y + new_dist * sin(angle); + rand_z = z; + + MaNGOS::NormalizeMapCoord(rand_x); + MaNGOS::NormalizeMapCoord(rand_y); + UpdateGroundPositionZ(rand_x,rand_y,rand_z); // update to LOS height if available +} + +void WorldObject::UpdateGroundPositionZ(float x, float y, float &z) const +{ + float new_z = MapManager::Instance().GetBaseMap(GetMapId())->GetHeight(x,y,z,true); + if(new_z > INVALID_HEIGHT) + z = new_z+ 0.05f; // just to be sure that we are not a few pixel under the surface +} + +bool WorldObject::IsPositionValid() const +{ + return MaNGOS::IsValidMapCoord(m_positionX,m_positionY,m_positionZ,m_orientation); +} + +void WorldObject::MonsterSay(const char* text, uint32 language, uint64 TargetGuid) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,CHAT_MSG_MONSTER_SAY,text,language,GetName(),TargetGuid); + SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_SAY),true); +} + +void WorldObject::MonsterYell(const char* text, uint32 language, uint64 TargetGuid) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,CHAT_MSG_MONSTER_YELL,text,language,GetName(),TargetGuid); + SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_YELL),true); +} + +void WorldObject::MonsterTextEmote(const char* text, uint64 TargetGuid, bool IsBossEmote) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,IsBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE,text,LANG_UNIVERSAL,GetName(),TargetGuid); + SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE),true); +} + +void WorldObject::MonsterWhisper(const char* text, uint64 receiver, bool IsBossWhisper) +{ + Player *player = objmgr.GetPlayer(receiver); + if(!player || !player->GetSession()) + return; + + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,IsBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER,text,LANG_UNIVERSAL,GetName(),receiver); + + player->GetSession()->SendPacket(&data); +} + +void WorldObject::SendPlaySound(uint32 Sound, bool OnlySelf) +{ + WorldPacket data(SMSG_PLAY_SOUND, 4); + data << Sound; + if (OnlySelf && GetTypeId() == TYPEID_PLAYER ) + ((Player*)this)->GetSession()->SendPacket( &data ); + else + SendMessageToSet( &data, true ); // ToSelf ignored in this case +} + +namespace MaNGOS +{ + class MessageChatLocaleCacheDo + { + public: + MessageChatLocaleCacheDo(WorldObject const& obj, ChatMsg msgtype, int32 textId, uint32 language, uint64 targetGUID, float dist) + : i_object(obj), i_msgtype(msgtype), i_textId(textId), i_language(language), + i_targetGUID(targetGUID), i_dist(dist) + { + } + + ~MessageChatLocaleCacheDo() + { + for(int i = 0; i < i_data_cache.size(); ++i) + delete i_data_cache[i]; + } + + void operator()(Player* p) + { + // skip far away players + if(p->GetDistance(&i_object) > i_dist) + return; + + uint32 loc_idx = p->GetSession()->GetSessionDbLocaleIndex(); + uint32 cache_idx = loc_idx+1; + WorldPacket* data; + + // create if not cached yet + if(i_data_cache.size() < cache_idx+1 || !i_data_cache[cache_idx]) + { + if(i_data_cache.size() < cache_idx+1) + i_data_cache.resize(cache_idx+1); + + char const* text = objmgr.GetMangosString(i_textId,loc_idx); + + data = new WorldPacket(SMSG_MESSAGECHAT, 200); + + // TODO: i_object.GetName() also must be localized? + i_object.BuildMonsterChat(data,i_msgtype,text,i_language,i_object.GetName(),i_targetGUID); + + i_data_cache[cache_idx] = data; + } + else + data = i_data_cache[cache_idx]; + + p->SendDirectMessage(data); + } + + private: + WorldObject const& i_object; + ChatMsg i_msgtype; + int32 i_textId; + uint32 i_language; + uint64 i_targetGUID; + float i_dist; + std::vector i_data_cache; // 0 = default, i => i-1 locale index + }; +} // namespace MaNGOS + +void WorldObject::MonsterSay(int32 textId, uint32 language, uint64 TargetGuid) +{ + CellPair p = MaNGOS::ComputeCellPair(GetPositionX(), GetPositionY()); + + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + MaNGOS::MessageChatLocaleCacheDo say_do(*this, CHAT_MSG_MONSTER_SAY, textId,language,TargetGuid,sWorld.getConfig(CONFIG_LISTEN_RANGE_SAY)); + MaNGOS::PlayerWorker say_worker(say_do); + TypeContainerVisitor, WorldTypeMapContainer > message(say_worker); + CellLock cell_lock(cell, p); + cell_lock->Visit(cell_lock, message, *GetMap()); +} + +void WorldObject::MonsterYell(int32 textId, uint32 language, uint64 TargetGuid) +{ + CellPair p = MaNGOS::ComputeCellPair(GetPositionX(), GetPositionY()); + + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + MaNGOS::MessageChatLocaleCacheDo say_do(*this, CHAT_MSG_MONSTER_YELL, textId,language,TargetGuid,sWorld.getConfig(CONFIG_LISTEN_RANGE_YELL)); + MaNGOS::PlayerWorker say_worker(say_do); + TypeContainerVisitor, WorldTypeMapContainer > message(say_worker); + CellLock cell_lock(cell, p); + cell_lock->Visit(cell_lock, message, *GetMap()); +} + +void WorldObject::MonsterTextEmote(int32 textId, uint64 TargetGuid, bool IsBossEmote) +{ + CellPair p = MaNGOS::ComputeCellPair(GetPositionX(), GetPositionY()); + + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + MaNGOS::MessageChatLocaleCacheDo say_do(*this, IsBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, textId,LANG_UNIVERSAL,TargetGuid,sWorld.getConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE)); + MaNGOS::PlayerWorker say_worker(say_do); + TypeContainerVisitor, WorldTypeMapContainer > message(say_worker); + CellLock cell_lock(cell, p); + cell_lock->Visit(cell_lock, message, *GetMap()); +} + +void WorldObject::MonsterWhisper(int32 textId, uint64 receiver, bool IsBossWhisper) +{ + Player *player = objmgr.GetPlayer(receiver); + if(!player || !player->GetSession()) + return; + + uint32 loc_idx = player->GetSession()->GetSessionDbLocaleIndex(); + char const* text = objmgr.GetMangosString(textId,loc_idx); + + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,IsBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER,text,LANG_UNIVERSAL,GetName(),receiver); + + player->GetSession()->SendPacket(&data); +} + +void WorldObject::BuildMonsterChat(WorldPacket *data, uint8 msgtype, char const* text, uint32 language, char const* name, uint64 targetGuid) const +{ + bool pre = (msgtype==CHAT_MSG_MONSTER_EMOTE || msgtype==CHAT_MSG_RAID_BOSS_EMOTE); + + *data << (uint8)msgtype; + *data << (uint32)language; + *data << (uint64)GetGUID(); + *data << (uint32)0; //2.1.0 + *data << (uint32)(strlen(name)+1); + *data << name; + *data << (uint64)targetGuid; //Unit Target + if( targetGuid && !IS_PLAYER_GUID(targetGuid) ) + { + *data << (uint32)1; // target name length + *data << (uint8)0; // target name + } + *data << (uint32)(strlen(text)+1+(pre?3:0)); + if(pre) + data->append("%s ",3); + *data << text; + *data << (uint8)0; // ChatTag +} + +void WorldObject::BuildHeartBeatMsg(WorldPacket *data) const +{ + //Heartbeat message cannot be used for non-units + if (!isType(TYPEMASK_UNIT)) + return; + + data->Initialize(MSG_MOVE_HEARTBEAT, 32); + data->append(GetPackGUID()); + *data << uint32(((Unit*)this)->GetUnitMovementFlags()); // movement flags + *data << uint8(0); // 2.3.0 + *data << getMSTime(); // time + *data << m_positionX; + *data << m_positionY; + *data << m_positionZ; + *data << m_orientation; + *data << uint32(0); +} + +void WorldObject::BuildTeleportAckMsg(WorldPacket *data, float x, float y, float z, float ang) const +{ + //TeleportAck message cannot be used for non-units + if (!isType(TYPEMASK_UNIT)) + return; + + data->Initialize(MSG_MOVE_TELEPORT_ACK, 41); + data->append(GetPackGUID()); + *data << uint32(0); // this value increments every time + *data << uint32(((Unit*)this)->GetUnitMovementFlags()); // movement flags + *data << uint8(0); // 2.3.0 + *data << getMSTime(); // time + *data << x; + *data << y; + *data << z; + *data << ang; + *data << uint32(0); +} + +void WorldObject::SendMessageToSet(WorldPacket *data, bool /*bToSelf*/) +{ + MapManager::Instance().GetMap(m_mapId, this)->MessageBroadcast(this, data); +} + +void WorldObject::SendMessageToSetInRange(WorldPacket *data, float dist, bool /*bToSelf*/) +{ + MapManager::Instance().GetMap(m_mapId, this)->MessageDistBroadcast(this, data, dist); +} + +void WorldObject::SendObjectDeSpawnAnim(uint64 guid) +{ + WorldPacket data(SMSG_GAMEOBJECT_DESPAWN_ANIM, 8); + data << guid; + SendMessageToSet(&data, true); +} + +Map* WorldObject::GetMap() const +{ + return MapManager::Instance().GetMap(GetMapId(), this); +} + +Map const* WorldObject::GetBaseMap() const +{ + return MapManager::Instance().GetBaseMap(GetMapId()); +} + +void WorldObject::AddObjectToRemoveList() +{ + Map* map = GetMap(); + if(!map) + { + sLog.outError("Object (TypeId: %u Entry: %u GUID: %u) at attempt add to move list not have valid map (Id: %u).",GetTypeId(),GetEntry(),GetGUIDLow(),GetMapId()); + return; + } + + map->AddObjectToRemoveList(this); +} + +Creature* WorldObject::SummonCreature(uint32 id, float x, float y, float z, float ang,TempSummonType spwtype,uint32 despwtime) +{ + TemporarySummon* pCreature = new TemporarySummon(GetGUID()); + + pCreature->SetInstanceId(GetInstanceId()); + uint32 team = 0; + if (GetTypeId()==TYPEID_PLAYER) + team = ((Player*)this)->GetTeam(); + + if (!pCreature->Create(objmgr.GenerateLowGuid(HIGHGUID_UNIT), GetMap(), id, team)) + { + delete pCreature; + return NULL; + } + + if (x == 0.0f && y == 0.0f && z == 0.0f) + GetClosePoint(x, y, z, pCreature->GetObjectSize()); + + pCreature->Relocate(x, y, z, ang); + + if(!pCreature->IsPositionValid()) + { + sLog.outError("ERROR: Creature (guidlow %d, entry %d) not summoned. Suggested coordinates isn't valid (X: %f Y: %f)",pCreature->GetGUIDLow(),pCreature->GetEntry(),pCreature->GetPositionX(),pCreature->GetPositionY()); + delete pCreature; + return NULL; + } + + pCreature->Summon(spwtype, despwtime); + + if(GetTypeId()==TYPEID_UNIT && ((Creature*)this)->AI()) + ((Creature*)this)->AI()->JustSummoned(pCreature); + + //return the creature therewith the summoner has access to it + return pCreature; +} + +void WorldObject::GetNearPoint2D(float &x, float &y, float distance2d, float absAngle ) const +{ + x = GetPositionX() + (GetObjectSize() + distance2d) * cos(absAngle); + y = GetPositionY() + (GetObjectSize() + distance2d) * sin(absAngle); + + MaNGOS::NormalizeMapCoord(x); + MaNGOS::NormalizeMapCoord(y); +} + +void WorldObject::GetNearPoint(WorldObject const* searcher, float &x, float &y, float &z, float searcher_size, float distance2d, float absAngle ) const +{ + GetNearPoint2D(x,y,distance2d+searcher_size,absAngle); + + z = GetPositionZ(); + + UpdateGroundPositionZ(x,y,z); +} diff --git a/src/game/ObjectMgr.cpp b/src/game/ObjectMgr.cpp index 1bbca47d3b5..74a6a3ef684 100644 --- a/src/game/ObjectMgr.cpp +++ b/src/game/ObjectMgr.cpp @@ -6679,6 +6679,8 @@ void ObjectMgr::LoadTrainerSpell() itr->second.Clear(); m_mCacheTrainerSpellMap.clear(); + std::set skip_trainers; + QueryResult *result = WorldDatabase.PQuery("SELECT entry, spell,spellcost,reqskill,reqskillvalue,reqlevel FROM npc_trainer"); if( !result ) @@ -6714,7 +6716,11 @@ void ObjectMgr::LoadTrainerSpell() if(!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER)) { - sLog.outErrorDb("Table `npc_trainer` have data for not creature template (Entry: %u) without trainer flag, ignore", entry); + if(skip_trainers.count(entry) == 0) + { + sLog.outErrorDb("Table `npc_trainer` have data for not creature template (Entry: %u) without trainer flag, ignore", entry); + skip_trainers.insert(entry); + } continue; } @@ -6764,6 +6770,8 @@ void ObjectMgr::LoadVendors() itr->second.Clear(); m_mCacheVendorItemMap.clear(); + std::set skip_vendors; + QueryResult *result = WorldDatabase.PQuery("SELECT entry, item, maxcount, incrtime, ExtendedCost FROM npc_vendor"); if( !result ) { @@ -6790,7 +6798,7 @@ void ObjectMgr::LoadVendors() uint32 incrtime = fields[3].GetUInt32(); uint32 ExtendedCost = fields[4].GetUInt32(); - if(!IsVendorItemValid(entry,item_id,maxcount,incrtime,ExtendedCost)) + if(!IsVendorItemValid(entry,item_id,maxcount,incrtime,ExtendedCost,NULL,&skip_vendors)) continue; VendorItemData& vList = m_mCacheVendorItemMap[entry]; @@ -6878,7 +6886,7 @@ bool ObjectMgr::RemoveVendorItem( uint32 entry,uint32 item ) return true; } -bool ObjectMgr::IsVendorItemValid( uint32 vendor_entry, uint32 item_id, uint32 maxcount, uint32 incrtime, uint32 ExtendedCost, Player* pl ) const +bool ObjectMgr::IsVendorItemValid( uint32 vendor_entry, uint32 item_id, uint32 maxcount, uint32 incrtime, uint32 ExtendedCost, Player* pl, std::set* skip_vendors ) const { CreatureInfo const* cInfo = GetCreatureTemplate(vendor_entry); if(!cInfo) @@ -6892,10 +6900,16 @@ bool ObjectMgr::IsVendorItemValid( uint32 vendor_entry, uint32 item_id, uint32 m if(!(cInfo->npcflag & UNIT_NPC_FLAG_VENDOR)) { - if(pl) - ChatHandler(pl).SendSysMessage(LANG_COMMAND_VENDORSELECTION); - else - sLog.outErrorDb("Table `npc_vendor` have data for not creature template (Entry: %u) without vendor flag, ignore", vendor_entry); + if(!skip_vendors || skip_vendors->count(vendor_entry)==0) + { + if(pl) + ChatHandler(pl).SendSysMessage(LANG_COMMAND_VENDORSELECTION); + else + sLog.outErrorDb("Table `npc_vendor` have data for not creature template (Entry: %u) without vendor flag, ignore", vendor_entry); + + if(skip_vendors) + skip_vendors->insert(vendor_entry); + } return false; } diff --git a/src/game/ObjectMgr.h b/src/game/ObjectMgr.h index 5bb3aafde72..f5d35009307 100644 --- a/src/game/ObjectMgr.h +++ b/src/game/ObjectMgr.h @@ -742,7 +742,7 @@ class ObjectMgr } void AddVendorItem(uint32 entry,uint32 item, uint32 maxcount, uint32 incrtime, uint32 ExtendedCost); bool RemoveVendorItem(uint32 entry,uint32 item); - bool IsVendorItemValid( uint32 vendor_entry, uint32 item, uint32 maxcount, uint32 ptime, uint32 ExtendedCost, Player* pl = NULL ) const; + bool IsVendorItemValid( uint32 vendor_entry, uint32 item, uint32 maxcount, uint32 ptime, uint32 ExtendedCost, Player* pl = NULL, std::set* skip_vendors = NULL ) const; protected: uint32 m_auctionid; uint32 m_mailid; diff --git a/src/game/PetHandler.cpp b/src/game/PetHandler.cpp index 3251d02d5b9..9ac00048d49 100644 --- a/src/game/PetHandler.cpp +++ b/src/game/PetHandler.cpp @@ -1,648 +1,649 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "WorldPacket.h" -#include "WorldSession.h" -#include "World.h" -#include "ObjectMgr.h" -#include "SpellMgr.h" -#include "Log.h" -#include "Opcodes.h" -#include "Spell.h" -#include "ObjectAccessor.h" -#include "MapManager.h" -#include "CreatureAI.h" -#include "Util.h" -#include "Pet.h" - -void WorldSession::HandlePetAction( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,8+2+2+8); - - uint64 guid1; - uint16 spellid; - uint16 flag; - uint64 guid2; - recv_data >> guid1; //pet guid - recv_data >> spellid; - recv_data >> flag; //delete = 0x0700 CastSpell = C100 - recv_data >> guid2; //tag guid - - // used also for charmed creature - Unit* pet= ObjectAccessor::GetUnit(*_player,guid1); - sLog.outDetail( "HandlePetAction.Pet %u flag is %u, spellid is %u, target %u.\n", uint32(GUID_LOPART(guid1)), flag, spellid, uint32(GUID_LOPART(guid2)) ); - if(!pet) - { - sLog.outError( "Pet %u not exist.\n", uint32(GUID_LOPART(guid1)) ); - return; - } - - if(pet != GetPlayer()->GetPet() && pet != GetPlayer()->GetCharm()) - { - sLog.outError( "HandlePetAction.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid1)),GetPlayer()->GetName() ); - return; - } - - if(!pet->isAlive()) - return; - - if(pet->GetTypeId() == TYPEID_PLAYER && !(flag == ACT_COMMAND && spellid == COMMAND_ATTACK)) - return; - - CharmInfo *charmInfo = pet->GetCharmInfo(); - if(!charmInfo) - { - sLog.outError("WorldSession::HandlePetAction: object "I64FMTD" is considered pet-like but doesn't have a charminfo!", pet->GetGUID()); - return; - } - - switch(flag) - { - case ACT_COMMAND: //0x0700 - switch(spellid) - { - case COMMAND_STAY: //flat=1792 //STAY - pet->StopMoving(); - pet->GetMotionMaster()->Clear(); - pet->GetMotionMaster()->MoveIdle(); - charmInfo->SetCommandState( COMMAND_STAY ); - break; - case COMMAND_FOLLOW: //spellid=1792 //FOLLOW - pet->AttackStop(); - pet->GetMotionMaster()->MoveFollow(_player,PET_FOLLOW_DIST,PET_FOLLOW_ANGLE); - charmInfo->SetCommandState( COMMAND_FOLLOW ); - break; - case COMMAND_ATTACK: //spellid=1792 //ATTACK - { - // only place where pet can be player - pet->clearUnitState(UNIT_STAT_FOLLOW); - uint64 selguid = _player->GetSelection(); - Unit *TargetUnit = ObjectAccessor::GetUnit(*_player, selguid); - if(!TargetUnit) - return; - - // not let attack friendly units. - if( GetPlayer()->IsFriendlyTo(TargetUnit)) - return; - - if(pet->getVictim()) - pet->AttackStop(); - - if(pet->GetTypeId() != TYPEID_PLAYER) - { - pet->GetMotionMaster()->Clear(); - if (((Creature*)pet)->AI()) - ((Creature*)pet)->AI()->AttackStart(TargetUnit); - - //10% chance to play special pet attack talk, else growl - if(((Creature*)pet)->isPet() && ((Pet*)pet)->getPetType() == SUMMON_PET && pet != TargetUnit && urand(0, 100) < 10) - pet->SendPetTalk((uint32)PET_TALK_ATTACK); - else - { - // 90% chance for pet and 100% chance for charmed creature - pet->SendPetAIReaction(guid1); - } - } - else // charmed player - { - pet->Attack(TargetUnit,true); - pet->SendPetAIReaction(guid1); - } - break; - } - case COMMAND_ABANDON: // abandon (hunter pet) or dismiss (summoned pet) - if(((Creature*)pet)->isPet()) - { - Pet* p = (Pet*)pet; - if(p->getPetType() == HUNTER_PET) - _player->RemovePet(p,PET_SAVE_AS_DELETED); - else - //dismissing a summoned pet is like killing them (this prevents returning a soulshard...) - p->setDeathState(CORPSE); - } - else // charmed - _player->Uncharm(); - break; - default: - sLog.outError("WORLD: unknown PET flag Action %i and spellid %i.\n", flag, spellid); - } - break; - case ACT_REACTION: // 0x600 - switch(spellid) - { - case REACT_PASSIVE: //passive - case REACT_DEFENSIVE: //recovery - case REACT_AGGRESSIVE: //activete - charmInfo->SetReactState( ReactStates(spellid) ); - break; - } - break; - case ACT_DISABLED: //0x8100 spell (disabled), ignore - case ACT_CAST: //0x0100 - case ACT_ENABLED: //0xc100 spell - { - Unit* unit_target; - if(guid2) - unit_target = ObjectAccessor::GetUnit(*_player,guid2); - else - unit_target = NULL; - - // do not cast unknown spells - SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellid ); - if(!spellInfo) - { - sLog.outError("WORLD: unknown PET spell id %i\n", spellid); - return; - } - - for(uint32 i = 0; i < 3;i++) - { - if(spellInfo->EffectImplicitTargetA[i] == TARGET_ALL_ENEMY_IN_AREA || spellInfo->EffectImplicitTargetA[i] == TARGET_ALL_ENEMY_IN_AREA_INSTANT || spellInfo->EffectImplicitTargetA[i] == TARGET_ALL_ENEMY_IN_AREA_CHANNELED) - return; - } - - // do not cast not learned spells - if(!pet->HasSpell(spellid) || IsPassiveSpell(spellid)) - return; - - pet->clearUnitState(UNIT_STAT_FOLLOW); - - Spell *spell = new Spell(pet, spellInfo, false); - - int16 result = spell->PetCanCast(unit_target); - - //auto turn to target unless possessed - if(result == SPELL_FAILED_UNIT_NOT_INFRONT && !pet->HasAuraType(SPELL_AURA_MOD_POSSESS)) - { - pet->SetInFront(unit_target); - if( unit_target->GetTypeId() == TYPEID_PLAYER ) - pet->SendUpdateToPlayer( (Player*)unit_target ); - if(Unit* powner = pet->GetCharmerOrOwner()) - if(powner->GetTypeId() == TYPEID_PLAYER) - pet->SendUpdateToPlayer((Player*)powner); - result = -1; - } - - if(result == -1) - { - ((Creature*)pet)->AddCreatureSpellCooldown(spellid); - if (((Creature*)pet)->isPet()) - ((Pet*)pet)->CheckLearning(spellid); - - unit_target = spell->m_targets.getUnitTarget(); - - //10% chance to play special pet attack talk, else growl - //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell - if(((Creature*)pet)->isPet() && (((Pet*)pet)->getPetType() == SUMMON_PET) && (pet != unit_target) && (urand(0, 100) < 10)) - pet->SendPetTalk((uint32)PET_TALK_SPECIAL_SPELL); - else - { - pet->SendPetAIReaction(guid1); - } - - if( unit_target && !GetPlayer()->IsFriendlyTo(unit_target) && !pet->HasAuraType(SPELL_AURA_MOD_POSSESS)) - { - pet->clearUnitState(UNIT_STAT_FOLLOW); - if(pet->getVictim()) - pet->AttackStop(); - pet->GetMotionMaster()->Clear(); - if (((Creature*)pet)->AI()) - ((Creature*)pet)->AI()->AttackStart(unit_target); - } - - spell->prepare(&(spell->m_targets)); - } - else - { - if(pet->HasAuraType(SPELL_AURA_MOD_POSSESS)) - { - WorldPacket data(SMSG_CAST_FAILED, (4+1+1)); - data << uint32(spellid) << uint8(2) << uint8(result); - switch (result) - { - case SPELL_FAILED_REQUIRES_SPELL_FOCUS: - data << uint32(spellInfo->RequiresSpellFocus); - break; - case SPELL_FAILED_REQUIRES_AREA: - data << uint32(spellInfo->AreaId); - break; - } - SendPacket(&data); - } - else - pet->SendPetCastFail(spellid, result); - - if(!((Creature*)pet)->HasSpellCooldown(spellid)) - pet->SendPetClearCooldown(spellid); - - spell->finish(false); - delete spell; - } - break; - } - default: - sLog.outError("WORLD: unknown PET flag Action %i and spellid %i.\n", flag, spellid); - } -} - -void WorldSession::HandlePetNameQuery( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,4+8); - - sLog.outDetail( "HandlePetNameQuery. CMSG_PET_NAME_QUERY\n" ); - - uint32 petnumber; - uint64 petguid; - - recv_data >> petnumber; - recv_data >> petguid; - - SendPetNameQuery(petguid,petnumber); -} - -void WorldSession::SendPetNameQuery( uint64 petguid, uint32 petnumber) -{ - Creature* pet = ObjectAccessor::GetCreatureOrPet(*_player, petguid); - if(!pet || !pet->GetCharmInfo() || pet->GetCharmInfo()->GetPetNumber() != petnumber) - return; - - std::string name = pet->GetName(); - - WorldPacket data(SMSG_PET_NAME_QUERY_RESPONSE, (4+4+name.size()+1)); - data << uint32(petnumber); - data << name.c_str(); - data << uint32(pet->GetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP)); - - if( pet->isPet() && ((Pet*)pet)->GetDeclinedNames() ) - { - data << uint8(1); - for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) - data << ((Pet*)pet)->GetDeclinedNames()->name[i]; - } - else - data << uint8(0); - - _player->GetSession()->SendPacket(&data); -} - -void WorldSession::HandlePetSetAction( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,8+4+2+2); - - sLog.outDetail( "HandlePetSetAction. CMSG_PET_SET_ACTION\n" ); - - uint64 petguid; - uint32 position; - uint16 spell_id; - uint16 act_state; - uint8 count; - - recv_data >> petguid; - - // FIXME: charmed case - //Pet* pet = ObjectAccessor::Instance().GetPet(petguid); - if(ObjectAccessor::FindPlayer(petguid)) - return; - - Creature* pet = ObjectAccessor::GetCreatureOrPet(*_player, petguid); - - if(!pet || (pet != _player->GetPet() && pet != _player->GetCharm())) - { - sLog.outError( "HandlePetSetAction: Unknown pet or pet owner.\n" ); - return; - } - - CharmInfo *charmInfo = pet->GetCharmInfo(); - if(!charmInfo) - { - sLog.outError("WorldSession::HandlePetSetAction: object "I64FMTD" is considered pet-like but doesn't have a charminfo!", pet->GetGUID()); - return; - } - - count = (recv_data.size() == 24) ? 2 : 1; - for(uint8 i = 0; i < count; i++) - { - recv_data >> position; - recv_data >> spell_id; - recv_data >> act_state; - - sLog.outDetail( "Player %s has changed pet spell action. Position: %u, Spell: %u, State: 0x%X\n", _player->GetName(), position, spell_id, act_state); - - //if it's act for spell (en/disable/cast) and there is a spell given (0 = remove spell) which pet doesn't know, don't add - if(!((act_state == ACT_ENABLED || act_state == ACT_DISABLED || act_state == ACT_CAST) && spell_id && !pet->HasSpell(spell_id))) - { - //sign for autocast - if(act_state == ACT_ENABLED && spell_id) - { - if(pet->isCharmed()) - charmInfo->ToggleCreatureAutocast(spell_id, true); - else - ((Pet*)pet)->ToggleAutocast(spell_id, true); - } - //sign for no/turn off autocast - else if(act_state == ACT_DISABLED && spell_id) - { - if(pet->isCharmed()) - charmInfo->ToggleCreatureAutocast(spell_id, false); - else - ((Pet*)pet)->ToggleAutocast(spell_id, false); - } - - charmInfo->GetActionBarEntry(position)->Type = act_state; - charmInfo->GetActionBarEntry(position)->SpellOrAction = spell_id; - } - } -} - -void WorldSession::HandlePetRename( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,8+1+1+1+1+1+1+1); - - sLog.outDetail( "HandlePetRename. CMSG_PET_RENAME\n" ); - - uint64 petguid; - uint8 isdeclined; - - std::string name; - DeclinedName declinedname; - - recv_data >> petguid; - recv_data >> name; - recv_data >> isdeclined; - - Pet* pet = ObjectAccessor::GetPet(petguid); - // check it! - if( !pet || !pet->isPet() || ((Pet*)pet)->getPetType()!= HUNTER_PET || - pet->GetByteValue(UNIT_FIELD_BYTES_2, 2) != UNIT_RENAME_ALLOWED || - pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo() ) - return; - - if((!ObjectMgr::IsValidPetName(name)) || (objmgr.IsReservedName(name))) - { - SendNotification("Invalid name"); - return; - } - pet->SetName(name); - - Unit *owner = pet->GetOwner(); - if(owner && (owner->GetTypeId() == TYPEID_PLAYER) && ((Player*)owner)->GetGroup()) - ((Player*)owner)->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_NAME); - - pet->SetByteValue(UNIT_FIELD_BYTES_2, 2, UNIT_RENAME_NOT_ALLOWED); - - if(isdeclined) - { - for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) - recv_data >> declinedname.name[i]; - - std::wstring wname; - Utf8toWStr(name,wname); - if(!ObjectMgr::CheckDeclinedNames(GetMainPartOfName(wname,0),declinedname)) - { - SendNotification("Invalid name"); - return; - } - } - - CharacterDatabase.BeginTransaction(); - if(isdeclined) - { - for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) - CharacterDatabase.escape_string(declinedname.name[i]); - CharacterDatabase.PExecute("DELETE FROM character_pet_declinedname WHERE owner = '%u' AND id = '%u'", _player->GetGUIDLow(), pet->GetCharmInfo()->GetPetNumber()); - CharacterDatabase.PExecute("INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES ('%u','%u','%s','%s','%s','%s','%s')", - pet->GetCharmInfo()->GetPetNumber(), _player->GetGUIDLow(), declinedname.name[0].c_str(),declinedname.name[1].c_str(),declinedname.name[2].c_str(),declinedname.name[3].c_str(),declinedname.name[4].c_str()); - } - - CharacterDatabase.escape_string(name); - CharacterDatabase.PExecute("UPDATE character_pet SET name = '%s', renamed = '1' WHERE owner = '%u' AND id = '%u'", name.c_str(),_player->GetGUIDLow(),pet->GetCharmInfo()->GetPetNumber() ); - CharacterDatabase.CommitTransaction(); - - pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, time(NULL)); -} - -void WorldSession::HandlePetAbandon( WorldPacket & recv_data ) -{ - CHECK_PACKET_SIZE(recv_data,8); - - uint64 guid; - recv_data >> guid; //pet guid - sLog.outDetail( "HandlePetAbandon. CMSG_PET_ABANDON pet guid is %u", GUID_LOPART(guid) ); - - // pet/charmed - Creature* pet=ObjectAccessor::GetCreatureOrPet(*_player, guid); - if(pet) - { - if(pet->isPet()) - { - if(pet->GetGUID() == _player->GetPetGUID()) - { - uint32 feelty = pet->GetPower(POWER_HAPPINESS); - pet->SetPower(POWER_HAPPINESS ,(feelty-50000) > 0 ?(feelty-50000) : 0); - } - - _player->RemovePet((Pet*)pet,PET_SAVE_AS_DELETED); - } - else if(pet->GetGUID() == _player->GetCharmGUID()) - { - _player->Uncharm(); - } - } -} - -void WorldSession::HandlePetUnlearnOpcode(WorldPacket& recvPacket) -{ - CHECK_PACKET_SIZE(recvPacket,8); - - sLog.outDetail("CMSG_PET_UNLEARN"); - uint64 guid; - recvPacket >> guid; - - Pet* pet = _player->GetPet(); - - if(!pet || pet->getPetType() != HUNTER_PET || pet->m_spells.size() <= 1) - return; - - if(guid != pet->GetGUID()) - { - sLog.outError( "HandlePetUnlearnOpcode.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid)),GetPlayer()->GetName() ); - return; - } - - CharmInfo *charmInfo = pet->GetCharmInfo(); - if(!charmInfo) - { - sLog.outError("WorldSession::HandlePetUnlearnOpcode: object "I64FMTD" is considered pet-like but doesn't have a charminfo!", pet->GetGUID()); - return; - } - - uint32 cost = pet->resetTalentsCost(); - - if (GetPlayer()->GetMoney() < cost) - { - GetPlayer()->SendBuyError( BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0); - return; - } - - for(PetSpellMap::iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end();) - { - uint32 spell_id = itr->first; // Pet::removeSpell can invalidate iterator at erase NEW spell - ++itr; - pet->removeSpell(spell_id); - } - - pet->SetTP(pet->getLevel() * (pet->GetLoyaltyLevel() - 1)); - - for(uint8 i = 0; i < 10; i++) - { - if(charmInfo->GetActionBarEntry(i)->SpellOrAction && charmInfo->GetActionBarEntry(i)->Type == ACT_ENABLED || charmInfo->GetActionBarEntry(i)->Type == ACT_DISABLED) - charmInfo->GetActionBarEntry(i)->SpellOrAction = 0; - } - - // relearn pet passives - pet->LearnPetPassives(); - - pet->m_resetTalentsTime = time(NULL); - pet->m_resetTalentsCost = cost; - GetPlayer()->ModifyMoney(-(int32)cost); - - GetPlayer()->PetSpellInitialize(); -} - -void WorldSession::HandlePetSpellAutocastOpcode( WorldPacket& recvPacket ) -{ - CHECK_PACKET_SIZE(recvPacket,8+2+2+1); - - sLog.outDetail("CMSG_PET_SPELL_AUTOCAST"); - uint64 guid; - uint16 spellid; - uint16 spellid2; //maybe second spell, automatically toggled off when first toggled on? - uint8 state; //1 for on, 0 for off - recvPacket >> guid >> spellid >> spellid2 >> state; - - if(!_player->GetPet() && !_player->GetCharm()) - return; - - if(ObjectAccessor::FindPlayer(guid)) - return; - - Creature* pet=ObjectAccessor::GetCreatureOrPet(*_player,guid); - - if(!pet || (pet != _player->GetPet() && pet != _player->GetCharm())) - { - sLog.outError( "HandlePetSpellAutocastOpcode.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid)),GetPlayer()->GetName() ); - return; - } - - // do not add not learned spells/ passive spells - if(!pet->HasSpell(spellid) || IsPassiveSpell(spellid)) - return; - - CharmInfo *charmInfo = pet->GetCharmInfo(); - if(!charmInfo) - { - sLog.outError("WorldSession::HandlePetSpellAutocastOpcod: object "I64FMTD" is considered pet-like but doesn't have a charminfo!", pet->GetGUID()); - return; - } - - if(pet->isCharmed()) - //state can be used as boolean - pet->GetCharmInfo()->ToggleCreatureAutocast(spellid, state); - else - ((Pet*)pet)->ToggleAutocast(spellid, state); - - for(uint8 i = 0; i < 10; ++i) - { - if((charmInfo->GetActionBarEntry(i)->Type == ACT_ENABLED || charmInfo->GetActionBarEntry(i)->Type == ACT_DISABLED) && spellid == charmInfo->GetActionBarEntry(i)->SpellOrAction) - charmInfo->GetActionBarEntry(i)->Type = state ? ACT_ENABLED : ACT_DISABLED; - } -} - -void WorldSession::HandleAddDynamicTargetObsoleteOpcode( WorldPacket& recvPacket ) -{ - sLog.outDetail("WORLD: CMSG_PET_CAST_SPELL"); - - CHECK_PACKET_SIZE(recvPacket,8+4); - uint64 guid; - uint32 spellid; - - recvPacket >> guid >> spellid; - - if(!_player->GetPet() && !_player->GetCharm()) - return; - - if(ObjectAccessor::FindPlayer(guid)) - return; - - Creature* pet=ObjectAccessor::GetCreatureOrPet(*_player,guid); - - if(!pet || (pet != _player->GetPet() && pet!= _player->GetCharm())) - { - sLog.outError( "HandleAddDynamicTargetObsoleteOpcode.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid)),GetPlayer()->GetName() ); - return; - } - - SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellid); - if(!spellInfo) - { - sLog.outError("WORLD: unknown PET spell id %i\n", spellid); - return; - } - - // do not cast not learned spells - if(!pet->HasSpell(spellid) || IsPassiveSpell(spellid)) - return; - - SpellCastTargets targets; - if(!targets.read(&recvPacket,pet)) - return; - - pet->clearUnitState(UNIT_STAT_FOLLOW); - - Spell *spell = new Spell(pet, spellInfo, false); - spell->m_targets = targets; - - int16 result = spell->PetCanCast(NULL); - if(result == -1) - { - pet->AddCreatureSpellCooldown(spellid); - if(pet->isPet()) - { - Pet* p = (Pet*)pet; - p->CheckLearning(spellid); - //10% chance to play special pet attack talk, else growl - //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell - if(p->getPetType() == SUMMON_PET && (urand(0, 100) < 10)) - pet->SendPetTalk((uint32)PET_TALK_SPECIAL_SPELL); - else - pet->SendPetAIReaction(guid); - } - - spell->prepare(&(spell->m_targets)); - } - else - { - pet->SendPetCastFail(spellid, result); - if(!pet->HasSpellCooldown(spellid)) - pet->SendPetClearCooldown(spellid); - - spell->finish(false); - delete spell; - } -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "World.h" +#include "ObjectMgr.h" +#include "SpellMgr.h" +#include "Log.h" +#include "Opcodes.h" +#include "Spell.h" +#include "ObjectAccessor.h" +#include "MapManager.h" +#include "CreatureAI.h" +#include "Util.h" +#include "Pet.h" +#include "Language.h" + +void WorldSession::HandlePetAction( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,8+2+2+8); + + uint64 guid1; + uint16 spellid; + uint16 flag; + uint64 guid2; + recv_data >> guid1; //pet guid + recv_data >> spellid; + recv_data >> flag; //delete = 0x0700 CastSpell = C100 + recv_data >> guid2; //tag guid + + // used also for charmed creature + Unit* pet= ObjectAccessor::GetUnit(*_player,guid1); + sLog.outDetail( "HandlePetAction.Pet %u flag is %u, spellid is %u, target %u.\n", uint32(GUID_LOPART(guid1)), flag, spellid, uint32(GUID_LOPART(guid2)) ); + if(!pet) + { + sLog.outError( "Pet %u not exist.\n", uint32(GUID_LOPART(guid1)) ); + return; + } + + if(pet != GetPlayer()->GetPet() && pet != GetPlayer()->GetCharm()) + { + sLog.outError( "HandlePetAction.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid1)),GetPlayer()->GetName() ); + return; + } + + if(!pet->isAlive()) + return; + + if(pet->GetTypeId() == TYPEID_PLAYER && !(flag == ACT_COMMAND && spellid == COMMAND_ATTACK)) + return; + + CharmInfo *charmInfo = pet->GetCharmInfo(); + if(!charmInfo) + { + sLog.outError("WorldSession::HandlePetAction: object "I64FMTD" is considered pet-like but doesn't have a charminfo!", pet->GetGUID()); + return; + } + + switch(flag) + { + case ACT_COMMAND: //0x0700 + switch(spellid) + { + case COMMAND_STAY: //flat=1792 //STAY + pet->StopMoving(); + pet->GetMotionMaster()->Clear(); + pet->GetMotionMaster()->MoveIdle(); + charmInfo->SetCommandState( COMMAND_STAY ); + break; + case COMMAND_FOLLOW: //spellid=1792 //FOLLOW + pet->AttackStop(); + pet->GetMotionMaster()->MoveFollow(_player,PET_FOLLOW_DIST,PET_FOLLOW_ANGLE); + charmInfo->SetCommandState( COMMAND_FOLLOW ); + break; + case COMMAND_ATTACK: //spellid=1792 //ATTACK + { + // only place where pet can be player + pet->clearUnitState(UNIT_STAT_FOLLOW); + uint64 selguid = _player->GetSelection(); + Unit *TargetUnit = ObjectAccessor::GetUnit(*_player, selguid); + if(!TargetUnit) + return; + + // not let attack friendly units. + if( GetPlayer()->IsFriendlyTo(TargetUnit)) + return; + + if(pet->getVictim()) + pet->AttackStop(); + + if(pet->GetTypeId() != TYPEID_PLAYER) + { + pet->GetMotionMaster()->Clear(); + if (((Creature*)pet)->AI()) + ((Creature*)pet)->AI()->AttackStart(TargetUnit); + + //10% chance to play special pet attack talk, else growl + if(((Creature*)pet)->isPet() && ((Pet*)pet)->getPetType() == SUMMON_PET && pet != TargetUnit && urand(0, 100) < 10) + pet->SendPetTalk((uint32)PET_TALK_ATTACK); + else + { + // 90% chance for pet and 100% chance for charmed creature + pet->SendPetAIReaction(guid1); + } + } + else // charmed player + { + pet->Attack(TargetUnit,true); + pet->SendPetAIReaction(guid1); + } + break; + } + case COMMAND_ABANDON: // abandon (hunter pet) or dismiss (summoned pet) + if(((Creature*)pet)->isPet()) + { + Pet* p = (Pet*)pet; + if(p->getPetType() == HUNTER_PET) + _player->RemovePet(p,PET_SAVE_AS_DELETED); + else + //dismissing a summoned pet is like killing them (this prevents returning a soulshard...) + p->setDeathState(CORPSE); + } + else // charmed + _player->Uncharm(); + break; + default: + sLog.outError("WORLD: unknown PET flag Action %i and spellid %i.\n", flag, spellid); + } + break; + case ACT_REACTION: // 0x600 + switch(spellid) + { + case REACT_PASSIVE: //passive + case REACT_DEFENSIVE: //recovery + case REACT_AGGRESSIVE: //activete + charmInfo->SetReactState( ReactStates(spellid) ); + break; + } + break; + case ACT_DISABLED: //0x8100 spell (disabled), ignore + case ACT_CAST: //0x0100 + case ACT_ENABLED: //0xc100 spell + { + Unit* unit_target; + if(guid2) + unit_target = ObjectAccessor::GetUnit(*_player,guid2); + else + unit_target = NULL; + + // do not cast unknown spells + SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellid ); + if(!spellInfo) + { + sLog.outError("WORLD: unknown PET spell id %i\n", spellid); + return; + } + + for(uint32 i = 0; i < 3;i++) + { + if(spellInfo->EffectImplicitTargetA[i] == TARGET_ALL_ENEMY_IN_AREA || spellInfo->EffectImplicitTargetA[i] == TARGET_ALL_ENEMY_IN_AREA_INSTANT || spellInfo->EffectImplicitTargetA[i] == TARGET_ALL_ENEMY_IN_AREA_CHANNELED) + return; + } + + // do not cast not learned spells + if(!pet->HasSpell(spellid) || IsPassiveSpell(spellid)) + return; + + pet->clearUnitState(UNIT_STAT_FOLLOW); + + Spell *spell = new Spell(pet, spellInfo, false); + + int16 result = spell->PetCanCast(unit_target); + + //auto turn to target unless possessed + if(result == SPELL_FAILED_UNIT_NOT_INFRONT && !pet->HasAuraType(SPELL_AURA_MOD_POSSESS)) + { + pet->SetInFront(unit_target); + if( unit_target->GetTypeId() == TYPEID_PLAYER ) + pet->SendUpdateToPlayer( (Player*)unit_target ); + if(Unit* powner = pet->GetCharmerOrOwner()) + if(powner->GetTypeId() == TYPEID_PLAYER) + pet->SendUpdateToPlayer((Player*)powner); + result = -1; + } + + if(result == -1) + { + ((Creature*)pet)->AddCreatureSpellCooldown(spellid); + if (((Creature*)pet)->isPet()) + ((Pet*)pet)->CheckLearning(spellid); + + unit_target = spell->m_targets.getUnitTarget(); + + //10% chance to play special pet attack talk, else growl + //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell + if(((Creature*)pet)->isPet() && (((Pet*)pet)->getPetType() == SUMMON_PET) && (pet != unit_target) && (urand(0, 100) < 10)) + pet->SendPetTalk((uint32)PET_TALK_SPECIAL_SPELL); + else + { + pet->SendPetAIReaction(guid1); + } + + if( unit_target && !GetPlayer()->IsFriendlyTo(unit_target) && !pet->HasAuraType(SPELL_AURA_MOD_POSSESS)) + { + pet->clearUnitState(UNIT_STAT_FOLLOW); + if(pet->getVictim()) + pet->AttackStop(); + pet->GetMotionMaster()->Clear(); + if (((Creature*)pet)->AI()) + ((Creature*)pet)->AI()->AttackStart(unit_target); + } + + spell->prepare(&(spell->m_targets)); + } + else + { + if(pet->HasAuraType(SPELL_AURA_MOD_POSSESS)) + { + WorldPacket data(SMSG_CAST_FAILED, (4+1+1)); + data << uint32(spellid) << uint8(2) << uint8(result); + switch (result) + { + case SPELL_FAILED_REQUIRES_SPELL_FOCUS: + data << uint32(spellInfo->RequiresSpellFocus); + break; + case SPELL_FAILED_REQUIRES_AREA: + data << uint32(spellInfo->AreaId); + break; + } + SendPacket(&data); + } + else + pet->SendPetCastFail(spellid, result); + + if(!((Creature*)pet)->HasSpellCooldown(spellid)) + pet->SendPetClearCooldown(spellid); + + spell->finish(false); + delete spell; + } + break; + } + default: + sLog.outError("WORLD: unknown PET flag Action %i and spellid %i.\n", flag, spellid); + } +} + +void WorldSession::HandlePetNameQuery( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,4+8); + + sLog.outDetail( "HandlePetNameQuery. CMSG_PET_NAME_QUERY\n" ); + + uint32 petnumber; + uint64 petguid; + + recv_data >> petnumber; + recv_data >> petguid; + + SendPetNameQuery(petguid,petnumber); +} + +void WorldSession::SendPetNameQuery( uint64 petguid, uint32 petnumber) +{ + Creature* pet = ObjectAccessor::GetCreatureOrPet(*_player, petguid); + if(!pet || !pet->GetCharmInfo() || pet->GetCharmInfo()->GetPetNumber() != petnumber) + return; + + std::string name = pet->GetName(); + + WorldPacket data(SMSG_PET_NAME_QUERY_RESPONSE, (4+4+name.size()+1)); + data << uint32(petnumber); + data << name.c_str(); + data << uint32(pet->GetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP)); + + if( pet->isPet() && ((Pet*)pet)->GetDeclinedNames() ) + { + data << uint8(1); + for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) + data << ((Pet*)pet)->GetDeclinedNames()->name[i]; + } + else + data << uint8(0); + + _player->GetSession()->SendPacket(&data); +} + +void WorldSession::HandlePetSetAction( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,8+4+2+2); + + sLog.outDetail( "HandlePetSetAction. CMSG_PET_SET_ACTION\n" ); + + uint64 petguid; + uint32 position; + uint16 spell_id; + uint16 act_state; + uint8 count; + + recv_data >> petguid; + + // FIXME: charmed case + //Pet* pet = ObjectAccessor::Instance().GetPet(petguid); + if(ObjectAccessor::FindPlayer(petguid)) + return; + + Creature* pet = ObjectAccessor::GetCreatureOrPet(*_player, petguid); + + if(!pet || (pet != _player->GetPet() && pet != _player->GetCharm())) + { + sLog.outError( "HandlePetSetAction: Unknown pet or pet owner.\n" ); + return; + } + + CharmInfo *charmInfo = pet->GetCharmInfo(); + if(!charmInfo) + { + sLog.outError("WorldSession::HandlePetSetAction: object "I64FMTD" is considered pet-like but doesn't have a charminfo!", pet->GetGUID()); + return; + } + + count = (recv_data.size() == 24) ? 2 : 1; + for(uint8 i = 0; i < count; i++) + { + recv_data >> position; + recv_data >> spell_id; + recv_data >> act_state; + + sLog.outDetail( "Player %s has changed pet spell action. Position: %u, Spell: %u, State: 0x%X\n", _player->GetName(), position, spell_id, act_state); + + //if it's act for spell (en/disable/cast) and there is a spell given (0 = remove spell) which pet doesn't know, don't add + if(!((act_state == ACT_ENABLED || act_state == ACT_DISABLED || act_state == ACT_CAST) && spell_id && !pet->HasSpell(spell_id))) + { + //sign for autocast + if(act_state == ACT_ENABLED && spell_id) + { + if(pet->isCharmed()) + charmInfo->ToggleCreatureAutocast(spell_id, true); + else + ((Pet*)pet)->ToggleAutocast(spell_id, true); + } + //sign for no/turn off autocast + else if(act_state == ACT_DISABLED && spell_id) + { + if(pet->isCharmed()) + charmInfo->ToggleCreatureAutocast(spell_id, false); + else + ((Pet*)pet)->ToggleAutocast(spell_id, false); + } + + charmInfo->GetActionBarEntry(position)->Type = act_state; + charmInfo->GetActionBarEntry(position)->SpellOrAction = spell_id; + } + } +} + +void WorldSession::HandlePetRename( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,8+1+1+1+1+1+1+1); + + sLog.outDetail( "HandlePetRename. CMSG_PET_RENAME\n" ); + + uint64 petguid; + uint8 isdeclined; + + std::string name; + DeclinedName declinedname; + + recv_data >> petguid; + recv_data >> name; + recv_data >> isdeclined; + + Pet* pet = ObjectAccessor::GetPet(petguid); + // check it! + if( !pet || !pet->isPet() || ((Pet*)pet)->getPetType()!= HUNTER_PET || + pet->GetByteValue(UNIT_FIELD_BYTES_2, 2) != UNIT_RENAME_ALLOWED || + pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo() ) + return; + + if((!ObjectMgr::IsValidPetName(name)) || (objmgr.IsReservedName(name))) + { + SendNotification(LANG_PET_INVALID_NAME); + return; + } + pet->SetName(name); + + Unit *owner = pet->GetOwner(); + if(owner && (owner->GetTypeId() == TYPEID_PLAYER) && ((Player*)owner)->GetGroup()) + ((Player*)owner)->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_NAME); + + pet->SetByteValue(UNIT_FIELD_BYTES_2, 2, UNIT_RENAME_NOT_ALLOWED); + + if(isdeclined) + { + for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) + recv_data >> declinedname.name[i]; + + std::wstring wname; + Utf8toWStr(name,wname); + if(!ObjectMgr::CheckDeclinedNames(GetMainPartOfName(wname,0),declinedname)) + { + SendNotification(LANG_PET_INVALID_NAME); + return; + } + } + + CharacterDatabase.BeginTransaction(); + if(isdeclined) + { + for(int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) + CharacterDatabase.escape_string(declinedname.name[i]); + CharacterDatabase.PExecute("DELETE FROM character_pet_declinedname WHERE owner = '%u' AND id = '%u'", _player->GetGUIDLow(), pet->GetCharmInfo()->GetPetNumber()); + CharacterDatabase.PExecute("INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES ('%u','%u','%s','%s','%s','%s','%s')", + pet->GetCharmInfo()->GetPetNumber(), _player->GetGUIDLow(), declinedname.name[0].c_str(),declinedname.name[1].c_str(),declinedname.name[2].c_str(),declinedname.name[3].c_str(),declinedname.name[4].c_str()); + } + + CharacterDatabase.escape_string(name); + CharacterDatabase.PExecute("UPDATE character_pet SET name = '%s', renamed = '1' WHERE owner = '%u' AND id = '%u'", name.c_str(),_player->GetGUIDLow(),pet->GetCharmInfo()->GetPetNumber() ); + CharacterDatabase.CommitTransaction(); + + pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, time(NULL)); +} + +void WorldSession::HandlePetAbandon( WorldPacket & recv_data ) +{ + CHECK_PACKET_SIZE(recv_data,8); + + uint64 guid; + recv_data >> guid; //pet guid + sLog.outDetail( "HandlePetAbandon. CMSG_PET_ABANDON pet guid is %u", GUID_LOPART(guid) ); + + // pet/charmed + Creature* pet=ObjectAccessor::GetCreatureOrPet(*_player, guid); + if(pet) + { + if(pet->isPet()) + { + if(pet->GetGUID() == _player->GetPetGUID()) + { + uint32 feelty = pet->GetPower(POWER_HAPPINESS); + pet->SetPower(POWER_HAPPINESS ,(feelty-50000) > 0 ?(feelty-50000) : 0); + } + + _player->RemovePet((Pet*)pet,PET_SAVE_AS_DELETED); + } + else if(pet->GetGUID() == _player->GetCharmGUID()) + { + _player->Uncharm(); + } + } +} + +void WorldSession::HandlePetUnlearnOpcode(WorldPacket& recvPacket) +{ + CHECK_PACKET_SIZE(recvPacket,8); + + sLog.outDetail("CMSG_PET_UNLEARN"); + uint64 guid; + recvPacket >> guid; + + Pet* pet = _player->GetPet(); + + if(!pet || pet->getPetType() != HUNTER_PET || pet->m_spells.size() <= 1) + return; + + if(guid != pet->GetGUID()) + { + sLog.outError( "HandlePetUnlearnOpcode.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid)),GetPlayer()->GetName() ); + return; + } + + CharmInfo *charmInfo = pet->GetCharmInfo(); + if(!charmInfo) + { + sLog.outError("WorldSession::HandlePetUnlearnOpcode: object "I64FMTD" is considered pet-like but doesn't have a charminfo!", pet->GetGUID()); + return; + } + + uint32 cost = pet->resetTalentsCost(); + + if (GetPlayer()->GetMoney() < cost) + { + GetPlayer()->SendBuyError( BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0); + return; + } + + for(PetSpellMap::iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end();) + { + uint32 spell_id = itr->first; // Pet::removeSpell can invalidate iterator at erase NEW spell + ++itr; + pet->removeSpell(spell_id); + } + + pet->SetTP(pet->getLevel() * (pet->GetLoyaltyLevel() - 1)); + + for(uint8 i = 0; i < 10; i++) + { + if(charmInfo->GetActionBarEntry(i)->SpellOrAction && charmInfo->GetActionBarEntry(i)->Type == ACT_ENABLED || charmInfo->GetActionBarEntry(i)->Type == ACT_DISABLED) + charmInfo->GetActionBarEntry(i)->SpellOrAction = 0; + } + + // relearn pet passives + pet->LearnPetPassives(); + + pet->m_resetTalentsTime = time(NULL); + pet->m_resetTalentsCost = cost; + GetPlayer()->ModifyMoney(-(int32)cost); + + GetPlayer()->PetSpellInitialize(); +} + +void WorldSession::HandlePetSpellAutocastOpcode( WorldPacket& recvPacket ) +{ + CHECK_PACKET_SIZE(recvPacket,8+2+2+1); + + sLog.outDetail("CMSG_PET_SPELL_AUTOCAST"); + uint64 guid; + uint16 spellid; + uint16 spellid2; //maybe second spell, automatically toggled off when first toggled on? + uint8 state; //1 for on, 0 for off + recvPacket >> guid >> spellid >> spellid2 >> state; + + if(!_player->GetPet() && !_player->GetCharm()) + return; + + if(ObjectAccessor::FindPlayer(guid)) + return; + + Creature* pet=ObjectAccessor::GetCreatureOrPet(*_player,guid); + + if(!pet || (pet != _player->GetPet() && pet != _player->GetCharm())) + { + sLog.outError( "HandlePetSpellAutocastOpcode.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid)),GetPlayer()->GetName() ); + return; + } + + // do not add not learned spells/ passive spells + if(!pet->HasSpell(spellid) || IsPassiveSpell(spellid)) + return; + + CharmInfo *charmInfo = pet->GetCharmInfo(); + if(!charmInfo) + { + sLog.outError("WorldSession::HandlePetSpellAutocastOpcod: object "I64FMTD" is considered pet-like but doesn't have a charminfo!", pet->GetGUID()); + return; + } + + if(pet->isCharmed()) + //state can be used as boolean + pet->GetCharmInfo()->ToggleCreatureAutocast(spellid, state); + else + ((Pet*)pet)->ToggleAutocast(spellid, state); + + for(uint8 i = 0; i < 10; ++i) + { + if((charmInfo->GetActionBarEntry(i)->Type == ACT_ENABLED || charmInfo->GetActionBarEntry(i)->Type == ACT_DISABLED) && spellid == charmInfo->GetActionBarEntry(i)->SpellOrAction) + charmInfo->GetActionBarEntry(i)->Type = state ? ACT_ENABLED : ACT_DISABLED; + } +} + +void WorldSession::HandleAddDynamicTargetObsoleteOpcode( WorldPacket& recvPacket ) +{ + sLog.outDetail("WORLD: CMSG_PET_CAST_SPELL"); + + CHECK_PACKET_SIZE(recvPacket,8+4); + uint64 guid; + uint32 spellid; + + recvPacket >> guid >> spellid; + + if(!_player->GetPet() && !_player->GetCharm()) + return; + + if(ObjectAccessor::FindPlayer(guid)) + return; + + Creature* pet=ObjectAccessor::GetCreatureOrPet(*_player,guid); + + if(!pet || (pet != _player->GetPet() && pet!= _player->GetCharm())) + { + sLog.outError( "HandleAddDynamicTargetObsoleteOpcode.Pet %u isn't pet of player %s .\n", uint32(GUID_LOPART(guid)),GetPlayer()->GetName() ); + return; + } + + SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellid); + if(!spellInfo) + { + sLog.outError("WORLD: unknown PET spell id %i\n", spellid); + return; + } + + // do not cast not learned spells + if(!pet->HasSpell(spellid) || IsPassiveSpell(spellid)) + return; + + SpellCastTargets targets; + if(!targets.read(&recvPacket,pet)) + return; + + pet->clearUnitState(UNIT_STAT_FOLLOW); + + Spell *spell = new Spell(pet, spellInfo, false); + spell->m_targets = targets; + + int16 result = spell->PetCanCast(NULL); + if(result == -1) + { + pet->AddCreatureSpellCooldown(spellid); + if(pet->isPet()) + { + Pet* p = (Pet*)pet; + p->CheckLearning(spellid); + //10% chance to play special pet attack talk, else growl + //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell + if(p->getPetType() == SUMMON_PET && (urand(0, 100) < 10)) + pet->SendPetTalk((uint32)PET_TALK_SPECIAL_SPELL); + else + pet->SendPetAIReaction(guid); + } + + spell->prepare(&(spell->m_targets)); + } + else + { + pet->SendPetCastFail(spellid, result); + if(!pet->HasSpellCooldown(spellid)) + pet->SendPetClearCooldown(spellid); + + spell->finish(false); + delete spell; + } +} diff --git a/src/game/PetitionsHandler.cpp b/src/game/PetitionsHandler.cpp index 6957634dc9e..2c5c4493c10 100644 --- a/src/game/PetitionsHandler.cpp +++ b/src/game/PetitionsHandler.cpp @@ -1,936 +1,936 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "Language.h" -#include "WorldPacket.h" -#include "WorldSession.h" -#include "World.h" -#include "ObjectMgr.h" -#include "Log.h" -#include "Opcodes.h" -#include "Guild.h" -#include "ArenaTeam.h" -#include "MapManager.h" -#include "GossipDef.h" -#include "SocialMgr.h" - -/*enum PetitionType // dbc data -{ - PETITION_TYPE_GUILD = 1, - PETITION_TYPE_ARENA_TEAM = 3 -};*/ - -// Charters ID in item_template -#define GUILD_CHARTER 5863 -#define GUILD_CHARTER_COST 1000 // 10 S -#define ARENA_TEAM_CHARTER_2v2 23560 -#define ARENA_TEAM_CHARTER_2v2_COST 800000 // 80 G -#define ARENA_TEAM_CHARTER_3v3 23561 -#define ARENA_TEAM_CHARTER_3v3_COST 1200000 // 120 G -#define ARENA_TEAM_CHARTER_5v5 23562 -#define ARENA_TEAM_CHARTER_5v5_COST 2000000 // 200 G - -void WorldSession::HandlePetitionBuyOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 8+8+4+1+5*8+2+1+4+4); - - sLog.outDebug("Received opcode CMSG_PETITION_BUY"); - //recv_data.hexlike(); - - uint64 guidNPC; - uint64 unk1, unk3, unk4, unk5, unk6, unk7; - uint32 unk2; - std::string name; - uint16 unk8; - uint8 unk9; - uint32 unk10; // selected index - uint32 unk11; - recv_data >> guidNPC; // NPC GUID - recv_data >> unk1; // 0 - recv_data >> unk2; // 0 - recv_data >> name; // name - - // recheck - CHECK_PACKET_SIZE(recv_data, 8+8+4+(name.size()+1)+5*8+2+1+4+4); - - recv_data >> unk3; // 0 - recv_data >> unk4; // 0 - recv_data >> unk5; // 0 - recv_data >> unk6; // 0 - recv_data >> unk7; // 0 - recv_data >> unk8; // 0 - recv_data >> unk9; // 0 - recv_data >> unk10; // index - recv_data >> unk11; // 0 - sLog.outDebug("Petitioner with GUID %u tried sell petition: name %s", GUID_LOPART(guidNPC), name.c_str()); - - // prevent cheating - Creature *pCreature = ObjectAccessor::GetNPCIfCanInteractWith(*_player, guidNPC,UNIT_NPC_FLAG_PETITIONER); - if (!pCreature) - { - sLog.outDebug("WORLD: HandlePetitionBuyOpcode - Unit (GUID: %u) not found or you can't interact with him.", GUID_LOPART(guidNPC)); - return; - } - - // remove fake death - if(GetPlayer()->hasUnitState(UNIT_STAT_DIED)) - GetPlayer()->RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH); - - uint32 charterid = 0; - uint32 cost = 0; - uint32 type = 0; - if(pCreature->isTabardDesigner()) - { - // if tabard designer, then trying to buy a guild charter. - // do not let if already in guild. - if(_player->GetGuildId()) - return; - - charterid = GUILD_CHARTER; - cost = GUILD_CHARTER_COST; - type = 9; - } - else - { - // TODO: find correct opcode - if(_player->getLevel() < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) - { - SendNotification(GetMangosString(LANG_ARENA_ONE_TOOLOW), 70); - return; - } - - for(uint8 i = 0; i < MAX_ARENA_SLOT; i++) - { - if(_player->GetArenaTeamId(i) && (i == (unk10-1))) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ALREADY_IN_ARENA_TEAM); - return; - } - } - switch(unk10) - { - case 1: - charterid = ARENA_TEAM_CHARTER_2v2; - cost = ARENA_TEAM_CHARTER_2v2_COST; - type = 2; // 2v2 - break; - case 2: - charterid = ARENA_TEAM_CHARTER_3v3; - cost = ARENA_TEAM_CHARTER_3v3_COST; - type = 3; // 3v3 - break; - case 3: - charterid = ARENA_TEAM_CHARTER_5v5; - cost = ARENA_TEAM_CHARTER_5v5_COST; - type = 5; // 5v5 - break; - default: - sLog.outDebug("unknown selection at buy petition: %u", unk10); - return; - } - } - - if(type == 9) - { - if(objmgr.GetGuildByName(name)) - { - SendGuildCommandResult(GUILD_CREATE_S, name, GUILD_NAME_EXISTS); - return; - } - if(objmgr.IsReservedName(name)) - { - SendGuildCommandResult(GUILD_CREATE_S, name, GUILD_NAME_INVALID); - return; - } - if(!ObjectMgr::IsValidCharterName(name)) - { - SendGuildCommandResult(GUILD_CREATE_S, name, GUILD_NAME_INVALID); - return; - } - } - else - { - if(objmgr.GetArenaTeamByName(name)) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_EXISTS_S); - return; - } - if(objmgr.IsReservedName(name)) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_INVALID); - return; - } - if(!ObjectMgr::IsValidCharterName(name)) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_INVALID); - return; - } - } - - ItemPrototype const *pProto = objmgr.GetItemPrototype(charterid); - if(!pProto) - { - _player->SendBuyError(BUY_ERR_CANT_FIND_ITEM, NULL, charterid, 0); - return; - } - - if(_player->GetMoney() < cost) - { //player hasn't got enough money - _player->SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, pCreature, charterid, 0); - return; - } - - ItemPosCountVec dest; - uint8 msg = _player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, charterid, pProto->BuyCount ); - if(msg != EQUIP_ERR_OK) - { - _player->SendBuyError(msg, pCreature, charterid, 0); - return; - } - - _player->ModifyMoney(-(int32)cost); - Item *charter = _player->StoreNewItem(dest, charterid, true); - if(!charter) - return; - - charter->SetUInt32Value(ITEM_FIELD_ENCHANTMENT, charter->GetGUIDLow()); - // ITEM_FIELD_ENCHANTMENT is guild/arenateam id - // ITEM_FIELD_ENCHANTMENT+1 is current signatures count (showed on item) - charter->SetState(ITEM_CHANGED, _player); - _player->SendNewItem(charter, 1, true, false); - - // a petition is invalid, if both the owner and the type matches - QueryResult *result = CharacterDatabase.PQuery("SELECT petitionguid FROM petition WHERE ownerguid = '%u' AND type = '%u'", _player->GetGUIDLow(), type); - - std::ostringstream ssInvalidPetitionGUIDs; - - if (result) - { - - do - { - Field *fields = result->Fetch(); - ssInvalidPetitionGUIDs << "'" << fields[0].GetUInt32() << "' , "; - } while (result->NextRow()); - - delete result; - } - - // delete petitions with the same guid as this one - ssInvalidPetitionGUIDs << "'" << charter->GetGUIDLow() << "'"; - - sLog.outDebug("Invalid petition GUIDs: %s", ssInvalidPetitionGUIDs.str().c_str()); - CharacterDatabase.escape_string(name); - CharacterDatabase.BeginTransaction(); - CharacterDatabase.PExecute("DELETE FROM petition WHERE petitionguid IN ( %s )", ssInvalidPetitionGUIDs.str().c_str()); - CharacterDatabase.PExecute("DELETE FROM petition_sign WHERE petitionguid IN ( %s )", ssInvalidPetitionGUIDs.str().c_str()); - CharacterDatabase.PExecute("INSERT INTO petition (ownerguid, petitionguid, name, type) VALUES ('%u', '%u', '%s', '%u')", - _player->GetGUIDLow(), charter->GetGUIDLow(), name.c_str(), type); - CharacterDatabase.CommitTransaction(); -} - -void WorldSession::HandlePetitionShowSignOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - // ok - sLog.outDebug("Received opcode CMSG_PETITION_SHOW_SIGNATURES"); - //recv_data.hexlike(); - - uint8 signs = 0; - uint64 petitionguid; - recv_data >> petitionguid; // petition guid - - // solve (possible) some strange compile problems with explicit use GUID_LOPART(petitionguid) at some GCC versions (wrong code optimization in compiler?) - uint32 petitionguid_low = GUID_LOPART(petitionguid); - - QueryResult *result = CharacterDatabase.PQuery("SELECT petitionguid, type FROM petition WHERE petitionguid = '%u'", petitionguid_low); - if(!result) - { - sLog.outError("any petition on server..."); - return; - } - Field *fields = result->Fetch(); - uint32 type = fields[1].GetUInt32(); - delete result; - // if guild petition and has guild => error, return; - if(type==9 && _player->GetGuildId()) - return; - - result = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE petitionguid = '%u'", petitionguid_low); - - // result==NULL also correct in case no sign yet - if(result) - signs = result->GetRowCount(); - - sLog.outDebug("CMSG_PETITION_SHOW_SIGNATURES petition entry: '%u'", petitionguid_low); - - WorldPacket data(SMSG_PETITION_SHOW_SIGNATURES, (8+8+4+1+signs*12)); - data << petitionguid; // petition guid - data << _player->GetGUID(); // owner guid - data << petitionguid_low; // guild guid (in mangos always same as GUID_LOPART(petitionguid) - data << signs; // sign's count - - for(uint8 i = 1; i <= signs; i++) - { - Field *fields = result->Fetch(); - uint64 plguid = fields[0].GetUInt64(); - - data << plguid; // Player GUID - data << (uint32)0; // there 0 ... - - result->NextRow(); - } - delete result; - SendPacket(&data); -} - -void WorldSession::HandlePetitionQueryOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 4+8); - - sLog.outDebug("Received opcode CMSG_PETITION_QUERY"); // ok - //recv_data.hexlike(); - - uint32 guildguid; - uint64 petitionguid; - recv_data >> guildguid; // in mangos always same as GUID_LOPART(petitionguid) - recv_data >> petitionguid; // petition guid - sLog.outDebug("CMSG_PETITION_QUERY Petition GUID %u Guild GUID %u", GUID_LOPART(petitionguid), guildguid); - - SendPetitionQueryOpcode(petitionguid); -} - -void WorldSession::SendPetitionQueryOpcode(uint64 petitionguid) -{ - uint64 ownerguid = 0; - uint32 type; - std::string name = "NO_NAME_FOR_GUID"; - uint8 signs = 0; - - QueryResult *result = CharacterDatabase.PQuery( - "SELECT ownerguid, name, " - " (SELECT COUNT(playerguid) FROM petition_sign WHERE petition_sign.petitionguid = '%u') AS signs " - "FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid), GUID_LOPART(petitionguid)); - - if(result) - { - Field* fields = result->Fetch(); - ownerguid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_PLAYER); - name = fields[1].GetCppString(); - signs = fields[2].GetUInt8(); - delete result; - } - else - { - sLog.outDebug("CMSG_PETITION_QUERY failed for petition (GUID: %u)", GUID_LOPART(petitionguid)); - return; - } - - QueryResult *result2 = CharacterDatabase.PQuery("SELECT type FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - - if(result2) - { - Field* fields = result2->Fetch(); - type = fields[0].GetUInt32(); - delete result2; - } - else - { - sLog.outDebug("CMSG_PETITION_QUERY failed for petition (GUID: %u)", GUID_LOPART(petitionguid)); - return; - } - - WorldPacket data(SMSG_PETITION_QUERY_RESPONSE, (4+8+name.size()+1+1+4*13)); - data << GUID_LOPART(petitionguid); // guild/team guid (in mangos always same as GUID_LOPART(petition guid) - data << ownerguid; // charter owner guid - data << name; // name (guild/arena team) - data << uint8(0); // 1 - if(type == 9) - { - data << uint32(9); - data << uint32(9); - data << uint32(0); // bypass client - side limitation, a different value is needed here for each petition - } - else - { - data << type-1; - data << type-1; - data << type; // bypass client - side limitation, a different value is needed here for each petition - } - data << uint32(0); // 5 - data << uint32(0); // 6 - data << uint32(0); // 7 - data << uint32(0); // 8 - data << uint16(0); // 9 2 bytes field - data << uint32(0); // 10 - data << uint32(0); // 11 - data << uint32(0); // 13 count of next strings? - data << uint32(0); // 14 - if(type == 9) - data << uint32(0); // 15 0 - guild, 1 - arena team - else - data << uint32(1); - SendPacket(&data); -} - -void WorldSession::HandlePetitionRenameOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 8+1); - - sLog.outDebug("Received opcode MSG_PETITION_RENAME"); // ok - //recv_data.hexlike(); - - uint64 petitionguid; - uint32 type; - std::string newname; - - recv_data >> petitionguid; // guid - recv_data >> newname; // new name - - Item *item = _player->GetItemByGuid(petitionguid); - if(!item) - return; - - QueryResult *result2 = CharacterDatabase.PQuery("SELECT type FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - - if(result2) - { - Field* fields = result2->Fetch(); - type = fields[0].GetUInt32(); - delete result2; - } - else - { - sLog.outDebug("CMSG_PETITION_QUERY failed for petition (GUID: %u)", GUID_LOPART(petitionguid)); - return; - } - - if(type == 9) - { - if(objmgr.GetGuildByName(newname)) - { - SendGuildCommandResult(GUILD_CREATE_S, newname, GUILD_NAME_EXISTS); - return; - } - if(objmgr.IsReservedName(newname)) - { - SendGuildCommandResult(GUILD_CREATE_S, newname, GUILD_NAME_INVALID); - return; - } - if(!ObjectMgr::IsValidCharterName(newname)) - { - SendGuildCommandResult(GUILD_CREATE_S, newname, GUILD_NAME_INVALID); - return; - } - } - else - { - if(objmgr.GetArenaTeamByName(newname)) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, newname, "", ERR_ARENA_TEAM_NAME_EXISTS_S); - return; - } - if(objmgr.IsReservedName(newname)) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, newname, "", ERR_ARENA_TEAM_NAME_INVALID); - return; - } - if(!ObjectMgr::IsValidCharterName(newname)) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, newname, "", ERR_ARENA_TEAM_NAME_INVALID); - return; - } - } - - std::string db_newname = newname; - CharacterDatabase.escape_string(db_newname); - CharacterDatabase.PExecute("UPDATE petition SET name = '%s' WHERE petitionguid = '%u'", - db_newname.c_str(), GUID_LOPART(petitionguid)); - - sLog.outDebug("Petition (GUID: %u) renamed to '%s'", GUID_LOPART(petitionguid), newname.c_str()); - WorldPacket data(MSG_PETITION_RENAME, (8+newname.size()+1)); - data << petitionguid; - data << newname; - SendPacket(&data); -} - -void WorldSession::HandlePetitionSignOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 8+1); - - sLog.outDebug("Received opcode CMSG_PETITION_SIGN"); // ok - //recv_data.hexlike(); - - Field *fields; - uint64 petitionguid; - uint32 type; - uint8 unk; - uint64 ownerguid; - recv_data >> petitionguid; // petition guid - recv_data >> unk; - - uint8 signs = 0; - - QueryResult *result = CharacterDatabase.PQuery( - "SELECT ownerguid, " - " (SELECT COUNT(playerguid) FROM petition_sign WHERE petition_sign.petitionguid = '%u') AS signs " - "FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid), GUID_LOPART(petitionguid)); - - if(!result) - { - sLog.outError("any petition on server..."); - return; - } - - fields = result->Fetch(); - ownerguid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_PLAYER); - signs = fields[1].GetUInt8(); - - delete result; - - uint32 plguidlo = _player->GetGUIDLow(); - if(GUID_LOPART(ownerguid) == plguidlo) - return; - - // not let enemies sign guild charter - if(!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && GetPlayer()->GetTeam() != objmgr.GetPlayerTeamByGUID(ownerguid)) - return; - - QueryResult *result2 = CharacterDatabase.PQuery("SELECT type FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - - if(result2) - { - Field* fields = result2->Fetch(); - type = fields[0].GetUInt32(); - delete result2; - } - else - { - sLog.outDebug("CMSG_PETITION_QUERY failed for petition (GUID: %u)", GUID_LOPART(petitionguid)); - return; - } - - if(type != 9 && _player->getLevel() < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) - { - // player is too low level to join an arena team - SendNotification("You must be level %u to join an arena team!",sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)); - return; - } - - signs += 1; - if(signs > type) // client signs maximum - return; - - //client doesn't allow to sign petition two times by one character, but not check sign by another character from same account - //not allow sign another player from already sign player account - result = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE player_account = '%u' AND petitionguid = '%u'", GetAccountId(), GUID_LOPART(petitionguid)); - - if(result) - { - delete result; - WorldPacket data(SMSG_PETITION_SIGN_RESULTS, (8+8+4)); - data << petitionguid; - data << _player->GetGUID(); - data << (uint32)PETITION_SIGN_ALREADY_SIGNED; - - // close at signer side - SendPacket(&data); - - // update for owner if online - if(Player *owner = objmgr.GetPlayer(ownerguid)) - owner->GetSession()->SendPacket(&data); - return; - } - - CharacterDatabase.PExecute("INSERT INTO petition_sign (ownerguid,petitionguid, playerguid, player_account) VALUES ('%u', '%u', '%u','%u')", GUID_LOPART(ownerguid),GUID_LOPART(petitionguid), plguidlo,GetAccountId()); - - sLog.outDebug("PETITION SIGN: GUID %u by player: %s (GUID: %u Account: %u)", GUID_LOPART(petitionguid), _player->GetName(),plguidlo,GetAccountId()); - - WorldPacket data(SMSG_PETITION_SIGN_RESULTS, (8+8+4)); - data << petitionguid; - data << _player->GetGUID(); - data << (uint32)PETITION_SIGN_OK; - - // close at signer side - SendPacket(&data); - - // update signs count on charter, required testing... - //Item *item = _player->GetItemByGuid(petitionguid)); - //if(item) - // item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT+1, signs); - - // update for owner if online - if(Player *owner = objmgr.GetPlayer(ownerguid)) - owner->GetSession()->SendPacket(&data); -} - -void WorldSession::HandlePetitionDeclineOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - sLog.outDebug("Received opcode MSG_PETITION_DECLINE"); // ok - //recv_data.hexlike(); - - uint64 petitionguid; - uint64 ownerguid; - recv_data >> petitionguid; // petition guid - sLog.outDebug("Petition %u declined by %u", GUID_LOPART(petitionguid), _player->GetGUIDLow()); - - QueryResult *result = CharacterDatabase.PQuery("SELECT ownerguid FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - if(!result) - return; - - Field *fields = result->Fetch(); - ownerguid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_PLAYER); - delete result; - - Player *owner = objmgr.GetPlayer(ownerguid); - if(owner) // petition owner online - { - WorldPacket data(MSG_PETITION_DECLINE, 8); - data << _player->GetGUID(); - owner->GetSession()->SendPacket(&data); - } -} - -void WorldSession::HandleOfferPetitionOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 4+8+8); - - sLog.outDebug("Received opcode CMSG_OFFER_PETITION"); // ok - //recv_data.hexlike(); - - uint8 signs = 0; - uint64 petitionguid, plguid; - uint32 petitiontype; - Player *player; - recv_data >> petitiontype; // 2.0.8 - petition type? - recv_data >> petitionguid; // petition guid - recv_data >> plguid; // player guid - sLog.outDebug("OFFER PETITION: type %u, GUID1 %u, to player id: %u", petitiontype, GUID_LOPART(petitionguid), GUID_LOPART(plguid)); - - player = ObjectAccessor::FindPlayer(plguid); - if(!player || player->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow())) - return; - - // not let offer to enemies - if (!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && GetPlayer()->GetTeam() != player->GetTeam() ) - return; - - QueryResult *result = CharacterDatabase.PQuery("SELECT petitionguid FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - if(!result) - { - sLog.outError("any petition on server..."); - return; - } - - delete result; - - result = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - // result==NULL also correct charter without signs - if(result) - signs = result->GetRowCount(); - - WorldPacket data(SMSG_PETITION_SHOW_SIGNATURES, (8+8+4+signs+signs*12)); - data << petitionguid; // petition guid - data << _player->GetGUID(); // owner guid - data << GUID_LOPART(petitionguid); // guild guid (in mangos always same as GUID_LOPART(petition guid) - data << signs; // sign's count - - for(uint8 i = 1; i <= signs; i++) - { - Field *fields = result->Fetch(); - uint64 plguid = fields[0].GetUInt64(); - - data << plguid; // Player GUID - data << (uint32)0; // there 0 ... - - result->NextRow(); - } - - delete result; - player->GetSession()->SendPacket(&data); -} - -void WorldSession::HandleTurnInPetitionOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - sLog.outDebug("Received opcode CMSG_TURN_IN_PETITION"); // ok - //recv_data.hexlike(); - - WorldPacket data; - uint64 petitionguid; - - uint32 ownerguidlo; - uint32 type; - std::string name; - - recv_data >> petitionguid; - - sLog.outDebug("Petition %u turned in by %u", GUID_LOPART(petitionguid), _player->GetGUIDLow()); - - // data - QueryResult *result = CharacterDatabase.PQuery("SELECT ownerguid, name, type FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - if(result) - { - Field *fields = result->Fetch(); - ownerguidlo = fields[0].GetUInt32(); - name = fields[1].GetCppString(); - type = fields[2].GetUInt32(); - delete result; - } - else - { - sLog.outError("petition table has broken data!"); - return; - } - - if(type == 9) - { - if(_player->GetGuildId()) - { - data.Initialize(SMSG_TURN_IN_PETITION_RESULTS, 4); - data << (uint32)PETITION_TURN_ALREADY_IN_GUILD; // already in guild - _player->GetSession()->SendPacket(&data); - return; - } - } - else - { - uint8 slot = ArenaTeam::GetSlotByType(type); - if(slot >= MAX_ARENA_SLOT) - return; - - if(_player->GetArenaTeamId(slot)) - { - //data.Initialize(SMSG_TURN_IN_PETITION_RESULTS, 4); - //data << (uint32)PETITION_TURN_ALREADY_IN_GUILD; // already in guild - //_player->GetSession()->SendPacket(&data); - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ALREADY_IN_ARENA_TEAM); - return; - } - } - - if(_player->GetGUIDLow() != ownerguidlo) - return; - - // signs - uint8 signs; - result = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - if(result) - signs = result->GetRowCount(); - else - signs = 0; - - uint32 count; - //if(signs < sWorld.getConfig(CONFIG_MIN_PETITION_SIGNS)) - if(type == 9) - count = sWorld.getConfig(CONFIG_MIN_PETITION_SIGNS); - else - count = type-1; - if(signs < count) - { - data.Initialize(SMSG_TURN_IN_PETITION_RESULTS, 4); - data << (uint32)PETITION_TURN_NEED_MORE_SIGNATURES; // need more signatures... - SendPacket(&data); - delete result; - return; - } - - if(type == 9) - { - if(objmgr.GetGuildByName(name)) - { - SendGuildCommandResult(GUILD_CREATE_S, name, GUILD_NAME_EXISTS); - delete result; - return; - } - } - else - { - if(objmgr.GetArenaTeamByName(name)) - { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_EXISTS_S); - delete result; - return; - } - } - - // and at last charter item check - Item *item = _player->GetItemByGuid(petitionguid); - if(!item) - { - delete result; - return; - } - - // OK! - - // delete charter item - _player->DestroyItem(item->GetBagSlot(),item->GetSlot(), true); - - if(type == 9) // create guild - { - Guild* guild = new Guild; - if(!guild->create(_player->GetGUID(), name)) - { - delete guild; - delete result; - return; - } - - // register guild and add guildmaster - objmgr.AddGuild(guild); - - // add members - for(uint8 i = 0; i < signs; ++i) - { - Field* fields = result->Fetch(); - guild->AddMember(fields[0].GetUInt64(), guild->GetLowestRank()); - result->NextRow(); - } - } - else // or arena team - { - ArenaTeam* at = new ArenaTeam; - if(!at->create(_player->GetGUID(), type, name)) - { - sLog.outError("PetitionsHandler: arena team create failed."); - delete at; - delete result; - return; - } - - CHECK_PACKET_SIZE(recv_data, 8+5*4); - uint32 icon, iconcolor, border, bordercolor, backgroud; - recv_data >> backgroud >> icon >> iconcolor >> border >> bordercolor; - - at->SetEmblem(backgroud, icon, iconcolor, border, bordercolor); - - // register team and add captain - objmgr.AddArenaTeam(at); - sLog.outDebug("PetitonsHandler: arena team added to objmrg"); - - // add members - for(uint8 i = 0; i < signs; ++i) - { - Field* fields = result->Fetch(); - sLog.outDebug("PetitionsHandler: adding arena member %u", fields[0].GetUInt64()); - at->AddMember(fields[0].GetUInt64()); - result->NextRow(); - } - } - - delete result; - - CharacterDatabase.BeginTransaction(); - CharacterDatabase.PExecute("DELETE FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - CharacterDatabase.PExecute("DELETE FROM petition_sign WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); - CharacterDatabase.CommitTransaction(); - - // created - sLog.outDebug("TURN IN PETITION GUID %u", GUID_LOPART(petitionguid)); - - data.Initialize(SMSG_TURN_IN_PETITION_RESULTS, 4); - data << (uint32)PETITION_TURN_OK; - SendPacket(&data); -} - -void WorldSession::HandlePetitionShowListOpcode(WorldPacket & recv_data) -{ - CHECK_PACKET_SIZE(recv_data, 8); - - sLog.outDebug("Received CMSG_PETITION_SHOWLIST"); // ok - //recv_data.hexlike(); - - uint64 guid; - recv_data >> guid; - - SendPetitionShowList(guid); -} - -void WorldSession::SendPetitionShowList(uint64 guid) -{ - Creature *pCreature = ObjectAccessor::GetNPCIfCanInteractWith(*_player, guid, UNIT_NPC_FLAG_PETITIONER); - if (!pCreature) - { - sLog.outDebug("WORLD: HandlePetitionShowListOpcode - Unit (GUID: %u) not found or you can't interact with him.", uint32(GUID_LOPART(guid))); - return; - } - - // remove fake death - if(GetPlayer()->hasUnitState(UNIT_STAT_DIED)) - GetPlayer()->RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH); - - uint8 count = 0; - if(pCreature->isTabardDesigner()) - count = 1; - else - count = 3; - - WorldPacket data(SMSG_PETITION_SHOWLIST, 8+1+4*6); - data << guid; // npc guid - data << count; // count - if(count == 1) - { - data << uint32(1); // index - data << uint32(GUILD_CHARTER); // charter entry - data << uint32(16161); // charter display id - data << uint32(GUILD_CHARTER_COST); // charter cost - data << uint32(0); // unknown - data << uint32(9); // required signs? - } - else - { - // 2v2 - data << uint32(1); // index - data << uint32(ARENA_TEAM_CHARTER_2v2); // charter entry - data << uint32(16161); // charter display id - data << uint32(ARENA_TEAM_CHARTER_2v2_COST); // charter cost - data << uint32(2); // unknown - data << uint32(2); // required signs? - // 3v3 - data << uint32(2); // index - data << uint32(ARENA_TEAM_CHARTER_3v3); // charter entry - data << uint32(16161); // charter display id - data << uint32(ARENA_TEAM_CHARTER_3v3_COST); // charter cost - data << uint32(3); // unknown - data << uint32(3); // required signs? - // 5v5 - data << uint32(3); // index - data << uint32(ARENA_TEAM_CHARTER_5v5); // charter entry - data << uint32(16161); // charter display id - data << uint32(ARENA_TEAM_CHARTER_5v5_COST); // charter cost - data << uint32(5); // unknown - data << uint32(5); // required signs? - } - //for(uint8 i = 0; i < count; i++) - //{ - // data << uint32(i); // index - // data << uint32(GUILD_CHARTER); // charter entry - // data << uint32(16161); // charter display id - // data << uint32(GUILD_CHARTER_COST+i); // charter cost - // data << uint32(0); // unknown - // data << uint32(9); // required signs? - //} - SendPacket(&data); - sLog.outDebug("Sent SMSG_PETITION_SHOWLIST"); -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "Language.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "World.h" +#include "ObjectMgr.h" +#include "Log.h" +#include "Opcodes.h" +#include "Guild.h" +#include "ArenaTeam.h" +#include "MapManager.h" +#include "GossipDef.h" +#include "SocialMgr.h" + +/*enum PetitionType // dbc data +{ + PETITION_TYPE_GUILD = 1, + PETITION_TYPE_ARENA_TEAM = 3 +};*/ + +// Charters ID in item_template +#define GUILD_CHARTER 5863 +#define GUILD_CHARTER_COST 1000 // 10 S +#define ARENA_TEAM_CHARTER_2v2 23560 +#define ARENA_TEAM_CHARTER_2v2_COST 800000 // 80 G +#define ARENA_TEAM_CHARTER_3v3 23561 +#define ARENA_TEAM_CHARTER_3v3_COST 1200000 // 120 G +#define ARENA_TEAM_CHARTER_5v5 23562 +#define ARENA_TEAM_CHARTER_5v5_COST 2000000 // 200 G + +void WorldSession::HandlePetitionBuyOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 8+8+4+1+5*8+2+1+4+4); + + sLog.outDebug("Received opcode CMSG_PETITION_BUY"); + //recv_data.hexlike(); + + uint64 guidNPC; + uint64 unk1, unk3, unk4, unk5, unk6, unk7; + uint32 unk2; + std::string name; + uint16 unk8; + uint8 unk9; + uint32 unk10; // selected index + uint32 unk11; + recv_data >> guidNPC; // NPC GUID + recv_data >> unk1; // 0 + recv_data >> unk2; // 0 + recv_data >> name; // name + + // recheck + CHECK_PACKET_SIZE(recv_data, 8+8+4+(name.size()+1)+5*8+2+1+4+4); + + recv_data >> unk3; // 0 + recv_data >> unk4; // 0 + recv_data >> unk5; // 0 + recv_data >> unk6; // 0 + recv_data >> unk7; // 0 + recv_data >> unk8; // 0 + recv_data >> unk9; // 0 + recv_data >> unk10; // index + recv_data >> unk11; // 0 + sLog.outDebug("Petitioner with GUID %u tried sell petition: name %s", GUID_LOPART(guidNPC), name.c_str()); + + // prevent cheating + Creature *pCreature = ObjectAccessor::GetNPCIfCanInteractWith(*_player, guidNPC,UNIT_NPC_FLAG_PETITIONER); + if (!pCreature) + { + sLog.outDebug("WORLD: HandlePetitionBuyOpcode - Unit (GUID: %u) not found or you can't interact with him.", GUID_LOPART(guidNPC)); + return; + } + + // remove fake death + if(GetPlayer()->hasUnitState(UNIT_STAT_DIED)) + GetPlayer()->RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH); + + uint32 charterid = 0; + uint32 cost = 0; + uint32 type = 0; + if(pCreature->isTabardDesigner()) + { + // if tabard designer, then trying to buy a guild charter. + // do not let if already in guild. + if(_player->GetGuildId()) + return; + + charterid = GUILD_CHARTER; + cost = GUILD_CHARTER_COST; + type = 9; + } + else + { + // TODO: find correct opcode + if(_player->getLevel() < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + { + SendNotification(LANG_ARENA_ONE_TOOLOW, 70); + return; + } + + for(uint8 i = 0; i < MAX_ARENA_SLOT; i++) + { + if(_player->GetArenaTeamId(i) && (i == (unk10-1))) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ALREADY_IN_ARENA_TEAM); + return; + } + } + switch(unk10) + { + case 1: + charterid = ARENA_TEAM_CHARTER_2v2; + cost = ARENA_TEAM_CHARTER_2v2_COST; + type = 2; // 2v2 + break; + case 2: + charterid = ARENA_TEAM_CHARTER_3v3; + cost = ARENA_TEAM_CHARTER_3v3_COST; + type = 3; // 3v3 + break; + case 3: + charterid = ARENA_TEAM_CHARTER_5v5; + cost = ARENA_TEAM_CHARTER_5v5_COST; + type = 5; // 5v5 + break; + default: + sLog.outDebug("unknown selection at buy petition: %u", unk10); + return; + } + } + + if(type == 9) + { + if(objmgr.GetGuildByName(name)) + { + SendGuildCommandResult(GUILD_CREATE_S, name, GUILD_NAME_EXISTS); + return; + } + if(objmgr.IsReservedName(name)) + { + SendGuildCommandResult(GUILD_CREATE_S, name, GUILD_NAME_INVALID); + return; + } + if(!ObjectMgr::IsValidCharterName(name)) + { + SendGuildCommandResult(GUILD_CREATE_S, name, GUILD_NAME_INVALID); + return; + } + } + else + { + if(objmgr.GetArenaTeamByName(name)) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_EXISTS_S); + return; + } + if(objmgr.IsReservedName(name)) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_INVALID); + return; + } + if(!ObjectMgr::IsValidCharterName(name)) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_INVALID); + return; + } + } + + ItemPrototype const *pProto = objmgr.GetItemPrototype(charterid); + if(!pProto) + { + _player->SendBuyError(BUY_ERR_CANT_FIND_ITEM, NULL, charterid, 0); + return; + } + + if(_player->GetMoney() < cost) + { //player hasn't got enough money + _player->SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, pCreature, charterid, 0); + return; + } + + ItemPosCountVec dest; + uint8 msg = _player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, charterid, pProto->BuyCount ); + if(msg != EQUIP_ERR_OK) + { + _player->SendBuyError(msg, pCreature, charterid, 0); + return; + } + + _player->ModifyMoney(-(int32)cost); + Item *charter = _player->StoreNewItem(dest, charterid, true); + if(!charter) + return; + + charter->SetUInt32Value(ITEM_FIELD_ENCHANTMENT, charter->GetGUIDLow()); + // ITEM_FIELD_ENCHANTMENT is guild/arenateam id + // ITEM_FIELD_ENCHANTMENT+1 is current signatures count (showed on item) + charter->SetState(ITEM_CHANGED, _player); + _player->SendNewItem(charter, 1, true, false); + + // a petition is invalid, if both the owner and the type matches + QueryResult *result = CharacterDatabase.PQuery("SELECT petitionguid FROM petition WHERE ownerguid = '%u' AND type = '%u'", _player->GetGUIDLow(), type); + + std::ostringstream ssInvalidPetitionGUIDs; + + if (result) + { + + do + { + Field *fields = result->Fetch(); + ssInvalidPetitionGUIDs << "'" << fields[0].GetUInt32() << "' , "; + } while (result->NextRow()); + + delete result; + } + + // delete petitions with the same guid as this one + ssInvalidPetitionGUIDs << "'" << charter->GetGUIDLow() << "'"; + + sLog.outDebug("Invalid petition GUIDs: %s", ssInvalidPetitionGUIDs.str().c_str()); + CharacterDatabase.escape_string(name); + CharacterDatabase.BeginTransaction(); + CharacterDatabase.PExecute("DELETE FROM petition WHERE petitionguid IN ( %s )", ssInvalidPetitionGUIDs.str().c_str()); + CharacterDatabase.PExecute("DELETE FROM petition_sign WHERE petitionguid IN ( %s )", ssInvalidPetitionGUIDs.str().c_str()); + CharacterDatabase.PExecute("INSERT INTO petition (ownerguid, petitionguid, name, type) VALUES ('%u', '%u', '%s', '%u')", + _player->GetGUIDLow(), charter->GetGUIDLow(), name.c_str(), type); + CharacterDatabase.CommitTransaction(); +} + +void WorldSession::HandlePetitionShowSignOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + // ok + sLog.outDebug("Received opcode CMSG_PETITION_SHOW_SIGNATURES"); + //recv_data.hexlike(); + + uint8 signs = 0; + uint64 petitionguid; + recv_data >> petitionguid; // petition guid + + // solve (possible) some strange compile problems with explicit use GUID_LOPART(petitionguid) at some GCC versions (wrong code optimization in compiler?) + uint32 petitionguid_low = GUID_LOPART(petitionguid); + + QueryResult *result = CharacterDatabase.PQuery("SELECT petitionguid, type FROM petition WHERE petitionguid = '%u'", petitionguid_low); + if(!result) + { + sLog.outError("any petition on server..."); + return; + } + Field *fields = result->Fetch(); + uint32 type = fields[1].GetUInt32(); + delete result; + // if guild petition and has guild => error, return; + if(type==9 && _player->GetGuildId()) + return; + + result = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE petitionguid = '%u'", petitionguid_low); + + // result==NULL also correct in case no sign yet + if(result) + signs = result->GetRowCount(); + + sLog.outDebug("CMSG_PETITION_SHOW_SIGNATURES petition entry: '%u'", petitionguid_low); + + WorldPacket data(SMSG_PETITION_SHOW_SIGNATURES, (8+8+4+1+signs*12)); + data << petitionguid; // petition guid + data << _player->GetGUID(); // owner guid + data << petitionguid_low; // guild guid (in mangos always same as GUID_LOPART(petitionguid) + data << signs; // sign's count + + for(uint8 i = 1; i <= signs; i++) + { + Field *fields = result->Fetch(); + uint64 plguid = fields[0].GetUInt64(); + + data << plguid; // Player GUID + data << (uint32)0; // there 0 ... + + result->NextRow(); + } + delete result; + SendPacket(&data); +} + +void WorldSession::HandlePetitionQueryOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 4+8); + + sLog.outDebug("Received opcode CMSG_PETITION_QUERY"); // ok + //recv_data.hexlike(); + + uint32 guildguid; + uint64 petitionguid; + recv_data >> guildguid; // in mangos always same as GUID_LOPART(petitionguid) + recv_data >> petitionguid; // petition guid + sLog.outDebug("CMSG_PETITION_QUERY Petition GUID %u Guild GUID %u", GUID_LOPART(petitionguid), guildguid); + + SendPetitionQueryOpcode(petitionguid); +} + +void WorldSession::SendPetitionQueryOpcode(uint64 petitionguid) +{ + uint64 ownerguid = 0; + uint32 type; + std::string name = "NO_NAME_FOR_GUID"; + uint8 signs = 0; + + QueryResult *result = CharacterDatabase.PQuery( + "SELECT ownerguid, name, " + " (SELECT COUNT(playerguid) FROM petition_sign WHERE petition_sign.petitionguid = '%u') AS signs " + "FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid), GUID_LOPART(petitionguid)); + + if(result) + { + Field* fields = result->Fetch(); + ownerguid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_PLAYER); + name = fields[1].GetCppString(); + signs = fields[2].GetUInt8(); + delete result; + } + else + { + sLog.outDebug("CMSG_PETITION_QUERY failed for petition (GUID: %u)", GUID_LOPART(petitionguid)); + return; + } + + QueryResult *result2 = CharacterDatabase.PQuery("SELECT type FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + + if(result2) + { + Field* fields = result2->Fetch(); + type = fields[0].GetUInt32(); + delete result2; + } + else + { + sLog.outDebug("CMSG_PETITION_QUERY failed for petition (GUID: %u)", GUID_LOPART(petitionguid)); + return; + } + + WorldPacket data(SMSG_PETITION_QUERY_RESPONSE, (4+8+name.size()+1+1+4*13)); + data << GUID_LOPART(petitionguid); // guild/team guid (in mangos always same as GUID_LOPART(petition guid) + data << ownerguid; // charter owner guid + data << name; // name (guild/arena team) + data << uint8(0); // 1 + if(type == 9) + { + data << uint32(9); + data << uint32(9); + data << uint32(0); // bypass client - side limitation, a different value is needed here for each petition + } + else + { + data << type-1; + data << type-1; + data << type; // bypass client - side limitation, a different value is needed here for each petition + } + data << uint32(0); // 5 + data << uint32(0); // 6 + data << uint32(0); // 7 + data << uint32(0); // 8 + data << uint16(0); // 9 2 bytes field + data << uint32(0); // 10 + data << uint32(0); // 11 + data << uint32(0); // 13 count of next strings? + data << uint32(0); // 14 + if(type == 9) + data << uint32(0); // 15 0 - guild, 1 - arena team + else + data << uint32(1); + SendPacket(&data); +} + +void WorldSession::HandlePetitionRenameOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 8+1); + + sLog.outDebug("Received opcode MSG_PETITION_RENAME"); // ok + //recv_data.hexlike(); + + uint64 petitionguid; + uint32 type; + std::string newname; + + recv_data >> petitionguid; // guid + recv_data >> newname; // new name + + Item *item = _player->GetItemByGuid(petitionguid); + if(!item) + return; + + QueryResult *result2 = CharacterDatabase.PQuery("SELECT type FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + + if(result2) + { + Field* fields = result2->Fetch(); + type = fields[0].GetUInt32(); + delete result2; + } + else + { + sLog.outDebug("CMSG_PETITION_QUERY failed for petition (GUID: %u)", GUID_LOPART(petitionguid)); + return; + } + + if(type == 9) + { + if(objmgr.GetGuildByName(newname)) + { + SendGuildCommandResult(GUILD_CREATE_S, newname, GUILD_NAME_EXISTS); + return; + } + if(objmgr.IsReservedName(newname)) + { + SendGuildCommandResult(GUILD_CREATE_S, newname, GUILD_NAME_INVALID); + return; + } + if(!ObjectMgr::IsValidCharterName(newname)) + { + SendGuildCommandResult(GUILD_CREATE_S, newname, GUILD_NAME_INVALID); + return; + } + } + else + { + if(objmgr.GetArenaTeamByName(newname)) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, newname, "", ERR_ARENA_TEAM_NAME_EXISTS_S); + return; + } + if(objmgr.IsReservedName(newname)) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, newname, "", ERR_ARENA_TEAM_NAME_INVALID); + return; + } + if(!ObjectMgr::IsValidCharterName(newname)) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, newname, "", ERR_ARENA_TEAM_NAME_INVALID); + return; + } + } + + std::string db_newname = newname; + CharacterDatabase.escape_string(db_newname); + CharacterDatabase.PExecute("UPDATE petition SET name = '%s' WHERE petitionguid = '%u'", + db_newname.c_str(), GUID_LOPART(petitionguid)); + + sLog.outDebug("Petition (GUID: %u) renamed to '%s'", GUID_LOPART(petitionguid), newname.c_str()); + WorldPacket data(MSG_PETITION_RENAME, (8+newname.size()+1)); + data << petitionguid; + data << newname; + SendPacket(&data); +} + +void WorldSession::HandlePetitionSignOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 8+1); + + sLog.outDebug("Received opcode CMSG_PETITION_SIGN"); // ok + //recv_data.hexlike(); + + Field *fields; + uint64 petitionguid; + uint32 type; + uint8 unk; + uint64 ownerguid; + recv_data >> petitionguid; // petition guid + recv_data >> unk; + + uint8 signs = 0; + + QueryResult *result = CharacterDatabase.PQuery( + "SELECT ownerguid, " + " (SELECT COUNT(playerguid) FROM petition_sign WHERE petition_sign.petitionguid = '%u') AS signs " + "FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid), GUID_LOPART(petitionguid)); + + if(!result) + { + sLog.outError("any petition on server..."); + return; + } + + fields = result->Fetch(); + ownerguid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_PLAYER); + signs = fields[1].GetUInt8(); + + delete result; + + uint32 plguidlo = _player->GetGUIDLow(); + if(GUID_LOPART(ownerguid) == plguidlo) + return; + + // not let enemies sign guild charter + if(!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && GetPlayer()->GetTeam() != objmgr.GetPlayerTeamByGUID(ownerguid)) + return; + + QueryResult *result2 = CharacterDatabase.PQuery("SELECT type FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + + if(result2) + { + Field* fields = result2->Fetch(); + type = fields[0].GetUInt32(); + delete result2; + } + else + { + sLog.outDebug("CMSG_PETITION_QUERY failed for petition (GUID: %u)", GUID_LOPART(petitionguid)); + return; + } + + if(type != 9 && _player->getLevel() < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + { + // player is too low level to join an arena team + SendNotification(LANG_YOUR_ARENA_LEVEL_REQ_ERROR,sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)); + return; + } + + signs += 1; + if(signs > type) // client signs maximum + return; + + //client doesn't allow to sign petition two times by one character, but not check sign by another character from same account + //not allow sign another player from already sign player account + result = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE player_account = '%u' AND petitionguid = '%u'", GetAccountId(), GUID_LOPART(petitionguid)); + + if(result) + { + delete result; + WorldPacket data(SMSG_PETITION_SIGN_RESULTS, (8+8+4)); + data << petitionguid; + data << _player->GetGUID(); + data << (uint32)PETITION_SIGN_ALREADY_SIGNED; + + // close at signer side + SendPacket(&data); + + // update for owner if online + if(Player *owner = objmgr.GetPlayer(ownerguid)) + owner->GetSession()->SendPacket(&data); + return; + } + + CharacterDatabase.PExecute("INSERT INTO petition_sign (ownerguid,petitionguid, playerguid, player_account) VALUES ('%u', '%u', '%u','%u')", GUID_LOPART(ownerguid),GUID_LOPART(petitionguid), plguidlo,GetAccountId()); + + sLog.outDebug("PETITION SIGN: GUID %u by player: %s (GUID: %u Account: %u)", GUID_LOPART(petitionguid), _player->GetName(),plguidlo,GetAccountId()); + + WorldPacket data(SMSG_PETITION_SIGN_RESULTS, (8+8+4)); + data << petitionguid; + data << _player->GetGUID(); + data << (uint32)PETITION_SIGN_OK; + + // close at signer side + SendPacket(&data); + + // update signs count on charter, required testing... + //Item *item = _player->GetItemByGuid(petitionguid)); + //if(item) + // item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT+1, signs); + + // update for owner if online + if(Player *owner = objmgr.GetPlayer(ownerguid)) + owner->GetSession()->SendPacket(&data); +} + +void WorldSession::HandlePetitionDeclineOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + sLog.outDebug("Received opcode MSG_PETITION_DECLINE"); // ok + //recv_data.hexlike(); + + uint64 petitionguid; + uint64 ownerguid; + recv_data >> petitionguid; // petition guid + sLog.outDebug("Petition %u declined by %u", GUID_LOPART(petitionguid), _player->GetGUIDLow()); + + QueryResult *result = CharacterDatabase.PQuery("SELECT ownerguid FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + if(!result) + return; + + Field *fields = result->Fetch(); + ownerguid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_PLAYER); + delete result; + + Player *owner = objmgr.GetPlayer(ownerguid); + if(owner) // petition owner online + { + WorldPacket data(MSG_PETITION_DECLINE, 8); + data << _player->GetGUID(); + owner->GetSession()->SendPacket(&data); + } +} + +void WorldSession::HandleOfferPetitionOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 4+8+8); + + sLog.outDebug("Received opcode CMSG_OFFER_PETITION"); // ok + //recv_data.hexlike(); + + uint8 signs = 0; + uint64 petitionguid, plguid; + uint32 petitiontype; + Player *player; + recv_data >> petitiontype; // 2.0.8 - petition type? + recv_data >> petitionguid; // petition guid + recv_data >> plguid; // player guid + sLog.outDebug("OFFER PETITION: type %u, GUID1 %u, to player id: %u", petitiontype, GUID_LOPART(petitionguid), GUID_LOPART(plguid)); + + player = ObjectAccessor::FindPlayer(plguid); + if(!player || player->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow())) + return; + + // not let offer to enemies + if (!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && GetPlayer()->GetTeam() != player->GetTeam() ) + return; + + QueryResult *result = CharacterDatabase.PQuery("SELECT petitionguid FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + if(!result) + { + sLog.outError("any petition on server..."); + return; + } + + delete result; + + result = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + // result==NULL also correct charter without signs + if(result) + signs = result->GetRowCount(); + + WorldPacket data(SMSG_PETITION_SHOW_SIGNATURES, (8+8+4+signs+signs*12)); + data << petitionguid; // petition guid + data << _player->GetGUID(); // owner guid + data << GUID_LOPART(petitionguid); // guild guid (in mangos always same as GUID_LOPART(petition guid) + data << signs; // sign's count + + for(uint8 i = 1; i <= signs; i++) + { + Field *fields = result->Fetch(); + uint64 plguid = fields[0].GetUInt64(); + + data << plguid; // Player GUID + data << (uint32)0; // there 0 ... + + result->NextRow(); + } + + delete result; + player->GetSession()->SendPacket(&data); +} + +void WorldSession::HandleTurnInPetitionOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + sLog.outDebug("Received opcode CMSG_TURN_IN_PETITION"); // ok + //recv_data.hexlike(); + + WorldPacket data; + uint64 petitionguid; + + uint32 ownerguidlo; + uint32 type; + std::string name; + + recv_data >> petitionguid; + + sLog.outDebug("Petition %u turned in by %u", GUID_LOPART(petitionguid), _player->GetGUIDLow()); + + // data + QueryResult *result = CharacterDatabase.PQuery("SELECT ownerguid, name, type FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + if(result) + { + Field *fields = result->Fetch(); + ownerguidlo = fields[0].GetUInt32(); + name = fields[1].GetCppString(); + type = fields[2].GetUInt32(); + delete result; + } + else + { + sLog.outError("petition table has broken data!"); + return; + } + + if(type == 9) + { + if(_player->GetGuildId()) + { + data.Initialize(SMSG_TURN_IN_PETITION_RESULTS, 4); + data << (uint32)PETITION_TURN_ALREADY_IN_GUILD; // already in guild + _player->GetSession()->SendPacket(&data); + return; + } + } + else + { + uint8 slot = ArenaTeam::GetSlotByType(type); + if(slot >= MAX_ARENA_SLOT) + return; + + if(_player->GetArenaTeamId(slot)) + { + //data.Initialize(SMSG_TURN_IN_PETITION_RESULTS, 4); + //data << (uint32)PETITION_TURN_ALREADY_IN_GUILD; // already in guild + //_player->GetSession()->SendPacket(&data); + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ALREADY_IN_ARENA_TEAM); + return; + } + } + + if(_player->GetGUIDLow() != ownerguidlo) + return; + + // signs + uint8 signs; + result = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + if(result) + signs = result->GetRowCount(); + else + signs = 0; + + uint32 count; + //if(signs < sWorld.getConfig(CONFIG_MIN_PETITION_SIGNS)) + if(type == 9) + count = sWorld.getConfig(CONFIG_MIN_PETITION_SIGNS); + else + count = type-1; + if(signs < count) + { + data.Initialize(SMSG_TURN_IN_PETITION_RESULTS, 4); + data << (uint32)PETITION_TURN_NEED_MORE_SIGNATURES; // need more signatures... + SendPacket(&data); + delete result; + return; + } + + if(type == 9) + { + if(objmgr.GetGuildByName(name)) + { + SendGuildCommandResult(GUILD_CREATE_S, name, GUILD_NAME_EXISTS); + delete result; + return; + } + } + else + { + if(objmgr.GetArenaTeamByName(name)) + { + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_EXISTS_S); + delete result; + return; + } + } + + // and at last charter item check + Item *item = _player->GetItemByGuid(petitionguid); + if(!item) + { + delete result; + return; + } + + // OK! + + // delete charter item + _player->DestroyItem(item->GetBagSlot(),item->GetSlot(), true); + + if(type == 9) // create guild + { + Guild* guild = new Guild; + if(!guild->create(_player->GetGUID(), name)) + { + delete guild; + delete result; + return; + } + + // register guild and add guildmaster + objmgr.AddGuild(guild); + + // add members + for(uint8 i = 0; i < signs; ++i) + { + Field* fields = result->Fetch(); + guild->AddMember(fields[0].GetUInt64(), guild->GetLowestRank()); + result->NextRow(); + } + } + else // or arena team + { + ArenaTeam* at = new ArenaTeam; + if(!at->create(_player->GetGUID(), type, name)) + { + sLog.outError("PetitionsHandler: arena team create failed."); + delete at; + delete result; + return; + } + + CHECK_PACKET_SIZE(recv_data, 8+5*4); + uint32 icon, iconcolor, border, bordercolor, backgroud; + recv_data >> backgroud >> icon >> iconcolor >> border >> bordercolor; + + at->SetEmblem(backgroud, icon, iconcolor, border, bordercolor); + + // register team and add captain + objmgr.AddArenaTeam(at); + sLog.outDebug("PetitonsHandler: arena team added to objmrg"); + + // add members + for(uint8 i = 0; i < signs; ++i) + { + Field* fields = result->Fetch(); + sLog.outDebug("PetitionsHandler: adding arena member %u", fields[0].GetUInt64()); + at->AddMember(fields[0].GetUInt64()); + result->NextRow(); + } + } + + delete result; + + CharacterDatabase.BeginTransaction(); + CharacterDatabase.PExecute("DELETE FROM petition WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + CharacterDatabase.PExecute("DELETE FROM petition_sign WHERE petitionguid = '%u'", GUID_LOPART(petitionguid)); + CharacterDatabase.CommitTransaction(); + + // created + sLog.outDebug("TURN IN PETITION GUID %u", GUID_LOPART(petitionguid)); + + data.Initialize(SMSG_TURN_IN_PETITION_RESULTS, 4); + data << (uint32)PETITION_TURN_OK; + SendPacket(&data); +} + +void WorldSession::HandlePetitionShowListOpcode(WorldPacket & recv_data) +{ + CHECK_PACKET_SIZE(recv_data, 8); + + sLog.outDebug("Received CMSG_PETITION_SHOWLIST"); // ok + //recv_data.hexlike(); + + uint64 guid; + recv_data >> guid; + + SendPetitionShowList(guid); +} + +void WorldSession::SendPetitionShowList(uint64 guid) +{ + Creature *pCreature = ObjectAccessor::GetNPCIfCanInteractWith(*_player, guid, UNIT_NPC_FLAG_PETITIONER); + if (!pCreature) + { + sLog.outDebug("WORLD: HandlePetitionShowListOpcode - Unit (GUID: %u) not found or you can't interact with him.", uint32(GUID_LOPART(guid))); + return; + } + + // remove fake death + if(GetPlayer()->hasUnitState(UNIT_STAT_DIED)) + GetPlayer()->RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH); + + uint8 count = 0; + if(pCreature->isTabardDesigner()) + count = 1; + else + count = 3; + + WorldPacket data(SMSG_PETITION_SHOWLIST, 8+1+4*6); + data << guid; // npc guid + data << count; // count + if(count == 1) + { + data << uint32(1); // index + data << uint32(GUILD_CHARTER); // charter entry + data << uint32(16161); // charter display id + data << uint32(GUILD_CHARTER_COST); // charter cost + data << uint32(0); // unknown + data << uint32(9); // required signs? + } + else + { + // 2v2 + data << uint32(1); // index + data << uint32(ARENA_TEAM_CHARTER_2v2); // charter entry + data << uint32(16161); // charter display id + data << uint32(ARENA_TEAM_CHARTER_2v2_COST); // charter cost + data << uint32(2); // unknown + data << uint32(2); // required signs? + // 3v3 + data << uint32(2); // index + data << uint32(ARENA_TEAM_CHARTER_3v3); // charter entry + data << uint32(16161); // charter display id + data << uint32(ARENA_TEAM_CHARTER_3v3_COST); // charter cost + data << uint32(3); // unknown + data << uint32(3); // required signs? + // 5v5 + data << uint32(3); // index + data << uint32(ARENA_TEAM_CHARTER_5v5); // charter entry + data << uint32(16161); // charter display id + data << uint32(ARENA_TEAM_CHARTER_5v5_COST); // charter cost + data << uint32(5); // unknown + data << uint32(5); // required signs? + } + //for(uint8 i = 0; i < count; i++) + //{ + // data << uint32(i); // index + // data << uint32(GUILD_CHARTER); // charter entry + // data << uint32(16161); // charter display id + // data << uint32(GUILD_CHARTER_COST+i); // charter cost + // data << uint32(0); // unknown + // data << uint32(9); // required signs? + //} + SendPacket(&data); + sLog.outDebug("Sent SMSG_PETITION_SHOWLIST"); +} diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 0da7f36c3eb..56e123f6e6f 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -265,8 +265,8 @@ Player::Player (WorldSession *session): Unit() if(GetSession()->GetSecurity() >= SEC_GAMEMASTER) SetAcceptTicket(true); - // players always and GM if set in config accept whispers by default - if(GetSession()->GetSecurity() == SEC_PLAYER || sWorld.getConfig(CONFIG_GM_WISPERING_TO)) + // players always accept + if(GetSession()->GetSecurity() == SEC_PLAYER) SetAcceptWhispers(true); m_curSelection = 0; @@ -1415,7 +1415,7 @@ uint8 Player::chatTag() const // 0x4 - gm // 0x2 - dnd // 0x1 - afk - if(isGameMaster()) + if(isGMChat()) return 4; else if(isDND()) return 3; @@ -1555,10 +1555,13 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati } } - SetSemaphoreTeleport(false); - if(!GetSession()->PlayerLogout()) + { + // don't reset teleport semaphore while logging out, otherwise m_teleport_dest won't be used in Player::SaveToDB + SetSemaphoreTeleport(false); + UpdateZone(GetZoneId()); + } // new zone if(old_zone != GetZoneId()) @@ -9322,6 +9325,11 @@ uint8 Player::CanEquipItem( uint8 slot, uint16 &dest, Item *pItem, bool swap, bo ItemPrototype const *pProto = pItem->GetProto(); if( pProto ) { + // May be here should be more stronger checks; STUNNED checked + // ROOT, CONFUSED, DISTRACTED, FLEEING this needs to be checked. + if (not_loading && hasUnitState(UNIT_STAT_STUNNED)) + return EQUIP_ERR_YOU_ARE_STUNNED; + if(pItem->IsBindedNotWith(GetGUID())) return EQUIP_ERR_DONT_OWN_THAT_ITEM; @@ -9346,6 +9354,9 @@ uint8 Player::CanEquipItem( uint8 slot, uint16 &dest, Item *pItem, bool swap, bo if(isInCombat()&& pProto->Class == ITEM_CLASS_WEAPON && m_weaponChangeTimer != 0) return EQUIP_ERR_CANT_DO_RIGHT_NOW; // maybe exist better err + if(IsNonMeleeSpellCasted(false)) + return EQUIP_ERR_CANT_DO_RIGHT_NOW; + uint8 eslot = FindEquipSlot( pProto, slot, swap ); if( eslot == NULL_SLOT ) return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; @@ -13798,16 +13809,45 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder ) { switch(sWorld.getConfig(CONFIG_GM_LOGIN_STATE)) { - case 0: // disable - break; - case 1: // enable - SetGameMaster(true); - break; + default: + case 0: break; // disable + case 1: SetGameMaster(true); break; // enable case 2: // save state - if(gmstate) + if(gmstate & PLAYER_EXTRA_GM_ON) SetGameMaster(true); break; + } + + switch(sWorld.getConfig(CONFIG_GM_ACCEPT_TICKETS)) + { default: + case 0: break; // disable + case 1: SetAcceptTicket(true); break; // enable + case 2: // save state + if(gmstate & PLAYER_EXTRA_GM_ACCEPT_TICKETS) + SetAcceptTicket(true); + break; + } + + switch(sWorld.getConfig(CONFIG_GM_CHAT)) + { + default: + case 0: break; // disable + case 1: SetGMChat(true); break; // enable + case 2: // save state + if(gmstate & PLAYER_EXTRA_GM_CHAT) + SetGMChat(true); + break; + } + + switch(sWorld.getConfig(CONFIG_GM_WISPERING_TO)) + { + default: + case 0: break; // disable + case 1: SetAcceptWhispers(true); break; // enable + case 2: // save state + if(gmstate & PLAYER_EXTRA_ACCEPT_WHISPERS) + SetAcceptWhispers(true); break; } } @@ -14877,7 +14917,7 @@ void Player::SaveToDB() ss << "0"; ss << ", "; - ss << (isGameMaster()? 1 : 0); + ss << m_ExtraFlags; ss << ", "; ss << uint32(m_stableSlots); // to prevent save uint8 as char @@ -16387,13 +16427,15 @@ bool Player::BuyItemFromVendor(uint64 vendorguid, uint32 item, uint8 count, uint return false; } - VendorItem const* crItem = vItems->FindItem(item); - if(!crItem) + size_t vendor_slot = vItems->FindItemSlot(item); + if(vendor_slot >= vItems->GetItemCount()) { SendBuyError( BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0); return false; } + VendorItem const* crItem = vItems->m_items[vendor_slot]; + // check current item amount if it limited if( crItem->maxcount != 0 ) { @@ -16520,7 +16562,7 @@ bool Player::BuyItemFromVendor(uint64 vendorguid, uint32 item, uint8 count, uint WorldPacket data(SMSG_BUY_ITEM, (8+4+4+4)); data << pCreature->GetGUID(); - data << (uint32)crItem->item; + data << (uint32)(vendor_slot+1); // numbered from 1 at client data << (uint32)(crItem->maxcount > 0 ? new_count : 0xFFFFFFFF); data << (uint32)count; GetSession()->SendPacket(&data); @@ -16559,7 +16601,7 @@ bool Player::BuyItemFromVendor(uint64 vendorguid, uint32 item, uint8 count, uint WorldPacket data(SMSG_BUY_ITEM, (8+4+4+4)); data << pCreature->GetGUID(); - data << (uint32)crItem->item; + data << (uint32)(vendor_slot+1); // numbered from 1 at client data << (uint32)(crItem->maxcount > 0 ? new_count : 0xFFFFFFFF); data << (uint32)count; GetSession()->SendPacket(&data); diff --git a/src/game/Player.h b/src/game/Player.h index 33ccc79e307..b8c35e941f7 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -496,6 +496,7 @@ enum PlayerExtraFlags PLAYER_EXTRA_ACCEPT_WHISPERS = 0x0004, PLAYER_EXTRA_TAXICHEAT = 0x0008, PLAYER_EXTRA_GM_INVISIBLE = 0x0010, + PLAYER_EXTRA_GM_CHAT = 0x0020, // Show GM badge in chat messages // other states PLAYER_EXTRA_PVP_DEATH = 0x0100 // store PvP death status until corpse creating. @@ -954,6 +955,8 @@ class MANGOS_DLL_SPEC Player : public Unit void SetAcceptWhispers(bool on) { if(on) m_ExtraFlags |= PLAYER_EXTRA_ACCEPT_WHISPERS; else m_ExtraFlags &= ~PLAYER_EXTRA_ACCEPT_WHISPERS; } bool isGameMaster() const { return m_ExtraFlags & PLAYER_EXTRA_GM_ON; } void SetGameMaster(bool on); + bool isGMChat() const { return GetSession()->GetSecurity() >= SEC_MODERATOR && (m_ExtraFlags & PLAYER_EXTRA_GM_CHAT); } + void SetGMChat(bool on) { if(on) m_ExtraFlags |= PLAYER_EXTRA_GM_CHAT; else m_ExtraFlags &= ~PLAYER_EXTRA_GM_CHAT; } bool isTaxiCheater() const { return m_ExtraFlags & PLAYER_EXTRA_TAXICHEAT; } void SetTaxiCheater(bool on) { if(on) m_ExtraFlags |= PLAYER_EXTRA_TAXICHEAT; else m_ExtraFlags &= ~PLAYER_EXTRA_TAXICHEAT; } bool isGMVisible() const { return !(m_ExtraFlags & PLAYER_EXTRA_GM_INVISIBLE); } diff --git a/src/game/PointMovementGenerator.cpp b/src/game/PointMovementGenerator.cpp index b03263c8bb6..b0e685fa412 100644 --- a/src/game/PointMovementGenerator.cpp +++ b/src/game/PointMovementGenerator.cpp @@ -41,7 +41,7 @@ bool PointMovementGenerator::Update(T &unit, const uint32 &diff) if(!&unit) return false; - if(unit.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED)) + if(unit.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED)) return true; Traveller traveller(unit); diff --git a/src/game/RandomMovementGenerator.cpp b/src/game/RandomMovementGenerator.cpp index 15273843ffc..e27368c73f3 100644 --- a/src/game/RandomMovementGenerator.cpp +++ b/src/game/RandomMovementGenerator.cpp @@ -126,7 +126,7 @@ template<> bool RandomMovementGenerator::Update(Creature &creature, const uint32 &diff) { - if(creature.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED | UNIT_STAT_DISTRACTED)) + if(creature.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED | UNIT_STAT_DISTRACTED)) { i_nextMoveTime.Update(i_nextMoveTime.GetExpiry()); // Expire the timer creature.clearUnitState(UNIT_STAT_ROAMING); diff --git a/src/game/SocialMgr.cpp b/src/game/SocialMgr.cpp index 85afb48108a..858e6af6239 100644 --- a/src/game/SocialMgr.cpp +++ b/src/game/SocialMgr.cpp @@ -1,309 +1,311 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "SocialMgr.h" -#include "Policies/SingletonImp.h" -#include "Database/DatabaseEnv.h" -#include "Opcodes.h" -#include "WorldPacket.h" -#include "WorldSession.h" -#include "Player.h" -#include "ObjectMgr.h" -#include "World.h" -#include "Util.h" - -INSTANTIATE_SINGLETON_1( SocialMgr ); - -PlayerSocial::PlayerSocial() -{ - m_playerGUID = 0; -} - -PlayerSocial::~PlayerSocial() -{ - m_playerSocialMap.clear(); -} - -bool PlayerSocial::AddToSocialList(uint32 friend_guid, bool ignore) -{ - // prevent list (client-side) overflow - if(m_playerSocialMap.size() >= (255-1)) - return false; - - uint32 flag = SOCIAL_FLAG_FRIEND; - if(ignore) - flag = SOCIAL_FLAG_IGNORED; - - PlayerSocialMap::iterator itr = m_playerSocialMap.find(friend_guid); - if(itr != m_playerSocialMap.end()) - { - CharacterDatabase.PExecute("UPDATE character_social SET flags = (flags | %u) WHERE guid = '%u' AND friend = '%u'", flag, GetPlayerGUID(), friend_guid); - m_playerSocialMap[friend_guid].Flags |= flag; - } - else - { - CharacterDatabase.PExecute("INSERT INTO character_social (guid, friend, flags) VALUES ('%u', '%u', '%u')", GetPlayerGUID(), friend_guid, flag); - FriendInfo fi; - fi.Flags |= flag; - m_playerSocialMap[friend_guid] = fi; - } - return true; -} - -void PlayerSocial::RemoveFromSocialList(uint32 friend_guid, bool ignore) -{ - PlayerSocialMap::iterator itr = m_playerSocialMap.find(friend_guid); - if(itr == m_playerSocialMap.end()) // not exist - return; - - uint32 flag = SOCIAL_FLAG_FRIEND; - if(ignore) - flag = SOCIAL_FLAG_IGNORED; - - itr->second.Flags &= ~flag; - if(itr->second.Flags == 0) - { - CharacterDatabase.PExecute("DELETE FROM character_social WHERE guid = '%u' AND friend = '%u'", GetPlayerGUID(), friend_guid); - m_playerSocialMap.erase(itr); - } - else - { - CharacterDatabase.PExecute("UPDATE character_social SET flags = (flags & ~%u) WHERE guid = '%u' AND friend = '%u'", flag, GetPlayerGUID(), friend_guid); - } -} - -void PlayerSocial::SetFriendNote(uint32 friend_guid, std::string note) -{ - PlayerSocialMap::iterator itr = m_playerSocialMap.find(friend_guid); - if(itr == m_playerSocialMap.end()) // not exist - return; - - utf8truncate(note,48); // DB and client size limitation - - CharacterDatabase.escape_string(note); - CharacterDatabase.PExecute("UPDATE character_social SET note = '%s' WHERE guid = '%u' AND friend = '%u'", note.c_str(), GetPlayerGUID(), friend_guid); - m_playerSocialMap[friend_guid].Note = note; -} - -void PlayerSocial::SendSocialList() -{ - Player *plr = objmgr.GetPlayer(GetPlayerGUID()); - if(!plr) - return; - - uint32 size = m_playerSocialMap.size(); - - WorldPacket data(SMSG_CONTACT_LIST, (4+4+size*25)); // just can guess size - data << uint32(7); // unk flag (0x1, 0x2, 0x4), 0x7 if it include ignore list - data << uint32(size); // friends count - - for(PlayerSocialMap::iterator itr = m_playerSocialMap.begin(); itr != m_playerSocialMap.end(); ++itr) - { - sSocialMgr.GetFriendInfo(plr, itr->first, itr->second); - - data << uint64(itr->first); // player guid - data << uint32(itr->second.Flags); // player flag (0x1-friend?, 0x2-ignored?, 0x4-muted?) - data << itr->second.Note; // string note - if(itr->second.Flags & SOCIAL_FLAG_FRIEND) // if IsFriend() - { - data << uint8(itr->second.Status); // online/offline/etc? - if(itr->second.Status) // if online - { - data << uint32(itr->second.Area); // player area - data << uint32(itr->second.Level); // player level - data << uint32(itr->second.Class); // player class - } - } - } - - plr->GetSession()->SendPacket(&data); - sLog.outDebug("WORLD: Sent SMSG_CONTACT_LIST"); -} - -bool PlayerSocial::HasFriend(uint32 friend_guid) -{ - PlayerSocialMap::iterator itr = m_playerSocialMap.find(friend_guid); - if(itr != m_playerSocialMap.end()) - return itr->second.Flags & SOCIAL_FLAG_FRIEND; - return false; -} - -bool PlayerSocial::HasIgnore(uint32 ignore_guid) -{ - PlayerSocialMap::iterator itr = m_playerSocialMap.find(ignore_guid); - if(itr != m_playerSocialMap.end()) - return itr->second.Flags & SOCIAL_FLAG_IGNORED; - return false; -} - -SocialMgr::SocialMgr() -{ - -} - -SocialMgr::~SocialMgr() -{ - -} - -void SocialMgr::RemovePlayerSocial(uint32 guid) -{ - SocialMap::iterator itr = m_socialMap.find(guid); - if(itr != m_socialMap.end()) - m_socialMap.erase(itr); -} - -void SocialMgr::GetFriendInfo(Player *player, uint32 friendGUID, FriendInfo &friendInfo) -{ - if(!player) - return; - - Player *pFriend = ObjectAccessor::FindPlayer(friendGUID); - - uint32 team = player->GetTeam(); - uint32 security = player->GetSession()->GetSecurity(); - bool allowTwoSideWhoList = sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_WHO_LIST); - bool gmInWhoList = sWorld.getConfig(CONFIG_GM_IN_WHO_LIST) || security > SEC_PLAYER; - - // PLAYER see his team only and PLAYER can't see MODERATOR, GAME MASTER, ADMINISTRATOR characters - // MODERATOR, GAME MASTER, ADMINISTRATOR can see all - if( pFriend && pFriend->GetName() && - ( security > SEC_PLAYER || - ( pFriend->GetTeam() == team || allowTwoSideWhoList ) && - ( pFriend->GetSession()->GetSecurity() == SEC_PLAYER || gmInWhoList && pFriend->IsVisibleGloballyFor(player) ))) - { - friendInfo.Status = FRIEND_STATUS_ONLINE; - if(pFriend->isAFK()) - friendInfo.Status = FRIEND_STATUS_AFK; - if(pFriend->isDND()) - friendInfo.Status = FRIEND_STATUS_DND; - friendInfo.Area = pFriend->GetZoneId(); - friendInfo.Level = pFriend->getLevel(); - friendInfo.Class = pFriend->getClass(); - } - else - { - friendInfo.Status = FRIEND_STATUS_OFFLINE; - friendInfo.Area = 0; - friendInfo.Level = 0; - friendInfo.Class = 0; - } -} - -void SocialMgr::MakeFriendStatusPacket(FriendsResult result, uint32 guid, WorldPacket *data) -{ - data->Initialize(SMSG_FRIEND_STATUS, 5); - *data << uint8(result); - *data << uint64(guid); -} - -void SocialMgr::SendFriendStatus(Player *player, FriendsResult result, uint32 friend_guid, std::string name, bool broadcast) -{ - FriendInfo fi; - - WorldPacket data; - MakeFriendStatusPacket(result, friend_guid, &data); - switch(result) - { - case FRIEND_ONLINE: - GetFriendInfo(player, friend_guid, fi); - data << uint8(fi.Status); - data << uint32(fi.Area); - data << uint32(fi.Level); - data << uint32(fi.Class); - break; - case FRIEND_ADDED_ONLINE: - GetFriendInfo(player, friend_guid, fi); - data << name; - data << uint8(fi.Status); - data << uint32(fi.Area); - data << uint32(fi.Level); - data << uint32(fi.Class); - break; - case FRIEND_ADDED_OFFLINE: - data << name; - break; - } - - if(broadcast) - BroadcastToFriendListers(player, &data); - else - player->GetSession()->SendPacket(&data); -} - -void SocialMgr::BroadcastToFriendListers(Player *player, WorldPacket *packet) -{ - if(!player) - return; - - uint32 team = player->GetTeam(); - uint32 security = player->GetSession()->GetSecurity(); - uint32 guid = player->GetGUIDLow(); - bool gmInWhoList = sWorld.getConfig(CONFIG_GM_IN_WHO_LIST); - bool allowTwoSideWhoList = sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_WHO_LIST); - - for(SocialMap::iterator itr = m_socialMap.begin(); itr != m_socialMap.end(); ++itr) - { - PlayerSocialMap::iterator itr2 = itr->second.m_playerSocialMap.find(guid); - if(itr2 != itr->second.m_playerSocialMap.end() && (itr2->second.Flags & SOCIAL_FLAG_FRIEND)) - { - Player *pFriend = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER)); - - // PLAYER see his team only and PLAYER can't see MODERATOR, GAME MASTER, ADMINISTRATOR characters - // MODERATOR, GAME MASTER, ADMINISTRATOR can see all - if( pFriend && pFriend->IsInWorld() && - ( pFriend->GetSession()->GetSecurity() > SEC_PLAYER || - ( pFriend->GetTeam() == team || allowTwoSideWhoList ) && - (security == SEC_PLAYER || gmInWhoList && player->IsVisibleGloballyFor(pFriend) ))) - { - pFriend->GetSession()->SendPacket(packet); - } - } - } -} - -PlayerSocial *SocialMgr::LoadFromDB(QueryResult *result, uint32 guid) -{ - PlayerSocial *social = &m_socialMap[guid]; - social->SetPlayerGUID(guid); - - if(!result) - return social; - - uint32 friend_guid = 0; - uint32 flags = 0; - std::string note = ""; - - do - { - Field *fields = result->Fetch(); - - friend_guid = fields[0].GetUInt32(); - flags = fields[1].GetUInt32(); - note = fields[2].GetCppString(); - - social->m_playerSocialMap[friend_guid] = FriendInfo(flags, note); - - // prevent list (client-side) overflow - if(social->m_playerSocialMap.size() >= 255) - break; - } - while( result->NextRow() ); - delete result; - return social; -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "SocialMgr.h" +#include "Policies/SingletonImp.h" +#include "Database/DatabaseEnv.h" +#include "Opcodes.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "Player.h" +#include "ObjectMgr.h" +#include "World.h" +#include "Util.h" + +INSTANTIATE_SINGLETON_1( SocialMgr ); + +PlayerSocial::PlayerSocial() +{ + m_playerGUID = 0; +} + +PlayerSocial::~PlayerSocial() +{ + m_playerSocialMap.clear(); +} + +bool PlayerSocial::AddToSocialList(uint32 friend_guid, bool ignore) +{ + // client limit + if(m_playerSocialMap.size() >= 50) + return false; + + uint32 flag = SOCIAL_FLAG_FRIEND; + if(ignore) + flag = SOCIAL_FLAG_IGNORED; + + PlayerSocialMap::iterator itr = m_playerSocialMap.find(friend_guid); + if(itr != m_playerSocialMap.end()) + { + CharacterDatabase.PExecute("UPDATE character_social SET flags = (flags | %u) WHERE guid = '%u' AND friend = '%u'", flag, GetPlayerGUID(), friend_guid); + m_playerSocialMap[friend_guid].Flags |= flag; + } + else + { + CharacterDatabase.PExecute("INSERT INTO character_social (guid, friend, flags) VALUES ('%u', '%u', '%u')", GetPlayerGUID(), friend_guid, flag); + FriendInfo fi; + fi.Flags |= flag; + m_playerSocialMap[friend_guid] = fi; + } + return true; +} + +void PlayerSocial::RemoveFromSocialList(uint32 friend_guid, bool ignore) +{ + PlayerSocialMap::iterator itr = m_playerSocialMap.find(friend_guid); + if(itr == m_playerSocialMap.end()) // not exist + return; + + uint32 flag = SOCIAL_FLAG_FRIEND; + if(ignore) + flag = SOCIAL_FLAG_IGNORED; + + itr->second.Flags &= ~flag; + if(itr->second.Flags == 0) + { + CharacterDatabase.PExecute("DELETE FROM character_social WHERE guid = '%u' AND friend = '%u'", GetPlayerGUID(), friend_guid); + m_playerSocialMap.erase(itr); + } + else + { + CharacterDatabase.PExecute("UPDATE character_social SET flags = (flags & ~%u) WHERE guid = '%u' AND friend = '%u'", flag, GetPlayerGUID(), friend_guid); + } +} + +void PlayerSocial::SetFriendNote(uint32 friend_guid, std::string note) +{ + PlayerSocialMap::iterator itr = m_playerSocialMap.find(friend_guid); + if(itr == m_playerSocialMap.end()) // not exist + return; + + utf8truncate(note,48); // DB and client size limitation + + CharacterDatabase.escape_string(note); + CharacterDatabase.PExecute("UPDATE character_social SET note = '%s' WHERE guid = '%u' AND friend = '%u'", note.c_str(), GetPlayerGUID(), friend_guid); + m_playerSocialMap[friend_guid].Note = note; +} + +void PlayerSocial::SendSocialList() +{ + Player *plr = objmgr.GetPlayer(GetPlayerGUID()); + if(!plr) + return; + + uint32 size = m_playerSocialMap.size(); + + WorldPacket data(SMSG_CONTACT_LIST, (4+4+size*25)); // just can guess size + data << uint32(7); // unk flag (0x1, 0x2, 0x4), 0x7 if it include ignore list + data << uint32(size); // friends count + + for(PlayerSocialMap::iterator itr = m_playerSocialMap.begin(); itr != m_playerSocialMap.end(); ++itr) + { + sSocialMgr.GetFriendInfo(plr, itr->first, itr->second); + + data << uint64(itr->first); // player guid + data << uint32(itr->second.Flags); // player flag (0x1-friend?, 0x2-ignored?, 0x4-muted?) + data << itr->second.Note; // string note + if(itr->second.Flags & SOCIAL_FLAG_FRIEND) // if IsFriend() + { + data << uint8(itr->second.Status); // online/offline/etc? + if(itr->second.Status) // if online + { + data << uint32(itr->second.Area); // player area + data << uint32(itr->second.Level); // player level + data << uint32(itr->second.Class); // player class + } + } + } + + plr->GetSession()->SendPacket(&data); + sLog.outDebug("WORLD: Sent SMSG_CONTACT_LIST"); +} + +bool PlayerSocial::HasFriend(uint32 friend_guid) +{ + PlayerSocialMap::iterator itr = m_playerSocialMap.find(friend_guid); + if(itr != m_playerSocialMap.end()) + return itr->second.Flags & SOCIAL_FLAG_FRIEND; + return false; +} + +bool PlayerSocial::HasIgnore(uint32 ignore_guid) +{ + PlayerSocialMap::iterator itr = m_playerSocialMap.find(ignore_guid); + if(itr != m_playerSocialMap.end()) + return itr->second.Flags & SOCIAL_FLAG_IGNORED; + return false; +} + +SocialMgr::SocialMgr() +{ + +} + +SocialMgr::~SocialMgr() +{ + +} + +void SocialMgr::RemovePlayerSocial(uint32 guid) +{ + SocialMap::iterator itr = m_socialMap.find(guid); + if(itr != m_socialMap.end()) + m_socialMap.erase(itr); +} + +void SocialMgr::GetFriendInfo(Player *player, uint32 friendGUID, FriendInfo &friendInfo) +{ + if(!player) + return; + + Player *pFriend = ObjectAccessor::FindPlayer(friendGUID); + + uint32 team = player->GetTeam(); + uint32 security = player->GetSession()->GetSecurity(); + bool allowTwoSideWhoList = sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_WHO_LIST); + bool gmInWhoList = sWorld.getConfig(CONFIG_GM_IN_WHO_LIST) || security > SEC_PLAYER; + + PlayerSocialMap::iterator itr = player->GetSocial()->m_playerSocialMap.find(friendGUID); + if(itr != player->GetSocial()->m_playerSocialMap.end()) + friendInfo.Note = itr->second.Note; + + // PLAYER see his team only and PLAYER can't see MODERATOR, GAME MASTER, ADMINISTRATOR characters + // MODERATOR, GAME MASTER, ADMINISTRATOR can see all + if( pFriend && pFriend->GetName() && + ( security > SEC_PLAYER || + ( pFriend->GetTeam() == team || allowTwoSideWhoList ) && + ( pFriend->GetSession()->GetSecurity() == SEC_PLAYER || gmInWhoList && pFriend->IsVisibleGloballyFor(player) ))) + { + friendInfo.Status = FRIEND_STATUS_ONLINE; + if(pFriend->isAFK()) + friendInfo.Status = FRIEND_STATUS_AFK; + if(pFriend->isDND()) + friendInfo.Status = FRIEND_STATUS_DND; + friendInfo.Area = pFriend->GetZoneId(); + friendInfo.Level = pFriend->getLevel(); + friendInfo.Class = pFriend->getClass(); + } + else + { + friendInfo.Status = FRIEND_STATUS_OFFLINE; + friendInfo.Area = 0; + friendInfo.Level = 0; + friendInfo.Class = 0; + } +} + +void SocialMgr::MakeFriendStatusPacket(FriendsResult result, uint32 guid, WorldPacket *data) +{ + data->Initialize(SMSG_FRIEND_STATUS, 5); + *data << uint8(result); + *data << uint64(guid); +} + +void SocialMgr::SendFriendStatus(Player *player, FriendsResult result, uint32 friend_guid, std::string name, bool broadcast) +{ + FriendInfo fi; + + WorldPacket data; + MakeFriendStatusPacket(result, friend_guid, &data); + GetFriendInfo(player, friend_guid, fi); + switch(result) + { + case FRIEND_ADDED_OFFLINE: + case FRIEND_ADDED_ONLINE: + data << fi.Note; + break; + } + + switch(result) + { + case FRIEND_ADDED_ONLINE: + case FRIEND_ONLINE: + data << uint8(fi.Status); + data << uint32(fi.Area); + data << uint32(fi.Level); + data << uint32(fi.Class); + break; + } + + if(broadcast) + BroadcastToFriendListers(player, &data); + else + player->GetSession()->SendPacket(&data); +} + +void SocialMgr::BroadcastToFriendListers(Player *player, WorldPacket *packet) +{ + if(!player) + return; + + uint32 team = player->GetTeam(); + uint32 security = player->GetSession()->GetSecurity(); + uint32 guid = player->GetGUIDLow(); + bool gmInWhoList = sWorld.getConfig(CONFIG_GM_IN_WHO_LIST); + bool allowTwoSideWhoList = sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_WHO_LIST); + + for(SocialMap::iterator itr = m_socialMap.begin(); itr != m_socialMap.end(); ++itr) + { + PlayerSocialMap::iterator itr2 = itr->second.m_playerSocialMap.find(guid); + if(itr2 != itr->second.m_playerSocialMap.end() && (itr2->second.Flags & SOCIAL_FLAG_FRIEND)) + { + Player *pFriend = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER)); + + // PLAYER see his team only and PLAYER can't see MODERATOR, GAME MASTER, ADMINISTRATOR characters + // MODERATOR, GAME MASTER, ADMINISTRATOR can see all + if( pFriend && pFriend->IsInWorld() && + ( pFriend->GetSession()->GetSecurity() > SEC_PLAYER || + ( pFriend->GetTeam() == team || allowTwoSideWhoList ) && + (security == SEC_PLAYER || gmInWhoList && player->IsVisibleGloballyFor(pFriend) ))) + { + pFriend->GetSession()->SendPacket(packet); + } + } + } +} + +PlayerSocial *SocialMgr::LoadFromDB(QueryResult *result, uint32 guid) +{ + PlayerSocial *social = &m_socialMap[guid]; + social->SetPlayerGUID(guid); + + if(!result) + return social; + + uint32 friend_guid = 0; + uint32 flags = 0; + std::string note = ""; + + do + { + Field *fields = result->Fetch(); + + friend_guid = fields[0].GetUInt32(); + flags = fields[1].GetUInt32(); + note = fields[2].GetCppString(); + + social->m_playerSocialMap[friend_guid] = FriendInfo(flags, note); + + // client limit + if(social->m_playerSocialMap.size() >= 50) + break; + } + while( result->NextRow() ); + delete result; + return social; +} diff --git a/src/game/Spell.cpp b/src/game/Spell.cpp index 3be0b6989b2..ba5b5d97b47 100644 --- a/src/game/Spell.cpp +++ b/src/game/Spell.cpp @@ -986,7 +986,7 @@ void Spell::DoSpellHitOnUnit(Unit *unit, const uint32 effectMask) if( !(m_spellInfo->AttributesEx3 & SPELL_ATTR_EX3_NO_INITIAL_AGGRO) ) { - if(!unit->IsStandState() && !unit->hasUnitState(UNIT_STAT_STUNDED)) + if(!unit->IsStandState() && !unit->hasUnitState(UNIT_STAT_STUNNED)) unit->SetStandState(PLAYER_STATE_NONE); if(!unit->isInCombat() && unit->GetTypeId() != TYPEID_PLAYER && ((Creature*)unit)->AI()) @@ -2407,7 +2407,7 @@ void Spell::update(uint32 difftime) cancel(); // check for incapacitating player states - if( m_caster->hasUnitState(UNIT_STAT_STUNDED | UNIT_STAT_CONFUSED)) + if( m_caster->hasUnitState(UNIT_STAT_STUNNED | UNIT_STAT_CONFUSED)) cancel(); // check if player has turned if flag is set diff --git a/src/game/SpellAuras.cpp b/src/game/SpellAuras.cpp index e5abe4d842f..d8623a6ebe2 100644 --- a/src/game/SpellAuras.cpp +++ b/src/game/SpellAuras.cpp @@ -1,6360 +1,6360 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "Database/DatabaseEnv.h" -#include "WorldPacket.h" -#include "WorldSession.h" -#include "Opcodes.h" -#include "Log.h" -#include "UpdateMask.h" -#include "World.h" -#include "ObjectMgr.h" -#include "SpellMgr.h" -#include "Player.h" -#include "Unit.h" -#include "Spell.h" -#include "SpellAuras.h" -#include "DynamicObject.h" -#include "Group.h" -#include "UpdateData.h" -#include "MapManager.h" -#include "ObjectAccessor.h" -#include "Policies/SingletonImp.h" -#include "Totem.h" -#include "Creature.h" -#include "Formulas.h" -#include "BattleGround.h" -#include "CreatureAI.h" -#include "Util.h" -#include "GridNotifiers.h" -#include "GridNotifiersImpl.h" -#include "CellImpl.h" - -#define NULL_AURA_SLOT 0xFF - -pAuraHandler AuraHandler[TOTAL_AURAS]= -{ - &Aura::HandleNULL, // 0 SPELL_AURA_NONE - &Aura::HandleBindSight, // 1 SPELL_AURA_BIND_SIGHT - &Aura::HandleModPossess, // 2 SPELL_AURA_MOD_POSSESS - &Aura::HandlePeriodicDamage, // 3 SPELL_AURA_PERIODIC_DAMAGE - &Aura::HandleAuraDummy, // 4 SPELL_AURA_DUMMY - &Aura::HandleModConfuse, // 5 SPELL_AURA_MOD_CONFUSE - &Aura::HandleModCharm, // 6 SPELL_AURA_MOD_CHARM - &Aura::HandleModFear, // 7 SPELL_AURA_MOD_FEAR - &Aura::HandlePeriodicHeal, // 8 SPELL_AURA_PERIODIC_HEAL - &Aura::HandleModAttackSpeed, // 9 SPELL_AURA_MOD_ATTACKSPEED - &Aura::HandleModThreat, // 10 SPELL_AURA_MOD_THREAT - &Aura::HandleModTaunt, // 11 SPELL_AURA_MOD_TAUNT - &Aura::HandleAuraModStun, // 12 SPELL_AURA_MOD_STUN - &Aura::HandleModDamageDone, // 13 SPELL_AURA_MOD_DAMAGE_DONE - &Aura::HandleNoImmediateEffect, // 14 SPELL_AURA_MOD_DAMAGE_TAKEN implemented in Unit::MeleeDamageBonus and Unit::SpellDamageBonus - &Aura::HandleNoImmediateEffect, // 15 SPELL_AURA_DAMAGE_SHIELD implemented in Unit::DoAttackDamage - &Aura::HandleModStealth, // 16 SPELL_AURA_MOD_STEALTH - &Aura::HandleNoImmediateEffect, // 17 SPELL_AURA_MOD_STEALTH_DETECT - &Aura::HandleInvisibility, // 18 SPELL_AURA_MOD_INVISIBILITY - &Aura::HandleInvisibilityDetect, // 19 SPELL_AURA_MOD_INVISIBILITY_DETECTION - &Aura::HandleAuraModTotalHealthPercentRegen, // 20 SPELL_AURA_OBS_MOD_HEALTH - &Aura::HandleAuraModTotalManaPercentRegen, // 21 SPELL_AURA_OBS_MOD_MANA - &Aura::HandleAuraModResistance, // 22 SPELL_AURA_MOD_RESISTANCE - &Aura::HandlePeriodicTriggerSpell, // 23 SPELL_AURA_PERIODIC_TRIGGER_SPELL - &Aura::HandlePeriodicEnergize, // 24 SPELL_AURA_PERIODIC_ENERGIZE - &Aura::HandleAuraModPacify, // 25 SPELL_AURA_MOD_PACIFY - &Aura::HandleAuraModRoot, // 26 SPELL_AURA_MOD_ROOT - &Aura::HandleAuraModSilence, // 27 SPELL_AURA_MOD_SILENCE - &Aura::HandleNoImmediateEffect, // 28 SPELL_AURA_REFLECT_SPELLS implement in Unit::SpellHitResult - &Aura::HandleAuraModStat, // 29 SPELL_AURA_MOD_STAT - &Aura::HandleAuraModSkill, // 30 SPELL_AURA_MOD_SKILL - &Aura::HandleAuraModIncreaseSpeed, // 31 SPELL_AURA_MOD_INCREASE_SPEED - &Aura::HandleAuraModIncreaseMountedSpeed, // 32 SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED - &Aura::HandleAuraModDecreaseSpeed, // 33 SPELL_AURA_MOD_DECREASE_SPEED - &Aura::HandleAuraModIncreaseHealth, // 34 SPELL_AURA_MOD_INCREASE_HEALTH - &Aura::HandleAuraModIncreaseEnergy, // 35 SPELL_AURA_MOD_INCREASE_ENERGY - &Aura::HandleAuraModShapeshift, // 36 SPELL_AURA_MOD_SHAPESHIFT - &Aura::HandleAuraModEffectImmunity, // 37 SPELL_AURA_EFFECT_IMMUNITY - &Aura::HandleAuraModStateImmunity, // 38 SPELL_AURA_STATE_IMMUNITY - &Aura::HandleAuraModSchoolImmunity, // 39 SPELL_AURA_SCHOOL_IMMUNITY - &Aura::HandleAuraModDmgImmunity, // 40 SPELL_AURA_DAMAGE_IMMUNITY - &Aura::HandleAuraModDispelImmunity, // 41 SPELL_AURA_DISPEL_IMMUNITY - &Aura::HandleAuraProcTriggerSpell, // 42 SPELL_AURA_PROC_TRIGGER_SPELL implemented in Unit::ProcDamageAndSpellFor and Unit::HandleProcTriggerSpell - &Aura::HandleNoImmediateEffect, // 43 SPELL_AURA_PROC_TRIGGER_DAMAGE implemented in Unit::ProcDamageAndSpellFor - &Aura::HandleAuraTrackCreatures, // 44 SPELL_AURA_TRACK_CREATURES - &Aura::HandleAuraTrackResources, // 45 SPELL_AURA_TRACK_RESOURCES - &Aura::HandleUnused, // 46 SPELL_AURA_MOD_PARRY_SKILL obsolete? - &Aura::HandleAuraModParryPercent, // 47 SPELL_AURA_MOD_PARRY_PERCENT - &Aura::HandleUnused, // 48 SPELL_AURA_MOD_DODGE_SKILL obsolete? - &Aura::HandleAuraModDodgePercent, // 49 SPELL_AURA_MOD_DODGE_PERCENT - &Aura::HandleUnused, // 50 SPELL_AURA_MOD_BLOCK_SKILL obsolete? - &Aura::HandleAuraModBlockPercent, // 51 SPELL_AURA_MOD_BLOCK_PERCENT - &Aura::HandleAuraModCritPercent, // 52 SPELL_AURA_MOD_CRIT_PERCENT - &Aura::HandlePeriodicLeech, // 53 SPELL_AURA_PERIODIC_LEECH - &Aura::HandleModHitChance, // 54 SPELL_AURA_MOD_HIT_CHANCE - &Aura::HandleModSpellHitChance, // 55 SPELL_AURA_MOD_SPELL_HIT_CHANCE - &Aura::HandleAuraTransform, // 56 SPELL_AURA_TRANSFORM - &Aura::HandleModSpellCritChance, // 57 SPELL_AURA_MOD_SPELL_CRIT_CHANCE - &Aura::HandleAuraModIncreaseSwimSpeed, // 58 SPELL_AURA_MOD_INCREASE_SWIM_SPEED - &Aura::HandleNoImmediateEffect, // 59 SPELL_AURA_MOD_DAMAGE_DONE_CREATURE implemented in Unit::MeleeDamageBonus and Unit::SpellDamageBonus - &Aura::HandleAuraModPacifyAndSilence, // 60 SPELL_AURA_MOD_PACIFY_SILENCE - &Aura::HandleAuraModScale, // 61 SPELL_AURA_MOD_SCALE - &Aura::HandleNULL, // 62 SPELL_AURA_PERIODIC_HEALTH_FUNNEL - &Aura::HandleUnused, // 63 SPELL_AURA_PERIODIC_MANA_FUNNEL obsolete? - &Aura::HandlePeriodicManaLeech, // 64 SPELL_AURA_PERIODIC_MANA_LEECH - &Aura::HandleModCastingSpeed, // 65 SPELL_AURA_MOD_CASTING_SPEED - &Aura::HandleFeignDeath, // 66 SPELL_AURA_FEIGN_DEATH - &Aura::HandleAuraModDisarm, // 67 SPELL_AURA_MOD_DISARM - &Aura::HandleAuraModStalked, // 68 SPELL_AURA_MOD_STALKED - &Aura::HandleSchoolAbsorb, // 69 SPELL_AURA_SCHOOL_ABSORB implemented in Unit::CalcAbsorbResist - &Aura::HandleUnused, // 70 SPELL_AURA_EXTRA_ATTACKS Useless, used by only one spell that has only visual effect - &Aura::HandleModSpellCritChanceShool, // 71 SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL - &Aura::HandleModPowerCostPCT, // 72 SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT - &Aura::HandleModPowerCost, // 73 SPELL_AURA_MOD_POWER_COST_SCHOOL - &Aura::HandleNoImmediateEffect, // 74 SPELL_AURA_REFLECT_SPELLS_SCHOOL implemented in Unit::SpellHitResult - &Aura::HandleNoImmediateEffect, // 75 SPELL_AURA_MOD_LANGUAGE - &Aura::HandleFarSight, // 76 SPELL_AURA_FAR_SIGHT - &Aura::HandleModMechanicImmunity, // 77 SPELL_AURA_MECHANIC_IMMUNITY - &Aura::HandleAuraMounted, // 78 SPELL_AURA_MOUNTED - &Aura::HandleModDamagePercentDone, // 79 SPELL_AURA_MOD_DAMAGE_PERCENT_DONE - &Aura::HandleModPercentStat, // 80 SPELL_AURA_MOD_PERCENT_STAT - &Aura::HandleNoImmediateEffect, // 81 SPELL_AURA_SPLIT_DAMAGE_PCT - &Aura::HandleWaterBreathing, // 82 SPELL_AURA_WATER_BREATHING - &Aura::HandleModBaseResistance, // 83 SPELL_AURA_MOD_BASE_RESISTANCE - &Aura::HandleModRegen, // 84 SPELL_AURA_MOD_REGEN - &Aura::HandleModPowerRegen, // 85 SPELL_AURA_MOD_POWER_REGEN - &Aura::HandleChannelDeathItem, // 86 SPELL_AURA_CHANNEL_DEATH_ITEM - &Aura::HandleNoImmediateEffect, // 87 SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN implemented in Unit::MeleeDamageBonus and Unit::SpellDamageBonus - &Aura::HandleNoImmediateEffect, // 88 SPELL_AURA_MOD_HEALTH_REGEN_PERCENT - &Aura::HandlePeriodicDamagePCT, // 89 SPELL_AURA_PERIODIC_DAMAGE_PERCENT - &Aura::HandleUnused, // 90 SPELL_AURA_MOD_RESIST_CHANCE Useless - &Aura::HandleNoImmediateEffect, // 91 SPELL_AURA_MOD_DETECT_RANGE implemented in Creature::GetAttackDistance - &Aura::HandlePreventFleeing, // 92 SPELL_AURA_PREVENTS_FLEEING - &Aura::HandleModUnattackable, // 93 SPELL_AURA_MOD_UNATTACKABLE - &Aura::HandleNoImmediateEffect, // 94 SPELL_AURA_INTERRUPT_REGEN implemented in Player::RegenerateAll - &Aura::HandleAuraGhost, // 95 SPELL_AURA_GHOST - &Aura::HandleNoImmediateEffect, // 96 SPELL_AURA_SPELL_MAGNET implemented in Spell::SelectMagnetTarget - &Aura::HandleManaShield, // 97 SPELL_AURA_MANA_SHIELD implemented in Unit::CalcAbsorbResist - &Aura::HandleAuraModSkill, // 98 SPELL_AURA_MOD_SKILL_TALENT - &Aura::HandleAuraModAttackPower, // 99 SPELL_AURA_MOD_ATTACK_POWER - &Aura::HandleUnused, //100 SPELL_AURA_AURAS_VISIBLE obsolete? all player can see all auras now - &Aura::HandleModResistancePercent, //101 SPELL_AURA_MOD_RESISTANCE_PCT - &Aura::HandleNoImmediateEffect, //102 SPELL_AURA_MOD_MELEE_ATTACK_POWER_VERSUS implemented in Unit::MeleeDamageBonus - &Aura::HandleAuraModTotalThreat, //103 SPELL_AURA_MOD_TOTAL_THREAT - &Aura::HandleAuraWaterWalk, //104 SPELL_AURA_WATER_WALK - &Aura::HandleAuraFeatherFall, //105 SPELL_AURA_FEATHER_FALL - &Aura::HandleAuraHover, //106 SPELL_AURA_HOVER - &Aura::HandleAddModifier, //107 SPELL_AURA_ADD_FLAT_MODIFIER - &Aura::HandleAddModifier, //108 SPELL_AURA_ADD_PCT_MODIFIER - &Aura::HandleNoImmediateEffect, //109 SPELL_AURA_ADD_TARGET_TRIGGER - &Aura::HandleModPowerRegenPCT, //110 SPELL_AURA_MOD_POWER_REGEN_PERCENT - &Aura::HandleNULL, //111 SPELL_AURA_ADD_CASTER_HIT_TRIGGER - &Aura::HandleNoImmediateEffect, //112 SPELL_AURA_OVERRIDE_CLASS_SCRIPTS - &Aura::HandleNoImmediateEffect, //113 SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN implemented in Unit::MeleeDamageBonus - &Aura::HandleNoImmediateEffect, //114 SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN_PCT implemented in Unit::MeleeDamageBonus - &Aura::HandleAuraHealing, //115 SPELL_AURA_MOD_HEALING - &Aura::HandleNoImmediateEffect, //116 SPELL_AURA_MOD_REGEN_DURING_COMBAT - &Aura::HandleNoImmediateEffect, //117 SPELL_AURA_MOD_MECHANIC_RESISTANCE implemented in Unit::MagicSpellHitResult - &Aura::HandleAuraHealingPct, //118 SPELL_AURA_MOD_HEALING_PCT - &Aura::HandleUnused, //119 SPELL_AURA_SHARE_PET_TRACKING useless - &Aura::HandleAuraUntrackable, //120 SPELL_AURA_UNTRACKABLE - &Aura::HandleAuraEmpathy, //121 SPELL_AURA_EMPATHY - &Aura::HandleModOffhandDamagePercent, //122 SPELL_AURA_MOD_OFFHAND_DAMAGE_PCT - &Aura::HandleModTargetResistance, //123 SPELL_AURA_MOD_TARGET_RESISTANCE - &Aura::HandleAuraModRangedAttackPower, //124 SPELL_AURA_MOD_RANGED_ATTACK_POWER - &Aura::HandleNoImmediateEffect, //125 SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN implemented in Unit::MeleeDamageBonus - &Aura::HandleNoImmediateEffect, //126 SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT implemented in Unit::MeleeDamageBonus - &Aura::HandleNoImmediateEffect, //127 SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS implemented in Unit::MeleeDamageBonus - &Aura::HandleModPossessPet, //128 SPELL_AURA_MOD_POSSESS_PET - &Aura::HandleAuraModIncreaseSpeed, //129 SPELL_AURA_MOD_SPEED_ALWAYS - &Aura::HandleAuraModIncreaseMountedSpeed, //130 SPELL_AURA_MOD_MOUNTED_SPEED_ALWAYS - &Aura::HandleNoImmediateEffect, //131 SPELL_AURA_MOD_RANGED_ATTACK_POWER_VERSUS implemented in Unit::MeleeDamageBonus - &Aura::HandleAuraModIncreaseEnergyPercent, //132 SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT - &Aura::HandleAuraModIncreaseHealthPercent, //133 SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT - &Aura::HandleAuraModRegenInterrupt, //134 SPELL_AURA_MOD_MANA_REGEN_INTERRUPT - &Aura::HandleModHealingDone, //135 SPELL_AURA_MOD_HEALING_DONE - &Aura::HandleAuraHealingPct, //136 SPELL_AURA_MOD_HEALING_DONE_PERCENT implemented in Unit::SpellHealingBonus - &Aura::HandleModTotalPercentStat, //137 SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE - &Aura::HandleHaste, //138 SPELL_AURA_MOD_HASTE - &Aura::HandleForceReaction, //139 SPELL_AURA_FORCE_REACTION - &Aura::HandleAuraModRangedHaste, //140 SPELL_AURA_MOD_RANGED_HASTE - &Aura::HandleRangedAmmoHaste, //141 SPELL_AURA_MOD_RANGED_AMMO_HASTE - &Aura::HandleAuraModBaseResistancePCT, //142 SPELL_AURA_MOD_BASE_RESISTANCE_PCT - &Aura::HandleAuraModResistanceExclusive, //143 SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE - &Aura::HandleNoImmediateEffect, //144 SPELL_AURA_SAFE_FALL implemented in WorldSession::HandleMovementOpcodes - &Aura::HandleUnused, //145 SPELL_AURA_CHARISMA obsolete? - &Aura::HandleUnused, //146 SPELL_AURA_PERSUADED obsolete? - &Aura::HandleNULL, //147 SPELL_AURA_ADD_CREATURE_IMMUNITY - &Aura::HandleAuraRetainComboPoints, //148 SPELL_AURA_RETAIN_COMBO_POINTS - &Aura::HandleNoImmediateEffect, //149 SPELL_AURA_RESIST_PUSHBACK - &Aura::HandleShieldBlockValue, //150 SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT - &Aura::HandleAuraTrackStealthed, //151 SPELL_AURA_TRACK_STEALTHED - &Aura::HandleNoImmediateEffect, //152 SPELL_AURA_MOD_DETECTED_RANGE implemented in Creature::GetAttackDistance - &Aura::HandleNoImmediateEffect, //153 SPELL_AURA_SPLIT_DAMAGE_FLAT - &Aura::HandleNoImmediateEffect, //154 SPELL_AURA_MOD_STEALTH_LEVEL - &Aura::HandleNoImmediateEffect, //155 SPELL_AURA_MOD_WATER_BREATHING - &Aura::HandleNoImmediateEffect, //156 SPELL_AURA_MOD_REPUTATION_GAIN - &Aura::HandleNULL, //157 SPELL_AURA_PET_DAMAGE_MULTI - &Aura::HandleShieldBlockValue, //158 SPELL_AURA_MOD_SHIELD_BLOCKVALUE - &Aura::HandleNoImmediateEffect, //159 SPELL_AURA_NO_PVP_CREDIT only for Honorless Target spell - &Aura::HandleNoImmediateEffect, //160 SPELL_AURA_MOD_AOE_AVOIDANCE implemended in Unit::MagicSpellHitResult - &Aura::HandleNoImmediateEffect, //161 SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT - &Aura::HandleAuraPowerBurn, //162 SPELL_AURA_POWER_BURN_MANA - &Aura::HandleNoImmediateEffect, //163 SPELL_AURA_MOD_CRIT_DAMAGE_BONUS_MELEE - &Aura::HandleUnused, //164 useless, only one test spell - &Aura::HandleAuraAttackPowerAttacker, //165 SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS implemented in Unit::MeleeDamageBonus - &Aura::HandleAuraModAttackPowerPercent, //166 SPELL_AURA_MOD_ATTACK_POWER_PCT - &Aura::HandleAuraModRangedAttackPowerPercent, //167 SPELL_AURA_MOD_RANGED_ATTACK_POWER_PCT - &Aura::HandleNoImmediateEffect, //168 SPELL_AURA_MOD_DAMAGE_DONE_VERSUS implemented in Unit::SpellDamageBonus, Unit::MeleeDamageBonus - &Aura::HandleNoImmediateEffect, //169 SPELL_AURA_MOD_CRIT_PERCENT_VERSUS implemented in Unit::DealDamageBySchool, Unit::DoAttackDamage, Unit::SpellCriticalBonus - &Aura::HandleNULL, //170 SPELL_AURA_DETECT_AMORE only for Detect Amore spell - &Aura::HandleAuraModIncreaseSpeed, //171 SPELL_AURA_MOD_SPEED_NOT_STACK - &Aura::HandleAuraModIncreaseMountedSpeed, //172 SPELL_AURA_MOD_MOUNTED_SPEED_NOT_STACK - &Aura::HandleUnused, //173 SPELL_AURA_ALLOW_CHAMPION_SPELLS only for Proclaim Champion spell - &Aura::HandleModSpellDamagePercentFromStat, //174 SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT implemented in Unit::SpellBaseDamageBonus (by defeult intelect, dependent from SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT) - &Aura::HandleModSpellHealingPercentFromStat, //175 SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT implemented in Unit::SpellBaseHealingBonus - &Aura::HandleSpiritOfRedemption, //176 SPELL_AURA_SPIRIT_OF_REDEMPTION only for Spirit of Redemption spell, die at aura end - &Aura::HandleNULL, //177 SPELL_AURA_AOE_CHARM - &Aura::HandleNoImmediateEffect, //178 SPELL_AURA_MOD_DEBUFF_RESISTANCE implemented in Unit::MagicSpellHitResult - &Aura::HandleNoImmediateEffect, //179 SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE implemented in Unit::SpellCriticalBonus - &Aura::HandleNoImmediateEffect, //180 SPELL_AURA_MOD_FLAT_SPELL_DAMAGE_VERSUS implemented in Unit::SpellDamageBonus - &Aura::HandleUnused, //181 SPELL_AURA_MOD_FLAT_SPELL_CRIT_DAMAGE_VERSUS unused - &Aura::HandleAuraModResistenceOfStatPercent, //182 SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT - &Aura::HandleNULL, //183 SPELL_AURA_MOD_CRITICAL_THREAT - &Aura::HandleNoImmediateEffect, //184 SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE implemented in Unit::RollMeleeOutcomeAgainst - &Aura::HandleNoImmediateEffect, //185 SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE implemented in Unit::RollMeleeOutcomeAgainst - &Aura::HandleNoImmediateEffect, //186 SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE implemented in Unit::MagicSpellHitResult - &Aura::HandleNoImmediateEffect, //187 SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE implemended in Unit::GetUnitCriticalChance - &Aura::HandleNoImmediateEffect, //188 SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_CHANCE implemented in Unit::GetUnitCriticalChance - &Aura::HandleModRating, //189 SPELL_AURA_MOD_RATING - &Aura::HandleNULL, //190 SPELL_AURA_MOD_FACTION_REPUTATION_GAIN - &Aura::HandleAuraModUseNormalSpeed, //191 SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED - &Aura::HandleModMeleeRangedSpeedPct, //192 SPELL_AURA_HASTE_MELEE - &Aura::HandleModCombatSpeedPct, //193 SPELL_AURA_MELEE_SLOW (in fact combat (any type attack) speed pct) - &Aura::HandleUnused, //194 SPELL_AURA_MOD_DEPRICATED_1 not used now (old SPELL_AURA_MOD_SPELL_DAMAGE_OF_INTELLECT) - &Aura::HandleUnused, //195 SPELL_AURA_MOD_DEPRICATED_2 not used now (old SPELL_AURA_MOD_SPELL_HEALING_OF_INTELLECT) - &Aura::HandleNULL, //196 SPELL_AURA_MOD_COOLDOWN - &Aura::HandleNoImmediateEffect, //197 SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE implemented in Unit::SpellCriticalBonus Unit::GetUnitCriticalChance - &Aura::HandleUnused, //198 SPELL_AURA_MOD_ALL_WEAPON_SKILLS - &Aura::HandleNoImmediateEffect, //199 SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT implemented in Unit::MagicSpellHitResult - &Aura::HandleNoImmediateEffect, //200 SPELL_AURA_MOD_XP_PCT implemented in Player::GiveXP - &Aura::HandleAuraAllowFlight, //201 SPELL_AURA_FLY this aura enable flight mode... - &Aura::HandleNoImmediateEffect, //202 SPELL_AURA_CANNOT_BE_DODGED implemented in Unit::RollPhysicalOutcomeAgainst - &Aura::HandleNoImmediateEffect, //203 SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE implemented in Unit::DoAttackDamage - &Aura::HandleNoImmediateEffect, //204 SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE implemented in Unit::DoAttackDamage - &Aura::HandleNULL, //205 vulnerable to school dmg? - &Aura::HandleNULL, //206 SPELL_AURA_MOD_SPEED_MOUNTED - &Aura::HandleAuraModIncreaseFlightSpeed, //207 SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED - &Aura::HandleAuraModIncreaseFlightSpeed, //208 SPELL_AURA_MOD_SPEED_FLIGHT, used only in spell: Flight Form (Passive) - &Aura::HandleAuraModIncreaseFlightSpeed, //209 SPELL_AURA_MOD_FLIGHT_SPEED_ALWAYS - &Aura::HandleNULL, //210 Commentator's Command - &Aura::HandleAuraModIncreaseFlightSpeed, //211 SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACK - &Aura::HandleAuraModRangedAttackPowerOfStatPercent, //212 SPELL_AURA_MOD_RANGED_ATTACK_POWER_OF_STAT_PERCENT - &Aura::HandleNoImmediateEffect, //213 SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT implemented in Player::RewardRage - &Aura::HandleNULL, //214 Tamed Pet Passive - &Aura::HandleArenaPreparation, //215 SPELL_AURA_ARENA_PREPARATION - &Aura::HandleModCastingSpeed, //216 SPELL_AURA_HASTE_SPELLS - &Aura::HandleUnused, //217 unused - &Aura::HandleAuraModRangedHaste, //218 SPELL_AURA_HASTE_RANGED - &Aura::HandleModManaRegen, //219 SPELL_AURA_MOD_MANA_REGEN_FROM_STAT - &Aura::HandleNULL, //220 SPELL_AURA_MOD_RATING_FROM_STAT - &Aura::HandleNULL, //221 ignored - &Aura::HandleUnused, //222 unused - &Aura::HandleNULL, //223 Cold Stare - &Aura::HandleUnused, //224 unused - &Aura::HandleNoImmediateEffect, //225 SPELL_AURA_PRAYER_OF_MENDING - &Aura::HandleAuraPeriodicDummy, //226 SPELL_AURA_PERIODIC_DUMMY - &Aura::HandleNULL, //227 periodic trigger spell - &Aura::HandleNoImmediateEffect, //228 stealth detection - &Aura::HandleNULL, //229 SPELL_AURA_MOD_AOE_DAMAGE_AVOIDANCE - &Aura::HandleAuraModIncreaseMaxHealth, //230 Commanding Shout - &Aura::HandleNULL, //231 - &Aura::HandleNoImmediateEffect, //232 SPELL_AURA_MECHANIC_DURATION_MOD implement in Unit::CalculateSpellDuration - &Aura::HandleNULL, //233 set model id to the one of the creature with id m_modifier.m_miscvalue - &Aura::HandleNoImmediateEffect, //234 SPELL_AURA_MECHANIC_DURATION_MOD_NOT_STACK implement in Unit::CalculateSpellDuration - &Aura::HandleAuraModDispelResist, //235 SPELL_AURA_MOD_DISPEL_RESIST implement in Unit::MagicSpellHitResult - &Aura::HandleUnused, //236 unused - &Aura::HandleModSpellDamagePercentFromAttackPower, //237 SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER implemented in Unit::SpellBaseDamageBonus - &Aura::HandleModSpellHealingPercentFromAttackPower, //238 SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER implemented in Unit::SpellBaseHealingBonus - &Aura::HandleAuraModScale, //239 SPELL_AURA_MOD_SCALE_2 only in Noggenfogger Elixir (16595) before 2.3.0 aura 61 - &Aura::HandleAuraModExpertise, //240 SPELL_AURA_MOD_EXPERTISE - &Aura::HandleForceMoveForward, //241 Forces the player to move forward - &Aura::HandleUnused, //242 SPELL_AURA_MOD_SPELL_DAMAGE_FROM_HEALING - &Aura::HandleUnused, //243 used by two test spells - &Aura::HandleComprehendLanguage, //244 Comprehend language - &Aura::HandleUnused, //245 SPELL_AURA_MOD_DURATION_OF_MAGIC_EFFECTS - &Aura::HandleUnused, //246 unused - &Aura::HandleUnused, //247 unused - &Aura::HandleNoImmediateEffect, //248 SPELL_AURA_MOD_COMBAT_RESULT_CHANCE implemented in Unit::RollMeleeOutcomeAgainst - &Aura::HandleNULL, //249 - &Aura::HandleAuraModIncreaseHealth, //250 SPELL_AURA_MOD_INCREASE_HEALTH_2 - &Aura::HandleNULL, //251 SPELL_AURA_MOD_ENEMY_DODGE - &Aura::HandleUnused, //252 unused - &Aura::HandleUnused, //253 unused - &Aura::HandleUnused, //254 unused - &Aura::HandleUnused, //255 unused - &Aura::HandleUnused, //256 unused - &Aura::HandleUnused, //257 unused - &Aura::HandleUnused, //258 unused - &Aura::HandleUnused, //259 unused - &Aura::HandleUnused, //260 unused - &Aura::HandleNULL //261 SPELL_AURA_261 some phased state (44856 spell) -}; - -Aura::Aura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, Unit *caster, Item* castItem) : -m_procCharges(0), m_spellmod(NULL), m_effIndex(eff), m_caster_guid(0), m_target(target), -m_timeCla(1000), m_castItemGuid(castItem?castItem->GetGUID():0), m_auraSlot(MAX_AURAS), -m_positive(false), m_permanent(false), m_isPeriodic(false), m_isTrigger(false), m_isAreaAura(false), -m_isPersistent(false), m_updated(false), m_removeMode(AURA_REMOVE_BY_DEFAULT), m_isRemovedOnShapeLost(true), m_in_use(false), -m_periodicTimer(0), m_PeriodicEventId(0), m_AuraDRGroup(DIMINISHING_NONE) -{ - assert(target); - - assert(spellproto && spellproto == sSpellStore.LookupEntry( spellproto->Id ) && "`info` must be pointer to sSpellStore element"); - - m_spellProto = spellproto; - - m_currentBasePoints = currentBasePoints ? *currentBasePoints : m_spellProto->EffectBasePoints[eff]; - - m_isPassive = IsPassiveSpell(GetId()); - m_positive = IsPositiveEffect(GetId(), m_effIndex); - - m_applyTime = time(NULL); - - int32 damage; - if(!caster) - { - m_caster_guid = target->GetGUID(); - damage = m_currentBasePoints+1; // stored value-1 - m_maxduration = target->CalculateSpellDuration(m_spellProto, m_effIndex, target); - } - else - { - m_caster_guid = caster->GetGUID(); - - damage = caster->CalculateSpellDamage(m_spellProto,m_effIndex,m_currentBasePoints,target); - m_maxduration = caster->CalculateSpellDuration(m_spellProto, m_effIndex, target); - - if (!damage && castItem && castItem->GetItemSuffixFactor()) - { - ItemRandomSuffixEntry const *item_rand_suffix = sItemRandomSuffixStore.LookupEntry(abs(castItem->GetItemRandomPropertyId())); - if(item_rand_suffix) - { - for (int k=0; k<3; k++) - { - SpellItemEnchantmentEntry const *pEnchant = sSpellItemEnchantmentStore.LookupEntry(item_rand_suffix->enchant_id[k]); - if(pEnchant) - { - for (int t=0; t<3; t++) - if(pEnchant->spellid[t] == m_spellProto->Id) - { - damage = uint32((item_rand_suffix->prefix[k]*castItem->GetItemSuffixFactor()) / 10000 ); - break; - } - } - - if(damage) - break; - } - } - } - } - - if(m_maxduration == -1 || m_isPassive && m_spellProto->DurationIndex == 0) - m_permanent = true; - - Player* modOwner = caster ? caster->GetSpellModOwner() : NULL; - - if(!m_permanent && modOwner) - modOwner->ApplySpellMod(GetId(), SPELLMOD_DURATION, m_maxduration); - - m_duration = m_maxduration; - - if(modOwner) - modOwner->ApplySpellMod(GetId(), SPELLMOD_ACTIVATION_TIME, m_periodicTimer); - - sLog.outDebug("Aura: construct Spellid : %u, Aura : %u Duration : %d Target : %d Damage : %d", m_spellProto->Id, m_spellProto->EffectApplyAuraName[eff], m_maxduration, m_spellProto->EffectImplicitTargetA[eff],damage); - - m_effIndex = eff; - SetModifier(AuraType(m_spellProto->EffectApplyAuraName[eff]), damage, m_spellProto->EffectAmplitude[eff], m_spellProto->EffectMiscValue[eff]); - - m_isDeathPersist = IsDeathPersistentSpell(m_spellProto); - - if(m_spellProto->procCharges) - { - m_procCharges = m_spellProto->procCharges; - - if(modOwner) - modOwner->ApplySpellMod(GetId(), SPELLMOD_CHARGES, m_procCharges); - } - else - m_procCharges = -1; - - m_isRemovedOnShapeLost = (m_caster_guid==m_target->GetGUID() && m_spellProto->Stances && - !(m_spellProto->AttributesEx2 & 0x80000) && !(m_spellProto->Attributes & 0x10000)); -} - -Aura::~Aura() -{ -} - -AreaAura::AreaAura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, -Unit *caster, Item* castItem) : Aura(spellproto, eff, currentBasePoints, target, caster, castItem) -{ - m_isAreaAura = true; - - // caster==NULL in constructor args if target==caster in fact - Unit* caster_ptr = caster ? caster : target; - - m_radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(GetSpellProto()->EffectRadiusIndex[m_effIndex])); - if(Player* modOwner = caster_ptr->GetSpellModOwner()) - modOwner->ApplySpellMod(GetId(), SPELLMOD_RADIUS, m_radius); - - switch(spellproto->Effect[eff]) - { - case SPELL_EFFECT_APPLY_AREA_AURA_PARTY: - m_areaAuraType = AREA_AURA_PARTY; - if(target->GetTypeId() == TYPEID_UNIT && ((Creature*)target)->isTotem()) - m_modifier.m_auraname = SPELL_AURA_NONE; - break; - case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND: - m_areaAuraType = AREA_AURA_FRIEND; - break; - case SPELL_EFFECT_APPLY_AREA_AURA_ENEMY: - m_areaAuraType = AREA_AURA_ENEMY; - if(target == caster_ptr) - m_modifier.m_auraname = SPELL_AURA_NONE; // Do not do any effect on self - break; - case SPELL_EFFECT_APPLY_AREA_AURA_PET: - m_areaAuraType = AREA_AURA_PET; - break; - case SPELL_EFFECT_APPLY_AREA_AURA_OWNER: - m_areaAuraType = AREA_AURA_OWNER; - if(target == caster_ptr) - m_modifier.m_auraname = SPELL_AURA_NONE; - break; - default: - sLog.outError("Wrong spell effect in AreaAura constructor"); - ASSERT(false); - break; - } -} - -AreaAura::~AreaAura() -{ -} - -PersistentAreaAura::PersistentAreaAura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, -Unit *caster, Item* castItem) : Aura(spellproto, eff, currentBasePoints, target, caster, castItem) -{ - m_isPersistent = true; -} - -PersistentAreaAura::~PersistentAreaAura() -{ -} - -SingleEnemyTargetAura::SingleEnemyTargetAura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, -Unit *caster, Item* castItem) : Aura(spellproto, eff, currentBasePoints, target, caster, castItem) -{ - if (caster) - m_casters_target_guid = caster->GetTypeId()==TYPEID_PLAYER ? ((Player*)caster)->GetSelection() : caster->GetUInt64Value(UNIT_FIELD_TARGET); - else - m_casters_target_guid = 0; -} - -SingleEnemyTargetAura::~SingleEnemyTargetAura() -{ -} - -Unit* SingleEnemyTargetAura::GetTriggerTarget() const -{ - return ObjectAccessor::GetUnit(*m_target, m_casters_target_guid); -} - -Aura* CreateAura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, Unit *caster, Item* castItem) -{ - if (IsAreaAuraEffect(spellproto->Effect[eff])) - return new AreaAura(spellproto, eff, currentBasePoints, target, caster, castItem); - - uint32 triggeredSpellId = spellproto->EffectTriggerSpell[eff]; - - SpellEntry const* triggredSpellInfo = sSpellStore.LookupEntry(triggeredSpellId); - if (triggredSpellInfo) - for (int i = 0; i < 3; ++i) - if (triggredSpellInfo->EffectImplicitTargetA[i] == TARGET_SINGLE_ENEMY) - return new SingleEnemyTargetAura(spellproto, eff, currentBasePoints, target, caster, castItem); - - return new Aura(spellproto, eff, currentBasePoints, target, caster, castItem); -} - -Unit* Aura::GetCaster() const -{ - if(m_caster_guid==m_target->GetGUID()) - return m_target; - - //return ObjectAccessor::GetUnit(*m_target,m_caster_guid); - //must return caster even if it's in another grid/map - Unit *unit = ObjectAccessor::GetObjectInWorld(m_caster_guid, (Unit*)NULL); - return unit && unit->IsInWorld() ? unit : NULL; -} - -void Aura::SetModifier(AuraType t, int32 a, uint32 pt, int32 miscValue) -{ - m_modifier.m_auraname = t; - m_modifier.m_amount = a; - m_modifier.m_miscvalue = miscValue; - m_modifier.periodictime = pt; -} - -void Aura::Update(uint32 diff) -{ - if (m_duration > 0) - { - m_duration -= diff; - if (m_duration < 0) - m_duration = 0; - m_timeCla -= diff; - - // GetEffIndex()==0 prevent double/triple apply manaPerSecond/manaPerSecondPerLevel to same spell with many auras - // all spells with manaPerSecond/manaPerSecondPerLevel have aura in effect 0 - if(GetEffIndex()==0 && m_timeCla <= 0) - { - if(Unit* caster = GetCaster()) - { - Powers powertype = Powers(m_spellProto->powerType); - int32 manaPerSecond = m_spellProto->manaPerSecond + m_spellProto->manaPerSecondPerLevel * caster->getLevel(); - m_timeCla = 1000; - if (manaPerSecond) - { - if(powertype==POWER_HEALTH) - caster->ModifyHealth(-manaPerSecond); - else - caster->ModifyPower(powertype,-manaPerSecond); - } - } - } - } - - // Channeled aura required check distance from caster - if(IsChanneledSpell(m_spellProto) && m_caster_guid != m_target->GetGUID()) - { - Unit* caster = GetCaster(); - if(!caster) - { - m_target->RemoveAura(GetId(),GetEffIndex()); - return; - } - - // Get spell range - float radius; - SpellModOp mod; - if (m_spellProto->EffectRadiusIndex[GetEffIndex()]) - { - radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(m_spellProto->EffectRadiusIndex[GetEffIndex()])); - mod = SPELLMOD_RADIUS; - } - else - { - radius = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellProto->rangeIndex)); - mod = SPELLMOD_RANGE; - } - - if(Player* modOwner = caster->GetSpellModOwner()) - modOwner->ApplySpellMod(GetId(), mod, radius,NULL); - - if(!caster->IsWithinDistInMap(m_target,radius)) - { - m_target->RemoveAura(GetId(),GetEffIndex()); - return; - } - } - - if(m_isPeriodic && (m_duration >= 0 || m_isPassive || m_permanent)) - { - m_periodicTimer -= diff; - if(m_periodicTimer <= 0) // tick also at m_periodicTimer==0 to prevent lost last tick in case max m_duration == (max m_periodicTimer)*N - { - if( m_modifier.m_auraname == SPELL_AURA_MOD_REGEN || - m_modifier.m_auraname == SPELL_AURA_MOD_POWER_REGEN || - // Cannibalize, eating items and other spells - m_modifier.m_auraname == SPELL_AURA_OBS_MOD_HEALTH || - // Eating items and other spells - m_modifier.m_auraname == SPELL_AURA_OBS_MOD_MANA ) - { - ApplyModifier(true); - return; - } - // update before applying (aura can be removed in TriggerSpell or PeriodicTick calls) - m_periodicTimer += m_modifier.periodictime; - - if(m_isTrigger) - TriggerSpell(); - else - PeriodicTick(); - } - } -} - -void AreaAura::Update(uint32 diff) -{ - // update for the caster of the aura - if(m_caster_guid == m_target->GetGUID()) - { - Unit* caster = m_target; - - if( !caster->hasUnitState(UNIT_STAT_ISOLATED) ) - { - Unit* owner = caster->GetCharmerOrOwner(); - if (!owner) - owner = caster; - std::list targets; - - switch(m_areaAuraType) - { - case AREA_AURA_PARTY: - { - Group *pGroup = NULL; - - if (owner->GetTypeId() == TYPEID_PLAYER) - pGroup = ((Player*)owner)->GetGroup(); - - if( pGroup) - { - uint8 subgroup = ((Player*)owner)->GetSubGroup(); - for(GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) - { - Player* Target = itr->getSource(); - if(Target && Target->isAlive() && Target->GetSubGroup()==subgroup && caster->IsFriendlyTo(Target)) - { - if(caster->IsWithinDistInMap(Target, m_radius)) - targets.push_back(Target); - Pet *pet = Target->GetPet(); - if(pet && pet->isAlive() && caster->IsWithinDistInMap(pet, m_radius)) - targets.push_back(pet); - } - } - } - else - { - // add owner - if( owner != caster && caster->IsWithinDistInMap(owner, m_radius) ) - targets.push_back(owner); - // add caster's pet - Unit* pet = caster->GetPet(); - if( pet && caster->IsWithinDistInMap(pet, m_radius)) - targets.push_back(pet); - } - break; - } - case AREA_AURA_FRIEND: - { - CellPair p(MaNGOS::ComputeCellPair(caster->GetPositionX(), caster->GetPositionY())); - Cell cell(p); - cell.data.Part.reserved = ALL_DISTRICT; - cell.SetNoCreate(); - - MaNGOS::AnyFriendlyUnitInObjectRangeCheck u_check(caster, owner, m_radius); - MaNGOS::UnitListSearcher searcher(targets, u_check); - TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); - TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); - CellLock cell_lock(cell, p); - cell_lock->Visit(cell_lock, world_unit_searcher, *MapManager::Instance().GetMap(caster->GetMapId(), caster)); - cell_lock->Visit(cell_lock, grid_unit_searcher, *MapManager::Instance().GetMap(caster->GetMapId(), caster)); - break; - } - case AREA_AURA_ENEMY: - { - CellPair p(MaNGOS::ComputeCellPair(caster->GetPositionX(), caster->GetPositionY())); - Cell cell(p); - cell.data.Part.reserved = ALL_DISTRICT; - cell.SetNoCreate(); - - MaNGOS::AnyAoETargetUnitInObjectRangeCheck u_check(caster, owner, m_radius); // No GetCharmer in searcher - MaNGOS::UnitListSearcher searcher(targets, u_check); - TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); - TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); - CellLock cell_lock(cell, p); - cell_lock->Visit(cell_lock, world_unit_searcher, *MapManager::Instance().GetMap(caster->GetMapId(), caster)); - cell_lock->Visit(cell_lock, grid_unit_searcher, *MapManager::Instance().GetMap(caster->GetMapId(), caster)); - break; - } - case AREA_AURA_OWNER: - case AREA_AURA_PET: - { - if(owner != caster) - targets.push_back(owner); - break; - } - } - - for(std::list::iterator tIter = targets.begin(); tIter != targets.end(); tIter++) - { - if((*tIter)->HasAura(GetId(), m_effIndex)) - continue; - - if(SpellEntry const *actualSpellInfo = spellmgr.SelectAuraRankForPlayerLevel(GetSpellProto(), (*tIter)->getLevel())) - { - int32 actualBasePoints = m_currentBasePoints; - // recalculate basepoints for lower rank (all AreaAura spell not use custom basepoints?) - if(actualSpellInfo != GetSpellProto()) - actualBasePoints = actualSpellInfo->EffectBasePoints[m_effIndex]; - AreaAura *aur = new AreaAura(actualSpellInfo, m_effIndex, &actualBasePoints, (*tIter), caster, NULL); - (*tIter)->AddAura(aur); - } - } - } - Aura::Update(diff); - } - else // aura at non-caster - { - Unit * tmp_target = m_target; - Unit* caster = GetCaster(); - uint32 tmp_spellId = GetId(), tmp_effIndex = m_effIndex; - - // WARNING: the aura may get deleted during the update - // DO NOT access its members after update! - Aura::Update(diff); - - // remove aura if out-of-range from caster (after teleport for example) - // or caster is isolated or caster no longer has the aura - // or caster is (no longer) friendly - bool needFriendly = (m_areaAuraType == AREA_AURA_ENEMY ? false : true); - if( !caster || caster->hasUnitState(UNIT_STAT_ISOLATED) || - !caster->IsWithinDistInMap(tmp_target, m_radius) || - !caster->HasAura(tmp_spellId, tmp_effIndex) || - caster->IsFriendlyTo(tmp_target) != needFriendly - ) - { - tmp_target->RemoveAura(tmp_spellId, tmp_effIndex); - } - else if( m_areaAuraType == AREA_AURA_PARTY) // check if in same sub group - { - // not check group if target == owner or target == pet - if (caster->GetCharmerOrOwnerGUID() != tmp_target->GetGUID() && caster->GetGUID() != tmp_target->GetCharmerOrOwnerGUID()) - { - Player* check = caster->GetCharmerOrOwnerPlayerOrPlayerItself(); - - Group *pGroup = check ? check->GetGroup() : NULL; - if( pGroup ) - { - Player* checkTarget = tmp_target->GetCharmerOrOwnerPlayerOrPlayerItself(); - if(!checkTarget || !pGroup->SameSubGroup(check, checkTarget)) - tmp_target->RemoveAura(tmp_spellId, tmp_effIndex); - } - else - tmp_target->RemoveAura(tmp_spellId, tmp_effIndex); - } - } - else if( m_areaAuraType == AREA_AURA_PET || m_areaAuraType == AREA_AURA_OWNER ) - { - if( tmp_target->GetGUID() != caster->GetCharmerOrOwnerGUID() ) - tmp_target->RemoveAura(tmp_spellId, tmp_effIndex); - } - } -} - -void PersistentAreaAura::Update(uint32 diff) -{ - bool remove = false; - - // remove the aura if its caster or the dynamic object causing it was removed - // or if the target moves too far from the dynamic object - Unit *caster = GetCaster(); - if (caster) - { - DynamicObject *dynObj = caster->GetDynObject(GetId(), GetEffIndex()); - if (dynObj) - { - if (!m_target->IsWithinDistInMap(dynObj, dynObj->GetRadius())) - remove = true; - } - else - remove = true; - } - else - remove = true; - - Unit *tmp_target = m_target; - uint32 tmp_id = GetId(), tmp_index = GetEffIndex(); - - // WARNING: the aura may get deleted during the update - // DO NOT access its members after update! - Aura::Update(diff); - - if(remove) - tmp_target->RemoveAura(tmp_id, tmp_index); -} - -void Aura::ApplyModifier(bool apply, bool Real) -{ - AuraType aura = m_modifier.m_auraname; - - m_in_use = true; - if(aura= MAX_AURAS || m_isPassive) - return; - - if( m_target->GetTypeId() == TYPEID_PLAYER) - { - WorldPacket data(SMSG_UPDATE_AURA_DURATION, 5); - data << (uint8)m_auraSlot << (uint32)m_duration; - ((Player*)m_target)->SendDirectMessage(&data); - - data.Initialize(SMSG_SET_EXTRA_AURA_INFO, (8+1+4+4+4)); - data.append(m_target->GetPackGUID()); - data << uint8(m_auraSlot); - data << uint32(GetId()); - data << uint32(GetAuraMaxDuration()); - data << uint32(GetAuraDuration()); - ((Player*)m_target)->SendDirectMessage(&data); - } - - // not send in case player loading (will not work anyway until player not added to map), sent in visibility change code - if(m_target->GetTypeId() == TYPEID_PLAYER && ((Player*)m_target)->GetSession()->PlayerLoading()) - return; - - Unit* caster = GetCaster(); - - if(caster && caster->GetTypeId() == TYPEID_PLAYER && caster != m_target) - SendAuraDurationForCaster((Player*)caster); -} - -void Aura::SendAuraDurationForCaster(Player* caster) -{ - WorldPacket data(SMSG_SET_EXTRA_AURA_INFO_NEED_UPDATE, (8+1+4+4+4)); - data.append(m_target->GetPackGUID()); - data << uint8(m_auraSlot); - data << uint32(GetId()); - data << uint32(GetAuraMaxDuration()); // full - data << uint32(GetAuraDuration()); // remain - caster->GetSession()->SendPacket(&data); -} - -void Aura::_AddAura() -{ - if (!GetId()) - return; - if(!m_target) - return; - - // we can found aura in NULL_AURA_SLOT and then need store state instead check slot != NULL_AURA_SLOT - bool samespell = false; - bool secondaura = false; - uint8 slot = NULL_AURA_SLOT; - - for(uint8 i = 0; i < 3; i++) - { - Unit::spellEffectPair spair = Unit::spellEffectPair(GetId(), i); - for(Unit::AuraMap::const_iterator itr = m_target->GetAuras().lower_bound(spair); itr != m_target->GetAuras().upper_bound(spair); ++itr) - { - // allow use single slot only by auras from same caster - if(itr->second->GetCasterGUID()==GetCasterGUID()) - { - samespell = true; - if (m_effIndex > itr->second->GetEffIndex()) - secondaura = true; - slot = itr->second->GetAuraSlot(); - break; - } - } - - if(samespell) - break; - } - - // not call total regen auras at adding - switch (m_modifier.m_auraname) - { - case SPELL_AURA_OBS_MOD_HEALTH: - case SPELL_AURA_OBS_MOD_MANA: - m_periodicTimer = m_modifier.periodictime; - break; - case SPELL_AURA_MOD_REGEN: - case SPELL_AURA_MOD_POWER_REGEN: - case SPELL_AURA_MOD_MANA_REGEN_FROM_STAT: - m_periodicTimer = 5000; - break; - } - - // register aura - if (getDiminishGroup() != DIMINISHING_NONE ) - m_target->ApplyDiminishingAura(getDiminishGroup(),true); - - Unit* caster = GetCaster(); - - // passive auras (except totem auras) do not get placed in the slots - // area auras with SPELL_AURA_NONE are not shown on target - if((!m_isPassive || (caster && caster->GetTypeId() == TYPEID_UNIT && ((Creature*)caster)->isTotem())) && - (m_spellProto->Effect[GetEffIndex()] != SPELL_EFFECT_APPLY_AREA_AURA_ENEMY || m_target != caster)) - { - if(!samespell) // new slot need - { - if (IsPositive()) // empty positive slot - { - for (uint8 i = 0; i < MAX_POSITIVE_AURAS; i++) - { - if (m_target->GetUInt32Value((uint16)(UNIT_FIELD_AURA + i)) == 0) - { - slot = i; - break; - } - } - } - else // empty negative slot - { - for (uint8 i = MAX_POSITIVE_AURAS; i < MAX_AURAS; i++) - { - if (m_target->GetUInt32Value((uint16)(UNIT_FIELD_AURA + i)) == 0) - { - slot = i; - break; - } - } - } - - SetAuraSlot( slot ); - - // Not update fields for not first spell's aura, all data already in fields - if(!secondaura) - { - if(slot < MAX_AURAS) // slot found - { - SetAura(slot, false); - SetAuraFlag(slot, true); - SetAuraLevel(slot,caster ? caster->getLevel() : sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)); - UpdateAuraCharges(); - - // update for out of range group members - m_target->UpdateAuraForGroup(slot); - } - - UpdateAuraDuration(); - } - } - else // use found slot - { - SetAuraSlot( slot ); - // Not recalculate stack count for second aura of the same spell - if (!secondaura) - UpdateSlotCounterAndDuration(true); - } - - // Update Seals information - if( IsSealSpell(GetSpellProto()) ) - m_target->ModifyAuraState(AURA_STATE_JUDGEMENT, true); - - // Conflagrate aura state - if (GetSpellProto()->SpellFamilyName == SPELLFAMILY_WARLOCK && (GetSpellProto()->SpellFamilyFlags & 4)) - m_target->ModifyAuraState(AURA_STATE_IMMOLATE, true); - - if(GetSpellProto()->SpellFamilyName == SPELLFAMILY_DRUID - && (GetSpellProto()->SpellFamilyFlags == 0x40 || GetSpellProto()->SpellFamilyFlags == 0x10)) - { - m_target->ModifyAuraState(AURA_STATE_SWIFTMEND, true); - } - } -} - -void Aura::_RemoveAura() -{ - // Remove all triggered by aura spells vs unlimited duration - // except same aura replace case - if(m_removeMode!=AURA_REMOVE_BY_STACK) - CleanupTriggeredSpells(); - - Unit* caster = GetCaster(); - - if(caster && IsPersistent()) - { - DynamicObject *dynObj = caster->GetDynObject(GetId(), GetEffIndex()); - if (dynObj) - dynObj->RemoveAffected(m_target); - } - - // unregister aura - if (getDiminishGroup() != DIMINISHING_NONE ) - m_target->ApplyDiminishingAura(getDiminishGroup(),false); - - //passive auras do not get put in slots - // Note: but totem can be not accessible for aura target in time remove (to far for find in grid) - //if(m_isPassive && !(caster && caster->GetTypeId() == TYPEID_UNIT && ((Creature*)caster)->isTotem())) - // return; - - uint8 slot = GetAuraSlot(); - - if(slot >= MAX_AURAS) // slot not set - return; - - if(m_target->GetUInt32Value((uint16)(UNIT_FIELD_AURA + slot)) == 0) - return; - - bool samespell = false; - bool sameaura = false; - - // find other aura in same slot (current already removed from list) - for(uint8 i = 0; i < 3; i++) - { - Unit::spellEffectPair spair = Unit::spellEffectPair(GetId(), i); - for(Unit::AuraMap::const_iterator itr = m_target->GetAuras().lower_bound(spair); itr != m_target->GetAuras().upper_bound(spair); ++itr) - { - if(itr->second->GetAuraSlot()==slot) - { - samespell = true; - - if(GetEffIndex()==i) - sameaura = true; - - break; - } - } - if(samespell) - break; - } - - // only remove icon when the last aura of the spell is removed (current aura already removed from list) - if (!samespell) - { - SetAura(slot, true); - SetAuraFlag(slot, false); - SetAuraLevel(slot,caster ? caster->getLevel() : sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)); - - SetAuraApplication(slot, 0); - // update for out of range group members - m_target->UpdateAuraForGroup(slot); - - if( IsSealSpell(GetSpellProto()) ) - m_target->ModifyAuraState(AURA_STATE_JUDGEMENT,false); - - // Conflagrate aura state - if (GetSpellProto()->SpellFamilyName == SPELLFAMILY_WARLOCK && (GetSpellProto()->SpellFamilyFlags & 4)) - m_target->ModifyAuraState(AURA_STATE_IMMOLATE, false); - - // Swiftmend aura state - if(GetSpellProto()->SpellFamilyName == SPELLFAMILY_DRUID - && (GetSpellProto()->SpellFamilyFlags == 0x40 || GetSpellProto()->SpellFamilyFlags == 0x10)) - { - bool found = false; - Unit::AuraList const& RejorRegr = m_target->GetAurasByType(SPELL_AURA_PERIODIC_HEAL); - for(Unit::AuraList::const_iterator i = RejorRegr.begin(); i != RejorRegr.end(); ++i) - { - if((*i)->GetSpellProto()->SpellFamilyName == SPELLFAMILY_DRUID - && ((*i)->GetSpellProto()->SpellFamilyFlags == 0x40 || (*i)->GetSpellProto()->SpellFamilyFlags == 0x10) ) - { - found = true; - break; - } - } - if(!found) - m_target->ModifyAuraState(AURA_STATE_SWIFTMEND, false); - } - - // reset cooldown state for spells - if(caster && caster->GetTypeId() == TYPEID_PLAYER) - { - if ( GetSpellProto()->Attributes & SPELL_ATTR_DISABLED_WHILE_ACTIVE ) - ((Player*)caster)->SendCooldownEvent(GetSpellProto()); - } - } - else if(sameaura) // decrease count for spell, only for same aura effect, or this spell auras in remove proccess. - UpdateSlotCounterAndDuration(false); -} - -void Aura::SetAuraFlag(uint32 slot, bool add) -{ - uint32 index = slot / 4; - uint32 byte = (slot % 4) * 8; - uint32 val = m_target->GetUInt32Value(UNIT_FIELD_AURAFLAGS + index); - val &= ~((uint32)AFLAG_MASK << byte); - if(add) - { - if (IsPositive()) - val |= ((uint32)AFLAG_POSITIVE << byte); - else - val |= ((uint32)AFLAG_NEGATIVE << byte); - } - m_target->SetUInt32Value(UNIT_FIELD_AURAFLAGS + index, val); -} - -void Aura::SetAuraLevel(uint32 slot,uint32 level) -{ - uint32 index = slot / 4; - uint32 byte = (slot % 4) * 8; - uint32 val = m_target->GetUInt32Value(UNIT_FIELD_AURALEVELS + index); - val &= ~(0xFF << byte); - val |= (level << byte); - m_target->SetUInt32Value(UNIT_FIELD_AURALEVELS + index, val); -} - -void Aura::SetAuraApplication(uint32 slot, int8 count) -{ - uint32 index = slot / 4; - uint32 byte = (slot % 4) * 8; - uint32 val = m_target->GetUInt32Value(UNIT_FIELD_AURAAPPLICATIONS + index); - val &= ~(0xFF << byte); - val |= ((uint8(count)) << byte); - m_target->SetUInt32Value(UNIT_FIELD_AURAAPPLICATIONS + index, val); -} - -void Aura::UpdateSlotCounterAndDuration(bool add) -{ - uint8 slot = GetAuraSlot(); - if(slot >= MAX_AURAS) - return; - - // calculate amount of similar auras by same effect index (similar different spells) - int8 count = 0; - - // calculate auras and update durations in case aura adding - Unit::AuraList const& aura_list = m_target->GetAurasByType(GetModifier()->m_auraname); - for(Unit::AuraList::const_iterator i = aura_list.begin();i != aura_list.end(); ++i) - { - if( (*i)->GetId()==GetId() && (*i)->GetEffIndex()==m_effIndex && - (*i)->GetCasterGUID()==GetCasterGUID() ) - { - ++count; - - if(add) - (*i)->SetAuraDuration(GetAuraDuration()); - } - } - - // at aura add aura not added yet, at aura remove aura already removed - // in field stored (count-1) - if(!add) - --count; - - SetAuraApplication(slot, count); - - UpdateAuraDuration(); -} - -/*********************************************************/ -/*** BASIC AURA FUNCTION ***/ -/*********************************************************/ -void Aura::HandleAddModifier(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER || !Real) - return; - - SpellEntry const *spellInfo = GetSpellProto(); - if(!spellInfo) - return; - - if(m_modifier.m_miscvalue >= MAX_SPELLMOD) - return; - - if (apply) - { - // Add custom charges for some mod aura - switch (m_spellProto->Id) - { - case 17941: // Shadow Trance - case 22008: // Netherwind Focus - case 34936: // Backlash - m_procCharges = 1; - break; - } - - SpellModifier *mod = new SpellModifier; - mod->op = SpellModOp(m_modifier.m_miscvalue); - mod->value = m_modifier.m_amount; - mod->type = SpellModType(m_modifier.m_auraname); // SpellModType value == spell aura types - mod->spellId = GetId(); - mod->effectId = m_effIndex; - mod->lastAffected = NULL; - - uint64 spellAffectMask = spellmgr.GetSpellAffectMask(GetId(), m_effIndex); - - if (spellAffectMask) - mod->mask = spellAffectMask; - else - mod->mask = spellInfo->EffectItemType[m_effIndex]; - - if (m_procCharges > 0) - mod->charges = m_procCharges; - else - mod->charges = 0; - - m_spellmod = mod; - } - - uint64 spellFamilyMask = m_spellmod->mask; - - ((Player*)m_target)->AddSpellMod(m_spellmod, apply); - - // reapply some passive spells after add/remove related spellmods - if(spellInfo->SpellFamilyName==SPELLFAMILY_WARRIOR && (spellFamilyMask & 0x0000100000000000LL)) - { - m_target->RemoveAurasDueToSpell(45471); - - if(apply) - m_target->CastSpell(m_target,45471,true); - } -} - -void Aura::TriggerSpell() -{ - Unit* caster = GetCaster(); - Unit* target = GetTriggerTarget(); - - if(!caster || !target) - return; - - // generic casting code with custom spells and target/caster customs - uint32 trigger_spell_id = GetSpellProto()->EffectTriggerSpell[m_effIndex]; - - uint64 originalCasterGUID = GetCasterGUID(); - - SpellEntry const *triggredSpellInfo = sSpellStore.LookupEntry(trigger_spell_id); - SpellEntry const *auraSpellInfo = GetSpellProto(); - uint32 auraId = auraSpellInfo->Id; - - // specific code for cases with no trigger spell provided in field - if (triggredSpellInfo == NULL) - { - switch(auraSpellInfo->SpellFamilyName) - { - case SPELLFAMILY_GENERIC: - { - switch(auraId) - { - // Firestone Passive (1-5 rangs) - case 758: - case 17945: - case 17947: - case 17949: - case 27252: - { - if (caster->GetTypeId()!=TYPEID_PLAYER) - return; - Item* item = ((Player*)caster)->GetWeaponForAttack(BASE_ATTACK); - if (!item) - return; - uint32 enchant_id = 0; - switch (GetId()) - { - case 758: enchant_id = 1803; break; // Rank 1 - case 17945: enchant_id = 1823; break; // Rank 2 - case 17947: enchant_id = 1824; break; // Rank 3 - case 17949: enchant_id = 1825; break; // Rank 4 - case 27252: enchant_id = 2645; break; // Rank 5 - default: - return; - } - // remove old enchanting before applying new - ((Player*)caster)->ApplyEnchantment(item,TEMP_ENCHANTMENT_SLOT,false); - item->SetEnchantment(TEMP_ENCHANTMENT_SLOT, enchant_id, m_modifier.periodictime+1000, 0); - // add new enchanting - ((Player*)caster)->ApplyEnchantment(item,TEMP_ENCHANTMENT_SLOT,true); - return; - } -// // Periodic Mana Burn -// case 812: break; -// // Polymorphic Ray -// case 6965: break; -// // Fire Nova (1-7 Rangs) -// case 8350: -// case 8508: -// case 8509: -// case 11312: -// case 11313: -// case 25540: -// case 25544: -// break; - // Thaumaturgy Channel - case 9712: trigger_spell_id = 21029; break; -// // Egan's Blaster -// case 17368: break; -// // Haunted -// case 18347: break; -// // Ranshalla Waiting -// case 18953: break; -// // Inferno -// case 19695: break; -// // Frostwolf Muzzle DND -// case 21794: break; -// // Alterac Ram Collar DND -// case 21866: break; -// // Celebras Waiting -// case 21916: break; - // Brood Affliction: Bronze - case 23170: - { - m_target->CastSpell(m_target, 23171, true, 0, this); - return; - } -// // Mark of Frost -// case 23184: break; - // Restoration - case 23493: - { - int32 heal = caster->GetMaxHealth() / 10; - caster->ModifyHealth( heal ); - caster->SendHealSpellLog(caster, 23493, heal); - - int32 mana = caster->GetMaxPower(POWER_MANA); - if (mana) - { - mana /= 10; - caster->ModifyPower( POWER_MANA, mana ); - caster->SendEnergizeSpellLog(caster, 23493, mana, POWER_MANA); - } - break; - } -// // Stoneclaw Totem Passive TEST -// case 23792: break; -// // Axe Flurry -// case 24018: break; -// // Mark of Arlokk -// case 24210: break; -// // Restoration -// case 24379: break; -// // Happy Pet -// case 24716: break; -// // Dream Fog -// case 24780: break; -// // Cannon Prep -// case 24832: break; -// // Shadow Bolt Whirl -// case 24834: break; -// // Stink Trap -// case 24918: break; -// // Mark of Nature -// case 25041: break; -// // Agro Drones -// case 25152: break; -// // Consume -// case 25371: break; -// // Pain Spike -// case 25572: break; -// // Rotate 360 -// case 26009: break; -// // Rotate -360 -// case 26136: break; -// // Consume -// case 26196: break; -// // Berserk -// case 26615: break; -// // Defile -// case 27177: break; -// // Teleport: IF/UC -// case 27601: break; -// // Five Fat Finger Exploding Heart Technique -// case 27673: break; -// // Nitrous Boost -// case 27746: break; -// // Steam Tank Passive -// case 27747: break; -// // Frost Blast -// case 27808: break; -// // Detonate Mana -// case 27819: break; -// // Controller Timer -// case 28095: break; -// // Stalagg Chain -// case 28096: break; -// // Stalagg Tesla Passive -// case 28097: break; -// // Feugen Tesla Passive -// case 28109: break; -// // Feugen Chain -// case 28111: break; -// // Mark of Didier -// case 28114: break; -// // Communique Timer, camp -// case 28346: break; -// // Icebolt -// case 28522: break; -// // Silithyst -// case 29519: break; -// // Inoculate Nestlewood Owlkin - case 29528: trigger_spell_id = 28713; break; -// // Overload -// case 29768: break; -// // Return Fire -// case 29788: break; -// // Return Fire -// case 29793: break; -// // Return Fire -// case 29794: break; -// // Guardian of Icecrown Passive -// case 29897: break; - // Feed Captured Animal - case 29917: trigger_spell_id = 29916; break; -// // Flame Wreath -// case 29946: break; -// // Flame Wreath -// case 29947: break; -// // Mind Exhaustion Passive -// case 30025: break; -// // Nether Beam - Serenity -// case 30401: break; - // Extract Gas - case 30427: - { - // move loot to player inventory and despawn target - if(caster->GetTypeId() ==TYPEID_PLAYER && - target->GetTypeId() == TYPEID_UNIT && - ((Creature*)target)->GetCreatureInfo()->type == CREATURE_TYPE_GAS_CLOUD) - { - Player* player = (Player*)caster; - Creature* creature = (Creature*)target; - // missing lootid has been reported on startup - just return - if (!creature->GetCreatureInfo()->SkinLootId) - { - return; - } - Loot *loot = &creature->loot; - loot->clear(); - loot->FillLoot(creature->GetCreatureInfo()->SkinLootId, LootTemplates_Skinning, NULL); - for(uint8 i=0;iitems.size();i++) - { - LootItem *item = loot->LootItemInSlot(i,player); - ItemPosCountVec dest; - uint8 msg = player->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, item->itemid, item->count ); - if ( msg == EQUIP_ERR_OK ) - { - Item * newitem = player->StoreNewItem( dest, item->itemid, true, item->randomPropertyId); - - player->SendNewItem(newitem, uint32(item->count), false, false, true); - } - else - player->SendEquipError( msg, NULL, NULL ); - } - creature->setDeathState(JUST_DIED); - creature->RemoveCorpse(); - creature->SetHealth(0); // just for nice GM-mode view - } - return; - break; - } - // Quake - case 30576: trigger_spell_id = 30571; break; -// // Burning Maul -// case 30598: break; -// // Regeneration -// case 30799: -// case 30800: -// case 30801: -// break; -// // Despawn Self - Smoke cloud -// case 31269: break; -// // Time Rift Periodic -// case 31320: break; -// // Corrupt Medivh -// case 31326: break; - // Doom - case 31347: - { - m_target->CastSpell(m_target,31350,true); - m_target->DealDamage(m_target, m_target->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); - return; - } - // Spellcloth - case 31373: - { - // Summon Elemental after create item - caster->SummonCreature(17870, 0, 0, 0, caster->GetOrientation(), TEMPSUMMON_DEAD_DESPAWN, 0); - return; - } -// // Bloodmyst Tesla -// case 31611: break; -// // Doomfire -// case 31944: break; -// // Teleport Test -// case 32236: break; -// // Earthquake -// case 32686: break; -// // Possess -// case 33401: break; -// // Draw Shadows -// case 33563: break; -// // Murmur's Touch -// case 33711: break; - // Flame Quills - case 34229: - { - // cast 24 spells 34269-34289, 34314-34316 - for(uint32 spell_id = 34269; spell_id != 34290; ++spell_id) - caster->CastSpell(m_target,spell_id,true); - for(uint32 spell_id = 34314; spell_id != 34317; ++spell_id) - caster->CastSpell(m_target,spell_id,true); - return; - } -// // Gravity Lapse -// case 34480: break; -// // Tornado -// case 34683: break; -// // Frostbite Rotate -// case 34748: break; -// // Arcane Flurry -// case 34821: break; -// // Interrupt Shutdown -// case 35016: break; -// // Interrupt Shutdown -// case 35176: break; -// // Inferno -// case 35268: break; -// // Salaadin's Tesla -// case 35515: break; -// // Ethereal Channel (Red) -// case 35518: break; -// // Nether Vapor -// case 35879: break; -// // Dark Portal Storm -// case 36018: break; -// // Burning Maul -// case 36056: break; -// // Living Grove Defender Lifespan -// case 36061: break; -// // Professor Dabiri Talks -// case 36064: break; -// // Kael Gaining Power -// case 36091: break; -// // They Must Burn Bomb Aura -// case 36344: break; -// // They Must Burn Bomb Aura (self) -// case 36350: break; -// // Stolen Ravenous Ravager Egg -// case 36401: break; -// // Activated Cannon -// case 36410: break; -// // Stolen Ravenous Ravager Egg -// case 36418: break; -// // Enchanted Weapons -// case 36510: break; -// // Cursed Scarab Periodic -// case 36556: break; -// // Cursed Scarab Despawn Periodic -// case 36561: break; -// // Vision Guide -// case 36573: break; -// // Cannon Charging (platform) -// case 36785: break; -// // Cannon Charging (self) -// case 36860: break; - // Remote Toy - case 37027: trigger_spell_id = 37029; break; -// // Mark of Death -// case 37125: break; -// // Arcane Flurry -// case 37268: break; -// // Spout -// case 37429: break; -// // Spout -// case 37430: break; -// // Karazhan - Chess NPC AI, Snapshot timer -// case 37440: break; -// // Karazhan - Chess NPC AI, action timer -// case 37504: break; -// // Karazhan - Chess: Is Square OCCUPIED aura (DND) -// case 39400: break; -// // Banish -// case 37546: break; -// // Shriveling Gaze -// case 37589: break; -// // Fake Aggro Radius (2 yd) -// case 37815: break; -// // Corrupt Medivh -// case 37853: break; - // Eye of Grillok - case 38495: - { - m_target->CastSpell(m_target, 38530, true); - return; - } - // Absorb Eye of Grillok (Zezzak's Shard) - case 38554: - { - if(m_target->GetTypeId() != TYPEID_UNIT) - return; - - caster->CastSpell(caster, 38495, true); - - Creature* creatureTarget = (Creature*)m_target; - - creatureTarget->setDeathState(JUST_DIED); - creatureTarget->RemoveCorpse(); - creatureTarget->SetHealth(0); // just for nice GM-mode view - return; - } -// // Magic Sucker Device timer -// case 38672: break; -// // Tomb Guarding Charging -// case 38751: break; -// // Murmur's Touch -// case 38794: break; -// // Activate Nether-wraith Beacon (31742 Nether-wraith Beacon item) -// case 39105: break; -// // Drain World Tree Visual -// case 39140: break; -// // Quest - Dustin's Undead Dragon Visual aura -// case 39259: break; -// // Hellfire - The Exorcism, Jules releases darkness, aura -// case 39306: break; -// // Inferno -// case 39346: break; -// // Enchanted Weapons -// case 39489: break; -// // Shadow Bolt Whirl -// case 39630: break; -// // Shadow Bolt Whirl -// case 39634: break; -// // Shadow Inferno -// case 39645: break; - // Tear of Azzinoth Summon Channel - it's not really supposed to do anything,and this only prevents the console spam - case 39857: trigger_spell_id = 39856; break; -// // Soulgrinder Ritual Visual (Smashed) -// case 39974: break; -// // Simon Game Pre-game timer -// case 40041: break; -// // Knockdown Fel Cannon: The Aggro Check Aura -// case 40113: break; -// // Spirit Lance -// case 40157: break; -// // Demon Transform 2 -// case 40398: break; -// // Demon Transform 1 -// case 40511: break; -// // Ancient Flames -// case 40657: break; -// // Ethereal Ring Cannon: Cannon Aura -// case 40734: break; -// // Cage Trap -// case 40760: break; -// // Random Periodic -// case 40867: break; -// // Prismatic Shield -// case 40879: break; -// // Aura of Desire -// case 41350: break; -// // Dementia -// case 41404: break; -// // Chaos Form -// case 41629: break; -// // Alert Drums -// case 42177: break; -// // Spout -// case 42581: break; -// // Spout -// case 42582: break; -// // Return to the Spirit Realm -// case 44035: break; -// // Curse of Boundless Agony -// case 45050: break; -// // Earthquake -// case 46240: break; - // Personalized Weather - case 46736: trigger_spell_id = 46737; break; -// // Stay Submerged -// case 46981: break; -// // Dragonblight Ram -// case 47015: break; -// // Party G.R.E.N.A.D.E. -// case 51510: break; - default: - break; - } - break; - } - case SPELLFAMILY_MAGE: - { - switch(auraId) - { - // Invisibility - case 66: - { - if(!m_duration) - m_target->CastSpell(m_target, 32612, true, NULL, this); - return; - } - default: - break; - } - break; - } -// case SPELLFAMILY_WARRIOR: -// { -// switch(auraId) -// { -// // Wild Magic -// case 23410: break; -// // Corrupted Totems -// case 23425: break; -// default: -// break; -// } -// break; -// } -// case SPELLFAMILY_PRIEST: -// { -// switch(auraId) -// { -// // Blue Beam -// case 32930: break; -// // Fury of the Dreghood Elders -// case 35460: break; -// default: -// break; -// } - // break; - // } - case SPELLFAMILY_DRUID: - { - switch(auraId) - { - // Cat Form - // trigger_spell_id not set and unknown effect triggered in this case, ignoring for while - case 768: - return; - // Frenzied Regeneration - case 22842: - case 22895: - case 22896: - case 26999: - { - int32 LifePerRage = GetModifier()->m_amount; - - int32 lRage = m_target->GetPower(POWER_RAGE); - if(lRage > 100) // rage stored as rage*10 - lRage = 100; - m_target->ModifyPower(POWER_RAGE, -lRage); - int32 FRTriggerBasePoints = int32(lRage*LifePerRage/10); - m_target->CastCustomSpell(m_target,22845,&FRTriggerBasePoints,NULL,NULL,true,NULL,this); - return; - } - default: - break; - } - break; - } - -// case SPELLFAMILY_HUNTER: -// { -// switch(auraId) -// { -// //Frost Trap Aura -// case 13810: -// return; -// //Rizzle's Frost Trap -// case 39900: -// return; -// // Tame spells -// case 19597: // Tame Ice Claw Bear -// case 19676: // Tame Snow Leopard -// case 19677: // Tame Large Crag Boar -// case 19678: // Tame Adult Plainstrider -// case 19679: // Tame Prairie Stalker -// case 19680: // Tame Swoop -// case 19681: // Tame Dire Mottled Boar -// case 19682: // Tame Surf Crawler -// case 19683: // Tame Armored Scorpid -// case 19684: // Tame Webwood Lurker -// case 19685: // Tame Nightsaber Stalker -// case 19686: // Tame Strigid Screecher -// case 30100: // Tame Crazed Dragonhawk -// case 30103: // Tame Elder Springpaw -// case 30104: // Tame Mistbat -// case 30647: // Tame Barbed Crawler -// case 30648: // Tame Greater Timberstrider -// case 30652: // Tame Nightstalker -// return; -// default: -// break; -// } -// break; -// } - case SPELLFAMILY_SHAMAN: - { - switch(auraId) - { - // Lightning Shield (The Earthshatterer set trigger after cast Lighting Shield) - case 28820: - { - // Need remove self if Lightning Shield not active - Unit::AuraMap const& auras = target->GetAuras(); - for(Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) - { - SpellEntry const* spell = itr->second->GetSpellProto(); - if( spell->SpellFamilyName == SPELLFAMILY_SHAMAN && - spell->SpellFamilyFlags & 0x0000000000000400L) - return; - } - target->RemoveAurasDueToSpell(28820); - return; - } - // Totemic Mastery (Skyshatter Regalia (Shaman Tier 6) - bonus) - case 38443: - { - bool all = true; - for(int i = 0; i < MAX_TOTEM; ++i) - { - if(!caster->m_TotemSlot[i]) - { - all = false; - break; - } - } - - if(all) - caster->CastSpell(caster,38437,true); - else - caster->RemoveAurasDueToSpell(38437); - return; - } - default: - break; - } - break; - } - default: - break; - } - // Reget trigger spell proto - triggredSpellInfo = sSpellStore.LookupEntry(trigger_spell_id); - if(triggredSpellInfo == NULL) - { - sLog.outError("Aura::TriggerSpell: Spell %u have 0 in EffectTriggered[%d], not handled custom case?",GetId(),GetEffIndex()); - return; - } - } - else - { - // Spell exist but require costum code - switch(auraId) - { - // Curse of Idiocy - case 1010: - { - // TODO: spell casted by result in correct way mostly - // BUT: - // 1) target show casting at each triggered cast: target don't must show casting animation for any triggered spell - // but must show affect apply like item casting - // 2) maybe aura must be replace by new with accumulative stat mods insteed stacking - - // prevent cast by triggered auras - if(m_caster_guid == m_target->GetGUID()) - return; - - // stop triggering after each affected stats lost > 90 - int32 intelectLoss = 0; - int32 spiritLoss = 0; - - Unit::AuraList const& mModStat = m_target->GetAurasByType(SPELL_AURA_MOD_STAT); - for(Unit::AuraList::const_iterator i = mModStat.begin(); i != mModStat.end(); ++i) - { - if ((*i)->GetId() == 1010) - { - switch((*i)->GetModifier()->m_miscvalue) - { - case STAT_INTELLECT: intelectLoss += (*i)->GetModifier()->m_amount; break; - case STAT_SPIRIT: spiritLoss += (*i)->GetModifier()->m_amount; break; - default: break; - } - } - } - - if(intelectLoss <= -90 && spiritLoss <= -90) - return; - - caster = target; - originalCasterGUID = 0; - break; - } - // Mana Tide - case 16191: - { - caster->CastCustomSpell(target, trigger_spell_id, &m_modifier.m_amount, NULL, NULL, true, NULL, this, originalCasterGUID); - return; - } - } - } - // All ok cast by default case - Spell *spell = new Spell(caster, triggredSpellInfo, true, originalCasterGUID ); - - SpellCastTargets targets; - targets.setUnitTarget( target ); - - // if spell create dynamic object extract area from it - if(DynamicObject* dynObj = caster->GetDynObject(GetId())) - targets.setDestination(dynObj->GetPositionX(),dynObj->GetPositionY(),dynObj->GetPositionZ()); - - spell->prepare(&targets, this); -} - -/*********************************************************/ -/*** AURA EFFECTS ***/ -/*********************************************************/ - -void Aura::HandleAuraDummy(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - Unit* caster = GetCaster(); - - // AT APPLY - if(apply) - { - switch(GetId()) - { - case 1515: // Tame beast - // FIX_ME: this is 2.0.12 threat effect replaced in 2.1.x by dummy aura, must be checked for correctness - if( caster && m_target->CanHaveThreatList()) - m_target->AddThreat(caster, 10.0f); - return; - case 13139: // net-o-matic - // root to self part of (root_target->charge->root_self sequence - if(caster) - caster->CastSpell(caster,13138,true,NULL,this); - return; - case 39850: // Rocket Blast - if(roll_chance_i(20)) // backfire stun - m_target->CastSpell(m_target, 51581, true, NULL, this); - return; - case 46354: // Blood Elf Illusion - if(caster) - { - switch(caster->getGender()) - { - case GENDER_FEMALE: - caster->CastSpell(m_target,46356,true,NULL,this); - break; - case GENDER_MALE: - caster->CastSpell(m_target,46355,true,NULL,this); - break; - default: - break; - } - } - return; - case 46699: // Requires No Ammo - if(m_target->GetTypeId()==TYPEID_PLAYER) - ((Player*)m_target)->RemoveAmmo(); // not use ammo and not allow use - return; - } - - // Earth Shield - if ( caster && GetSpellProto()->SpellFamilyName == SPELLFAMILY_SHAMAN && (GetSpellProto()->SpellFamilyFlags & 0x40000000000LL)) - { - // prevent double apply bonuses - if(m_target->GetTypeId()!=TYPEID_PLAYER || !((Player*)m_target)->GetSession()->PlayerLoading()) - m_modifier.m_amount = caster->SpellHealingBonus(GetSpellProto(), m_modifier.m_amount, SPELL_DIRECT_DAMAGE, m_target); - return; - } - } - // AT REMOVE - else - { - if( m_target->GetTypeId() == TYPEID_PLAYER && - ( GetSpellProto()->Effect[0]==72 || GetSpellProto()->Effect[0]==6 && - ( GetSpellProto()->EffectApplyAuraName[0]==1 || GetSpellProto()->EffectApplyAuraName[0]==128 ) ) ) - { - // spells with SpellEffect=72 and aura=4: 6196, 6197, 21171, 21425 - m_target->SetUInt64Value(PLAYER_FARSIGHT, 0); - WorldPacket data(SMSG_CLEAR_FAR_SIGHT_IMMEDIATE, 0); - ((Player*)m_target)->GetSession()->SendPacket(&data); - return; - } - - if( (IsQuestTameSpell(GetId())) && caster && caster->isAlive() && m_target->isAlive()) - { - uint32 finalSpelId = 0; - switch(GetId()) - { - case 19548: finalSpelId = 19597; break; - case 19674: finalSpelId = 19677; break; - case 19687: finalSpelId = 19676; break; - case 19688: finalSpelId = 19678; break; - case 19689: finalSpelId = 19679; break; - case 19692: finalSpelId = 19680; break; - case 19693: finalSpelId = 19684; break; - case 19694: finalSpelId = 19681; break; - case 19696: finalSpelId = 19682; break; - case 19697: finalSpelId = 19683; break; - case 19699: finalSpelId = 19685; break; - case 19700: finalSpelId = 19686; break; - case 30646: finalSpelId = 30647; break; - case 30653: finalSpelId = 30648; break; - case 30654: finalSpelId = 30652; break; - case 30099: finalSpelId = 30100; break; - case 30102: finalSpelId = 30103; break; - case 30105: finalSpelId = 30104; break; - } - - if(finalSpelId) - caster->CastSpell(m_target,finalSpelId,true,NULL,this); - return; - } - // Dark Fiend - if(GetId()==45934) - { - // Kill target if dispeled - if (m_removeMode==AURA_REMOVE_BY_DISPEL) - m_target->DealDamage(m_target, m_target->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); - return; - } - - // Burning Winds - if(GetId()==46308) // casted only at creatures at spawn - { - m_target->CastSpell(m_target,47287,true,NULL,this); - return; - } - } - - // AT APPLY & REMOVE - - switch(m_spellProto->SpellFamilyName) - { - case SPELLFAMILY_GENERIC: - { - // Unstable Power - if( GetId()==24658 ) - { - uint32 spellId = 24659; - if (apply) - { - const SpellEntry *spell = sSpellStore.LookupEntry(spellId); - if (!spell) - return; - for (int i=0; i < spell->StackAmount; ++i) - caster->CastSpell(m_target, spell->Id, true, NULL, NULL, GetCasterGUID()); - return; - } - m_target->RemoveAurasDueToSpell(spellId); - return; - } - // Restless Strength - if( GetId()==24661 ) - { - uint32 spellId = 24662; - if (apply) - { - const SpellEntry *spell = sSpellStore.LookupEntry(spellId); - if (!spell) - return; - for (int i=0; i < spell->StackAmount; ++i) - caster->CastSpell(m_target, spell->Id, true, NULL, NULL, GetCasterGUID()); - return; - } - m_target->RemoveAurasDueToSpell(spellId); - return; - } - // Victorious - if(GetId()==32216 && m_target->getClass()==CLASS_WARRIOR) - { - m_target->ModifyAuraState(AURA_STATE_WARRIOR_VICTORY_RUSH, apply); - return; - } - //Summon Fire Elemental - if (GetId() == 40133 && caster) - { - Unit *owner = caster->GetOwner(); - if (owner && owner->GetTypeId() == TYPEID_PLAYER) - { - if(apply) - owner->CastSpell(owner,8985,true); - else - ((Player*)owner)->RemovePet(NULL, PET_SAVE_NOT_IN_SLOT, true); - } - return; - } - - //Summon Earth Elemental - if (GetId() == 40132 && caster) - { - Unit *owner = caster->GetOwner(); - if (owner && owner->GetTypeId() == TYPEID_PLAYER) - { - if(apply) - owner->CastSpell(owner,19704,true); - else - ((Player*)owner)->RemovePet(NULL, PET_SAVE_NOT_IN_SLOT, true); - } - return; - } - break; - } - case SPELLFAMILY_MAGE: - { - // Hypothermia - if( GetId()==41425 ) - { - m_target->ModifyAuraState(AURA_STATE_HYPOTHERMIA,apply); - return; - } - break; - } - case SPELLFAMILY_DRUID: - { - // Lifebloom - if ( GetSpellProto()->SpellFamilyFlags & 0x1000000000LL ) - { - if ( apply ) - { - if ( caster ) - // prevent double apply bonuses - if(m_target->GetTypeId()!=TYPEID_PLAYER || !((Player*)m_target)->GetSession()->PlayerLoading()) - m_modifier.m_amount = caster->SpellHealingBonus(GetSpellProto(), m_modifier.m_amount, SPELL_DIRECT_DAMAGE, m_target); - } - else - { - // Final heal only on dispelled or duration end - if ( !(GetAuraDuration() <= 0 || m_removeMode==AURA_REMOVE_BY_DISPEL) ) - return; - - // have a look if there is still some other Lifebloom dummy aura - Unit::AuraList auras = m_target->GetAurasByType(SPELL_AURA_DUMMY); - for(Unit::AuraList::iterator itr = auras.begin(); itr!=auras.end(); itr++) - if((*itr)->GetSpellProto()->SpellFamilyName == SPELLFAMILY_DRUID && - (*itr)->GetSpellProto()->SpellFamilyFlags & 0x1000000000LL) - return; - - // final heal - m_target->CastCustomSpell(m_target,33778,&m_modifier.m_amount,NULL,NULL,true,NULL,this,GetCasterGUID()); - } - return; - } - - // Predatory Strikes - if(m_target->GetTypeId()==TYPEID_PLAYER && GetSpellProto()->SpellIconID == 1563) - { - ((Player*)m_target)->UpdateAttackPowerAndDamage(); - return; - } - // Idol of the Emerald Queen - if ( GetId() == 34246 && m_target->GetTypeId()==TYPEID_PLAYER ) - { - if(apply) - { - SpellModifier *mod = new SpellModifier; - mod->op = SPELLMOD_DOT; - mod->value = m_modifier.m_amount/7; - mod->type = SPELLMOD_FLAT; - mod->spellId = GetId(); - mod->effectId = m_effIndex; - mod->lastAffected = NULL; - mod->mask = 0x001000000000LL; - mod->charges = 0; - - m_spellmod = mod; - } - - ((Player*)m_target)->AddSpellMod(m_spellmod, apply); - return; - } - break; - } - case SPELLFAMILY_HUNTER: - { - // Improved Aspect of the Viper - if( GetId()==38390 && m_target->GetTypeId()==TYPEID_PLAYER ) - { - if(apply) - { - // + effect value for Aspect of the Viper - SpellModifier *mod = new SpellModifier; - mod->op = SPELLMOD_EFFECT1; - mod->value = m_modifier.m_amount; - mod->type = SPELLMOD_FLAT; - mod->spellId = GetId(); - mod->effectId = m_effIndex; - mod->lastAffected = NULL; - mod->mask = 0x4000000000000LL; - mod->charges = 0; - - m_spellmod = mod; - } - - ((Player*)m_target)->AddSpellMod(m_spellmod, apply); - return; - } - break; - } - case SPELLFAMILY_SHAMAN: - { - // Improved Weapon Totems - if( GetSpellProto()->SpellIconID == 57 && m_target->GetTypeId()==TYPEID_PLAYER ) - { - if(apply) - { - SpellModifier *mod = new SpellModifier; - mod->op = SPELLMOD_EFFECT1; - mod->value = m_modifier.m_amount; - mod->type = SPELLMOD_PCT; - mod->spellId = GetId(); - mod->effectId = m_effIndex; - mod->lastAffected = NULL; - switch (m_effIndex) - { - case 0: - mod->mask = 0x00200000000LL; // Windfury Totem - break; - case 1: - mod->mask = 0x00400000000LL; // Flametongue Totem - break; - } - mod->charges = 0; - - m_spellmod = mod; - } - - ((Player*)m_target)->AddSpellMod(m_spellmod, apply); - return; - } - break; - } - } - - // pet auras - if(PetAura const* petSpell = spellmgr.GetPetAura(GetId())) - { - if(apply) - m_target->AddPetAura(petSpell); - else - m_target->RemovePetAura(petSpell); - return; - } -} - -void Aura::HandleAuraPeriodicDummy(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - SpellEntry const*spell = GetSpellProto(); - switch( spell->SpellFamilyName) - { - case SPELLFAMILY_ROGUE: - { - // Master of Subtlety - if (spell->Id==31666 && !apply && Real) - { - m_target->RemoveAurasDueToSpell(31665); - break; - } - break; - } - case SPELLFAMILY_HUNTER: - { - // Aspect of the Viper - if (spell->SpellFamilyFlags&0x0004000000000000LL) - { - // Update regen on remove - if (!apply && m_target->GetTypeId() == TYPEID_PLAYER) - ((Player*)m_target)->UpdateManaRegen(); - break; - } - break; - } - } - - m_isPeriodic = apply; -} - -void Aura::HandleAuraMounted(bool apply, bool Real) -{ - if(apply) - { - CreatureInfo const* ci = objmgr.GetCreatureTemplate(m_modifier.m_miscvalue); - if(!ci) - { - sLog.outErrorDb("AuraMounted: `creature_template`='%u' not found in database (only need it modelid)", m_modifier.m_miscvalue); - return; - } - - uint32 team = 0; - if (m_target->GetTypeId()==TYPEID_PLAYER) - team = ((Player*)m_target)->GetTeam(); - - uint32 display_id = objmgr.ChooseDisplayId(team,ci); - CreatureModelInfo const *minfo = objmgr.GetCreatureModelRandomGender(display_id); - if (minfo) - display_id = minfo->modelid; - - m_target->Mount(display_id); - } - else - { - m_target->Unmount(); - } -} - -void Aura::HandleAuraWaterWalk(bool apply, bool Real) -{ - // only at real add/remove aura - if(!Real) - return; - - WorldPacket data; - if(apply) - data.Initialize(SMSG_MOVE_WATER_WALK, 8+4); - else - data.Initialize(SMSG_MOVE_LAND_WALK, 8+4); - data.append(m_target->GetPackGUID()); - data << uint32(0); - m_target->SendMessageToSet(&data,true); -} - -void Aura::HandleAuraFeatherFall(bool apply, bool Real) -{ - // only at real add/remove aura - if(!Real) - return; - - WorldPacket data; - if(apply) - data.Initialize(SMSG_MOVE_FEATHER_FALL, 8+4); - else - data.Initialize(SMSG_MOVE_NORMAL_FALL, 8+4); - data.append(m_target->GetPackGUID()); - data << (uint32)0; - m_target->SendMessageToSet(&data,true); -} - -void Aura::HandleAuraHover(bool apply, bool Real) -{ - // only at real add/remove aura - if(!Real) - return; - - WorldPacket data; - if(apply) - data.Initialize(SMSG_MOVE_SET_HOVER, 8+4); - else - data.Initialize(SMSG_MOVE_UNSET_HOVER, 8+4); - data.append(m_target->GetPackGUID()); - data << uint32(0); - m_target->SendMessageToSet(&data,true); -} - -void Aura::HandleWaterBreathing(bool apply, bool Real) -{ - if(apply) - m_target->waterbreath = true; - else if(m_target->GetAurasByType(SPELL_AURA_WATER_BREATHING).empty()) - { - m_target->waterbreath = false; - - // update for enable timer in case not moving target - if(m_target->GetTypeId()==TYPEID_PLAYER && m_target->IsInWorld()) - { - ((Player*)m_target)->UpdateUnderwaterState(m_target->GetMap(),m_target->GetPositionX(),m_target->GetPositionY(),m_target->GetPositionZ()); - ((Player*)m_target)->HandleDrowning(); - } - } -} - -void Aura::HandleAuraModShapeshift(bool apply, bool Real) -{ - if(!Real) - return; - - uint32 modelid = 0; - Powers PowerType = POWER_MANA; - ShapeshiftForm form = ShapeshiftForm(m_modifier.m_miscvalue); - switch(form) - { - case FORM_CAT: - if(Player::TeamForRace(m_target->getRace())==ALLIANCE) - modelid = 892; - else - modelid = 8571; - PowerType = POWER_ENERGY; - break; - case FORM_TRAVEL: - modelid = 632; - break; - case FORM_AQUA: - if(Player::TeamForRace(m_target->getRace())==ALLIANCE) - modelid = 2428; - else - modelid = 2428; - break; - case FORM_BEAR: - if(Player::TeamForRace(m_target->getRace())==ALLIANCE) - modelid = 2281; - else - modelid = 2289; - PowerType = POWER_RAGE; - break; - case FORM_GHOUL: - if(Player::TeamForRace(m_target->getRace())==ALLIANCE) - modelid = 10045; - break; - case FORM_DIREBEAR: - if(Player::TeamForRace(m_target->getRace())==ALLIANCE) - modelid = 2281; - else - modelid = 2289; - PowerType = POWER_RAGE; - break; - case FORM_CREATUREBEAR: - modelid = 902; - break; - case FORM_GHOSTWOLF: - modelid = 4613; - break; - case FORM_FLIGHT: - if(Player::TeamForRace(m_target->getRace())==ALLIANCE) - modelid = 20857; - else - modelid = 20872; - break; - case FORM_MOONKIN: - if(Player::TeamForRace(m_target->getRace())==ALLIANCE) - modelid = 15374; - else - modelid = 15375; - break; - case FORM_FLIGHT_EPIC: - if(Player::TeamForRace(m_target->getRace())==ALLIANCE) - modelid = 21243; - else - modelid = 21244; - break; - case FORM_AMBIENT: - case FORM_SHADOW: - case FORM_STEALTH: - break; - case FORM_TREE: - modelid = 864; - break; - case FORM_BATTLESTANCE: - case FORM_BERSERKERSTANCE: - case FORM_DEFENSIVESTANCE: - PowerType = POWER_RAGE; - break; - case FORM_SPIRITOFREDEMPTION: - modelid = 16031; - break; - default: - sLog.outError("Auras: Unknown Shapeshift Type: %u", m_modifier.m_miscvalue); - } - - // remove polymorph before changing display id to keep new display id - switch ( form ) - { - case FORM_CAT: - case FORM_TREE: - case FORM_TRAVEL: - case FORM_AQUA: - case FORM_BEAR: - case FORM_DIREBEAR: - case FORM_FLIGHT_EPIC: - case FORM_FLIGHT: - case FORM_MOONKIN: - // remove movement affects - m_target->RemoveSpellsCausingAura(SPELL_AURA_MOD_ROOT); - m_target->RemoveSpellsCausingAura(SPELL_AURA_MOD_DECREASE_SPEED); - - // and polymorphic affects - if(m_target->IsPolymorphed()) - m_target->RemoveAurasDueToSpell(m_target->getTransForm()); - break; - default: - break; - } - - if(apply) - { - // remove other shapeshift before applying a new one - if(m_target->m_ShapeShiftFormSpellId) - { - m_target->RemoveAurasDueToSpell(m_target->m_ShapeShiftFormSpellId,this); - } - - m_target->SetByteValue(UNIT_FIELD_BYTES_2, 3, form); - - if(modelid > 0) - { - m_target->SetDisplayId(modelid); - } - - if(PowerType != POWER_MANA) - { - // reset power to default values only at power change - if(m_target->getPowerType()!=PowerType) - m_target->setPowerType(PowerType); - - switch(form) - { - case FORM_CAT: - case FORM_BEAR: - case FORM_DIREBEAR: - { - // get furor proc chance - uint32 FurorChance = 0; - Unit::AuraList const& mDummy = m_target->GetAurasByType(SPELL_AURA_DUMMY); - for(Unit::AuraList::const_iterator i = mDummy.begin(); i != mDummy.end(); ++i) - { - if ((*i)->GetSpellProto()->SpellIconID == 238) - { - FurorChance = (*i)->GetModifier()->m_amount; - break; - } - } - - if (m_modifier.m_miscvalue == FORM_CAT) - { - m_target->SetPower(POWER_ENERGY,0); - if(urand(1,100) <= FurorChance) - { - m_target->CastSpell(m_target,17099,true,NULL,this); - } - } - else - { - m_target->SetPower(POWER_RAGE,0); - if(urand(1,100) <= FurorChance) - { - m_target->CastSpell(m_target,17057,true,NULL,this); - } - } - break; - } - case FORM_BATTLESTANCE: - case FORM_DEFENSIVESTANCE: - case FORM_BERSERKERSTANCE: - { - uint32 Rage_val = 0; - // Stance mastery + Tactical mastery (both passive, and last have aura only in defense stance, but need apply at any stance switch) - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - PlayerSpellMap const& sp_list = ((Player *)m_target)->GetSpellMap(); - for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) - { - if(itr->second->state == PLAYERSPELL_REMOVED) continue; - SpellEntry const *spellInfo = sSpellStore.LookupEntry(itr->first); - if (spellInfo && spellInfo->SpellFamilyName == SPELLFAMILY_WARRIOR && spellInfo->SpellIconID == 139) - Rage_val += m_target->CalculateSpellDamage(spellInfo,0,spellInfo->EffectBasePoints[0],m_target) * 10; - } - } - - if (m_target->GetPower(POWER_RAGE) > Rage_val) - m_target->SetPower(POWER_RAGE,Rage_val); - break; - } - default: - break; - } - } - - m_target->m_ShapeShiftFormSpellId = GetId(); - m_target->m_form = form; - } - else - { - m_target->SetDisplayId(m_target->GetNativeDisplayId()); - m_target->SetByteValue(UNIT_FIELD_BYTES_2, 3, FORM_NONE); - if(m_target->getClass() == CLASS_DRUID) - m_target->setPowerType(POWER_MANA); - m_target->m_ShapeShiftFormSpellId = 0; - m_target->m_form = FORM_NONE; - - switch(form) - { - // Nordrassil Harness - bonus - case FORM_BEAR: - case FORM_DIREBEAR: - case FORM_CAT: - { - if(Aura* dummy = m_target->GetDummyAura(37315) ) - m_target->CastSpell(m_target,37316,true,NULL,dummy); - break; - } - // Nordrassil Regalia - bonus - case FORM_MOONKIN: - { - if(Aura* dummy = m_target->GetDummyAura(37324) ) - m_target->CastSpell(m_target,37325,true,NULL,dummy); - break; - } - } - } - - // adding/removing linked auras - // add/remove the shapeshift aura's boosts - HandleShapeshiftBoosts(apply); - - if(m_target->GetTypeId()==TYPEID_PLAYER) - ((Player*)m_target)->InitDataForForm(); -} - -void Aura::HandleAuraTransform(bool apply, bool Real) -{ - if (apply) - { - // special case (spell specific functionality) - if(m_modifier.m_miscvalue==0) - { - // player applied only - if(m_target->GetTypeId()!=TYPEID_PLAYER) - return; - - switch(GetId()) - { - // Orb of Deception - case 16739: - { - uint32 orb_model = m_target->GetNativeDisplayId(); - switch(orb_model) - { - // Troll Female - case 1479: m_target->SetDisplayId(10134); break; - // Troll Male - case 1478: m_target->SetDisplayId(10135); break; - // Tauren Male - case 59: m_target->SetDisplayId(10136); break; - // Human Male - case 49: m_target->SetDisplayId(10137); break; - // Human Female - case 50: m_target->SetDisplayId(10138); break; - // Orc Male - case 51: m_target->SetDisplayId(10139); break; - // Orc Female - case 52: m_target->SetDisplayId(10140); break; - // Dwarf Male - case 53: m_target->SetDisplayId(10141); break; - // Dwarf Female - case 54: m_target->SetDisplayId(10142); break; - // NightElf Male - case 55: m_target->SetDisplayId(10143); break; - // NightElf Female - case 56: m_target->SetDisplayId(10144); break; - // Undead Female - case 58: m_target->SetDisplayId(10145); break; - // Undead Male - case 57: m_target->SetDisplayId(10146); break; - // Tauren Female - case 60: m_target->SetDisplayId(10147); break; - // Gnome Male - case 1563: m_target->SetDisplayId(10148); break; - // Gnome Female - case 1564: m_target->SetDisplayId(10149); break; - // BloodElf Female - case 15475: m_target->SetDisplayId(17830); break; - // BloodElf Male - case 15476: m_target->SetDisplayId(17829); break; - // Dranei Female - case 16126: m_target->SetDisplayId(17828); break; - // Dranei Male - case 16125: m_target->SetDisplayId(17827); break; - default: break; - } - break; - } - // Murloc costume - case 42365: m_target->SetDisplayId(21723); break; - default: break; - } - } - else - { - CreatureInfo const * ci = objmgr.GetCreatureTemplate(m_modifier.m_miscvalue); - if(!ci) - { - //pig pink ^_^ - m_target->SetDisplayId(16358); - sLog.outError("Auras: unknown creature id = %d (only need its modelid) Form Spell Aura Transform in Spell ID = %d", m_modifier.m_miscvalue, GetId()); - } - else - { - // Will use the default model here - m_target->SetDisplayId(ci->DisplayID_A); - - // Dragonmaw Illusion (set mount model also) - if(GetId()==42016 && m_target->GetMountID() && !m_target->GetAurasByType(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED).empty()) - m_target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID,16314); - } - m_target->setTransForm(GetId()); - } - - // polymorph case - if( Real && m_target->GetTypeId() == TYPEID_PLAYER && m_target->IsPolymorphed()) - { - // for players, start regeneration after 1s (in polymorph fast regeneration case) - // only if caster is Player (after patch 2.4.2) - if(IS_PLAYER_GUID(GetCasterGUID()) ) - ((Player*)m_target)->setRegenTimer(1000); - - //dismount polymorphed target (after patch 2.4.2) - if (m_target->IsMounted()) - m_target->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); - } - } - else - { - Unit::AuraList const& otherTransforms = m_target->GetAurasByType(SPELL_AURA_TRANSFORM); - if(otherTransforms.empty()) - { - m_target->SetDisplayId(m_target->GetNativeDisplayId()); - m_target->setTransForm(0); - } - else - { - // look for other transform auras - Aura* handledAura = *otherTransforms.begin(); - for(Unit::AuraList::const_iterator i = otherTransforms.begin();i != otherTransforms.end(); ++i) - { - // negative auras are prefered - if(!IsPositiveSpell((*i)->GetSpellProto()->Id)) - { - handledAura = *i; - break; - } - } - handledAura->ApplyModifier(true); - } - - // Dragonmaw Illusion (restore mount model) - if(GetId()==42016 && m_target->GetMountID()==16314) - { - if(!m_target->GetAurasByType(SPELL_AURA_MOUNTED).empty()) - { - uint32 cr_id = m_target->GetAurasByType(SPELL_AURA_MOUNTED).front()->GetModifier()->m_miscvalue; - if(CreatureInfo const* ci = objmgr.GetCreatureTemplate(cr_id)) - { - uint32 team = 0; - if (m_target->GetTypeId()==TYPEID_PLAYER) - team = ((Player*)m_target)->GetTeam(); - - uint32 display_id = objmgr.ChooseDisplayId(team,ci); - CreatureModelInfo const *minfo = objmgr.GetCreatureModelRandomGender(display_id); - if (minfo) - display_id = minfo->modelid; - - m_target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID,display_id); - } - } - } - } -} - -void Aura::HandleForceReaction(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - if(!Real) - return; - - Player* player = (Player*)m_target; - - uint32 faction_id = m_modifier.m_miscvalue; - uint32 faction_rank = m_modifier.m_amount; - - if(apply) - player->m_forcedReactions[faction_id] = ReputationRank(faction_rank); - else - player->m_forcedReactions.erase(faction_id); - - WorldPacket data; - data.Initialize(SMSG_SET_FORCED_REACTIONS, 4+player->m_forcedReactions.size()*(4+4)); - data << uint32(player->m_forcedReactions.size()); - for(ForcedReactions::const_iterator itr = player->m_forcedReactions.begin(); itr != player->m_forcedReactions.end(); ++itr) - { - data << uint32(itr->first); // faction_id (Faction.dbc) - data << uint32(itr->second); // reputation rank - } - player->SendDirectMessage(&data); -} - -void Aura::HandleAuraModSkill(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - uint32 prot=GetSpellProto()->EffectMiscValue[m_effIndex]; - int32 points = GetModifier()->m_amount; - - ((Player*)m_target)->ModifySkillBonus(prot,(apply ? points: -points),m_modifier.m_auraname==SPELL_AURA_MOD_SKILL_TALENT); - if(prot == SKILL_DEFENSE) - ((Player*)m_target)->UpdateDefenseBonusesMod(); -} - -void Aura::HandleChannelDeathItem(bool apply, bool Real) -{ - if(Real && !apply) - { - Unit* caster = GetCaster(); - Unit* victim = GetTarget(); - if(!caster || caster->GetTypeId() != TYPEID_PLAYER || !victim || m_removeMode!=AURA_REMOVE_BY_DEATH) - return; - - SpellEntry const *spellInfo = GetSpellProto(); - if(spellInfo->EffectItemType[m_effIndex] == 0) - return; - - // Soul Shard only from non-grey units - if( spellInfo->EffectItemType[m_effIndex] == 6265 && - (victim->getLevel() <= MaNGOS::XP::GetGrayLevel(caster->getLevel()) || - victim->GetTypeId()==TYPEID_UNIT && !((Player*)caster)->isAllowedToLoot((Creature*)victim)) ) - return; - ItemPosCountVec dest; - uint8 msg = ((Player*)caster)->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, spellInfo->EffectItemType[m_effIndex], 1 ); - if( msg != EQUIP_ERR_OK ) - { - ((Player*)caster)->SendEquipError( msg, NULL, NULL ); - return; - } - - Item* newitem = ((Player*)caster)->StoreNewItem(dest, spellInfo->EffectItemType[m_effIndex], true); - ((Player*)caster)->SendNewItem(newitem, 1, true, false); - } -} - -void Aura::HandleBindSight(bool apply, bool Real) -{ - Unit* caster = GetCaster(); - if(!caster || caster->GetTypeId() != TYPEID_PLAYER) - return; - - caster->SetUInt64Value(PLAYER_FARSIGHT,apply ? m_target->GetGUID() : 0); -} - -void Aura::HandleFarSight(bool apply, bool Real) -{ - Unit* caster = GetCaster(); - if(!caster || caster->GetTypeId() != TYPEID_PLAYER) - return; - - caster->SetUInt64Value(PLAYER_FARSIGHT,apply ? m_modifier.m_miscvalue : 0); -} - -void Aura::HandleAuraTrackCreatures(bool apply, bool Real) -{ - if(m_target->GetTypeId()!=TYPEID_PLAYER) - return; - - if(apply) - m_target->RemoveNoStackAurasDueToAura(this); - m_target->SetUInt32Value(PLAYER_TRACK_CREATURES, apply ? ((uint32)1)<<(m_modifier.m_miscvalue-1) : 0 ); -} - -void Aura::HandleAuraTrackResources(bool apply, bool Real) -{ - if(m_target->GetTypeId()!=TYPEID_PLAYER) - return; - - if(apply) - m_target->RemoveNoStackAurasDueToAura(this); - m_target->SetUInt32Value(PLAYER_TRACK_RESOURCES, apply ? ((uint32)1)<<(m_modifier.m_miscvalue-1): 0 ); -} - -void Aura::HandleAuraTrackStealthed(bool apply, bool Real) -{ - if(m_target->GetTypeId()!=TYPEID_PLAYER) - return; - - if(apply) - m_target->RemoveNoStackAurasDueToAura(this); - - m_target->ApplyModFlag(PLAYER_FIELD_BYTES,PLAYER_FIELD_BYTE_TRACK_STEALTHED,apply); -} - -void Aura::HandleAuraModScale(bool apply, bool Real) -{ - m_target->ApplyPercentModFloatValue(OBJECT_FIELD_SCALE_X,m_modifier.m_amount,apply); -} - -void Aura::HandleModPossess(bool apply, bool Real) -{ - if(!Real) - return; - - if(m_target->getLevel() > m_modifier.m_amount) - return; - - // not possess yourself - if(GetCasterGUID() == m_target->GetGUID()) - return; - - Unit* caster = GetCaster(); - if(!caster) - return; - - if( apply ) - { - m_target->SetCharmerGUID(GetCasterGUID()); - m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,caster->getFaction()); - caster->SetCharm(m_target); - - m_target->CombatStop(); - m_target->DeleteThreatList(); - if(m_target->GetTypeId() == TYPEID_UNIT) - { - m_target->StopMoving(); - m_target->GetMotionMaster()->Clear(); - m_target->GetMotionMaster()->MoveIdle(); - CharmInfo *charmInfo = ((Creature*)m_target)->InitCharmInfo(m_target); - charmInfo->InitPossessCreateSpells(); - } - - if(caster->GetTypeId() == TYPEID_PLAYER) - { - ((Player*)caster)->PossessSpellInitialize(); - } - } - else - { - m_target->SetCharmerGUID(0); - - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - ((Player*)m_target)->setFactionForRace(m_target->getRace()); - } - else if(m_target->GetTypeId() == TYPEID_UNIT) - { - CreatureInfo const *cinfo = ((Creature*)m_target)->GetCreatureInfo(); - m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,cinfo->faction_A); - } - - caster->SetCharm(0); - - if(caster->GetTypeId() == TYPEID_PLAYER) - { - WorldPacket data(SMSG_PET_SPELLS, 8); - data << uint64(0); - ((Player*)caster)->GetSession()->SendPacket(&data); - } - if(m_target->GetTypeId() == TYPEID_UNIT) - { - ((Creature*)m_target)->AIM_Initialize(); - - if (((Creature*)m_target)->AI()) - ((Creature*)m_target)->AI()->AttackStart(caster); - } - } - if(caster->GetTypeId() == TYPEID_PLAYER) - caster->SetUInt64Value(PLAYER_FARSIGHT,apply ? m_target->GetGUID() : 0); -} - -void Aura::HandleModPossessPet(bool apply, bool Real) -{ - if(!Real) - return; - - Unit* caster = GetCaster(); - if(!caster || caster->GetTypeId() != TYPEID_PLAYER) - return; - if(caster->GetPet() != m_target) - return; - - if(apply) - { - caster->SetUInt64Value(PLAYER_FARSIGHT, m_target->GetGUID()); - m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNKNOWN5); - } - else - { - caster->SetUInt64Value(PLAYER_FARSIGHT, 0); - m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNKNOWN5); - } -} - -void Aura::HandleModCharm(bool apply, bool Real) -{ - if(!Real) - return; - - // not charm yourself - if(GetCasterGUID() == m_target->GetGUID()) - return; - - Unit* caster = GetCaster(); - if(!caster) - return; - - if(int32(m_target->getLevel()) <= m_modifier.m_amount) - { - if( apply ) - { - m_target->SetCharmerGUID(GetCasterGUID()); - m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,caster->getFaction()); - m_target->CastStop(m_target==caster ? GetId() : 0); - caster->SetCharm(m_target); - - m_target->CombatStop(); - m_target->DeleteThreatList(); - - if(m_target->GetTypeId() == TYPEID_UNIT) - { - ((Creature*)m_target)->AIM_Initialize(); - CharmInfo *charmInfo = ((Creature*)m_target)->InitCharmInfo(m_target); - charmInfo->InitCharmCreateSpells(); - charmInfo->SetReactState( REACT_DEFENSIVE ); - - if(caster->GetTypeId() == TYPEID_PLAYER && caster->getClass() == CLASS_WARLOCK) - { - CreatureInfo const *cinfo = ((Creature*)m_target)->GetCreatureInfo(); - if(cinfo && cinfo->type == CREATURE_TYPE_DEMON) - { - //to prevent client crash - m_target->SetFlag(UNIT_FIELD_BYTES_0, 2048); - //just to enable stat window - charmInfo->SetPetNumber(objmgr.GeneratePetNumber(), true); - //if charmed two demons the same session, the 2nd gets the 1st one's name - m_target->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, time(NULL)); - } - } - } - - if(caster->GetTypeId() == TYPEID_PLAYER) - { - ((Player*)caster)->CharmSpellInitialize(); - } - } - else - { - m_target->SetCharmerGUID(0); - - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - ((Player*)m_target)->setFactionForRace(m_target->getRace()); - } - else - { - CreatureInfo const *cinfo = ((Creature*)m_target)->GetCreatureInfo(); - - // restore faction - if(((Creature*)m_target)->isPet()) - { - if(Unit* owner = m_target->GetOwner()) - m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,owner->getFaction()); - else if(cinfo) - m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,cinfo->faction_A); - } - else if(cinfo) // normal creature - m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,cinfo->faction_A); - - // restore UNIT_FIELD_BYTES_0 - if(cinfo && caster->GetTypeId() == TYPEID_PLAYER && caster->getClass() == CLASS_WARLOCK && cinfo->type == CREATURE_TYPE_DEMON) - { - CreatureDataAddon const *cainfo = ((Creature*)m_target)->GetCreatureAddon(); - if(cainfo && cainfo->bytes0 != 0) - m_target->SetUInt32Value(UNIT_FIELD_BYTES_0, cainfo->bytes0); - else - m_target->RemoveFlag(UNIT_FIELD_BYTES_0, 2048); - - if(m_target->GetCharmInfo()) - m_target->GetCharmInfo()->SetPetNumber(0, true); - else - sLog.outError("Aura::HandleModCharm: target="I64FMTD" with typeid=%d has a charm aura but no charm info!", m_target->GetGUID(), m_target->GetTypeId()); - } - } - - caster->SetCharm(0); - - if(caster->GetTypeId() == TYPEID_PLAYER) - { - WorldPacket data(SMSG_PET_SPELLS, 8); - data << uint64(0); - ((Player*)caster)->GetSession()->SendPacket(&data); - } - if(m_target->GetTypeId() == TYPEID_UNIT) - { - ((Creature*)m_target)->AIM_Initialize(); - if (((Creature*)m_target)->AI()) - ((Creature*)m_target)->AI()->AttackStart(caster); - } - } - } -} - -void Aura::HandleModConfuse(bool apply, bool Real) -{ - if(!Real) - return; - - m_target->SetConfused(apply, GetCasterGUID(), GetId()); -} - -void Aura::HandleModFear(bool apply, bool Real) -{ - if (!Real) - return; - - m_target->SetFeared(apply, GetCasterGUID(), GetId()); -} - -void Aura::HandleFeignDeath(bool apply, bool Real) -{ - if(!Real) - return; - - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - if( apply ) - { - /* - WorldPacket data(SMSG_FEIGN_DEATH_RESISTED, 9); - data<GetGUID(); - data<SendMessageToSet(&data,true); - */ - // blizz like 2.0.x - m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNKNOWN6); - // blizz like 2.0.x - m_target->SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FEIGN_DEATH); - // blizz like 2.0.x - m_target->SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_DEAD); - - m_target->addUnitState(UNIT_STAT_DIED); - m_target->CombatStop(); - - // prevent interrupt message - if(m_caster_guid==m_target->GetGUID() && m_target->m_currentSpells[CURRENT_GENERIC_SPELL]) - m_target->m_currentSpells[CURRENT_GENERIC_SPELL]->finish(); - m_target->InterruptNonMeleeSpells(true); - m_target->getHostilRefManager().deleteReferences(); - } - else - { - /* - WorldPacket data(SMSG_FEIGN_DEATH_RESISTED, 9); - data<GetGUID(); - data<SendMessageToSet(&data,true); - */ - // blizz like 2.0.x - m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNKNOWN6); - // blizz like 2.0.x - m_target->RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FEIGN_DEATH); - // blizz like 2.0.x - m_target->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_DEAD); - - m_target->clearUnitState(UNIT_STAT_DIED); - } -} - -void Aura::HandleAuraModDisarm(bool apply, bool Real) -{ - if(!Real) - return; - - if(!apply && m_target->HasAuraType(SPELL_AURA_MOD_DISARM)) - return; - - // not sure for it's correctness - if(apply) - m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED); - else - m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED); - - // only at real add/remove aura - if (m_target->GetTypeId() != TYPEID_PLAYER) - return; - - // main-hand attack speed already set to special value for feral form already and don't must chnage and reset at remove. - if (((Player *)m_target)->IsInFeralForm()) - return; - - if (apply) - m_target->SetAttackTime(BASE_ATTACK,BASE_ATTACK_TIME); - else - ((Player *)m_target)->SetRegularAttackTime(); - - m_target->UpdateDamagePhysical(BASE_ATTACK); -} - -void Aura::HandleAuraModStun(bool apply, bool Real) -{ - if(!Real) - return; - - if (apply) - { - m_target->addUnitState(UNIT_STAT_STUNDED); - m_target->SetUInt64Value(UNIT_FIELD_TARGET, 0); - - m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_ROTATE); - m_target->CastStop(m_target->GetGUID() == GetCasterGUID() ? GetId() : 0); - - // Creature specific - if(m_target->GetTypeId() != TYPEID_PLAYER) - ((Creature*)m_target)->StopMoving(); - else - m_target->SetUnitMovementFlags(0); //Clear movement flags - - WorldPacket data(SMSG_FORCE_MOVE_ROOT, 8); - - data.append(m_target->GetPackGUID()); - data << uint32(0); - m_target->SendMessageToSet(&data,true); - } - else - { - // Real remove called after current aura remove from lists, check if other similar auras active - if(m_target->HasAuraType(SPELL_AURA_MOD_STUN)) - return; - - m_target->clearUnitState(UNIT_STAT_STUNDED); - m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_ROTATE); - - if(!m_target->hasUnitState(UNIT_STAT_ROOT)) // prevent allow move if have also root effect - { - if(m_target->getVictim() && m_target->isAlive()) - m_target->SetUInt64Value(UNIT_FIELD_TARGET,m_target->getVictim()->GetGUID() ); - - WorldPacket data(SMSG_FORCE_MOVE_UNROOT, 8+4); - data.append(m_target->GetPackGUID()); - data << uint32(0); - m_target->SendMessageToSet(&data,true); - } - - // Wyvern Sting - if (GetSpellProto()->SpellFamilyName == SPELLFAMILY_HUNTER && GetSpellProto()->SpellIconID == 1721) - { - Unit* caster = GetCaster(); - if( !caster || caster->GetTypeId()!=TYPEID_PLAYER ) - return; - - uint32 spell_id = 0; - - switch(GetId()) - { - case 19386: spell_id = 24131; break; - case 24132: spell_id = 24134; break; - case 24133: spell_id = 24135; break; - case 27068: spell_id = 27069; break; - default: - sLog.outError("Spell selection called for unexpected original spell %u, new spell for this spell family?",GetId()); - return; - } - - SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id); - - if(!spellInfo) - return; - - caster->CastSpell(m_target,spellInfo,true,NULL,this); - return; - } - } -} - -void Aura::HandleModStealth(bool apply, bool Real) -{ - if(apply) - { - // drop flag at stealth in bg - if(Real && m_target->GetTypeId()==TYPEID_PLAYER && ((Player*)m_target)->InBattleGround()) - if(BattleGround *bg = ((Player*)m_target)->GetBattleGround()) - bg->EventPlayerDroppedFlag((Player*)m_target); - - // only at real aura add - if(Real) - { - m_target->SetByteValue(UNIT_FIELD_BYTES_1, 2, 0x02); - if(m_target->GetTypeId()==TYPEID_PLAYER) - m_target->SetFlag(PLAYER_FIELD_BYTES2, 0x2000); - - // apply only if not in GM invisibility (and overwrite invisibility state) - if(m_target->GetVisibility()!=VISIBILITY_OFF) - { - m_target->SetVisibility(VISIBILITY_GROUP_NO_DETECT); - m_target->SetVisibility(VISIBILITY_GROUP_STEALTH); - } - - // for RACE_NIGHTELF stealth - if(m_target->GetTypeId()==TYPEID_PLAYER && GetId()==20580) - m_target->CastSpell(m_target, 21009, true, NULL, this); - } - } - else - { - // only at real aura remove - if(Real) - { - // for RACE_NIGHTELF stealth - if(m_target->GetTypeId()==TYPEID_PLAYER && GetId()==20580) - m_target->RemoveAurasDueToSpell(21009); - - // if last SPELL_AURA_MOD_STEALTH and no GM invisibility - if(!m_target->HasAuraType(SPELL_AURA_MOD_STEALTH) && m_target->GetVisibility()!=VISIBILITY_OFF) - { - m_target->SetByteValue(UNIT_FIELD_BYTES_1, 2, 0x00); - if(m_target->GetTypeId()==TYPEID_PLAYER) - m_target->RemoveFlag(PLAYER_FIELD_BYTES2, 0x2000); - - // restore invisibility if any - if(m_target->HasAuraType(SPELL_AURA_MOD_INVISIBILITY)) - { - m_target->SetVisibility(VISIBILITY_GROUP_NO_DETECT); - m_target->SetVisibility(VISIBILITY_GROUP_INVISIBILITY); - } - else - m_target->SetVisibility(VISIBILITY_ON); - } - } - } - - // Master of Subtlety - Unit::AuraList const& mDummyAuras = m_target->GetAurasByType(SPELL_AURA_DUMMY); - for(Unit::AuraList::const_iterator i = mDummyAuras.begin();i != mDummyAuras.end(); ++i) - { - if ((*i)->GetSpellProto()->SpellIconID == 2114) - { - if (apply) - { - int32 bp = (*i)->GetModifier()->m_amount; - m_target->CastCustomSpell(m_target,31665,&bp,NULL,NULL,true); - } - else - m_target->CastSpell(m_target,31666,true); - break; - } - } -} - -void Aura::HandleInvisibility(bool apply, bool Real) -{ - if(apply) - { - m_target->m_invisibilityMask |= (1 << m_modifier.m_miscvalue); - - if(Real && m_target->GetTypeId()==TYPEID_PLAYER) - { - // apply glow vision - m_target->SetFlag(PLAYER_FIELD_BYTES2,PLAYER_FIELD_BYTE2_INVISIBILITY_GLOW); - - // drop flag at invisible in bg - if(((Player*)m_target)->InBattleGround()) - if(BattleGround *bg = ((Player*)m_target)->GetBattleGround()) - bg->EventPlayerDroppedFlag((Player*)m_target); - } - - // apply only if not in GM invisibility and not stealth - if(m_target->GetVisibility()==VISIBILITY_ON) - { - // Aura not added yet but visibility code expect temporary add aura - m_target->SetVisibility(VISIBILITY_GROUP_NO_DETECT); - m_target->SetVisibility(VISIBILITY_GROUP_INVISIBILITY); - } - } - else - { - // recalculate value at modifier remove (current aura already removed) - m_target->m_invisibilityMask = 0; - Unit::AuraList const& auras = m_target->GetAurasByType(SPELL_AURA_MOD_INVISIBILITY); - for(Unit::AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) - m_target->m_invisibilityMask |= (1 << m_modifier.m_miscvalue); - - // only at real aura remove and if not have different invisibility auras. - if(Real && m_target->m_invisibilityMask==0) - { - // remove glow vision - if(m_target->GetTypeId() == TYPEID_PLAYER) - m_target->RemoveFlag(PLAYER_FIELD_BYTES2,PLAYER_FIELD_BYTE2_INVISIBILITY_GLOW); - - // apply only if not in GM invisibility & not stealthed while invisible - if(m_target->GetVisibility()!=VISIBILITY_OFF) - { - // if have stealth aura then already have stealth visibility - if(!m_target->HasAuraType(SPELL_AURA_MOD_STEALTH)) - m_target->SetVisibility(VISIBILITY_ON); - } - } - } -} - -void Aura::HandleInvisibilityDetect(bool apply, bool Real) -{ - if(apply) - { - m_target->m_detectInvisibilityMask |= (1 << m_modifier.m_miscvalue); - } - else - { - // recalculate value at modifier remove (current aura already removed) - m_target->m_detectInvisibilityMask = 0; - Unit::AuraList const& auras = m_target->GetAurasByType(SPELL_AURA_MOD_INVISIBILITY_DETECTION); - for(Unit::AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) - m_target->m_detectInvisibilityMask |= (1 << m_modifier.m_miscvalue); - } - if(Real && m_target->GetTypeId()==TYPEID_PLAYER) - ObjectAccessor::UpdateVisibilityForPlayer((Player*)m_target); -} - -void Aura::HandleAuraModRoot(bool apply, bool Real) -{ - // only at real add/remove aura - if(!Real) - return; - - uint32 apply_stat = UNIT_STAT_ROOT; - if (apply) - { - m_target->addUnitState(UNIT_STAT_ROOT); - m_target->SetUInt64Value (UNIT_FIELD_TARGET, 0); - // probably wrong - m_target->SetFlag(UNIT_FIELD_FLAGS,(apply_stat<<16)); - - //Save last orientation - if( m_target->getVictim() ) - m_target->SetOrientation(m_target->GetAngle(m_target->getVictim())); - - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - WorldPacket data(SMSG_FORCE_MOVE_ROOT, 10); - data.append(m_target->GetPackGUID()); - data << (uint32)2; - m_target->SendMessageToSet(&data,true); - - //Clear unit movement flags - m_target->SetUnitMovementFlags(0); - } - else - ((Creature *)m_target)->StopMoving(); - } - else - { - // Real remove called after current aura remove from lists, check if other similar auras active - if(m_target->HasAuraType(SPELL_AURA_MOD_ROOT)) - return; - - m_target->clearUnitState(UNIT_STAT_ROOT); - // probably wrong - m_target->RemoveFlag(UNIT_FIELD_FLAGS,(apply_stat<<16)); - - if(!m_target->hasUnitState(UNIT_STAT_STUNDED)) // prevent allow move if have also stun effect - { - if(m_target->getVictim() && m_target->isAlive()) - m_target->SetUInt64Value (UNIT_FIELD_TARGET,m_target->getVictim()->GetGUID() ); - - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - WorldPacket data(SMSG_FORCE_MOVE_UNROOT, 10); - data.append(m_target->GetPackGUID()); - data << (uint32)2; - m_target->SendMessageToSet(&data,true); - } - } - } -} - -void Aura::HandleAuraModSilence(bool apply, bool Real) -{ - // only at real add/remove aura - if(!Real) - return; - - if(apply) - { - m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED); - // Stop cast only spells vs PreventionType == SPELL_PREVENTION_TYPE_SILENCE - for (uint32 i = CURRENT_MELEE_SPELL; i < CURRENT_MAX_SPELL;i++) - { - Spell* currentSpell = m_target->m_currentSpells[i]; - if (currentSpell && currentSpell->m_spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE) - { - uint32 state = currentSpell->getState(); - // Stop spells on prepere or casting state - if ( state == SPELL_STATE_PREPARING || state == SPELL_STATE_CASTING ) - { - currentSpell->cancel(); - currentSpell->SetDeletable(true); - m_target->m_currentSpells[i] = NULL; - } - } - } - - switch (GetId()) - { - // Arcane Torrent (Energy) - case 25046: - { - Unit * caster = GetCaster(); - if (!caster) - return; - - // Search Mana Tap auras on caster - int32 energy = 0; - Unit::AuraList const& m_dummyAuras = caster->GetAurasByType(SPELL_AURA_DUMMY); - for(Unit::AuraList::const_iterator i = m_dummyAuras.begin(); i != m_dummyAuras.end(); ++i) - if ((*i)->GetId() == 28734) - ++energy; - if (energy) - { - energy *= 10; - caster->CastCustomSpell(caster, 25048, &energy, NULL, NULL, true); - caster->RemoveAurasDueToSpell(28734); - } - } - } - } - else - { - // Real remove called after current aura remove from lists, check if other similar auras active - if(m_target->HasAuraType(SPELL_AURA_MOD_SILENCE)) - return; - - m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED); - } -} - -void Aura::HandleModThreat(bool apply, bool Real) -{ - // only at real add/remove aura - if(!Real) - return; - - if(!m_target->isAlive()) - return; - - Unit* caster = GetCaster(); - - if(!caster || !caster->isAlive()) - return; - - int level_diff = 0; - int multiplier = 0; - switch (GetId()) - { - // Arcane Shroud - case 26400: - level_diff = m_target->getLevel() - 60; - multiplier = 2; - break; - // The Eye of Diminution - case 28862: - level_diff = m_target->getLevel() - 60; - multiplier = 1; - break; - } - if (level_diff > 0) - m_modifier.m_amount += multiplier * level_diff; - - for(int8 x=0;x < MAX_SPELL_SCHOOL;x++) - { - if(m_modifier.m_miscvalue & int32(1<GetTypeId() == TYPEID_PLAYER) - ApplyPercentModFloatVar(m_target->m_threatModifier[x], m_positive ? m_modifier.m_amount : -m_modifier.m_amount, apply); - } - } -} - -void Aura::HandleAuraModTotalThreat(bool apply, bool Real) -{ - // only at real add/remove aura - if(!Real) - return; - - if(!m_target->isAlive() || m_target->GetTypeId()!= TYPEID_PLAYER) - return; - - Unit* caster = GetCaster(); - - if(!caster || !caster->isAlive()) - return; - - float threatMod = 0.0f; - if(apply) - threatMod = float(m_modifier.m_amount); - else - threatMod = float(-m_modifier.m_amount); - - m_target->getHostilRefManager().threatAssist(caster, threatMod); -} - -void Aura::HandleModTaunt(bool apply, bool Real) -{ - // only at real add/remove aura - if(!Real) - return; - - if(!m_target->isAlive() || !m_target->CanHaveThreatList()) - return; - - Unit* caster = GetCaster(); - - if(!caster || !caster->isAlive() || caster->GetTypeId() != TYPEID_PLAYER) - return; - - if(apply) - { - m_target->TauntApply(caster); - } - else - { - // When taunt aura fades out, mob will switch to previous target if current has less than 1.1 * secondthreat - m_target->TauntFadeOut(caster); - } -} - -/*********************************************************/ -/*** MODIFY SPEED ***/ -/*********************************************************/ -void Aura::HandleAuraModIncreaseSpeed(bool apply, bool Real) -{ - // all applied/removed only at real aura add/remove - if(!Real) - return; - - m_target->UpdateSpeed(MOVE_RUN, true); -} - -void Aura::HandleAuraModIncreaseMountedSpeed(bool apply, bool Real) -{ - // all applied/removed only at real aura add/remove - if(!Real) - return; - - m_target->UpdateSpeed(MOVE_RUN, true); -} - -void Aura::HandleAuraModIncreaseFlightSpeed(bool apply, bool Real) -{ - // all applied/removed only at real aura add/remove - if(!Real) - return; - - // Enable Fly mode for flying mounts - if (m_modifier.m_auraname == SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED) - { - WorldPacket data; - if(apply) - data.Initialize(SMSG_MOVE_SET_CAN_FLY, 12); - else - data.Initialize(SMSG_MOVE_UNSET_CAN_FLY, 12); - data.append(m_target->GetPackGUID()); - data << uint32(0); // unknown - m_target->SendMessageToSet(&data, true); - - //Players on flying mounts must be immune to polymorph - if (m_target->GetTypeId()==TYPEID_PLAYER) - m_target->ApplySpellImmune(GetId(),IMMUNITY_MECHANIC,MECHANIC_POLYMORPH,apply); - - // Dragonmaw Illusion (overwrite mount model, mounted aura already applied) - if( apply && m_target->HasAura(42016,0) && m_target->GetMountID()) - m_target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID,16314); - } - - m_target->UpdateSpeed(MOVE_FLY, true); -} - -void Aura::HandleAuraModIncreaseSwimSpeed(bool apply, bool Real) -{ - // all applied/removed only at real aura add/remove - if(!Real) - return; - - m_target->UpdateSpeed(MOVE_SWIM, true); -} - -void Aura::HandleAuraModDecreaseSpeed(bool apply, bool Real) -{ - // all applied/removed only at real aura add/remove - if(!Real) - return; - - m_target->UpdateSpeed(MOVE_RUN, true); - m_target->UpdateSpeed(MOVE_SWIM, true); - m_target->UpdateSpeed(MOVE_FLY, true); -} - -void Aura::HandleAuraModUseNormalSpeed(bool apply, bool Real) -{ - // all applied/removed only at real aura add/remove - if(!Real) - return; - - m_target->UpdateSpeed(MOVE_RUN, true); - m_target->UpdateSpeed(MOVE_SWIM, true); - m_target->UpdateSpeed(MOVE_FLY, true); -} - -/*********************************************************/ -/*** IMMUNITY ***/ -/*********************************************************/ - -void Aura::HandleModMechanicImmunity(bool apply, bool Real) -{ - uint32 mechanic = 1 << m_modifier.m_miscvalue; - - //immune movement impairment and loss of control - if(GetId()==42292) - mechanic=IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK; - - if(apply && GetSpellProto()->AttributesEx & SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY) - { - Unit::AuraMap& Auras = m_target->GetAuras(); - for(Unit::AuraMap::iterator iter = Auras.begin(), next; iter != Auras.end(); iter = next) - { - next = iter; - ++next; - SpellEntry const *spell = iter->second->GetSpellProto(); - if (!( spell->Attributes & SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY) // spells unaffected by invulnerability - && !iter->second->IsPositive() // only remove negative spells - && spell->Id != GetId()) - { - //check for mechanic mask - if(GetSpellMechanicMask(spell, iter->second->GetEffIndex()) & mechanic) - { - m_target->RemoveAurasDueToSpell(spell->Id); - if(Auras.empty()) - break; - else - next = Auras.begin(); - } - } - } - } - - m_target->ApplySpellImmune(GetId(),IMMUNITY_MECHANIC,m_modifier.m_miscvalue,apply); - - // special cases - switch(m_modifier.m_miscvalue) - { - case MECHANIC_INVULNERABILITY: - m_target->ModifyAuraState(AURA_STATE_FORBEARANCE,apply); - break; - case MECHANIC_SHIELD: - m_target->ModifyAuraState(AURA_STATE_WEAKENED_SOUL,apply); - break; - } - - // Bestial Wrath - if ( GetSpellProto()->SpellFamilyName == SPELLFAMILY_HUNTER && GetSpellProto()->SpellIconID == 1680) - { - // The Beast Within cast on owner if talent present - if ( Unit* owner = m_target->GetOwner() ) - { - // Search talent - Unit::AuraList const& m_dummyAuras = owner->GetAurasByType(SPELL_AURA_DUMMY); - for(Unit::AuraList::const_iterator i = m_dummyAuras.begin(); i != m_dummyAuras.end(); ++i) - { - if ( (*i)->GetSpellProto()->SpellIconID == 2229 ) - { - if (apply) - owner->CastSpell(owner, 34471, true, 0, this); - else - owner->RemoveAurasDueToSpell(34471); - break; - } - } - } - } - - // The Beast Within and Bestial Wrath - immunity - if(GetId() == 19574 || GetId() == 34471) - { - if(apply) - { - m_target->CastSpell(m_target,24395,true); - m_target->CastSpell(m_target,24396,true); - m_target->CastSpell(m_target,24397,true); - m_target->CastSpell(m_target,26592,true); - } - else - { - m_target->RemoveAurasDueToSpell(24395); - m_target->RemoveAurasDueToSpell(24396); - m_target->RemoveAurasDueToSpell(24397); - m_target->RemoveAurasDueToSpell(26592); - } - } -} - -void Aura::HandleAuraModEffectImmunity(bool apply, bool Real) -{ - if(!apply) - { - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - if(((Player*)m_target)->InBattleGround()) - { - BattleGround *bg = ((Player*)m_target)->GetBattleGround(); - if(bg) - { - switch(bg->GetTypeID()) - { - case BATTLEGROUND_AV: - { - break; - } - case BATTLEGROUND_WS: - { - // Warsong Flag, horde // Silverwing Flag, alliance - if(GetId() == 23333 || GetId() == 23335) - bg->EventPlayerDroppedFlag(((Player*)m_target)); - break; - } - case BATTLEGROUND_AB: - { - break; - } - case BATTLEGROUND_EY: - { - if(GetId() == 34976) - bg->EventPlayerDroppedFlag(((Player*)m_target)); - break; - } - } - } - } - } - } - - m_target->ApplySpellImmune(GetId(),IMMUNITY_EFFECT,m_modifier.m_miscvalue,apply); -} - -void Aura::HandleAuraModStateImmunity(bool apply, bool Real) -{ - if(apply && Real && GetSpellProto()->AttributesEx & SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY) - { - Unit::AuraList const& auraList = m_target->GetAurasByType(AuraType(m_modifier.m_miscvalue)); - for(Unit::AuraList::const_iterator itr = auraList.begin(); itr != auraList.end();) - { - if (auraList.front() != this) // skip itself aura (it already added) - { - m_target->RemoveAurasDueToSpell(auraList.front()->GetId()); - itr = auraList.begin(); - } - else - ++itr; - } - } - - m_target->ApplySpellImmune(GetId(),IMMUNITY_STATE,m_modifier.m_miscvalue,apply); -} - -void Aura::HandleAuraModSchoolImmunity(bool apply, bool Real) -{ - m_target->ApplySpellImmune(GetId(),IMMUNITY_SCHOOL,m_modifier.m_miscvalue,apply); - - if(Real && apply && GetSpellProto()->AttributesEx & SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY) - { - if(IsPositiveSpell(GetId())) //Only positive immunity removes auras - { - uint32 school_mask = m_modifier.m_miscvalue; - Unit::AuraMap& Auras = m_target->GetAuras(); - for(Unit::AuraMap::iterator iter = Auras.begin(), next; iter != Auras.end(); iter = next) - { - next = iter; - ++next; - SpellEntry const *spell = iter->second->GetSpellProto(); - if((GetSpellSchoolMask(spell) & school_mask)//Check for school mask - && !( spell->Attributes & SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY) //Spells unaffected by invulnerability - && !iter->second->IsPositive() //Don't remove positive spells - && spell->Id != GetId() ) //Don't remove self - { - m_target->RemoveAurasDueToSpell(spell->Id); - if(Auras.empty()) - break; - else - next = Auras.begin(); - } - } - } - } - if( Real && GetSpellProto()->Mechanic == MECHANIC_BANISH ) - { - if( apply ) - m_target->addUnitState(UNIT_STAT_ISOLATED); - else - m_target->clearUnitState(UNIT_STAT_ISOLATED); - } -} - -void Aura::HandleAuraModDmgImmunity(bool apply, bool Real) -{ - m_target->ApplySpellImmune(GetId(),IMMUNITY_DAMAGE,m_modifier.m_miscvalue,apply); -} - -void Aura::HandleAuraModDispelImmunity(bool apply, bool Real) -{ - // all applied/removed only at real aura add/remove - if(!Real) - return; - - m_target->ApplySpellDispelImmunity(m_spellProto, DispelType(m_modifier.m_miscvalue), apply); -} - -void Aura::HandleAuraProcTriggerSpell(bool apply, bool Real) -{ - if(!Real) - return; - - if(apply) - { - // some spell have charges by functionality not have its in spell data - switch (GetId()) - { - case 28200: // Ascendance (Talisman of Ascendance trinket) - m_procCharges = 6; - UpdateAuraCharges(); - break; - default: break; - } - } -} - -void Aura::HandleAuraModStalked(bool apply, bool Real) -{ - // used by spells: Hunter's Mark, Mind Vision, Syndicate Tracker (MURP) DND - if(apply) - m_target->SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TRACK_UNIT); - else - m_target->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TRACK_UNIT); -} - -/*********************************************************/ -/*** PERIODIC ***/ -/*********************************************************/ - -void Aura::HandlePeriodicTriggerSpell(bool apply, bool Real) -{ - if (m_periodicTimer <= 0) - m_periodicTimer += m_modifier.periodictime; - - m_isPeriodic = apply; - m_isTrigger = apply; - - // Curse of the Plaguebringer - if (!apply && m_spellProto->Id == 29213 && m_removeMode!=AURA_REMOVE_BY_DISPEL) - { - // Cast Wrath of the Plaguebringer if not dispelled - m_target->CastSpell(m_target, 29214, true, 0, this); - } -} - -void Aura::HandlePeriodicEnergize(bool apply, bool Real) -{ - if (m_periodicTimer <= 0) - m_periodicTimer += m_modifier.periodictime; - - m_isPeriodic = apply; -} - -void Aura::HandlePeriodicHeal(bool apply, bool Real) -{ - if (m_periodicTimer <= 0) - m_periodicTimer += m_modifier.periodictime; - - m_isPeriodic = apply; - - // only at real apply - if (Real && apply && GetSpellProto()->Mechanic == MECHANIC_BANDAGE) - { - // provided m_target as original caster to prevent apply aura caster selection for this negative buff - m_target->CastSpell(m_target,11196,true,NULL,this,m_target->GetGUID()); - } - - // For prevent double apply bonuses - bool loading = (m_target->GetTypeId() == TYPEID_PLAYER && ((Player*)m_target)->GetSession()->PlayerLoading()); - - if(!loading && apply) - { - switch (m_spellProto->SpellFamilyName) - { - case SPELLFAMILY_DRUID: - { - // Rejuvenation - if(m_spellProto->SpellFamilyFlags & 0x0000000000000010LL) - { - if(Unit* caster = GetCaster()) - { - Unit::AuraList const& classScripts = caster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); - for(Unit::AuraList::const_iterator k = classScripts.begin(); k != classScripts.end(); ++k) - { - int32 tickcount = GetSpellDuration(m_spellProto) / m_spellProto->EffectAmplitude[m_effIndex]; - switch((*k)->GetModifier()->m_miscvalue) - { - case 4953: // Increased Rejuvenation Healing - Harold's Rejuvenating Broach Aura - case 4415: // Increased Rejuvenation Healing - Idol of Rejuvenation Aura - { - m_modifier.m_amount += (*k)->GetModifier()->m_amount / tickcount; - break; - } - } - } - } - } - } - } - } -} - -void Aura::HandlePeriodicDamage(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - if (m_periodicTimer <= 0) - m_periodicTimer += m_modifier.periodictime; - - m_isPeriodic = apply; - - // For prevent double apply bonuses - bool loading = (m_target->GetTypeId() == TYPEID_PLAYER && ((Player*)m_target)->GetSession()->PlayerLoading()); - - Unit *caster = GetCaster(); - - switch (m_spellProto->SpellFamilyName) - { - case SPELLFAMILY_GENERIC: - { - // Pounce Bleed - if ( m_spellProto->SpellIconID == 147 && m_spellProto->SpellVisual == 0 ) - { - // $AP*0.18/6 bonus per tick - if (apply && !loading && caster) - m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * 3 / 100); - return; - } - break; - } - case SPELLFAMILY_WARRIOR: - { - // Rend - if (m_spellProto->SpellFamilyFlags & 0x0000000000000020LL) - { - // 0.00743*(($MWB+$mwb)/2+$AP/14*$MWS) bonus per tick - if (apply && !loading && caster) - { - float ap = caster->GetTotalAttackPowerValue(BASE_ATTACK); - int32 mws = caster->GetAttackTime(BASE_ATTACK); - float mwb_min = caster->GetWeaponDamageRange(BASE_ATTACK,MINDAMAGE); - float mwb_max = caster->GetWeaponDamageRange(BASE_ATTACK,MAXDAMAGE); - // WARNING! in 3.0 multipler 0.00743f change to 0.6 - m_modifier.m_amount+=int32(((mwb_min+mwb_max)/2+ap*mws/14000)*0.00743f); - } - return; - } - break; - } - case SPELLFAMILY_DRUID: - { - // Rake - if (m_spellProto->SpellFamilyFlags & 0x0000000000001000LL) - { - // $AP*0.06/3 bonus per tick - if (apply && !loading && caster) - m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * 2 / 100); - return; - } - // Lacerate - if (m_spellProto->SpellFamilyFlags & 0x000000010000000000LL) - { - // $AP*0.05/5 bonus per tick - if (apply && !loading && caster) - m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) / 100); - return; - } - // Rip - if (m_spellProto->SpellFamilyFlags & 0x000000000000800000LL) - { - // $AP * min(0.06*$cp, 0.24)/6 [Yes, there is no difference, wheather 4 or 5 CPs are being used] - if (apply && !loading && caster && caster->GetTypeId() == TYPEID_PLAYER) - { - uint8 cp = ((Player*)caster)->GetComboPoints(); - - // Idol of Feral Shadows. Cant be handled as SpellMod in SpellAura:Dummy due its dependency from CPs - Unit::AuraList const& dummyAuras = caster->GetAurasByType(SPELL_AURA_DUMMY); - for(Unit::AuraList::const_iterator itr = dummyAuras.begin(); itr != dummyAuras.end(); ++itr) - { - if((*itr)->GetId()==34241) - { - m_modifier.m_amount += cp * (*itr)->GetModifier()->m_amount; - break; - } - } - - if (cp > 4) cp = 4; - m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * cp / 100); - } - return; - } - break; - } - case SPELLFAMILY_ROGUE: - { - // Deadly poison aura state - if((m_spellProto->SpellFamilyFlags & 0x10000) && m_spellProto->SpellVisual==5100) - { - if(apply) - m_target->ModifyAuraState(AURA_STATE_DEADLY_POISON,true); - else - { - // current aura already removed, search present of another - bool found = false; - Unit::AuraList const& auras = m_target->GetAurasByType(SPELL_AURA_PERIODIC_DAMAGE); - for(Unit::AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) - { - SpellEntry const* itr_spell = (*itr)->GetSpellProto(); - if(itr_spell && itr_spell->SpellFamilyName==SPELLFAMILY_ROGUE && (itr_spell->SpellFamilyFlags & 0x10000) && itr_spell->SpellVisual==5100) - { - found = true; - break; - } - } - // this has been last deadly poison aura - if(!found) - m_target->ModifyAuraState(AURA_STATE_DEADLY_POISON,false); - } - return; - } - // Rupture - if (m_spellProto->SpellFamilyFlags & 0x000000000000100000LL) - { - // Dmg/tick = $AP*min(0.01*$cp, 0.03) [Like Rip: only the first three CP inrease the contribution from AP] - if (apply && !loading && caster && caster->GetTypeId() == TYPEID_PLAYER) - { - uint8 cp = ((Player*)caster)->GetComboPoints(); - if (cp > 3) cp = 3; - m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * cp / 100); - } - return; - } - // Garrote - if (m_spellProto->SpellFamilyFlags & 0x000000000000000100LL) - { - // $AP*0.18/6 bonus per tick - if (apply && !loading && caster) - m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * 3 / 100); - return; - } - break; - } - case SPELLFAMILY_HUNTER: - { - // Serpent Sting - if (m_spellProto->SpellFamilyFlags & 0x0000000000004000LL) - { - // $RAP*0.1/5 bonus per tick - if (apply && !loading && caster) - m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(RANGED_ATTACK) * 10 / 500); - return; - } - // Immolation Trap - if (m_spellProto->SpellFamilyFlags & 0x0000000000000004LL && m_spellProto->SpellIconID == 678) - { - // $RAP*0.1/5 bonus per tick - if (apply && !loading && caster) - m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(RANGED_ATTACK) * 10 / 500); - return; - } - break; - } - case SPELLFAMILY_PALADIN: - { - // Consecration - if (m_spellProto->SpellFamilyFlags & 0x0000000000000020LL) - { - if (apply && !loading) - { - if(Unit* caster = GetCaster()) - { - Unit::AuraList const& classScripts = caster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); - for(Unit::AuraList::const_iterator k = classScripts.begin(); k != classScripts.end(); ++k) - { - int32 tickcount = GetSpellDuration(m_spellProto) / m_spellProto->EffectAmplitude[m_effIndex]; - switch((*k)->GetModifier()->m_miscvalue) - { - case 5147: // Improved Consecration - Libram of the Eternal Rest - { - m_modifier.m_amount += (*k)->GetModifier()->m_amount / tickcount; - break; - } - } - } - } - } - return; - } - break; - } - default: - break; - } -} - -void Aura::HandlePeriodicDamagePCT(bool apply, bool Real) -{ - if (m_periodicTimer <= 0) - m_periodicTimer += m_modifier.periodictime; - - m_isPeriodic = apply; -} - -void Aura::HandlePeriodicLeech(bool apply, bool Real) -{ - if (m_periodicTimer <= 0) - m_periodicTimer += m_modifier.periodictime; - - m_isPeriodic = apply; -} - -void Aura::HandlePeriodicManaLeech(bool apply, bool Real) -{ - if (m_periodicTimer <= 0) - m_periodicTimer += m_modifier.periodictime; - - m_isPeriodic = apply; -} - -/*********************************************************/ -/*** MODIFY STATS ***/ -/*********************************************************/ - -/********************************/ -/*** RESISTANCE ***/ -/********************************/ - -void Aura::HandleAuraModResistanceExclusive(bool apply, bool Real) -{ - for(int8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL;x++) - { - if(m_modifier.m_miscvalue & int32(1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), BASE_VALUE, float(m_modifier.m_amount), apply); - if(m_target->GetTypeId() == TYPEID_PLAYER) - m_target->ApplyResistanceBuffModsMod(SpellSchools(x),m_positive,m_modifier.m_amount, apply); - } - } -} - -void Aura::HandleAuraModResistance(bool apply, bool Real) -{ - for(int8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL;x++) - { - if(m_modifier.m_miscvalue & int32(1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), TOTAL_VALUE, float(m_modifier.m_amount), apply); - if(m_target->GetTypeId() == TYPEID_PLAYER || ((Creature*)m_target)->isPet()) - m_target->ApplyResistanceBuffModsMod(SpellSchools(x),m_positive,m_modifier.m_amount, apply); - } - } - - // Faerie Fire (druid versions) - if( m_spellProto->SpellIconID == 109 && - m_spellProto->SpellFamilyName == SPELLFAMILY_DRUID && - m_spellProto->SpellFamilyFlags & 0x0000000000000400LL ) - { - m_target->ModifyAuraState(AURA_STATE_FAERIE_FIRE,apply); - } -} - -void Aura::HandleAuraModBaseResistancePCT(bool apply, bool Real) -{ - // only players have base stats - if(m_target->GetTypeId() != TYPEID_PLAYER) - { - //pets only have base armor - if(((Creature*)m_target)->isPet() && (m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL)) - { - m_target->HandleStatModifier(UNIT_MOD_ARMOR, BASE_PCT, float(m_modifier.m_amount), apply); - } - } - else - { - for(int8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL;x++) - { - if(m_modifier.m_miscvalue & int32(1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), BASE_PCT, float(m_modifier.m_amount), apply); - } - } - } -} - -void Aura::HandleModResistancePercent(bool apply, bool Real) -{ - for(int8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; i++) - { - if(m_modifier.m_miscvalue & int32(1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), TOTAL_PCT, float(m_modifier.m_amount), apply); - if(m_target->GetTypeId() == TYPEID_PLAYER || ((Creature*)m_target)->isPet()) - { - m_target->ApplyResistanceBuffModsPercentMod(SpellSchools(i),true,m_modifier.m_amount, apply); - m_target->ApplyResistanceBuffModsPercentMod(SpellSchools(i),false,m_modifier.m_amount, apply); - } - } - } -} - -void Aura::HandleModBaseResistance(bool apply, bool Real) -{ - // only players have base stats - if(m_target->GetTypeId() != TYPEID_PLAYER) - { - //only pets have base stats - if(((Creature*)m_target)->isPet() && (m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL)) - m_target->HandleStatModifier(UNIT_MOD_ARMOR, TOTAL_VALUE, float(m_modifier.m_amount), apply); - } - else - { - for(int i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; i++) - if(m_modifier.m_miscvalue & (1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), TOTAL_VALUE, float(m_modifier.m_amount), apply); - } -} - -/********************************/ -/*** STAT ***/ -/********************************/ - -void Aura::HandleAuraModStat(bool apply, bool Real) -{ - if (m_modifier.m_miscvalue < -2 || m_modifier.m_miscvalue > 4) - { - sLog.outError("WARNING: Spell %u effect %u have unsupported misc value (%i) for SPELL_AURA_MOD_STAT ",GetId(),GetEffIndex(),m_modifier.m_miscvalue); - return; - } - - for(int32 i = STAT_STRENGTH; i < MAX_STATS; i++) - { - // -1 or -2 is all stats ( misc < -2 checked in function beginning ) - if (m_modifier.m_miscvalue < 0 || m_modifier.m_miscvalue == i) - { - //m_target->ApplyStatMod(Stats(i), m_modifier.m_amount,apply); - m_target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_VALUE, float(m_modifier.m_amount), apply); - if(m_target->GetTypeId() == TYPEID_PLAYER || ((Creature*)m_target)->isPet()) - m_target->ApplyStatBuffMod(Stats(i),m_modifier.m_amount,apply); - } - } -} - -void Aura::HandleModPercentStat(bool apply, bool Real) -{ - if (m_modifier.m_miscvalue < -1 || m_modifier.m_miscvalue > 4) - { - sLog.outError("WARNING: Misc Value for SPELL_AURA_MOD_PERCENT_STAT not valid"); - return; - } - - // only players have base stats - if (m_target->GetTypeId() != TYPEID_PLAYER) - return; - - for (int32 i = STAT_STRENGTH; i < MAX_STATS; ++i) - { - if(m_modifier.m_miscvalue == i || m_modifier.m_miscvalue == -1) - { - m_target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), BASE_PCT, float(m_modifier.m_amount), apply); - } - } -} - -void Aura::HandleModSpellDamagePercentFromStat(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - // Magic damage modifiers implemented in Unit::SpellDamageBonus - // This information for client side use only - // Recalculate bonus - ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); -} - -void Aura::HandleModSpellHealingPercentFromStat(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - // Recalculate bonus - ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); -} - -void Aura::HandleAuraModDispelResist(bool apply, bool Real) -{ - if(!Real || !apply) - return; - - if(GetId()==33206) - m_target->CastSpell(m_target,44416,true,NULL,this,GetCasterGUID()); -} - -void Aura::HandleModSpellDamagePercentFromAttackPower(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - // Magic damage modifiers implemented in Unit::SpellDamageBonus - // This information for client side use only - // Recalculate bonus - ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); -} - -void Aura::HandleModSpellHealingPercentFromAttackPower(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - // Recalculate bonus - ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); -} - -void Aura::HandleModHealingDone(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - // implemented in Unit::SpellHealingBonus - // this information is for client side only - ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); -} - -void Aura::HandleModTotalPercentStat(bool apply, bool Real) -{ - if (m_modifier.m_miscvalue < -1 || m_modifier.m_miscvalue > 4) - { - sLog.outError("WARNING: Misc Value for SPELL_AURA_MOD_PERCENT_STAT not valid"); - return; - } - - //save current and max HP before applying aura - uint32 curHPValue = m_target->GetHealth(); - uint32 maxHPValue = m_target->GetMaxHealth(); - - for (int32 i = STAT_STRENGTH; i < MAX_STATS; i++) - { - if(m_modifier.m_miscvalue == i || m_modifier.m_miscvalue == -1) - { - m_target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_PCT, float(m_modifier.m_amount), apply); - if(m_target->GetTypeId() == TYPEID_PLAYER || ((Creature*)m_target)->isPet()) - m_target->ApplyStatPercentBuffMod(Stats(i), m_modifier.m_amount, apply ); - } - } - - //recalculate current HP/MP after applying aura modifications (only for spells with 0x10 flag) - if ((m_modifier.m_miscvalue == STAT_STAMINA) && (maxHPValue > 0) && (m_spellProto->Attributes & 0x10)) - { - // newHP = (curHP / maxHP) * newMaxHP = (newMaxHP * curHP) / maxHP -> which is better because no int -> double -> int conversion is needed - uint32 newHPValue = (m_target->GetMaxHealth() * curHPValue) / maxHPValue; - m_target->SetHealth(newHPValue); - } -} - -void Aura::HandleAuraModResistenceOfStatPercent(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - if(m_modifier.m_miscvalue != SPELL_SCHOOL_MASK_NORMAL) - { - // support required adding replace UpdateArmor by loop by UpdateResistence at intelect update - // and include in UpdateResistence same code as in UpdateArmor for aura mod apply. - sLog.outError("Aura SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT(182) need adding support for non-armor resistences!"); - return; - } - - // Recalculate Armor - m_target->UpdateArmor(); -} - -/********************************/ -/*** HEAL & ENERGIZE ***/ -/********************************/ -void Aura::HandleAuraModTotalHealthPercentRegen(bool apply, bool Real) -{ - /* - Need additional checking for auras who reduce or increase healing, magic effect like Dumpen Magic, - so this aura not fully working. - */ - if(apply) - { - if(!m_target->isAlive()) - return; - - if((GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) && !m_target->IsSitState()) - m_target->SetStandState(PLAYER_STATE_SIT); - - if(m_periodicTimer <= 0) - { - m_periodicTimer += m_modifier.periodictime; - - if(m_target->GetHealth() < m_target->GetMaxHealth()) - { - // PeriodicTick can cast triggered spells with stats changes - PeriodicTick(); - } - } - } - - m_isPeriodic = apply; -} - -void Aura::HandleAuraModTotalManaPercentRegen(bool apply, bool Real) -{ - if((GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) && apply && !m_target->IsSitState()) - m_target->SetStandState(PLAYER_STATE_SIT); - if(apply) - { - if(m_modifier.periodictime == 0) - m_modifier.periodictime = 1000; - if(m_periodicTimer <= 0 && m_target->getPowerType() == POWER_MANA) - { - m_periodicTimer += m_modifier.periodictime; - - if(m_target->GetPower(POWER_MANA) < m_target->GetMaxPower(POWER_MANA)) - { - // PeriodicTick can cast triggered spells with stats changes - PeriodicTick(); - } - } - } - - m_isPeriodic = apply; -} - -void Aura::HandleModRegen(bool apply, bool Real) // eating -{ - if(apply) - { - if(!m_target->isAlive()) - return; - - if ((GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) && !m_target->IsSitState()) - m_target->SetStandState(PLAYER_STATE_SIT); - - if(m_periodicTimer <= 0) - { - m_periodicTimer += 5000; - int32 gain = m_target->ModifyHealth(m_modifier.m_amount); - Unit *caster = GetCaster(); - if (caster) - { - SpellEntry const *spellProto = GetSpellProto(); - if (spellProto) - m_target->getHostilRefManager().threatAssist(caster, float(gain) * 0.5f, spellProto); - } - } - } - - m_isPeriodic = apply; -} - -void Aura::HandleModPowerRegen(bool apply, bool Real) // drinking -{ - if ((GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) && apply && !m_target->IsSitState()) - m_target->SetStandState(PLAYER_STATE_SIT); - - if(apply && m_periodicTimer <= 0) - { - m_periodicTimer += 2000; - - Powers pt = m_target->getPowerType(); - if(int32(pt) != m_modifier.m_miscvalue) - return; - - if ( GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED ) - { - // eating anim - m_target->HandleEmoteCommand(EMOTE_ONESHOT_EAT); - } - else if( GetId() == 20577 ) - { - // cannibalize anim - m_target->HandleEmoteCommand(398); - } - - // Warrior talent, gain 1 rage every 3 seconds while in combat - if(pt == POWER_RAGE && m_target->isInCombat()) - { - m_target->ModifyPower(pt, m_modifier.m_amount*10/17); - m_periodicTimer += 1000; - } - } - m_isPeriodic = apply; - if (Real && m_target->GetTypeId() == TYPEID_PLAYER && m_modifier.m_miscvalue == POWER_MANA) - ((Player*)m_target)->UpdateManaRegen(); -} - -void Aura::HandleModPowerRegenPCT(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - if (m_target->GetTypeId() != TYPEID_PLAYER) - return; - - // Update manaregen value - if (m_modifier.m_miscvalue == POWER_MANA) - ((Player*)m_target)->UpdateManaRegen(); -} - -void Aura::HandleModManaRegen(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - if (m_target->GetTypeId() != TYPEID_PLAYER) - return; - - //Note: an increase in regen does NOT cause threat. - ((Player*)m_target)->UpdateManaRegen(); -} - -void Aura::HandleComprehendLanguage(bool apply, bool Real) -{ - if(apply) - m_target->SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_COMPREHEND_LANG); - else - m_target->RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_COMPREHEND_LANG); -} - -void Aura::HandleAuraModIncreaseHealth(bool apply, bool Real) -{ - // Special case with temporary increase max/current health - switch(GetId()) - { - case 12976: // Warrior Last Stand triggered spell - case 28726: // Nightmare Seed ( Nightmare Seed ) - case 34511: // Valor (Bulwark of Kings, Bulwark of the Ancient Kings) - case 44055: // Tremendous Fortitude (Battlemaster's Alacrity) - { - if(Real) - { - if(apply) - { - m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(m_modifier.m_amount), apply); - m_target->ModifyHealth(m_modifier.m_amount); - } - else - { - if (int32(m_target->GetHealth()) > m_modifier.m_amount) - m_target->ModifyHealth(-m_modifier.m_amount); - else - m_target->SetHealth(1); - m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(m_modifier.m_amount), apply); - } - } - return; - } - } - - // generic case - m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(m_modifier.m_amount), apply); -} - -void Aura::HandleAuraModIncreaseMaxHealth(bool apply, bool Real) -{ - uint32 oldhealth = m_target->GetHealth(); - double healthPercentage = (double)oldhealth / (double)m_target->GetMaxHealth(); - - m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(m_modifier.m_amount), apply); - - // refresh percentage - if(oldhealth > 0) - { - uint32 newhealth = uint32(ceil((double)m_target->GetMaxHealth() * healthPercentage)); - if(newhealth==0) - newhealth = 1; - - m_target->SetHealth(newhealth); - } -} - -void Aura::HandleAuraModIncreaseEnergy(bool apply, bool Real) -{ - Powers powerType = m_target->getPowerType(); - if(int32(powerType) != m_modifier.m_miscvalue) - return; - - m_target->HandleStatModifier(UnitMods(UNIT_MOD_POWER_START + powerType), TOTAL_VALUE, float(m_modifier.m_amount), apply); -} - -void Aura::HandleAuraModIncreaseEnergyPercent(bool apply, bool Real) -{ - Powers powerType = m_target->getPowerType(); - if(int32(powerType) != m_modifier.m_miscvalue) - return; - - m_target->HandleStatModifier(UnitMods(UNIT_MOD_POWER_START + powerType), TOTAL_PCT, float(m_modifier.m_amount), apply); -} - -void Aura::HandleAuraModIncreaseHealthPercent(bool apply, bool Real) -{ - //m_target->ApplyMaxHealthPercentMod(m_modifier.m_amount,apply); - m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_PCT, float(m_modifier.m_amount), apply); -} - -/********************************/ -/*** FIGHT ***/ -/********************************/ - -void Aura::HandleAuraModParryPercent(bool apply, bool Real) -{ - if(m_target->GetTypeId()!=TYPEID_PLAYER) - return; - - ((Player*)m_target)->UpdateParryPercentage(); -} - -void Aura::HandleAuraModDodgePercent(bool apply, bool Real) -{ - if(m_target->GetTypeId()!=TYPEID_PLAYER) - return; - - ((Player*)m_target)->UpdateDodgePercentage(); - //sLog.outError("BONUS DODGE CHANCE: + %f", float(m_modifier.m_amount)); -} - -void Aura::HandleAuraModBlockPercent(bool apply, bool Real) -{ - if(m_target->GetTypeId()!=TYPEID_PLAYER) - return; - - ((Player*)m_target)->UpdateBlockPercentage(); - //sLog.outError("BONUS BLOCK CHANCE: + %f", float(m_modifier.m_amount)); -} - -void Aura::HandleAuraModRegenInterrupt(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - if(m_target->GetTypeId()!=TYPEID_PLAYER) - return; - - ((Player*)m_target)->UpdateManaRegen(); -} - -void Aura::HandleAuraModCritPercent(bool apply, bool Real) -{ - if(m_target->GetTypeId()!=TYPEID_PLAYER) - return; - - // apply item specific bonuses for already equipped weapon - if(Real) - { - for(int i = 0; i < MAX_ATTACK; ++i) - if(Item* pItem = ((Player*)m_target)->GetWeaponForAttack(WeaponAttackType(i))) - ((Player*)m_target)->_ApplyWeaponDependentAuraCritMod(pItem,WeaponAttackType(i),this,apply); - } - - // mods must be applied base at equipped weapon class and subclass comparison - // with spell->EquippedItemClass and EquippedItemSubClassMask and EquippedItemInventoryTypeMask - // m_modifier.m_miscvalue comparison with item generated damage types - - if (GetSpellProto()->EquippedItemClass == -1) - { - ((Player*)m_target)->HandleBaseModValue(CRIT_PERCENTAGE, FLAT_MOD, float (m_modifier.m_amount), apply); - ((Player*)m_target)->HandleBaseModValue(OFFHAND_CRIT_PERCENTAGE, FLAT_MOD, float (m_modifier.m_amount), apply); - ((Player*)m_target)->HandleBaseModValue(RANGED_CRIT_PERCENTAGE, FLAT_MOD, float (m_modifier.m_amount), apply); - } - else - { - // done in Player::_ApplyWeaponDependentAuraMods - } -} - -void Aura::HandleModHitChance(bool apply, bool Real) -{ - m_target->m_modMeleeHitChance += apply ? m_modifier.m_amount : (-m_modifier.m_amount); - m_target->m_modRangedHitChance += apply ? m_modifier.m_amount : (-m_modifier.m_amount); -} - -void Aura::HandleModSpellHitChance(bool apply, bool Real) -{ - m_target->m_modSpellHitChance += apply ? m_modifier.m_amount: (-m_modifier.m_amount); -} - -void Aura::HandleModSpellCritChance(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - ((Player*)m_target)->UpdateAllSpellCritChances(); - } - else - { - m_target->m_baseSpellCritChance += apply ? m_modifier.m_amount:(-m_modifier.m_amount); - } -} - -void Aura::HandleModSpellCritChanceShool(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - for(int school = SPELL_SCHOOL_NORMAL; school < MAX_SPELL_SCHOOL; ++school) - if (m_modifier.m_miscvalue & (1<UpdateSpellCritChance(school); -} - -/********************************/ -/*** ATTACK SPEED ***/ -/********************************/ - -void Aura::HandleModCastingSpeed(bool apply, bool Real) -{ - m_target->ApplyCastTimePercentMod(m_modifier.m_amount,apply); -} - -void Aura::HandleModMeleeRangedSpeedPct(bool apply, bool Real) -{ - m_target->ApplyAttackTimePercentMod(BASE_ATTACK,m_modifier.m_amount,apply); - m_target->ApplyAttackTimePercentMod(OFF_ATTACK,m_modifier.m_amount,apply); - m_target->ApplyAttackTimePercentMod(RANGED_ATTACK, m_modifier.m_amount, apply); -} - -void Aura::HandleModCombatSpeedPct(bool apply, bool Real) -{ - m_target->ApplyCastTimePercentMod(m_modifier.m_amount,apply); - m_target->ApplyAttackTimePercentMod(BASE_ATTACK,m_modifier.m_amount,apply); - m_target->ApplyAttackTimePercentMod(OFF_ATTACK,m_modifier.m_amount,apply); - m_target->ApplyAttackTimePercentMod(RANGED_ATTACK, m_modifier.m_amount, apply); -} - -void Aura::HandleModAttackSpeed(bool apply, bool Real) -{ - if(!m_target->isAlive() ) - return; - - m_target->ApplyAttackTimePercentMod(BASE_ATTACK,m_modifier.m_amount,apply); -} - -void Aura::HandleHaste(bool apply, bool Real) -{ - m_target->ApplyAttackTimePercentMod(BASE_ATTACK, m_modifier.m_amount,apply); - m_target->ApplyAttackTimePercentMod(OFF_ATTACK, m_modifier.m_amount,apply); - m_target->ApplyAttackTimePercentMod(RANGED_ATTACK,m_modifier.m_amount,apply); -} - -void Aura::HandleAuraModRangedHaste(bool apply, bool Real) -{ - m_target->ApplyAttackTimePercentMod(RANGED_ATTACK, m_modifier.m_amount, apply); -} - -void Aura::HandleRangedAmmoHaste(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - m_target->ApplyAttackTimePercentMod(RANGED_ATTACK,m_modifier.m_amount, apply); -} - -/********************************/ -/*** ATTACK POWER ***/ -/********************************/ - -void Aura::HandleAuraModAttackPower(bool apply, bool Real) -{ - m_target->HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(m_modifier.m_amount), apply); -} - -void Aura::HandleAuraModRangedAttackPower(bool apply, bool Real) -{ - if((m_target->getClassMask() & CLASSMASK_WAND_USERS)!=0) - return; - - m_target->HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(m_modifier.m_amount), apply); -} - -void Aura::HandleAuraAttackPowerAttacker(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - Unit *caster = GetCaster(); - - if (!caster) - return; - - // Hunter's Mark - if (m_spellProto->SpellFamilyName == SPELLFAMILY_HUNTER && m_spellProto->SpellFamilyFlags & 0x0000000000000400LL) - { - // Check Improved Hunter's Mark bonus on caster - Unit::AuraList const& mOverrideClassScript = caster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); - for(Unit::AuraList::const_iterator i = mOverrideClassScript.begin(); i != mOverrideClassScript.end(); ++i) - { - Modifier* mod = (*i)->GetModifier(); - // mproved Hunter's Mark script from 5236 to 5240 - if (mod->m_miscvalue >= 5236 && mod->m_miscvalue <= 5240) - { - // Get amount of ranged bonus for this spell.. - int32 ranged_bonus = caster->CalculateSpellDamage(m_spellProto, 1, m_spellProto->EffectBasePoints[1], m_target); - // Set melee attack power bonus % from ranged depends from Improved mask aura - m_modifier.m_amount = mod->m_amount * ranged_bonus / 100; - m_currentBasePoints = m_modifier.m_amount; - break; - } - } - return; - } -} - -void Aura::HandleAuraModAttackPowerPercent(bool apply, bool Real) -{ - //UNIT_FIELD_ATTACK_POWER_MULTIPLIER = multiplier - 1 - m_target->HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT, float(m_modifier.m_amount), apply); -} - -void Aura::HandleAuraModRangedAttackPowerPercent(bool apply, bool Real) -{ - if((m_target->getClassMask() & CLASSMASK_WAND_USERS)!=0) - return; - - //UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER = multiplier - 1 - m_target->HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_PCT, float(m_modifier.m_amount), apply); -} - -void Aura::HandleAuraModRangedAttackPowerOfStatPercent(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - if(m_target->GetTypeId() == TYPEID_PLAYER && (m_target->getClassMask() & CLASSMASK_WAND_USERS)!=0) - return; - - if(m_modifier.m_miscvalue != STAT_INTELLECT) - { - // support required adding UpdateAttackPowerAndDamage calls at stat update - sLog.outError("Aura SPELL_AURA_MOD_RANGED_ATTACK_POWER_OF_STAT_PERCENT (212) need support non-intelect stats!"); - return; - } - - // Recalculate bonus - ((Player*)m_target)->UpdateAttackPowerAndDamage(true); -} - -/********************************/ -/*** DAMAGE BONUS ***/ -/********************************/ -void Aura::HandleModDamageDone(bool apply, bool Real) -{ - // apply item specific bonuses for already equipped weapon - if(Real && m_target->GetTypeId()==TYPEID_PLAYER) - { - for(int i = 0; i < MAX_ATTACK; ++i) - if(Item* pItem = ((Player*)m_target)->GetWeaponForAttack(WeaponAttackType(i))) - ((Player*)m_target)->_ApplyWeaponDependentAuraDamageMod(pItem,WeaponAttackType(i),this,apply); - } - - // m_modifier.m_miscvalue is bitmask of spell schools - // 1 ( 0-bit ) - normal school damage (SPELL_SCHOOL_MASK_NORMAL) - // 126 - full bitmask all magic damages (SPELL_SCHOOL_MASK_MAGIC) including wands - // 127 - full bitmask any damages - // - // mods must be applied base at equipped weapon class and subclass comparison - // with spell->EquippedItemClass and EquippedItemSubClassMask and EquippedItemInventoryTypeMask - // m_modifier.m_miscvalue comparison with item generated damage types - - if((m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL) != 0) - { - // apply generic physical damage bonuses including wand case - if (GetSpellProto()->EquippedItemClass == -1 || m_target->GetTypeId() != TYPEID_PLAYER) - { - m_target->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, float(m_modifier.m_amount), apply); - m_target->HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, float(m_modifier.m_amount), apply); - m_target->HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_VALUE, float(m_modifier.m_amount), apply); - } - else - { - // done in Player::_ApplyWeaponDependentAuraMods - } - - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - if(m_positive) - m_target->ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS,m_modifier.m_amount,apply); - else - m_target->ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG,m_modifier.m_amount,apply); - } - } - - // Skip non magic case for speedup - if((m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_MAGIC) == 0) - return; - - if( GetSpellProto()->EquippedItemClass != -1 || GetSpellProto()->EquippedItemInventoryTypeMask != 0 ) - { - // wand magic case (skip generic to all item spell bonuses) - // done in Player::_ApplyWeaponDependentAuraMods - - // Skip item specific requirements for not wand magic damage - return; - } - - // Magic damage modifiers implemented in Unit::SpellDamageBonus - // This information for client side use only - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - if(m_positive) - { - for(int i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; i++) - { - if((m_modifier.m_miscvalue & (1<ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS+i,m_modifier.m_amount,apply); - } - } - else - { - for(int i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; i++) - { - if((m_modifier.m_miscvalue & (1<ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG+i,m_modifier.m_amount,apply); - } - } - Pet* pet = m_target->GetPet(); - if(pet) - pet->UpdateAttackPowerAndDamage(); - } -} - -void Aura::HandleModDamagePercentDone(bool apply, bool Real) -{ - sLog.outDebug("AURA MOD DAMAGE type:%u negative:%u", m_modifier.m_miscvalue, m_positive ? 0 : 1); - - // apply item specific bonuses for already equipped weapon - if(Real && m_target->GetTypeId()==TYPEID_PLAYER) - { - for(int i = 0; i < MAX_ATTACK; ++i) - if(Item* pItem = ((Player*)m_target)->GetWeaponForAttack(WeaponAttackType(i))) - ((Player*)m_target)->_ApplyWeaponDependentAuraDamageMod(pItem,WeaponAttackType(i),this,apply); - } - - // m_modifier.m_miscvalue is bitmask of spell schools - // 1 ( 0-bit ) - normal school damage (SPELL_SCHOOL_MASK_NORMAL) - // 126 - full bitmask all magic damages (SPELL_SCHOOL_MASK_MAGIC) including wand - // 127 - full bitmask any damages - // - // mods must be applied base at equipped weapon class and subclass comparison - // with spell->EquippedItemClass and EquippedItemSubClassMask and EquippedItemInventoryTypeMask - // m_modifier.m_miscvalue comparison with item generated damage types - - if((m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL) != 0) - { - // apply generic physical damage bonuses including wand case - if (GetSpellProto()->EquippedItemClass == -1 || m_target->GetTypeId() != TYPEID_PLAYER) - { - m_target->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, float(m_modifier.m_amount), apply); - m_target->HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, float(m_modifier.m_amount), apply); - m_target->HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_PCT, float(m_modifier.m_amount), apply); - } - else - { - // done in Player::_ApplyWeaponDependentAuraMods - } - // For show in client - if(m_target->GetTypeId() == TYPEID_PLAYER) - m_target->ApplyModSignedFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT,m_modifier.m_amount/100.0f,apply); - } - - // Skip non magic case for speedup - if((m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_MAGIC) == 0) - return; - - if( GetSpellProto()->EquippedItemClass != -1 || GetSpellProto()->EquippedItemInventoryTypeMask != 0 ) - { - // wand magic case (skip generic to all item spell bonuses) - // done in Player::_ApplyWeaponDependentAuraMods - - // Skip item specific requirements for not wand magic damage - return; - } - - // Magic damage percent modifiers implemented in Unit::SpellDamageBonus - // Send info to client - if(m_target->GetTypeId() == TYPEID_PLAYER) - for(int i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) - m_target->ApplyModSignedFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT+i,m_modifier.m_amount/100.0f,apply); -} - -void Aura::HandleModOffhandDamagePercent(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - sLog.outDebug("AURA MOD OFFHAND DAMAGE"); - - m_target->HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, float(m_modifier.m_amount), apply); -} - -/********************************/ -/*** POWER COST ***/ -/********************************/ - -void Aura::HandleModPowerCostPCT(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - float amount = m_modifier.m_amount/100.0f; - for(int i = 0; i < MAX_SPELL_SCHOOL; ++i) - if(m_modifier.m_miscvalue & (1<ApplyModSignedFloatValue(UNIT_FIELD_POWER_COST_MULTIPLIER+i,amount,apply); -} - -void Aura::HandleModPowerCost(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - for(int i = 0; i < MAX_SPELL_SCHOOL; ++i) - if(m_modifier.m_miscvalue & (1<ApplyModInt32Value(UNIT_FIELD_POWER_COST_MODIFIER+i,m_modifier.m_amount,apply); -} - -/*********************************************************/ -/*** OTHERS ***/ -/*********************************************************/ - -void Aura::HandleShapeshiftBoosts(bool apply) -{ - uint32 spellId = 0; - uint32 spellId2 = 0; - uint32 HotWSpellId = 0; - - switch(GetModifier()->m_miscvalue) - { - case FORM_CAT: - spellId = 3025; - HotWSpellId = 24900; - break; - case FORM_TREE: - spellId = 5420; - break; - case FORM_TRAVEL: - spellId = 5419; - break; - case FORM_AQUA: - spellId = 5421; - break; - case FORM_BEAR: - spellId = 1178; - spellId2 = 21178; - HotWSpellId = 24899; - break; - case FORM_DIREBEAR: - spellId = 9635; - spellId2 = 21178; - HotWSpellId = 24899; - break; - case FORM_BATTLESTANCE: - spellId = 21156; - break; - case FORM_DEFENSIVESTANCE: - spellId = 7376; - break; - case FORM_BERSERKERSTANCE: - spellId = 7381; - break; - case FORM_MOONKIN: - spellId = 24905; - // aura from effect trigger spell - spellId2 = 24907; - break; - case FORM_FLIGHT: - spellId = 33948; - break; - case FORM_FLIGHT_EPIC: - spellId = 40122; - spellId2 = 40121; - break; - case FORM_SPIRITOFREDEMPTION: - spellId = 27792; - spellId2 = 27795; // must be second, this important at aura remove to prevent to early iterator invalidation. - break; - case FORM_GHOSTWOLF: - case FORM_AMBIENT: - case FORM_GHOUL: - case FORM_SHADOW: - case FORM_STEALTH: - case FORM_CREATURECAT: - case FORM_CREATUREBEAR: - spellId = 0; - break; - } - - uint32 form = GetModifier()->m_miscvalue-1; - - if(apply) - { - if (spellId) m_target->CastSpell(m_target, spellId, true, NULL, this ); - if (spellId2) m_target->CastSpell(m_target, spellId2, true, NULL, this); - - if(m_target->GetTypeId() == TYPEID_PLAYER) - { - const PlayerSpellMap& sp_list = ((Player *)m_target)->GetSpellMap(); - for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) - { - if(itr->second->state == PLAYERSPELL_REMOVED) continue; - if(itr->first==spellId || itr->first==spellId2) continue; - SpellEntry const *spellInfo = sSpellStore.LookupEntry(itr->first); - if (!spellInfo || !(spellInfo->Attributes & ((1<<6) | (1<<7)))) continue; - if (spellInfo->Stances & (1<CastSpell(m_target, itr->first, true, NULL, this); - } - //LotP - if (((Player*)m_target)->HasSpell(17007)) - { - SpellEntry const *spellInfo = sSpellStore.LookupEntry(24932); - if (spellInfo && spellInfo->Stances & (1<CastSpell(m_target, 24932, true, NULL, this); - } - // HotW - if (HotWSpellId) - { - Unit::AuraList const& mModTotalStatPct = m_target->GetAurasByType(SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE); - for(Unit::AuraList::const_iterator i = mModTotalStatPct.begin(); i != mModTotalStatPct.end(); ++i) - { - if ((*i)->GetSpellProto()->SpellIconID == 240 && (*i)->GetModifier()->m_miscvalue == 3) - { - int32 HotWMod = (*i)->GetModifier()->m_amount; - if(GetModifier()->m_miscvalue == FORM_CAT) - HotWMod /= 2; - - m_target->CastCustomSpell(m_target, HotWSpellId, &HotWMod, NULL, NULL, true, NULL, this); - break; - } - } - } - } - } - else - { - m_target->RemoveAurasDueToSpell(spellId); - m_target->RemoveAurasDueToSpell(spellId2); - - Unit::AuraMap& tAuras = m_target->GetAuras(); - for (Unit::AuraMap::iterator itr = tAuras.begin(); itr != tAuras.end();) - { - if (itr->second->IsRemovedOnShapeLost()) - { - m_target->RemoveAurasDueToSpell(itr->second->GetId()); - itr = tAuras.begin(); - } - else - { - ++itr; - } - } - } - - /*double healthPercentage = (double)m_target->GetHealth() / (double)m_target->GetMaxHealth(); - m_target->SetHealth(uint32(ceil((double)m_target->GetMaxHealth() * healthPercentage)));*/ -} - -void Aura::HandleAuraEmpathy(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_UNIT) - return; - - CreatureInfo const * ci = objmgr.GetCreatureTemplate(m_target->GetEntry()); - if(ci && ci->type == CREATURE_TYPE_BEAST) - { - m_target->ApplyModUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_SPECIALINFO, apply); - } -} - -void Aura::HandleAuraUntrackable(bool apply, bool Real) -{ - if(apply) - m_target->SetFlag(UNIT_FIELD_BYTES_1, PLAYER_STATE_FLAG_UNTRACKABLE); - else - m_target->RemoveFlag(UNIT_FIELD_BYTES_1, PLAYER_STATE_FLAG_UNTRACKABLE); -} - -void Aura::HandleAuraModPacify(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - if(apply) - m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED); - else - m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED); -} - -void Aura::HandleAuraModPacifyAndSilence(bool apply, bool Real) -{ - HandleAuraModPacify(apply,Real); - HandleAuraModSilence(apply,Real); -} - -void Aura::HandleAuraGhost(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - if(apply) - { - m_target->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST); - } - else - { - m_target->RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST); - } -} - -void Aura::HandleAuraAllowFlight(bool apply, bool Real) -{ - // all applied/removed only at real aura add/remove - if(!Real) - return; - - // allow fly - WorldPacket data; - if(apply) - data.Initialize(SMSG_MOVE_SET_CAN_FLY, 12); - else - data.Initialize(SMSG_MOVE_UNSET_CAN_FLY, 12); - data.append(m_target->GetPackGUID()); - data << uint32(0); // unk - m_target->SendMessageToSet(&data, true); -} - -void Aura::HandleModRating(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - for (uint32 rating = 0; rating < MAX_COMBAT_RATING; ++rating) - if (m_modifier.m_miscvalue & (1 << rating)) - ((Player*)m_target)->ApplyRatingMod(CombatRating(rating), m_modifier.m_amount, apply); -} - -void Aura::HandleForceMoveForward(bool apply, bool Real) -{ - if(!Real || m_target->GetTypeId() != TYPEID_PLAYER) - return; - if(apply) - m_target->SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FORCE_MOVE); - else - m_target->RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FORCE_MOVE); -} - -void Aura::HandleAuraModExpertise(bool apply, bool Real) -{ - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - ((Player*)m_target)->UpdateExpertise(BASE_ATTACK); - ((Player*)m_target)->UpdateExpertise(OFF_ATTACK); -} - -void Aura::HandleModTargetResistance(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - // applied to damage as HandleNoImmediateEffect in Unit::CalcAbsorbResist and Unit::CalcArmorReducedDamage - - // show armor penetration - if (m_target->GetTypeId() == TYPEID_PLAYER && (m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL)) - m_target->ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_PHYSICAL_RESISTANCE,m_modifier.m_amount, apply); - - // show as spell penetration only full spell penetration bonuses (all resistances except armor and holy - if (m_target->GetTypeId() == TYPEID_PLAYER && (m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_SPELL)==SPELL_SCHOOL_MASK_SPELL) - m_target->ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE,m_modifier.m_amount, apply); -} - -//HandleNoImmediateEffect auras implementation to support new stat system -void Aura::HandleAuraHealing(bool apply, bool Real) -{ - //m_target->HandleStatModifier(UNIT_MOD_HEALING, TOTAL_VALUE, float(m_modifier.m_amount), apply); -} - -void Aura::HandleAuraHealingPct(bool apply, bool Real) -{ - //m_target->HandleStatModifier(UNIT_MOD_HEALING, TOTAL_PCT, float(m_modifier.m_amount), apply); -} - -void Aura::HandleShieldBlockValue(bool apply, bool Real) -{ - BaseModType modType = FLAT_MOD; - if(m_modifier.m_auraname == SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT) - modType = PCT_MOD; - - if(m_target->GetTypeId() == TYPEID_PLAYER) - ((Player*)m_target)->HandleBaseModValue(SHIELD_BLOCK_VALUE, modType, float(m_modifier.m_amount), apply); -} - -void Aura::HandleAuraRetainComboPoints(bool apply, bool Real) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - if(m_target->GetTypeId() != TYPEID_PLAYER) - return; - - Player *target = (Player*)m_target; - - // combo points was added in SPELL_EFFECT_ADD_COMBO_POINTS handler - // remove only if aura expire by time (in case combo points amount change aura removed without combo points lost) - if( !apply && m_duration==0 && target->GetComboTarget()) - if(Unit* unit = ObjectAccessor::GetUnit(*m_target,target->GetComboTarget())) - target->AddComboPoints(unit, -m_modifier.m_amount); -} - -void Aura::HandleModUnattackable( bool Apply, bool Real ) -{ - if(Real && Apply) - m_target->CombatStop(); - - m_target->ApplyModFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE,Apply); -} - -void Aura::HandleSpiritOfRedemption( bool apply, bool Real ) -{ - // spells required only Real aura add/remove - if(!Real) - return; - - // prepare spirit state - if(apply) - { - if(m_target->GetTypeId()==TYPEID_PLAYER) - { - // disable breath/etc timers - ((Player*)m_target)->StopMirrorTimers(); - - // set stand state (expected in this form) - if(!m_target->IsStandState()) - m_target->SetStandState(PLAYER_STATE_NONE); - } - - m_target->SetHealth(1); - } - // die at aura end - else - m_target->DealDamage(m_target, m_target->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, GetSpellProto(), false); -} - -void Aura::CleanupTriggeredSpells() -{ - uint32 tSpellId = m_spellProto->EffectTriggerSpell[GetEffIndex()]; - if(!tSpellId) - return; - - SpellEntry const* tProto = sSpellStore.LookupEntry(tSpellId); - if(!tProto) - return; - - if(GetSpellDuration(tProto) != -1) - return; - - // needed for spell 43680, maybe others - // TODO: is there a spell flag, which can solve this in a more sophisticated way? - if(m_spellProto->EffectApplyAuraName[GetEffIndex()] == SPELL_AURA_PERIODIC_TRIGGER_SPELL && - GetSpellDuration(m_spellProto) == m_spellProto->EffectAmplitude[GetEffIndex()]) - return; - m_target->RemoveAurasDueToSpell(tSpellId); -} - -void Aura::HandleAuraPowerBurn(bool apply, bool Real) -{ - if (m_periodicTimer <= 0) - m_periodicTimer += m_modifier.periodictime; - - m_isPeriodic = apply; -} - -void Aura::HandleSchoolAbsorb(bool apply, bool Real) -{ - if(!Real) - return; - - // prevent double apply bonuses - if(apply && (m_target->GetTypeId()!=TYPEID_PLAYER || !((Player*)m_target)->GetSession()->PlayerLoading())) - { - if(Unit* caster = GetCaster()) - { - float DoneActualBenefit = 0.0f; - switch(m_spellProto->SpellFamilyName) - { - case SPELLFAMILY_PRIEST: - if(m_spellProto->SpellFamilyFlags == 0x1) //PW:S - { - //+30% from +healing bonus - DoneActualBenefit = caster->SpellBaseHealingBonus(GetSpellSchoolMask(m_spellProto)) * 0.3f; - break; - } - break; - case SPELLFAMILY_MAGE: - if(m_spellProto->SpellFamilyFlags == 0x80100 || m_spellProto->SpellFamilyFlags == 0x8 || m_spellProto->SpellFamilyFlags == 0x100000000LL) - { - //frost ward, fire ward, ice barrier - //+10% from +spd bonus - DoneActualBenefit = caster->SpellBaseDamageBonus(GetSpellSchoolMask(m_spellProto)) * 0.1f; - break; - } - break; - case SPELLFAMILY_WARLOCK: - if(m_spellProto->SpellFamilyFlags == 0x00) - { - //shadow ward - //+10% from +spd bonus - DoneActualBenefit = caster->SpellBaseDamageBonus(GetSpellSchoolMask(m_spellProto)) * 0.1f; - break; - } - break; - default: - break; - } - - DoneActualBenefit *= caster->CalculateLevelPenalty(GetSpellProto()); - - m_modifier.m_amount += (int32)DoneActualBenefit; - } - } -} - -void Aura::PeriodicTick() -{ - if(!m_target->isAlive()) - return; - - switch(m_modifier.m_auraname) - { - case SPELL_AURA_PERIODIC_DAMAGE: - case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: - { - Unit *pCaster = GetCaster(); - if(!pCaster) - return; - - if( GetSpellProto()->Effect[GetEffIndex()]==SPELL_EFFECT_PERSISTENT_AREA_AURA && - pCaster->SpellHitResult(m_target,GetSpellProto(),false)!=SPELL_MISS_NONE) - return; - - // Check for immune (not use charges) - if(m_target->IsImmunedToDamage(GetSpellSchoolMask(GetSpellProto()))) - return; - - // some auras remove at specific health level or more - if(m_modifier.m_auraname==SPELL_AURA_PERIODIC_DAMAGE) - { - switch(GetId()) - { - case 43093: case 31956: case 38801: - case 35321: case 38363: case 39215: - if(m_target->GetHealth() == m_target->GetMaxHealth() ) - { - m_target->RemoveAurasDueToSpell(GetId()); - return; - } - break; - case 38772: - { - uint32 percent = - GetEffIndex() < 2 && GetSpellProto()->Effect[GetEffIndex()]==SPELL_EFFECT_DUMMY ? - pCaster->CalculateSpellDamage(GetSpellProto(),GetEffIndex()+1,GetSpellProto()->EffectBasePoints[GetEffIndex()+1],m_target) : - 100; - if(m_target->GetHealth()*100 >= m_target->GetMaxHealth()*percent ) - { - m_target->RemoveAurasDueToSpell(GetId()); - return; - } - break; - } - default: - break; - } - } - - uint32 absorb=0; - uint32 resist=0; - CleanDamage cleanDamage = CleanDamage(0, BASE_ATTACK, MELEE_HIT_NORMAL ); - - // ignore non positive values (can be result apply spellmods to aura damage - uint32 amount = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; - - uint32 pdamage; - - if(m_modifier.m_auraname == SPELL_AURA_PERIODIC_DAMAGE) - { - pdamage = amount; - - // Calculate armor mitigation if it is a physical spell - // But not for bleed mechanic spells - if ( GetSpellSchoolMask(GetSpellProto()) & SPELL_SCHOOL_MASK_NORMAL && - GetEffectMechanic(GetSpellProto(), m_effIndex) != MECHANIC_BLEED) - { - uint32 pdamageReductedArmor = pCaster->CalcArmorReducedDamage(m_target, pdamage); - cleanDamage.damage += pdamage - pdamageReductedArmor; - pdamage = pdamageReductedArmor; - } - - pdamage = pCaster->SpellDamageBonus(m_target,GetSpellProto(),pdamage,DOT); - - // Curse of Agony damage-per-tick calculation - if (GetSpellProto()->SpellFamilyName==SPELLFAMILY_WARLOCK && (GetSpellProto()->SpellFamilyFlags & 0x0000000000000400LL) && GetSpellProto()->SpellIconID==544) - { - // 1..4 ticks, 1/2 from normal tick damage - if (m_duration>=((m_maxduration-m_modifier.periodictime)*2/3)) - pdamage = pdamage/2; - // 9..12 ticks, 3/2 from normal tick damage - else if(m_duration<((m_maxduration-m_modifier.periodictime)/3)) - pdamage += (pdamage+1)/2; // +1 prevent 0.5 damage possible lost at 1..4 ticks - // 5..8 ticks have normal tick damage - } - } - else - pdamage = uint32(m_target->GetMaxHealth()*amount/100); - - //As of 2.2 resilience reduces damage from DoT ticks as much as the chance to not be critically hit - // Reduce dot damage from resilience for players - if (m_target->GetTypeId()==TYPEID_PLAYER) - pdamage-=((Player*)m_target)->GetDotDamageReduction(pdamage); - - pCaster->CalcAbsorbResist(m_target, GetSpellSchoolMask(GetSpellProto()), DOT, pdamage, &absorb, &resist); - - sLog.outDetail("PeriodicTick: %u (TypeId: %u) attacked %u (TypeId: %u) for %u dmg inflicted by %u abs is %u", - GetCasterGUID(), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId(),absorb); - - WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size - data.append(m_target->GetPackGUID()); - data.appendPackGUID(GetCasterGUID()); - data << uint32(GetId()); - data << uint32(1); - data << uint32(m_modifier.m_auraname); - data << (uint32)pdamage; - data << (uint32)GetSpellSchoolMask(GetSpellProto()); // will be mask in 2.4.x - data << (uint32)absorb; - data << (uint32)resist; - m_target->SendMessageToSet(&data,true); - - Unit* target = m_target; // aura can be deleted in DealDamage - SpellEntry const* spellProto = GetSpellProto(); - - pCaster->DealDamage(m_target, (pdamage <= absorb+resist) ? 0 : (pdamage-absorb-resist), &cleanDamage, DOT, GetSpellSchoolMask(GetSpellProto()), GetSpellProto(), true); - - // DO NOT ACCESS MEMBERS OF THE AURA FROM NOW ON (DealDamage can delete aura) - - pCaster->ProcDamageAndSpell(target, PROC_FLAG_HIT_SPELL, PROC_FLAG_TAKE_DAMAGE, (pdamage <= absorb+resist) ? 0 : (pdamage-absorb-resist), GetSpellSchoolMask(spellProto), spellProto); - break; - } - case SPELL_AURA_PERIODIC_LEECH: - { - Unit *pCaster = GetCaster(); - if(!pCaster) - return; - - if(!pCaster->isAlive()) - return; - - if( GetSpellProto()->Effect[GetEffIndex()]==SPELL_EFFECT_PERSISTENT_AREA_AURA && - pCaster->SpellHitResult(m_target,GetSpellProto(),false)!=SPELL_MISS_NONE) - return; - - // Check for immune (not use charges) - if(m_target->IsImmunedToDamage(GetSpellSchoolMask(GetSpellProto()))) - return; - - uint32 absorb=0; - uint32 resist=0; - CleanDamage cleanDamage = CleanDamage(0, BASE_ATTACK, MELEE_HIT_NORMAL ); - - uint32 pdamage = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; - - //Calculate armor mitigation if it is a physical spell - if (GetSpellSchoolMask(GetSpellProto()) & SPELL_SCHOOL_MASK_NORMAL) - { - uint32 pdamageReductedArmor = pCaster->CalcArmorReducedDamage(m_target, pdamage); - cleanDamage.damage += pdamage - pdamageReductedArmor; - pdamage = pdamageReductedArmor; - } - - pdamage = pCaster->SpellDamageBonus(m_target,GetSpellProto(),pdamage,DOT); - - // talent Soul Siphon add bonus to Drain Life spells - if( GetSpellProto()->SpellFamilyName == SPELLFAMILY_WARLOCK && (GetSpellProto()->SpellFamilyFlags & 0x8) ) - { - // find talent max bonus percentage - Unit::AuraList const& mClassScriptAuras = pCaster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); - for(Unit::AuraList::const_iterator i = mClassScriptAuras.begin(); i != mClassScriptAuras.end(); ++i) - { - if ((*i)->GetModifier()->m_miscvalue == 4992 || (*i)->GetModifier()->m_miscvalue == 4993) - { - if((*i)->GetEffIndex()!=1) - { - sLog.outError("Expected spell %u structure change, need code update",(*i)->GetId()); - break; - } - - // effect 1 m_amount - int32 maxPercent = (*i)->GetModifier()->m_amount; - // effect 0 m_amount - int32 stepPercent = pCaster->CalculateSpellDamage((*i)->GetSpellProto(),0,(*i)->GetSpellProto()->EffectBasePoints[0],pCaster); - - // count affliction effects and calc additional damage in percentage - int32 modPercent = 0; - Unit::AuraMap const& victimAuras = m_target->GetAuras(); - for (Unit::AuraMap::const_iterator itr = victimAuras.begin(); itr != victimAuras.end(); ++itr) - { - Aura* aura = itr->second; - if (aura->IsPositive())continue; - SpellEntry const* m_spell = aura->GetSpellProto(); - if (m_spell->SpellFamilyName != SPELLFAMILY_WARLOCK) - continue; - - SkillLineAbilityMap::const_iterator lower = spellmgr.GetBeginSkillLineAbilityMap(m_spell->Id); - SkillLineAbilityMap::const_iterator upper = spellmgr.GetEndSkillLineAbilityMap(m_spell->Id); - - for(SkillLineAbilityMap::const_iterator _spell_idx = lower; _spell_idx != upper; ++_spell_idx) - { - if(_spell_idx->second->skillId == SKILL_AFFLICTION) - { - modPercent += stepPercent; - if (modPercent >= maxPercent) - { - modPercent = maxPercent; - break; - } - } - } - } - pdamage += (pdamage*modPercent/100); - break; - } - } - } - - //As of 2.2 resilience reduces damage from DoT ticks as much as the chance to not be critically hit - // Reduce dot damage from resilience for players - if (m_target->GetTypeId()==TYPEID_PLAYER) - pdamage-=((Player*)m_target)->GetDotDamageReduction(pdamage); - - pCaster->CalcAbsorbResist(m_target, GetSpellSchoolMask(GetSpellProto()), DOT, pdamage, &absorb, &resist); - - if(m_target->GetHealth() < pdamage) - pdamage = uint32(m_target->GetHealth()); - - sLog.outDetail("PeriodicTick: %u (TypeId: %u) health leech of %u (TypeId: %u) for %u dmg inflicted by %u abs is %u", - GetCasterGUID(), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId(),absorb); - - pCaster->SendSpellNonMeleeDamageLog(m_target, GetId(), pdamage, GetSpellSchoolMask(GetSpellProto()), absorb, resist, false, 0); - - - Unit* target = m_target; // aura can be deleted in DealDamage - SpellEntry const* spellProto = GetSpellProto(); - float multiplier = spellProto->EffectMultipleValue[GetEffIndex()] > 0 ? spellProto->EffectMultipleValue[GetEffIndex()] : 1; - - uint32 new_damage = pCaster->DealDamage(m_target, (pdamage <= absorb+resist) ? 0 : (pdamage-absorb-resist), &cleanDamage, DOT, GetSpellSchoolMask(GetSpellProto()), GetSpellProto(), false); - - // DO NOT ACCESS MEMBERS OF THE AURA FROM NOW ON (DealDamage can delete aura) - - pCaster->ProcDamageAndSpell(target, PROC_FLAG_HIT_SPELL, PROC_FLAG_TAKE_DAMAGE, new_damage, GetSpellSchoolMask(spellProto), spellProto); - if (!target->isAlive() && pCaster->IsNonMeleeSpellCasted(false)) - { - for (uint32 i = CURRENT_FIRST_NON_MELEE_SPELL; i < CURRENT_MAX_SPELL; i++) - { - if (pCaster->m_currentSpells[i] && pCaster->m_currentSpells[i]->m_spellInfo->Id == spellProto->Id) - pCaster->m_currentSpells[i]->cancel(); - } - } - - - if(Player *modOwner = pCaster->GetSpellModOwner()) - modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_MULTIPLE_VALUE, multiplier); - - uint32 heal = pCaster->SpellHealingBonus(spellProto, uint32(new_damage * multiplier), DOT, pCaster); - - int32 gain = pCaster->ModifyHealth(heal); - pCaster->getHostilRefManager().threatAssist(pCaster, gain * 0.5f, spellProto); - - pCaster->SendHealSpellLog(pCaster, spellProto->Id, heal); - break; - } - case SPELL_AURA_PERIODIC_HEAL: - case SPELL_AURA_OBS_MOD_HEALTH: - { - Unit *pCaster = GetCaster(); - if(!pCaster) - return; - - // heal for caster damage (must be alive) - if(m_target != pCaster && GetSpellProto()->SpellVisual==163 && !pCaster->isAlive()) - return; - - // ignore non positive values (can be result apply spellmods to aura damage - uint32 amount = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; - - uint32 pdamage; - - if(m_modifier.m_auraname==SPELL_AURA_OBS_MOD_HEALTH) - pdamage = uint32(m_target->GetMaxHealth() * amount/100); - else - pdamage = amount; - - pdamage = pCaster->SpellHealingBonus(GetSpellProto(), pdamage, DOT, m_target); - - sLog.outDetail("PeriodicTick: %u (TypeId: %u) heal of %u (TypeId: %u) for %u health inflicted by %u", - GUID_LOPART(GetCasterGUID()), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId()); - - WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size - data.append(m_target->GetPackGUID()); - data.appendPackGUID(GetCasterGUID()); - data << uint32(GetId()); - data << uint32(1); - data << uint32(m_modifier.m_auraname); - data << (uint32)pdamage; - m_target->SendMessageToSet(&data,true); - - int32 gain = m_target->ModifyHealth(pdamage); - - // add HoTs to amount healed in bgs - if( pCaster->GetTypeId() == TYPEID_PLAYER ) - if( BattleGround *bg = ((Player*)pCaster)->GetBattleGround() ) - bg->UpdatePlayerScore(((Player*)pCaster), SCORE_HEALING_DONE, gain); - - //Do check before because m_modifier.auraName can be invalidate by DealDamage. - bool procSpell = (m_modifier.m_auraname == SPELL_AURA_PERIODIC_HEAL && m_target != pCaster); - - m_target->getHostilRefManager().threatAssist(pCaster, float(gain) * 0.5f, GetSpellProto()); - - Unit* target = m_target; // aura can be deleted in DealDamage - SpellEntry const* spellProto = GetSpellProto(); - bool haveCastItem = GetCastItemGUID()!=0; - - // heal for caster damage - if(m_target!=pCaster && spellProto->SpellVisual==163) - { - uint32 dmg = spellProto->manaPerSecond; - if(pCaster->GetHealth() <= dmg && pCaster->GetTypeId()==TYPEID_PLAYER) - { - pCaster->RemoveAurasDueToSpell(GetId()); - - // finish current generic/channeling spells, don't affect autorepeat - if(pCaster->m_currentSpells[CURRENT_GENERIC_SPELL]) - { - pCaster->m_currentSpells[CURRENT_GENERIC_SPELL]->finish(); - } - if(pCaster->m_currentSpells[CURRENT_CHANNELED_SPELL]) - { - pCaster->m_currentSpells[CURRENT_CHANNELED_SPELL]->SendChannelUpdate(0); - pCaster->m_currentSpells[CURRENT_CHANNELED_SPELL]->finish(); - } - } - else - { - pCaster->SendSpellNonMeleeDamageLog(pCaster, GetId(), gain, GetSpellSchoolMask(GetSpellProto()), 0, 0, false, 0, false); - - CleanDamage cleanDamage = CleanDamage(0, BASE_ATTACK, MELEE_HIT_NORMAL ); - pCaster->DealDamage(pCaster, gain, &cleanDamage, NODAMAGE, GetSpellSchoolMask(GetSpellProto()), GetSpellProto(), true); - } - } - - // ignore item heals - if(procSpell && !haveCastItem) - pCaster->ProcDamageAndSpell(target,PROC_FLAG_HEAL, PROC_FLAG_HEALED, pdamage, SPELL_SCHOOL_MASK_NONE, spellProto); - break; - } - case SPELL_AURA_PERIODIC_MANA_LEECH: - { - Unit *pCaster = GetCaster(); - if(!pCaster) - return; - - if(!pCaster->isAlive()) - return; - - if( GetSpellProto()->Effect[GetEffIndex()]==SPELL_EFFECT_PERSISTENT_AREA_AURA && - pCaster->SpellHitResult(m_target,GetSpellProto(),false)!=SPELL_MISS_NONE) - return; - - // Check for immune (not use charges) - if(m_target->IsImmunedToDamage(GetSpellSchoolMask(GetSpellProto()))) - return; - - // ignore non positive values (can be result apply spellmods to aura damage - uint32 pdamage = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; - - sLog.outDetail("PeriodicTick: %u (TypeId: %u) power leech of %u (TypeId: %u) for %u dmg inflicted by %u", - GUID_LOPART(GetCasterGUID()), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId()); - - if(m_modifier.m_miscvalue < 0 || m_modifier.m_miscvalue > 4) - break; - - Powers power = Powers(m_modifier.m_miscvalue); - - // power type might have changed between aura applying and tick (druid's shapeshift) - if(m_target->getPowerType() != power) - break; - - int32 drain_amount = m_target->GetPower(power) > pdamage ? pdamage : m_target->GetPower(power); - - // resilience reduce mana draining effect at spell crit damage reduction (added in 2.4) - if (power == POWER_MANA && m_target->GetTypeId() == TYPEID_PLAYER) - drain_amount -= ((Player*)m_target)->GetSpellCritDamageReduction(drain_amount); - - m_target->ModifyPower(power, -drain_amount); - - float gain_multiplier = 0; - - if(pCaster->GetMaxPower(power) > 0) - { - gain_multiplier = GetSpellProto()->EffectMultipleValue[GetEffIndex()]; - - if(Player *modOwner = pCaster->GetSpellModOwner()) - modOwner->ApplySpellMod(GetId(), SPELLMOD_MULTIPLE_VALUE, gain_multiplier); - } - - WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size - data.append(m_target->GetPackGUID()); - data.appendPackGUID(GetCasterGUID()); - data << uint32(GetId()); - data << uint32(1); - data << uint32(m_modifier.m_auraname); - data << (uint32)power; // power type - data << (uint32)drain_amount; - data << (float)gain_multiplier; - m_target->SendMessageToSet(&data,true); - - int32 gain_amount = int32(drain_amount*gain_multiplier); - - if(gain_amount) - { - int32 gain = pCaster->ModifyPower(power,gain_amount); - m_target->AddThreat(pCaster, float(gain) * 0.5f, GetSpellSchoolMask(GetSpellProto()), GetSpellProto()); - } - break; - } - case SPELL_AURA_PERIODIC_ENERGIZE: - { - // ignore non positive values (can be result apply spellmods to aura damage - uint32 pdamage = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; - - sLog.outDetail("PeriodicTick: %u (TypeId: %u) energize %u (TypeId: %u) for %u dmg inflicted by %u", - GUID_LOPART(GetCasterGUID()), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId()); - - if(m_modifier.m_miscvalue < 0 || m_modifier.m_miscvalue > 4) - break; - - Powers power = Powers(m_modifier.m_miscvalue); - - if(m_target->GetMaxPower(power) == 0) - break; - - WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size - data.append(m_target->GetPackGUID()); - data.appendPackGUID(GetCasterGUID()); - data << uint32(GetId()); - data << uint32(1); - data << uint32(m_modifier.m_auraname); - data << (uint32)power; // power type - data << (uint32)pdamage; - m_target->SendMessageToSet(&data,true); - - int32 gain = m_target->ModifyPower(power,pdamage); - - if(Unit* pCaster = GetCaster()) - m_target->getHostilRefManager().threatAssist(pCaster, float(gain) * 0.5f, GetSpellProto()); - break; - } - case SPELL_AURA_OBS_MOD_MANA: - { - // ignore non positive values (can be result apply spellmods to aura damage - uint32 amount = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; - - uint32 pdamage = uint32(m_target->GetMaxPower(POWER_MANA) * amount/100); - - sLog.outDetail("PeriodicTick: %u (TypeId: %u) energize %u (TypeId: %u) for %u mana inflicted by %u", - GUID_LOPART(GetCasterGUID()), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId()); - - if(m_target->GetMaxPower(POWER_MANA) == 0) - break; - - WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size - data.append(m_target->GetPackGUID()); - data.appendPackGUID(GetCasterGUID()); - data << uint32(GetId()); - data << uint32(1); - data << uint32(m_modifier.m_auraname); - data << (uint32)0; // ? - data << (uint32)pdamage; - m_target->SendMessageToSet(&data,true); - - int32 gain = m_target->ModifyPower(POWER_MANA, pdamage); - - if(Unit* pCaster = GetCaster()) - m_target->getHostilRefManager().threatAssist(pCaster, float(gain) * 0.5f, GetSpellProto()); - break; - } - case SPELL_AURA_POWER_BURN_MANA: - { - Unit *pCaster = GetCaster(); - if(!pCaster) - return; - - // Check for immune (not use charges) - if(m_target->IsImmunedToDamage(GetSpellSchoolMask(GetSpellProto()))) - return; - - int32 pdamage = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; - - Powers powerType = Powers(m_modifier.m_miscvalue); - - if(!m_target->isAlive() || m_target->getPowerType() != powerType) - return; - - // resilience reduce mana draining effect at spell crit damage reduction (added in 2.4) - if (powerType == POWER_MANA && m_target->GetTypeId() == TYPEID_PLAYER) - pdamage -= ((Player*)m_target)->GetSpellCritDamageReduction(pdamage); - - uint32 gain = uint32(-m_target->ModifyPower(powerType, -pdamage)); - - gain = uint32(gain * GetSpellProto()->EffectMultipleValue[GetEffIndex()]); - - //maybe has to be sent different to client, but not by SMSG_PERIODICAURALOG - pCaster->SpellNonMeleeDamageLog(m_target, GetId(), gain); - break; - } - // Here tick dummy auras - case SPELL_AURA_PERIODIC_DUMMY: - { - PeriodicDummyTick(); - break; - } - default: - break; - } -} - -void Aura::PeriodicDummyTick() -{ - SpellEntry const* spell = GetSpellProto(); - switch (spell->Id) - { - // Drink - case 430: - case 431: - case 432: - case 1133: - case 1135: - case 1137: - case 10250: - case 22734: - case 27089: - case 34291: - case 43706: - case 46755: - { - if (m_target->GetTypeId() != TYPEID_PLAYER) - return; - // Search SPELL_AURA_MOD_POWER_REGEN aura for this spell and add bonus - Unit::AuraList const& aura = m_target->GetAurasByType(SPELL_AURA_MOD_POWER_REGEN); - for(Unit::AuraList::const_iterator i = aura.begin(); i != aura.end(); ++i) - { - if ((*i)->GetId() == GetId()) - { - // Get tick number - int32 tick = (m_maxduration - m_duration) / m_modifier.periodictime; - // Default case (not on arenas) - if (tick == 0) - { - (*i)->GetModifier()->m_amount = m_modifier.m_amount; - ((Player*)m_target)->UpdateManaRegen(); - // Disable continue - m_isPeriodic = false; - } - return; - //********************************************** - // Code commended since arena patch not added - // This feature uses only in arenas - //********************************************** - // Here need increase mana regen per tick (6 second rule) - // on 0 tick - 0 (handled in 2 second) - // on 1 tick - 166% (handled in 4 second) - // on 2 tick - 133% (handled in 6 second) - // Not need update after 3 tick - /* - if (tick > 3) - return; - // Apply bonus for 0 - 3 tick - switch (tick) - { - case 0: // 0% - (*i)->GetModifier()->m_amount = m_modifier.m_amount = 0; - break; - case 1: // 166% - (*i)->GetModifier()->m_amount = m_modifier.m_amount * 5 / 3; - break; - case 2: // 133% - (*i)->GetModifier()->m_amount = m_modifier.m_amount * 4 / 3; - break; - default: // 100% - normal regen - (*i)->GetModifier()->m_amount = m_modifier.m_amount; - break; - } - ((Player*)m_target)->UpdateManaRegen(); - return;*/ - } - } - return; - } -// // Panda -// case 19230: break; -// // Master of Subtlety -// case 31666: break; -// // Gossip NPC Periodic - Talk -// case 33208: break; -// // Gossip NPC Periodic - Despawn -// case 33209: break; -// // Force of Nature -// case 33831: break; - // Aspect of the Viper - case 34074: - { - if (m_target->GetTypeId() != TYPEID_PLAYER) - return; - // Should be manauser - if (m_target->getPowerType()!=POWER_MANA) - return; - Unit *caster = GetCaster(); - if (!caster) - return; - // Regen amount is max (100% from spell) on 21% or less mana and min on 92.5% or greater mana (20% from spell) - int mana = m_target->GetPower(POWER_MANA); - int max_mana = m_target->GetMaxPower(POWER_MANA); - int32 base_regen = caster->CalculateSpellDamage(m_spellProto, m_effIndex, m_currentBasePoints, m_target); - float regen_pct = 1.20f - 1.1f * mana / max_mana; - if (regen_pct > 1.0f) regen_pct = 1.0f; - else if (regen_pct < 0.2f) regen_pct = 0.2f; - m_modifier.m_amount = int32 (base_regen * regen_pct); - ((Player*)m_target)->UpdateManaRegen(); - return; - } -// // Steal Weapon -// case 36207: break; -// // Simon Game START timer, (DND) -// case 39993: break; -// // Harpooner's Mark -// case 40084: break; -// // Knockdown Fel Cannon: break; The Aggro Burst -// case 40119: break; -// // Old Mount Spell -// case 40154: break; -// // Magnetic Pull -// case 40581: break; -// // Ethereal Ring: break; The Bolt Burst -// case 40801: break; -// // Crystal Prison -// case 40846: break; -// // Copy Weapon -// case 41054: break; -// // Ethereal Ring Visual, Lightning Aura -// case 41477: break; -// // Ethereal Ring Visual, Lightning Aura (Fork) -// case 41525: break; -// // Ethereal Ring Visual, Lightning Jumper Aura -// case 41567: break; -// // No Man's Land -// case 41955: break; -// // Headless Horseman - Fire -// case 42074: break; -// // Headless Horseman - Visual - Large Fire -// case 42075: break; -// // Headless Horseman - Start Fire, Periodic Aura -// case 42140: break; -// // Ram Speed Boost -// case 42152: break; -// // Headless Horseman - Fires Out Victory Aura -// case 42235: break; -// // Pumpkin Life Cycle -// case 42280: break; -// // Brewfest Request Chick Chuck Mug Aura -// case 42537: break; -// // Squashling -// case 42596: break; -// // Headless Horseman Climax, Head: Periodic -// case 42603: break; -// // Fire Bomb -// case 42621: break; -// // Headless Horseman - Conflagrate, Periodic Aura -// case 42637: break; -// // Headless Horseman - Create Pumpkin Treats Aura -// case 42774: break; -// // Headless Horseman Climax - Summoning Rhyme Aura -// case 42879: break; -// // Tricky Treat -// case 42919: break; -// // Giddyup! -// case 42924: break; -// // Ram - Trot -// case 42992: break; -// // Ram - Canter -// case 42993: break; -// // Ram - Gallop -// case 42994: break; -// // Ram Level - Neutral -// case 43310: break; -// // Headless Horseman - Maniacal Laugh, Maniacal, Delayed 17 -// case 43884: break; -// // Headless Horseman - Maniacal Laugh, Maniacal, other, Delayed 17 -// case 44000: break; -// // Energy Feedback -// case 44328: break; -// // Romantic Picnic -// case 45102: break; -// // Romantic Picnic -// case 45123: break; -// // Looking for Love -// case 45124: break; -// // Kite - Lightning Strike Kite Aura -// case 45197: break; -// // Rocket Chicken -// case 45202: break; -// // Copy Offhand Weapon -// case 45205: break; -// // Upper Deck - Kite - Lightning Periodic Aura -// case 45207: break; -// // Kite -Sky Lightning Strike Kite Aura -// case 45251: break; -// // Ribbon Pole Dancer Check Aura -// case 45390: break; -// // Holiday - Midsummer, Ribbon Pole Periodic Visual -// case 45406: break; -// // Parachute -// case 45472: break; -// // Alliance Flag, Extra Damage Debuff -// case 45898: break; -// // Horde Flag, Extra Damage Debuff -// case 45899: break; -// // Ahune - Summoning Rhyme Aura -// case 45926: break; -// // Ahune - Slippery Floor -// case 45945: break; -// // Ahune's Shield -// case 45954: break; -// // Nether Vapor Lightning -// case 45960: break; -// // Darkness -// case 45996: break; -// // Summon Blood Elves Periodic -// case 46041: break; -// // Transform Visual Missile Periodic -// case 46205: break; -// // Find Opening Beam End -// case 46333: break; -// // Ice Spear Control Aura -// case 46371: break; -// // Hailstone Chill -// case 46458: break; -// // Hailstone Chill, Internal -// case 46465: break; -// // Chill, Internal Shifter -// case 46549: break; -// // Summon Ice Spear Knockback Delayer -// case 46878: break; -// // Burninate Effect -// case 47214: break; -// // Fizzcrank Practice Parachute -// case 47228: break; -// // Send Mug Control Aura -// case 47369: break; -// // Direbrew's Disarm (precast) -// case 47407: break; -// // Mole Machine Port Schedule -// case 47489: break; -// // Mole Machine Portal Schedule -// case 49466: break; -// // Drink Coffee -// case 49472: break; -// // Listening to Music -// case 50493: break; -// // Love Rocket Barrage -// case 50530: break; - default: - break; - } -} - -void Aura::HandlePreventFleeing(bool apply, bool Real) -{ - if(!Real) - return; - - Unit::AuraList const& fearAuras = m_target->GetAurasByType(SPELL_AURA_MOD_FEAR); - if( !fearAuras.empty() ) - { - if (apply) - m_target->SetFeared(false, fearAuras.front()->GetCasterGUID()); - else - m_target->SetFeared(true); - } -} - -void Aura::HandleManaShield(bool apply, bool Real) -{ - if(!Real) - return; - - // prevent double apply bonuses - if(apply && (m_target->GetTypeId()!=TYPEID_PLAYER || !((Player*)m_target)->GetSession()->PlayerLoading())) - { - if(Unit* caster = GetCaster()) - { - float DoneActualBenefit = 0.0f; - switch(m_spellProto->SpellFamilyName) - { - case SPELLFAMILY_MAGE: - if(m_spellProto->SpellFamilyFlags & 0x8000) - { - // Mana Shield - // +50% from +spd bonus - DoneActualBenefit = caster->SpellBaseDamageBonus(GetSpellSchoolMask(m_spellProto)) * 0.5f; - break; - } - break; - default: - break; - } - - DoneActualBenefit *= caster->CalculateLevelPenalty(GetSpellProto()); - - m_modifier.m_amount += (int32)DoneActualBenefit; - } - } -} - -void Aura::HandleArenaPreparation(bool apply, bool Real) -{ - if(!Real) - return; - - if(apply) - m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PREPARATION); - else - m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PREPARATION); -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "Opcodes.h" +#include "Log.h" +#include "UpdateMask.h" +#include "World.h" +#include "ObjectMgr.h" +#include "SpellMgr.h" +#include "Player.h" +#include "Unit.h" +#include "Spell.h" +#include "SpellAuras.h" +#include "DynamicObject.h" +#include "Group.h" +#include "UpdateData.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "Policies/SingletonImp.h" +#include "Totem.h" +#include "Creature.h" +#include "Formulas.h" +#include "BattleGround.h" +#include "CreatureAI.h" +#include "Util.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +#define NULL_AURA_SLOT 0xFF + +pAuraHandler AuraHandler[TOTAL_AURAS]= +{ + &Aura::HandleNULL, // 0 SPELL_AURA_NONE + &Aura::HandleBindSight, // 1 SPELL_AURA_BIND_SIGHT + &Aura::HandleModPossess, // 2 SPELL_AURA_MOD_POSSESS + &Aura::HandlePeriodicDamage, // 3 SPELL_AURA_PERIODIC_DAMAGE + &Aura::HandleAuraDummy, // 4 SPELL_AURA_DUMMY + &Aura::HandleModConfuse, // 5 SPELL_AURA_MOD_CONFUSE + &Aura::HandleModCharm, // 6 SPELL_AURA_MOD_CHARM + &Aura::HandleModFear, // 7 SPELL_AURA_MOD_FEAR + &Aura::HandlePeriodicHeal, // 8 SPELL_AURA_PERIODIC_HEAL + &Aura::HandleModAttackSpeed, // 9 SPELL_AURA_MOD_ATTACKSPEED + &Aura::HandleModThreat, // 10 SPELL_AURA_MOD_THREAT + &Aura::HandleModTaunt, // 11 SPELL_AURA_MOD_TAUNT + &Aura::HandleAuraModStun, // 12 SPELL_AURA_MOD_STUN + &Aura::HandleModDamageDone, // 13 SPELL_AURA_MOD_DAMAGE_DONE + &Aura::HandleNoImmediateEffect, // 14 SPELL_AURA_MOD_DAMAGE_TAKEN implemented in Unit::MeleeDamageBonus and Unit::SpellDamageBonus + &Aura::HandleNoImmediateEffect, // 15 SPELL_AURA_DAMAGE_SHIELD implemented in Unit::DoAttackDamage + &Aura::HandleModStealth, // 16 SPELL_AURA_MOD_STEALTH + &Aura::HandleNoImmediateEffect, // 17 SPELL_AURA_MOD_STEALTH_DETECT + &Aura::HandleInvisibility, // 18 SPELL_AURA_MOD_INVISIBILITY + &Aura::HandleInvisibilityDetect, // 19 SPELL_AURA_MOD_INVISIBILITY_DETECTION + &Aura::HandleAuraModTotalHealthPercentRegen, // 20 SPELL_AURA_OBS_MOD_HEALTH + &Aura::HandleAuraModTotalManaPercentRegen, // 21 SPELL_AURA_OBS_MOD_MANA + &Aura::HandleAuraModResistance, // 22 SPELL_AURA_MOD_RESISTANCE + &Aura::HandlePeriodicTriggerSpell, // 23 SPELL_AURA_PERIODIC_TRIGGER_SPELL + &Aura::HandlePeriodicEnergize, // 24 SPELL_AURA_PERIODIC_ENERGIZE + &Aura::HandleAuraModPacify, // 25 SPELL_AURA_MOD_PACIFY + &Aura::HandleAuraModRoot, // 26 SPELL_AURA_MOD_ROOT + &Aura::HandleAuraModSilence, // 27 SPELL_AURA_MOD_SILENCE + &Aura::HandleNoImmediateEffect, // 28 SPELL_AURA_REFLECT_SPELLS implement in Unit::SpellHitResult + &Aura::HandleAuraModStat, // 29 SPELL_AURA_MOD_STAT + &Aura::HandleAuraModSkill, // 30 SPELL_AURA_MOD_SKILL + &Aura::HandleAuraModIncreaseSpeed, // 31 SPELL_AURA_MOD_INCREASE_SPEED + &Aura::HandleAuraModIncreaseMountedSpeed, // 32 SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED + &Aura::HandleAuraModDecreaseSpeed, // 33 SPELL_AURA_MOD_DECREASE_SPEED + &Aura::HandleAuraModIncreaseHealth, // 34 SPELL_AURA_MOD_INCREASE_HEALTH + &Aura::HandleAuraModIncreaseEnergy, // 35 SPELL_AURA_MOD_INCREASE_ENERGY + &Aura::HandleAuraModShapeshift, // 36 SPELL_AURA_MOD_SHAPESHIFT + &Aura::HandleAuraModEffectImmunity, // 37 SPELL_AURA_EFFECT_IMMUNITY + &Aura::HandleAuraModStateImmunity, // 38 SPELL_AURA_STATE_IMMUNITY + &Aura::HandleAuraModSchoolImmunity, // 39 SPELL_AURA_SCHOOL_IMMUNITY + &Aura::HandleAuraModDmgImmunity, // 40 SPELL_AURA_DAMAGE_IMMUNITY + &Aura::HandleAuraModDispelImmunity, // 41 SPELL_AURA_DISPEL_IMMUNITY + &Aura::HandleAuraProcTriggerSpell, // 42 SPELL_AURA_PROC_TRIGGER_SPELL implemented in Unit::ProcDamageAndSpellFor and Unit::HandleProcTriggerSpell + &Aura::HandleNoImmediateEffect, // 43 SPELL_AURA_PROC_TRIGGER_DAMAGE implemented in Unit::ProcDamageAndSpellFor + &Aura::HandleAuraTrackCreatures, // 44 SPELL_AURA_TRACK_CREATURES + &Aura::HandleAuraTrackResources, // 45 SPELL_AURA_TRACK_RESOURCES + &Aura::HandleUnused, // 46 SPELL_AURA_MOD_PARRY_SKILL obsolete? + &Aura::HandleAuraModParryPercent, // 47 SPELL_AURA_MOD_PARRY_PERCENT + &Aura::HandleUnused, // 48 SPELL_AURA_MOD_DODGE_SKILL obsolete? + &Aura::HandleAuraModDodgePercent, // 49 SPELL_AURA_MOD_DODGE_PERCENT + &Aura::HandleUnused, // 50 SPELL_AURA_MOD_BLOCK_SKILL obsolete? + &Aura::HandleAuraModBlockPercent, // 51 SPELL_AURA_MOD_BLOCK_PERCENT + &Aura::HandleAuraModCritPercent, // 52 SPELL_AURA_MOD_CRIT_PERCENT + &Aura::HandlePeriodicLeech, // 53 SPELL_AURA_PERIODIC_LEECH + &Aura::HandleModHitChance, // 54 SPELL_AURA_MOD_HIT_CHANCE + &Aura::HandleModSpellHitChance, // 55 SPELL_AURA_MOD_SPELL_HIT_CHANCE + &Aura::HandleAuraTransform, // 56 SPELL_AURA_TRANSFORM + &Aura::HandleModSpellCritChance, // 57 SPELL_AURA_MOD_SPELL_CRIT_CHANCE + &Aura::HandleAuraModIncreaseSwimSpeed, // 58 SPELL_AURA_MOD_INCREASE_SWIM_SPEED + &Aura::HandleNoImmediateEffect, // 59 SPELL_AURA_MOD_DAMAGE_DONE_CREATURE implemented in Unit::MeleeDamageBonus and Unit::SpellDamageBonus + &Aura::HandleAuraModPacifyAndSilence, // 60 SPELL_AURA_MOD_PACIFY_SILENCE + &Aura::HandleAuraModScale, // 61 SPELL_AURA_MOD_SCALE + &Aura::HandleNULL, // 62 SPELL_AURA_PERIODIC_HEALTH_FUNNEL + &Aura::HandleUnused, // 63 SPELL_AURA_PERIODIC_MANA_FUNNEL obsolete? + &Aura::HandlePeriodicManaLeech, // 64 SPELL_AURA_PERIODIC_MANA_LEECH + &Aura::HandleModCastingSpeed, // 65 SPELL_AURA_MOD_CASTING_SPEED + &Aura::HandleFeignDeath, // 66 SPELL_AURA_FEIGN_DEATH + &Aura::HandleAuraModDisarm, // 67 SPELL_AURA_MOD_DISARM + &Aura::HandleAuraModStalked, // 68 SPELL_AURA_MOD_STALKED + &Aura::HandleSchoolAbsorb, // 69 SPELL_AURA_SCHOOL_ABSORB implemented in Unit::CalcAbsorbResist + &Aura::HandleUnused, // 70 SPELL_AURA_EXTRA_ATTACKS Useless, used by only one spell that has only visual effect + &Aura::HandleModSpellCritChanceShool, // 71 SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL + &Aura::HandleModPowerCostPCT, // 72 SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT + &Aura::HandleModPowerCost, // 73 SPELL_AURA_MOD_POWER_COST_SCHOOL + &Aura::HandleNoImmediateEffect, // 74 SPELL_AURA_REFLECT_SPELLS_SCHOOL implemented in Unit::SpellHitResult + &Aura::HandleNoImmediateEffect, // 75 SPELL_AURA_MOD_LANGUAGE + &Aura::HandleFarSight, // 76 SPELL_AURA_FAR_SIGHT + &Aura::HandleModMechanicImmunity, // 77 SPELL_AURA_MECHANIC_IMMUNITY + &Aura::HandleAuraMounted, // 78 SPELL_AURA_MOUNTED + &Aura::HandleModDamagePercentDone, // 79 SPELL_AURA_MOD_DAMAGE_PERCENT_DONE + &Aura::HandleModPercentStat, // 80 SPELL_AURA_MOD_PERCENT_STAT + &Aura::HandleNoImmediateEffect, // 81 SPELL_AURA_SPLIT_DAMAGE_PCT + &Aura::HandleWaterBreathing, // 82 SPELL_AURA_WATER_BREATHING + &Aura::HandleModBaseResistance, // 83 SPELL_AURA_MOD_BASE_RESISTANCE + &Aura::HandleModRegen, // 84 SPELL_AURA_MOD_REGEN + &Aura::HandleModPowerRegen, // 85 SPELL_AURA_MOD_POWER_REGEN + &Aura::HandleChannelDeathItem, // 86 SPELL_AURA_CHANNEL_DEATH_ITEM + &Aura::HandleNoImmediateEffect, // 87 SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN implemented in Unit::MeleeDamageBonus and Unit::SpellDamageBonus + &Aura::HandleNoImmediateEffect, // 88 SPELL_AURA_MOD_HEALTH_REGEN_PERCENT + &Aura::HandlePeriodicDamagePCT, // 89 SPELL_AURA_PERIODIC_DAMAGE_PERCENT + &Aura::HandleUnused, // 90 SPELL_AURA_MOD_RESIST_CHANCE Useless + &Aura::HandleNoImmediateEffect, // 91 SPELL_AURA_MOD_DETECT_RANGE implemented in Creature::GetAttackDistance + &Aura::HandlePreventFleeing, // 92 SPELL_AURA_PREVENTS_FLEEING + &Aura::HandleModUnattackable, // 93 SPELL_AURA_MOD_UNATTACKABLE + &Aura::HandleNoImmediateEffect, // 94 SPELL_AURA_INTERRUPT_REGEN implemented in Player::RegenerateAll + &Aura::HandleAuraGhost, // 95 SPELL_AURA_GHOST + &Aura::HandleNoImmediateEffect, // 96 SPELL_AURA_SPELL_MAGNET implemented in Spell::SelectMagnetTarget + &Aura::HandleManaShield, // 97 SPELL_AURA_MANA_SHIELD implemented in Unit::CalcAbsorbResist + &Aura::HandleAuraModSkill, // 98 SPELL_AURA_MOD_SKILL_TALENT + &Aura::HandleAuraModAttackPower, // 99 SPELL_AURA_MOD_ATTACK_POWER + &Aura::HandleUnused, //100 SPELL_AURA_AURAS_VISIBLE obsolete? all player can see all auras now + &Aura::HandleModResistancePercent, //101 SPELL_AURA_MOD_RESISTANCE_PCT + &Aura::HandleNoImmediateEffect, //102 SPELL_AURA_MOD_MELEE_ATTACK_POWER_VERSUS implemented in Unit::MeleeDamageBonus + &Aura::HandleAuraModTotalThreat, //103 SPELL_AURA_MOD_TOTAL_THREAT + &Aura::HandleAuraWaterWalk, //104 SPELL_AURA_WATER_WALK + &Aura::HandleAuraFeatherFall, //105 SPELL_AURA_FEATHER_FALL + &Aura::HandleAuraHover, //106 SPELL_AURA_HOVER + &Aura::HandleAddModifier, //107 SPELL_AURA_ADD_FLAT_MODIFIER + &Aura::HandleAddModifier, //108 SPELL_AURA_ADD_PCT_MODIFIER + &Aura::HandleNoImmediateEffect, //109 SPELL_AURA_ADD_TARGET_TRIGGER + &Aura::HandleModPowerRegenPCT, //110 SPELL_AURA_MOD_POWER_REGEN_PERCENT + &Aura::HandleNULL, //111 SPELL_AURA_ADD_CASTER_HIT_TRIGGER + &Aura::HandleNoImmediateEffect, //112 SPELL_AURA_OVERRIDE_CLASS_SCRIPTS + &Aura::HandleNoImmediateEffect, //113 SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN implemented in Unit::MeleeDamageBonus + &Aura::HandleNoImmediateEffect, //114 SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN_PCT implemented in Unit::MeleeDamageBonus + &Aura::HandleAuraHealing, //115 SPELL_AURA_MOD_HEALING + &Aura::HandleNoImmediateEffect, //116 SPELL_AURA_MOD_REGEN_DURING_COMBAT + &Aura::HandleNoImmediateEffect, //117 SPELL_AURA_MOD_MECHANIC_RESISTANCE implemented in Unit::MagicSpellHitResult + &Aura::HandleAuraHealingPct, //118 SPELL_AURA_MOD_HEALING_PCT + &Aura::HandleUnused, //119 SPELL_AURA_SHARE_PET_TRACKING useless + &Aura::HandleAuraUntrackable, //120 SPELL_AURA_UNTRACKABLE + &Aura::HandleAuraEmpathy, //121 SPELL_AURA_EMPATHY + &Aura::HandleModOffhandDamagePercent, //122 SPELL_AURA_MOD_OFFHAND_DAMAGE_PCT + &Aura::HandleModTargetResistance, //123 SPELL_AURA_MOD_TARGET_RESISTANCE + &Aura::HandleAuraModRangedAttackPower, //124 SPELL_AURA_MOD_RANGED_ATTACK_POWER + &Aura::HandleNoImmediateEffect, //125 SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN implemented in Unit::MeleeDamageBonus + &Aura::HandleNoImmediateEffect, //126 SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT implemented in Unit::MeleeDamageBonus + &Aura::HandleNoImmediateEffect, //127 SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS implemented in Unit::MeleeDamageBonus + &Aura::HandleModPossessPet, //128 SPELL_AURA_MOD_POSSESS_PET + &Aura::HandleAuraModIncreaseSpeed, //129 SPELL_AURA_MOD_SPEED_ALWAYS + &Aura::HandleAuraModIncreaseMountedSpeed, //130 SPELL_AURA_MOD_MOUNTED_SPEED_ALWAYS + &Aura::HandleNoImmediateEffect, //131 SPELL_AURA_MOD_RANGED_ATTACK_POWER_VERSUS implemented in Unit::MeleeDamageBonus + &Aura::HandleAuraModIncreaseEnergyPercent, //132 SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT + &Aura::HandleAuraModIncreaseHealthPercent, //133 SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT + &Aura::HandleAuraModRegenInterrupt, //134 SPELL_AURA_MOD_MANA_REGEN_INTERRUPT + &Aura::HandleModHealingDone, //135 SPELL_AURA_MOD_HEALING_DONE + &Aura::HandleAuraHealingPct, //136 SPELL_AURA_MOD_HEALING_DONE_PERCENT implemented in Unit::SpellHealingBonus + &Aura::HandleModTotalPercentStat, //137 SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE + &Aura::HandleHaste, //138 SPELL_AURA_MOD_HASTE + &Aura::HandleForceReaction, //139 SPELL_AURA_FORCE_REACTION + &Aura::HandleAuraModRangedHaste, //140 SPELL_AURA_MOD_RANGED_HASTE + &Aura::HandleRangedAmmoHaste, //141 SPELL_AURA_MOD_RANGED_AMMO_HASTE + &Aura::HandleAuraModBaseResistancePCT, //142 SPELL_AURA_MOD_BASE_RESISTANCE_PCT + &Aura::HandleAuraModResistanceExclusive, //143 SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE + &Aura::HandleNoImmediateEffect, //144 SPELL_AURA_SAFE_FALL implemented in WorldSession::HandleMovementOpcodes + &Aura::HandleUnused, //145 SPELL_AURA_CHARISMA obsolete? + &Aura::HandleUnused, //146 SPELL_AURA_PERSUADED obsolete? + &Aura::HandleNULL, //147 SPELL_AURA_ADD_CREATURE_IMMUNITY + &Aura::HandleAuraRetainComboPoints, //148 SPELL_AURA_RETAIN_COMBO_POINTS + &Aura::HandleNoImmediateEffect, //149 SPELL_AURA_RESIST_PUSHBACK + &Aura::HandleShieldBlockValue, //150 SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT + &Aura::HandleAuraTrackStealthed, //151 SPELL_AURA_TRACK_STEALTHED + &Aura::HandleNoImmediateEffect, //152 SPELL_AURA_MOD_DETECTED_RANGE implemented in Creature::GetAttackDistance + &Aura::HandleNoImmediateEffect, //153 SPELL_AURA_SPLIT_DAMAGE_FLAT + &Aura::HandleNoImmediateEffect, //154 SPELL_AURA_MOD_STEALTH_LEVEL + &Aura::HandleNoImmediateEffect, //155 SPELL_AURA_MOD_WATER_BREATHING + &Aura::HandleNoImmediateEffect, //156 SPELL_AURA_MOD_REPUTATION_GAIN + &Aura::HandleNULL, //157 SPELL_AURA_PET_DAMAGE_MULTI + &Aura::HandleShieldBlockValue, //158 SPELL_AURA_MOD_SHIELD_BLOCKVALUE + &Aura::HandleNoImmediateEffect, //159 SPELL_AURA_NO_PVP_CREDIT only for Honorless Target spell + &Aura::HandleNoImmediateEffect, //160 SPELL_AURA_MOD_AOE_AVOIDANCE implemended in Unit::MagicSpellHitResult + &Aura::HandleNoImmediateEffect, //161 SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT + &Aura::HandleAuraPowerBurn, //162 SPELL_AURA_POWER_BURN_MANA + &Aura::HandleNoImmediateEffect, //163 SPELL_AURA_MOD_CRIT_DAMAGE_BONUS_MELEE + &Aura::HandleUnused, //164 useless, only one test spell + &Aura::HandleAuraAttackPowerAttacker, //165 SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS implemented in Unit::MeleeDamageBonus + &Aura::HandleAuraModAttackPowerPercent, //166 SPELL_AURA_MOD_ATTACK_POWER_PCT + &Aura::HandleAuraModRangedAttackPowerPercent, //167 SPELL_AURA_MOD_RANGED_ATTACK_POWER_PCT + &Aura::HandleNoImmediateEffect, //168 SPELL_AURA_MOD_DAMAGE_DONE_VERSUS implemented in Unit::SpellDamageBonus, Unit::MeleeDamageBonus + &Aura::HandleNoImmediateEffect, //169 SPELL_AURA_MOD_CRIT_PERCENT_VERSUS implemented in Unit::DealDamageBySchool, Unit::DoAttackDamage, Unit::SpellCriticalBonus + &Aura::HandleNULL, //170 SPELL_AURA_DETECT_AMORE only for Detect Amore spell + &Aura::HandleAuraModIncreaseSpeed, //171 SPELL_AURA_MOD_SPEED_NOT_STACK + &Aura::HandleAuraModIncreaseMountedSpeed, //172 SPELL_AURA_MOD_MOUNTED_SPEED_NOT_STACK + &Aura::HandleUnused, //173 SPELL_AURA_ALLOW_CHAMPION_SPELLS only for Proclaim Champion spell + &Aura::HandleModSpellDamagePercentFromStat, //174 SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT implemented in Unit::SpellBaseDamageBonus (by defeult intelect, dependent from SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT) + &Aura::HandleModSpellHealingPercentFromStat, //175 SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT implemented in Unit::SpellBaseHealingBonus + &Aura::HandleSpiritOfRedemption, //176 SPELL_AURA_SPIRIT_OF_REDEMPTION only for Spirit of Redemption spell, die at aura end + &Aura::HandleNULL, //177 SPELL_AURA_AOE_CHARM + &Aura::HandleNoImmediateEffect, //178 SPELL_AURA_MOD_DEBUFF_RESISTANCE implemented in Unit::MagicSpellHitResult + &Aura::HandleNoImmediateEffect, //179 SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE implemented in Unit::SpellCriticalBonus + &Aura::HandleNoImmediateEffect, //180 SPELL_AURA_MOD_FLAT_SPELL_DAMAGE_VERSUS implemented in Unit::SpellDamageBonus + &Aura::HandleUnused, //181 SPELL_AURA_MOD_FLAT_SPELL_CRIT_DAMAGE_VERSUS unused + &Aura::HandleAuraModResistenceOfStatPercent, //182 SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT + &Aura::HandleNULL, //183 SPELL_AURA_MOD_CRITICAL_THREAT + &Aura::HandleNoImmediateEffect, //184 SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE implemented in Unit::RollMeleeOutcomeAgainst + &Aura::HandleNoImmediateEffect, //185 SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE implemented in Unit::RollMeleeOutcomeAgainst + &Aura::HandleNoImmediateEffect, //186 SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE implemented in Unit::MagicSpellHitResult + &Aura::HandleNoImmediateEffect, //187 SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE implemended in Unit::GetUnitCriticalChance + &Aura::HandleNoImmediateEffect, //188 SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_CHANCE implemented in Unit::GetUnitCriticalChance + &Aura::HandleModRating, //189 SPELL_AURA_MOD_RATING + &Aura::HandleNULL, //190 SPELL_AURA_MOD_FACTION_REPUTATION_GAIN + &Aura::HandleAuraModUseNormalSpeed, //191 SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED + &Aura::HandleModMeleeRangedSpeedPct, //192 SPELL_AURA_HASTE_MELEE + &Aura::HandleModCombatSpeedPct, //193 SPELL_AURA_MELEE_SLOW (in fact combat (any type attack) speed pct) + &Aura::HandleUnused, //194 SPELL_AURA_MOD_DEPRICATED_1 not used now (old SPELL_AURA_MOD_SPELL_DAMAGE_OF_INTELLECT) + &Aura::HandleUnused, //195 SPELL_AURA_MOD_DEPRICATED_2 not used now (old SPELL_AURA_MOD_SPELL_HEALING_OF_INTELLECT) + &Aura::HandleNULL, //196 SPELL_AURA_MOD_COOLDOWN + &Aura::HandleNoImmediateEffect, //197 SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE implemented in Unit::SpellCriticalBonus Unit::GetUnitCriticalChance + &Aura::HandleUnused, //198 SPELL_AURA_MOD_ALL_WEAPON_SKILLS + &Aura::HandleNoImmediateEffect, //199 SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT implemented in Unit::MagicSpellHitResult + &Aura::HandleNoImmediateEffect, //200 SPELL_AURA_MOD_XP_PCT implemented in Player::GiveXP + &Aura::HandleAuraAllowFlight, //201 SPELL_AURA_FLY this aura enable flight mode... + &Aura::HandleNoImmediateEffect, //202 SPELL_AURA_CANNOT_BE_DODGED implemented in Unit::RollPhysicalOutcomeAgainst + &Aura::HandleNoImmediateEffect, //203 SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE implemented in Unit::DoAttackDamage + &Aura::HandleNoImmediateEffect, //204 SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE implemented in Unit::DoAttackDamage + &Aura::HandleNULL, //205 vulnerable to school dmg? + &Aura::HandleNULL, //206 SPELL_AURA_MOD_SPEED_MOUNTED + &Aura::HandleAuraModIncreaseFlightSpeed, //207 SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED + &Aura::HandleAuraModIncreaseFlightSpeed, //208 SPELL_AURA_MOD_SPEED_FLIGHT, used only in spell: Flight Form (Passive) + &Aura::HandleAuraModIncreaseFlightSpeed, //209 SPELL_AURA_MOD_FLIGHT_SPEED_ALWAYS + &Aura::HandleNULL, //210 Commentator's Command + &Aura::HandleAuraModIncreaseFlightSpeed, //211 SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACK + &Aura::HandleAuraModRangedAttackPowerOfStatPercent, //212 SPELL_AURA_MOD_RANGED_ATTACK_POWER_OF_STAT_PERCENT + &Aura::HandleNoImmediateEffect, //213 SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT implemented in Player::RewardRage + &Aura::HandleNULL, //214 Tamed Pet Passive + &Aura::HandleArenaPreparation, //215 SPELL_AURA_ARENA_PREPARATION + &Aura::HandleModCastingSpeed, //216 SPELL_AURA_HASTE_SPELLS + &Aura::HandleUnused, //217 unused + &Aura::HandleAuraModRangedHaste, //218 SPELL_AURA_HASTE_RANGED + &Aura::HandleModManaRegen, //219 SPELL_AURA_MOD_MANA_REGEN_FROM_STAT + &Aura::HandleNULL, //220 SPELL_AURA_MOD_RATING_FROM_STAT + &Aura::HandleNULL, //221 ignored + &Aura::HandleUnused, //222 unused + &Aura::HandleNULL, //223 Cold Stare + &Aura::HandleUnused, //224 unused + &Aura::HandleNoImmediateEffect, //225 SPELL_AURA_PRAYER_OF_MENDING + &Aura::HandleAuraPeriodicDummy, //226 SPELL_AURA_PERIODIC_DUMMY + &Aura::HandleNULL, //227 periodic trigger spell + &Aura::HandleNoImmediateEffect, //228 stealth detection + &Aura::HandleNULL, //229 SPELL_AURA_MOD_AOE_DAMAGE_AVOIDANCE + &Aura::HandleAuraModIncreaseMaxHealth, //230 Commanding Shout + &Aura::HandleNULL, //231 + &Aura::HandleNoImmediateEffect, //232 SPELL_AURA_MECHANIC_DURATION_MOD implement in Unit::CalculateSpellDuration + &Aura::HandleNULL, //233 set model id to the one of the creature with id m_modifier.m_miscvalue + &Aura::HandleNoImmediateEffect, //234 SPELL_AURA_MECHANIC_DURATION_MOD_NOT_STACK implement in Unit::CalculateSpellDuration + &Aura::HandleAuraModDispelResist, //235 SPELL_AURA_MOD_DISPEL_RESIST implement in Unit::MagicSpellHitResult + &Aura::HandleUnused, //236 unused + &Aura::HandleModSpellDamagePercentFromAttackPower, //237 SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER implemented in Unit::SpellBaseDamageBonus + &Aura::HandleModSpellHealingPercentFromAttackPower, //238 SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER implemented in Unit::SpellBaseHealingBonus + &Aura::HandleAuraModScale, //239 SPELL_AURA_MOD_SCALE_2 only in Noggenfogger Elixir (16595) before 2.3.0 aura 61 + &Aura::HandleAuraModExpertise, //240 SPELL_AURA_MOD_EXPERTISE + &Aura::HandleForceMoveForward, //241 Forces the player to move forward + &Aura::HandleUnused, //242 SPELL_AURA_MOD_SPELL_DAMAGE_FROM_HEALING + &Aura::HandleUnused, //243 used by two test spells + &Aura::HandleComprehendLanguage, //244 Comprehend language + &Aura::HandleUnused, //245 SPELL_AURA_MOD_DURATION_OF_MAGIC_EFFECTS + &Aura::HandleUnused, //246 unused + &Aura::HandleUnused, //247 unused + &Aura::HandleNoImmediateEffect, //248 SPELL_AURA_MOD_COMBAT_RESULT_CHANCE implemented in Unit::RollMeleeOutcomeAgainst + &Aura::HandleNULL, //249 + &Aura::HandleAuraModIncreaseHealth, //250 SPELL_AURA_MOD_INCREASE_HEALTH_2 + &Aura::HandleNULL, //251 SPELL_AURA_MOD_ENEMY_DODGE + &Aura::HandleUnused, //252 unused + &Aura::HandleUnused, //253 unused + &Aura::HandleUnused, //254 unused + &Aura::HandleUnused, //255 unused + &Aura::HandleUnused, //256 unused + &Aura::HandleUnused, //257 unused + &Aura::HandleUnused, //258 unused + &Aura::HandleUnused, //259 unused + &Aura::HandleUnused, //260 unused + &Aura::HandleNULL //261 SPELL_AURA_261 some phased state (44856 spell) +}; + +Aura::Aura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, Unit *caster, Item* castItem) : +m_procCharges(0), m_spellmod(NULL), m_effIndex(eff), m_caster_guid(0), m_target(target), +m_timeCla(1000), m_castItemGuid(castItem?castItem->GetGUID():0), m_auraSlot(MAX_AURAS), +m_positive(false), m_permanent(false), m_isPeriodic(false), m_isTrigger(false), m_isAreaAura(false), +m_isPersistent(false), m_updated(false), m_removeMode(AURA_REMOVE_BY_DEFAULT), m_isRemovedOnShapeLost(true), m_in_use(false), +m_periodicTimer(0), m_PeriodicEventId(0), m_AuraDRGroup(DIMINISHING_NONE) +{ + assert(target); + + assert(spellproto && spellproto == sSpellStore.LookupEntry( spellproto->Id ) && "`info` must be pointer to sSpellStore element"); + + m_spellProto = spellproto; + + m_currentBasePoints = currentBasePoints ? *currentBasePoints : m_spellProto->EffectBasePoints[eff]; + + m_isPassive = IsPassiveSpell(GetId()); + m_positive = IsPositiveEffect(GetId(), m_effIndex); + + m_applyTime = time(NULL); + + int32 damage; + if(!caster) + { + m_caster_guid = target->GetGUID(); + damage = m_currentBasePoints+1; // stored value-1 + m_maxduration = target->CalculateSpellDuration(m_spellProto, m_effIndex, target); + } + else + { + m_caster_guid = caster->GetGUID(); + + damage = caster->CalculateSpellDamage(m_spellProto,m_effIndex,m_currentBasePoints,target); + m_maxduration = caster->CalculateSpellDuration(m_spellProto, m_effIndex, target); + + if (!damage && castItem && castItem->GetItemSuffixFactor()) + { + ItemRandomSuffixEntry const *item_rand_suffix = sItemRandomSuffixStore.LookupEntry(abs(castItem->GetItemRandomPropertyId())); + if(item_rand_suffix) + { + for (int k=0; k<3; k++) + { + SpellItemEnchantmentEntry const *pEnchant = sSpellItemEnchantmentStore.LookupEntry(item_rand_suffix->enchant_id[k]); + if(pEnchant) + { + for (int t=0; t<3; t++) + if(pEnchant->spellid[t] == m_spellProto->Id) + { + damage = uint32((item_rand_suffix->prefix[k]*castItem->GetItemSuffixFactor()) / 10000 ); + break; + } + } + + if(damage) + break; + } + } + } + } + + if(m_maxduration == -1 || m_isPassive && m_spellProto->DurationIndex == 0) + m_permanent = true; + + Player* modOwner = caster ? caster->GetSpellModOwner() : NULL; + + if(!m_permanent && modOwner) + modOwner->ApplySpellMod(GetId(), SPELLMOD_DURATION, m_maxduration); + + m_duration = m_maxduration; + + if(modOwner) + modOwner->ApplySpellMod(GetId(), SPELLMOD_ACTIVATION_TIME, m_periodicTimer); + + sLog.outDebug("Aura: construct Spellid : %u, Aura : %u Duration : %d Target : %d Damage : %d", m_spellProto->Id, m_spellProto->EffectApplyAuraName[eff], m_maxduration, m_spellProto->EffectImplicitTargetA[eff],damage); + + m_effIndex = eff; + SetModifier(AuraType(m_spellProto->EffectApplyAuraName[eff]), damage, m_spellProto->EffectAmplitude[eff], m_spellProto->EffectMiscValue[eff]); + + m_isDeathPersist = IsDeathPersistentSpell(m_spellProto); + + if(m_spellProto->procCharges) + { + m_procCharges = m_spellProto->procCharges; + + if(modOwner) + modOwner->ApplySpellMod(GetId(), SPELLMOD_CHARGES, m_procCharges); + } + else + m_procCharges = -1; + + m_isRemovedOnShapeLost = (m_caster_guid==m_target->GetGUID() && m_spellProto->Stances && + !(m_spellProto->AttributesEx2 & 0x80000) && !(m_spellProto->Attributes & 0x10000)); +} + +Aura::~Aura() +{ +} + +AreaAura::AreaAura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, +Unit *caster, Item* castItem) : Aura(spellproto, eff, currentBasePoints, target, caster, castItem) +{ + m_isAreaAura = true; + + // caster==NULL in constructor args if target==caster in fact + Unit* caster_ptr = caster ? caster : target; + + m_radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(GetSpellProto()->EffectRadiusIndex[m_effIndex])); + if(Player* modOwner = caster_ptr->GetSpellModOwner()) + modOwner->ApplySpellMod(GetId(), SPELLMOD_RADIUS, m_radius); + + switch(spellproto->Effect[eff]) + { + case SPELL_EFFECT_APPLY_AREA_AURA_PARTY: + m_areaAuraType = AREA_AURA_PARTY; + if(target->GetTypeId() == TYPEID_UNIT && ((Creature*)target)->isTotem()) + m_modifier.m_auraname = SPELL_AURA_NONE; + break; + case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND: + m_areaAuraType = AREA_AURA_FRIEND; + break; + case SPELL_EFFECT_APPLY_AREA_AURA_ENEMY: + m_areaAuraType = AREA_AURA_ENEMY; + if(target == caster_ptr) + m_modifier.m_auraname = SPELL_AURA_NONE; // Do not do any effect on self + break; + case SPELL_EFFECT_APPLY_AREA_AURA_PET: + m_areaAuraType = AREA_AURA_PET; + break; + case SPELL_EFFECT_APPLY_AREA_AURA_OWNER: + m_areaAuraType = AREA_AURA_OWNER; + if(target == caster_ptr) + m_modifier.m_auraname = SPELL_AURA_NONE; + break; + default: + sLog.outError("Wrong spell effect in AreaAura constructor"); + ASSERT(false); + break; + } +} + +AreaAura::~AreaAura() +{ +} + +PersistentAreaAura::PersistentAreaAura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, +Unit *caster, Item* castItem) : Aura(spellproto, eff, currentBasePoints, target, caster, castItem) +{ + m_isPersistent = true; +} + +PersistentAreaAura::~PersistentAreaAura() +{ +} + +SingleEnemyTargetAura::SingleEnemyTargetAura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, +Unit *caster, Item* castItem) : Aura(spellproto, eff, currentBasePoints, target, caster, castItem) +{ + if (caster) + m_casters_target_guid = caster->GetTypeId()==TYPEID_PLAYER ? ((Player*)caster)->GetSelection() : caster->GetUInt64Value(UNIT_FIELD_TARGET); + else + m_casters_target_guid = 0; +} + +SingleEnemyTargetAura::~SingleEnemyTargetAura() +{ +} + +Unit* SingleEnemyTargetAura::GetTriggerTarget() const +{ + return ObjectAccessor::GetUnit(*m_target, m_casters_target_guid); +} + +Aura* CreateAura(SpellEntry const* spellproto, uint32 eff, int32 *currentBasePoints, Unit *target, Unit *caster, Item* castItem) +{ + if (IsAreaAuraEffect(spellproto->Effect[eff])) + return new AreaAura(spellproto, eff, currentBasePoints, target, caster, castItem); + + uint32 triggeredSpellId = spellproto->EffectTriggerSpell[eff]; + + SpellEntry const* triggredSpellInfo = sSpellStore.LookupEntry(triggeredSpellId); + if (triggredSpellInfo) + for (int i = 0; i < 3; ++i) + if (triggredSpellInfo->EffectImplicitTargetA[i] == TARGET_SINGLE_ENEMY) + return new SingleEnemyTargetAura(spellproto, eff, currentBasePoints, target, caster, castItem); + + return new Aura(spellproto, eff, currentBasePoints, target, caster, castItem); +} + +Unit* Aura::GetCaster() const +{ + if(m_caster_guid==m_target->GetGUID()) + return m_target; + + //return ObjectAccessor::GetUnit(*m_target,m_caster_guid); + //must return caster even if it's in another grid/map + Unit *unit = ObjectAccessor::GetObjectInWorld(m_caster_guid, (Unit*)NULL); + return unit && unit->IsInWorld() ? unit : NULL; +} + +void Aura::SetModifier(AuraType t, int32 a, uint32 pt, int32 miscValue) +{ + m_modifier.m_auraname = t; + m_modifier.m_amount = a; + m_modifier.m_miscvalue = miscValue; + m_modifier.periodictime = pt; +} + +void Aura::Update(uint32 diff) +{ + if (m_duration > 0) + { + m_duration -= diff; + if (m_duration < 0) + m_duration = 0; + m_timeCla -= diff; + + // GetEffIndex()==0 prevent double/triple apply manaPerSecond/manaPerSecondPerLevel to same spell with many auras + // all spells with manaPerSecond/manaPerSecondPerLevel have aura in effect 0 + if(GetEffIndex()==0 && m_timeCla <= 0) + { + if(Unit* caster = GetCaster()) + { + Powers powertype = Powers(m_spellProto->powerType); + int32 manaPerSecond = m_spellProto->manaPerSecond + m_spellProto->manaPerSecondPerLevel * caster->getLevel(); + m_timeCla = 1000; + if (manaPerSecond) + { + if(powertype==POWER_HEALTH) + caster->ModifyHealth(-manaPerSecond); + else + caster->ModifyPower(powertype,-manaPerSecond); + } + } + } + } + + // Channeled aura required check distance from caster + if(IsChanneledSpell(m_spellProto) && m_caster_guid != m_target->GetGUID()) + { + Unit* caster = GetCaster(); + if(!caster) + { + m_target->RemoveAura(GetId(),GetEffIndex()); + return; + } + + // Get spell range + float radius; + SpellModOp mod; + if (m_spellProto->EffectRadiusIndex[GetEffIndex()]) + { + radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(m_spellProto->EffectRadiusIndex[GetEffIndex()])); + mod = SPELLMOD_RADIUS; + } + else + { + radius = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellProto->rangeIndex)); + mod = SPELLMOD_RANGE; + } + + if(Player* modOwner = caster->GetSpellModOwner()) + modOwner->ApplySpellMod(GetId(), mod, radius,NULL); + + if(!caster->IsWithinDistInMap(m_target,radius)) + { + m_target->RemoveAura(GetId(),GetEffIndex()); + return; + } + } + + if(m_isPeriodic && (m_duration >= 0 || m_isPassive || m_permanent)) + { + m_periodicTimer -= diff; + if(m_periodicTimer <= 0) // tick also at m_periodicTimer==0 to prevent lost last tick in case max m_duration == (max m_periodicTimer)*N + { + if( m_modifier.m_auraname == SPELL_AURA_MOD_REGEN || + m_modifier.m_auraname == SPELL_AURA_MOD_POWER_REGEN || + // Cannibalize, eating items and other spells + m_modifier.m_auraname == SPELL_AURA_OBS_MOD_HEALTH || + // Eating items and other spells + m_modifier.m_auraname == SPELL_AURA_OBS_MOD_MANA ) + { + ApplyModifier(true); + return; + } + // update before applying (aura can be removed in TriggerSpell or PeriodicTick calls) + m_periodicTimer += m_modifier.periodictime; + + if(m_isTrigger) + TriggerSpell(); + else + PeriodicTick(); + } + } +} + +void AreaAura::Update(uint32 diff) +{ + // update for the caster of the aura + if(m_caster_guid == m_target->GetGUID()) + { + Unit* caster = m_target; + + if( !caster->hasUnitState(UNIT_STAT_ISOLATED) ) + { + Unit* owner = caster->GetCharmerOrOwner(); + if (!owner) + owner = caster; + std::list targets; + + switch(m_areaAuraType) + { + case AREA_AURA_PARTY: + { + Group *pGroup = NULL; + + if (owner->GetTypeId() == TYPEID_PLAYER) + pGroup = ((Player*)owner)->GetGroup(); + + if( pGroup) + { + uint8 subgroup = ((Player*)owner)->GetSubGroup(); + for(GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* Target = itr->getSource(); + if(Target && Target->isAlive() && Target->GetSubGroup()==subgroup && caster->IsFriendlyTo(Target)) + { + if(caster->IsWithinDistInMap(Target, m_radius)) + targets.push_back(Target); + Pet *pet = Target->GetPet(); + if(pet && pet->isAlive() && caster->IsWithinDistInMap(pet, m_radius)) + targets.push_back(pet); + } + } + } + else + { + // add owner + if( owner != caster && caster->IsWithinDistInMap(owner, m_radius) ) + targets.push_back(owner); + // add caster's pet + Unit* pet = caster->GetPet(); + if( pet && caster->IsWithinDistInMap(pet, m_radius)) + targets.push_back(pet); + } + break; + } + case AREA_AURA_FRIEND: + { + CellPair p(MaNGOS::ComputeCellPair(caster->GetPositionX(), caster->GetPositionY())); + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + MaNGOS::AnyFriendlyUnitInObjectRangeCheck u_check(caster, owner, m_radius); + MaNGOS::UnitListSearcher searcher(targets, u_check); + TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); + TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); + CellLock cell_lock(cell, p); + cell_lock->Visit(cell_lock, world_unit_searcher, *MapManager::Instance().GetMap(caster->GetMapId(), caster)); + cell_lock->Visit(cell_lock, grid_unit_searcher, *MapManager::Instance().GetMap(caster->GetMapId(), caster)); + break; + } + case AREA_AURA_ENEMY: + { + CellPair p(MaNGOS::ComputeCellPair(caster->GetPositionX(), caster->GetPositionY())); + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + MaNGOS::AnyAoETargetUnitInObjectRangeCheck u_check(caster, owner, m_radius); // No GetCharmer in searcher + MaNGOS::UnitListSearcher searcher(targets, u_check); + TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); + TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); + CellLock cell_lock(cell, p); + cell_lock->Visit(cell_lock, world_unit_searcher, *MapManager::Instance().GetMap(caster->GetMapId(), caster)); + cell_lock->Visit(cell_lock, grid_unit_searcher, *MapManager::Instance().GetMap(caster->GetMapId(), caster)); + break; + } + case AREA_AURA_OWNER: + case AREA_AURA_PET: + { + if(owner != caster) + targets.push_back(owner); + break; + } + } + + for(std::list::iterator tIter = targets.begin(); tIter != targets.end(); tIter++) + { + if((*tIter)->HasAura(GetId(), m_effIndex)) + continue; + + if(SpellEntry const *actualSpellInfo = spellmgr.SelectAuraRankForPlayerLevel(GetSpellProto(), (*tIter)->getLevel())) + { + int32 actualBasePoints = m_currentBasePoints; + // recalculate basepoints for lower rank (all AreaAura spell not use custom basepoints?) + if(actualSpellInfo != GetSpellProto()) + actualBasePoints = actualSpellInfo->EffectBasePoints[m_effIndex]; + AreaAura *aur = new AreaAura(actualSpellInfo, m_effIndex, &actualBasePoints, (*tIter), caster, NULL); + (*tIter)->AddAura(aur); + } + } + } + Aura::Update(diff); + } + else // aura at non-caster + { + Unit * tmp_target = m_target; + Unit* caster = GetCaster(); + uint32 tmp_spellId = GetId(), tmp_effIndex = m_effIndex; + + // WARNING: the aura may get deleted during the update + // DO NOT access its members after update! + Aura::Update(diff); + + // remove aura if out-of-range from caster (after teleport for example) + // or caster is isolated or caster no longer has the aura + // or caster is (no longer) friendly + bool needFriendly = (m_areaAuraType == AREA_AURA_ENEMY ? false : true); + if( !caster || caster->hasUnitState(UNIT_STAT_ISOLATED) || + !caster->IsWithinDistInMap(tmp_target, m_radius) || + !caster->HasAura(tmp_spellId, tmp_effIndex) || + caster->IsFriendlyTo(tmp_target) != needFriendly + ) + { + tmp_target->RemoveAura(tmp_spellId, tmp_effIndex); + } + else if( m_areaAuraType == AREA_AURA_PARTY) // check if in same sub group + { + // not check group if target == owner or target == pet + if (caster->GetCharmerOrOwnerGUID() != tmp_target->GetGUID() && caster->GetGUID() != tmp_target->GetCharmerOrOwnerGUID()) + { + Player* check = caster->GetCharmerOrOwnerPlayerOrPlayerItself(); + + Group *pGroup = check ? check->GetGroup() : NULL; + if( pGroup ) + { + Player* checkTarget = tmp_target->GetCharmerOrOwnerPlayerOrPlayerItself(); + if(!checkTarget || !pGroup->SameSubGroup(check, checkTarget)) + tmp_target->RemoveAura(tmp_spellId, tmp_effIndex); + } + else + tmp_target->RemoveAura(tmp_spellId, tmp_effIndex); + } + } + else if( m_areaAuraType == AREA_AURA_PET || m_areaAuraType == AREA_AURA_OWNER ) + { + if( tmp_target->GetGUID() != caster->GetCharmerOrOwnerGUID() ) + tmp_target->RemoveAura(tmp_spellId, tmp_effIndex); + } + } +} + +void PersistentAreaAura::Update(uint32 diff) +{ + bool remove = false; + + // remove the aura if its caster or the dynamic object causing it was removed + // or if the target moves too far from the dynamic object + Unit *caster = GetCaster(); + if (caster) + { + DynamicObject *dynObj = caster->GetDynObject(GetId(), GetEffIndex()); + if (dynObj) + { + if (!m_target->IsWithinDistInMap(dynObj, dynObj->GetRadius())) + remove = true; + } + else + remove = true; + } + else + remove = true; + + Unit *tmp_target = m_target; + uint32 tmp_id = GetId(), tmp_index = GetEffIndex(); + + // WARNING: the aura may get deleted during the update + // DO NOT access its members after update! + Aura::Update(diff); + + if(remove) + tmp_target->RemoveAura(tmp_id, tmp_index); +} + +void Aura::ApplyModifier(bool apply, bool Real) +{ + AuraType aura = m_modifier.m_auraname; + + m_in_use = true; + if(aura= MAX_AURAS || m_isPassive) + return; + + if( m_target->GetTypeId() == TYPEID_PLAYER) + { + WorldPacket data(SMSG_UPDATE_AURA_DURATION, 5); + data << (uint8)m_auraSlot << (uint32)m_duration; + ((Player*)m_target)->SendDirectMessage(&data); + + data.Initialize(SMSG_SET_EXTRA_AURA_INFO, (8+1+4+4+4)); + data.append(m_target->GetPackGUID()); + data << uint8(m_auraSlot); + data << uint32(GetId()); + data << uint32(GetAuraMaxDuration()); + data << uint32(GetAuraDuration()); + ((Player*)m_target)->SendDirectMessage(&data); + } + + // not send in case player loading (will not work anyway until player not added to map), sent in visibility change code + if(m_target->GetTypeId() == TYPEID_PLAYER && ((Player*)m_target)->GetSession()->PlayerLoading()) + return; + + Unit* caster = GetCaster(); + + if(caster && caster->GetTypeId() == TYPEID_PLAYER && caster != m_target) + SendAuraDurationForCaster((Player*)caster); +} + +void Aura::SendAuraDurationForCaster(Player* caster) +{ + WorldPacket data(SMSG_SET_EXTRA_AURA_INFO_NEED_UPDATE, (8+1+4+4+4)); + data.append(m_target->GetPackGUID()); + data << uint8(m_auraSlot); + data << uint32(GetId()); + data << uint32(GetAuraMaxDuration()); // full + data << uint32(GetAuraDuration()); // remain + caster->GetSession()->SendPacket(&data); +} + +void Aura::_AddAura() +{ + if (!GetId()) + return; + if(!m_target) + return; + + // we can found aura in NULL_AURA_SLOT and then need store state instead check slot != NULL_AURA_SLOT + bool samespell = false; + bool secondaura = false; + uint8 slot = NULL_AURA_SLOT; + + for(uint8 i = 0; i < 3; i++) + { + Unit::spellEffectPair spair = Unit::spellEffectPair(GetId(), i); + for(Unit::AuraMap::const_iterator itr = m_target->GetAuras().lower_bound(spair); itr != m_target->GetAuras().upper_bound(spair); ++itr) + { + // allow use single slot only by auras from same caster + if(itr->second->GetCasterGUID()==GetCasterGUID()) + { + samespell = true; + if (m_effIndex > itr->second->GetEffIndex()) + secondaura = true; + slot = itr->second->GetAuraSlot(); + break; + } + } + + if(samespell) + break; + } + + // not call total regen auras at adding + switch (m_modifier.m_auraname) + { + case SPELL_AURA_OBS_MOD_HEALTH: + case SPELL_AURA_OBS_MOD_MANA: + m_periodicTimer = m_modifier.periodictime; + break; + case SPELL_AURA_MOD_REGEN: + case SPELL_AURA_MOD_POWER_REGEN: + case SPELL_AURA_MOD_MANA_REGEN_FROM_STAT: + m_periodicTimer = 5000; + break; + } + + // register aura + if (getDiminishGroup() != DIMINISHING_NONE ) + m_target->ApplyDiminishingAura(getDiminishGroup(),true); + + Unit* caster = GetCaster(); + + // passive auras (except totem auras) do not get placed in the slots + // area auras with SPELL_AURA_NONE are not shown on target + if((!m_isPassive || (caster && caster->GetTypeId() == TYPEID_UNIT && ((Creature*)caster)->isTotem())) && + (m_spellProto->Effect[GetEffIndex()] != SPELL_EFFECT_APPLY_AREA_AURA_ENEMY || m_target != caster)) + { + if(!samespell) // new slot need + { + if (IsPositive()) // empty positive slot + { + for (uint8 i = 0; i < MAX_POSITIVE_AURAS; i++) + { + if (m_target->GetUInt32Value((uint16)(UNIT_FIELD_AURA + i)) == 0) + { + slot = i; + break; + } + } + } + else // empty negative slot + { + for (uint8 i = MAX_POSITIVE_AURAS; i < MAX_AURAS; i++) + { + if (m_target->GetUInt32Value((uint16)(UNIT_FIELD_AURA + i)) == 0) + { + slot = i; + break; + } + } + } + + SetAuraSlot( slot ); + + // Not update fields for not first spell's aura, all data already in fields + if(!secondaura) + { + if(slot < MAX_AURAS) // slot found + { + SetAura(slot, false); + SetAuraFlag(slot, true); + SetAuraLevel(slot,caster ? caster->getLevel() : sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)); + UpdateAuraCharges(); + + // update for out of range group members + m_target->UpdateAuraForGroup(slot); + } + + UpdateAuraDuration(); + } + } + else // use found slot + { + SetAuraSlot( slot ); + // Not recalculate stack count for second aura of the same spell + if (!secondaura) + UpdateSlotCounterAndDuration(true); + } + + // Update Seals information + if( IsSealSpell(GetSpellProto()) ) + m_target->ModifyAuraState(AURA_STATE_JUDGEMENT, true); + + // Conflagrate aura state + if (GetSpellProto()->SpellFamilyName == SPELLFAMILY_WARLOCK && (GetSpellProto()->SpellFamilyFlags & 4)) + m_target->ModifyAuraState(AURA_STATE_IMMOLATE, true); + + if(GetSpellProto()->SpellFamilyName == SPELLFAMILY_DRUID + && (GetSpellProto()->SpellFamilyFlags == 0x40 || GetSpellProto()->SpellFamilyFlags == 0x10)) + { + m_target->ModifyAuraState(AURA_STATE_SWIFTMEND, true); + } + } +} + +void Aura::_RemoveAura() +{ + // Remove all triggered by aura spells vs unlimited duration + // except same aura replace case + if(m_removeMode!=AURA_REMOVE_BY_STACK) + CleanupTriggeredSpells(); + + Unit* caster = GetCaster(); + + if(caster && IsPersistent()) + { + DynamicObject *dynObj = caster->GetDynObject(GetId(), GetEffIndex()); + if (dynObj) + dynObj->RemoveAffected(m_target); + } + + // unregister aura + if (getDiminishGroup() != DIMINISHING_NONE ) + m_target->ApplyDiminishingAura(getDiminishGroup(),false); + + //passive auras do not get put in slots + // Note: but totem can be not accessible for aura target in time remove (to far for find in grid) + //if(m_isPassive && !(caster && caster->GetTypeId() == TYPEID_UNIT && ((Creature*)caster)->isTotem())) + // return; + + uint8 slot = GetAuraSlot(); + + if(slot >= MAX_AURAS) // slot not set + return; + + if(m_target->GetUInt32Value((uint16)(UNIT_FIELD_AURA + slot)) == 0) + return; + + bool samespell = false; + bool sameaura = false; + + // find other aura in same slot (current already removed from list) + for(uint8 i = 0; i < 3; i++) + { + Unit::spellEffectPair spair = Unit::spellEffectPair(GetId(), i); + for(Unit::AuraMap::const_iterator itr = m_target->GetAuras().lower_bound(spair); itr != m_target->GetAuras().upper_bound(spair); ++itr) + { + if(itr->second->GetAuraSlot()==slot) + { + samespell = true; + + if(GetEffIndex()==i) + sameaura = true; + + break; + } + } + if(samespell) + break; + } + + // only remove icon when the last aura of the spell is removed (current aura already removed from list) + if (!samespell) + { + SetAura(slot, true); + SetAuraFlag(slot, false); + SetAuraLevel(slot,caster ? caster->getLevel() : sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)); + + SetAuraApplication(slot, 0); + // update for out of range group members + m_target->UpdateAuraForGroup(slot); + + if( IsSealSpell(GetSpellProto()) ) + m_target->ModifyAuraState(AURA_STATE_JUDGEMENT,false); + + // Conflagrate aura state + if (GetSpellProto()->SpellFamilyName == SPELLFAMILY_WARLOCK && (GetSpellProto()->SpellFamilyFlags & 4)) + m_target->ModifyAuraState(AURA_STATE_IMMOLATE, false); + + // Swiftmend aura state + if(GetSpellProto()->SpellFamilyName == SPELLFAMILY_DRUID + && (GetSpellProto()->SpellFamilyFlags == 0x40 || GetSpellProto()->SpellFamilyFlags == 0x10)) + { + bool found = false; + Unit::AuraList const& RejorRegr = m_target->GetAurasByType(SPELL_AURA_PERIODIC_HEAL); + for(Unit::AuraList::const_iterator i = RejorRegr.begin(); i != RejorRegr.end(); ++i) + { + if((*i)->GetSpellProto()->SpellFamilyName == SPELLFAMILY_DRUID + && ((*i)->GetSpellProto()->SpellFamilyFlags == 0x40 || (*i)->GetSpellProto()->SpellFamilyFlags == 0x10) ) + { + found = true; + break; + } + } + if(!found) + m_target->ModifyAuraState(AURA_STATE_SWIFTMEND, false); + } + + // reset cooldown state for spells + if(caster && caster->GetTypeId() == TYPEID_PLAYER) + { + if ( GetSpellProto()->Attributes & SPELL_ATTR_DISABLED_WHILE_ACTIVE ) + ((Player*)caster)->SendCooldownEvent(GetSpellProto()); + } + } + else if(sameaura) // decrease count for spell, only for same aura effect, or this spell auras in remove proccess. + UpdateSlotCounterAndDuration(false); +} + +void Aura::SetAuraFlag(uint32 slot, bool add) +{ + uint32 index = slot / 4; + uint32 byte = (slot % 4) * 8; + uint32 val = m_target->GetUInt32Value(UNIT_FIELD_AURAFLAGS + index); + val &= ~((uint32)AFLAG_MASK << byte); + if(add) + { + if (IsPositive()) + val |= ((uint32)AFLAG_POSITIVE << byte); + else + val |= ((uint32)AFLAG_NEGATIVE << byte); + } + m_target->SetUInt32Value(UNIT_FIELD_AURAFLAGS + index, val); +} + +void Aura::SetAuraLevel(uint32 slot,uint32 level) +{ + uint32 index = slot / 4; + uint32 byte = (slot % 4) * 8; + uint32 val = m_target->GetUInt32Value(UNIT_FIELD_AURALEVELS + index); + val &= ~(0xFF << byte); + val |= (level << byte); + m_target->SetUInt32Value(UNIT_FIELD_AURALEVELS + index, val); +} + +void Aura::SetAuraApplication(uint32 slot, int8 count) +{ + uint32 index = slot / 4; + uint32 byte = (slot % 4) * 8; + uint32 val = m_target->GetUInt32Value(UNIT_FIELD_AURAAPPLICATIONS + index); + val &= ~(0xFF << byte); + val |= ((uint8(count)) << byte); + m_target->SetUInt32Value(UNIT_FIELD_AURAAPPLICATIONS + index, val); +} + +void Aura::UpdateSlotCounterAndDuration(bool add) +{ + uint8 slot = GetAuraSlot(); + if(slot >= MAX_AURAS) + return; + + // calculate amount of similar auras by same effect index (similar different spells) + int8 count = 0; + + // calculate auras and update durations in case aura adding + Unit::AuraList const& aura_list = m_target->GetAurasByType(GetModifier()->m_auraname); + for(Unit::AuraList::const_iterator i = aura_list.begin();i != aura_list.end(); ++i) + { + if( (*i)->GetId()==GetId() && (*i)->GetEffIndex()==m_effIndex && + (*i)->GetCasterGUID()==GetCasterGUID() ) + { + ++count; + + if(add) + (*i)->SetAuraDuration(GetAuraDuration()); + } + } + + // at aura add aura not added yet, at aura remove aura already removed + // in field stored (count-1) + if(!add) + --count; + + SetAuraApplication(slot, count); + + UpdateAuraDuration(); +} + +/*********************************************************/ +/*** BASIC AURA FUNCTION ***/ +/*********************************************************/ +void Aura::HandleAddModifier(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER || !Real) + return; + + SpellEntry const *spellInfo = GetSpellProto(); + if(!spellInfo) + return; + + if(m_modifier.m_miscvalue >= MAX_SPELLMOD) + return; + + if (apply) + { + // Add custom charges for some mod aura + switch (m_spellProto->Id) + { + case 17941: // Shadow Trance + case 22008: // Netherwind Focus + case 34936: // Backlash + m_procCharges = 1; + break; + } + + SpellModifier *mod = new SpellModifier; + mod->op = SpellModOp(m_modifier.m_miscvalue); + mod->value = m_modifier.m_amount; + mod->type = SpellModType(m_modifier.m_auraname); // SpellModType value == spell aura types + mod->spellId = GetId(); + mod->effectId = m_effIndex; + mod->lastAffected = NULL; + + uint64 spellAffectMask = spellmgr.GetSpellAffectMask(GetId(), m_effIndex); + + if (spellAffectMask) + mod->mask = spellAffectMask; + else + mod->mask = spellInfo->EffectItemType[m_effIndex]; + + if (m_procCharges > 0) + mod->charges = m_procCharges; + else + mod->charges = 0; + + m_spellmod = mod; + } + + uint64 spellFamilyMask = m_spellmod->mask; + + ((Player*)m_target)->AddSpellMod(m_spellmod, apply); + + // reapply some passive spells after add/remove related spellmods + if(spellInfo->SpellFamilyName==SPELLFAMILY_WARRIOR && (spellFamilyMask & 0x0000100000000000LL)) + { + m_target->RemoveAurasDueToSpell(45471); + + if(apply) + m_target->CastSpell(m_target,45471,true); + } +} + +void Aura::TriggerSpell() +{ + Unit* caster = GetCaster(); + Unit* target = GetTriggerTarget(); + + if(!caster || !target) + return; + + // generic casting code with custom spells and target/caster customs + uint32 trigger_spell_id = GetSpellProto()->EffectTriggerSpell[m_effIndex]; + + uint64 originalCasterGUID = GetCasterGUID(); + + SpellEntry const *triggredSpellInfo = sSpellStore.LookupEntry(trigger_spell_id); + SpellEntry const *auraSpellInfo = GetSpellProto(); + uint32 auraId = auraSpellInfo->Id; + + // specific code for cases with no trigger spell provided in field + if (triggredSpellInfo == NULL) + { + switch(auraSpellInfo->SpellFamilyName) + { + case SPELLFAMILY_GENERIC: + { + switch(auraId) + { + // Firestone Passive (1-5 rangs) + case 758: + case 17945: + case 17947: + case 17949: + case 27252: + { + if (caster->GetTypeId()!=TYPEID_PLAYER) + return; + Item* item = ((Player*)caster)->GetWeaponForAttack(BASE_ATTACK); + if (!item) + return; + uint32 enchant_id = 0; + switch (GetId()) + { + case 758: enchant_id = 1803; break; // Rank 1 + case 17945: enchant_id = 1823; break; // Rank 2 + case 17947: enchant_id = 1824; break; // Rank 3 + case 17949: enchant_id = 1825; break; // Rank 4 + case 27252: enchant_id = 2645; break; // Rank 5 + default: + return; + } + // remove old enchanting before applying new + ((Player*)caster)->ApplyEnchantment(item,TEMP_ENCHANTMENT_SLOT,false); + item->SetEnchantment(TEMP_ENCHANTMENT_SLOT, enchant_id, m_modifier.periodictime+1000, 0); + // add new enchanting + ((Player*)caster)->ApplyEnchantment(item,TEMP_ENCHANTMENT_SLOT,true); + return; + } +// // Periodic Mana Burn +// case 812: break; +// // Polymorphic Ray +// case 6965: break; +// // Fire Nova (1-7 Rangs) +// case 8350: +// case 8508: +// case 8509: +// case 11312: +// case 11313: +// case 25540: +// case 25544: +// break; + // Thaumaturgy Channel + case 9712: trigger_spell_id = 21029; break; +// // Egan's Blaster +// case 17368: break; +// // Haunted +// case 18347: break; +// // Ranshalla Waiting +// case 18953: break; +// // Inferno +// case 19695: break; +// // Frostwolf Muzzle DND +// case 21794: break; +// // Alterac Ram Collar DND +// case 21866: break; +// // Celebras Waiting +// case 21916: break; + // Brood Affliction: Bronze + case 23170: + { + m_target->CastSpell(m_target, 23171, true, 0, this); + return; + } +// // Mark of Frost +// case 23184: break; + // Restoration + case 23493: + { + int32 heal = caster->GetMaxHealth() / 10; + caster->ModifyHealth( heal ); + caster->SendHealSpellLog(caster, 23493, heal); + + int32 mana = caster->GetMaxPower(POWER_MANA); + if (mana) + { + mana /= 10; + caster->ModifyPower( POWER_MANA, mana ); + caster->SendEnergizeSpellLog(caster, 23493, mana, POWER_MANA); + } + break; + } +// // Stoneclaw Totem Passive TEST +// case 23792: break; +// // Axe Flurry +// case 24018: break; +// // Mark of Arlokk +// case 24210: break; +// // Restoration +// case 24379: break; +// // Happy Pet +// case 24716: break; +// // Dream Fog +// case 24780: break; +// // Cannon Prep +// case 24832: break; +// // Shadow Bolt Whirl +// case 24834: break; +// // Stink Trap +// case 24918: break; +// // Mark of Nature +// case 25041: break; +// // Agro Drones +// case 25152: break; +// // Consume +// case 25371: break; +// // Pain Spike +// case 25572: break; +// // Rotate 360 +// case 26009: break; +// // Rotate -360 +// case 26136: break; +// // Consume +// case 26196: break; +// // Berserk +// case 26615: break; +// // Defile +// case 27177: break; +// // Teleport: IF/UC +// case 27601: break; +// // Five Fat Finger Exploding Heart Technique +// case 27673: break; +// // Nitrous Boost +// case 27746: break; +// // Steam Tank Passive +// case 27747: break; +// // Frost Blast +// case 27808: break; +// // Detonate Mana +// case 27819: break; +// // Controller Timer +// case 28095: break; +// // Stalagg Chain +// case 28096: break; +// // Stalagg Tesla Passive +// case 28097: break; +// // Feugen Tesla Passive +// case 28109: break; +// // Feugen Chain +// case 28111: break; +// // Mark of Didier +// case 28114: break; +// // Communique Timer, camp +// case 28346: break; +// // Icebolt +// case 28522: break; +// // Silithyst +// case 29519: break; +// // Inoculate Nestlewood Owlkin + case 29528: trigger_spell_id = 28713; break; +// // Overload +// case 29768: break; +// // Return Fire +// case 29788: break; +// // Return Fire +// case 29793: break; +// // Return Fire +// case 29794: break; +// // Guardian of Icecrown Passive +// case 29897: break; + // Feed Captured Animal + case 29917: trigger_spell_id = 29916; break; +// // Flame Wreath +// case 29946: break; +// // Flame Wreath +// case 29947: break; +// // Mind Exhaustion Passive +// case 30025: break; +// // Nether Beam - Serenity +// case 30401: break; + // Extract Gas + case 30427: + { + // move loot to player inventory and despawn target + if(caster->GetTypeId() ==TYPEID_PLAYER && + target->GetTypeId() == TYPEID_UNIT && + ((Creature*)target)->GetCreatureInfo()->type == CREATURE_TYPE_GAS_CLOUD) + { + Player* player = (Player*)caster; + Creature* creature = (Creature*)target; + // missing lootid has been reported on startup - just return + if (!creature->GetCreatureInfo()->SkinLootId) + { + return; + } + Loot *loot = &creature->loot; + loot->clear(); + loot->FillLoot(creature->GetCreatureInfo()->SkinLootId, LootTemplates_Skinning, NULL); + for(uint8 i=0;iitems.size();i++) + { + LootItem *item = loot->LootItemInSlot(i,player); + ItemPosCountVec dest; + uint8 msg = player->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, item->itemid, item->count ); + if ( msg == EQUIP_ERR_OK ) + { + Item * newitem = player->StoreNewItem( dest, item->itemid, true, item->randomPropertyId); + + player->SendNewItem(newitem, uint32(item->count), false, false, true); + } + else + player->SendEquipError( msg, NULL, NULL ); + } + creature->setDeathState(JUST_DIED); + creature->RemoveCorpse(); + creature->SetHealth(0); // just for nice GM-mode view + } + return; + break; + } + // Quake + case 30576: trigger_spell_id = 30571; break; +// // Burning Maul +// case 30598: break; +// // Regeneration +// case 30799: +// case 30800: +// case 30801: +// break; +// // Despawn Self - Smoke cloud +// case 31269: break; +// // Time Rift Periodic +// case 31320: break; +// // Corrupt Medivh +// case 31326: break; + // Doom + case 31347: + { + m_target->CastSpell(m_target,31350,true); + m_target->DealDamage(m_target, m_target->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); + return; + } + // Spellcloth + case 31373: + { + // Summon Elemental after create item + caster->SummonCreature(17870, 0, 0, 0, caster->GetOrientation(), TEMPSUMMON_DEAD_DESPAWN, 0); + return; + } +// // Bloodmyst Tesla +// case 31611: break; +// // Doomfire +// case 31944: break; +// // Teleport Test +// case 32236: break; +// // Earthquake +// case 32686: break; +// // Possess +// case 33401: break; +// // Draw Shadows +// case 33563: break; +// // Murmur's Touch +// case 33711: break; + // Flame Quills + case 34229: + { + // cast 24 spells 34269-34289, 34314-34316 + for(uint32 spell_id = 34269; spell_id != 34290; ++spell_id) + caster->CastSpell(m_target,spell_id,true); + for(uint32 spell_id = 34314; spell_id != 34317; ++spell_id) + caster->CastSpell(m_target,spell_id,true); + return; + } +// // Gravity Lapse +// case 34480: break; +// // Tornado +// case 34683: break; +// // Frostbite Rotate +// case 34748: break; +// // Arcane Flurry +// case 34821: break; +// // Interrupt Shutdown +// case 35016: break; +// // Interrupt Shutdown +// case 35176: break; +// // Inferno +// case 35268: break; +// // Salaadin's Tesla +// case 35515: break; +// // Ethereal Channel (Red) +// case 35518: break; +// // Nether Vapor +// case 35879: break; +// // Dark Portal Storm +// case 36018: break; +// // Burning Maul +// case 36056: break; +// // Living Grove Defender Lifespan +// case 36061: break; +// // Professor Dabiri Talks +// case 36064: break; +// // Kael Gaining Power +// case 36091: break; +// // They Must Burn Bomb Aura +// case 36344: break; +// // They Must Burn Bomb Aura (self) +// case 36350: break; +// // Stolen Ravenous Ravager Egg +// case 36401: break; +// // Activated Cannon +// case 36410: break; +// // Stolen Ravenous Ravager Egg +// case 36418: break; +// // Enchanted Weapons +// case 36510: break; +// // Cursed Scarab Periodic +// case 36556: break; +// // Cursed Scarab Despawn Periodic +// case 36561: break; +// // Vision Guide +// case 36573: break; +// // Cannon Charging (platform) +// case 36785: break; +// // Cannon Charging (self) +// case 36860: break; + // Remote Toy + case 37027: trigger_spell_id = 37029; break; +// // Mark of Death +// case 37125: break; +// // Arcane Flurry +// case 37268: break; +// // Spout +// case 37429: break; +// // Spout +// case 37430: break; +// // Karazhan - Chess NPC AI, Snapshot timer +// case 37440: break; +// // Karazhan - Chess NPC AI, action timer +// case 37504: break; +// // Karazhan - Chess: Is Square OCCUPIED aura (DND) +// case 39400: break; +// // Banish +// case 37546: break; +// // Shriveling Gaze +// case 37589: break; +// // Fake Aggro Radius (2 yd) +// case 37815: break; +// // Corrupt Medivh +// case 37853: break; + // Eye of Grillok + case 38495: + { + m_target->CastSpell(m_target, 38530, true); + return; + } + // Absorb Eye of Grillok (Zezzak's Shard) + case 38554: + { + if(m_target->GetTypeId() != TYPEID_UNIT) + return; + + caster->CastSpell(caster, 38495, true); + + Creature* creatureTarget = (Creature*)m_target; + + creatureTarget->setDeathState(JUST_DIED); + creatureTarget->RemoveCorpse(); + creatureTarget->SetHealth(0); // just for nice GM-mode view + return; + } +// // Magic Sucker Device timer +// case 38672: break; +// // Tomb Guarding Charging +// case 38751: break; +// // Murmur's Touch +// case 38794: break; +// // Activate Nether-wraith Beacon (31742 Nether-wraith Beacon item) +// case 39105: break; +// // Drain World Tree Visual +// case 39140: break; +// // Quest - Dustin's Undead Dragon Visual aura +// case 39259: break; +// // Hellfire - The Exorcism, Jules releases darkness, aura +// case 39306: break; +// // Inferno +// case 39346: break; +// // Enchanted Weapons +// case 39489: break; +// // Shadow Bolt Whirl +// case 39630: break; +// // Shadow Bolt Whirl +// case 39634: break; +// // Shadow Inferno +// case 39645: break; + // Tear of Azzinoth Summon Channel - it's not really supposed to do anything,and this only prevents the console spam + case 39857: trigger_spell_id = 39856; break; +// // Soulgrinder Ritual Visual (Smashed) +// case 39974: break; +// // Simon Game Pre-game timer +// case 40041: break; +// // Knockdown Fel Cannon: The Aggro Check Aura +// case 40113: break; +// // Spirit Lance +// case 40157: break; +// // Demon Transform 2 +// case 40398: break; +// // Demon Transform 1 +// case 40511: break; +// // Ancient Flames +// case 40657: break; +// // Ethereal Ring Cannon: Cannon Aura +// case 40734: break; +// // Cage Trap +// case 40760: break; +// // Random Periodic +// case 40867: break; +// // Prismatic Shield +// case 40879: break; +// // Aura of Desire +// case 41350: break; +// // Dementia +// case 41404: break; +// // Chaos Form +// case 41629: break; +// // Alert Drums +// case 42177: break; +// // Spout +// case 42581: break; +// // Spout +// case 42582: break; +// // Return to the Spirit Realm +// case 44035: break; +// // Curse of Boundless Agony +// case 45050: break; +// // Earthquake +// case 46240: break; + // Personalized Weather + case 46736: trigger_spell_id = 46737; break; +// // Stay Submerged +// case 46981: break; +// // Dragonblight Ram +// case 47015: break; +// // Party G.R.E.N.A.D.E. +// case 51510: break; + default: + break; + } + break; + } + case SPELLFAMILY_MAGE: + { + switch(auraId) + { + // Invisibility + case 66: + { + if(!m_duration) + m_target->CastSpell(m_target, 32612, true, NULL, this); + return; + } + default: + break; + } + break; + } +// case SPELLFAMILY_WARRIOR: +// { +// switch(auraId) +// { +// // Wild Magic +// case 23410: break; +// // Corrupted Totems +// case 23425: break; +// default: +// break; +// } +// break; +// } +// case SPELLFAMILY_PRIEST: +// { +// switch(auraId) +// { +// // Blue Beam +// case 32930: break; +// // Fury of the Dreghood Elders +// case 35460: break; +// default: +// break; +// } + // break; + // } + case SPELLFAMILY_DRUID: + { + switch(auraId) + { + // Cat Form + // trigger_spell_id not set and unknown effect triggered in this case, ignoring for while + case 768: + return; + // Frenzied Regeneration + case 22842: + case 22895: + case 22896: + case 26999: + { + int32 LifePerRage = GetModifier()->m_amount; + + int32 lRage = m_target->GetPower(POWER_RAGE); + if(lRage > 100) // rage stored as rage*10 + lRage = 100; + m_target->ModifyPower(POWER_RAGE, -lRage); + int32 FRTriggerBasePoints = int32(lRage*LifePerRage/10); + m_target->CastCustomSpell(m_target,22845,&FRTriggerBasePoints,NULL,NULL,true,NULL,this); + return; + } + default: + break; + } + break; + } + +// case SPELLFAMILY_HUNTER: +// { +// switch(auraId) +// { +// //Frost Trap Aura +// case 13810: +// return; +// //Rizzle's Frost Trap +// case 39900: +// return; +// // Tame spells +// case 19597: // Tame Ice Claw Bear +// case 19676: // Tame Snow Leopard +// case 19677: // Tame Large Crag Boar +// case 19678: // Tame Adult Plainstrider +// case 19679: // Tame Prairie Stalker +// case 19680: // Tame Swoop +// case 19681: // Tame Dire Mottled Boar +// case 19682: // Tame Surf Crawler +// case 19683: // Tame Armored Scorpid +// case 19684: // Tame Webwood Lurker +// case 19685: // Tame Nightsaber Stalker +// case 19686: // Tame Strigid Screecher +// case 30100: // Tame Crazed Dragonhawk +// case 30103: // Tame Elder Springpaw +// case 30104: // Tame Mistbat +// case 30647: // Tame Barbed Crawler +// case 30648: // Tame Greater Timberstrider +// case 30652: // Tame Nightstalker +// return; +// default: +// break; +// } +// break; +// } + case SPELLFAMILY_SHAMAN: + { + switch(auraId) + { + // Lightning Shield (The Earthshatterer set trigger after cast Lighting Shield) + case 28820: + { + // Need remove self if Lightning Shield not active + Unit::AuraMap const& auras = target->GetAuras(); + for(Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + SpellEntry const* spell = itr->second->GetSpellProto(); + if( spell->SpellFamilyName == SPELLFAMILY_SHAMAN && + spell->SpellFamilyFlags & 0x0000000000000400L) + return; + } + target->RemoveAurasDueToSpell(28820); + return; + } + // Totemic Mastery (Skyshatter Regalia (Shaman Tier 6) - bonus) + case 38443: + { + bool all = true; + for(int i = 0; i < MAX_TOTEM; ++i) + { + if(!caster->m_TotemSlot[i]) + { + all = false; + break; + } + } + + if(all) + caster->CastSpell(caster,38437,true); + else + caster->RemoveAurasDueToSpell(38437); + return; + } + default: + break; + } + break; + } + default: + break; + } + // Reget trigger spell proto + triggredSpellInfo = sSpellStore.LookupEntry(trigger_spell_id); + if(triggredSpellInfo == NULL) + { + sLog.outError("Aura::TriggerSpell: Spell %u have 0 in EffectTriggered[%d], not handled custom case?",GetId(),GetEffIndex()); + return; + } + } + else + { + // Spell exist but require costum code + switch(auraId) + { + // Curse of Idiocy + case 1010: + { + // TODO: spell casted by result in correct way mostly + // BUT: + // 1) target show casting at each triggered cast: target don't must show casting animation for any triggered spell + // but must show affect apply like item casting + // 2) maybe aura must be replace by new with accumulative stat mods insteed stacking + + // prevent cast by triggered auras + if(m_caster_guid == m_target->GetGUID()) + return; + + // stop triggering after each affected stats lost > 90 + int32 intelectLoss = 0; + int32 spiritLoss = 0; + + Unit::AuraList const& mModStat = m_target->GetAurasByType(SPELL_AURA_MOD_STAT); + for(Unit::AuraList::const_iterator i = mModStat.begin(); i != mModStat.end(); ++i) + { + if ((*i)->GetId() == 1010) + { + switch((*i)->GetModifier()->m_miscvalue) + { + case STAT_INTELLECT: intelectLoss += (*i)->GetModifier()->m_amount; break; + case STAT_SPIRIT: spiritLoss += (*i)->GetModifier()->m_amount; break; + default: break; + } + } + } + + if(intelectLoss <= -90 && spiritLoss <= -90) + return; + + caster = target; + originalCasterGUID = 0; + break; + } + // Mana Tide + case 16191: + { + caster->CastCustomSpell(target, trigger_spell_id, &m_modifier.m_amount, NULL, NULL, true, NULL, this, originalCasterGUID); + return; + } + } + } + // All ok cast by default case + Spell *spell = new Spell(caster, triggredSpellInfo, true, originalCasterGUID ); + + SpellCastTargets targets; + targets.setUnitTarget( target ); + + // if spell create dynamic object extract area from it + if(DynamicObject* dynObj = caster->GetDynObject(GetId())) + targets.setDestination(dynObj->GetPositionX(),dynObj->GetPositionY(),dynObj->GetPositionZ()); + + spell->prepare(&targets, this); +} + +/*********************************************************/ +/*** AURA EFFECTS ***/ +/*********************************************************/ + +void Aura::HandleAuraDummy(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + Unit* caster = GetCaster(); + + // AT APPLY + if(apply) + { + switch(GetId()) + { + case 1515: // Tame beast + // FIX_ME: this is 2.0.12 threat effect replaced in 2.1.x by dummy aura, must be checked for correctness + if( caster && m_target->CanHaveThreatList()) + m_target->AddThreat(caster, 10.0f); + return; + case 13139: // net-o-matic + // root to self part of (root_target->charge->root_self sequence + if(caster) + caster->CastSpell(caster,13138,true,NULL,this); + return; + case 39850: // Rocket Blast + if(roll_chance_i(20)) // backfire stun + m_target->CastSpell(m_target, 51581, true, NULL, this); + return; + case 46354: // Blood Elf Illusion + if(caster) + { + switch(caster->getGender()) + { + case GENDER_FEMALE: + caster->CastSpell(m_target,46356,true,NULL,this); + break; + case GENDER_MALE: + caster->CastSpell(m_target,46355,true,NULL,this); + break; + default: + break; + } + } + return; + case 46699: // Requires No Ammo + if(m_target->GetTypeId()==TYPEID_PLAYER) + ((Player*)m_target)->RemoveAmmo(); // not use ammo and not allow use + return; + } + + // Earth Shield + if ( caster && GetSpellProto()->SpellFamilyName == SPELLFAMILY_SHAMAN && (GetSpellProto()->SpellFamilyFlags & 0x40000000000LL)) + { + // prevent double apply bonuses + if(m_target->GetTypeId()!=TYPEID_PLAYER || !((Player*)m_target)->GetSession()->PlayerLoading()) + m_modifier.m_amount = caster->SpellHealingBonus(GetSpellProto(), m_modifier.m_amount, SPELL_DIRECT_DAMAGE, m_target); + return; + } + } + // AT REMOVE + else + { + if( m_target->GetTypeId() == TYPEID_PLAYER && + ( GetSpellProto()->Effect[0]==72 || GetSpellProto()->Effect[0]==6 && + ( GetSpellProto()->EffectApplyAuraName[0]==1 || GetSpellProto()->EffectApplyAuraName[0]==128 ) ) ) + { + // spells with SpellEffect=72 and aura=4: 6196, 6197, 21171, 21425 + m_target->SetUInt64Value(PLAYER_FARSIGHT, 0); + WorldPacket data(SMSG_CLEAR_FAR_SIGHT_IMMEDIATE, 0); + ((Player*)m_target)->GetSession()->SendPacket(&data); + return; + } + + if( (IsQuestTameSpell(GetId())) && caster && caster->isAlive() && m_target->isAlive()) + { + uint32 finalSpelId = 0; + switch(GetId()) + { + case 19548: finalSpelId = 19597; break; + case 19674: finalSpelId = 19677; break; + case 19687: finalSpelId = 19676; break; + case 19688: finalSpelId = 19678; break; + case 19689: finalSpelId = 19679; break; + case 19692: finalSpelId = 19680; break; + case 19693: finalSpelId = 19684; break; + case 19694: finalSpelId = 19681; break; + case 19696: finalSpelId = 19682; break; + case 19697: finalSpelId = 19683; break; + case 19699: finalSpelId = 19685; break; + case 19700: finalSpelId = 19686; break; + case 30646: finalSpelId = 30647; break; + case 30653: finalSpelId = 30648; break; + case 30654: finalSpelId = 30652; break; + case 30099: finalSpelId = 30100; break; + case 30102: finalSpelId = 30103; break; + case 30105: finalSpelId = 30104; break; + } + + if(finalSpelId) + caster->CastSpell(m_target,finalSpelId,true,NULL,this); + return; + } + // Dark Fiend + if(GetId()==45934) + { + // Kill target if dispeled + if (m_removeMode==AURA_REMOVE_BY_DISPEL) + m_target->DealDamage(m_target, m_target->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); + return; + } + + // Burning Winds + if(GetId()==46308) // casted only at creatures at spawn + { + m_target->CastSpell(m_target,47287,true,NULL,this); + return; + } + } + + // AT APPLY & REMOVE + + switch(m_spellProto->SpellFamilyName) + { + case SPELLFAMILY_GENERIC: + { + // Unstable Power + if( GetId()==24658 ) + { + uint32 spellId = 24659; + if (apply) + { + const SpellEntry *spell = sSpellStore.LookupEntry(spellId); + if (!spell) + return; + for (int i=0; i < spell->StackAmount; ++i) + caster->CastSpell(m_target, spell->Id, true, NULL, NULL, GetCasterGUID()); + return; + } + m_target->RemoveAurasDueToSpell(spellId); + return; + } + // Restless Strength + if( GetId()==24661 ) + { + uint32 spellId = 24662; + if (apply) + { + const SpellEntry *spell = sSpellStore.LookupEntry(spellId); + if (!spell) + return; + for (int i=0; i < spell->StackAmount; ++i) + caster->CastSpell(m_target, spell->Id, true, NULL, NULL, GetCasterGUID()); + return; + } + m_target->RemoveAurasDueToSpell(spellId); + return; + } + // Victorious + if(GetId()==32216 && m_target->getClass()==CLASS_WARRIOR) + { + m_target->ModifyAuraState(AURA_STATE_WARRIOR_VICTORY_RUSH, apply); + return; + } + //Summon Fire Elemental + if (GetId() == 40133 && caster) + { + Unit *owner = caster->GetOwner(); + if (owner && owner->GetTypeId() == TYPEID_PLAYER) + { + if(apply) + owner->CastSpell(owner,8985,true); + else + ((Player*)owner)->RemovePet(NULL, PET_SAVE_NOT_IN_SLOT, true); + } + return; + } + + //Summon Earth Elemental + if (GetId() == 40132 && caster) + { + Unit *owner = caster->GetOwner(); + if (owner && owner->GetTypeId() == TYPEID_PLAYER) + { + if(apply) + owner->CastSpell(owner,19704,true); + else + ((Player*)owner)->RemovePet(NULL, PET_SAVE_NOT_IN_SLOT, true); + } + return; + } + break; + } + case SPELLFAMILY_MAGE: + { + // Hypothermia + if( GetId()==41425 ) + { + m_target->ModifyAuraState(AURA_STATE_HYPOTHERMIA,apply); + return; + } + break; + } + case SPELLFAMILY_DRUID: + { + // Lifebloom + if ( GetSpellProto()->SpellFamilyFlags & 0x1000000000LL ) + { + if ( apply ) + { + if ( caster ) + // prevent double apply bonuses + if(m_target->GetTypeId()!=TYPEID_PLAYER || !((Player*)m_target)->GetSession()->PlayerLoading()) + m_modifier.m_amount = caster->SpellHealingBonus(GetSpellProto(), m_modifier.m_amount, SPELL_DIRECT_DAMAGE, m_target); + } + else + { + // Final heal only on dispelled or duration end + if ( !(GetAuraDuration() <= 0 || m_removeMode==AURA_REMOVE_BY_DISPEL) ) + return; + + // have a look if there is still some other Lifebloom dummy aura + Unit::AuraList auras = m_target->GetAurasByType(SPELL_AURA_DUMMY); + for(Unit::AuraList::iterator itr = auras.begin(); itr!=auras.end(); itr++) + if((*itr)->GetSpellProto()->SpellFamilyName == SPELLFAMILY_DRUID && + (*itr)->GetSpellProto()->SpellFamilyFlags & 0x1000000000LL) + return; + + // final heal + m_target->CastCustomSpell(m_target,33778,&m_modifier.m_amount,NULL,NULL,true,NULL,this,GetCasterGUID()); + } + return; + } + + // Predatory Strikes + if(m_target->GetTypeId()==TYPEID_PLAYER && GetSpellProto()->SpellIconID == 1563) + { + ((Player*)m_target)->UpdateAttackPowerAndDamage(); + return; + } + // Idol of the Emerald Queen + if ( GetId() == 34246 && m_target->GetTypeId()==TYPEID_PLAYER ) + { + if(apply) + { + SpellModifier *mod = new SpellModifier; + mod->op = SPELLMOD_DOT; + mod->value = m_modifier.m_amount/7; + mod->type = SPELLMOD_FLAT; + mod->spellId = GetId(); + mod->effectId = m_effIndex; + mod->lastAffected = NULL; + mod->mask = 0x001000000000LL; + mod->charges = 0; + + m_spellmod = mod; + } + + ((Player*)m_target)->AddSpellMod(m_spellmod, apply); + return; + } + break; + } + case SPELLFAMILY_HUNTER: + { + // Improved Aspect of the Viper + if( GetId()==38390 && m_target->GetTypeId()==TYPEID_PLAYER ) + { + if(apply) + { + // + effect value for Aspect of the Viper + SpellModifier *mod = new SpellModifier; + mod->op = SPELLMOD_EFFECT1; + mod->value = m_modifier.m_amount; + mod->type = SPELLMOD_FLAT; + mod->spellId = GetId(); + mod->effectId = m_effIndex; + mod->lastAffected = NULL; + mod->mask = 0x4000000000000LL; + mod->charges = 0; + + m_spellmod = mod; + } + + ((Player*)m_target)->AddSpellMod(m_spellmod, apply); + return; + } + break; + } + case SPELLFAMILY_SHAMAN: + { + // Improved Weapon Totems + if( GetSpellProto()->SpellIconID == 57 && m_target->GetTypeId()==TYPEID_PLAYER ) + { + if(apply) + { + SpellModifier *mod = new SpellModifier; + mod->op = SPELLMOD_EFFECT1; + mod->value = m_modifier.m_amount; + mod->type = SPELLMOD_PCT; + mod->spellId = GetId(); + mod->effectId = m_effIndex; + mod->lastAffected = NULL; + switch (m_effIndex) + { + case 0: + mod->mask = 0x00200000000LL; // Windfury Totem + break; + case 1: + mod->mask = 0x00400000000LL; // Flametongue Totem + break; + } + mod->charges = 0; + + m_spellmod = mod; + } + + ((Player*)m_target)->AddSpellMod(m_spellmod, apply); + return; + } + break; + } + } + + // pet auras + if(PetAura const* petSpell = spellmgr.GetPetAura(GetId())) + { + if(apply) + m_target->AddPetAura(petSpell); + else + m_target->RemovePetAura(petSpell); + return; + } +} + +void Aura::HandleAuraPeriodicDummy(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + SpellEntry const*spell = GetSpellProto(); + switch( spell->SpellFamilyName) + { + case SPELLFAMILY_ROGUE: + { + // Master of Subtlety + if (spell->Id==31666 && !apply && Real) + { + m_target->RemoveAurasDueToSpell(31665); + break; + } + break; + } + case SPELLFAMILY_HUNTER: + { + // Aspect of the Viper + if (spell->SpellFamilyFlags&0x0004000000000000LL) + { + // Update regen on remove + if (!apply && m_target->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_target)->UpdateManaRegen(); + break; + } + break; + } + } + + m_isPeriodic = apply; +} + +void Aura::HandleAuraMounted(bool apply, bool Real) +{ + if(apply) + { + CreatureInfo const* ci = objmgr.GetCreatureTemplate(m_modifier.m_miscvalue); + if(!ci) + { + sLog.outErrorDb("AuraMounted: `creature_template`='%u' not found in database (only need it modelid)", m_modifier.m_miscvalue); + return; + } + + uint32 team = 0; + if (m_target->GetTypeId()==TYPEID_PLAYER) + team = ((Player*)m_target)->GetTeam(); + + uint32 display_id = objmgr.ChooseDisplayId(team,ci); + CreatureModelInfo const *minfo = objmgr.GetCreatureModelRandomGender(display_id); + if (minfo) + display_id = minfo->modelid; + + m_target->Mount(display_id); + } + else + { + m_target->Unmount(); + } +} + +void Aura::HandleAuraWaterWalk(bool apply, bool Real) +{ + // only at real add/remove aura + if(!Real) + return; + + WorldPacket data; + if(apply) + data.Initialize(SMSG_MOVE_WATER_WALK, 8+4); + else + data.Initialize(SMSG_MOVE_LAND_WALK, 8+4); + data.append(m_target->GetPackGUID()); + data << uint32(0); + m_target->SendMessageToSet(&data,true); +} + +void Aura::HandleAuraFeatherFall(bool apply, bool Real) +{ + // only at real add/remove aura + if(!Real) + return; + + WorldPacket data; + if(apply) + data.Initialize(SMSG_MOVE_FEATHER_FALL, 8+4); + else + data.Initialize(SMSG_MOVE_NORMAL_FALL, 8+4); + data.append(m_target->GetPackGUID()); + data << (uint32)0; + m_target->SendMessageToSet(&data,true); +} + +void Aura::HandleAuraHover(bool apply, bool Real) +{ + // only at real add/remove aura + if(!Real) + return; + + WorldPacket data; + if(apply) + data.Initialize(SMSG_MOVE_SET_HOVER, 8+4); + else + data.Initialize(SMSG_MOVE_UNSET_HOVER, 8+4); + data.append(m_target->GetPackGUID()); + data << uint32(0); + m_target->SendMessageToSet(&data,true); +} + +void Aura::HandleWaterBreathing(bool apply, bool Real) +{ + if(apply) + m_target->waterbreath = true; + else if(m_target->GetAurasByType(SPELL_AURA_WATER_BREATHING).empty()) + { + m_target->waterbreath = false; + + // update for enable timer in case not moving target + if(m_target->GetTypeId()==TYPEID_PLAYER && m_target->IsInWorld()) + { + ((Player*)m_target)->UpdateUnderwaterState(m_target->GetMap(),m_target->GetPositionX(),m_target->GetPositionY(),m_target->GetPositionZ()); + ((Player*)m_target)->HandleDrowning(); + } + } +} + +void Aura::HandleAuraModShapeshift(bool apply, bool Real) +{ + if(!Real) + return; + + uint32 modelid = 0; + Powers PowerType = POWER_MANA; + ShapeshiftForm form = ShapeshiftForm(m_modifier.m_miscvalue); + switch(form) + { + case FORM_CAT: + if(Player::TeamForRace(m_target->getRace())==ALLIANCE) + modelid = 892; + else + modelid = 8571; + PowerType = POWER_ENERGY; + break; + case FORM_TRAVEL: + modelid = 632; + break; + case FORM_AQUA: + if(Player::TeamForRace(m_target->getRace())==ALLIANCE) + modelid = 2428; + else + modelid = 2428; + break; + case FORM_BEAR: + if(Player::TeamForRace(m_target->getRace())==ALLIANCE) + modelid = 2281; + else + modelid = 2289; + PowerType = POWER_RAGE; + break; + case FORM_GHOUL: + if(Player::TeamForRace(m_target->getRace())==ALLIANCE) + modelid = 10045; + break; + case FORM_DIREBEAR: + if(Player::TeamForRace(m_target->getRace())==ALLIANCE) + modelid = 2281; + else + modelid = 2289; + PowerType = POWER_RAGE; + break; + case FORM_CREATUREBEAR: + modelid = 902; + break; + case FORM_GHOSTWOLF: + modelid = 4613; + break; + case FORM_FLIGHT: + if(Player::TeamForRace(m_target->getRace())==ALLIANCE) + modelid = 20857; + else + modelid = 20872; + break; + case FORM_MOONKIN: + if(Player::TeamForRace(m_target->getRace())==ALLIANCE) + modelid = 15374; + else + modelid = 15375; + break; + case FORM_FLIGHT_EPIC: + if(Player::TeamForRace(m_target->getRace())==ALLIANCE) + modelid = 21243; + else + modelid = 21244; + break; + case FORM_AMBIENT: + case FORM_SHADOW: + case FORM_STEALTH: + break; + case FORM_TREE: + modelid = 864; + break; + case FORM_BATTLESTANCE: + case FORM_BERSERKERSTANCE: + case FORM_DEFENSIVESTANCE: + PowerType = POWER_RAGE; + break; + case FORM_SPIRITOFREDEMPTION: + modelid = 16031; + break; + default: + sLog.outError("Auras: Unknown Shapeshift Type: %u", m_modifier.m_miscvalue); + } + + // remove polymorph before changing display id to keep new display id + switch ( form ) + { + case FORM_CAT: + case FORM_TREE: + case FORM_TRAVEL: + case FORM_AQUA: + case FORM_BEAR: + case FORM_DIREBEAR: + case FORM_FLIGHT_EPIC: + case FORM_FLIGHT: + case FORM_MOONKIN: + // remove movement affects + m_target->RemoveSpellsCausingAura(SPELL_AURA_MOD_ROOT); + m_target->RemoveSpellsCausingAura(SPELL_AURA_MOD_DECREASE_SPEED); + + // and polymorphic affects + if(m_target->IsPolymorphed()) + m_target->RemoveAurasDueToSpell(m_target->getTransForm()); + break; + default: + break; + } + + if(apply) + { + // remove other shapeshift before applying a new one + if(m_target->m_ShapeShiftFormSpellId) + { + m_target->RemoveAurasDueToSpell(m_target->m_ShapeShiftFormSpellId,this); + } + + m_target->SetByteValue(UNIT_FIELD_BYTES_2, 3, form); + + if(modelid > 0) + { + m_target->SetDisplayId(modelid); + } + + if(PowerType != POWER_MANA) + { + // reset power to default values only at power change + if(m_target->getPowerType()!=PowerType) + m_target->setPowerType(PowerType); + + switch(form) + { + case FORM_CAT: + case FORM_BEAR: + case FORM_DIREBEAR: + { + // get furor proc chance + uint32 FurorChance = 0; + Unit::AuraList const& mDummy = m_target->GetAurasByType(SPELL_AURA_DUMMY); + for(Unit::AuraList::const_iterator i = mDummy.begin(); i != mDummy.end(); ++i) + { + if ((*i)->GetSpellProto()->SpellIconID == 238) + { + FurorChance = (*i)->GetModifier()->m_amount; + break; + } + } + + if (m_modifier.m_miscvalue == FORM_CAT) + { + m_target->SetPower(POWER_ENERGY,0); + if(urand(1,100) <= FurorChance) + { + m_target->CastSpell(m_target,17099,true,NULL,this); + } + } + else + { + m_target->SetPower(POWER_RAGE,0); + if(urand(1,100) <= FurorChance) + { + m_target->CastSpell(m_target,17057,true,NULL,this); + } + } + break; + } + case FORM_BATTLESTANCE: + case FORM_DEFENSIVESTANCE: + case FORM_BERSERKERSTANCE: + { + uint32 Rage_val = 0; + // Stance mastery + Tactical mastery (both passive, and last have aura only in defense stance, but need apply at any stance switch) + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + PlayerSpellMap const& sp_list = ((Player *)m_target)->GetSpellMap(); + for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) + { + if(itr->second->state == PLAYERSPELL_REMOVED) continue; + SpellEntry const *spellInfo = sSpellStore.LookupEntry(itr->first); + if (spellInfo && spellInfo->SpellFamilyName == SPELLFAMILY_WARRIOR && spellInfo->SpellIconID == 139) + Rage_val += m_target->CalculateSpellDamage(spellInfo,0,spellInfo->EffectBasePoints[0],m_target) * 10; + } + } + + if (m_target->GetPower(POWER_RAGE) > Rage_val) + m_target->SetPower(POWER_RAGE,Rage_val); + break; + } + default: + break; + } + } + + m_target->m_ShapeShiftFormSpellId = GetId(); + m_target->m_form = form; + } + else + { + m_target->SetDisplayId(m_target->GetNativeDisplayId()); + m_target->SetByteValue(UNIT_FIELD_BYTES_2, 3, FORM_NONE); + if(m_target->getClass() == CLASS_DRUID) + m_target->setPowerType(POWER_MANA); + m_target->m_ShapeShiftFormSpellId = 0; + m_target->m_form = FORM_NONE; + + switch(form) + { + // Nordrassil Harness - bonus + case FORM_BEAR: + case FORM_DIREBEAR: + case FORM_CAT: + { + if(Aura* dummy = m_target->GetDummyAura(37315) ) + m_target->CastSpell(m_target,37316,true,NULL,dummy); + break; + } + // Nordrassil Regalia - bonus + case FORM_MOONKIN: + { + if(Aura* dummy = m_target->GetDummyAura(37324) ) + m_target->CastSpell(m_target,37325,true,NULL,dummy); + break; + } + } + } + + // adding/removing linked auras + // add/remove the shapeshift aura's boosts + HandleShapeshiftBoosts(apply); + + if(m_target->GetTypeId()==TYPEID_PLAYER) + ((Player*)m_target)->InitDataForForm(); +} + +void Aura::HandleAuraTransform(bool apply, bool Real) +{ + if (apply) + { + // special case (spell specific functionality) + if(m_modifier.m_miscvalue==0) + { + // player applied only + if(m_target->GetTypeId()!=TYPEID_PLAYER) + return; + + switch(GetId()) + { + // Orb of Deception + case 16739: + { + uint32 orb_model = m_target->GetNativeDisplayId(); + switch(orb_model) + { + // Troll Female + case 1479: m_target->SetDisplayId(10134); break; + // Troll Male + case 1478: m_target->SetDisplayId(10135); break; + // Tauren Male + case 59: m_target->SetDisplayId(10136); break; + // Human Male + case 49: m_target->SetDisplayId(10137); break; + // Human Female + case 50: m_target->SetDisplayId(10138); break; + // Orc Male + case 51: m_target->SetDisplayId(10139); break; + // Orc Female + case 52: m_target->SetDisplayId(10140); break; + // Dwarf Male + case 53: m_target->SetDisplayId(10141); break; + // Dwarf Female + case 54: m_target->SetDisplayId(10142); break; + // NightElf Male + case 55: m_target->SetDisplayId(10143); break; + // NightElf Female + case 56: m_target->SetDisplayId(10144); break; + // Undead Female + case 58: m_target->SetDisplayId(10145); break; + // Undead Male + case 57: m_target->SetDisplayId(10146); break; + // Tauren Female + case 60: m_target->SetDisplayId(10147); break; + // Gnome Male + case 1563: m_target->SetDisplayId(10148); break; + // Gnome Female + case 1564: m_target->SetDisplayId(10149); break; + // BloodElf Female + case 15475: m_target->SetDisplayId(17830); break; + // BloodElf Male + case 15476: m_target->SetDisplayId(17829); break; + // Dranei Female + case 16126: m_target->SetDisplayId(17828); break; + // Dranei Male + case 16125: m_target->SetDisplayId(17827); break; + default: break; + } + break; + } + // Murloc costume + case 42365: m_target->SetDisplayId(21723); break; + default: break; + } + } + else + { + CreatureInfo const * ci = objmgr.GetCreatureTemplate(m_modifier.m_miscvalue); + if(!ci) + { + //pig pink ^_^ + m_target->SetDisplayId(16358); + sLog.outError("Auras: unknown creature id = %d (only need its modelid) Form Spell Aura Transform in Spell ID = %d", m_modifier.m_miscvalue, GetId()); + } + else + { + // Will use the default model here + m_target->SetDisplayId(ci->DisplayID_A); + + // Dragonmaw Illusion (set mount model also) + if(GetId()==42016 && m_target->GetMountID() && !m_target->GetAurasByType(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED).empty()) + m_target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID,16314); + } + m_target->setTransForm(GetId()); + } + + // polymorph case + if( Real && m_target->GetTypeId() == TYPEID_PLAYER && m_target->IsPolymorphed()) + { + // for players, start regeneration after 1s (in polymorph fast regeneration case) + // only if caster is Player (after patch 2.4.2) + if(IS_PLAYER_GUID(GetCasterGUID()) ) + ((Player*)m_target)->setRegenTimer(1000); + + //dismount polymorphed target (after patch 2.4.2) + if (m_target->IsMounted()) + m_target->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); + } + } + else + { + Unit::AuraList const& otherTransforms = m_target->GetAurasByType(SPELL_AURA_TRANSFORM); + if(otherTransforms.empty()) + { + m_target->SetDisplayId(m_target->GetNativeDisplayId()); + m_target->setTransForm(0); + } + else + { + // look for other transform auras + Aura* handledAura = *otherTransforms.begin(); + for(Unit::AuraList::const_iterator i = otherTransforms.begin();i != otherTransforms.end(); ++i) + { + // negative auras are prefered + if(!IsPositiveSpell((*i)->GetSpellProto()->Id)) + { + handledAura = *i; + break; + } + } + handledAura->ApplyModifier(true); + } + + // Dragonmaw Illusion (restore mount model) + if(GetId()==42016 && m_target->GetMountID()==16314) + { + if(!m_target->GetAurasByType(SPELL_AURA_MOUNTED).empty()) + { + uint32 cr_id = m_target->GetAurasByType(SPELL_AURA_MOUNTED).front()->GetModifier()->m_miscvalue; + if(CreatureInfo const* ci = objmgr.GetCreatureTemplate(cr_id)) + { + uint32 team = 0; + if (m_target->GetTypeId()==TYPEID_PLAYER) + team = ((Player*)m_target)->GetTeam(); + + uint32 display_id = objmgr.ChooseDisplayId(team,ci); + CreatureModelInfo const *minfo = objmgr.GetCreatureModelRandomGender(display_id); + if (minfo) + display_id = minfo->modelid; + + m_target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID,display_id); + } + } + } + } +} + +void Aura::HandleForceReaction(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + if(!Real) + return; + + Player* player = (Player*)m_target; + + uint32 faction_id = m_modifier.m_miscvalue; + uint32 faction_rank = m_modifier.m_amount; + + if(apply) + player->m_forcedReactions[faction_id] = ReputationRank(faction_rank); + else + player->m_forcedReactions.erase(faction_id); + + WorldPacket data; + data.Initialize(SMSG_SET_FORCED_REACTIONS, 4+player->m_forcedReactions.size()*(4+4)); + data << uint32(player->m_forcedReactions.size()); + for(ForcedReactions::const_iterator itr = player->m_forcedReactions.begin(); itr != player->m_forcedReactions.end(); ++itr) + { + data << uint32(itr->first); // faction_id (Faction.dbc) + data << uint32(itr->second); // reputation rank + } + player->SendDirectMessage(&data); +} + +void Aura::HandleAuraModSkill(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 prot=GetSpellProto()->EffectMiscValue[m_effIndex]; + int32 points = GetModifier()->m_amount; + + ((Player*)m_target)->ModifySkillBonus(prot,(apply ? points: -points),m_modifier.m_auraname==SPELL_AURA_MOD_SKILL_TALENT); + if(prot == SKILL_DEFENSE) + ((Player*)m_target)->UpdateDefenseBonusesMod(); +} + +void Aura::HandleChannelDeathItem(bool apply, bool Real) +{ + if(Real && !apply) + { + Unit* caster = GetCaster(); + Unit* victim = GetTarget(); + if(!caster || caster->GetTypeId() != TYPEID_PLAYER || !victim || m_removeMode!=AURA_REMOVE_BY_DEATH) + return; + + SpellEntry const *spellInfo = GetSpellProto(); + if(spellInfo->EffectItemType[m_effIndex] == 0) + return; + + // Soul Shard only from non-grey units + if( spellInfo->EffectItemType[m_effIndex] == 6265 && + (victim->getLevel() <= MaNGOS::XP::GetGrayLevel(caster->getLevel()) || + victim->GetTypeId()==TYPEID_UNIT && !((Player*)caster)->isAllowedToLoot((Creature*)victim)) ) + return; + ItemPosCountVec dest; + uint8 msg = ((Player*)caster)->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, spellInfo->EffectItemType[m_effIndex], 1 ); + if( msg != EQUIP_ERR_OK ) + { + ((Player*)caster)->SendEquipError( msg, NULL, NULL ); + return; + } + + Item* newitem = ((Player*)caster)->StoreNewItem(dest, spellInfo->EffectItemType[m_effIndex], true); + ((Player*)caster)->SendNewItem(newitem, 1, true, false); + } +} + +void Aura::HandleBindSight(bool apply, bool Real) +{ + Unit* caster = GetCaster(); + if(!caster || caster->GetTypeId() != TYPEID_PLAYER) + return; + + caster->SetUInt64Value(PLAYER_FARSIGHT,apply ? m_target->GetGUID() : 0); +} + +void Aura::HandleFarSight(bool apply, bool Real) +{ + Unit* caster = GetCaster(); + if(!caster || caster->GetTypeId() != TYPEID_PLAYER) + return; + + caster->SetUInt64Value(PLAYER_FARSIGHT,apply ? m_modifier.m_miscvalue : 0); +} + +void Aura::HandleAuraTrackCreatures(bool apply, bool Real) +{ + if(m_target->GetTypeId()!=TYPEID_PLAYER) + return; + + if(apply) + m_target->RemoveNoStackAurasDueToAura(this); + m_target->SetUInt32Value(PLAYER_TRACK_CREATURES, apply ? ((uint32)1)<<(m_modifier.m_miscvalue-1) : 0 ); +} + +void Aura::HandleAuraTrackResources(bool apply, bool Real) +{ + if(m_target->GetTypeId()!=TYPEID_PLAYER) + return; + + if(apply) + m_target->RemoveNoStackAurasDueToAura(this); + m_target->SetUInt32Value(PLAYER_TRACK_RESOURCES, apply ? ((uint32)1)<<(m_modifier.m_miscvalue-1): 0 ); +} + +void Aura::HandleAuraTrackStealthed(bool apply, bool Real) +{ + if(m_target->GetTypeId()!=TYPEID_PLAYER) + return; + + if(apply) + m_target->RemoveNoStackAurasDueToAura(this); + + m_target->ApplyModFlag(PLAYER_FIELD_BYTES,PLAYER_FIELD_BYTE_TRACK_STEALTHED,apply); +} + +void Aura::HandleAuraModScale(bool apply, bool Real) +{ + m_target->ApplyPercentModFloatValue(OBJECT_FIELD_SCALE_X,m_modifier.m_amount,apply); +} + +void Aura::HandleModPossess(bool apply, bool Real) +{ + if(!Real) + return; + + if(m_target->getLevel() > m_modifier.m_amount) + return; + + // not possess yourself + if(GetCasterGUID() == m_target->GetGUID()) + return; + + Unit* caster = GetCaster(); + if(!caster) + return; + + if( apply ) + { + m_target->SetCharmerGUID(GetCasterGUID()); + m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,caster->getFaction()); + caster->SetCharm(m_target); + + m_target->CombatStop(); + m_target->DeleteThreatList(); + if(m_target->GetTypeId() == TYPEID_UNIT) + { + m_target->StopMoving(); + m_target->GetMotionMaster()->Clear(); + m_target->GetMotionMaster()->MoveIdle(); + CharmInfo *charmInfo = ((Creature*)m_target)->InitCharmInfo(m_target); + charmInfo->InitPossessCreateSpells(); + } + + if(caster->GetTypeId() == TYPEID_PLAYER) + { + ((Player*)caster)->PossessSpellInitialize(); + } + } + else + { + m_target->SetCharmerGUID(0); + + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + ((Player*)m_target)->setFactionForRace(m_target->getRace()); + } + else if(m_target->GetTypeId() == TYPEID_UNIT) + { + CreatureInfo const *cinfo = ((Creature*)m_target)->GetCreatureInfo(); + m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,cinfo->faction_A); + } + + caster->SetCharm(0); + + if(caster->GetTypeId() == TYPEID_PLAYER) + { + WorldPacket data(SMSG_PET_SPELLS, 8); + data << uint64(0); + ((Player*)caster)->GetSession()->SendPacket(&data); + } + if(m_target->GetTypeId() == TYPEID_UNIT) + { + ((Creature*)m_target)->AIM_Initialize(); + + if (((Creature*)m_target)->AI()) + ((Creature*)m_target)->AI()->AttackStart(caster); + } + } + if(caster->GetTypeId() == TYPEID_PLAYER) + caster->SetUInt64Value(PLAYER_FARSIGHT,apply ? m_target->GetGUID() : 0); +} + +void Aura::HandleModPossessPet(bool apply, bool Real) +{ + if(!Real) + return; + + Unit* caster = GetCaster(); + if(!caster || caster->GetTypeId() != TYPEID_PLAYER) + return; + if(caster->GetPet() != m_target) + return; + + if(apply) + { + caster->SetUInt64Value(PLAYER_FARSIGHT, m_target->GetGUID()); + m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNKNOWN5); + } + else + { + caster->SetUInt64Value(PLAYER_FARSIGHT, 0); + m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNKNOWN5); + } +} + +void Aura::HandleModCharm(bool apply, bool Real) +{ + if(!Real) + return; + + // not charm yourself + if(GetCasterGUID() == m_target->GetGUID()) + return; + + Unit* caster = GetCaster(); + if(!caster) + return; + + if(int32(m_target->getLevel()) <= m_modifier.m_amount) + { + if( apply ) + { + m_target->SetCharmerGUID(GetCasterGUID()); + m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,caster->getFaction()); + m_target->CastStop(m_target==caster ? GetId() : 0); + caster->SetCharm(m_target); + + m_target->CombatStop(); + m_target->DeleteThreatList(); + + if(m_target->GetTypeId() == TYPEID_UNIT) + { + ((Creature*)m_target)->AIM_Initialize(); + CharmInfo *charmInfo = ((Creature*)m_target)->InitCharmInfo(m_target); + charmInfo->InitCharmCreateSpells(); + charmInfo->SetReactState( REACT_DEFENSIVE ); + + if(caster->GetTypeId() == TYPEID_PLAYER && caster->getClass() == CLASS_WARLOCK) + { + CreatureInfo const *cinfo = ((Creature*)m_target)->GetCreatureInfo(); + if(cinfo && cinfo->type == CREATURE_TYPE_DEMON) + { + //to prevent client crash + m_target->SetFlag(UNIT_FIELD_BYTES_0, 2048); + //just to enable stat window + charmInfo->SetPetNumber(objmgr.GeneratePetNumber(), true); + //if charmed two demons the same session, the 2nd gets the 1st one's name + m_target->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, time(NULL)); + } + } + } + + if(caster->GetTypeId() == TYPEID_PLAYER) + { + ((Player*)caster)->CharmSpellInitialize(); + } + } + else + { + m_target->SetCharmerGUID(0); + + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + ((Player*)m_target)->setFactionForRace(m_target->getRace()); + } + else + { + CreatureInfo const *cinfo = ((Creature*)m_target)->GetCreatureInfo(); + + // restore faction + if(((Creature*)m_target)->isPet()) + { + if(Unit* owner = m_target->GetOwner()) + m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,owner->getFaction()); + else if(cinfo) + m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,cinfo->faction_A); + } + else if(cinfo) // normal creature + m_target->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,cinfo->faction_A); + + // restore UNIT_FIELD_BYTES_0 + if(cinfo && caster->GetTypeId() == TYPEID_PLAYER && caster->getClass() == CLASS_WARLOCK && cinfo->type == CREATURE_TYPE_DEMON) + { + CreatureDataAddon const *cainfo = ((Creature*)m_target)->GetCreatureAddon(); + if(cainfo && cainfo->bytes0 != 0) + m_target->SetUInt32Value(UNIT_FIELD_BYTES_0, cainfo->bytes0); + else + m_target->RemoveFlag(UNIT_FIELD_BYTES_0, 2048); + + if(m_target->GetCharmInfo()) + m_target->GetCharmInfo()->SetPetNumber(0, true); + else + sLog.outError("Aura::HandleModCharm: target="I64FMTD" with typeid=%d has a charm aura but no charm info!", m_target->GetGUID(), m_target->GetTypeId()); + } + } + + caster->SetCharm(0); + + if(caster->GetTypeId() == TYPEID_PLAYER) + { + WorldPacket data(SMSG_PET_SPELLS, 8); + data << uint64(0); + ((Player*)caster)->GetSession()->SendPacket(&data); + } + if(m_target->GetTypeId() == TYPEID_UNIT) + { + ((Creature*)m_target)->AIM_Initialize(); + if (((Creature*)m_target)->AI()) + ((Creature*)m_target)->AI()->AttackStart(caster); + } + } + } +} + +void Aura::HandleModConfuse(bool apply, bool Real) +{ + if(!Real) + return; + + m_target->SetConfused(apply, GetCasterGUID(), GetId()); +} + +void Aura::HandleModFear(bool apply, bool Real) +{ + if (!Real) + return; + + m_target->SetFeared(apply, GetCasterGUID(), GetId()); +} + +void Aura::HandleFeignDeath(bool apply, bool Real) +{ + if(!Real) + return; + + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + if( apply ) + { + /* + WorldPacket data(SMSG_FEIGN_DEATH_RESISTED, 9); + data<GetGUID(); + data<SendMessageToSet(&data,true); + */ + // blizz like 2.0.x + m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNKNOWN6); + // blizz like 2.0.x + m_target->SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FEIGN_DEATH); + // blizz like 2.0.x + m_target->SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_DEAD); + + m_target->addUnitState(UNIT_STAT_DIED); + m_target->CombatStop(); + + // prevent interrupt message + if(m_caster_guid==m_target->GetGUID() && m_target->m_currentSpells[CURRENT_GENERIC_SPELL]) + m_target->m_currentSpells[CURRENT_GENERIC_SPELL]->finish(); + m_target->InterruptNonMeleeSpells(true); + m_target->getHostilRefManager().deleteReferences(); + } + else + { + /* + WorldPacket data(SMSG_FEIGN_DEATH_RESISTED, 9); + data<GetGUID(); + data<SendMessageToSet(&data,true); + */ + // blizz like 2.0.x + m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNKNOWN6); + // blizz like 2.0.x + m_target->RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FEIGN_DEATH); + // blizz like 2.0.x + m_target->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_DEAD); + + m_target->clearUnitState(UNIT_STAT_DIED); + } +} + +void Aura::HandleAuraModDisarm(bool apply, bool Real) +{ + if(!Real) + return; + + if(!apply && m_target->HasAuraType(SPELL_AURA_MOD_DISARM)) + return; + + // not sure for it's correctness + if(apply) + m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED); + else + m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED); + + // only at real add/remove aura + if (m_target->GetTypeId() != TYPEID_PLAYER) + return; + + // main-hand attack speed already set to special value for feral form already and don't must chnage and reset at remove. + if (((Player *)m_target)->IsInFeralForm()) + return; + + if (apply) + m_target->SetAttackTime(BASE_ATTACK,BASE_ATTACK_TIME); + else + ((Player *)m_target)->SetRegularAttackTime(); + + m_target->UpdateDamagePhysical(BASE_ATTACK); +} + +void Aura::HandleAuraModStun(bool apply, bool Real) +{ + if(!Real) + return; + + if (apply) + { + m_target->addUnitState(UNIT_STAT_STUNNED); + m_target->SetUInt64Value(UNIT_FIELD_TARGET, 0); + + m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_ROTATE); + m_target->CastStop(m_target->GetGUID() == GetCasterGUID() ? GetId() : 0); + + // Creature specific + if(m_target->GetTypeId() != TYPEID_PLAYER) + ((Creature*)m_target)->StopMoving(); + else + m_target->SetUnitMovementFlags(0); //Clear movement flags + + WorldPacket data(SMSG_FORCE_MOVE_ROOT, 8); + + data.append(m_target->GetPackGUID()); + data << uint32(0); + m_target->SendMessageToSet(&data,true); + } + else + { + // Real remove called after current aura remove from lists, check if other similar auras active + if(m_target->HasAuraType(SPELL_AURA_MOD_STUN)) + return; + + m_target->clearUnitState(UNIT_STAT_STUNNED); + m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_ROTATE); + + if(!m_target->hasUnitState(UNIT_STAT_ROOT)) // prevent allow move if have also root effect + { + if(m_target->getVictim() && m_target->isAlive()) + m_target->SetUInt64Value(UNIT_FIELD_TARGET,m_target->getVictim()->GetGUID() ); + + WorldPacket data(SMSG_FORCE_MOVE_UNROOT, 8+4); + data.append(m_target->GetPackGUID()); + data << uint32(0); + m_target->SendMessageToSet(&data,true); + } + + // Wyvern Sting + if (GetSpellProto()->SpellFamilyName == SPELLFAMILY_HUNTER && GetSpellProto()->SpellIconID == 1721) + { + Unit* caster = GetCaster(); + if( !caster || caster->GetTypeId()!=TYPEID_PLAYER ) + return; + + uint32 spell_id = 0; + + switch(GetId()) + { + case 19386: spell_id = 24131; break; + case 24132: spell_id = 24134; break; + case 24133: spell_id = 24135; break; + case 27068: spell_id = 27069; break; + default: + sLog.outError("Spell selection called for unexpected original spell %u, new spell for this spell family?",GetId()); + return; + } + + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id); + + if(!spellInfo) + return; + + caster->CastSpell(m_target,spellInfo,true,NULL,this); + return; + } + } +} + +void Aura::HandleModStealth(bool apply, bool Real) +{ + if(apply) + { + // drop flag at stealth in bg + if(Real && m_target->GetTypeId()==TYPEID_PLAYER && ((Player*)m_target)->InBattleGround()) + if(BattleGround *bg = ((Player*)m_target)->GetBattleGround()) + bg->EventPlayerDroppedFlag((Player*)m_target); + + // only at real aura add + if(Real) + { + m_target->SetByteValue(UNIT_FIELD_BYTES_1, 2, 0x02); + if(m_target->GetTypeId()==TYPEID_PLAYER) + m_target->SetFlag(PLAYER_FIELD_BYTES2, 0x2000); + + // apply only if not in GM invisibility (and overwrite invisibility state) + if(m_target->GetVisibility()!=VISIBILITY_OFF) + { + m_target->SetVisibility(VISIBILITY_GROUP_NO_DETECT); + m_target->SetVisibility(VISIBILITY_GROUP_STEALTH); + } + + // for RACE_NIGHTELF stealth + if(m_target->GetTypeId()==TYPEID_PLAYER && GetId()==20580) + m_target->CastSpell(m_target, 21009, true, NULL, this); + } + } + else + { + // only at real aura remove + if(Real) + { + // for RACE_NIGHTELF stealth + if(m_target->GetTypeId()==TYPEID_PLAYER && GetId()==20580) + m_target->RemoveAurasDueToSpell(21009); + + // if last SPELL_AURA_MOD_STEALTH and no GM invisibility + if(!m_target->HasAuraType(SPELL_AURA_MOD_STEALTH) && m_target->GetVisibility()!=VISIBILITY_OFF) + { + m_target->SetByteValue(UNIT_FIELD_BYTES_1, 2, 0x00); + if(m_target->GetTypeId()==TYPEID_PLAYER) + m_target->RemoveFlag(PLAYER_FIELD_BYTES2, 0x2000); + + // restore invisibility if any + if(m_target->HasAuraType(SPELL_AURA_MOD_INVISIBILITY)) + { + m_target->SetVisibility(VISIBILITY_GROUP_NO_DETECT); + m_target->SetVisibility(VISIBILITY_GROUP_INVISIBILITY); + } + else + m_target->SetVisibility(VISIBILITY_ON); + } + } + } + + // Master of Subtlety + Unit::AuraList const& mDummyAuras = m_target->GetAurasByType(SPELL_AURA_DUMMY); + for(Unit::AuraList::const_iterator i = mDummyAuras.begin();i != mDummyAuras.end(); ++i) + { + if ((*i)->GetSpellProto()->SpellIconID == 2114) + { + if (apply) + { + int32 bp = (*i)->GetModifier()->m_amount; + m_target->CastCustomSpell(m_target,31665,&bp,NULL,NULL,true); + } + else + m_target->CastSpell(m_target,31666,true); + break; + } + } +} + +void Aura::HandleInvisibility(bool apply, bool Real) +{ + if(apply) + { + m_target->m_invisibilityMask |= (1 << m_modifier.m_miscvalue); + + if(Real && m_target->GetTypeId()==TYPEID_PLAYER) + { + // apply glow vision + m_target->SetFlag(PLAYER_FIELD_BYTES2,PLAYER_FIELD_BYTE2_INVISIBILITY_GLOW); + + // drop flag at invisible in bg + if(((Player*)m_target)->InBattleGround()) + if(BattleGround *bg = ((Player*)m_target)->GetBattleGround()) + bg->EventPlayerDroppedFlag((Player*)m_target); + } + + // apply only if not in GM invisibility and not stealth + if(m_target->GetVisibility()==VISIBILITY_ON) + { + // Aura not added yet but visibility code expect temporary add aura + m_target->SetVisibility(VISIBILITY_GROUP_NO_DETECT); + m_target->SetVisibility(VISIBILITY_GROUP_INVISIBILITY); + } + } + else + { + // recalculate value at modifier remove (current aura already removed) + m_target->m_invisibilityMask = 0; + Unit::AuraList const& auras = m_target->GetAurasByType(SPELL_AURA_MOD_INVISIBILITY); + for(Unit::AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + m_target->m_invisibilityMask |= (1 << m_modifier.m_miscvalue); + + // only at real aura remove and if not have different invisibility auras. + if(Real && m_target->m_invisibilityMask==0) + { + // remove glow vision + if(m_target->GetTypeId() == TYPEID_PLAYER) + m_target->RemoveFlag(PLAYER_FIELD_BYTES2,PLAYER_FIELD_BYTE2_INVISIBILITY_GLOW); + + // apply only if not in GM invisibility & not stealthed while invisible + if(m_target->GetVisibility()!=VISIBILITY_OFF) + { + // if have stealth aura then already have stealth visibility + if(!m_target->HasAuraType(SPELL_AURA_MOD_STEALTH)) + m_target->SetVisibility(VISIBILITY_ON); + } + } + } +} + +void Aura::HandleInvisibilityDetect(bool apply, bool Real) +{ + if(apply) + { + m_target->m_detectInvisibilityMask |= (1 << m_modifier.m_miscvalue); + } + else + { + // recalculate value at modifier remove (current aura already removed) + m_target->m_detectInvisibilityMask = 0; + Unit::AuraList const& auras = m_target->GetAurasByType(SPELL_AURA_MOD_INVISIBILITY_DETECTION); + for(Unit::AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + m_target->m_detectInvisibilityMask |= (1 << m_modifier.m_miscvalue); + } + if(Real && m_target->GetTypeId()==TYPEID_PLAYER) + ObjectAccessor::UpdateVisibilityForPlayer((Player*)m_target); +} + +void Aura::HandleAuraModRoot(bool apply, bool Real) +{ + // only at real add/remove aura + if(!Real) + return; + + uint32 apply_stat = UNIT_STAT_ROOT; + if (apply) + { + m_target->addUnitState(UNIT_STAT_ROOT); + m_target->SetUInt64Value (UNIT_FIELD_TARGET, 0); + // probably wrong + m_target->SetFlag(UNIT_FIELD_FLAGS,(apply_stat<<16)); + + //Save last orientation + if( m_target->getVictim() ) + m_target->SetOrientation(m_target->GetAngle(m_target->getVictim())); + + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + WorldPacket data(SMSG_FORCE_MOVE_ROOT, 10); + data.append(m_target->GetPackGUID()); + data << (uint32)2; + m_target->SendMessageToSet(&data,true); + + //Clear unit movement flags + m_target->SetUnitMovementFlags(0); + } + else + ((Creature *)m_target)->StopMoving(); + } + else + { + // Real remove called after current aura remove from lists, check if other similar auras active + if(m_target->HasAuraType(SPELL_AURA_MOD_ROOT)) + return; + + m_target->clearUnitState(UNIT_STAT_ROOT); + // probably wrong + m_target->RemoveFlag(UNIT_FIELD_FLAGS,(apply_stat<<16)); + + if(!m_target->hasUnitState(UNIT_STAT_STUNNED)) // prevent allow move if have also stun effect + { + if(m_target->getVictim() && m_target->isAlive()) + m_target->SetUInt64Value (UNIT_FIELD_TARGET,m_target->getVictim()->GetGUID() ); + + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + WorldPacket data(SMSG_FORCE_MOVE_UNROOT, 10); + data.append(m_target->GetPackGUID()); + data << (uint32)2; + m_target->SendMessageToSet(&data,true); + } + } + } +} + +void Aura::HandleAuraModSilence(bool apply, bool Real) +{ + // only at real add/remove aura + if(!Real) + return; + + if(apply) + { + m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED); + // Stop cast only spells vs PreventionType == SPELL_PREVENTION_TYPE_SILENCE + for (uint32 i = CURRENT_MELEE_SPELL; i < CURRENT_MAX_SPELL;i++) + { + Spell* currentSpell = m_target->m_currentSpells[i]; + if (currentSpell && currentSpell->m_spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE) + { + uint32 state = currentSpell->getState(); + // Stop spells on prepere or casting state + if ( state == SPELL_STATE_PREPARING || state == SPELL_STATE_CASTING ) + { + currentSpell->cancel(); + currentSpell->SetDeletable(true); + m_target->m_currentSpells[i] = NULL; + } + } + } + + switch (GetId()) + { + // Arcane Torrent (Energy) + case 25046: + { + Unit * caster = GetCaster(); + if (!caster) + return; + + // Search Mana Tap auras on caster + int32 energy = 0; + Unit::AuraList const& m_dummyAuras = caster->GetAurasByType(SPELL_AURA_DUMMY); + for(Unit::AuraList::const_iterator i = m_dummyAuras.begin(); i != m_dummyAuras.end(); ++i) + if ((*i)->GetId() == 28734) + ++energy; + if (energy) + { + energy *= 10; + caster->CastCustomSpell(caster, 25048, &energy, NULL, NULL, true); + caster->RemoveAurasDueToSpell(28734); + } + } + } + } + else + { + // Real remove called after current aura remove from lists, check if other similar auras active + if(m_target->HasAuraType(SPELL_AURA_MOD_SILENCE)) + return; + + m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED); + } +} + +void Aura::HandleModThreat(bool apply, bool Real) +{ + // only at real add/remove aura + if(!Real) + return; + + if(!m_target->isAlive()) + return; + + Unit* caster = GetCaster(); + + if(!caster || !caster->isAlive()) + return; + + int level_diff = 0; + int multiplier = 0; + switch (GetId()) + { + // Arcane Shroud + case 26400: + level_diff = m_target->getLevel() - 60; + multiplier = 2; + break; + // The Eye of Diminution + case 28862: + level_diff = m_target->getLevel() - 60; + multiplier = 1; + break; + } + if (level_diff > 0) + m_modifier.m_amount += multiplier * level_diff; + + for(int8 x=0;x < MAX_SPELL_SCHOOL;x++) + { + if(m_modifier.m_miscvalue & int32(1<GetTypeId() == TYPEID_PLAYER) + ApplyPercentModFloatVar(m_target->m_threatModifier[x], m_positive ? m_modifier.m_amount : -m_modifier.m_amount, apply); + } + } +} + +void Aura::HandleAuraModTotalThreat(bool apply, bool Real) +{ + // only at real add/remove aura + if(!Real) + return; + + if(!m_target->isAlive() || m_target->GetTypeId()!= TYPEID_PLAYER) + return; + + Unit* caster = GetCaster(); + + if(!caster || !caster->isAlive()) + return; + + float threatMod = 0.0f; + if(apply) + threatMod = float(m_modifier.m_amount); + else + threatMod = float(-m_modifier.m_amount); + + m_target->getHostilRefManager().threatAssist(caster, threatMod); +} + +void Aura::HandleModTaunt(bool apply, bool Real) +{ + // only at real add/remove aura + if(!Real) + return; + + if(!m_target->isAlive() || !m_target->CanHaveThreatList()) + return; + + Unit* caster = GetCaster(); + + if(!caster || !caster->isAlive() || caster->GetTypeId() != TYPEID_PLAYER) + return; + + if(apply) + { + m_target->TauntApply(caster); + } + else + { + // When taunt aura fades out, mob will switch to previous target if current has less than 1.1 * secondthreat + m_target->TauntFadeOut(caster); + } +} + +/*********************************************************/ +/*** MODIFY SPEED ***/ +/*********************************************************/ +void Aura::HandleAuraModIncreaseSpeed(bool apply, bool Real) +{ + // all applied/removed only at real aura add/remove + if(!Real) + return; + + m_target->UpdateSpeed(MOVE_RUN, true); +} + +void Aura::HandleAuraModIncreaseMountedSpeed(bool apply, bool Real) +{ + // all applied/removed only at real aura add/remove + if(!Real) + return; + + m_target->UpdateSpeed(MOVE_RUN, true); +} + +void Aura::HandleAuraModIncreaseFlightSpeed(bool apply, bool Real) +{ + // all applied/removed only at real aura add/remove + if(!Real) + return; + + // Enable Fly mode for flying mounts + if (m_modifier.m_auraname == SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED) + { + WorldPacket data; + if(apply) + data.Initialize(SMSG_MOVE_SET_CAN_FLY, 12); + else + data.Initialize(SMSG_MOVE_UNSET_CAN_FLY, 12); + data.append(m_target->GetPackGUID()); + data << uint32(0); // unknown + m_target->SendMessageToSet(&data, true); + + //Players on flying mounts must be immune to polymorph + if (m_target->GetTypeId()==TYPEID_PLAYER) + m_target->ApplySpellImmune(GetId(),IMMUNITY_MECHANIC,MECHANIC_POLYMORPH,apply); + + // Dragonmaw Illusion (overwrite mount model, mounted aura already applied) + if( apply && m_target->HasAura(42016,0) && m_target->GetMountID()) + m_target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID,16314); + } + + m_target->UpdateSpeed(MOVE_FLY, true); +} + +void Aura::HandleAuraModIncreaseSwimSpeed(bool apply, bool Real) +{ + // all applied/removed only at real aura add/remove + if(!Real) + return; + + m_target->UpdateSpeed(MOVE_SWIM, true); +} + +void Aura::HandleAuraModDecreaseSpeed(bool apply, bool Real) +{ + // all applied/removed only at real aura add/remove + if(!Real) + return; + + m_target->UpdateSpeed(MOVE_RUN, true); + m_target->UpdateSpeed(MOVE_SWIM, true); + m_target->UpdateSpeed(MOVE_FLY, true); +} + +void Aura::HandleAuraModUseNormalSpeed(bool apply, bool Real) +{ + // all applied/removed only at real aura add/remove + if(!Real) + return; + + m_target->UpdateSpeed(MOVE_RUN, true); + m_target->UpdateSpeed(MOVE_SWIM, true); + m_target->UpdateSpeed(MOVE_FLY, true); +} + +/*********************************************************/ +/*** IMMUNITY ***/ +/*********************************************************/ + +void Aura::HandleModMechanicImmunity(bool apply, bool Real) +{ + uint32 mechanic = 1 << m_modifier.m_miscvalue; + + //immune movement impairment and loss of control + if(GetId()==42292) + mechanic=IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK; + + if(apply && GetSpellProto()->AttributesEx & SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY) + { + Unit::AuraMap& Auras = m_target->GetAuras(); + for(Unit::AuraMap::iterator iter = Auras.begin(), next; iter != Auras.end(); iter = next) + { + next = iter; + ++next; + SpellEntry const *spell = iter->second->GetSpellProto(); + if (!( spell->Attributes & SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY) // spells unaffected by invulnerability + && !iter->second->IsPositive() // only remove negative spells + && spell->Id != GetId()) + { + //check for mechanic mask + if(GetSpellMechanicMask(spell, iter->second->GetEffIndex()) & mechanic) + { + m_target->RemoveAurasDueToSpell(spell->Id); + if(Auras.empty()) + break; + else + next = Auras.begin(); + } + } + } + } + + m_target->ApplySpellImmune(GetId(),IMMUNITY_MECHANIC,m_modifier.m_miscvalue,apply); + + // special cases + switch(m_modifier.m_miscvalue) + { + case MECHANIC_INVULNERABILITY: + m_target->ModifyAuraState(AURA_STATE_FORBEARANCE,apply); + break; + case MECHANIC_SHIELD: + m_target->ModifyAuraState(AURA_STATE_WEAKENED_SOUL,apply); + break; + } + + // Bestial Wrath + if ( GetSpellProto()->SpellFamilyName == SPELLFAMILY_HUNTER && GetSpellProto()->SpellIconID == 1680) + { + // The Beast Within cast on owner if talent present + if ( Unit* owner = m_target->GetOwner() ) + { + // Search talent + Unit::AuraList const& m_dummyAuras = owner->GetAurasByType(SPELL_AURA_DUMMY); + for(Unit::AuraList::const_iterator i = m_dummyAuras.begin(); i != m_dummyAuras.end(); ++i) + { + if ( (*i)->GetSpellProto()->SpellIconID == 2229 ) + { + if (apply) + owner->CastSpell(owner, 34471, true, 0, this); + else + owner->RemoveAurasDueToSpell(34471); + break; + } + } + } + } + + // The Beast Within and Bestial Wrath - immunity + if(GetId() == 19574 || GetId() == 34471) + { + if(apply) + { + m_target->CastSpell(m_target,24395,true); + m_target->CastSpell(m_target,24396,true); + m_target->CastSpell(m_target,24397,true); + m_target->CastSpell(m_target,26592,true); + } + else + { + m_target->RemoveAurasDueToSpell(24395); + m_target->RemoveAurasDueToSpell(24396); + m_target->RemoveAurasDueToSpell(24397); + m_target->RemoveAurasDueToSpell(26592); + } + } +} + +void Aura::HandleAuraModEffectImmunity(bool apply, bool Real) +{ + if(!apply) + { + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + if(((Player*)m_target)->InBattleGround()) + { + BattleGround *bg = ((Player*)m_target)->GetBattleGround(); + if(bg) + { + switch(bg->GetTypeID()) + { + case BATTLEGROUND_AV: + { + break; + } + case BATTLEGROUND_WS: + { + // Warsong Flag, horde // Silverwing Flag, alliance + if(GetId() == 23333 || GetId() == 23335) + bg->EventPlayerDroppedFlag(((Player*)m_target)); + break; + } + case BATTLEGROUND_AB: + { + break; + } + case BATTLEGROUND_EY: + { + if(GetId() == 34976) + bg->EventPlayerDroppedFlag(((Player*)m_target)); + break; + } + } + } + } + } + } + + m_target->ApplySpellImmune(GetId(),IMMUNITY_EFFECT,m_modifier.m_miscvalue,apply); +} + +void Aura::HandleAuraModStateImmunity(bool apply, bool Real) +{ + if(apply && Real && GetSpellProto()->AttributesEx & SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY) + { + Unit::AuraList const& auraList = m_target->GetAurasByType(AuraType(m_modifier.m_miscvalue)); + for(Unit::AuraList::const_iterator itr = auraList.begin(); itr != auraList.end();) + { + if (auraList.front() != this) // skip itself aura (it already added) + { + m_target->RemoveAurasDueToSpell(auraList.front()->GetId()); + itr = auraList.begin(); + } + else + ++itr; + } + } + + m_target->ApplySpellImmune(GetId(),IMMUNITY_STATE,m_modifier.m_miscvalue,apply); +} + +void Aura::HandleAuraModSchoolImmunity(bool apply, bool Real) +{ + m_target->ApplySpellImmune(GetId(),IMMUNITY_SCHOOL,m_modifier.m_miscvalue,apply); + + if(Real && apply && GetSpellProto()->AttributesEx & SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY) + { + if(IsPositiveSpell(GetId())) //Only positive immunity removes auras + { + uint32 school_mask = m_modifier.m_miscvalue; + Unit::AuraMap& Auras = m_target->GetAuras(); + for(Unit::AuraMap::iterator iter = Auras.begin(), next; iter != Auras.end(); iter = next) + { + next = iter; + ++next; + SpellEntry const *spell = iter->second->GetSpellProto(); + if((GetSpellSchoolMask(spell) & school_mask)//Check for school mask + && !( spell->Attributes & SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY) //Spells unaffected by invulnerability + && !iter->second->IsPositive() //Don't remove positive spells + && spell->Id != GetId() ) //Don't remove self + { + m_target->RemoveAurasDueToSpell(spell->Id); + if(Auras.empty()) + break; + else + next = Auras.begin(); + } + } + } + } + if( Real && GetSpellProto()->Mechanic == MECHANIC_BANISH ) + { + if( apply ) + m_target->addUnitState(UNIT_STAT_ISOLATED); + else + m_target->clearUnitState(UNIT_STAT_ISOLATED); + } +} + +void Aura::HandleAuraModDmgImmunity(bool apply, bool Real) +{ + m_target->ApplySpellImmune(GetId(),IMMUNITY_DAMAGE,m_modifier.m_miscvalue,apply); +} + +void Aura::HandleAuraModDispelImmunity(bool apply, bool Real) +{ + // all applied/removed only at real aura add/remove + if(!Real) + return; + + m_target->ApplySpellDispelImmunity(m_spellProto, DispelType(m_modifier.m_miscvalue), apply); +} + +void Aura::HandleAuraProcTriggerSpell(bool apply, bool Real) +{ + if(!Real) + return; + + if(apply) + { + // some spell have charges by functionality not have its in spell data + switch (GetId()) + { + case 28200: // Ascendance (Talisman of Ascendance trinket) + m_procCharges = 6; + UpdateAuraCharges(); + break; + default: break; + } + } +} + +void Aura::HandleAuraModStalked(bool apply, bool Real) +{ + // used by spells: Hunter's Mark, Mind Vision, Syndicate Tracker (MURP) DND + if(apply) + m_target->SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TRACK_UNIT); + else + m_target->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TRACK_UNIT); +} + +/*********************************************************/ +/*** PERIODIC ***/ +/*********************************************************/ + +void Aura::HandlePeriodicTriggerSpell(bool apply, bool Real) +{ + if (m_periodicTimer <= 0) + m_periodicTimer += m_modifier.periodictime; + + m_isPeriodic = apply; + m_isTrigger = apply; + + // Curse of the Plaguebringer + if (!apply && m_spellProto->Id == 29213 && m_removeMode!=AURA_REMOVE_BY_DISPEL) + { + // Cast Wrath of the Plaguebringer if not dispelled + m_target->CastSpell(m_target, 29214, true, 0, this); + } +} + +void Aura::HandlePeriodicEnergize(bool apply, bool Real) +{ + if (m_periodicTimer <= 0) + m_periodicTimer += m_modifier.periodictime; + + m_isPeriodic = apply; +} + +void Aura::HandlePeriodicHeal(bool apply, bool Real) +{ + if (m_periodicTimer <= 0) + m_periodicTimer += m_modifier.periodictime; + + m_isPeriodic = apply; + + // only at real apply + if (Real && apply && GetSpellProto()->Mechanic == MECHANIC_BANDAGE) + { + // provided m_target as original caster to prevent apply aura caster selection for this negative buff + m_target->CastSpell(m_target,11196,true,NULL,this,m_target->GetGUID()); + } + + // For prevent double apply bonuses + bool loading = (m_target->GetTypeId() == TYPEID_PLAYER && ((Player*)m_target)->GetSession()->PlayerLoading()); + + if(!loading && apply) + { + switch (m_spellProto->SpellFamilyName) + { + case SPELLFAMILY_DRUID: + { + // Rejuvenation + if(m_spellProto->SpellFamilyFlags & 0x0000000000000010LL) + { + if(Unit* caster = GetCaster()) + { + Unit::AuraList const& classScripts = caster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for(Unit::AuraList::const_iterator k = classScripts.begin(); k != classScripts.end(); ++k) + { + int32 tickcount = GetSpellDuration(m_spellProto) / m_spellProto->EffectAmplitude[m_effIndex]; + switch((*k)->GetModifier()->m_miscvalue) + { + case 4953: // Increased Rejuvenation Healing - Harold's Rejuvenating Broach Aura + case 4415: // Increased Rejuvenation Healing - Idol of Rejuvenation Aura + { + m_modifier.m_amount += (*k)->GetModifier()->m_amount / tickcount; + break; + } + } + } + } + } + } + } + } +} + +void Aura::HandlePeriodicDamage(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + if (m_periodicTimer <= 0) + m_periodicTimer += m_modifier.periodictime; + + m_isPeriodic = apply; + + // For prevent double apply bonuses + bool loading = (m_target->GetTypeId() == TYPEID_PLAYER && ((Player*)m_target)->GetSession()->PlayerLoading()); + + Unit *caster = GetCaster(); + + switch (m_spellProto->SpellFamilyName) + { + case SPELLFAMILY_GENERIC: + { + // Pounce Bleed + if ( m_spellProto->SpellIconID == 147 && m_spellProto->SpellVisual == 0 ) + { + // $AP*0.18/6 bonus per tick + if (apply && !loading && caster) + m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * 3 / 100); + return; + } + break; + } + case SPELLFAMILY_WARRIOR: + { + // Rend + if (m_spellProto->SpellFamilyFlags & 0x0000000000000020LL) + { + // 0.00743*(($MWB+$mwb)/2+$AP/14*$MWS) bonus per tick + if (apply && !loading && caster) + { + float ap = caster->GetTotalAttackPowerValue(BASE_ATTACK); + int32 mws = caster->GetAttackTime(BASE_ATTACK); + float mwb_min = caster->GetWeaponDamageRange(BASE_ATTACK,MINDAMAGE); + float mwb_max = caster->GetWeaponDamageRange(BASE_ATTACK,MAXDAMAGE); + // WARNING! in 3.0 multipler 0.00743f change to 0.6 + m_modifier.m_amount+=int32(((mwb_min+mwb_max)/2+ap*mws/14000)*0.00743f); + } + return; + } + break; + } + case SPELLFAMILY_DRUID: + { + // Rake + if (m_spellProto->SpellFamilyFlags & 0x0000000000001000LL) + { + // $AP*0.06/3 bonus per tick + if (apply && !loading && caster) + m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * 2 / 100); + return; + } + // Lacerate + if (m_spellProto->SpellFamilyFlags & 0x000000010000000000LL) + { + // $AP*0.05/5 bonus per tick + if (apply && !loading && caster) + m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) / 100); + return; + } + // Rip + if (m_spellProto->SpellFamilyFlags & 0x000000000000800000LL) + { + // $AP * min(0.06*$cp, 0.24)/6 [Yes, there is no difference, wheather 4 or 5 CPs are being used] + if (apply && !loading && caster && caster->GetTypeId() == TYPEID_PLAYER) + { + uint8 cp = ((Player*)caster)->GetComboPoints(); + + // Idol of Feral Shadows. Cant be handled as SpellMod in SpellAura:Dummy due its dependency from CPs + Unit::AuraList const& dummyAuras = caster->GetAurasByType(SPELL_AURA_DUMMY); + for(Unit::AuraList::const_iterator itr = dummyAuras.begin(); itr != dummyAuras.end(); ++itr) + { + if((*itr)->GetId()==34241) + { + m_modifier.m_amount += cp * (*itr)->GetModifier()->m_amount; + break; + } + } + + if (cp > 4) cp = 4; + m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * cp / 100); + } + return; + } + break; + } + case SPELLFAMILY_ROGUE: + { + // Deadly poison aura state + if((m_spellProto->SpellFamilyFlags & 0x10000) && m_spellProto->SpellVisual==5100) + { + if(apply) + m_target->ModifyAuraState(AURA_STATE_DEADLY_POISON,true); + else + { + // current aura already removed, search present of another + bool found = false; + Unit::AuraList const& auras = m_target->GetAurasByType(SPELL_AURA_PERIODIC_DAMAGE); + for(Unit::AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + SpellEntry const* itr_spell = (*itr)->GetSpellProto(); + if(itr_spell && itr_spell->SpellFamilyName==SPELLFAMILY_ROGUE && (itr_spell->SpellFamilyFlags & 0x10000) && itr_spell->SpellVisual==5100) + { + found = true; + break; + } + } + // this has been last deadly poison aura + if(!found) + m_target->ModifyAuraState(AURA_STATE_DEADLY_POISON,false); + } + return; + } + // Rupture + if (m_spellProto->SpellFamilyFlags & 0x000000000000100000LL) + { + // Dmg/tick = $AP*min(0.01*$cp, 0.03) [Like Rip: only the first three CP inrease the contribution from AP] + if (apply && !loading && caster && caster->GetTypeId() == TYPEID_PLAYER) + { + uint8 cp = ((Player*)caster)->GetComboPoints(); + if (cp > 3) cp = 3; + m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * cp / 100); + } + return; + } + // Garrote + if (m_spellProto->SpellFamilyFlags & 0x000000000000000100LL) + { + // $AP*0.18/6 bonus per tick + if (apply && !loading && caster) + m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * 3 / 100); + return; + } + break; + } + case SPELLFAMILY_HUNTER: + { + // Serpent Sting + if (m_spellProto->SpellFamilyFlags & 0x0000000000004000LL) + { + // $RAP*0.1/5 bonus per tick + if (apply && !loading && caster) + m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(RANGED_ATTACK) * 10 / 500); + return; + } + // Immolation Trap + if (m_spellProto->SpellFamilyFlags & 0x0000000000000004LL && m_spellProto->SpellIconID == 678) + { + // $RAP*0.1/5 bonus per tick + if (apply && !loading && caster) + m_modifier.m_amount += int32(caster->GetTotalAttackPowerValue(RANGED_ATTACK) * 10 / 500); + return; + } + break; + } + case SPELLFAMILY_PALADIN: + { + // Consecration + if (m_spellProto->SpellFamilyFlags & 0x0000000000000020LL) + { + if (apply && !loading) + { + if(Unit* caster = GetCaster()) + { + Unit::AuraList const& classScripts = caster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for(Unit::AuraList::const_iterator k = classScripts.begin(); k != classScripts.end(); ++k) + { + int32 tickcount = GetSpellDuration(m_spellProto) / m_spellProto->EffectAmplitude[m_effIndex]; + switch((*k)->GetModifier()->m_miscvalue) + { + case 5147: // Improved Consecration - Libram of the Eternal Rest + { + m_modifier.m_amount += (*k)->GetModifier()->m_amount / tickcount; + break; + } + } + } + } + } + return; + } + break; + } + default: + break; + } +} + +void Aura::HandlePeriodicDamagePCT(bool apply, bool Real) +{ + if (m_periodicTimer <= 0) + m_periodicTimer += m_modifier.periodictime; + + m_isPeriodic = apply; +} + +void Aura::HandlePeriodicLeech(bool apply, bool Real) +{ + if (m_periodicTimer <= 0) + m_periodicTimer += m_modifier.periodictime; + + m_isPeriodic = apply; +} + +void Aura::HandlePeriodicManaLeech(bool apply, bool Real) +{ + if (m_periodicTimer <= 0) + m_periodicTimer += m_modifier.periodictime; + + m_isPeriodic = apply; +} + +/*********************************************************/ +/*** MODIFY STATS ***/ +/*********************************************************/ + +/********************************/ +/*** RESISTANCE ***/ +/********************************/ + +void Aura::HandleAuraModResistanceExclusive(bool apply, bool Real) +{ + for(int8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL;x++) + { + if(m_modifier.m_miscvalue & int32(1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), BASE_VALUE, float(m_modifier.m_amount), apply); + if(m_target->GetTypeId() == TYPEID_PLAYER) + m_target->ApplyResistanceBuffModsMod(SpellSchools(x),m_positive,m_modifier.m_amount, apply); + } + } +} + +void Aura::HandleAuraModResistance(bool apply, bool Real) +{ + for(int8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL;x++) + { + if(m_modifier.m_miscvalue & int32(1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), TOTAL_VALUE, float(m_modifier.m_amount), apply); + if(m_target->GetTypeId() == TYPEID_PLAYER || ((Creature*)m_target)->isPet()) + m_target->ApplyResistanceBuffModsMod(SpellSchools(x),m_positive,m_modifier.m_amount, apply); + } + } + + // Faerie Fire (druid versions) + if( m_spellProto->SpellIconID == 109 && + m_spellProto->SpellFamilyName == SPELLFAMILY_DRUID && + m_spellProto->SpellFamilyFlags & 0x0000000000000400LL ) + { + m_target->ModifyAuraState(AURA_STATE_FAERIE_FIRE,apply); + } +} + +void Aura::HandleAuraModBaseResistancePCT(bool apply, bool Real) +{ + // only players have base stats + if(m_target->GetTypeId() != TYPEID_PLAYER) + { + //pets only have base armor + if(((Creature*)m_target)->isPet() && (m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL)) + { + m_target->HandleStatModifier(UNIT_MOD_ARMOR, BASE_PCT, float(m_modifier.m_amount), apply); + } + } + else + { + for(int8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL;x++) + { + if(m_modifier.m_miscvalue & int32(1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), BASE_PCT, float(m_modifier.m_amount), apply); + } + } + } +} + +void Aura::HandleModResistancePercent(bool apply, bool Real) +{ + for(int8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; i++) + { + if(m_modifier.m_miscvalue & int32(1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), TOTAL_PCT, float(m_modifier.m_amount), apply); + if(m_target->GetTypeId() == TYPEID_PLAYER || ((Creature*)m_target)->isPet()) + { + m_target->ApplyResistanceBuffModsPercentMod(SpellSchools(i),true,m_modifier.m_amount, apply); + m_target->ApplyResistanceBuffModsPercentMod(SpellSchools(i),false,m_modifier.m_amount, apply); + } + } + } +} + +void Aura::HandleModBaseResistance(bool apply, bool Real) +{ + // only players have base stats + if(m_target->GetTypeId() != TYPEID_PLAYER) + { + //only pets have base stats + if(((Creature*)m_target)->isPet() && (m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL)) + m_target->HandleStatModifier(UNIT_MOD_ARMOR, TOTAL_VALUE, float(m_modifier.m_amount), apply); + } + else + { + for(int i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; i++) + if(m_modifier.m_miscvalue & (1<HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), TOTAL_VALUE, float(m_modifier.m_amount), apply); + } +} + +/********************************/ +/*** STAT ***/ +/********************************/ + +void Aura::HandleAuraModStat(bool apply, bool Real) +{ + if (m_modifier.m_miscvalue < -2 || m_modifier.m_miscvalue > 4) + { + sLog.outError("WARNING: Spell %u effect %u have unsupported misc value (%i) for SPELL_AURA_MOD_STAT ",GetId(),GetEffIndex(),m_modifier.m_miscvalue); + return; + } + + for(int32 i = STAT_STRENGTH; i < MAX_STATS; i++) + { + // -1 or -2 is all stats ( misc < -2 checked in function beginning ) + if (m_modifier.m_miscvalue < 0 || m_modifier.m_miscvalue == i) + { + //m_target->ApplyStatMod(Stats(i), m_modifier.m_amount,apply); + m_target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_VALUE, float(m_modifier.m_amount), apply); + if(m_target->GetTypeId() == TYPEID_PLAYER || ((Creature*)m_target)->isPet()) + m_target->ApplyStatBuffMod(Stats(i),m_modifier.m_amount,apply); + } + } +} + +void Aura::HandleModPercentStat(bool apply, bool Real) +{ + if (m_modifier.m_miscvalue < -1 || m_modifier.m_miscvalue > 4) + { + sLog.outError("WARNING: Misc Value for SPELL_AURA_MOD_PERCENT_STAT not valid"); + return; + } + + // only players have base stats + if (m_target->GetTypeId() != TYPEID_PLAYER) + return; + + for (int32 i = STAT_STRENGTH; i < MAX_STATS; ++i) + { + if(m_modifier.m_miscvalue == i || m_modifier.m_miscvalue == -1) + { + m_target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), BASE_PCT, float(m_modifier.m_amount), apply); + } + } +} + +void Aura::HandleModSpellDamagePercentFromStat(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + // Magic damage modifiers implemented in Unit::SpellDamageBonus + // This information for client side use only + // Recalculate bonus + ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); +} + +void Aura::HandleModSpellHealingPercentFromStat(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + // Recalculate bonus + ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); +} + +void Aura::HandleAuraModDispelResist(bool apply, bool Real) +{ + if(!Real || !apply) + return; + + if(GetId()==33206) + m_target->CastSpell(m_target,44416,true,NULL,this,GetCasterGUID()); +} + +void Aura::HandleModSpellDamagePercentFromAttackPower(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + // Magic damage modifiers implemented in Unit::SpellDamageBonus + // This information for client side use only + // Recalculate bonus + ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); +} + +void Aura::HandleModSpellHealingPercentFromAttackPower(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + // Recalculate bonus + ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); +} + +void Aura::HandleModHealingDone(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + // implemented in Unit::SpellHealingBonus + // this information is for client side only + ((Player*)m_target)->UpdateSpellDamageAndHealingBonus(); +} + +void Aura::HandleModTotalPercentStat(bool apply, bool Real) +{ + if (m_modifier.m_miscvalue < -1 || m_modifier.m_miscvalue > 4) + { + sLog.outError("WARNING: Misc Value for SPELL_AURA_MOD_PERCENT_STAT not valid"); + return; + } + + //save current and max HP before applying aura + uint32 curHPValue = m_target->GetHealth(); + uint32 maxHPValue = m_target->GetMaxHealth(); + + for (int32 i = STAT_STRENGTH; i < MAX_STATS; i++) + { + if(m_modifier.m_miscvalue == i || m_modifier.m_miscvalue == -1) + { + m_target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_PCT, float(m_modifier.m_amount), apply); + if(m_target->GetTypeId() == TYPEID_PLAYER || ((Creature*)m_target)->isPet()) + m_target->ApplyStatPercentBuffMod(Stats(i), m_modifier.m_amount, apply ); + } + } + + //recalculate current HP/MP after applying aura modifications (only for spells with 0x10 flag) + if ((m_modifier.m_miscvalue == STAT_STAMINA) && (maxHPValue > 0) && (m_spellProto->Attributes & 0x10)) + { + // newHP = (curHP / maxHP) * newMaxHP = (newMaxHP * curHP) / maxHP -> which is better because no int -> double -> int conversion is needed + uint32 newHPValue = (m_target->GetMaxHealth() * curHPValue) / maxHPValue; + m_target->SetHealth(newHPValue); + } +} + +void Aura::HandleAuraModResistenceOfStatPercent(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + if(m_modifier.m_miscvalue != SPELL_SCHOOL_MASK_NORMAL) + { + // support required adding replace UpdateArmor by loop by UpdateResistence at intelect update + // and include in UpdateResistence same code as in UpdateArmor for aura mod apply. + sLog.outError("Aura SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT(182) need adding support for non-armor resistences!"); + return; + } + + // Recalculate Armor + m_target->UpdateArmor(); +} + +/********************************/ +/*** HEAL & ENERGIZE ***/ +/********************************/ +void Aura::HandleAuraModTotalHealthPercentRegen(bool apply, bool Real) +{ + /* + Need additional checking for auras who reduce or increase healing, magic effect like Dumpen Magic, + so this aura not fully working. + */ + if(apply) + { + if(!m_target->isAlive()) + return; + + if((GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) && !m_target->IsSitState()) + m_target->SetStandState(PLAYER_STATE_SIT); + + if(m_periodicTimer <= 0) + { + m_periodicTimer += m_modifier.periodictime; + + if(m_target->GetHealth() < m_target->GetMaxHealth()) + { + // PeriodicTick can cast triggered spells with stats changes + PeriodicTick(); + } + } + } + + m_isPeriodic = apply; +} + +void Aura::HandleAuraModTotalManaPercentRegen(bool apply, bool Real) +{ + if((GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) && apply && !m_target->IsSitState()) + m_target->SetStandState(PLAYER_STATE_SIT); + if(apply) + { + if(m_modifier.periodictime == 0) + m_modifier.periodictime = 1000; + if(m_periodicTimer <= 0 && m_target->getPowerType() == POWER_MANA) + { + m_periodicTimer += m_modifier.periodictime; + + if(m_target->GetPower(POWER_MANA) < m_target->GetMaxPower(POWER_MANA)) + { + // PeriodicTick can cast triggered spells with stats changes + PeriodicTick(); + } + } + } + + m_isPeriodic = apply; +} + +void Aura::HandleModRegen(bool apply, bool Real) // eating +{ + if(apply) + { + if(!m_target->isAlive()) + return; + + if ((GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) && !m_target->IsSitState()) + m_target->SetStandState(PLAYER_STATE_SIT); + + if(m_periodicTimer <= 0) + { + m_periodicTimer += 5000; + int32 gain = m_target->ModifyHealth(m_modifier.m_amount); + Unit *caster = GetCaster(); + if (caster) + { + SpellEntry const *spellProto = GetSpellProto(); + if (spellProto) + m_target->getHostilRefManager().threatAssist(caster, float(gain) * 0.5f, spellProto); + } + } + } + + m_isPeriodic = apply; +} + +void Aura::HandleModPowerRegen(bool apply, bool Real) // drinking +{ + if ((GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) && apply && !m_target->IsSitState()) + m_target->SetStandState(PLAYER_STATE_SIT); + + if(apply && m_periodicTimer <= 0) + { + m_periodicTimer += 2000; + + Powers pt = m_target->getPowerType(); + if(int32(pt) != m_modifier.m_miscvalue) + return; + + if ( GetSpellProto()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED ) + { + // eating anim + m_target->HandleEmoteCommand(EMOTE_ONESHOT_EAT); + } + else if( GetId() == 20577 ) + { + // cannibalize anim + m_target->HandleEmoteCommand(398); + } + + // Warrior talent, gain 1 rage every 3 seconds while in combat + if(pt == POWER_RAGE && m_target->isInCombat()) + { + m_target->ModifyPower(pt, m_modifier.m_amount*10/17); + m_periodicTimer += 1000; + } + } + m_isPeriodic = apply; + if (Real && m_target->GetTypeId() == TYPEID_PLAYER && m_modifier.m_miscvalue == POWER_MANA) + ((Player*)m_target)->UpdateManaRegen(); +} + +void Aura::HandleModPowerRegenPCT(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + if (m_target->GetTypeId() != TYPEID_PLAYER) + return; + + // Update manaregen value + if (m_modifier.m_miscvalue == POWER_MANA) + ((Player*)m_target)->UpdateManaRegen(); +} + +void Aura::HandleModManaRegen(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + if (m_target->GetTypeId() != TYPEID_PLAYER) + return; + + //Note: an increase in regen does NOT cause threat. + ((Player*)m_target)->UpdateManaRegen(); +} + +void Aura::HandleComprehendLanguage(bool apply, bool Real) +{ + if(apply) + m_target->SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_COMPREHEND_LANG); + else + m_target->RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_COMPREHEND_LANG); +} + +void Aura::HandleAuraModIncreaseHealth(bool apply, bool Real) +{ + // Special case with temporary increase max/current health + switch(GetId()) + { + case 12976: // Warrior Last Stand triggered spell + case 28726: // Nightmare Seed ( Nightmare Seed ) + case 34511: // Valor (Bulwark of Kings, Bulwark of the Ancient Kings) + case 44055: // Tremendous Fortitude (Battlemaster's Alacrity) + { + if(Real) + { + if(apply) + { + m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(m_modifier.m_amount), apply); + m_target->ModifyHealth(m_modifier.m_amount); + } + else + { + if (int32(m_target->GetHealth()) > m_modifier.m_amount) + m_target->ModifyHealth(-m_modifier.m_amount); + else + m_target->SetHealth(1); + m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(m_modifier.m_amount), apply); + } + } + return; + } + } + + // generic case + m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(m_modifier.m_amount), apply); +} + +void Aura::HandleAuraModIncreaseMaxHealth(bool apply, bool Real) +{ + uint32 oldhealth = m_target->GetHealth(); + double healthPercentage = (double)oldhealth / (double)m_target->GetMaxHealth(); + + m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(m_modifier.m_amount), apply); + + // refresh percentage + if(oldhealth > 0) + { + uint32 newhealth = uint32(ceil((double)m_target->GetMaxHealth() * healthPercentage)); + if(newhealth==0) + newhealth = 1; + + m_target->SetHealth(newhealth); + } +} + +void Aura::HandleAuraModIncreaseEnergy(bool apply, bool Real) +{ + Powers powerType = m_target->getPowerType(); + if(int32(powerType) != m_modifier.m_miscvalue) + return; + + m_target->HandleStatModifier(UnitMods(UNIT_MOD_POWER_START + powerType), TOTAL_VALUE, float(m_modifier.m_amount), apply); +} + +void Aura::HandleAuraModIncreaseEnergyPercent(bool apply, bool Real) +{ + Powers powerType = m_target->getPowerType(); + if(int32(powerType) != m_modifier.m_miscvalue) + return; + + m_target->HandleStatModifier(UnitMods(UNIT_MOD_POWER_START + powerType), TOTAL_PCT, float(m_modifier.m_amount), apply); +} + +void Aura::HandleAuraModIncreaseHealthPercent(bool apply, bool Real) +{ + //m_target->ApplyMaxHealthPercentMod(m_modifier.m_amount,apply); + m_target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_PCT, float(m_modifier.m_amount), apply); +} + +/********************************/ +/*** FIGHT ***/ +/********************************/ + +void Aura::HandleAuraModParryPercent(bool apply, bool Real) +{ + if(m_target->GetTypeId()!=TYPEID_PLAYER) + return; + + ((Player*)m_target)->UpdateParryPercentage(); +} + +void Aura::HandleAuraModDodgePercent(bool apply, bool Real) +{ + if(m_target->GetTypeId()!=TYPEID_PLAYER) + return; + + ((Player*)m_target)->UpdateDodgePercentage(); + //sLog.outError("BONUS DODGE CHANCE: + %f", float(m_modifier.m_amount)); +} + +void Aura::HandleAuraModBlockPercent(bool apply, bool Real) +{ + if(m_target->GetTypeId()!=TYPEID_PLAYER) + return; + + ((Player*)m_target)->UpdateBlockPercentage(); + //sLog.outError("BONUS BLOCK CHANCE: + %f", float(m_modifier.m_amount)); +} + +void Aura::HandleAuraModRegenInterrupt(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + if(m_target->GetTypeId()!=TYPEID_PLAYER) + return; + + ((Player*)m_target)->UpdateManaRegen(); +} + +void Aura::HandleAuraModCritPercent(bool apply, bool Real) +{ + if(m_target->GetTypeId()!=TYPEID_PLAYER) + return; + + // apply item specific bonuses for already equipped weapon + if(Real) + { + for(int i = 0; i < MAX_ATTACK; ++i) + if(Item* pItem = ((Player*)m_target)->GetWeaponForAttack(WeaponAttackType(i))) + ((Player*)m_target)->_ApplyWeaponDependentAuraCritMod(pItem,WeaponAttackType(i),this,apply); + } + + // mods must be applied base at equipped weapon class and subclass comparison + // with spell->EquippedItemClass and EquippedItemSubClassMask and EquippedItemInventoryTypeMask + // m_modifier.m_miscvalue comparison with item generated damage types + + if (GetSpellProto()->EquippedItemClass == -1) + { + ((Player*)m_target)->HandleBaseModValue(CRIT_PERCENTAGE, FLAT_MOD, float (m_modifier.m_amount), apply); + ((Player*)m_target)->HandleBaseModValue(OFFHAND_CRIT_PERCENTAGE, FLAT_MOD, float (m_modifier.m_amount), apply); + ((Player*)m_target)->HandleBaseModValue(RANGED_CRIT_PERCENTAGE, FLAT_MOD, float (m_modifier.m_amount), apply); + } + else + { + // done in Player::_ApplyWeaponDependentAuraMods + } +} + +void Aura::HandleModHitChance(bool apply, bool Real) +{ + m_target->m_modMeleeHitChance += apply ? m_modifier.m_amount : (-m_modifier.m_amount); + m_target->m_modRangedHitChance += apply ? m_modifier.m_amount : (-m_modifier.m_amount); +} + +void Aura::HandleModSpellHitChance(bool apply, bool Real) +{ + m_target->m_modSpellHitChance += apply ? m_modifier.m_amount: (-m_modifier.m_amount); +} + +void Aura::HandleModSpellCritChance(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + ((Player*)m_target)->UpdateAllSpellCritChances(); + } + else + { + m_target->m_baseSpellCritChance += apply ? m_modifier.m_amount:(-m_modifier.m_amount); + } +} + +void Aura::HandleModSpellCritChanceShool(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + for(int school = SPELL_SCHOOL_NORMAL; school < MAX_SPELL_SCHOOL; ++school) + if (m_modifier.m_miscvalue & (1<UpdateSpellCritChance(school); +} + +/********************************/ +/*** ATTACK SPEED ***/ +/********************************/ + +void Aura::HandleModCastingSpeed(bool apply, bool Real) +{ + m_target->ApplyCastTimePercentMod(m_modifier.m_amount,apply); +} + +void Aura::HandleModMeleeRangedSpeedPct(bool apply, bool Real) +{ + m_target->ApplyAttackTimePercentMod(BASE_ATTACK,m_modifier.m_amount,apply); + m_target->ApplyAttackTimePercentMod(OFF_ATTACK,m_modifier.m_amount,apply); + m_target->ApplyAttackTimePercentMod(RANGED_ATTACK, m_modifier.m_amount, apply); +} + +void Aura::HandleModCombatSpeedPct(bool apply, bool Real) +{ + m_target->ApplyCastTimePercentMod(m_modifier.m_amount,apply); + m_target->ApplyAttackTimePercentMod(BASE_ATTACK,m_modifier.m_amount,apply); + m_target->ApplyAttackTimePercentMod(OFF_ATTACK,m_modifier.m_amount,apply); + m_target->ApplyAttackTimePercentMod(RANGED_ATTACK, m_modifier.m_amount, apply); +} + +void Aura::HandleModAttackSpeed(bool apply, bool Real) +{ + if(!m_target->isAlive() ) + return; + + m_target->ApplyAttackTimePercentMod(BASE_ATTACK,m_modifier.m_amount,apply); +} + +void Aura::HandleHaste(bool apply, bool Real) +{ + m_target->ApplyAttackTimePercentMod(BASE_ATTACK, m_modifier.m_amount,apply); + m_target->ApplyAttackTimePercentMod(OFF_ATTACK, m_modifier.m_amount,apply); + m_target->ApplyAttackTimePercentMod(RANGED_ATTACK,m_modifier.m_amount,apply); +} + +void Aura::HandleAuraModRangedHaste(bool apply, bool Real) +{ + m_target->ApplyAttackTimePercentMod(RANGED_ATTACK, m_modifier.m_amount, apply); +} + +void Aura::HandleRangedAmmoHaste(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + m_target->ApplyAttackTimePercentMod(RANGED_ATTACK,m_modifier.m_amount, apply); +} + +/********************************/ +/*** ATTACK POWER ***/ +/********************************/ + +void Aura::HandleAuraModAttackPower(bool apply, bool Real) +{ + m_target->HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(m_modifier.m_amount), apply); +} + +void Aura::HandleAuraModRangedAttackPower(bool apply, bool Real) +{ + if((m_target->getClassMask() & CLASSMASK_WAND_USERS)!=0) + return; + + m_target->HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(m_modifier.m_amount), apply); +} + +void Aura::HandleAuraAttackPowerAttacker(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + Unit *caster = GetCaster(); + + if (!caster) + return; + + // Hunter's Mark + if (m_spellProto->SpellFamilyName == SPELLFAMILY_HUNTER && m_spellProto->SpellFamilyFlags & 0x0000000000000400LL) + { + // Check Improved Hunter's Mark bonus on caster + Unit::AuraList const& mOverrideClassScript = caster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for(Unit::AuraList::const_iterator i = mOverrideClassScript.begin(); i != mOverrideClassScript.end(); ++i) + { + Modifier* mod = (*i)->GetModifier(); + // mproved Hunter's Mark script from 5236 to 5240 + if (mod->m_miscvalue >= 5236 && mod->m_miscvalue <= 5240) + { + // Get amount of ranged bonus for this spell.. + int32 ranged_bonus = caster->CalculateSpellDamage(m_spellProto, 1, m_spellProto->EffectBasePoints[1], m_target); + // Set melee attack power bonus % from ranged depends from Improved mask aura + m_modifier.m_amount = mod->m_amount * ranged_bonus / 100; + m_currentBasePoints = m_modifier.m_amount; + break; + } + } + return; + } +} + +void Aura::HandleAuraModAttackPowerPercent(bool apply, bool Real) +{ + //UNIT_FIELD_ATTACK_POWER_MULTIPLIER = multiplier - 1 + m_target->HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT, float(m_modifier.m_amount), apply); +} + +void Aura::HandleAuraModRangedAttackPowerPercent(bool apply, bool Real) +{ + if((m_target->getClassMask() & CLASSMASK_WAND_USERS)!=0) + return; + + //UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER = multiplier - 1 + m_target->HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_PCT, float(m_modifier.m_amount), apply); +} + +void Aura::HandleAuraModRangedAttackPowerOfStatPercent(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + if(m_target->GetTypeId() == TYPEID_PLAYER && (m_target->getClassMask() & CLASSMASK_WAND_USERS)!=0) + return; + + if(m_modifier.m_miscvalue != STAT_INTELLECT) + { + // support required adding UpdateAttackPowerAndDamage calls at stat update + sLog.outError("Aura SPELL_AURA_MOD_RANGED_ATTACK_POWER_OF_STAT_PERCENT (212) need support non-intelect stats!"); + return; + } + + // Recalculate bonus + ((Player*)m_target)->UpdateAttackPowerAndDamage(true); +} + +/********************************/ +/*** DAMAGE BONUS ***/ +/********************************/ +void Aura::HandleModDamageDone(bool apply, bool Real) +{ + // apply item specific bonuses for already equipped weapon + if(Real && m_target->GetTypeId()==TYPEID_PLAYER) + { + for(int i = 0; i < MAX_ATTACK; ++i) + if(Item* pItem = ((Player*)m_target)->GetWeaponForAttack(WeaponAttackType(i))) + ((Player*)m_target)->_ApplyWeaponDependentAuraDamageMod(pItem,WeaponAttackType(i),this,apply); + } + + // m_modifier.m_miscvalue is bitmask of spell schools + // 1 ( 0-bit ) - normal school damage (SPELL_SCHOOL_MASK_NORMAL) + // 126 - full bitmask all magic damages (SPELL_SCHOOL_MASK_MAGIC) including wands + // 127 - full bitmask any damages + // + // mods must be applied base at equipped weapon class and subclass comparison + // with spell->EquippedItemClass and EquippedItemSubClassMask and EquippedItemInventoryTypeMask + // m_modifier.m_miscvalue comparison with item generated damage types + + if((m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL) != 0) + { + // apply generic physical damage bonuses including wand case + if (GetSpellProto()->EquippedItemClass == -1 || m_target->GetTypeId() != TYPEID_PLAYER) + { + m_target->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, float(m_modifier.m_amount), apply); + m_target->HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, float(m_modifier.m_amount), apply); + m_target->HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_VALUE, float(m_modifier.m_amount), apply); + } + else + { + // done in Player::_ApplyWeaponDependentAuraMods + } + + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + if(m_positive) + m_target->ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS,m_modifier.m_amount,apply); + else + m_target->ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG,m_modifier.m_amount,apply); + } + } + + // Skip non magic case for speedup + if((m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_MAGIC) == 0) + return; + + if( GetSpellProto()->EquippedItemClass != -1 || GetSpellProto()->EquippedItemInventoryTypeMask != 0 ) + { + // wand magic case (skip generic to all item spell bonuses) + // done in Player::_ApplyWeaponDependentAuraMods + + // Skip item specific requirements for not wand magic damage + return; + } + + // Magic damage modifiers implemented in Unit::SpellDamageBonus + // This information for client side use only + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + if(m_positive) + { + for(int i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; i++) + { + if((m_modifier.m_miscvalue & (1<ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS+i,m_modifier.m_amount,apply); + } + } + else + { + for(int i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; i++) + { + if((m_modifier.m_miscvalue & (1<ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG+i,m_modifier.m_amount,apply); + } + } + Pet* pet = m_target->GetPet(); + if(pet) + pet->UpdateAttackPowerAndDamage(); + } +} + +void Aura::HandleModDamagePercentDone(bool apply, bool Real) +{ + sLog.outDebug("AURA MOD DAMAGE type:%u negative:%u", m_modifier.m_miscvalue, m_positive ? 0 : 1); + + // apply item specific bonuses for already equipped weapon + if(Real && m_target->GetTypeId()==TYPEID_PLAYER) + { + for(int i = 0; i < MAX_ATTACK; ++i) + if(Item* pItem = ((Player*)m_target)->GetWeaponForAttack(WeaponAttackType(i))) + ((Player*)m_target)->_ApplyWeaponDependentAuraDamageMod(pItem,WeaponAttackType(i),this,apply); + } + + // m_modifier.m_miscvalue is bitmask of spell schools + // 1 ( 0-bit ) - normal school damage (SPELL_SCHOOL_MASK_NORMAL) + // 126 - full bitmask all magic damages (SPELL_SCHOOL_MASK_MAGIC) including wand + // 127 - full bitmask any damages + // + // mods must be applied base at equipped weapon class and subclass comparison + // with spell->EquippedItemClass and EquippedItemSubClassMask and EquippedItemInventoryTypeMask + // m_modifier.m_miscvalue comparison with item generated damage types + + if((m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL) != 0) + { + // apply generic physical damage bonuses including wand case + if (GetSpellProto()->EquippedItemClass == -1 || m_target->GetTypeId() != TYPEID_PLAYER) + { + m_target->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, float(m_modifier.m_amount), apply); + m_target->HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, float(m_modifier.m_amount), apply); + m_target->HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_PCT, float(m_modifier.m_amount), apply); + } + else + { + // done in Player::_ApplyWeaponDependentAuraMods + } + // For show in client + if(m_target->GetTypeId() == TYPEID_PLAYER) + m_target->ApplyModSignedFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT,m_modifier.m_amount/100.0f,apply); + } + + // Skip non magic case for speedup + if((m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_MAGIC) == 0) + return; + + if( GetSpellProto()->EquippedItemClass != -1 || GetSpellProto()->EquippedItemInventoryTypeMask != 0 ) + { + // wand magic case (skip generic to all item spell bonuses) + // done in Player::_ApplyWeaponDependentAuraMods + + // Skip item specific requirements for not wand magic damage + return; + } + + // Magic damage percent modifiers implemented in Unit::SpellDamageBonus + // Send info to client + if(m_target->GetTypeId() == TYPEID_PLAYER) + for(int i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) + m_target->ApplyModSignedFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT+i,m_modifier.m_amount/100.0f,apply); +} + +void Aura::HandleModOffhandDamagePercent(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + sLog.outDebug("AURA MOD OFFHAND DAMAGE"); + + m_target->HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, float(m_modifier.m_amount), apply); +} + +/********************************/ +/*** POWER COST ***/ +/********************************/ + +void Aura::HandleModPowerCostPCT(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + float amount = m_modifier.m_amount/100.0f; + for(int i = 0; i < MAX_SPELL_SCHOOL; ++i) + if(m_modifier.m_miscvalue & (1<ApplyModSignedFloatValue(UNIT_FIELD_POWER_COST_MULTIPLIER+i,amount,apply); +} + +void Aura::HandleModPowerCost(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + for(int i = 0; i < MAX_SPELL_SCHOOL; ++i) + if(m_modifier.m_miscvalue & (1<ApplyModInt32Value(UNIT_FIELD_POWER_COST_MODIFIER+i,m_modifier.m_amount,apply); +} + +/*********************************************************/ +/*** OTHERS ***/ +/*********************************************************/ + +void Aura::HandleShapeshiftBoosts(bool apply) +{ + uint32 spellId = 0; + uint32 spellId2 = 0; + uint32 HotWSpellId = 0; + + switch(GetModifier()->m_miscvalue) + { + case FORM_CAT: + spellId = 3025; + HotWSpellId = 24900; + break; + case FORM_TREE: + spellId = 5420; + break; + case FORM_TRAVEL: + spellId = 5419; + break; + case FORM_AQUA: + spellId = 5421; + break; + case FORM_BEAR: + spellId = 1178; + spellId2 = 21178; + HotWSpellId = 24899; + break; + case FORM_DIREBEAR: + spellId = 9635; + spellId2 = 21178; + HotWSpellId = 24899; + break; + case FORM_BATTLESTANCE: + spellId = 21156; + break; + case FORM_DEFENSIVESTANCE: + spellId = 7376; + break; + case FORM_BERSERKERSTANCE: + spellId = 7381; + break; + case FORM_MOONKIN: + spellId = 24905; + // aura from effect trigger spell + spellId2 = 24907; + break; + case FORM_FLIGHT: + spellId = 33948; + break; + case FORM_FLIGHT_EPIC: + spellId = 40122; + spellId2 = 40121; + break; + case FORM_SPIRITOFREDEMPTION: + spellId = 27792; + spellId2 = 27795; // must be second, this important at aura remove to prevent to early iterator invalidation. + break; + case FORM_GHOSTWOLF: + case FORM_AMBIENT: + case FORM_GHOUL: + case FORM_SHADOW: + case FORM_STEALTH: + case FORM_CREATURECAT: + case FORM_CREATUREBEAR: + spellId = 0; + break; + } + + uint32 form = GetModifier()->m_miscvalue-1; + + if(apply) + { + if (spellId) m_target->CastSpell(m_target, spellId, true, NULL, this ); + if (spellId2) m_target->CastSpell(m_target, spellId2, true, NULL, this); + + if(m_target->GetTypeId() == TYPEID_PLAYER) + { + const PlayerSpellMap& sp_list = ((Player *)m_target)->GetSpellMap(); + for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) + { + if(itr->second->state == PLAYERSPELL_REMOVED) continue; + if(itr->first==spellId || itr->first==spellId2) continue; + SpellEntry const *spellInfo = sSpellStore.LookupEntry(itr->first); + if (!spellInfo || !(spellInfo->Attributes & ((1<<6) | (1<<7)))) continue; + if (spellInfo->Stances & (1<CastSpell(m_target, itr->first, true, NULL, this); + } + //LotP + if (((Player*)m_target)->HasSpell(17007)) + { + SpellEntry const *spellInfo = sSpellStore.LookupEntry(24932); + if (spellInfo && spellInfo->Stances & (1<CastSpell(m_target, 24932, true, NULL, this); + } + // HotW + if (HotWSpellId) + { + Unit::AuraList const& mModTotalStatPct = m_target->GetAurasByType(SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE); + for(Unit::AuraList::const_iterator i = mModTotalStatPct.begin(); i != mModTotalStatPct.end(); ++i) + { + if ((*i)->GetSpellProto()->SpellIconID == 240 && (*i)->GetModifier()->m_miscvalue == 3) + { + int32 HotWMod = (*i)->GetModifier()->m_amount; + if(GetModifier()->m_miscvalue == FORM_CAT) + HotWMod /= 2; + + m_target->CastCustomSpell(m_target, HotWSpellId, &HotWMod, NULL, NULL, true, NULL, this); + break; + } + } + } + } + } + else + { + m_target->RemoveAurasDueToSpell(spellId); + m_target->RemoveAurasDueToSpell(spellId2); + + Unit::AuraMap& tAuras = m_target->GetAuras(); + for (Unit::AuraMap::iterator itr = tAuras.begin(); itr != tAuras.end();) + { + if (itr->second->IsRemovedOnShapeLost()) + { + m_target->RemoveAurasDueToSpell(itr->second->GetId()); + itr = tAuras.begin(); + } + else + { + ++itr; + } + } + } + + /*double healthPercentage = (double)m_target->GetHealth() / (double)m_target->GetMaxHealth(); + m_target->SetHealth(uint32(ceil((double)m_target->GetMaxHealth() * healthPercentage)));*/ +} + +void Aura::HandleAuraEmpathy(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_UNIT) + return; + + CreatureInfo const * ci = objmgr.GetCreatureTemplate(m_target->GetEntry()); + if(ci && ci->type == CREATURE_TYPE_BEAST) + { + m_target->ApplyModUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_SPECIALINFO, apply); + } +} + +void Aura::HandleAuraUntrackable(bool apply, bool Real) +{ + if(apply) + m_target->SetFlag(UNIT_FIELD_BYTES_1, PLAYER_STATE_FLAG_UNTRACKABLE); + else + m_target->RemoveFlag(UNIT_FIELD_BYTES_1, PLAYER_STATE_FLAG_UNTRACKABLE); +} + +void Aura::HandleAuraModPacify(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + if(apply) + m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED); + else + m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED); +} + +void Aura::HandleAuraModPacifyAndSilence(bool apply, bool Real) +{ + HandleAuraModPacify(apply,Real); + HandleAuraModSilence(apply,Real); +} + +void Aura::HandleAuraGhost(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + if(apply) + { + m_target->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST); + } + else + { + m_target->RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST); + } +} + +void Aura::HandleAuraAllowFlight(bool apply, bool Real) +{ + // all applied/removed only at real aura add/remove + if(!Real) + return; + + // allow fly + WorldPacket data; + if(apply) + data.Initialize(SMSG_MOVE_SET_CAN_FLY, 12); + else + data.Initialize(SMSG_MOVE_UNSET_CAN_FLY, 12); + data.append(m_target->GetPackGUID()); + data << uint32(0); // unk + m_target->SendMessageToSet(&data, true); +} + +void Aura::HandleModRating(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + for (uint32 rating = 0; rating < MAX_COMBAT_RATING; ++rating) + if (m_modifier.m_miscvalue & (1 << rating)) + ((Player*)m_target)->ApplyRatingMod(CombatRating(rating), m_modifier.m_amount, apply); +} + +void Aura::HandleForceMoveForward(bool apply, bool Real) +{ + if(!Real || m_target->GetTypeId() != TYPEID_PLAYER) + return; + if(apply) + m_target->SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FORCE_MOVE); + else + m_target->RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FORCE_MOVE); +} + +void Aura::HandleAuraModExpertise(bool apply, bool Real) +{ + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)m_target)->UpdateExpertise(BASE_ATTACK); + ((Player*)m_target)->UpdateExpertise(OFF_ATTACK); +} + +void Aura::HandleModTargetResistance(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + // applied to damage as HandleNoImmediateEffect in Unit::CalcAbsorbResist and Unit::CalcArmorReducedDamage + + // show armor penetration + if (m_target->GetTypeId() == TYPEID_PLAYER && (m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_NORMAL)) + m_target->ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_PHYSICAL_RESISTANCE,m_modifier.m_amount, apply); + + // show as spell penetration only full spell penetration bonuses (all resistances except armor and holy + if (m_target->GetTypeId() == TYPEID_PLAYER && (m_modifier.m_miscvalue & SPELL_SCHOOL_MASK_SPELL)==SPELL_SCHOOL_MASK_SPELL) + m_target->ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE,m_modifier.m_amount, apply); +} + +//HandleNoImmediateEffect auras implementation to support new stat system +void Aura::HandleAuraHealing(bool apply, bool Real) +{ + //m_target->HandleStatModifier(UNIT_MOD_HEALING, TOTAL_VALUE, float(m_modifier.m_amount), apply); +} + +void Aura::HandleAuraHealingPct(bool apply, bool Real) +{ + //m_target->HandleStatModifier(UNIT_MOD_HEALING, TOTAL_PCT, float(m_modifier.m_amount), apply); +} + +void Aura::HandleShieldBlockValue(bool apply, bool Real) +{ + BaseModType modType = FLAT_MOD; + if(m_modifier.m_auraname == SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT) + modType = PCT_MOD; + + if(m_target->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_target)->HandleBaseModValue(SHIELD_BLOCK_VALUE, modType, float(m_modifier.m_amount), apply); +} + +void Aura::HandleAuraRetainComboPoints(bool apply, bool Real) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + if(m_target->GetTypeId() != TYPEID_PLAYER) + return; + + Player *target = (Player*)m_target; + + // combo points was added in SPELL_EFFECT_ADD_COMBO_POINTS handler + // remove only if aura expire by time (in case combo points amount change aura removed without combo points lost) + if( !apply && m_duration==0 && target->GetComboTarget()) + if(Unit* unit = ObjectAccessor::GetUnit(*m_target,target->GetComboTarget())) + target->AddComboPoints(unit, -m_modifier.m_amount); +} + +void Aura::HandleModUnattackable( bool Apply, bool Real ) +{ + if(Real && Apply) + m_target->CombatStop(); + + m_target->ApplyModFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE,Apply); +} + +void Aura::HandleSpiritOfRedemption( bool apply, bool Real ) +{ + // spells required only Real aura add/remove + if(!Real) + return; + + // prepare spirit state + if(apply) + { + if(m_target->GetTypeId()==TYPEID_PLAYER) + { + // disable breath/etc timers + ((Player*)m_target)->StopMirrorTimers(); + + // set stand state (expected in this form) + if(!m_target->IsStandState()) + m_target->SetStandState(PLAYER_STATE_NONE); + } + + m_target->SetHealth(1); + } + // die at aura end + else + m_target->DealDamage(m_target, m_target->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, GetSpellProto(), false); +} + +void Aura::CleanupTriggeredSpells() +{ + uint32 tSpellId = m_spellProto->EffectTriggerSpell[GetEffIndex()]; + if(!tSpellId) + return; + + SpellEntry const* tProto = sSpellStore.LookupEntry(tSpellId); + if(!tProto) + return; + + if(GetSpellDuration(tProto) != -1) + return; + + // needed for spell 43680, maybe others + // TODO: is there a spell flag, which can solve this in a more sophisticated way? + if(m_spellProto->EffectApplyAuraName[GetEffIndex()] == SPELL_AURA_PERIODIC_TRIGGER_SPELL && + GetSpellDuration(m_spellProto) == m_spellProto->EffectAmplitude[GetEffIndex()]) + return; + m_target->RemoveAurasDueToSpell(tSpellId); +} + +void Aura::HandleAuraPowerBurn(bool apply, bool Real) +{ + if (m_periodicTimer <= 0) + m_periodicTimer += m_modifier.periodictime; + + m_isPeriodic = apply; +} + +void Aura::HandleSchoolAbsorb(bool apply, bool Real) +{ + if(!Real) + return; + + // prevent double apply bonuses + if(apply && (m_target->GetTypeId()!=TYPEID_PLAYER || !((Player*)m_target)->GetSession()->PlayerLoading())) + { + if(Unit* caster = GetCaster()) + { + float DoneActualBenefit = 0.0f; + switch(m_spellProto->SpellFamilyName) + { + case SPELLFAMILY_PRIEST: + if(m_spellProto->SpellFamilyFlags == 0x1) //PW:S + { + //+30% from +healing bonus + DoneActualBenefit = caster->SpellBaseHealingBonus(GetSpellSchoolMask(m_spellProto)) * 0.3f; + break; + } + break; + case SPELLFAMILY_MAGE: + if(m_spellProto->SpellFamilyFlags == 0x80100 || m_spellProto->SpellFamilyFlags == 0x8 || m_spellProto->SpellFamilyFlags == 0x100000000LL) + { + //frost ward, fire ward, ice barrier + //+10% from +spd bonus + DoneActualBenefit = caster->SpellBaseDamageBonus(GetSpellSchoolMask(m_spellProto)) * 0.1f; + break; + } + break; + case SPELLFAMILY_WARLOCK: + if(m_spellProto->SpellFamilyFlags == 0x00) + { + //shadow ward + //+10% from +spd bonus + DoneActualBenefit = caster->SpellBaseDamageBonus(GetSpellSchoolMask(m_spellProto)) * 0.1f; + break; + } + break; + default: + break; + } + + DoneActualBenefit *= caster->CalculateLevelPenalty(GetSpellProto()); + + m_modifier.m_amount += (int32)DoneActualBenefit; + } + } +} + +void Aura::PeriodicTick() +{ + if(!m_target->isAlive()) + return; + + switch(m_modifier.m_auraname) + { + case SPELL_AURA_PERIODIC_DAMAGE: + case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: + { + Unit *pCaster = GetCaster(); + if(!pCaster) + return; + + if( GetSpellProto()->Effect[GetEffIndex()]==SPELL_EFFECT_PERSISTENT_AREA_AURA && + pCaster->SpellHitResult(m_target,GetSpellProto(),false)!=SPELL_MISS_NONE) + return; + + // Check for immune (not use charges) + if(m_target->IsImmunedToDamage(GetSpellSchoolMask(GetSpellProto()))) + return; + + // some auras remove at specific health level or more + if(m_modifier.m_auraname==SPELL_AURA_PERIODIC_DAMAGE) + { + switch(GetId()) + { + case 43093: case 31956: case 38801: + case 35321: case 38363: case 39215: + if(m_target->GetHealth() == m_target->GetMaxHealth() ) + { + m_target->RemoveAurasDueToSpell(GetId()); + return; + } + break; + case 38772: + { + uint32 percent = + GetEffIndex() < 2 && GetSpellProto()->Effect[GetEffIndex()]==SPELL_EFFECT_DUMMY ? + pCaster->CalculateSpellDamage(GetSpellProto(),GetEffIndex()+1,GetSpellProto()->EffectBasePoints[GetEffIndex()+1],m_target) : + 100; + if(m_target->GetHealth()*100 >= m_target->GetMaxHealth()*percent ) + { + m_target->RemoveAurasDueToSpell(GetId()); + return; + } + break; + } + default: + break; + } + } + + uint32 absorb=0; + uint32 resist=0; + CleanDamage cleanDamage = CleanDamage(0, BASE_ATTACK, MELEE_HIT_NORMAL ); + + // ignore non positive values (can be result apply spellmods to aura damage + uint32 amount = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; + + uint32 pdamage; + + if(m_modifier.m_auraname == SPELL_AURA_PERIODIC_DAMAGE) + { + pdamage = amount; + + // Calculate armor mitigation if it is a physical spell + // But not for bleed mechanic spells + if ( GetSpellSchoolMask(GetSpellProto()) & SPELL_SCHOOL_MASK_NORMAL && + GetEffectMechanic(GetSpellProto(), m_effIndex) != MECHANIC_BLEED) + { + uint32 pdamageReductedArmor = pCaster->CalcArmorReducedDamage(m_target, pdamage); + cleanDamage.damage += pdamage - pdamageReductedArmor; + pdamage = pdamageReductedArmor; + } + + pdamage = pCaster->SpellDamageBonus(m_target,GetSpellProto(),pdamage,DOT); + + // Curse of Agony damage-per-tick calculation + if (GetSpellProto()->SpellFamilyName==SPELLFAMILY_WARLOCK && (GetSpellProto()->SpellFamilyFlags & 0x0000000000000400LL) && GetSpellProto()->SpellIconID==544) + { + // 1..4 ticks, 1/2 from normal tick damage + if (m_duration>=((m_maxduration-m_modifier.periodictime)*2/3)) + pdamage = pdamage/2; + // 9..12 ticks, 3/2 from normal tick damage + else if(m_duration<((m_maxduration-m_modifier.periodictime)/3)) + pdamage += (pdamage+1)/2; // +1 prevent 0.5 damage possible lost at 1..4 ticks + // 5..8 ticks have normal tick damage + } + } + else + pdamage = uint32(m_target->GetMaxHealth()*amount/100); + + //As of 2.2 resilience reduces damage from DoT ticks as much as the chance to not be critically hit + // Reduce dot damage from resilience for players + if (m_target->GetTypeId()==TYPEID_PLAYER) + pdamage-=((Player*)m_target)->GetDotDamageReduction(pdamage); + + pCaster->CalcAbsorbResist(m_target, GetSpellSchoolMask(GetSpellProto()), DOT, pdamage, &absorb, &resist); + + sLog.outDetail("PeriodicTick: %u (TypeId: %u) attacked %u (TypeId: %u) for %u dmg inflicted by %u abs is %u", + GetCasterGUID(), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId(),absorb); + + WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size + data.append(m_target->GetPackGUID()); + data.appendPackGUID(GetCasterGUID()); + data << uint32(GetId()); + data << uint32(1); + data << uint32(m_modifier.m_auraname); + data << (uint32)pdamage; + data << (uint32)GetSpellSchoolMask(GetSpellProto()); // will be mask in 2.4.x + data << (uint32)absorb; + data << (uint32)resist; + m_target->SendMessageToSet(&data,true); + + Unit* target = m_target; // aura can be deleted in DealDamage + SpellEntry const* spellProto = GetSpellProto(); + + pCaster->DealDamage(m_target, (pdamage <= absorb+resist) ? 0 : (pdamage-absorb-resist), &cleanDamage, DOT, GetSpellSchoolMask(GetSpellProto()), GetSpellProto(), true); + + // DO NOT ACCESS MEMBERS OF THE AURA FROM NOW ON (DealDamage can delete aura) + + pCaster->ProcDamageAndSpell(target, PROC_FLAG_HIT_SPELL, PROC_FLAG_TAKE_DAMAGE, (pdamage <= absorb+resist) ? 0 : (pdamage-absorb-resist), GetSpellSchoolMask(spellProto), spellProto); + break; + } + case SPELL_AURA_PERIODIC_LEECH: + { + Unit *pCaster = GetCaster(); + if(!pCaster) + return; + + if(!pCaster->isAlive()) + return; + + if( GetSpellProto()->Effect[GetEffIndex()]==SPELL_EFFECT_PERSISTENT_AREA_AURA && + pCaster->SpellHitResult(m_target,GetSpellProto(),false)!=SPELL_MISS_NONE) + return; + + // Check for immune (not use charges) + if(m_target->IsImmunedToDamage(GetSpellSchoolMask(GetSpellProto()))) + return; + + uint32 absorb=0; + uint32 resist=0; + CleanDamage cleanDamage = CleanDamage(0, BASE_ATTACK, MELEE_HIT_NORMAL ); + + uint32 pdamage = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; + + //Calculate armor mitigation if it is a physical spell + if (GetSpellSchoolMask(GetSpellProto()) & SPELL_SCHOOL_MASK_NORMAL) + { + uint32 pdamageReductedArmor = pCaster->CalcArmorReducedDamage(m_target, pdamage); + cleanDamage.damage += pdamage - pdamageReductedArmor; + pdamage = pdamageReductedArmor; + } + + pdamage = pCaster->SpellDamageBonus(m_target,GetSpellProto(),pdamage,DOT); + + // talent Soul Siphon add bonus to Drain Life spells + if( GetSpellProto()->SpellFamilyName == SPELLFAMILY_WARLOCK && (GetSpellProto()->SpellFamilyFlags & 0x8) ) + { + // find talent max bonus percentage + Unit::AuraList const& mClassScriptAuras = pCaster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for(Unit::AuraList::const_iterator i = mClassScriptAuras.begin(); i != mClassScriptAuras.end(); ++i) + { + if ((*i)->GetModifier()->m_miscvalue == 4992 || (*i)->GetModifier()->m_miscvalue == 4993) + { + if((*i)->GetEffIndex()!=1) + { + sLog.outError("Expected spell %u structure change, need code update",(*i)->GetId()); + break; + } + + // effect 1 m_amount + int32 maxPercent = (*i)->GetModifier()->m_amount; + // effect 0 m_amount + int32 stepPercent = pCaster->CalculateSpellDamage((*i)->GetSpellProto(),0,(*i)->GetSpellProto()->EffectBasePoints[0],pCaster); + + // count affliction effects and calc additional damage in percentage + int32 modPercent = 0; + Unit::AuraMap const& victimAuras = m_target->GetAuras(); + for (Unit::AuraMap::const_iterator itr = victimAuras.begin(); itr != victimAuras.end(); ++itr) + { + Aura* aura = itr->second; + if (aura->IsPositive())continue; + SpellEntry const* m_spell = aura->GetSpellProto(); + if (m_spell->SpellFamilyName != SPELLFAMILY_WARLOCK) + continue; + + SkillLineAbilityMap::const_iterator lower = spellmgr.GetBeginSkillLineAbilityMap(m_spell->Id); + SkillLineAbilityMap::const_iterator upper = spellmgr.GetEndSkillLineAbilityMap(m_spell->Id); + + for(SkillLineAbilityMap::const_iterator _spell_idx = lower; _spell_idx != upper; ++_spell_idx) + { + if(_spell_idx->second->skillId == SKILL_AFFLICTION) + { + modPercent += stepPercent; + if (modPercent >= maxPercent) + { + modPercent = maxPercent; + break; + } + } + } + } + pdamage += (pdamage*modPercent/100); + break; + } + } + } + + //As of 2.2 resilience reduces damage from DoT ticks as much as the chance to not be critically hit + // Reduce dot damage from resilience for players + if (m_target->GetTypeId()==TYPEID_PLAYER) + pdamage-=((Player*)m_target)->GetDotDamageReduction(pdamage); + + pCaster->CalcAbsorbResist(m_target, GetSpellSchoolMask(GetSpellProto()), DOT, pdamage, &absorb, &resist); + + if(m_target->GetHealth() < pdamage) + pdamage = uint32(m_target->GetHealth()); + + sLog.outDetail("PeriodicTick: %u (TypeId: %u) health leech of %u (TypeId: %u) for %u dmg inflicted by %u abs is %u", + GetCasterGUID(), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId(),absorb); + + pCaster->SendSpellNonMeleeDamageLog(m_target, GetId(), pdamage, GetSpellSchoolMask(GetSpellProto()), absorb, resist, false, 0); + + + Unit* target = m_target; // aura can be deleted in DealDamage + SpellEntry const* spellProto = GetSpellProto(); + float multiplier = spellProto->EffectMultipleValue[GetEffIndex()] > 0 ? spellProto->EffectMultipleValue[GetEffIndex()] : 1; + + uint32 new_damage = pCaster->DealDamage(m_target, (pdamage <= absorb+resist) ? 0 : (pdamage-absorb-resist), &cleanDamage, DOT, GetSpellSchoolMask(GetSpellProto()), GetSpellProto(), false); + + // DO NOT ACCESS MEMBERS OF THE AURA FROM NOW ON (DealDamage can delete aura) + + pCaster->ProcDamageAndSpell(target, PROC_FLAG_HIT_SPELL, PROC_FLAG_TAKE_DAMAGE, new_damage, GetSpellSchoolMask(spellProto), spellProto); + if (!target->isAlive() && pCaster->IsNonMeleeSpellCasted(false)) + { + for (uint32 i = CURRENT_FIRST_NON_MELEE_SPELL; i < CURRENT_MAX_SPELL; i++) + { + if (pCaster->m_currentSpells[i] && pCaster->m_currentSpells[i]->m_spellInfo->Id == spellProto->Id) + pCaster->m_currentSpells[i]->cancel(); + } + } + + + if(Player *modOwner = pCaster->GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_MULTIPLE_VALUE, multiplier); + + uint32 heal = pCaster->SpellHealingBonus(spellProto, uint32(new_damage * multiplier), DOT, pCaster); + + int32 gain = pCaster->ModifyHealth(heal); + pCaster->getHostilRefManager().threatAssist(pCaster, gain * 0.5f, spellProto); + + pCaster->SendHealSpellLog(pCaster, spellProto->Id, heal); + break; + } + case SPELL_AURA_PERIODIC_HEAL: + case SPELL_AURA_OBS_MOD_HEALTH: + { + Unit *pCaster = GetCaster(); + if(!pCaster) + return; + + // heal for caster damage (must be alive) + if(m_target != pCaster && GetSpellProto()->SpellVisual==163 && !pCaster->isAlive()) + return; + + // ignore non positive values (can be result apply spellmods to aura damage + uint32 amount = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; + + uint32 pdamage; + + if(m_modifier.m_auraname==SPELL_AURA_OBS_MOD_HEALTH) + pdamage = uint32(m_target->GetMaxHealth() * amount/100); + else + pdamage = amount; + + pdamage = pCaster->SpellHealingBonus(GetSpellProto(), pdamage, DOT, m_target); + + sLog.outDetail("PeriodicTick: %u (TypeId: %u) heal of %u (TypeId: %u) for %u health inflicted by %u", + GUID_LOPART(GetCasterGUID()), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId()); + + WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size + data.append(m_target->GetPackGUID()); + data.appendPackGUID(GetCasterGUID()); + data << uint32(GetId()); + data << uint32(1); + data << uint32(m_modifier.m_auraname); + data << (uint32)pdamage; + m_target->SendMessageToSet(&data,true); + + int32 gain = m_target->ModifyHealth(pdamage); + + // add HoTs to amount healed in bgs + if( pCaster->GetTypeId() == TYPEID_PLAYER ) + if( BattleGround *bg = ((Player*)pCaster)->GetBattleGround() ) + bg->UpdatePlayerScore(((Player*)pCaster), SCORE_HEALING_DONE, gain); + + //Do check before because m_modifier.auraName can be invalidate by DealDamage. + bool procSpell = (m_modifier.m_auraname == SPELL_AURA_PERIODIC_HEAL && m_target != pCaster); + + m_target->getHostilRefManager().threatAssist(pCaster, float(gain) * 0.5f, GetSpellProto()); + + Unit* target = m_target; // aura can be deleted in DealDamage + SpellEntry const* spellProto = GetSpellProto(); + bool haveCastItem = GetCastItemGUID()!=0; + + // heal for caster damage + if(m_target!=pCaster && spellProto->SpellVisual==163) + { + uint32 dmg = spellProto->manaPerSecond; + if(pCaster->GetHealth() <= dmg && pCaster->GetTypeId()==TYPEID_PLAYER) + { + pCaster->RemoveAurasDueToSpell(GetId()); + + // finish current generic/channeling spells, don't affect autorepeat + if(pCaster->m_currentSpells[CURRENT_GENERIC_SPELL]) + { + pCaster->m_currentSpells[CURRENT_GENERIC_SPELL]->finish(); + } + if(pCaster->m_currentSpells[CURRENT_CHANNELED_SPELL]) + { + pCaster->m_currentSpells[CURRENT_CHANNELED_SPELL]->SendChannelUpdate(0); + pCaster->m_currentSpells[CURRENT_CHANNELED_SPELL]->finish(); + } + } + else + { + pCaster->SendSpellNonMeleeDamageLog(pCaster, GetId(), gain, GetSpellSchoolMask(GetSpellProto()), 0, 0, false, 0, false); + + CleanDamage cleanDamage = CleanDamage(0, BASE_ATTACK, MELEE_HIT_NORMAL ); + pCaster->DealDamage(pCaster, gain, &cleanDamage, NODAMAGE, GetSpellSchoolMask(GetSpellProto()), GetSpellProto(), true); + } + } + + // ignore item heals + if(procSpell && !haveCastItem) + pCaster->ProcDamageAndSpell(target,PROC_FLAG_HEAL, PROC_FLAG_HEALED, pdamage, SPELL_SCHOOL_MASK_NONE, spellProto); + break; + } + case SPELL_AURA_PERIODIC_MANA_LEECH: + { + Unit *pCaster = GetCaster(); + if(!pCaster) + return; + + if(!pCaster->isAlive()) + return; + + if( GetSpellProto()->Effect[GetEffIndex()]==SPELL_EFFECT_PERSISTENT_AREA_AURA && + pCaster->SpellHitResult(m_target,GetSpellProto(),false)!=SPELL_MISS_NONE) + return; + + // Check for immune (not use charges) + if(m_target->IsImmunedToDamage(GetSpellSchoolMask(GetSpellProto()))) + return; + + // ignore non positive values (can be result apply spellmods to aura damage + uint32 pdamage = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; + + sLog.outDetail("PeriodicTick: %u (TypeId: %u) power leech of %u (TypeId: %u) for %u dmg inflicted by %u", + GUID_LOPART(GetCasterGUID()), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId()); + + if(m_modifier.m_miscvalue < 0 || m_modifier.m_miscvalue > 4) + break; + + Powers power = Powers(m_modifier.m_miscvalue); + + // power type might have changed between aura applying and tick (druid's shapeshift) + if(m_target->getPowerType() != power) + break; + + int32 drain_amount = m_target->GetPower(power) > pdamage ? pdamage : m_target->GetPower(power); + + // resilience reduce mana draining effect at spell crit damage reduction (added in 2.4) + if (power == POWER_MANA && m_target->GetTypeId() == TYPEID_PLAYER) + drain_amount -= ((Player*)m_target)->GetSpellCritDamageReduction(drain_amount); + + m_target->ModifyPower(power, -drain_amount); + + float gain_multiplier = 0; + + if(pCaster->GetMaxPower(power) > 0) + { + gain_multiplier = GetSpellProto()->EffectMultipleValue[GetEffIndex()]; + + if(Player *modOwner = pCaster->GetSpellModOwner()) + modOwner->ApplySpellMod(GetId(), SPELLMOD_MULTIPLE_VALUE, gain_multiplier); + } + + WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size + data.append(m_target->GetPackGUID()); + data.appendPackGUID(GetCasterGUID()); + data << uint32(GetId()); + data << uint32(1); + data << uint32(m_modifier.m_auraname); + data << (uint32)power; // power type + data << (uint32)drain_amount; + data << (float)gain_multiplier; + m_target->SendMessageToSet(&data,true); + + int32 gain_amount = int32(drain_amount*gain_multiplier); + + if(gain_amount) + { + int32 gain = pCaster->ModifyPower(power,gain_amount); + m_target->AddThreat(pCaster, float(gain) * 0.5f, GetSpellSchoolMask(GetSpellProto()), GetSpellProto()); + } + break; + } + case SPELL_AURA_PERIODIC_ENERGIZE: + { + // ignore non positive values (can be result apply spellmods to aura damage + uint32 pdamage = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; + + sLog.outDetail("PeriodicTick: %u (TypeId: %u) energize %u (TypeId: %u) for %u dmg inflicted by %u", + GUID_LOPART(GetCasterGUID()), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId()); + + if(m_modifier.m_miscvalue < 0 || m_modifier.m_miscvalue > 4) + break; + + Powers power = Powers(m_modifier.m_miscvalue); + + if(m_target->GetMaxPower(power) == 0) + break; + + WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size + data.append(m_target->GetPackGUID()); + data.appendPackGUID(GetCasterGUID()); + data << uint32(GetId()); + data << uint32(1); + data << uint32(m_modifier.m_auraname); + data << (uint32)power; // power type + data << (uint32)pdamage; + m_target->SendMessageToSet(&data,true); + + int32 gain = m_target->ModifyPower(power,pdamage); + + if(Unit* pCaster = GetCaster()) + m_target->getHostilRefManager().threatAssist(pCaster, float(gain) * 0.5f, GetSpellProto()); + break; + } + case SPELL_AURA_OBS_MOD_MANA: + { + // ignore non positive values (can be result apply spellmods to aura damage + uint32 amount = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; + + uint32 pdamage = uint32(m_target->GetMaxPower(POWER_MANA) * amount/100); + + sLog.outDetail("PeriodicTick: %u (TypeId: %u) energize %u (TypeId: %u) for %u mana inflicted by %u", + GUID_LOPART(GetCasterGUID()), GuidHigh2TypeId(GUID_HIPART(GetCasterGUID())), m_target->GetGUIDLow(), m_target->GetTypeId(), pdamage, GetId()); + + if(m_target->GetMaxPower(POWER_MANA) == 0) + break; + + WorldPacket data(SMSG_PERIODICAURALOG, (21+16));// we guess size + data.append(m_target->GetPackGUID()); + data.appendPackGUID(GetCasterGUID()); + data << uint32(GetId()); + data << uint32(1); + data << uint32(m_modifier.m_auraname); + data << (uint32)0; // ? + data << (uint32)pdamage; + m_target->SendMessageToSet(&data,true); + + int32 gain = m_target->ModifyPower(POWER_MANA, pdamage); + + if(Unit* pCaster = GetCaster()) + m_target->getHostilRefManager().threatAssist(pCaster, float(gain) * 0.5f, GetSpellProto()); + break; + } + case SPELL_AURA_POWER_BURN_MANA: + { + Unit *pCaster = GetCaster(); + if(!pCaster) + return; + + // Check for immune (not use charges) + if(m_target->IsImmunedToDamage(GetSpellSchoolMask(GetSpellProto()))) + return; + + int32 pdamage = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; + + Powers powerType = Powers(m_modifier.m_miscvalue); + + if(!m_target->isAlive() || m_target->getPowerType() != powerType) + return; + + // resilience reduce mana draining effect at spell crit damage reduction (added in 2.4) + if (powerType == POWER_MANA && m_target->GetTypeId() == TYPEID_PLAYER) + pdamage -= ((Player*)m_target)->GetSpellCritDamageReduction(pdamage); + + uint32 gain = uint32(-m_target->ModifyPower(powerType, -pdamage)); + + gain = uint32(gain * GetSpellProto()->EffectMultipleValue[GetEffIndex()]); + + //maybe has to be sent different to client, but not by SMSG_PERIODICAURALOG + pCaster->SpellNonMeleeDamageLog(m_target, GetId(), gain); + break; + } + // Here tick dummy auras + case SPELL_AURA_PERIODIC_DUMMY: + { + PeriodicDummyTick(); + break; + } + default: + break; + } +} + +void Aura::PeriodicDummyTick() +{ + SpellEntry const* spell = GetSpellProto(); + switch (spell->Id) + { + // Drink + case 430: + case 431: + case 432: + case 1133: + case 1135: + case 1137: + case 10250: + case 22734: + case 27089: + case 34291: + case 43706: + case 46755: + { + if (m_target->GetTypeId() != TYPEID_PLAYER) + return; + // Search SPELL_AURA_MOD_POWER_REGEN aura for this spell and add bonus + Unit::AuraList const& aura = m_target->GetAurasByType(SPELL_AURA_MOD_POWER_REGEN); + for(Unit::AuraList::const_iterator i = aura.begin(); i != aura.end(); ++i) + { + if ((*i)->GetId() == GetId()) + { + // Get tick number + int32 tick = (m_maxduration - m_duration) / m_modifier.periodictime; + // Default case (not on arenas) + if (tick == 0) + { + (*i)->GetModifier()->m_amount = m_modifier.m_amount; + ((Player*)m_target)->UpdateManaRegen(); + // Disable continue + m_isPeriodic = false; + } + return; + //********************************************** + // Code commended since arena patch not added + // This feature uses only in arenas + //********************************************** + // Here need increase mana regen per tick (6 second rule) + // on 0 tick - 0 (handled in 2 second) + // on 1 tick - 166% (handled in 4 second) + // on 2 tick - 133% (handled in 6 second) + // Not need update after 3 tick + /* + if (tick > 3) + return; + // Apply bonus for 0 - 3 tick + switch (tick) + { + case 0: // 0% + (*i)->GetModifier()->m_amount = m_modifier.m_amount = 0; + break; + case 1: // 166% + (*i)->GetModifier()->m_amount = m_modifier.m_amount * 5 / 3; + break; + case 2: // 133% + (*i)->GetModifier()->m_amount = m_modifier.m_amount * 4 / 3; + break; + default: // 100% - normal regen + (*i)->GetModifier()->m_amount = m_modifier.m_amount; + break; + } + ((Player*)m_target)->UpdateManaRegen(); + return;*/ + } + } + return; + } +// // Panda +// case 19230: break; +// // Master of Subtlety +// case 31666: break; +// // Gossip NPC Periodic - Talk +// case 33208: break; +// // Gossip NPC Periodic - Despawn +// case 33209: break; +// // Force of Nature +// case 33831: break; + // Aspect of the Viper + case 34074: + { + if (m_target->GetTypeId() != TYPEID_PLAYER) + return; + // Should be manauser + if (m_target->getPowerType()!=POWER_MANA) + return; + Unit *caster = GetCaster(); + if (!caster) + return; + // Regen amount is max (100% from spell) on 21% or less mana and min on 92.5% or greater mana (20% from spell) + int mana = m_target->GetPower(POWER_MANA); + int max_mana = m_target->GetMaxPower(POWER_MANA); + int32 base_regen = caster->CalculateSpellDamage(m_spellProto, m_effIndex, m_currentBasePoints, m_target); + float regen_pct = 1.20f - 1.1f * mana / max_mana; + if (regen_pct > 1.0f) regen_pct = 1.0f; + else if (regen_pct < 0.2f) regen_pct = 0.2f; + m_modifier.m_amount = int32 (base_regen * regen_pct); + ((Player*)m_target)->UpdateManaRegen(); + return; + } +// // Steal Weapon +// case 36207: break; +// // Simon Game START timer, (DND) +// case 39993: break; +// // Harpooner's Mark +// case 40084: break; +// // Knockdown Fel Cannon: break; The Aggro Burst +// case 40119: break; +// // Old Mount Spell +// case 40154: break; +// // Magnetic Pull +// case 40581: break; +// // Ethereal Ring: break; The Bolt Burst +// case 40801: break; +// // Crystal Prison +// case 40846: break; +// // Copy Weapon +// case 41054: break; +// // Ethereal Ring Visual, Lightning Aura +// case 41477: break; +// // Ethereal Ring Visual, Lightning Aura (Fork) +// case 41525: break; +// // Ethereal Ring Visual, Lightning Jumper Aura +// case 41567: break; +// // No Man's Land +// case 41955: break; +// // Headless Horseman - Fire +// case 42074: break; +// // Headless Horseman - Visual - Large Fire +// case 42075: break; +// // Headless Horseman - Start Fire, Periodic Aura +// case 42140: break; +// // Ram Speed Boost +// case 42152: break; +// // Headless Horseman - Fires Out Victory Aura +// case 42235: break; +// // Pumpkin Life Cycle +// case 42280: break; +// // Brewfest Request Chick Chuck Mug Aura +// case 42537: break; +// // Squashling +// case 42596: break; +// // Headless Horseman Climax, Head: Periodic +// case 42603: break; +// // Fire Bomb +// case 42621: break; +// // Headless Horseman - Conflagrate, Periodic Aura +// case 42637: break; +// // Headless Horseman - Create Pumpkin Treats Aura +// case 42774: break; +// // Headless Horseman Climax - Summoning Rhyme Aura +// case 42879: break; +// // Tricky Treat +// case 42919: break; +// // Giddyup! +// case 42924: break; +// // Ram - Trot +// case 42992: break; +// // Ram - Canter +// case 42993: break; +// // Ram - Gallop +// case 42994: break; +// // Ram Level - Neutral +// case 43310: break; +// // Headless Horseman - Maniacal Laugh, Maniacal, Delayed 17 +// case 43884: break; +// // Headless Horseman - Maniacal Laugh, Maniacal, other, Delayed 17 +// case 44000: break; +// // Energy Feedback +// case 44328: break; +// // Romantic Picnic +// case 45102: break; +// // Romantic Picnic +// case 45123: break; +// // Looking for Love +// case 45124: break; +// // Kite - Lightning Strike Kite Aura +// case 45197: break; +// // Rocket Chicken +// case 45202: break; +// // Copy Offhand Weapon +// case 45205: break; +// // Upper Deck - Kite - Lightning Periodic Aura +// case 45207: break; +// // Kite -Sky Lightning Strike Kite Aura +// case 45251: break; +// // Ribbon Pole Dancer Check Aura +// case 45390: break; +// // Holiday - Midsummer, Ribbon Pole Periodic Visual +// case 45406: break; +// // Parachute +// case 45472: break; +// // Alliance Flag, Extra Damage Debuff +// case 45898: break; +// // Horde Flag, Extra Damage Debuff +// case 45899: break; +// // Ahune - Summoning Rhyme Aura +// case 45926: break; +// // Ahune - Slippery Floor +// case 45945: break; +// // Ahune's Shield +// case 45954: break; +// // Nether Vapor Lightning +// case 45960: break; +// // Darkness +// case 45996: break; +// // Summon Blood Elves Periodic +// case 46041: break; +// // Transform Visual Missile Periodic +// case 46205: break; +// // Find Opening Beam End +// case 46333: break; +// // Ice Spear Control Aura +// case 46371: break; +// // Hailstone Chill +// case 46458: break; +// // Hailstone Chill, Internal +// case 46465: break; +// // Chill, Internal Shifter +// case 46549: break; +// // Summon Ice Spear Knockback Delayer +// case 46878: break; +// // Burninate Effect +// case 47214: break; +// // Fizzcrank Practice Parachute +// case 47228: break; +// // Send Mug Control Aura +// case 47369: break; +// // Direbrew's Disarm (precast) +// case 47407: break; +// // Mole Machine Port Schedule +// case 47489: break; +// // Mole Machine Portal Schedule +// case 49466: break; +// // Drink Coffee +// case 49472: break; +// // Listening to Music +// case 50493: break; +// // Love Rocket Barrage +// case 50530: break; + default: + break; + } +} + +void Aura::HandlePreventFleeing(bool apply, bool Real) +{ + if(!Real) + return; + + Unit::AuraList const& fearAuras = m_target->GetAurasByType(SPELL_AURA_MOD_FEAR); + if( !fearAuras.empty() ) + { + if (apply) + m_target->SetFeared(false, fearAuras.front()->GetCasterGUID()); + else + m_target->SetFeared(true); + } +} + +void Aura::HandleManaShield(bool apply, bool Real) +{ + if(!Real) + return; + + // prevent double apply bonuses + if(apply && (m_target->GetTypeId()!=TYPEID_PLAYER || !((Player*)m_target)->GetSession()->PlayerLoading())) + { + if(Unit* caster = GetCaster()) + { + float DoneActualBenefit = 0.0f; + switch(m_spellProto->SpellFamilyName) + { + case SPELLFAMILY_MAGE: + if(m_spellProto->SpellFamilyFlags & 0x8000) + { + // Mana Shield + // +50% from +spd bonus + DoneActualBenefit = caster->SpellBaseDamageBonus(GetSpellSchoolMask(m_spellProto)) * 0.5f; + break; + } + break; + default: + break; + } + + DoneActualBenefit *= caster->CalculateLevelPenalty(GetSpellProto()); + + m_modifier.m_amount += (int32)DoneActualBenefit; + } + } +} + +void Aura::HandleArenaPreparation(bool apply, bool Real) +{ + if(!Real) + return; + + if(apply) + m_target->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PREPARATION); + else + m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PREPARATION); +} diff --git a/src/game/SpellEffects.cpp b/src/game/SpellEffects.cpp index 9816f683b05..06607e90fd8 100644 --- a/src/game/SpellEffects.cpp +++ b/src/game/SpellEffects.cpp @@ -1465,7 +1465,7 @@ void Spell::EffectDummy(uint32 i) if(!spell_proto) return; - if( !unitTarget->hasUnitState(UNIT_STAT_STUNDED) && m_caster->GetTypeId()==TYPEID_PLAYER) + if( !unitTarget->hasUnitState(UNIT_STAT_STUNNED) && m_caster->GetTypeId()==TYPEID_PLAYER) { // decreased damage (/2) for non-stunned target. SpellModifier *mod = new SpellModifier; @@ -3401,7 +3401,7 @@ void Spell::EffectDistract(uint32 /*i*/) return; // target must be OK to do this - if( unitTarget->hasUnitState(UNIT_STAT_CONFUSED | UNIT_STAT_STUNDED | UNIT_STAT_FLEEING ) ) + if( unitTarget->hasUnitState(UNIT_STAT_CONFUSED | UNIT_STAT_STUNNED | UNIT_STAT_FLEEING ) ) return; float angle = unitTarget->GetAngle(m_targets.m_destX, m_targets.m_destY); diff --git a/src/game/TargetedMovementGenerator.cpp b/src/game/TargetedMovementGenerator.cpp index 9d465877a2a..8687de479f2 100644 --- a/src/game/TargetedMovementGenerator.cpp +++ b/src/game/TargetedMovementGenerator.cpp @@ -47,7 +47,7 @@ TargetedMovementGenerator::_setTargetLocation(T &owner) if( !i_target.isValid() || !&owner ) return; - if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED | UNIT_STAT_DISTRACTED) ) + if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED | UNIT_STAT_DISTRACTED) ) return; // prevent redundant micro-movement for pets, other followers. @@ -127,7 +127,7 @@ TargetedMovementGenerator::Update(T &owner, const uint32 & time_diff) if( !&owner || !owner.isAlive()) return true; - if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED | UNIT_STAT_FLEEING | UNIT_STAT_DISTRACTED) ) + if( owner.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED | UNIT_STAT_FLEEING | UNIT_STAT_DISTRACTED) ) return true; // prevent movement while casting spells with cast time or channel time diff --git a/src/game/TradeHandler.cpp b/src/game/TradeHandler.cpp index eb874f82ffb..4a4273c8750 100644 --- a/src/game/TradeHandler.cpp +++ b/src/game/TradeHandler.cpp @@ -1,633 +1,634 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Common.h" -#include "WorldPacket.h" -#include "WorldSession.h" -#include "World.h" -#include "ObjectAccessor.h" -#include "Log.h" -#include "Opcodes.h" -#include "Player.h" -#include "Item.h" -#include "SocialMgr.h" - -enum TradeStatus -{ - TRADE_STATUS_BUSY = 0, - TRADE_STATUS_BEGIN_TRADE = 1, - TRADE_STATUS_OPEN_WINDOW = 2, - TRADE_STATUS_TRADE_CANCELED = 3, - TRADE_STATUS_TRADE_ACCEPT = 4, - TRADE_STATUS_BUSY_2 = 5, - TRADE_STATUS_NO_TARGET = 6, - TRADE_STATUS_BACK_TO_TRADE = 7, - TRADE_STATUS_TRADE_COMPLETE = 8, - // 9? - TRADE_STATUS_TARGET_TO_FAR = 10, - TRADE_STATUS_WRONG_FACTION = 11, - TRADE_STATUS_CLOSE_WINDOW = 12, - // 13? - TRADE_STATUS_IGNORE_YOU = 14, - TRADE_STATUS_YOU_STUNNED = 15, - TRADE_STATUS_TARGET_STUNNED = 16, - TRADE_STATUS_YOU_DEAD = 17, - TRADE_STATUS_TARGET_DEAD = 18, - TRADE_STATUS_YOU_LOGOUT = 19, - TRADE_STATUS_TARGET_LOGOUT = 20, - TRADE_STATUS_TRIAL_ACCOUNT = 21, // Trial accounts can not perform that action - TRADE_STATUS_ONLY_CONJURED = 22 // You can only trade conjured items... (cross realm BG related). -}; - -void WorldSession::SendTradeStatus(uint32 status) -{ - WorldPacket data; - - switch(status) - { - case TRADE_STATUS_BEGIN_TRADE: - data.Initialize(SMSG_TRADE_STATUS, 4+8); - data << uint32(status); - data << uint64(0); - break; - case TRADE_STATUS_OPEN_WINDOW: - data.Initialize(SMSG_TRADE_STATUS, 4+4); - data << uint32(status); - data << uint32(0); // added in 2.4.0 - break; - case TRADE_STATUS_CLOSE_WINDOW: - data.Initialize(SMSG_TRADE_STATUS, 4+4+1+4); - data << uint32(status); - data << uint32(0); - data << uint8(0); - data << uint32(0); - break; - case TRADE_STATUS_ONLY_CONJURED: - data.Initialize(SMSG_TRADE_STATUS, 4+1); - data << uint32(status); - data << uint8(0); - break; - default: - data.Initialize(SMSG_TRADE_STATUS, 4); - data << uint32(status); - break; - } - - SendPacket(&data); -} - -void WorldSession::HandleIgnoreTradeOpcode(WorldPacket& /*recvPacket*/) -{ - sLog.outDebug( "WORLD: Ignore Trade %u",_player->GetGUIDLow()); - // recvPacket.print_storage(); -} - -void WorldSession::HandleBusyTradeOpcode(WorldPacket& /*recvPacket*/) -{ - sLog.outDebug( "WORLD: Busy Trade %u",_player->GetGUIDLow()); - // recvPacket.print_storage(); -} - -void WorldSession::SendUpdateTrade() -{ - Item *item = NULL; - - if( !_player || !_player->pTrader ) - return; - - // reset trade status - if (_player->acceptTrade) - { - _player->acceptTrade = false; - SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - } - - if (_player->pTrader->acceptTrade) - { - _player->pTrader->acceptTrade = false; - _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - } - - WorldPacket data(SMSG_TRADE_STATUS_EXTENDED, (100)); // guess size - data << (uint8 ) 1; // can be different (only seen 0 and 1) - data << (uint32) 0; // added in 2.4.0, this value must be equal to value from TRADE_STATUS_OPEN_WINDOW status packet (different value for different players to block multiple trades?) - data << (uint32) TRADE_SLOT_COUNT; // trade slots count/number?, = next field in most cases - data << (uint32) TRADE_SLOT_COUNT; // trade slots count/number?, = prev field in most cases - data << (uint32) _player->pTrader->tradeGold; // trader gold - data << (uint32) 0; // spell casted on lowest slot item - - for(uint8 i = 0; i < TRADE_SLOT_COUNT; i++) - { - item = (_player->pTrader->tradeItems[i] != NULL_SLOT ? _player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i] ) : NULL); - - data << (uint8) i; // trade slot number, if not specified, then end of packet - - if(item) - { - data << (uint32) item->GetProto()->ItemId; // entry - // display id - data << (uint32) item->GetProto()->DisplayInfoID; - // stack count - data << (uint32) item->GetUInt32Value(ITEM_FIELD_STACK_COUNT); - data << (uint32) 0; // probably gift=1, created_by=0? - // gift creator - data << (uint64) item->GetUInt64Value(ITEM_FIELD_GIFTCREATOR); - data << (uint32) item->GetEnchantmentId(PERM_ENCHANTMENT_SLOT); - for(uint8 j = 0; j < 3; ++j) - data << (uint32) 0; // enchantment id (permanent/gems?) - // creator - data << (uint64) item->GetUInt64Value(ITEM_FIELD_CREATOR); - data << (uint32) item->GetSpellCharges(); // charges - data << (uint32) item->GetItemSuffixFactor(); // SuffixFactor - // random properties id - data << (uint32) item->GetItemRandomPropertyId(); - data << (uint32) item->GetProto()->LockID; // lock id - // max durability - data << (uint32) item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY); - // durability - data << (uint32) item->GetUInt32Value(ITEM_FIELD_DURABILITY); - } - else - { - for(uint8 j = 0; j < 18; j++) - data << uint32(0); - } - } - SendPacket(&data); -} - -//============================================================== -// transfer the items to the players - -void WorldSession::moveItems(Item* myItems[], Item* hisItems[]) -{ - for(int i=0; ipTrader->CanStoreItem( NULL_BAG, NULL_SLOT, traderDst, myItems[i], false ) == EQUIP_ERR_OK); - bool playerCanTrade = (hisItems[i]==NULL || _player->CanStoreItem( NULL_BAG, NULL_SLOT, playerDst, hisItems[i], false ) == EQUIP_ERR_OK); - if(traderCanTrade && playerCanTrade ) - { - // Ok, if trade item exists and can be stored - // If we trade in both directions we had to check, if the trade will work before we actually do it - // A roll back is not possible after we stored it - if(myItems[i]) - { - // logging - sLog.outDebug("partner storing: %u",myItems[i]->GetGUIDLow()); - if( _player->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE) ) - sLog.outCommand("GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)", - _player->GetName(),_player->GetSession()->GetAccountId(), - myItems[i]->GetProto()->Name1,myItems[i]->GetEntry(),myItems[i]->GetCount(), - _player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId()); - - // store - _player->pTrader->MoveItemToInventory( traderDst, myItems[i], true, true); - } - if(hisItems[i]) - { - // logging - sLog.outDebug("player storing: %u",hisItems[i]->GetGUIDLow()); - if( _player->pTrader->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE) ) - sLog.outCommand("GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)", - _player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId(), - hisItems[i]->GetProto()->Name1,hisItems[i]->GetEntry(),hisItems[i]->GetCount(), - _player->GetName(),_player->GetSession()->GetAccountId()); - - // store - _player->MoveItemToInventory( playerDst, hisItems[i], true, true); - } - } - else - { - // in case of fatal error log error message - // return the already removed items to the original owner - if(myItems[i]) - { - if(!traderCanTrade) - sLog.outError("trader can't store item: %u",myItems[i]->GetGUIDLow()); - if(_player->CanStoreItem( NULL_BAG, NULL_SLOT, playerDst, myItems[i], false ) == EQUIP_ERR_OK) - _player->MoveItemToInventory(playerDst, myItems[i], true, true); - else - sLog.outError("player can't take item back: %u",myItems[i]->GetGUIDLow()); - } - // return the already removed items to the original owner - if(hisItems[i]) - { - if(!playerCanTrade) - sLog.outError("player can't store item: %u",hisItems[i]->GetGUIDLow()); - if(_player->pTrader->CanStoreItem( NULL_BAG, NULL_SLOT, traderDst, hisItems[i], false ) == EQUIP_ERR_OK) - _player->pTrader->MoveItemToInventory(traderDst, hisItems[i], true, true); - else - sLog.outError("trader can't take item back: %u",hisItems[i]->GetGUIDLow()); - } - } - } -} - -//============================================================== - -void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/) -{ - Item *myItems[TRADE_SLOT_TRADED_COUNT] = { NULL, NULL, NULL, NULL, NULL, NULL }; - Item *hisItems[TRADE_SLOT_TRADED_COUNT] = { NULL, NULL, NULL, NULL, NULL, NULL }; - bool myCanCompleteTrade=true,hisCanCompleteTrade=true; - - if ( !GetPlayer()->pTrader ) - return; - - // not accept case incorrect money amount - if( _player->tradeGold > _player->GetMoney() ) - { - SendNotification( "You do not have enough gold" ); - _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - _player->acceptTrade = false; - return; - } - - // not accept case incorrect money amount - if( _player->pTrader->tradeGold > _player->pTrader->GetMoney() ) - { - _player->pTrader->GetSession( )->SendNotification( "You do not have enough gold" ); - SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - _player->pTrader->acceptTrade = false; - return; - } - - // not accept if some items now can't be trade (cheating) - for(int i=0; itradeItems[i] != NULL_SLOT ) - { - if(Item* item =_player->GetItemByPos( _player->tradeItems[i] )) - { - if(!item->CanBeTraded()) - { - SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); - return; - } - } - } - if(_player->pTrader->tradeItems[i] != NULL_SLOT) - { - if(Item* item =_player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i]) ) - { - if(!item->CanBeTraded()) - { - SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); - return; - } - } - } - } - - _player->acceptTrade = true; - if (_player->pTrader->acceptTrade ) - { - // inform partner client - _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT); - - // store items in local list and set 'in-trade' flag - for(int i=0; itradeItems[i] != NULL_SLOT ) - { - sLog.outDebug("player trade item bag: %u slot: %u",_player->tradeItems[i] >> 8, _player->tradeItems[i] & 255 ); - //Can return NULL - myItems[i]=_player->GetItemByPos( _player->tradeItems[i] ); - if (myItems[i]) - myItems[i]->SetInTrade(); - } - if(_player->pTrader->tradeItems[i] != NULL_SLOT) - { - sLog.outDebug("partner trade item bag: %u slot: %u",_player->pTrader->tradeItems[i] >> 8,_player->pTrader->tradeItems[i] & 255); - //Can return NULL - hisItems[i]=_player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i]); - if(hisItems[i]) - hisItems[i]->SetInTrade(); - } - } - - // test if item will fit in each inventory - hisCanCompleteTrade = (_player->pTrader->CanStoreItems( myItems,TRADE_SLOT_TRADED_COUNT )== EQUIP_ERR_OK); - myCanCompleteTrade = (_player->CanStoreItems( hisItems,TRADE_SLOT_TRADED_COUNT ) == EQUIP_ERR_OK); - - // clear 'in-trade' flag - for(int i=0; iSetInTrade(false); - if(hisItems[i]) hisItems[i]->SetInTrade(false); - } - - // in case of missing space report error - if(!myCanCompleteTrade) - { - SendNotification("You do not have enough free slots"); - GetPlayer( )->pTrader->GetSession( )->SendNotification("Your partner does not have enough free bag slots"); - SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - return; - } - else if (!hisCanCompleteTrade) - { - SendNotification("Your partner does not have enough free bag slots"); - GetPlayer()->pTrader->GetSession()->SendNotification("You do not have enough free slots"); - SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - return; - } - - // execute trade: 1. remove - for(int i=0; iSetUInt64Value( ITEM_FIELD_GIFTCREATOR,_player->GetGUID()); - _player->MoveItemFromInventory(_player->tradeItems[i] >> 8, _player->tradeItems[i] & 255, true); - } - if(hisItems[i]) - { - hisItems[i]->SetUInt64Value( ITEM_FIELD_GIFTCREATOR,_player->pTrader->GetGUID()); - _player->pTrader->MoveItemFromInventory(_player->pTrader->tradeItems[i] >> 8, _player->pTrader->tradeItems[i] & 255, true); - } - } - - // execute trade: 2. store - moveItems(myItems, hisItems); - - // logging money - if(sWorld.getConfig(CONFIG_GM_LOG_TRADE)) - { - if( _player->GetSession()->GetSecurity() > SEC_PLAYER && _player->tradeGold > 0) - sLog.outCommand("GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)", - _player->GetName(),_player->GetSession()->GetAccountId(), - _player->tradeGold, - _player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId()); - if( _player->pTrader->GetSession()->GetSecurity() > SEC_PLAYER && _player->pTrader->tradeGold > 0) - sLog.outCommand("GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)", - _player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId(), - _player->pTrader->tradeGold, - _player->GetName(),_player->GetSession()->GetAccountId()); - } - - // update money - _player->ModifyMoney( -int32(_player->tradeGold) ); - _player->ModifyMoney(_player->pTrader->tradeGold ); - _player->pTrader->ModifyMoney( -int32(_player->pTrader->tradeGold) ); - _player->pTrader->ModifyMoney(_player->tradeGold ); - - _player->ClearTrade(); - _player->pTrader->ClearTrade(); - - // desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards) - CharacterDatabase.BeginTransaction(); - _player->SaveInventoryAndGoldToDB(); - _player->pTrader->SaveInventoryAndGoldToDB(); - CharacterDatabase.CommitTransaction(); - - _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE); - SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE); - - _player->pTrader->pTrader = NULL; - _player->pTrader = NULL; - } - else - { - _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT); - } -} - -void WorldSession::HandleUnacceptTradeOpcode(WorldPacket& /*recvPacket*/) -{ - if ( !GetPlayer()->pTrader ) - return; - - _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - _player->acceptTrade = false; -} - -void WorldSession::HandleBeginTradeOpcode(WorldPacket& /*recvPacket*/) -{ - if(!_player->pTrader) - return; - - _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_OPEN_WINDOW); - _player->pTrader->ClearTrade(); - - SendTradeStatus(TRADE_STATUS_OPEN_WINDOW); - _player->ClearTrade(); -} - -void WorldSession::SendCancelTrade() -{ - SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); -} - -void WorldSession::HandleCancelTradeOpcode(WorldPacket& /*recvPacket*/) -{ - // sended also after LOGOUT COMPLETE - if(_player) // needed because STATUS_AUTHED - _player->TradeCancel(true); -} - -void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket) -{ - CHECK_PACKET_SIZE(recvPacket,8); - - if( GetPlayer()->pTrader ) - return; - - uint64 ID; - - if( !GetPlayer()->isAlive() ) - { - SendTradeStatus(TRADE_STATUS_YOU_DEAD); - return; - } - - if( GetPlayer()->hasUnitState(UNIT_STAT_STUNDED) ) - { - SendTradeStatus(TRADE_STATUS_YOU_STUNNED); - return; - } - - if( isLogingOut() ) - { - SendTradeStatus(TRADE_STATUS_YOU_LOGOUT); - return; - } - - if( GetPlayer()->isInFlight() ) - { - SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR); - return; - } - - recvPacket >> ID; - - Player* pOther = ObjectAccessor::FindPlayer( ID ); - - if( !pOther ) - { - SendTradeStatus(TRADE_STATUS_NO_TARGET); - return; - } - - if( pOther == GetPlayer() || pOther->pTrader ) - { - SendTradeStatus(TRADE_STATUS_BUSY); - return; - } - - if( !pOther->isAlive() ) - { - SendTradeStatus(TRADE_STATUS_TARGET_DEAD); - return; - } - - if( pOther->isInFlight() ) - { - SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR); - return; - } - - if( pOther->hasUnitState(UNIT_STAT_STUNDED) ) - { - SendTradeStatus(TRADE_STATUS_TARGET_STUNNED); - return; - } - - if( pOther->GetSession()->isLogingOut() ) - { - SendTradeStatus(TRADE_STATUS_TARGET_LOGOUT); - return; - } - - if( pOther->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow()) ) - { - SendTradeStatus(TRADE_STATUS_IGNORE_YOU); - return; - } - - if(pOther->GetTeam() !=_player->GetTeam() ) - { - SendTradeStatus(TRADE_STATUS_WRONG_FACTION); - return; - } - - if( pOther->GetDistance2d( _player ) > 10.0f ) - { - SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR); - return; - } - - // OK start trade - _player->pTrader = pOther; - pOther->pTrader =_player; - - WorldPacket data(SMSG_TRADE_STATUS, 12); - data << (uint32) TRADE_STATUS_BEGIN_TRADE; - data << (uint64)_player->GetGUID(); - _player->pTrader->GetSession()->SendPacket(&data); -} - -void WorldSession::HandleSetTradeGoldOpcode(WorldPacket& recvPacket) -{ - CHECK_PACKET_SIZE(recvPacket,4); - - if(!_player->pTrader) - return; - - uint32 gold; - - recvPacket >> gold; - - // gold can be incorrect, but this is checked at trade finished. - _player->tradeGold = gold; - - _player->pTrader->GetSession()->SendUpdateTrade(); -} - -void WorldSession::HandleSetTradeItemOpcode(WorldPacket& recvPacket) -{ - CHECK_PACKET_SIZE(recvPacket,1+1+1); - - if(!_player->pTrader) - return; - - // send update - uint8 tradeSlot; - uint8 bag; - uint8 slot; - - recvPacket >> tradeSlot; - recvPacket >> bag; - recvPacket >> slot; - - // invalid slot number - if(tradeSlot >= TRADE_SLOT_COUNT) - { - SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); - return; - } - - // check cheating, can't fail with correct client operations - Item* item = _player->GetItemByPos(bag,slot); - if(!item || tradeSlot!=TRADE_SLOT_NONTRADED && !item->CanBeTraded()) - { - SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); - return; - } - - uint16 pos = (bag << 8) | slot; - - // prevent place single item into many trade slots using cheating and client bugs - for(int i = 0; i < TRADE_SLOT_COUNT; ++i) - { - if(_player->tradeItems[i]==pos) - { - // cheating attempt - SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); - return; - } - } - - _player->tradeItems[tradeSlot] = pos; - - _player->pTrader->GetSession()->SendUpdateTrade(); -} - -void WorldSession::HandleClearTradeItemOpcode(WorldPacket& recvPacket) -{ - CHECK_PACKET_SIZE(recvPacket,1); - - if(!_player->pTrader) - return; - - uint8 tradeSlot; - recvPacket >> tradeSlot; - - // invalid slot number - if(tradeSlot >= TRADE_SLOT_COUNT) - return; - - _player->tradeItems[tradeSlot] = NULL_SLOT; - - _player->pTrader->GetSession()->SendUpdateTrade(); -} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "World.h" +#include "ObjectAccessor.h" +#include "Log.h" +#include "Opcodes.h" +#include "Player.h" +#include "Item.h" +#include "SocialMgr.h" +#include "Language.h" + +enum TradeStatus +{ + TRADE_STATUS_BUSY = 0, + TRADE_STATUS_BEGIN_TRADE = 1, + TRADE_STATUS_OPEN_WINDOW = 2, + TRADE_STATUS_TRADE_CANCELED = 3, + TRADE_STATUS_TRADE_ACCEPT = 4, + TRADE_STATUS_BUSY_2 = 5, + TRADE_STATUS_NO_TARGET = 6, + TRADE_STATUS_BACK_TO_TRADE = 7, + TRADE_STATUS_TRADE_COMPLETE = 8, + // 9? + TRADE_STATUS_TARGET_TO_FAR = 10, + TRADE_STATUS_WRONG_FACTION = 11, + TRADE_STATUS_CLOSE_WINDOW = 12, + // 13? + TRADE_STATUS_IGNORE_YOU = 14, + TRADE_STATUS_YOU_STUNNED = 15, + TRADE_STATUS_TARGET_STUNNED = 16, + TRADE_STATUS_YOU_DEAD = 17, + TRADE_STATUS_TARGET_DEAD = 18, + TRADE_STATUS_YOU_LOGOUT = 19, + TRADE_STATUS_TARGET_LOGOUT = 20, + TRADE_STATUS_TRIAL_ACCOUNT = 21, // Trial accounts can not perform that action + TRADE_STATUS_ONLY_CONJURED = 22 // You can only trade conjured items... (cross realm BG related). +}; + +void WorldSession::SendTradeStatus(uint32 status) +{ + WorldPacket data; + + switch(status) + { + case TRADE_STATUS_BEGIN_TRADE: + data.Initialize(SMSG_TRADE_STATUS, 4+8); + data << uint32(status); + data << uint64(0); + break; + case TRADE_STATUS_OPEN_WINDOW: + data.Initialize(SMSG_TRADE_STATUS, 4+4); + data << uint32(status); + data << uint32(0); // added in 2.4.0 + break; + case TRADE_STATUS_CLOSE_WINDOW: + data.Initialize(SMSG_TRADE_STATUS, 4+4+1+4); + data << uint32(status); + data << uint32(0); + data << uint8(0); + data << uint32(0); + break; + case TRADE_STATUS_ONLY_CONJURED: + data.Initialize(SMSG_TRADE_STATUS, 4+1); + data << uint32(status); + data << uint8(0); + break; + default: + data.Initialize(SMSG_TRADE_STATUS, 4); + data << uint32(status); + break; + } + + SendPacket(&data); +} + +void WorldSession::HandleIgnoreTradeOpcode(WorldPacket& /*recvPacket*/) +{ + sLog.outDebug( "WORLD: Ignore Trade %u",_player->GetGUIDLow()); + // recvPacket.print_storage(); +} + +void WorldSession::HandleBusyTradeOpcode(WorldPacket& /*recvPacket*/) +{ + sLog.outDebug( "WORLD: Busy Trade %u",_player->GetGUIDLow()); + // recvPacket.print_storage(); +} + +void WorldSession::SendUpdateTrade() +{ + Item *item = NULL; + + if( !_player || !_player->pTrader ) + return; + + // reset trade status + if (_player->acceptTrade) + { + _player->acceptTrade = false; + SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + } + + if (_player->pTrader->acceptTrade) + { + _player->pTrader->acceptTrade = false; + _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + } + + WorldPacket data(SMSG_TRADE_STATUS_EXTENDED, (100)); // guess size + data << (uint8 ) 1; // can be different (only seen 0 and 1) + data << (uint32) 0; // added in 2.4.0, this value must be equal to value from TRADE_STATUS_OPEN_WINDOW status packet (different value for different players to block multiple trades?) + data << (uint32) TRADE_SLOT_COUNT; // trade slots count/number?, = next field in most cases + data << (uint32) TRADE_SLOT_COUNT; // trade slots count/number?, = prev field in most cases + data << (uint32) _player->pTrader->tradeGold; // trader gold + data << (uint32) 0; // spell casted on lowest slot item + + for(uint8 i = 0; i < TRADE_SLOT_COUNT; i++) + { + item = (_player->pTrader->tradeItems[i] != NULL_SLOT ? _player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i] ) : NULL); + + data << (uint8) i; // trade slot number, if not specified, then end of packet + + if(item) + { + data << (uint32) item->GetProto()->ItemId; // entry + // display id + data << (uint32) item->GetProto()->DisplayInfoID; + // stack count + data << (uint32) item->GetUInt32Value(ITEM_FIELD_STACK_COUNT); + data << (uint32) 0; // probably gift=1, created_by=0? + // gift creator + data << (uint64) item->GetUInt64Value(ITEM_FIELD_GIFTCREATOR); + data << (uint32) item->GetEnchantmentId(PERM_ENCHANTMENT_SLOT); + for(uint8 j = 0; j < 3; ++j) + data << (uint32) 0; // enchantment id (permanent/gems?) + // creator + data << (uint64) item->GetUInt64Value(ITEM_FIELD_CREATOR); + data << (uint32) item->GetSpellCharges(); // charges + data << (uint32) item->GetItemSuffixFactor(); // SuffixFactor + // random properties id + data << (uint32) item->GetItemRandomPropertyId(); + data << (uint32) item->GetProto()->LockID; // lock id + // max durability + data << (uint32) item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY); + // durability + data << (uint32) item->GetUInt32Value(ITEM_FIELD_DURABILITY); + } + else + { + for(uint8 j = 0; j < 18; j++) + data << uint32(0); + } + } + SendPacket(&data); +} + +//============================================================== +// transfer the items to the players + +void WorldSession::moveItems(Item* myItems[], Item* hisItems[]) +{ + for(int i=0; ipTrader->CanStoreItem( NULL_BAG, NULL_SLOT, traderDst, myItems[i], false ) == EQUIP_ERR_OK); + bool playerCanTrade = (hisItems[i]==NULL || _player->CanStoreItem( NULL_BAG, NULL_SLOT, playerDst, hisItems[i], false ) == EQUIP_ERR_OK); + if(traderCanTrade && playerCanTrade ) + { + // Ok, if trade item exists and can be stored + // If we trade in both directions we had to check, if the trade will work before we actually do it + // A roll back is not possible after we stored it + if(myItems[i]) + { + // logging + sLog.outDebug("partner storing: %u",myItems[i]->GetGUIDLow()); + if( _player->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE) ) + sLog.outCommand("GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)", + _player->GetName(),_player->GetSession()->GetAccountId(), + myItems[i]->GetProto()->Name1,myItems[i]->GetEntry(),myItems[i]->GetCount(), + _player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId()); + + // store + _player->pTrader->MoveItemToInventory( traderDst, myItems[i], true, true); + } + if(hisItems[i]) + { + // logging + sLog.outDebug("player storing: %u",hisItems[i]->GetGUIDLow()); + if( _player->pTrader->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE) ) + sLog.outCommand("GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)", + _player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId(), + hisItems[i]->GetProto()->Name1,hisItems[i]->GetEntry(),hisItems[i]->GetCount(), + _player->GetName(),_player->GetSession()->GetAccountId()); + + // store + _player->MoveItemToInventory( playerDst, hisItems[i], true, true); + } + } + else + { + // in case of fatal error log error message + // return the already removed items to the original owner + if(myItems[i]) + { + if(!traderCanTrade) + sLog.outError("trader can't store item: %u",myItems[i]->GetGUIDLow()); + if(_player->CanStoreItem( NULL_BAG, NULL_SLOT, playerDst, myItems[i], false ) == EQUIP_ERR_OK) + _player->MoveItemToInventory(playerDst, myItems[i], true, true); + else + sLog.outError("player can't take item back: %u",myItems[i]->GetGUIDLow()); + } + // return the already removed items to the original owner + if(hisItems[i]) + { + if(!playerCanTrade) + sLog.outError("player can't store item: %u",hisItems[i]->GetGUIDLow()); + if(_player->pTrader->CanStoreItem( NULL_BAG, NULL_SLOT, traderDst, hisItems[i], false ) == EQUIP_ERR_OK) + _player->pTrader->MoveItemToInventory(traderDst, hisItems[i], true, true); + else + sLog.outError("trader can't take item back: %u",hisItems[i]->GetGUIDLow()); + } + } + } +} + +//============================================================== + +void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/) +{ + Item *myItems[TRADE_SLOT_TRADED_COUNT] = { NULL, NULL, NULL, NULL, NULL, NULL }; + Item *hisItems[TRADE_SLOT_TRADED_COUNT] = { NULL, NULL, NULL, NULL, NULL, NULL }; + bool myCanCompleteTrade=true,hisCanCompleteTrade=true; + + if ( !GetPlayer()->pTrader ) + return; + + // not accept case incorrect money amount + if( _player->tradeGold > _player->GetMoney() ) + { + SendNotification(LANG_NOT_ENOUGH_GOLD); + _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + _player->acceptTrade = false; + return; + } + + // not accept case incorrect money amount + if( _player->pTrader->tradeGold > _player->pTrader->GetMoney() ) + { + _player->pTrader->GetSession( )->SendNotification(LANG_NOT_ENOUGH_GOLD); + SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + _player->pTrader->acceptTrade = false; + return; + } + + // not accept if some items now can't be trade (cheating) + for(int i=0; itradeItems[i] != NULL_SLOT ) + { + if(Item* item =_player->GetItemByPos( _player->tradeItems[i] )) + { + if(!item->CanBeTraded()) + { + SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); + return; + } + } + } + if(_player->pTrader->tradeItems[i] != NULL_SLOT) + { + if(Item* item =_player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i]) ) + { + if(!item->CanBeTraded()) + { + SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); + return; + } + } + } + } + + _player->acceptTrade = true; + if (_player->pTrader->acceptTrade ) + { + // inform partner client + _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT); + + // store items in local list and set 'in-trade' flag + for(int i=0; itradeItems[i] != NULL_SLOT ) + { + sLog.outDebug("player trade item bag: %u slot: %u",_player->tradeItems[i] >> 8, _player->tradeItems[i] & 255 ); + //Can return NULL + myItems[i]=_player->GetItemByPos( _player->tradeItems[i] ); + if (myItems[i]) + myItems[i]->SetInTrade(); + } + if(_player->pTrader->tradeItems[i] != NULL_SLOT) + { + sLog.outDebug("partner trade item bag: %u slot: %u",_player->pTrader->tradeItems[i] >> 8,_player->pTrader->tradeItems[i] & 255); + //Can return NULL + hisItems[i]=_player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i]); + if(hisItems[i]) + hisItems[i]->SetInTrade(); + } + } + + // test if item will fit in each inventory + hisCanCompleteTrade = (_player->pTrader->CanStoreItems( myItems,TRADE_SLOT_TRADED_COUNT )== EQUIP_ERR_OK); + myCanCompleteTrade = (_player->CanStoreItems( hisItems,TRADE_SLOT_TRADED_COUNT ) == EQUIP_ERR_OK); + + // clear 'in-trade' flag + for(int i=0; iSetInTrade(false); + if(hisItems[i]) hisItems[i]->SetInTrade(false); + } + + // in case of missing space report error + if(!myCanCompleteTrade) + { + SendNotification(LANG_NOT_FREE_TRADE_SLOTS); + GetPlayer( )->pTrader->GetSession( )->SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS); + SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + return; + } + else if (!hisCanCompleteTrade) + { + SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS); + GetPlayer()->pTrader->GetSession()->SendNotification(LANG_NOT_FREE_TRADE_SLOTS); + SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + return; + } + + // execute trade: 1. remove + for(int i=0; iSetUInt64Value( ITEM_FIELD_GIFTCREATOR,_player->GetGUID()); + _player->MoveItemFromInventory(_player->tradeItems[i] >> 8, _player->tradeItems[i] & 255, true); + } + if(hisItems[i]) + { + hisItems[i]->SetUInt64Value( ITEM_FIELD_GIFTCREATOR,_player->pTrader->GetGUID()); + _player->pTrader->MoveItemFromInventory(_player->pTrader->tradeItems[i] >> 8, _player->pTrader->tradeItems[i] & 255, true); + } + } + + // execute trade: 2. store + moveItems(myItems, hisItems); + + // logging money + if(sWorld.getConfig(CONFIG_GM_LOG_TRADE)) + { + if( _player->GetSession()->GetSecurity() > SEC_PLAYER && _player->tradeGold > 0) + sLog.outCommand("GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)", + _player->GetName(),_player->GetSession()->GetAccountId(), + _player->tradeGold, + _player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId()); + if( _player->pTrader->GetSession()->GetSecurity() > SEC_PLAYER && _player->pTrader->tradeGold > 0) + sLog.outCommand("GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)", + _player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId(), + _player->pTrader->tradeGold, + _player->GetName(),_player->GetSession()->GetAccountId()); + } + + // update money + _player->ModifyMoney( -int32(_player->tradeGold) ); + _player->ModifyMoney(_player->pTrader->tradeGold ); + _player->pTrader->ModifyMoney( -int32(_player->pTrader->tradeGold) ); + _player->pTrader->ModifyMoney(_player->tradeGold ); + + _player->ClearTrade(); + _player->pTrader->ClearTrade(); + + // desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards) + CharacterDatabase.BeginTransaction(); + _player->SaveInventoryAndGoldToDB(); + _player->pTrader->SaveInventoryAndGoldToDB(); + CharacterDatabase.CommitTransaction(); + + _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE); + SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE); + + _player->pTrader->pTrader = NULL; + _player->pTrader = NULL; + } + else + { + _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT); + } +} + +void WorldSession::HandleUnacceptTradeOpcode(WorldPacket& /*recvPacket*/) +{ + if ( !GetPlayer()->pTrader ) + return; + + _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + _player->acceptTrade = false; +} + +void WorldSession::HandleBeginTradeOpcode(WorldPacket& /*recvPacket*/) +{ + if(!_player->pTrader) + return; + + _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_OPEN_WINDOW); + _player->pTrader->ClearTrade(); + + SendTradeStatus(TRADE_STATUS_OPEN_WINDOW); + _player->ClearTrade(); +} + +void WorldSession::SendCancelTrade() +{ + SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); +} + +void WorldSession::HandleCancelTradeOpcode(WorldPacket& /*recvPacket*/) +{ + // sended also after LOGOUT COMPLETE + if(_player) // needed because STATUS_AUTHED + _player->TradeCancel(true); +} + +void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket) +{ + CHECK_PACKET_SIZE(recvPacket,8); + + if( GetPlayer()->pTrader ) + return; + + uint64 ID; + + if( !GetPlayer()->isAlive() ) + { + SendTradeStatus(TRADE_STATUS_YOU_DEAD); + return; + } + + if( GetPlayer()->hasUnitState(UNIT_STAT_STUNNED) ) + { + SendTradeStatus(TRADE_STATUS_YOU_STUNNED); + return; + } + + if( isLogingOut() ) + { + SendTradeStatus(TRADE_STATUS_YOU_LOGOUT); + return; + } + + if( GetPlayer()->isInFlight() ) + { + SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR); + return; + } + + recvPacket >> ID; + + Player* pOther = ObjectAccessor::FindPlayer( ID ); + + if( !pOther ) + { + SendTradeStatus(TRADE_STATUS_NO_TARGET); + return; + } + + if( pOther == GetPlayer() || pOther->pTrader ) + { + SendTradeStatus(TRADE_STATUS_BUSY); + return; + } + + if( !pOther->isAlive() ) + { + SendTradeStatus(TRADE_STATUS_TARGET_DEAD); + return; + } + + if( pOther->isInFlight() ) + { + SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR); + return; + } + + if( pOther->hasUnitState(UNIT_STAT_STUNNED) ) + { + SendTradeStatus(TRADE_STATUS_TARGET_STUNNED); + return; + } + + if( pOther->GetSession()->isLogingOut() ) + { + SendTradeStatus(TRADE_STATUS_TARGET_LOGOUT); + return; + } + + if( pOther->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow()) ) + { + SendTradeStatus(TRADE_STATUS_IGNORE_YOU); + return; + } + + if(pOther->GetTeam() !=_player->GetTeam() ) + { + SendTradeStatus(TRADE_STATUS_WRONG_FACTION); + return; + } + + if( pOther->GetDistance2d( _player ) > 10.0f ) + { + SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR); + return; + } + + // OK start trade + _player->pTrader = pOther; + pOther->pTrader =_player; + + WorldPacket data(SMSG_TRADE_STATUS, 12); + data << (uint32) TRADE_STATUS_BEGIN_TRADE; + data << (uint64)_player->GetGUID(); + _player->pTrader->GetSession()->SendPacket(&data); +} + +void WorldSession::HandleSetTradeGoldOpcode(WorldPacket& recvPacket) +{ + CHECK_PACKET_SIZE(recvPacket,4); + + if(!_player->pTrader) + return; + + uint32 gold; + + recvPacket >> gold; + + // gold can be incorrect, but this is checked at trade finished. + _player->tradeGold = gold; + + _player->pTrader->GetSession()->SendUpdateTrade(); +} + +void WorldSession::HandleSetTradeItemOpcode(WorldPacket& recvPacket) +{ + CHECK_PACKET_SIZE(recvPacket,1+1+1); + + if(!_player->pTrader) + return; + + // send update + uint8 tradeSlot; + uint8 bag; + uint8 slot; + + recvPacket >> tradeSlot; + recvPacket >> bag; + recvPacket >> slot; + + // invalid slot number + if(tradeSlot >= TRADE_SLOT_COUNT) + { + SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); + return; + } + + // check cheating, can't fail with correct client operations + Item* item = _player->GetItemByPos(bag,slot); + if(!item || tradeSlot!=TRADE_SLOT_NONTRADED && !item->CanBeTraded()) + { + SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); + return; + } + + uint16 pos = (bag << 8) | slot; + + // prevent place single item into many trade slots using cheating and client bugs + for(int i = 0; i < TRADE_SLOT_COUNT; ++i) + { + if(_player->tradeItems[i]==pos) + { + // cheating attempt + SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); + return; + } + } + + _player->tradeItems[tradeSlot] = pos; + + _player->pTrader->GetSession()->SendUpdateTrade(); +} + +void WorldSession::HandleClearTradeItemOpcode(WorldPacket& recvPacket) +{ + CHECK_PACKET_SIZE(recvPacket,1); + + if(!_player->pTrader) + return; + + uint8 tradeSlot; + recvPacket >> tradeSlot; + + // invalid slot number + if(tradeSlot >= TRADE_SLOT_COUNT) + return; + + _player->tradeItems[tradeSlot] = NULL_SLOT; + + _player->pTrader->GetSession()->SendUpdateTrade(); +} diff --git a/src/game/Unit.cpp b/src/game/Unit.cpp index 6acd2e077c7..bf4e98d427e 100644 --- a/src/game/Unit.cpp +++ b/src/game/Unit.cpp @@ -470,7 +470,7 @@ uint32 Unit::DealDamage(Unit *pVictim, uint32 damage, CleanDamage const* cleanDa if(pVictim != this) RemoveSpellsCausingAura(SPELL_AURA_MOD_INVISIBILITY); - if(pVictim->GetTypeId() == TYPEID_PLAYER && !pVictim->IsStandState() && !pVictim->hasUnitState(UNIT_STAT_STUNDED)) + if(pVictim->GetTypeId() == TYPEID_PLAYER && !pVictim->IsStandState() && !pVictim->hasUnitState(UNIT_STAT_STUNNED)) pVictim->SetStandState(PLAYER_STATE_NONE); } @@ -2149,7 +2149,7 @@ void Unit::DoAttackDamage (Unit *pVictim, uint32 *damage, CleanDamage *cleanDama void Unit::AttackerStateUpdate (Unit *pVictim, WeaponAttackType attType, bool extra ) { - if(hasUnitState(UNIT_STAT_CONFUSED | UNIT_STAT_STUNDED | UNIT_STAT_FLEEING) || HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED) ) + if(hasUnitState(UNIT_STAT_CONFUSED | UNIT_STAT_STUNNED | UNIT_STAT_FLEEING) || HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED) ) return; if (!pVictim->isAlive()) @@ -2961,7 +2961,7 @@ uint32 Unit::GetDefenseSkillValue(Unit const* target) const float Unit::GetUnitDodgeChance() const { - if(hasUnitState(UNIT_STAT_STUNDED)) + if(hasUnitState(UNIT_STAT_STUNNED)) return 0.0f; if( GetTypeId() == TYPEID_PLAYER ) return GetFloatValue(PLAYER_DODGE_PERCENTAGE); @@ -2980,7 +2980,7 @@ float Unit::GetUnitDodgeChance() const float Unit::GetUnitParryChance() const { - if ( IsNonMeleeSpellCasted(false) || hasUnitState(UNIT_STAT_STUNDED)) + if ( IsNonMeleeSpellCasted(false) || hasUnitState(UNIT_STAT_STUNNED)) return 0.0f; float chance = 0.0f; @@ -3012,7 +3012,7 @@ float Unit::GetUnitParryChance() const float Unit::GetUnitBlockChance() const { - if ( IsNonMeleeSpellCasted(false) || hasUnitState(UNIT_STAT_STUNDED)) + if ( IsNonMeleeSpellCasted(false) || hasUnitState(UNIT_STAT_STUNNED)) return 0.0f; if(GetTypeId() == TYPEID_PLAYER) @@ -7985,7 +7985,7 @@ bool Unit::IsImmunedToSpell(SpellEntry const* spellInfo, bool useCharges) //FIX ME this hack: don't get feared if stunned if (spellInfo->Mechanic == MECHANIC_FEAR ) { - if ( hasUnitState(UNIT_STAT_STUNDED) ) + if ( hasUnitState(UNIT_STAT_STUNNED) ) return true; } @@ -8662,7 +8662,7 @@ bool Unit::isVisibleForOrDetect(Unit const* u, bool detect, bool inVisibleList) return true; //If a mob or player is stunned he will not be able to detect stealth - if (u->hasUnitState(UNIT_STAT_STUNDED) && (u != this)) + if (u->hasUnitState(UNIT_STAT_STUNNED) && (u != this)) return false; // Creature can detect target only in aggro radius @@ -9136,7 +9136,7 @@ bool Unit::SelectHostilTarget() if(target) { - if(!hasUnitState(UNIT_STAT_STUNDED)) + if(!hasUnitState(UNIT_STAT_STUNNED)) SetInFront(target); ((Creature*)this)->AI()->AttackStart(target); return true; diff --git a/src/game/Unit.h b/src/game/Unit.h index a46a6de8106..5641f5a64df 100644 --- a/src/game/Unit.h +++ b/src/game/Unit.h @@ -346,7 +346,7 @@ enum UnitState UNIT_STAT_DIED = 0x0001, UNIT_STAT_MELEE_ATTACKING = 0x0002, // player is melee attacking someone //UNIT_STAT_MELEE_ATTACK_BY = 0x0004, // player is melee attack by someone - UNIT_STAT_STUNDED = 0x0008, + UNIT_STAT_STUNNED = 0x0008, UNIT_STAT_ROAMING = 0x0010, UNIT_STAT_CHASE = 0x0020, UNIT_STAT_SEARCHING = 0x0040, @@ -756,7 +756,7 @@ class MANGOS_DLL_SPEC Unit : public WorldObject bool CanFreeMove() const { return !hasUnitState(UNIT_STAT_CONFUSED | UNIT_STAT_FLEEING | UNIT_STAT_IN_FLIGHT | - UNIT_STAT_ROOT | UNIT_STAT_STUNDED | UNIT_STAT_DISTRACTED ) && GetOwnerGUID()==0; + UNIT_STAT_ROOT | UNIT_STAT_STUNNED | UNIT_STAT_DISTRACTED ) && GetOwnerGUID()==0; } uint32 getLevel() const { return GetUInt32Value(UNIT_FIELD_LEVEL); } diff --git a/src/game/WaypointMovementGenerator.cpp b/src/game/WaypointMovementGenerator.cpp index b998b61fa8a..fee4c216293 100644 --- a/src/game/WaypointMovementGenerator.cpp +++ b/src/game/WaypointMovementGenerator.cpp @@ -85,7 +85,7 @@ WaypointMovementGenerator::Update(Creature &creature, const uint32 &di // Waypoint movement can be switched on/off // This is quite handy for escort quests and other stuff - if(creature.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED | UNIT_STAT_DISTRACTED)) + if(creature.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED | UNIT_STAT_DISTRACTED)) return true; // prevent a crash at empty waypoint path. diff --git a/src/game/World.cpp b/src/game/World.cpp index 9ce6888a08a..b195e394f36 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -642,11 +642,14 @@ void World::LoadConfigSettings(bool reload) m_configs[CONFIG_MIN_PETITION_SIGNS] = 9; } - m_configs[CONFIG_GM_WISPERING_TO] = sConfig.GetBoolDefault("GM.WhisperingTo",false); - m_configs[CONFIG_GM_IN_GM_LIST] = sConfig.GetBoolDefault("GM.InGMList",false); - m_configs[CONFIG_GM_IN_WHO_LIST] = sConfig.GetBoolDefault("GM.InWhoList",false); - m_configs[CONFIG_GM_LOGIN_STATE] = sConfig.GetIntDefault("GM.LoginState",2); - m_configs[CONFIG_GM_LOG_TRADE] = sConfig.GetBoolDefault("GM.LogTrade", false); + m_configs[CONFIG_GM_LOGIN_STATE] = sConfig.GetIntDefault("GM.LoginState",2); + m_configs[CONFIG_GM_ACCEPT_TICKETS] = sConfig.GetIntDefault("GM.AcceptTickets",2); + m_configs[CONFIG_GM_CHAT] = sConfig.GetIntDefault("GM.Chat",2); + m_configs[CONFIG_GM_WISPERING_TO] = sConfig.GetIntDefault("GM.WhisperingTo",2); + + m_configs[CONFIG_GM_IN_GM_LIST] = sConfig.GetBoolDefault("GM.InGMList",false); + m_configs[CONFIG_GM_IN_WHO_LIST] = sConfig.GetBoolDefault("GM.InWhoList",false); + m_configs[CONFIG_GM_LOG_TRADE] = sConfig.GetBoolDefault("GM.LogTrade", false); m_configs[CONFIG_GROUP_VISIBILITY] = sConfig.GetIntDefault("Visibility.GroupMode",0); diff --git a/src/game/World.h b/src/game/World.h index fd15391405a..8543c94648b 100644 --- a/src/game/World.h +++ b/src/game/World.h @@ -1,540 +1,542 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -/// \addtogroup world The World -/// @{ -/// \file - -#ifndef __WORLD_H -#define __WORLD_H - -#include "Common.h" -#include "Timer.h" -#include "Policies/Singleton.h" - -#include -#include -#include - -class Object; -class WorldPacket; -class WorldSession; -class Player; -class Weather; -struct ScriptAction; -struct ScriptInfo; -class CliCommandHolder; -class SqlResultQueue; -class QueryResult; -class WorldSocket; - -enum ShutdownMask -{ - SHUTDOWN_MASK_RESTART = 1, - SHUTDOWN_MASK_IDLE = 2, -}; - -/// Timers for different object refresh rates -enum WorldTimers -{ - WUPDATE_OBJECTS = 0, - WUPDATE_SESSIONS = 1, - WUPDATE_AUCTIONS = 2, - WUPDATE_WEATHERS = 3, - WUPDATE_UPTIME = 4, - WUPDATE_CORPSES = 5, - WUPDATE_EVENTS = 6, - WUPDATE_COUNT = 7 -}; - -/// Configuration elements -enum WorldConfigs -{ - CONFIG_COMPRESSION = 0, - CONFIG_GRID_UNLOAD, - CONFIG_INTERVAL_SAVE, - CONFIG_INTERVAL_GRIDCLEAN, - CONFIG_INTERVAL_MAPUPDATE, - CONFIG_INTERVAL_CHANGEWEATHER, - CONFIG_PORT_WORLD, - CONFIG_SOCKET_SELECTTIME, - CONFIG_GROUP_XP_DISTANCE, - CONFIG_SIGHT_MONSTER, - CONFIG_SIGHT_GUARDER, - CONFIG_GAME_TYPE, - CONFIG_REALM_ZONE, - CONFIG_ALLOW_TWO_SIDE_ACCOUNTS, - CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHAT, - CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHANNEL, - CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP, - CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD, - CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION, - CONFIG_ALLOW_TWO_SIDE_INTERACTION_MAIL, - CONFIG_ALLOW_TWO_SIDE_WHO_LIST, - CONFIG_ALLOW_TWO_SIDE_ADD_FRIEND, - CONFIG_STRICT_PLAYER_NAMES, - CONFIG_STRICT_CHARTER_NAMES, - CONFIG_STRICT_PET_NAMES, - CONFIG_CHARACTERS_CREATING_DISABLED, - CONFIG_CHARACTERS_PER_ACCOUNT, - CONFIG_CHARACTERS_PER_REALM, - CONFIG_SKIP_CINEMATICS, - CONFIG_MAX_PLAYER_LEVEL, - CONFIG_START_PLAYER_LEVEL, - CONFIG_MAX_HONOR_POINTS, - CONFIG_MAX_ARENA_POINTS, - CONFIG_INSTANCE_IGNORE_LEVEL, - CONFIG_INSTANCE_IGNORE_RAID, - CONFIG_BATTLEGROUND_CAST_DESERTER, - CONFIG_INSTANCE_RESET_TIME_HOUR, - CONFIG_INSTANCE_UNLOAD_DELAY, - CONFIG_CAST_UNSTUCK, - CONFIG_MAX_PRIMARY_TRADE_SKILL, - CONFIG_MIN_PETITION_SIGNS, - CONFIG_GM_WISPERING_TO, - CONFIG_GM_IN_GM_LIST, - CONFIG_GM_IN_WHO_LIST, - CONFIG_GM_LOGIN_STATE, - CONFIG_GM_LOG_TRADE, - CONFIG_GROUP_VISIBILITY, - CONFIG_MAIL_DELIVERY_DELAY, - CONFIG_UPTIME_UPDATE, - CONFIG_SKILL_CHANCE_ORANGE, - CONFIG_SKILL_CHANCE_YELLOW, - CONFIG_SKILL_CHANCE_GREEN, - CONFIG_SKILL_CHANCE_GREY, - CONFIG_SKILL_CHANCE_MINING_STEPS, - CONFIG_SKILL_CHANCE_SKINNING_STEPS, - CONFIG_SKILL_PROSPECTING, - CONFIG_SKILL_GAIN_CRAFTING, - CONFIG_SKILL_GAIN_DEFENSE, - CONFIG_SKILL_GAIN_GATHERING, - CONFIG_SKILL_GAIN_WEAPON, - CONFIG_MAX_OVERSPEED_PINGS, - CONFIG_SAVE_RESPAWN_TIME_IMMEDIATLY, - CONFIG_WEATHER, - CONFIG_EXPANSION, - CONFIG_CHATFLOOD_MESSAGE_COUNT, - CONFIG_CHATFLOOD_MESSAGE_DELAY, - CONFIG_CHATFLOOD_MUTE_TIME, - CONFIG_EVENT_ANNOUNCE, - CONFIG_CREATURE_FAMILY_ASSISTEMCE_RADIUS, - CONFIG_WORLD_BOSS_LEVEL_DIFF, - CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF, - CONFIG_QUEST_HIGH_LEVEL_HIDE_DIFF, - CONFIG_RESTRICTED_LFG_CHANNEL, - CONFIG_SILENTLY_GM_JOIN_TO_CHANNEL, - CONFIG_TALENTS_INSPECTING, - CONFIG_CHAT_FAKE_MESSAGE_PREVENTING, - CONFIG_TCP_NO_DELAY, - CONFIG_CORPSE_DECAY_NORMAL, - CONFIG_CORPSE_DECAY_RARE, - CONFIG_CORPSE_DECAY_ELITE, - CONFIG_CORPSE_DECAY_RAREELITE, - CONFIG_CORPSE_DECAY_WORLDBOSS, - CONFIG_ADDON_CHANNEL, - CONFIG_DEATH_SICKNESS_LEVEL, - CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVP, - CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVE, - CONFIG_THREAT_RADIUS, - CONFIG_DECLINED_NAMES_USED, - CONFIG_LISTEN_RANGE_SAY, - CONFIG_LISTEN_RANGE_TEXTEMOTE, - CONFIG_LISTEN_RANGE_YELL, - CONFIG_ARENA_MAX_RATING_DIFFERENCE, - CONFIG_ARENA_RATING_DISCARD_TIMER, - CONFIG_ARENA_AUTO_DISTRIBUTE_POINTS, - CONFIG_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS, - CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER, - CONFIG_VALUE_COUNT -}; - -/// Server rates -enum Rates -{ - RATE_HEALTH=0, - RATE_POWER_MANA, - RATE_POWER_RAGE_INCOME, - RATE_POWER_RAGE_LOSS, - RATE_POWER_FOCUS, - RATE_SKILL_DISCOVERY, - RATE_DROP_ITEM_POOR, - RATE_DROP_ITEM_NORMAL, - RATE_DROP_ITEM_UNCOMMON, - RATE_DROP_ITEM_RARE, - RATE_DROP_ITEM_EPIC, - RATE_DROP_ITEM_LEGENDARY, - RATE_DROP_ITEM_ARTIFACT, - RATE_DROP_ITEM_REFERENCED, - RATE_DROP_MONEY, - RATE_XP_KILL, - RATE_XP_QUEST, - RATE_XP_EXPLORE, - RATE_XP_PAST_70, - RATE_REPUTATION_GAIN, - RATE_CREATURE_NORMAL_HP, - RATE_CREATURE_ELITE_ELITE_HP, - RATE_CREATURE_ELITE_RAREELITE_HP, - RATE_CREATURE_ELITE_WORLDBOSS_HP, - RATE_CREATURE_ELITE_RARE_HP, - RATE_CREATURE_NORMAL_DAMAGE, - RATE_CREATURE_ELITE_ELITE_DAMAGE, - RATE_CREATURE_ELITE_RAREELITE_DAMAGE, - RATE_CREATURE_ELITE_WORLDBOSS_DAMAGE, - RATE_CREATURE_ELITE_RARE_DAMAGE, - RATE_CREATURE_NORMAL_SPELLDAMAGE, - RATE_CREATURE_ELITE_ELITE_SPELLDAMAGE, - RATE_CREATURE_ELITE_RAREELITE_SPELLDAMAGE, - RATE_CREATURE_ELITE_WORLDBOSS_SPELLDAMAGE, - RATE_CREATURE_ELITE_RARE_SPELLDAMAGE, - RATE_CREATURE_AGGRO, - RATE_REST_INGAME, - RATE_REST_OFFLINE_IN_TAVERN_OR_CITY, - RATE_REST_OFFLINE_IN_WILDERNESS, - RATE_DAMAGE_FALL, - RATE_AUCTION_TIME, - RATE_AUCTION_DEPOSIT, - RATE_AUCTION_CUT, - RATE_HONOR, - RATE_MINING_AMOUNT, - RATE_MINING_NEXT, - RATE_TALENT, - RATE_LOYALTY, - RATE_CORPSE_DECAY_LOOTED, - RATE_INSTANCE_RESET_TIME, - RATE_TARGET_POS_RECALCULATION_RANGE, - RATE_DURABILITY_LOSS_DAMAGE, - RATE_DURABILITY_LOSS_PARRY, - RATE_DURABILITY_LOSS_ABSORB, - RATE_DURABILITY_LOSS_BLOCK, - MAX_RATES -}; - -/// Type of server -enum RealmType -{ - REALM_TYPE_NORMAL = 0, - REALM_TYPE_PVP = 1, - REALM_TYPE_NORMAL2 = 4, - REALM_TYPE_RP = 6, - REALM_TYPE_RPPVP = 8, - REALM_TYPE_FFA_PVP = 16 // custom, free for all pvp mode like arena PvP in all zones except rest activated places and sanctuaries - // replaced by REALM_PVP in realm list -}; - -enum RealmZone -{ - REALM_ZONE_UNKNOWN = 0, // any language - REALM_ZONE_DEVELOPMENT = 1, // any language - REALM_ZONE_UNITED_STATES = 2, // extended-Latin - REALM_ZONE_OCEANIC = 3, // extended-Latin - REALM_ZONE_LATIN_AMERICA = 4, // extended-Latin - REALM_ZONE_TOURNAMENT_5 = 5, // basic-Latin at create, any at login - REALM_ZONE_KOREA = 6, // East-Asian - REALM_ZONE_TOURNAMENT_7 = 7, // basic-Latin at create, any at login - REALM_ZONE_ENGLISH = 8, // extended-Latin - REALM_ZONE_GERMAN = 9, // extended-Latin - REALM_ZONE_FRENCH = 10, // extended-Latin - REALM_ZONE_SPANISH = 11, // extended-Latin - REALM_ZONE_RUSSIAN = 12, // Cyrillic - REALM_ZONE_TOURNAMENT_13 = 13, // basic-Latin at create, any at login - REALM_ZONE_TAIWAN = 14, // East-Asian - REALM_ZONE_TOURNAMENT_15 = 15, // basic-Latin at create, any at login - REALM_ZONE_CHINA = 16, // East-Asian - REALM_ZONE_CN1 = 17, // basic-Latin at create, any at login - REALM_ZONE_CN2 = 18, // basic-Latin at create, any at login - REALM_ZONE_CN3 = 19, // basic-Latin at create, any at login - REALM_ZONE_CN4 = 20, // basic-Latin at create, any at login - REALM_ZONE_CN5 = 21, // basic-Latin at create, any at login - REALM_ZONE_CN6 = 22, // basic-Latin at create, any at login - REALM_ZONE_CN7 = 23, // basic-Latin at create, any at login - REALM_ZONE_CN8 = 24, // basic-Latin at create, any at login - REALM_ZONE_TOURNAMENT_25 = 25, // basic-Latin at create, any at login - REALM_ZONE_TEST_SERVER = 26, // any language - REALM_ZONE_TOURNAMENT_27 = 27, // basic-Latin at create, any at login - REALM_ZONE_QA_SERVER = 28, // any language - REALM_ZONE_CN9 = 29 // basic-Latin at create, any at login -}; - -/// Ban function return codes -enum BanReturn -{ - BAN_SUCCESS, - BAN_SYNTAX_ERROR, - BAN_NOTFOUND -}; - -// DB scripting commands -#define SCRIPT_COMMAND_TALK 0 // source = unit, target=any, datalong ( 0=say, 1=whisper, 2=yell, 3=emote text) -#define SCRIPT_COMMAND_EMOTE 1 // source = unit, datalong = anim_id -#define SCRIPT_COMMAND_FIELD_SET 2 // source = any, datalong = field_id, datalog2 = value -#define SCRIPT_COMMAND_MOVE_TO 3 // source = Creature, datalog2 = time, x/y/z -#define SCRIPT_COMMAND_FLAG_SET 4 // source = any, datalong = field_id, datalog2 = bitmask -#define SCRIPT_COMMAND_FLAG_REMOVE 5 // source = any, datalong = field_id, datalog2 = bitmask -#define SCRIPT_COMMAND_TELEPORT_TO 6 // source or target with Player, datalong = map_id, x/y/z -#define SCRIPT_COMMAND_QUEST_EXPLORED 7 // one from source or target must be Player, another GO/Creature, datalong=quest_id, datalong2=distance or 0 -#define SCRIPT_COMMAND_RESPAWN_GAMEOBJECT 9 // source = any (summoner), datalong=db_guid, datalong2=despawn_delay -#define SCRIPT_COMMAND_TEMP_SUMMON_CREATURE 10 // source = any (summoner), datalong=creature entry, datalong2=despawn_delay -#define SCRIPT_COMMAND_OPEN_DOOR 11 // source = unit, datalong=db_guid, datalong2=reset_delay -#define SCRIPT_COMMAND_CLOSE_DOOR 12 // source = unit, datalong=db_guid, datalong2=reset_delay -#define SCRIPT_COMMAND_ACTIVATE_OBJECT 13 // source = unit, target=GO -#define SCRIPT_COMMAND_REMOVE_AURA 14 // source (datalong2!=0) or target (datalong==0) unit, datalong = spell_id -#define SCRIPT_COMMAND_CAST_SPELL 15 // source (datalong2!=0) or target (datalong==0) unit, datalong = spell_id - -/// CLI related stuff, define here to prevent cyclic dependancies - -typedef int(* pPrintf)(const char*,...); -typedef void(* pCliFunc)(char *,pPrintf); - -/// Command Template class -struct CliCommand -{ - char const * cmd; - pCliFunc Func; - char const * description; -}; - -/// Storage class for commands issued for delayed execution -class CliCommandHolder -{ - private: - const CliCommand *cmd; - char *args; - pPrintf m_zprintf; - public: - CliCommandHolder(const CliCommand *command, const char *arguments, pPrintf p_zprintf) - : cmd(command), m_zprintf(p_zprintf) - { - size_t len = strlen(arguments)+1; - args = new char[len]; - memcpy(args, arguments, len); - } - ~CliCommandHolder() { delete[] args; } - void Execute() const { cmd->Func(args, m_zprintf); } - pPrintf GetOutputMethod() const {return (m_zprintf);} -}; - -/// The World -class World -{ - public: - static volatile bool m_stopEvent; - static volatile uint32 m_worldLoopCounter; - - World(); - ~World(); - - WorldSession* FindSession(uint32 id) const; - void AddSession(WorldSession *s); - bool RemoveSession(uint32 id); - /// Get the number of current active sessions - void UpdateMaxSessionCounters(); - uint32 GetActiveAndQueuedSessionCount() const { return m_sessions.size(); } - uint32 GetActiveSessionCount() const { return m_sessions.size() - m_QueuedPlayer.size(); } - uint32 GetQueuedSessionCount() const { return m_QueuedPlayer.size(); } - /// Get the maximum number of parallel sessions on the server since last reboot - uint32 GetMaxQueuedSessionCount() const { return m_maxQueuedSessionCount; } - uint32 GetMaxActiveSessionCount() const { return m_maxActiveSessionCount; } - Player* FindPlayerInZone(uint32 zone); - - Weather* FindWeather(uint32 id) const; - Weather* AddWeather(uint32 zone_id); - void RemoveWeather(uint32 zone_id); - - /// Get the active session server limit (or security level limitations) - uint32 GetPlayerAmountLimit() const { return m_playerLimit >= 0 ? m_playerLimit : 0; } - AccountTypes GetPlayerSecurityLimit() const { return m_playerLimit <= 0 ? AccountTypes(-m_playerLimit) : SEC_PLAYER; } - - /// Set the active session server limit (or security level limitation) - void SetPlayerLimit(int32 limit, bool needUpdate = false); - - //player Queue - typedef std::list Queue; - void AddQueuedPlayer(WorldSession*); - void RemoveQueuedPlayer(WorldSession*); - int32 GetQueuePos(WorldSession*); - uint32 GetQueueSize() const { return m_QueuedPlayer.size(); } - - /// \todo Actions on m_allowMovement still to be implemented - /// Is movement allowed? - bool getAllowMovement() const { return m_allowMovement; } - /// Allow/Disallow object movements - void SetAllowMovement(bool allow) { m_allowMovement = allow; } - - /// Set a new Message of the Day - void SetMotd(std::string motd) { m_motd = motd; } - /// Get the current Message of the Day - const char* GetMotd() const { return m_motd.c_str(); } - - uint32 GetDefaultDbcLocale() const { return m_defaultDbcLocale; } - - /// Get the path where data (dbc, maps) are stored on disk - std::string GetDataPath() const { return m_dataPath; } - - /// When server started? - time_t const& GetStartTime() const { return m_startTime; } - /// What time is it? - time_t const& GetGameTime() const { return m_gameTime; } - /// Uptime (in secs) - uint32 GetUptime() const { return uint32(m_gameTime - m_startTime); } - - /// Get the maximum skill level a player can reach - uint16 GetConfigMaxSkillValue() const - { - uint32 lvl = getConfig(CONFIG_MAX_PLAYER_LEVEL); - return lvl > 60 ? 300 + ((lvl - 60) * 75) / 10 : lvl*5; - } - - void SetInitialWorldSettings(); - void LoadConfigSettings(bool reload = false); - - void SendWorldText(int32 string_id, ...); - void SendGlobalMessage(WorldPacket *packet, WorldSession *self = 0, uint32 team = 0); - void SendZoneMessage(uint32 zone, WorldPacket *packet, WorldSession *self = 0, uint32 team = 0); - void SendZoneText(uint32 zone, const char *text, WorldSession *self = 0, uint32 team = 0); - void SendServerMessage(uint32 type, const char *text = "", Player* player = NULL); - - /// Are we in the middle of a shutdown? - uint32 GetShutdownMask() const { return m_ShutdownMask; } - bool IsShutdowning() const { return m_ShutdownTimer > 0; } - void ShutdownServ(uint32 time, uint32 options = 0); - void ShutdownCancel(); - void ShutdownMsg(bool show = false, Player* player = NULL); - - void Update(time_t diff); - - void UpdateSessions( time_t diff ); - /// Set a server rate (see #Rates) - void setRate(Rates rate,float value) { rate_values[rate]=value; } - /// Get a server rate (see #Rates) - float getRate(Rates rate) const { return rate_values[rate]; } - - /// Set a server configuration element (see #WorldConfigs) - void setConfig(uint32 index,uint32 value) - { - if(index > const& scripts, uint32 id, Object* source, Object* target); - void ScriptCommandStart(ScriptInfo const& script, uint32 delay, Object* source, Object* target); - bool IsScriptScheduled() const { return !m_scriptSchedule.empty(); } - - // for max speed access - static float GetMaxVisibleDistanceForCreature() { return m_MaxVisibleDistanceForCreature; } - static float GetMaxVisibleDistanceForPlayer() { return m_MaxVisibleDistanceForPlayer; } - static float GetMaxVisibleDistanceForObject() { return m_MaxVisibleDistanceForObject; } - static float GetMaxVisibleDistanceInFlight() { return m_MaxVisibleDistanceInFlight; } - static float GetVisibleUnitGreyDistance() { return m_VisibleUnitGreyDistance; } - static float GetVisibleObjectGreyDistance() { return m_VisibleObjectGreyDistance; } - - void ProcessCliCommands(); - void QueueCliCommand(CliCommandHolder* command) { cliCmdQueue.add(command); } - - void UpdateResultQueue(); - void InitResultQueue(); - - void UpdateRealmCharCount(uint32 accid); - - LocaleConstant GetAvailableDbcLocale(LocaleConstant locale) const { if(m_availableDbcLocaleMask & (1 << locale)) return locale; else return m_defaultDbcLocale; } - protected: - void _UpdateGameTime(); - void ScriptsProcess(); - // callback for UpdateRealmCharacters - void _UpdateRealmCharCount(QueryResult *resultCharCount, uint32 accountId); - - void InitDailyQuestResetTime(); - void ResetDailyQuests(); - private: - time_t m_startTime; - time_t m_gameTime; - IntervalTimer m_timers[WUPDATE_COUNT]; - uint32 mail_timer; - uint32 mail_timer_expires; - - typedef HM_NAMESPACE::hash_map WeatherMap; - WeatherMap m_weathers; - typedef HM_NAMESPACE::hash_map SessionMap; - SessionMap m_sessions; - std::set m_kicked_sessions; - uint32 m_maxActiveSessionCount; - uint32 m_maxQueuedSessionCount; - - std::multimap m_scriptSchedule; - - float rate_values[MAX_RATES]; - uint32 m_configs[CONFIG_VALUE_COUNT]; - int32 m_playerLimit; - LocaleConstant m_defaultDbcLocale; // from config for one from loaded DBC locales - uint32 m_availableDbcLocaleMask; // by loaded DBC - void DetectDBCLang(); - bool m_allowMovement; - std::string m_motd; - std::string m_dataPath; - - uint32 m_ShutdownTimer; - uint32 m_ShutdownMask; - - // for max speed access - static float m_MaxVisibleDistanceForCreature; - static float m_MaxVisibleDistanceForPlayer; - static float m_MaxVisibleDistanceForObject; - static float m_MaxVisibleDistanceInFlight; - static float m_VisibleUnitGreyDistance; - static float m_VisibleObjectGreyDistance; - - // CLI command holder to be thread safe - ZThread::LockedQueue cliCmdQueue; - SqlResultQueue *m_resultQueue; - - // next daily quests reset time - time_t m_NextDailyQuestReset; - - //Player Queue - Queue m_QueuedPlayer; - - //sessions that are added async - void AddSession_(WorldSession* s); - ZThread::LockedQueue addSessQueue; -}; - -extern uint32 realmID; - -#define sWorld MaNGOS::Singleton::Instance() -#endif -/// @} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/// \addtogroup world The World +/// @{ +/// \file + +#ifndef __WORLD_H +#define __WORLD_H + +#include "Common.h" +#include "Timer.h" +#include "Policies/Singleton.h" + +#include +#include +#include + +class Object; +class WorldPacket; +class WorldSession; +class Player; +class Weather; +struct ScriptAction; +struct ScriptInfo; +class CliCommandHolder; +class SqlResultQueue; +class QueryResult; +class WorldSocket; + +enum ShutdownMask +{ + SHUTDOWN_MASK_RESTART = 1, + SHUTDOWN_MASK_IDLE = 2, +}; + +/// Timers for different object refresh rates +enum WorldTimers +{ + WUPDATE_OBJECTS = 0, + WUPDATE_SESSIONS = 1, + WUPDATE_AUCTIONS = 2, + WUPDATE_WEATHERS = 3, + WUPDATE_UPTIME = 4, + WUPDATE_CORPSES = 5, + WUPDATE_EVENTS = 6, + WUPDATE_COUNT = 7 +}; + +/// Configuration elements +enum WorldConfigs +{ + CONFIG_COMPRESSION = 0, + CONFIG_GRID_UNLOAD, + CONFIG_INTERVAL_SAVE, + CONFIG_INTERVAL_GRIDCLEAN, + CONFIG_INTERVAL_MAPUPDATE, + CONFIG_INTERVAL_CHANGEWEATHER, + CONFIG_PORT_WORLD, + CONFIG_SOCKET_SELECTTIME, + CONFIG_GROUP_XP_DISTANCE, + CONFIG_SIGHT_MONSTER, + CONFIG_SIGHT_GUARDER, + CONFIG_GAME_TYPE, + CONFIG_REALM_ZONE, + CONFIG_ALLOW_TWO_SIDE_ACCOUNTS, + CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHAT, + CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHANNEL, + CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP, + CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD, + CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION, + CONFIG_ALLOW_TWO_SIDE_INTERACTION_MAIL, + CONFIG_ALLOW_TWO_SIDE_WHO_LIST, + CONFIG_ALLOW_TWO_SIDE_ADD_FRIEND, + CONFIG_STRICT_PLAYER_NAMES, + CONFIG_STRICT_CHARTER_NAMES, + CONFIG_STRICT_PET_NAMES, + CONFIG_CHARACTERS_CREATING_DISABLED, + CONFIG_CHARACTERS_PER_ACCOUNT, + CONFIG_CHARACTERS_PER_REALM, + CONFIG_SKIP_CINEMATICS, + CONFIG_MAX_PLAYER_LEVEL, + CONFIG_START_PLAYER_LEVEL, + CONFIG_MAX_HONOR_POINTS, + CONFIG_MAX_ARENA_POINTS, + CONFIG_INSTANCE_IGNORE_LEVEL, + CONFIG_INSTANCE_IGNORE_RAID, + CONFIG_BATTLEGROUND_CAST_DESERTER, + CONFIG_INSTANCE_RESET_TIME_HOUR, + CONFIG_INSTANCE_UNLOAD_DELAY, + CONFIG_CAST_UNSTUCK, + CONFIG_MAX_PRIMARY_TRADE_SKILL, + CONFIG_MIN_PETITION_SIGNS, + CONFIG_GM_LOGIN_STATE, + CONFIG_GM_ACCEPT_TICKETS, + CONFIG_GM_CHAT, + CONFIG_GM_WISPERING_TO, + CONFIG_GM_IN_GM_LIST, + CONFIG_GM_IN_WHO_LIST, + CONFIG_GM_LOG_TRADE, + CONFIG_GROUP_VISIBILITY, + CONFIG_MAIL_DELIVERY_DELAY, + CONFIG_UPTIME_UPDATE, + CONFIG_SKILL_CHANCE_ORANGE, + CONFIG_SKILL_CHANCE_YELLOW, + CONFIG_SKILL_CHANCE_GREEN, + CONFIG_SKILL_CHANCE_GREY, + CONFIG_SKILL_CHANCE_MINING_STEPS, + CONFIG_SKILL_CHANCE_SKINNING_STEPS, + CONFIG_SKILL_PROSPECTING, + CONFIG_SKILL_GAIN_CRAFTING, + CONFIG_SKILL_GAIN_DEFENSE, + CONFIG_SKILL_GAIN_GATHERING, + CONFIG_SKILL_GAIN_WEAPON, + CONFIG_MAX_OVERSPEED_PINGS, + CONFIG_SAVE_RESPAWN_TIME_IMMEDIATLY, + CONFIG_WEATHER, + CONFIG_EXPANSION, + CONFIG_CHATFLOOD_MESSAGE_COUNT, + CONFIG_CHATFLOOD_MESSAGE_DELAY, + CONFIG_CHATFLOOD_MUTE_TIME, + CONFIG_EVENT_ANNOUNCE, + CONFIG_CREATURE_FAMILY_ASSISTEMCE_RADIUS, + CONFIG_WORLD_BOSS_LEVEL_DIFF, + CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF, + CONFIG_QUEST_HIGH_LEVEL_HIDE_DIFF, + CONFIG_RESTRICTED_LFG_CHANNEL, + CONFIG_SILENTLY_GM_JOIN_TO_CHANNEL, + CONFIG_TALENTS_INSPECTING, + CONFIG_CHAT_FAKE_MESSAGE_PREVENTING, + CONFIG_TCP_NO_DELAY, + CONFIG_CORPSE_DECAY_NORMAL, + CONFIG_CORPSE_DECAY_RARE, + CONFIG_CORPSE_DECAY_ELITE, + CONFIG_CORPSE_DECAY_RAREELITE, + CONFIG_CORPSE_DECAY_WORLDBOSS, + CONFIG_ADDON_CHANNEL, + CONFIG_DEATH_SICKNESS_LEVEL, + CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVP, + CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVE, + CONFIG_THREAT_RADIUS, + CONFIG_DECLINED_NAMES_USED, + CONFIG_LISTEN_RANGE_SAY, + CONFIG_LISTEN_RANGE_TEXTEMOTE, + CONFIG_LISTEN_RANGE_YELL, + CONFIG_ARENA_MAX_RATING_DIFFERENCE, + CONFIG_ARENA_RATING_DISCARD_TIMER, + CONFIG_ARENA_AUTO_DISTRIBUTE_POINTS, + CONFIG_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS, + CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER, + CONFIG_VALUE_COUNT +}; + +/// Server rates +enum Rates +{ + RATE_HEALTH=0, + RATE_POWER_MANA, + RATE_POWER_RAGE_INCOME, + RATE_POWER_RAGE_LOSS, + RATE_POWER_FOCUS, + RATE_SKILL_DISCOVERY, + RATE_DROP_ITEM_POOR, + RATE_DROP_ITEM_NORMAL, + RATE_DROP_ITEM_UNCOMMON, + RATE_DROP_ITEM_RARE, + RATE_DROP_ITEM_EPIC, + RATE_DROP_ITEM_LEGENDARY, + RATE_DROP_ITEM_ARTIFACT, + RATE_DROP_ITEM_REFERENCED, + RATE_DROP_MONEY, + RATE_XP_KILL, + RATE_XP_QUEST, + RATE_XP_EXPLORE, + RATE_XP_PAST_70, + RATE_REPUTATION_GAIN, + RATE_CREATURE_NORMAL_HP, + RATE_CREATURE_ELITE_ELITE_HP, + RATE_CREATURE_ELITE_RAREELITE_HP, + RATE_CREATURE_ELITE_WORLDBOSS_HP, + RATE_CREATURE_ELITE_RARE_HP, + RATE_CREATURE_NORMAL_DAMAGE, + RATE_CREATURE_ELITE_ELITE_DAMAGE, + RATE_CREATURE_ELITE_RAREELITE_DAMAGE, + RATE_CREATURE_ELITE_WORLDBOSS_DAMAGE, + RATE_CREATURE_ELITE_RARE_DAMAGE, + RATE_CREATURE_NORMAL_SPELLDAMAGE, + RATE_CREATURE_ELITE_ELITE_SPELLDAMAGE, + RATE_CREATURE_ELITE_RAREELITE_SPELLDAMAGE, + RATE_CREATURE_ELITE_WORLDBOSS_SPELLDAMAGE, + RATE_CREATURE_ELITE_RARE_SPELLDAMAGE, + RATE_CREATURE_AGGRO, + RATE_REST_INGAME, + RATE_REST_OFFLINE_IN_TAVERN_OR_CITY, + RATE_REST_OFFLINE_IN_WILDERNESS, + RATE_DAMAGE_FALL, + RATE_AUCTION_TIME, + RATE_AUCTION_DEPOSIT, + RATE_AUCTION_CUT, + RATE_HONOR, + RATE_MINING_AMOUNT, + RATE_MINING_NEXT, + RATE_TALENT, + RATE_LOYALTY, + RATE_CORPSE_DECAY_LOOTED, + RATE_INSTANCE_RESET_TIME, + RATE_TARGET_POS_RECALCULATION_RANGE, + RATE_DURABILITY_LOSS_DAMAGE, + RATE_DURABILITY_LOSS_PARRY, + RATE_DURABILITY_LOSS_ABSORB, + RATE_DURABILITY_LOSS_BLOCK, + MAX_RATES +}; + +/// Type of server +enum RealmType +{ + REALM_TYPE_NORMAL = 0, + REALM_TYPE_PVP = 1, + REALM_TYPE_NORMAL2 = 4, + REALM_TYPE_RP = 6, + REALM_TYPE_RPPVP = 8, + REALM_TYPE_FFA_PVP = 16 // custom, free for all pvp mode like arena PvP in all zones except rest activated places and sanctuaries + // replaced by REALM_PVP in realm list +}; + +enum RealmZone +{ + REALM_ZONE_UNKNOWN = 0, // any language + REALM_ZONE_DEVELOPMENT = 1, // any language + REALM_ZONE_UNITED_STATES = 2, // extended-Latin + REALM_ZONE_OCEANIC = 3, // extended-Latin + REALM_ZONE_LATIN_AMERICA = 4, // extended-Latin + REALM_ZONE_TOURNAMENT_5 = 5, // basic-Latin at create, any at login + REALM_ZONE_KOREA = 6, // East-Asian + REALM_ZONE_TOURNAMENT_7 = 7, // basic-Latin at create, any at login + REALM_ZONE_ENGLISH = 8, // extended-Latin + REALM_ZONE_GERMAN = 9, // extended-Latin + REALM_ZONE_FRENCH = 10, // extended-Latin + REALM_ZONE_SPANISH = 11, // extended-Latin + REALM_ZONE_RUSSIAN = 12, // Cyrillic + REALM_ZONE_TOURNAMENT_13 = 13, // basic-Latin at create, any at login + REALM_ZONE_TAIWAN = 14, // East-Asian + REALM_ZONE_TOURNAMENT_15 = 15, // basic-Latin at create, any at login + REALM_ZONE_CHINA = 16, // East-Asian + REALM_ZONE_CN1 = 17, // basic-Latin at create, any at login + REALM_ZONE_CN2 = 18, // basic-Latin at create, any at login + REALM_ZONE_CN3 = 19, // basic-Latin at create, any at login + REALM_ZONE_CN4 = 20, // basic-Latin at create, any at login + REALM_ZONE_CN5 = 21, // basic-Latin at create, any at login + REALM_ZONE_CN6 = 22, // basic-Latin at create, any at login + REALM_ZONE_CN7 = 23, // basic-Latin at create, any at login + REALM_ZONE_CN8 = 24, // basic-Latin at create, any at login + REALM_ZONE_TOURNAMENT_25 = 25, // basic-Latin at create, any at login + REALM_ZONE_TEST_SERVER = 26, // any language + REALM_ZONE_TOURNAMENT_27 = 27, // basic-Latin at create, any at login + REALM_ZONE_QA_SERVER = 28, // any language + REALM_ZONE_CN9 = 29 // basic-Latin at create, any at login +}; + +/// Ban function return codes +enum BanReturn +{ + BAN_SUCCESS, + BAN_SYNTAX_ERROR, + BAN_NOTFOUND +}; + +// DB scripting commands +#define SCRIPT_COMMAND_TALK 0 // source = unit, target=any, datalong ( 0=say, 1=whisper, 2=yell, 3=emote text) +#define SCRIPT_COMMAND_EMOTE 1 // source = unit, datalong = anim_id +#define SCRIPT_COMMAND_FIELD_SET 2 // source = any, datalong = field_id, datalog2 = value +#define SCRIPT_COMMAND_MOVE_TO 3 // source = Creature, datalog2 = time, x/y/z +#define SCRIPT_COMMAND_FLAG_SET 4 // source = any, datalong = field_id, datalog2 = bitmask +#define SCRIPT_COMMAND_FLAG_REMOVE 5 // source = any, datalong = field_id, datalog2 = bitmask +#define SCRIPT_COMMAND_TELEPORT_TO 6 // source or target with Player, datalong = map_id, x/y/z +#define SCRIPT_COMMAND_QUEST_EXPLORED 7 // one from source or target must be Player, another GO/Creature, datalong=quest_id, datalong2=distance or 0 +#define SCRIPT_COMMAND_RESPAWN_GAMEOBJECT 9 // source = any (summoner), datalong=db_guid, datalong2=despawn_delay +#define SCRIPT_COMMAND_TEMP_SUMMON_CREATURE 10 // source = any (summoner), datalong=creature entry, datalong2=despawn_delay +#define SCRIPT_COMMAND_OPEN_DOOR 11 // source = unit, datalong=db_guid, datalong2=reset_delay +#define SCRIPT_COMMAND_CLOSE_DOOR 12 // source = unit, datalong=db_guid, datalong2=reset_delay +#define SCRIPT_COMMAND_ACTIVATE_OBJECT 13 // source = unit, target=GO +#define SCRIPT_COMMAND_REMOVE_AURA 14 // source (datalong2!=0) or target (datalong==0) unit, datalong = spell_id +#define SCRIPT_COMMAND_CAST_SPELL 15 // source (datalong2!=0) or target (datalong==0) unit, datalong = spell_id + +/// CLI related stuff, define here to prevent cyclic dependancies + +typedef int(* pPrintf)(const char*,...); +typedef void(* pCliFunc)(char *,pPrintf); + +/// Command Template class +struct CliCommand +{ + char const * cmd; + pCliFunc Func; + char const * description; +}; + +/// Storage class for commands issued for delayed execution +class CliCommandHolder +{ + private: + const CliCommand *cmd; + char *args; + pPrintf m_zprintf; + public: + CliCommandHolder(const CliCommand *command, const char *arguments, pPrintf p_zprintf) + : cmd(command), m_zprintf(p_zprintf) + { + size_t len = strlen(arguments)+1; + args = new char[len]; + memcpy(args, arguments, len); + } + ~CliCommandHolder() { delete[] args; } + void Execute() const { cmd->Func(args, m_zprintf); } + pPrintf GetOutputMethod() const {return (m_zprintf);} +}; + +/// The World +class World +{ + public: + static volatile bool m_stopEvent; + static volatile uint32 m_worldLoopCounter; + + World(); + ~World(); + + WorldSession* FindSession(uint32 id) const; + void AddSession(WorldSession *s); + bool RemoveSession(uint32 id); + /// Get the number of current active sessions + void UpdateMaxSessionCounters(); + uint32 GetActiveAndQueuedSessionCount() const { return m_sessions.size(); } + uint32 GetActiveSessionCount() const { return m_sessions.size() - m_QueuedPlayer.size(); } + uint32 GetQueuedSessionCount() const { return m_QueuedPlayer.size(); } + /// Get the maximum number of parallel sessions on the server since last reboot + uint32 GetMaxQueuedSessionCount() const { return m_maxQueuedSessionCount; } + uint32 GetMaxActiveSessionCount() const { return m_maxActiveSessionCount; } + Player* FindPlayerInZone(uint32 zone); + + Weather* FindWeather(uint32 id) const; + Weather* AddWeather(uint32 zone_id); + void RemoveWeather(uint32 zone_id); + + /// Get the active session server limit (or security level limitations) + uint32 GetPlayerAmountLimit() const { return m_playerLimit >= 0 ? m_playerLimit : 0; } + AccountTypes GetPlayerSecurityLimit() const { return m_playerLimit <= 0 ? AccountTypes(-m_playerLimit) : SEC_PLAYER; } + + /// Set the active session server limit (or security level limitation) + void SetPlayerLimit(int32 limit, bool needUpdate = false); + + //player Queue + typedef std::list Queue; + void AddQueuedPlayer(WorldSession*); + void RemoveQueuedPlayer(WorldSession*); + int32 GetQueuePos(WorldSession*); + uint32 GetQueueSize() const { return m_QueuedPlayer.size(); } + + /// \todo Actions on m_allowMovement still to be implemented + /// Is movement allowed? + bool getAllowMovement() const { return m_allowMovement; } + /// Allow/Disallow object movements + void SetAllowMovement(bool allow) { m_allowMovement = allow; } + + /// Set a new Message of the Day + void SetMotd(std::string motd) { m_motd = motd; } + /// Get the current Message of the Day + const char* GetMotd() const { return m_motd.c_str(); } + + uint32 GetDefaultDbcLocale() const { return m_defaultDbcLocale; } + + /// Get the path where data (dbc, maps) are stored on disk + std::string GetDataPath() const { return m_dataPath; } + + /// When server started? + time_t const& GetStartTime() const { return m_startTime; } + /// What time is it? + time_t const& GetGameTime() const { return m_gameTime; } + /// Uptime (in secs) + uint32 GetUptime() const { return uint32(m_gameTime - m_startTime); } + + /// Get the maximum skill level a player can reach + uint16 GetConfigMaxSkillValue() const + { + uint32 lvl = getConfig(CONFIG_MAX_PLAYER_LEVEL); + return lvl > 60 ? 300 + ((lvl - 60) * 75) / 10 : lvl*5; + } + + void SetInitialWorldSettings(); + void LoadConfigSettings(bool reload = false); + + void SendWorldText(int32 string_id, ...); + void SendGlobalMessage(WorldPacket *packet, WorldSession *self = 0, uint32 team = 0); + void SendZoneMessage(uint32 zone, WorldPacket *packet, WorldSession *self = 0, uint32 team = 0); + void SendZoneText(uint32 zone, const char *text, WorldSession *self = 0, uint32 team = 0); + void SendServerMessage(uint32 type, const char *text = "", Player* player = NULL); + + /// Are we in the middle of a shutdown? + uint32 GetShutdownMask() const { return m_ShutdownMask; } + bool IsShutdowning() const { return m_ShutdownTimer > 0; } + void ShutdownServ(uint32 time, uint32 options = 0); + void ShutdownCancel(); + void ShutdownMsg(bool show = false, Player* player = NULL); + + void Update(time_t diff); + + void UpdateSessions( time_t diff ); + /// Set a server rate (see #Rates) + void setRate(Rates rate,float value) { rate_values[rate]=value; } + /// Get a server rate (see #Rates) + float getRate(Rates rate) const { return rate_values[rate]; } + + /// Set a server configuration element (see #WorldConfigs) + void setConfig(uint32 index,uint32 value) + { + if(index > const& scripts, uint32 id, Object* source, Object* target); + void ScriptCommandStart(ScriptInfo const& script, uint32 delay, Object* source, Object* target); + bool IsScriptScheduled() const { return !m_scriptSchedule.empty(); } + + // for max speed access + static float GetMaxVisibleDistanceForCreature() { return m_MaxVisibleDistanceForCreature; } + static float GetMaxVisibleDistanceForPlayer() { return m_MaxVisibleDistanceForPlayer; } + static float GetMaxVisibleDistanceForObject() { return m_MaxVisibleDistanceForObject; } + static float GetMaxVisibleDistanceInFlight() { return m_MaxVisibleDistanceInFlight; } + static float GetVisibleUnitGreyDistance() { return m_VisibleUnitGreyDistance; } + static float GetVisibleObjectGreyDistance() { return m_VisibleObjectGreyDistance; } + + void ProcessCliCommands(); + void QueueCliCommand(CliCommandHolder* command) { cliCmdQueue.add(command); } + + void UpdateResultQueue(); + void InitResultQueue(); + + void UpdateRealmCharCount(uint32 accid); + + LocaleConstant GetAvailableDbcLocale(LocaleConstant locale) const { if(m_availableDbcLocaleMask & (1 << locale)) return locale; else return m_defaultDbcLocale; } + protected: + void _UpdateGameTime(); + void ScriptsProcess(); + // callback for UpdateRealmCharacters + void _UpdateRealmCharCount(QueryResult *resultCharCount, uint32 accountId); + + void InitDailyQuestResetTime(); + void ResetDailyQuests(); + private: + time_t m_startTime; + time_t m_gameTime; + IntervalTimer m_timers[WUPDATE_COUNT]; + uint32 mail_timer; + uint32 mail_timer_expires; + + typedef HM_NAMESPACE::hash_map WeatherMap; + WeatherMap m_weathers; + typedef HM_NAMESPACE::hash_map SessionMap; + SessionMap m_sessions; + std::set m_kicked_sessions; + uint32 m_maxActiveSessionCount; + uint32 m_maxQueuedSessionCount; + + std::multimap m_scriptSchedule; + + float rate_values[MAX_RATES]; + uint32 m_configs[CONFIG_VALUE_COUNT]; + int32 m_playerLimit; + LocaleConstant m_defaultDbcLocale; // from config for one from loaded DBC locales + uint32 m_availableDbcLocaleMask; // by loaded DBC + void DetectDBCLang(); + bool m_allowMovement; + std::string m_motd; + std::string m_dataPath; + + uint32 m_ShutdownTimer; + uint32 m_ShutdownMask; + + // for max speed access + static float m_MaxVisibleDistanceForCreature; + static float m_MaxVisibleDistanceForPlayer; + static float m_MaxVisibleDistanceForObject; + static float m_MaxVisibleDistanceInFlight; + static float m_VisibleUnitGreyDistance; + static float m_VisibleObjectGreyDistance; + + // CLI command holder to be thread safe + ZThread::LockedQueue cliCmdQueue; + SqlResultQueue *m_resultQueue; + + // next daily quests reset time + time_t m_NextDailyQuestReset; + + //Player Queue + Queue m_QueuedPlayer; + + //sessions that are added async + void AddSession_(WorldSession* s); + ZThread::LockedQueue addSessQueue; +}; + +extern uint32 realmID; + +#define sWorld MaNGOS::Singleton::Instance() +#endif +/// @} diff --git a/src/game/WorldSession.cpp b/src/game/WorldSession.cpp index 9ec9624182c..b6585185eea 100644 --- a/src/game/WorldSession.cpp +++ b/src/game/WorldSession.cpp @@ -1,493 +1,511 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -/** \file - \ingroup u2w -*/ - -#include "WorldSocket.h" -#include "Common.h" -#include "Database/DatabaseEnv.h" -#include "Log.h" -#include "Opcodes.h" -#include "WorldSocket.h" -#include "WorldPacket.h" -#include "WorldSession.h" -#include "Player.h" -#include "ObjectMgr.h" -#include "Group.h" -#include "Guild.h" -#include "World.h" -#include "MapManager.h" -#include "ObjectAccessor.h" -#include "BattleGroundMgr.h" -#include "Language.h" // for CMSG_CANCEL_MOUNT_AURA handler -#include "Chat.h" -#include "SocialMgr.h" - -/// WorldSession constructor -WorldSession::WorldSession(uint32 id, WorldSocket *sock, uint32 sec, bool tbc, time_t mute_time, LocaleConstant locale) : -LookingForGroup_auto_join(false), LookingForGroup_auto_add(false), m_muteTime(mute_time), -_player(NULL), m_Socket(sock),_security(sec), _accountId(id), m_isTBC(tbc), -m_sessionDbcLocale(sWorld.GetAvailableDbcLocale(locale)), m_sessionDbLocaleIndex(objmgr.GetIndexForLocale(locale)), -_logoutTime(0), m_playerLoading(false), m_playerLogout(false), m_playerRecentlyLogout(false), m_latency(0) -{ - if (sock) - { - m_Address = sock->GetRemoteAddress (); - sock->AddReference (); - } -} - -/// WorldSession destructor -WorldSession::~WorldSession() -{ - ///- unload player if not unloaded - if(_player) - LogoutPlayer(true); - - /// - If have unclosed socket, close it - if (m_Socket) - { - m_Socket->CloseSocket (); - m_Socket->RemoveReference (); - m_Socket = NULL; - } - - ///- empty incoming packet queue - while(!_recvQueue.empty()) - { - WorldPacket *packet = _recvQueue.next(); - delete packet; - } - - sWorld.RemoveQueuedPlayer(this); -} - -void WorldSession::SizeError(WorldPacket const& packet, uint32 size) const -{ - sLog.outError("Client (account %u) send packet %s (%u) with size %u but expected %u (attempt crash server?), skipped", - GetAccountId(),LookupOpcodeName(packet.GetOpcode()),packet.GetOpcode(),packet.size(),size); -} - -/// Get the player name -char const* WorldSession::GetPlayerName() const -{ - return GetPlayer() ? GetPlayer()->GetName() : ""; -} - -/// Send a packet to the client -void WorldSession::SendPacket(WorldPacket const* packet) -{ - if (!m_Socket) - return; - #ifdef MANGOS_DEBUG - // Code for network use statistic - static uint64 sendPacketCount = 0; - static uint64 sendPacketBytes = 0; - - static time_t firstTime = time(NULL); - static time_t lastTime = firstTime; // next 60 secs start time - - static uint64 sendLastPacketCount = 0; - static uint64 sendLastPacketBytes = 0; - - time_t cur_time = time(NULL); - - if((cur_time - lastTime) < 60) - { - sendPacketCount+=1; - sendPacketBytes+=packet->size(); - - sendLastPacketCount+=1; - sendLastPacketBytes+=packet->size(); - } - else - { - uint64 minTime = uint64(cur_time - lastTime); - uint64 fullTime = uint64(lastTime - firstTime); - sLog.outDetail("Send all time packets count: " I64FMTD " bytes: " I64FMTD " avr.count/sec: %f avr.bytes/sec: %f time: %u",sendPacketCount,sendPacketBytes,float(sendPacketCount)/fullTime,float(sendPacketBytes)/fullTime,uint32(fullTime)); - sLog.outDetail("Send last min packets count: " I64FMTD " bytes: " I64FMTD " avr.count/sec: %f avr.bytes/sec: %f",sendLastPacketCount,sendLastPacketBytes,float(sendLastPacketCount)/minTime,float(sendLastPacketBytes)/minTime); - - lastTime = cur_time; - sendLastPacketCount = 1; - sendLastPacketBytes = packet->wpos(); // wpos is real written size - } -#endif // !MANGOS_DEBUG - - if (m_Socket->SendPacket (*packet) == -1) - { - m_Socket->CloseSocket (); - } -} - -/// Add an incoming packet to the queue -void WorldSession::QueuePacket(WorldPacket* new_packet) -{ - _recvQueue.add(new_packet); -} - -/// Logging helper for unexpected opcodes -void WorldSession::logUnexpectedOpcode(WorldPacket* packet, const char *reason) -{ - sLog.outError( "SESSION: received unexpected opcode %s (0x%.4X) %s", - LookupOpcodeName(packet->GetOpcode()), - packet->GetOpcode(), - reason); -} - -/// Update the WorldSession (triggered by World update) -bool WorldSession::Update(uint32 /*diff*/) -{ - if (m_Socket) - if (m_Socket->IsClosed ()) - { - m_Socket->RemoveReference (); - m_Socket = NULL; - } - - WorldPacket *packet; - - ///- Retrieve packets from the receive queue and call the appropriate handlers - /// \todo Is there a way to consolidate the OpcondeHandlerTable and the g_worldOpcodeNames to only maintain 1 list? - /// answer : there is a way, but this is better, because it would use redundant RAM - while (!_recvQueue.empty()) - { - packet = _recvQueue.next(); - - /*#if 1 - sLog.outError( "MOEP: %s (0x%.4X)", - LookupOpcodeName(packet->GetOpcode()), - packet->GetOpcode()); - #endif*/ - - if(packet->GetOpcode() >= NUM_MSG_TYPES) - { - sLog.outError( "SESSION: received non-existed opcode %s (0x%.4X)", - LookupOpcodeName(packet->GetOpcode()), - packet->GetOpcode()); - } - else - { - OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()]; - switch (opHandle.status) - { - case STATUS_LOGGEDIN: - if(!_player) - { - // skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets - if(!m_playerRecentlyLogout) - logUnexpectedOpcode(packet, "the player has not logged in yet"); - } - else if(_player->IsInWorld()) - (this->*opHandle.handler)(*packet); - // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer - break; - case STATUS_TRANSFER_PENDING: - if(!_player) - logUnexpectedOpcode(packet, "the player has not logged in yet"); - else if(_player->IsInWorld()) - logUnexpectedOpcode(packet, "the player is still in world"); - else - (this->*opHandle.handler)(*packet); - break; - case STATUS_AUTHED: - m_playerRecentlyLogout = false; - (this->*opHandle.handler)(*packet); - break; - case STATUS_NEVER: - sLog.outError( "SESSION: received not allowed opcode %s (0x%.4X)", - LookupOpcodeName(packet->GetOpcode()), - packet->GetOpcode()); - break; - } - } - - delete packet; - } - - ///- If necessary, log the player out - time_t currTime = time(NULL); - if (!m_Socket || (ShouldLogOut(currTime) && !m_playerLoading)) - LogoutPlayer(true); - - if (!m_Socket) - return false; //Will remove this session from the world session map - - return true; -} - -/// %Log the player out -void WorldSession::LogoutPlayer(bool Save) -{ - // finish pending transfers before starting the logout - while(_player && _player->IsBeingTeleported()) - HandleMoveWorldportAckOpcode(); - - m_playerLogout = true; - - if (_player) - { - ///- If the player just died before logging out, make him appear as a ghost - //FIXME: logout must be delayed in case lost connection with client in time of combat - if (_player->GetDeathTimer()) - { - _player->getHostilRefManager().deleteReferences(); - _player->BuildPlayerRepop(); - _player->RepopAtGraveyard(); - } - else if (!_player->getAttackers().empty()) - { - _player->CombatStop(); - _player->getHostilRefManager().setOnlineOfflineState(false); - _player->RemoveAllAurasOnDeath(); - - // build set of player who attack _player or who have pet attacking of _player - std::set aset; - for(Unit::AttackerSet::const_iterator itr = _player->getAttackers().begin(); itr != _player->getAttackers().end(); ++itr) - { - Unit* owner = (*itr)->GetOwner(); // including player controlled case - if(owner) - { - if(owner->GetTypeId()==TYPEID_PLAYER) - aset.insert((Player*)owner); - } - else - if((*itr)->GetTypeId()==TYPEID_PLAYER) - aset.insert((Player*)(*itr)); - } - - _player->SetPvPDeath(!aset.empty()); - _player->KillPlayer(); - _player->BuildPlayerRepop(); - _player->RepopAtGraveyard(); - - // give honor to all attackers from set like group case - for(std::set::const_iterator itr = aset.begin(); itr != aset.end(); ++itr) - (*itr)->RewardHonor(_player,aset.size()); - - // give bg rewards and update counters like kill by first from attackers - // this can't be called for all attackers. - if(!aset.empty()) - if(BattleGround *bg = _player->GetBattleGround()) - bg->HandleKillPlayer(_player,*aset.begin()); - } - else if(_player->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION)) - { - // this will kill character by SPELL_AURA_SPIRIT_OF_REDEMPTION - _player->RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT); - //_player->SetDeathPvP(*); set at SPELL_AURA_SPIRIT_OF_REDEMPTION apply time - _player->KillPlayer(); - _player->BuildPlayerRepop(); - _player->RepopAtGraveyard(); - } - - ///- Remove player from battleground (teleport to entrance) - if(_player->InBattleGround()) - _player->LeaveBattleground(); - - for (int i=0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) - { - if(int32 bgTypeId = _player->GetBattleGroundQueueId(i)) - { - _player->RemoveBattleGroundQueueId(bgTypeId); - sBattleGroundMgr.m_BattleGroundQueues[ bgTypeId ].RemovePlayer(_player->GetGUID(), true); - } - } - - ///- Reset the online field in the account table - // no point resetting online in character table here as Player::SaveToDB() will set it to 1 since player has not been removed from world at this stage - //No SQL injection as AccountID is uint32 - loginDatabase.PExecute("UPDATE account SET online = 0 WHERE id = '%u'", GetAccountId()); - - ///- If the player is in a guild, update the guild roster and broadcast a logout message to other guild members - Guild *guild = objmgr.GetGuildById(_player->GetGuildId()); - if(guild) - { - guild->LoadPlayerStatsByGuid(_player->GetGUID()); - guild->UpdateLogoutTime(_player->GetGUID()); - - WorldPacket data(SMSG_GUILD_EVENT, (1+1+12+8)); // name limited to 12 in character table. - data<<(uint8)GE_SIGNED_OFF; - data<<(uint8)1; - data<<_player->GetName(); - data<<_player->GetGUID(); - guild->BroadcastPacket(&data); - } - - ///- Remove pet - _player->RemovePet(NULL,PET_SAVE_AS_CURRENT, true); - - ///- empty buyback items and save the player in the database - // some save parts only correctly work in case player present in map/player_lists (pets, etc) - if(Save) - { - uint32 eslot; - for(int j = BUYBACK_SLOT_START; j < BUYBACK_SLOT_END; j++) - { - eslot = j - BUYBACK_SLOT_START; - _player->SetUInt64Value(PLAYER_FIELD_VENDORBUYBACK_SLOT_1+eslot*2,0); - _player->SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1+eslot,0); - _player->SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1+eslot,0); - } - _player->SaveToDB(); - } - - ///- Leave all channels before player delete... - _player->CleanupChannels(); - - ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group. - _player->UninviteFromGroup(); - - // remove player from the group if he is: - // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected) - if(_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && m_Socket) - _player->RemoveFromGroup(); - - ///- Remove the player from the world - // the player may not be in the world when logging out - // e.g if he got disconnected during a transfer to another map - // calls to GetMap in this case may cause crashes - if(_player->IsInWorld()) MapManager::Instance().GetMap(_player->GetMapId(), _player)->Remove(_player, false); - // RemoveFromWorld does cleanup that requires the player to be in the accessor - ObjectAccessor::Instance().RemoveObject(_player); - - ///- Send update to group - if(_player->GetGroup()) - _player->GetGroup()->SendUpdate(); - - ///- Broadcast a logout message to the player's friends - sSocialMgr.SendFriendStatus(_player, FRIEND_OFFLINE, _player->GetGUIDLow(), "", true); - - ///- Delete the player object - _player->CleanupsBeforeDelete(); // do some cleanup before deleting to prevent crash at crossreferences to already deleted data - - delete _player; - _player = NULL; - - ///- Send the 'logout complete' packet to the client - WorldPacket data( SMSG_LOGOUT_COMPLETE, 0 ); - SendPacket( &data ); - - ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline - //No SQL injection as AccountId is uint32 - CharacterDatabase.PExecute("UPDATE characters SET online = 0 WHERE account = '%u'", GetAccountId()); - sLog.outDebug( "SESSION: Sent SMSG_LOGOUT_COMPLETE Message" ); - } - - m_playerLogout = false; - m_playerRecentlyLogout = true; - LogoutRequest(0); -} - -/// Kick a player out of the World -void WorldSession::KickPlayer() -{ - if (m_Socket) - { - m_Socket->CloseSocket (); - } -} - -/// Cancel channeling handler - -void WorldSession::SendAreaTriggerMessage(const char* Text, ...) -{ - va_list ap; - char szStr [1024]; - szStr[0] = '\0'; - - va_start(ap, Text); - vsnprintf( szStr, 1024, Text, ap ); - va_end(ap); - - uint32 length = strlen(szStr)+1; - WorldPacket data(SMSG_AREA_TRIGGER_MESSAGE, 4+length); - data << length; - data << szStr; - SendPacket(&data); -} - -void WorldSession::SendNotification(const char *format,...) -{ - if(format) - { - va_list ap; - char szStr [1024]; - szStr[0] = '\0'; - va_start(ap, format); - vsnprintf( szStr, 1024, format, ap ); - va_end(ap); - - WorldPacket data(SMSG_NOTIFICATION, (strlen(szStr)+1)); - data << szStr; - SendPacket(&data); - } -} - -const char * WorldSession::GetMangosString( int32 entry ) -{ - return objmgr.GetMangosString(entry,GetSessionDbLocaleIndex()); -} - -void WorldSession::Handle_NULL( WorldPacket& recvPacket ) -{ - sLog.outError( "SESSION: received unhandled opcode %s (0x%.4X)", - LookupOpcodeName(recvPacket.GetOpcode()), - recvPacket.GetOpcode()); -} - -void WorldSession::Handle_EarlyProccess( WorldPacket& recvPacket ) -{ - sLog.outError( "SESSION: received opcode %s (0x%.4X) that must be proccessed in WorldSocket::OnRead", - LookupOpcodeName(recvPacket.GetOpcode()), - recvPacket.GetOpcode()); -} - -void WorldSession::Handle_ServerSide( WorldPacket& recvPacket ) -{ - sLog.outError( "SESSION: received sever-side opcode %s (0x%.4X)", - LookupOpcodeName(recvPacket.GetOpcode()), - recvPacket.GetOpcode()); -} - -void WorldSession::Handle_Depricated( WorldPacket& recvPacket ) -{ - sLog.outError( "SESSION: received depricated opcode %s (0x%.4X)", - LookupOpcodeName(recvPacket.GetOpcode()), - recvPacket.GetOpcode()); -} - -void WorldSession::SendAuthWaitQue(uint32 position) - { - if(position == 0) - { - WorldPacket packet( SMSG_AUTH_RESPONSE, 1 ); - packet << uint8( AUTH_OK ); - SendPacket(&packet); - } - else - { - WorldPacket packet( SMSG_AUTH_RESPONSE, 5 ); - packet << uint8( AUTH_WAIT_QUEUE ); - packet << uint32 (position); - SendPacket(&packet); - } - } - - +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup u2w +*/ + +#include "WorldSocket.h" +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "Log.h" +#include "Opcodes.h" +#include "WorldSocket.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "Player.h" +#include "ObjectMgr.h" +#include "Group.h" +#include "Guild.h" +#include "World.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "BattleGroundMgr.h" +#include "Language.h" // for CMSG_CANCEL_MOUNT_AURA handler +#include "Chat.h" +#include "SocialMgr.h" + +/// WorldSession constructor +WorldSession::WorldSession(uint32 id, WorldSocket *sock, uint32 sec, bool tbc, time_t mute_time, LocaleConstant locale) : +LookingForGroup_auto_join(false), LookingForGroup_auto_add(false), m_muteTime(mute_time), +_player(NULL), m_Socket(sock),_security(sec), _accountId(id), m_isTBC(tbc), +m_sessionDbcLocale(sWorld.GetAvailableDbcLocale(locale)), m_sessionDbLocaleIndex(objmgr.GetIndexForLocale(locale)), +_logoutTime(0), m_playerLoading(false), m_playerLogout(false), m_playerRecentlyLogout(false), m_latency(0) +{ + if (sock) + { + m_Address = sock->GetRemoteAddress (); + sock->AddReference (); + } +} + +/// WorldSession destructor +WorldSession::~WorldSession() +{ + ///- unload player if not unloaded + if(_player) + LogoutPlayer(true); + + /// - If have unclosed socket, close it + if (m_Socket) + { + m_Socket->CloseSocket (); + m_Socket->RemoveReference (); + m_Socket = NULL; + } + + ///- empty incoming packet queue + while(!_recvQueue.empty()) + { + WorldPacket *packet = _recvQueue.next(); + delete packet; + } + + sWorld.RemoveQueuedPlayer(this); +} + +void WorldSession::SizeError(WorldPacket const& packet, uint32 size) const +{ + sLog.outError("Client (account %u) send packet %s (%u) with size %u but expected %u (attempt crash server?), skipped", + GetAccountId(),LookupOpcodeName(packet.GetOpcode()),packet.GetOpcode(),packet.size(),size); +} + +/// Get the player name +char const* WorldSession::GetPlayerName() const +{ + return GetPlayer() ? GetPlayer()->GetName() : ""; +} + +/// Send a packet to the client +void WorldSession::SendPacket(WorldPacket const* packet) +{ + if (!m_Socket) + return; + #ifdef MANGOS_DEBUG + // Code for network use statistic + static uint64 sendPacketCount = 0; + static uint64 sendPacketBytes = 0; + + static time_t firstTime = time(NULL); + static time_t lastTime = firstTime; // next 60 secs start time + + static uint64 sendLastPacketCount = 0; + static uint64 sendLastPacketBytes = 0; + + time_t cur_time = time(NULL); + + if((cur_time - lastTime) < 60) + { + sendPacketCount+=1; + sendPacketBytes+=packet->size(); + + sendLastPacketCount+=1; + sendLastPacketBytes+=packet->size(); + } + else + { + uint64 minTime = uint64(cur_time - lastTime); + uint64 fullTime = uint64(lastTime - firstTime); + sLog.outDetail("Send all time packets count: " I64FMTD " bytes: " I64FMTD " avr.count/sec: %f avr.bytes/sec: %f time: %u",sendPacketCount,sendPacketBytes,float(sendPacketCount)/fullTime,float(sendPacketBytes)/fullTime,uint32(fullTime)); + sLog.outDetail("Send last min packets count: " I64FMTD " bytes: " I64FMTD " avr.count/sec: %f avr.bytes/sec: %f",sendLastPacketCount,sendLastPacketBytes,float(sendLastPacketCount)/minTime,float(sendLastPacketBytes)/minTime); + + lastTime = cur_time; + sendLastPacketCount = 1; + sendLastPacketBytes = packet->wpos(); // wpos is real written size + } +#endif // !MANGOS_DEBUG + + if (m_Socket->SendPacket (*packet) == -1) + { + m_Socket->CloseSocket (); + } +} + +/// Add an incoming packet to the queue +void WorldSession::QueuePacket(WorldPacket* new_packet) +{ + _recvQueue.add(new_packet); +} + +/// Logging helper for unexpected opcodes +void WorldSession::logUnexpectedOpcode(WorldPacket* packet, const char *reason) +{ + sLog.outError( "SESSION: received unexpected opcode %s (0x%.4X) %s", + LookupOpcodeName(packet->GetOpcode()), + packet->GetOpcode(), + reason); +} + +/// Update the WorldSession (triggered by World update) +bool WorldSession::Update(uint32 /*diff*/) +{ + if (m_Socket) + if (m_Socket->IsClosed ()) + { + m_Socket->RemoveReference (); + m_Socket = NULL; + } + + WorldPacket *packet; + + ///- Retrieve packets from the receive queue and call the appropriate handlers + /// \todo Is there a way to consolidate the OpcondeHandlerTable and the g_worldOpcodeNames to only maintain 1 list? + /// answer : there is a way, but this is better, because it would use redundant RAM + while (!_recvQueue.empty()) + { + packet = _recvQueue.next(); + + /*#if 1 + sLog.outError( "MOEP: %s (0x%.4X)", + LookupOpcodeName(packet->GetOpcode()), + packet->GetOpcode()); + #endif*/ + + if(packet->GetOpcode() >= NUM_MSG_TYPES) + { + sLog.outError( "SESSION: received non-existed opcode %s (0x%.4X)", + LookupOpcodeName(packet->GetOpcode()), + packet->GetOpcode()); + } + else + { + OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()]; + switch (opHandle.status) + { + case STATUS_LOGGEDIN: + if(!_player) + { + // skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets + if(!m_playerRecentlyLogout) + logUnexpectedOpcode(packet, "the player has not logged in yet"); + } + else if(_player->IsInWorld()) + (this->*opHandle.handler)(*packet); + // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer + break; + case STATUS_TRANSFER_PENDING: + if(!_player) + logUnexpectedOpcode(packet, "the player has not logged in yet"); + else if(_player->IsInWorld()) + logUnexpectedOpcode(packet, "the player is still in world"); + else + (this->*opHandle.handler)(*packet); + break; + case STATUS_AUTHED: + m_playerRecentlyLogout = false; + (this->*opHandle.handler)(*packet); + break; + case STATUS_NEVER: + sLog.outError( "SESSION: received not allowed opcode %s (0x%.4X)", + LookupOpcodeName(packet->GetOpcode()), + packet->GetOpcode()); + break; + } + } + + delete packet; + } + + ///- If necessary, log the player out + time_t currTime = time(NULL); + if (!m_Socket || (ShouldLogOut(currTime) && !m_playerLoading)) + LogoutPlayer(true); + + if (!m_Socket) + return false; //Will remove this session from the world session map + + return true; +} + +/// %Log the player out +void WorldSession::LogoutPlayer(bool Save) +{ + // finish pending transfers before starting the logout + while(_player && _player->IsBeingTeleported()) + HandleMoveWorldportAckOpcode(); + + m_playerLogout = true; + + if (_player) + { + ///- If the player just died before logging out, make him appear as a ghost + //FIXME: logout must be delayed in case lost connection with client in time of combat + if (_player->GetDeathTimer()) + { + _player->getHostilRefManager().deleteReferences(); + _player->BuildPlayerRepop(); + _player->RepopAtGraveyard(); + } + else if (!_player->getAttackers().empty()) + { + _player->CombatStop(); + _player->getHostilRefManager().setOnlineOfflineState(false); + _player->RemoveAllAurasOnDeath(); + + // build set of player who attack _player or who have pet attacking of _player + std::set aset; + for(Unit::AttackerSet::const_iterator itr = _player->getAttackers().begin(); itr != _player->getAttackers().end(); ++itr) + { + Unit* owner = (*itr)->GetOwner(); // including player controlled case + if(owner) + { + if(owner->GetTypeId()==TYPEID_PLAYER) + aset.insert((Player*)owner); + } + else + if((*itr)->GetTypeId()==TYPEID_PLAYER) + aset.insert((Player*)(*itr)); + } + + _player->SetPvPDeath(!aset.empty()); + _player->KillPlayer(); + _player->BuildPlayerRepop(); + _player->RepopAtGraveyard(); + + // give honor to all attackers from set like group case + for(std::set::const_iterator itr = aset.begin(); itr != aset.end(); ++itr) + (*itr)->RewardHonor(_player,aset.size()); + + // give bg rewards and update counters like kill by first from attackers + // this can't be called for all attackers. + if(!aset.empty()) + if(BattleGround *bg = _player->GetBattleGround()) + bg->HandleKillPlayer(_player,*aset.begin()); + } + else if(_player->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION)) + { + // this will kill character by SPELL_AURA_SPIRIT_OF_REDEMPTION + _player->RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT); + //_player->SetDeathPvP(*); set at SPELL_AURA_SPIRIT_OF_REDEMPTION apply time + _player->KillPlayer(); + _player->BuildPlayerRepop(); + _player->RepopAtGraveyard(); + } + + ///- Remove player from battleground (teleport to entrance) + if(_player->InBattleGround()) + _player->LeaveBattleground(); + + for (int i=0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++) + { + if(int32 bgTypeId = _player->GetBattleGroundQueueId(i)) + { + _player->RemoveBattleGroundQueueId(bgTypeId); + sBattleGroundMgr.m_BattleGroundQueues[ bgTypeId ].RemovePlayer(_player->GetGUID(), true); + } + } + + ///- Reset the online field in the account table + // no point resetting online in character table here as Player::SaveToDB() will set it to 1 since player has not been removed from world at this stage + //No SQL injection as AccountID is uint32 + loginDatabase.PExecute("UPDATE account SET online = 0 WHERE id = '%u'", GetAccountId()); + + ///- If the player is in a guild, update the guild roster and broadcast a logout message to other guild members + Guild *guild = objmgr.GetGuildById(_player->GetGuildId()); + if(guild) + { + guild->LoadPlayerStatsByGuid(_player->GetGUID()); + guild->UpdateLogoutTime(_player->GetGUID()); + + WorldPacket data(SMSG_GUILD_EVENT, (1+1+12+8)); // name limited to 12 in character table. + data<<(uint8)GE_SIGNED_OFF; + data<<(uint8)1; + data<<_player->GetName(); + data<<_player->GetGUID(); + guild->BroadcastPacket(&data); + } + + ///- Remove pet + _player->RemovePet(NULL,PET_SAVE_AS_CURRENT, true); + + ///- empty buyback items and save the player in the database + // some save parts only correctly work in case player present in map/player_lists (pets, etc) + if(Save) + { + uint32 eslot; + for(int j = BUYBACK_SLOT_START; j < BUYBACK_SLOT_END; j++) + { + eslot = j - BUYBACK_SLOT_START; + _player->SetUInt64Value(PLAYER_FIELD_VENDORBUYBACK_SLOT_1+eslot*2,0); + _player->SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1+eslot,0); + _player->SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1+eslot,0); + } + _player->SaveToDB(); + } + + ///- Leave all channels before player delete... + _player->CleanupChannels(); + + ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group. + _player->UninviteFromGroup(); + + // remove player from the group if he is: + // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected) + if(_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && m_Socket) + _player->RemoveFromGroup(); + + ///- Remove the player from the world + // the player may not be in the world when logging out + // e.g if he got disconnected during a transfer to another map + // calls to GetMap in this case may cause crashes + if(_player->IsInWorld()) MapManager::Instance().GetMap(_player->GetMapId(), _player)->Remove(_player, false); + // RemoveFromWorld does cleanup that requires the player to be in the accessor + ObjectAccessor::Instance().RemoveObject(_player); + + ///- Send update to group + if(_player->GetGroup()) + _player->GetGroup()->SendUpdate(); + + ///- Broadcast a logout message to the player's friends + sSocialMgr.SendFriendStatus(_player, FRIEND_OFFLINE, _player->GetGUIDLow(), "", true); + + ///- Delete the player object + _player->CleanupsBeforeDelete(); // do some cleanup before deleting to prevent crash at crossreferences to already deleted data + + delete _player; + _player = NULL; + + ///- Send the 'logout complete' packet to the client + WorldPacket data( SMSG_LOGOUT_COMPLETE, 0 ); + SendPacket( &data ); + + ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline + //No SQL injection as AccountId is uint32 + CharacterDatabase.PExecute("UPDATE characters SET online = 0 WHERE account = '%u'", GetAccountId()); + sLog.outDebug( "SESSION: Sent SMSG_LOGOUT_COMPLETE Message" ); + } + + m_playerLogout = false; + m_playerRecentlyLogout = true; + LogoutRequest(0); +} + +/// Kick a player out of the World +void WorldSession::KickPlayer() +{ + if (m_Socket) + { + m_Socket->CloseSocket (); + } +} + +/// Cancel channeling handler + +void WorldSession::SendAreaTriggerMessage(const char* Text, ...) +{ + va_list ap; + char szStr [1024]; + szStr[0] = '\0'; + + va_start(ap, Text); + vsnprintf( szStr, 1024, Text, ap ); + va_end(ap); + + uint32 length = strlen(szStr)+1; + WorldPacket data(SMSG_AREA_TRIGGER_MESSAGE, 4+length); + data << length; + data << szStr; + SendPacket(&data); +} + +void WorldSession::SendNotification(const char *format,...) +{ + if(format) + { + va_list ap; + char szStr [1024]; + szStr[0] = '\0'; + va_start(ap, format); + vsnprintf( szStr, 1024, format, ap ); + va_end(ap); + + WorldPacket data(SMSG_NOTIFICATION, (strlen(szStr)+1)); + data << szStr; + SendPacket(&data); + } +} + +void WorldSession::SendNotification(int32 string_id,...) +{ + char const* format = GetMangosString(string_id); + if(format) + { + va_list ap; + char szStr [1024]; + szStr[0] = '\0'; + va_start(ap, format); + vsnprintf( szStr, 1024, format, ap ); + va_end(ap); + + WorldPacket data(SMSG_NOTIFICATION, (strlen(szStr)+1)); + data << szStr; + SendPacket(&data); + } +} + +const char * WorldSession::GetMangosString( int32 entry ) +{ + return objmgr.GetMangosString(entry,GetSessionDbLocaleIndex()); +} + +void WorldSession::Handle_NULL( WorldPacket& recvPacket ) +{ + sLog.outError( "SESSION: received unhandled opcode %s (0x%.4X)", + LookupOpcodeName(recvPacket.GetOpcode()), + recvPacket.GetOpcode()); +} + +void WorldSession::Handle_EarlyProccess( WorldPacket& recvPacket ) +{ + sLog.outError( "SESSION: received opcode %s (0x%.4X) that must be proccessed in WorldSocket::OnRead", + LookupOpcodeName(recvPacket.GetOpcode()), + recvPacket.GetOpcode()); +} + +void WorldSession::Handle_ServerSide( WorldPacket& recvPacket ) +{ + sLog.outError( "SESSION: received sever-side opcode %s (0x%.4X)", + LookupOpcodeName(recvPacket.GetOpcode()), + recvPacket.GetOpcode()); +} + +void WorldSession::Handle_Depricated( WorldPacket& recvPacket ) +{ + sLog.outError( "SESSION: received depricated opcode %s (0x%.4X)", + LookupOpcodeName(recvPacket.GetOpcode()), + recvPacket.GetOpcode()); +} + +void WorldSession::SendAuthWaitQue(uint32 position) + { + if(position == 0) + { + WorldPacket packet( SMSG_AUTH_RESPONSE, 1 ); + packet << uint8( AUTH_OK ); + SendPacket(&packet); + } + else + { + WorldPacket packet( SMSG_AUTH_RESPONSE, 5 ); + packet << uint8( AUTH_WAIT_QUEUE ); + packet << uint32 (position); + SendPacket(&packet); + } + } + + diff --git a/src/game/WorldSession.h b/src/game/WorldSession.h index 0f46af26c94..637d833e2de 100644 --- a/src/game/WorldSession.h +++ b/src/game/WorldSession.h @@ -1,642 +1,643 @@ -/* - * Copyright (C) 2005-2008 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -/// \addtogroup u2w -/// @{ -/// \file - -#ifndef __WORLDSESSION_H -#define __WORLDSESSION_H - -#include "Common.h" - -class MailItemsInfo; -struct ItemPrototype; -struct AuctionEntry; - -class Creature; -class Item; -class Object; -class Player; -class Unit; -class WorldPacket; -class WorldSocket; -class WorldSession; -class QueryResult; -class LoginQueryHolder; -class CharacterHandler; - -#define CHECK_PACKET_SIZE(P,S) if((P).size() < (S)) return SizeError((P),(S)); - -enum PartyOperation -{ - PARTY_OP_INVITE = 0, - PARTY_OP_LEAVE = 2 -}; - -enum PartyResult -{ - PARTY_RESULT_OK = 0, - PARTY_RESULT_CANT_FIND_TARGET = 1, - PARTY_RESULT_NOT_IN_YOUR_PARTY = 2, - PARTY_RESULT_NOT_IN_YOUR_INSTANCE = 3, - PARTY_RESULT_PARTY_FULL = 4, - PARTY_RESULT_ALREADY_IN_GROUP = 5, - PARTY_RESULT_YOU_NOT_IN_GROUP = 6, - PARTY_RESULT_YOU_NOT_LEADER = 7, - PARTY_RESULT_TARGET_UNFRIENDLY = 8, - PARTY_RESULT_TARGET_IGNORE_YOU = 9, - PARTY_RESULT_INVITE_RESTRICTED = 13 -}; - -/// Player session in the World -class MANGOS_DLL_SPEC WorldSession -{ - friend class CharacterHandler; - public: - WorldSession(uint32 id, WorldSocket *sock, uint32 sec, bool tbc, time_t mute_time, LocaleConstant locale); - ~WorldSession(); - - bool PlayerLoading() const { return m_playerLoading; } - bool PlayerLogout() const { return m_playerLogout; } - - void SizeError(WorldPacket const& packet, uint32 size) const; - - void SendPacket(WorldPacket const* packet); - void SendNotification(const char *format,...) ATTR_PRINTF(2,3); - void SendLfgResult(uint32 type, uint32 entry, uint8 lfg_type); - void SendPartyResult(PartyOperation operation, std::string member, PartyResult res); - void SendAreaTriggerMessage(const char* Text, ...) ATTR_PRINTF(2,3); - - uint32 GetSecurity() const { return _security; } - uint32 GetAccountId() const { return _accountId; } - Player* GetPlayer() const { return _player; } - char const* GetPlayerName() const; - void SetSecurity(uint32 security) { _security = security; } - std::string& GetRemoteAddress() { return m_Address; } - void SetPlayer(Player *plr) { _player = plr; } - bool IsTBC() const { return m_isTBC; } - - /// Is the user engaged in a log out process? - bool isLogingOut() const { return _logoutTime || m_playerLogout; } - - /// Engage the logout process for the user - void LogoutRequest(time_t requestTime) - { - _logoutTime = requestTime; - } - - /// Is logout cooldown expired? - bool ShouldLogOut(time_t currTime) const - { - return (_logoutTime > 0 && currTime >= _logoutTime + 20); - } - - void LogoutPlayer(bool Save); - void KickPlayer(); - - void QueuePacket(WorldPacket* new_packet); - bool Update(uint32 diff); - - /// Handle the authentication waiting queue (to be completed) - void SendAuthWaitQue(uint32 position); - - //void SendTestCreatureQueryOpcode( uint32 entry, uint64 guid, uint32 testvalue ); - void SendNameQueryOpcode(Player* p); - void SendNameQueryOpcodeFromDB(uint64 guid); - static void SendNameQueryOpcodeFromDBCallBack(QueryResult *result, uint32 accountId); - - void SendTrainerList( uint64 guid ); - void SendTrainerList( uint64 guid,std::string strTitle ); - void SendListInventory( uint64 guid ); - void SendShowBank( uint64 guid ); - void SendTabardVendorActivate( uint64 guid ); - void SendSpiritResurrect(); - void SendBindPoint(Creature* npc); - void SendGMTicketGetTicket(uint32 status, char const* text); - - void SendAttackStop(Unit const* enemy); - - void SendBattlegGroundList( uint64 guid, uint32 bgTypeId ); - - void SendTradeStatus(uint32 status); - void SendCancelTrade(); - - void SendStablePet(uint64 guid ); - void SendPetitionQueryOpcode( uint64 petitionguid); - void SendUpdateTrade(); - - //pet - void SendPetNameQuery(uint64 guid, uint32 petnumber); - - //mail - //used with item_page table - bool SendItemInfo( uint32 itemid, WorldPacket data ); - static void SendReturnToSender(uint8 messageType, uint32 sender_acc, uint32 sender_guid, uint32 receiver_guid, std::string subject, uint32 itemTextId, MailItemsInfo *mi, uint32 money, uint32 COD, uint16 mailTemplateId = 0); - static void SendMailTo(Player* receiver, uint8 messageType, uint8 stationery, uint32 sender_guidlow_or_entry, uint32 received_guidlow, std::string subject, uint32 itemTextId, MailItemsInfo* mi, uint32 money, uint32 COD, uint32 checked, uint32 deliver_delay = 0, uint16 mailTemplateId = 0); - - //auction - void SendAuctionHello( uint64 guid, Creature * unit ); - void SendAuctionCommandResult( uint32 auctionId, uint32 Action, uint32 ErrorCode, uint32 bidError = 0); - void SendAuctionBidderNotification( uint32 location, uint32 auctionId, uint64 bidder, uint32 bidSum, uint32 diff, uint32 item_template); - void SendAuctionOwnerNotification( AuctionEntry * auction ); - bool SendAuctionInfo(WorldPacket & data, AuctionEntry* auction); - void SendAuctionOutbiddedMail( AuctionEntry * auction, uint32 newPrice ); - void SendAuctionCancelledToBidderMail( AuctionEntry* auction ); - - //Item Enchantment - void SendEnchantmentLog(uint64 Target, uint64 Caster,uint32 ItemID,uint32 SpellID); - void SendItemEnchantTimeUpdate(uint64 Playerguid, uint64 Itemguid,uint32 slot,uint32 Duration); - - //Taxi - void SendTaxiStatus( uint64 guid ); - void SendTaxiMenu( Creature* unit ); - void SendDoFlight( uint16 MountId, uint32 path, uint32 pathNode = 0 ); - bool SendLearnNewTaxiNode( Creature* unit ); - - // Guild/Arena Team - void SendGuildCommandResult(uint32 typecmd,std::string str,uint32 cmdresult); - void SendArenaTeamCommandResult(uint32 unk1, std::string str1, std::string str2, uint32 unk3); - void BuildArenaTeamEventPacket(WorldPacket *data, uint8 eventid, uint8 str_count, std::string str1, std::string str2, std::string str3); - void SendNotInArenaTeamPacket(uint8 type); - void SendPetitionShowList( uint64 guid ); - void SendSaveGuildEmblem( uint32 msg ); - - // Looking For Group - // TRUE values set by client sending CMSG_LFG_SET_AUTOJOIN and CMSG_LFM_CLEAR_AUTOFILL before player login - bool LookingForGroup_auto_join; - bool LookingForGroup_auto_add; - - void BuildPartyMemberStatsChangedPacket(Player *player, WorldPacket *data); - - void DoLootRelease( uint64 lguid ); - - // Account mute time - time_t m_muteTime; - - // Locales - LocaleConstant GetSessionDbcLocale() { return m_sessionDbcLocale; } - int GetSessionDbLocaleIndex() { return m_sessionDbLocaleIndex; } - const char *GetMangosString(int32 entry); - - uint32 GetLatency() const { return m_latency; } - void SetLatency(uint32 latency) { m_latency = latency; } - uint32 getDialogStatus(Player *pPlayer, Object* questgiver, uint32 defstatus); - - public: // opcodes handlers - - void Handle_NULL(WorldPacket& recvPacket); // not used - void Handle_EarlyProccess( WorldPacket& recvPacket);// just mark packets processed in WorldSocket::OnRead - void Handle_ServerSide(WorldPacket& recvPacket); // sever side only, can't be accepted from client - void Handle_Depricated(WorldPacket& recvPacket); // never used anymore by client - - void HandleCharEnumOpcode(WorldPacket& recvPacket); - void HandleCharDeleteOpcode(WorldPacket& recvPacket); - void HandleCharCreateOpcode(WorldPacket& recvPacket); - void HandlePlayerLoginOpcode(WorldPacket& recvPacket); - void HandleCharEnum(QueryResult * result); - void HandlePlayerLogin(LoginQueryHolder * holder); - - // played time - void HandlePlayedTime(WorldPacket& recvPacket); - - // new - void HandleMoveUnRootAck(WorldPacket& recvPacket); - void HandleMoveRootAck(WorldPacket& recvPacket); - void HandleLookingForGroup(WorldPacket& recvPacket); - - // new inspect - void HandleInspectOpcode(WorldPacket& recvPacket); - - // new party stats - void HandleInspectHonorStatsOpcode(WorldPacket& recvPacket); - - void HandleMoveWaterWalkAck(WorldPacket& recvPacket); - void HandleFeatherFallAck(WorldPacket &recv_data); - - void HandleMoveHoverAck( WorldPacket & recv_data ); - - void HandleMountSpecialAnimOpcode(WorldPacket &recvdata); - - // character view - void HandleToggleHelmOpcode(WorldPacket& recv_data); - void HandleToggleCloakOpcode(WorldPacket& recv_data); - - // repair - void HandleRepairItemOpcode(WorldPacket& recvPacket); - - // Knockback - void HandleMoveKnockBackAck(WorldPacket& recvPacket); - - void HandleMoveTeleportAck(WorldPacket& recvPacket); - void HandleForceSpeedChangeAck( WorldPacket & recv_data ); - - void HandlePingOpcode(WorldPacket& recvPacket); - void HandleAuthSessionOpcode(WorldPacket& recvPacket); - void HandleRepopRequestOpcode(WorldPacket& recvPacket); - void HandleAutostoreLootItemOpcode(WorldPacket& recvPacket); - void HandleLootMoneyOpcode(WorldPacket& recvPacket); - void HandleLootOpcode(WorldPacket& recvPacket); - void HandleLootReleaseOpcode(WorldPacket& recvPacket); - void HandleLootMasterGiveOpcode(WorldPacket& recvPacket); - void HandleWhoOpcode(WorldPacket& recvPacket); - void HandleLogoutRequestOpcode(WorldPacket& recvPacket); - void HandlePlayerLogoutOpcode(WorldPacket& recvPacket); - void HandleLogoutCancelOpcode(WorldPacket& recvPacket); - void HandleGMTicketGetTicketOpcode(WorldPacket& recvPacket); - void HandleGMTicketCreateOpcode(WorldPacket& recvPacket); - void HandleGMTicketSystemStatusOpcode(WorldPacket& recvPacket); - - void HandleGMTicketDeleteOpcode(WorldPacket& recvPacket); - void HandleGMTicketUpdateTextOpcode(WorldPacket& recvPacket); - - void HandleGMSurveySubmit(WorldPacket& recvPacket); - - void HandleTogglePvP(WorldPacket& recvPacket); - - void HandleZoneUpdateOpcode(WorldPacket& recvPacket); - void HandleSetTargetOpcode(WorldPacket& recvPacket); - void HandleSetSelectionOpcode(WorldPacket& recvPacket); - void HandleStandStateChangeOpcode(WorldPacket& recvPacket); - void HandleEmoteOpcode(WorldPacket& recvPacket); - void HandleFriendListOpcode(WorldPacket& recvPacket); - void HandleAddFriendOpcode(WorldPacket& recvPacket); - void HandleDelFriendOpcode(WorldPacket& recvPacket); - void HandleAddIgnoreOpcode(WorldPacket& recvPacket); - void HandleDelIgnoreOpcode(WorldPacket& recvPacket); - void HandleSetFriendNoteOpcode(WorldPacket& recvPacket); - void HandleBugOpcode(WorldPacket& recvPacket); - void HandleSetAmmoOpcode(WorldPacket& recvPacket); - void HandleItemNameQueryOpcode(WorldPacket& recvPacket); - - void HandleAreaTriggerOpcode(WorldPacket& recvPacket); - - void HandleSetFactionAtWar( WorldPacket & recv_data ); - void HandleSetFactionCheat( WorldPacket & recv_data ); - void HandleSetWatchedFactionIndexOpcode(WorldPacket & recv_data); - void HandleSetWatchedFactionInactiveOpcode(WorldPacket & recv_data); - - void HandleUpdateAccountData(WorldPacket& recvPacket); - void HandleRequestAccountData(WorldPacket& recvPacket); - void HandleSetActionButtonOpcode(WorldPacket& recvPacket); - - void HandleGameObjectUseOpcode(WorldPacket& recPacket); - void HandleMeetingStoneInfo(WorldPacket& recPacket); - - void HandleNameQueryOpcode(WorldPacket& recvPacket); - - void HandleQueryTimeOpcode(WorldPacket& recvPacket); - - void HandleCreatureQueryOpcode(WorldPacket& recvPacket); - - void HandleGameObjectQueryOpcode(WorldPacket& recvPacket); - - void HandleMoveWorldportAckOpcode(WorldPacket& recvPacket); - void HandleMoveWorldportAckOpcode(); // for server-side calls - - void HandleMovementOpcodes(WorldPacket& recvPacket); - void HandleSetActiveMoverOpcode(WorldPacket &recv_data); - void HandleMoveTimeSkippedOpcode(WorldPacket &recv_data); - - void HandleRequestRaidInfoOpcode( WorldPacket & recv_data ); - - void HandleBattlefieldStatusOpcode(WorldPacket &recv_data); - void HandleBattleMasterHelloOpcode(WorldPacket &recv_data); - - void HandleGroupInviteOpcode(WorldPacket& recvPacket); - //void HandleGroupCancelOpcode(WorldPacket& recvPacket); - void HandleGroupAcceptOpcode(WorldPacket& recvPacket); - void HandleGroupDeclineOpcode(WorldPacket& recvPacket); - void HandleGroupUninviteNameOpcode(WorldPacket& recvPacket); - void HandleGroupUninviteGuidOpcode(WorldPacket& recvPacket); - void HandleGroupUninvite(uint64 guid, std::string name); - void HandleGroupSetLeaderOpcode(WorldPacket& recvPacket); - void HandleGroupLeaveOpcode(WorldPacket& recvPacket); - void HandleGroupPassOnLootOpcode( WorldPacket &recv_data ); - void HandleLootMethodOpcode(WorldPacket& recvPacket); - void HandleLootRoll( WorldPacket &recv_data ); - void HandleRequestPartyMemberStatsOpcode( WorldPacket &recv_data ); - void HandleRaidIconTargetOpcode( WorldPacket & recv_data ); - void HandleRaidReadyCheckOpcode( WorldPacket & recv_data ); - void HandleRaidReadyCheckFinishOpcode( WorldPacket & recv_data ); - void HandleRaidConvertOpcode( WorldPacket & recv_data ); - void HandleGroupChangeSubGroupOpcode( WorldPacket & recv_data ); - void HandleGroupAssistantOpcode( WorldPacket & recv_data ); - void HandleGroupPromoteOpcode( WorldPacket & recv_data ); - - void HandlePetitionBuyOpcode(WorldPacket& recv_data); - void HandlePetitionShowSignOpcode(WorldPacket& recv_data); - void HandlePetitionQueryOpcode(WorldPacket& recv_data); - void HandlePetitionRenameOpcode(WorldPacket& recv_data); - void HandlePetitionSignOpcode(WorldPacket& recv_data); - void HandlePetitionDeclineOpcode(WorldPacket& recv_data); - void HandleOfferPetitionOpcode(WorldPacket& recv_data); - void HandleTurnInPetitionOpcode(WorldPacket& recv_data); - - void HandleGuildQueryOpcode(WorldPacket& recvPacket); - void HandleGuildCreateOpcode(WorldPacket& recvPacket); - void HandleGuildInviteOpcode(WorldPacket& recvPacket); - void HandleGuildRemoveOpcode(WorldPacket& recvPacket); - void HandleGuildAcceptOpcode(WorldPacket& recvPacket); - void HandleGuildDeclineOpcode(WorldPacket& recvPacket); - void HandleGuildInfoOpcode(WorldPacket& recvPacket); - void HandleGuildEventLogOpcode(WorldPacket& recvPacket); - void HandleGuildRosterOpcode(WorldPacket& recvPacket); - void HandleGuildPromoteOpcode(WorldPacket& recvPacket); - void HandleGuildDemoteOpcode(WorldPacket& recvPacket); - void HandleGuildLeaveOpcode(WorldPacket& recvPacket); - void HandleGuildDisbandOpcode(WorldPacket& recvPacket); - void HandleGuildLeaderOpcode(WorldPacket& recvPacket); - void HandleGuildMOTDOpcode(WorldPacket& recvPacket); - void HandleGuildSetPublicNoteOpcode(WorldPacket& recvPacket); - void HandleGuildSetOfficerNoteOpcode(WorldPacket& recvPacket); - void HandleGuildRankOpcode(WorldPacket& recvPacket); - void HandleGuildAddRankOpcode(WorldPacket& recvPacket); - void HandleGuildDelRankOpcode(WorldPacket& recvPacket); - void HandleGuildChangeInfoOpcode(WorldPacket& recvPacket); - void HandleGuildSaveEmblemOpcode(WorldPacket& recvPacket); - - void HandleTaxiNodeStatusQueryOpcode(WorldPacket& recvPacket); - void HandleTaxiQueryAvailableNodesOpcode(WorldPacket& recvPacket); - void HandleActivateTaxiOpcode(WorldPacket& recvPacket); - void HandleActivateTaxiFarOpcode(WorldPacket& recvPacket); - void HandleTaxiNextDestinationOpcode(WorldPacket& recvPacket); - - void HandleTabardVendorActivateOpcode(WorldPacket& recvPacket); - void HandleBankerActivateOpcode(WorldPacket& recvPacket); - void HandleBuyBankSlotOpcode(WorldPacket& recvPacket); - void HandleTrainerListOpcode(WorldPacket& recvPacket); - void HandleTrainerBuySpellOpcode(WorldPacket& recvPacket); - void HandlePetitionShowListOpcode(WorldPacket& recvPacket); - void HandleGossipHelloOpcode(WorldPacket& recvPacket); - void HandleGossipSelectOptionOpcode(WorldPacket& recvPacket); - void HandleSpiritHealerActivateOpcode(WorldPacket& recvPacket); - void HandleNpcTextQueryOpcode(WorldPacket& recvPacket); - void HandleBinderActivateOpcode(WorldPacket& recvPacket); - void HandleListStabledPetsOpcode(WorldPacket& recvPacket); - void HandleStablePet(WorldPacket& recvPacket); - void HandleUnstablePet(WorldPacket& recvPacket); - void HandleBuyStableSlot(WorldPacket& recvPacket); - void HandleStableRevivePet(WorldPacket& recvPacket); - void HandleStableSwapPet(WorldPacket& recvPacket); - - void HandleDuelAcceptedOpcode(WorldPacket& recvPacket); - void HandleDuelCancelledOpcode(WorldPacket& recvPacket); - - void HandleAcceptTradeOpcode(WorldPacket& recvPacket); - void HandleBeginTradeOpcode(WorldPacket& recvPacket); - void HandleBusyTradeOpcode(WorldPacket& recvPacket); - void HandleCancelTradeOpcode(WorldPacket& recvPacket); - void HandleClearTradeItemOpcode(WorldPacket& recvPacket); - void HandleIgnoreTradeOpcode(WorldPacket& recvPacket); - void HandleInitiateTradeOpcode(WorldPacket& recvPacket); - void HandleSetTradeGoldOpcode(WorldPacket& recvPacket); - void HandleSetTradeItemOpcode(WorldPacket& recvPacket); - void HandleUnacceptTradeOpcode(WorldPacket& recvPacket); - - void HandleAuctionHelloOpcode(WorldPacket& recvPacket); - void HandleAuctionListItems( WorldPacket & recv_data ); - void HandleAuctionListBidderItems( WorldPacket & recv_data ); - void HandleAuctionSellItem( WorldPacket & recv_data ); - void HandleAuctionRemoveItem( WorldPacket & recv_data ); - void HandleAuctionListOwnerItems( WorldPacket & recv_data ); - void HandleAuctionPlaceBid( WorldPacket & recv_data ); - - void HandleGetMail( WorldPacket & recv_data ); - void HandleSendMail( WorldPacket & recv_data ); - void HandleTakeMoney( WorldPacket & recv_data ); - void HandleTakeItem( WorldPacket & recv_data ); - void HandleMarkAsRead( WorldPacket & recv_data ); - void HandleReturnToSender( WorldPacket & recv_data ); - void HandleMailDelete( WorldPacket & recv_data ); - void HandleItemTextQuery( WorldPacket & recv_data); - void HandleMailCreateTextItem(WorldPacket & recv_data ); - void HandleMsgQueryNextMailtime(WorldPacket & recv_data ); - void HandleCancelChanneling(WorldPacket & recv_data ); - - void SendItemPageInfo( ItemPrototype *itemProto ); - void HandleSplitItemOpcode(WorldPacket& recvPacket); - void HandleSwapInvItemOpcode(WorldPacket& recvPacket); - void HandleDestroyItemOpcode(WorldPacket& recvPacket); - void HandleAutoEquipItemOpcode(WorldPacket& recvPacket); - void HandleItemQuerySingleOpcode(WorldPacket& recvPacket); - void HandleSellItemOpcode(WorldPacket& recvPacket); - void HandleBuyItemInSlotOpcode(WorldPacket& recvPacket); - void HandleBuyItemOpcode(WorldPacket& recvPacket); - void HandleListInventoryOpcode(WorldPacket& recvPacket); - void HandleAutoStoreBagItemOpcode(WorldPacket& recvPacket); - void HandleReadItem(WorldPacket& recvPacket); - void HandleAutoEquipItemSlotOpcode(WorldPacket & recvPacket); - void HandleSwapItem( WorldPacket & recvPacket); - void HandleBuybackItem(WorldPacket & recvPacket); - void HandleAutoBankItemOpcode(WorldPacket& recvPacket); - void HandleAutoStoreBankItemOpcode(WorldPacket& recvPacket); - void HandleWrapItemOpcode(WorldPacket& recvPacket); - - void HandleAttackSwingOpcode(WorldPacket& recvPacket); - void HandleAttackStopOpcode(WorldPacket& recvPacket); - void HandleSetSheathedOpcode(WorldPacket& recvPacket); - - void HandleUseItemOpcode(WorldPacket& recvPacket); - void HandleOpenItemOpcode(WorldPacket& recvPacket); - void HandleCastSpellOpcode(WorldPacket& recvPacket); - void HandleCancelCastOpcode(WorldPacket& recvPacket); - void HandleCancelAuraOpcode(WorldPacket& recvPacket); - void HandleCancelGrowthAuraOpcode(WorldPacket& recvPacket); - void HandleCancelAutoRepeatSpellOpcode(WorldPacket& recvPacket); - - void HandleLearnTalentOpcode(WorldPacket& recvPacket); - void HandleTalentWipeOpcode(WorldPacket& recvPacket); - void HandleUnlearnSkillOpcode(WorldPacket& recvPacket); - - void HandleQuestgiverStatusQueryOpcode(WorldPacket& recvPacket); - void HandleQuestgiverStatusQueryMultipleOpcode(WorldPacket& recvPacket); - void HandleQuestgiverHelloOpcode(WorldPacket& recvPacket); - void HandleQuestgiverAcceptQuestOpcode(WorldPacket& recvPacket); - void HandleQuestgiverQuestQueryOpcode(WorldPacket& recvPacket); - void HandleQuestgiverChooseRewardOpcode(WorldPacket& recvPacket); - void HandleQuestgiverRequestRewardOpcode(WorldPacket& recvPacket); - void HandleQuestQueryOpcode(WorldPacket& recvPacket); - void HandleQuestgiverCancel(WorldPacket& recv_data ); - void HandleQuestLogSwapQuest(WorldPacket& recv_data ); - void HandleQuestLogRemoveQuest(WorldPacket& recv_data); - void HandleQuestConfirmAccept(WorldPacket& recv_data); - void HandleQuestComplete(WorldPacket& recv_data); - void HandleQuestAutoLaunch(WorldPacket& recvPacket); - void HandleQuestPushToParty(WorldPacket& recvPacket); - void HandleQuestPushResult(WorldPacket& recvPacket); - - void HandleMessagechatOpcode(WorldPacket& recvPacket); - void HandleTextEmoteOpcode(WorldPacket& recvPacket); - void HandleChatIgnoredOpcode(WorldPacket& recvPacket); - - void HandleCorpseReclaimOpcode( WorldPacket& recvPacket ); - void HandleCorpseQueryOpcode( WorldPacket& recvPacket ); - void HandleResurrectResponseOpcode(WorldPacket& recvPacket); - void HandleSummonResponseOpcode(WorldPacket& recv_data); - - void HandleChannelJoin(WorldPacket& recvPacket); - void HandleChannelLeave(WorldPacket& recvPacket); - void HandleChannelList(WorldPacket& recvPacket); - void HandleChannelPassword(WorldPacket& recvPacket); - void HandleChannelSetOwner(WorldPacket& recvPacket); - void HandleChannelOwner(WorldPacket& recvPacket); - void HandleChannelModerator(WorldPacket& recvPacket); - void HandleChannelUnmoderator(WorldPacket& recvPacket); - void HandleChannelMute(WorldPacket& recvPacket); - void HandleChannelUnmute(WorldPacket& recvPacket); - void HandleChannelInvite(WorldPacket& recvPacket); - void HandleChannelKick(WorldPacket& recvPacket); - void HandleChannelBan(WorldPacket& recvPacket); - void HandleChannelUnban(WorldPacket& recvPacket); - void HandleChannelAnnounce(WorldPacket& recvPacket); - void HandleChannelModerate(WorldPacket& recvPacket); - void HandleChannelRosterQuery(WorldPacket& recvPacket); - void HandleChannelInfoQuery(WorldPacket& recvPacket); - void HandleChannelJoinNotify(WorldPacket& recvPacket); - - void HandleCompleteCinema(WorldPacket& recvPacket); - void HandleNextCinematicCamera(WorldPacket& recvPacket); - - void HandlePageQuerySkippedOpcode(WorldPacket& recvPacket); - void HandlePageQueryOpcode(WorldPacket& recvPacket); - - void HandleTutorialFlag ( WorldPacket & recv_data ); - void HandleTutorialClear( WorldPacket & recv_data ); - void HandleTutorialReset( WorldPacket & recv_data ); - - //Pet - void HandlePetAction( WorldPacket & recv_data ); - void HandlePetNameQuery( WorldPacket & recv_data ); - void HandlePetSetAction( WorldPacket & recv_data ); - void HandlePetAbandon( WorldPacket & recv_data ); - void HandlePetRename( WorldPacket & recv_data ); - void HandlePetCancelAuraOpcode( WorldPacket& recvPacket ); - void HandlePetUnlearnOpcode( WorldPacket& recvPacket ); - void HandlePetSpellAutocastOpcode( WorldPacket& recvPacket ); - void HandleAddDynamicTargetObsoleteOpcode( WorldPacket& recvPacket ); - - void HandleSetActionBar(WorldPacket& recv_data); - - void HandleChangePlayerNameOpcode(WorldPacket& recv_data); - void HandleDeclinedPlayerNameOpcode(WorldPacket& recv_data); - - void HandleTotemDestroy(WorldPacket& recv_data); - - //BattleGround - void HandleBattleGroundHelloOpcode(WorldPacket &recv_data); - void HandleBattleGroundJoinOpcode(WorldPacket &recv_data); - void HandleBattleGroundPlayerPositionsOpcode(WorldPacket& recv_data); - void HandleBattleGroundPVPlogdataOpcode( WorldPacket &recv_data ); - void HandleBattleGroundPlayerPortOpcode( WorldPacket &recv_data ); - void HandleBattleGroundListOpcode( WorldPacket &recv_data ); - void HandleBattleGroundLeaveOpcode( WorldPacket &recv_data ); - void HandleBattleGroundArenaJoin( WorldPacket &recv_data ); - void HandleBattleGroundReportAFK( WorldPacket &recv_data ); - - void HandleWardenDataOpcode(WorldPacket& recv_data); - void HandleWorldTeleportOpcode(WorldPacket& recv_data); - void HandleMinimapPingOpcode(WorldPacket& recv_data); - void HandleRandomRollOpcode(WorldPacket& recv_data); - void HandleFarSightOpcode(WorldPacket& recv_data); - void HandleSetLfgOpcode(WorldPacket& recv_data); - void HandleDungeonDifficultyOpcode(WorldPacket& recv_data); - void HandleMoveFlyModeChangeAckOpcode(WorldPacket& recv_data); - void HandleLfgAutoJoinOpcode(WorldPacket& recv_data); - void HandleLfgCancelAutoJoinOpcode(WorldPacket& recv_data); - void HandleLfmAutoAddMembersOpcode(WorldPacket& recv_data); - void HandleLfmCancelAutoAddmembersOpcode(WorldPacket& recv_data); - void HandleLfgClearOpcode(WorldPacket& recv_data); - void HandleLfmSetNoneOpcode(WorldPacket& recv_data); - void HandleLfmSetOpcode(WorldPacket& recv_data); - void HandleLfgSetCommentOpcode(WorldPacket& recv_data); - void HandleNewUnknownOpcode(WorldPacket& recv_data); - void HandleChooseTitleOpcode(WorldPacket& recv_data); - void HandleRealmStateRequestOpcode(WorldPacket& recv_data); - void HandleAllowMoveAckOpcode(WorldPacket& recv_data); - void HandleWhoisOpcode(WorldPacket& recv_data); - void HandleResetInstancesOpcode(WorldPacket& recv_data); - - // Arena Team - void HandleInspectArenaStatsOpcode(WorldPacket& recv_data); - void HandleArenaTeamQueryOpcode(WorldPacket& recv_data); - void HandleArenaTeamRosterOpcode(WorldPacket& recv_data); - void HandleArenaTeamAddMemberOpcode(WorldPacket& recv_data); - void HandleArenaTeamInviteAcceptOpcode(WorldPacket& recv_data); - void HandleArenaTeamInviteDeclineOpcode(WorldPacket& recv_data); - void HandleArenaTeamLeaveOpcode(WorldPacket& recv_data); - void HandleArenaTeamRemoveFromTeamOpcode(WorldPacket& recv_data); - void HandleArenaTeamDisbandOpcode(WorldPacket& recv_data); - void HandleArenaTeamPromoteToCaptainOpcode(WorldPacket& recv_data); - - void HandleAreaSpiritHealerQueryOpcode(WorldPacket& recv_data); - void HandleAreaSpiritHealerQueueOpcode(WorldPacket& recv_data); - void HandleDismountOpcode(WorldPacket& recv_data); - void HandleSelfResOpcode(WorldPacket& recv_data); - void HandleReportSpamOpcode(WorldPacket& recv_data); - void HandleRequestPetInfoOpcode(WorldPacket& recv_data); - - // Socket gem - void HandleSocketOpcode(WorldPacket& recv_data); - - void HandleCancelTempItemEnchantmentOpcode(WorldPacket& recv_data); - - void HandleChannelEnableVoiceOpcode(WorldPacket & recv_data); - void HandleVoiceSettingsOpcode(WorldPacket& recv_data); - void HandleChannelVoiceChatQuery(WorldPacket& recv_data); - void HandleSetTaxiBenchmarkOpcode(WorldPacket& recv_data); - - // Guild Bank - void HandleGuildBankGetRights(WorldPacket& recv_data); - void HandleGuildBankGetMoneyAmount(WorldPacket& recv_data); - void HandleGuildBankQuery(WorldPacket& recv_data); - void HandleGuildBankTabColon(WorldPacket& recv_data); - void HandleGuildBankLog(WorldPacket& recv_data); - void HandleGuildBankDeposit(WorldPacket& recv_data); - void HandleGuildBankWithdraw(WorldPacket& recv_data); - void HandleGuildBankDepositItem(WorldPacket& recv_data); - void HandleGuildBankModifyTab(WorldPacket& recv_data); - void HandleGuildBankBuyTab(WorldPacket& recv_data); - void HandleGuildBankTabText(WorldPacket& recv_data); - void HandleGuildBankSetTabText(WorldPacket& recv_data); - private: - // private trade methods - void moveItems(Item* myItems[], Item* hisItems[]); - - // logging helper - void logUnexpectedOpcode(WorldPacket *packet, const char * reason); - Player *_player; - WorldSocket *m_Socket; - std::string m_Address; - - uint32 _security; - uint32 _accountId; - bool m_isTBC; - - time_t _logoutTime; - bool m_playerLoading; // code processed in LoginPlayer - bool m_playerLogout; // code processed in LogoutPlayer - bool m_playerRecentlyLogout; - LocaleConstant m_sessionDbcLocale; - int m_sessionDbLocaleIndex; - uint32 m_latency; - - ZThread::LockedQueue _recvQueue; -}; -#endif -/// @} +/* + * Copyright (C) 2005-2008 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/// \addtogroup u2w +/// @{ +/// \file + +#ifndef __WORLDSESSION_H +#define __WORLDSESSION_H + +#include "Common.h" + +class MailItemsInfo; +struct ItemPrototype; +struct AuctionEntry; + +class Creature; +class Item; +class Object; +class Player; +class Unit; +class WorldPacket; +class WorldSocket; +class WorldSession; +class QueryResult; +class LoginQueryHolder; +class CharacterHandler; + +#define CHECK_PACKET_SIZE(P,S) if((P).size() < (S)) return SizeError((P),(S)); + +enum PartyOperation +{ + PARTY_OP_INVITE = 0, + PARTY_OP_LEAVE = 2 +}; + +enum PartyResult +{ + PARTY_RESULT_OK = 0, + PARTY_RESULT_CANT_FIND_TARGET = 1, + PARTY_RESULT_NOT_IN_YOUR_PARTY = 2, + PARTY_RESULT_NOT_IN_YOUR_INSTANCE = 3, + PARTY_RESULT_PARTY_FULL = 4, + PARTY_RESULT_ALREADY_IN_GROUP = 5, + PARTY_RESULT_YOU_NOT_IN_GROUP = 6, + PARTY_RESULT_YOU_NOT_LEADER = 7, + PARTY_RESULT_TARGET_UNFRIENDLY = 8, + PARTY_RESULT_TARGET_IGNORE_YOU = 9, + PARTY_RESULT_INVITE_RESTRICTED = 13 +}; + +/// Player session in the World +class MANGOS_DLL_SPEC WorldSession +{ + friend class CharacterHandler; + public: + WorldSession(uint32 id, WorldSocket *sock, uint32 sec, bool tbc, time_t mute_time, LocaleConstant locale); + ~WorldSession(); + + bool PlayerLoading() const { return m_playerLoading; } + bool PlayerLogout() const { return m_playerLogout; } + + void SizeError(WorldPacket const& packet, uint32 size) const; + + void SendPacket(WorldPacket const* packet); + void SendNotification(const char *format,...) ATTR_PRINTF(2,3); + void SendNotification(int32 string_id,...); + void SendLfgResult(uint32 type, uint32 entry, uint8 lfg_type); + void SendPartyResult(PartyOperation operation, std::string member, PartyResult res); + void SendAreaTriggerMessage(const char* Text, ...) ATTR_PRINTF(2,3); + + uint32 GetSecurity() const { return _security; } + uint32 GetAccountId() const { return _accountId; } + Player* GetPlayer() const { return _player; } + char const* GetPlayerName() const; + void SetSecurity(uint32 security) { _security = security; } + std::string& GetRemoteAddress() { return m_Address; } + void SetPlayer(Player *plr) { _player = plr; } + bool IsTBC() const { return m_isTBC; } + + /// Is the user engaged in a log out process? + bool isLogingOut() const { return _logoutTime || m_playerLogout; } + + /// Engage the logout process for the user + void LogoutRequest(time_t requestTime) + { + _logoutTime = requestTime; + } + + /// Is logout cooldown expired? + bool ShouldLogOut(time_t currTime) const + { + return (_logoutTime > 0 && currTime >= _logoutTime + 20); + } + + void LogoutPlayer(bool Save); + void KickPlayer(); + + void QueuePacket(WorldPacket* new_packet); + bool Update(uint32 diff); + + /// Handle the authentication waiting queue (to be completed) + void SendAuthWaitQue(uint32 position); + + //void SendTestCreatureQueryOpcode( uint32 entry, uint64 guid, uint32 testvalue ); + void SendNameQueryOpcode(Player* p); + void SendNameQueryOpcodeFromDB(uint64 guid); + static void SendNameQueryOpcodeFromDBCallBack(QueryResult *result, uint32 accountId); + + void SendTrainerList( uint64 guid ); + void SendTrainerList( uint64 guid,std::string strTitle ); + void SendListInventory( uint64 guid ); + void SendShowBank( uint64 guid ); + void SendTabardVendorActivate( uint64 guid ); + void SendSpiritResurrect(); + void SendBindPoint(Creature* npc); + void SendGMTicketGetTicket(uint32 status, char const* text); + + void SendAttackStop(Unit const* enemy); + + void SendBattlegGroundList( uint64 guid, uint32 bgTypeId ); + + void SendTradeStatus(uint32 status); + void SendCancelTrade(); + + void SendStablePet(uint64 guid ); + void SendPetitionQueryOpcode( uint64 petitionguid); + void SendUpdateTrade(); + + //pet + void SendPetNameQuery(uint64 guid, uint32 petnumber); + + //mail + //used with item_page table + bool SendItemInfo( uint32 itemid, WorldPacket data ); + static void SendReturnToSender(uint8 messageType, uint32 sender_acc, uint32 sender_guid, uint32 receiver_guid, std::string subject, uint32 itemTextId, MailItemsInfo *mi, uint32 money, uint32 COD, uint16 mailTemplateId = 0); + static void SendMailTo(Player* receiver, uint8 messageType, uint8 stationery, uint32 sender_guidlow_or_entry, uint32 received_guidlow, std::string subject, uint32 itemTextId, MailItemsInfo* mi, uint32 money, uint32 COD, uint32 checked, uint32 deliver_delay = 0, uint16 mailTemplateId = 0); + + //auction + void SendAuctionHello( uint64 guid, Creature * unit ); + void SendAuctionCommandResult( uint32 auctionId, uint32 Action, uint32 ErrorCode, uint32 bidError = 0); + void SendAuctionBidderNotification( uint32 location, uint32 auctionId, uint64 bidder, uint32 bidSum, uint32 diff, uint32 item_template); + void SendAuctionOwnerNotification( AuctionEntry * auction ); + bool SendAuctionInfo(WorldPacket & data, AuctionEntry* auction); + void SendAuctionOutbiddedMail( AuctionEntry * auction, uint32 newPrice ); + void SendAuctionCancelledToBidderMail( AuctionEntry* auction ); + + //Item Enchantment + void SendEnchantmentLog(uint64 Target, uint64 Caster,uint32 ItemID,uint32 SpellID); + void SendItemEnchantTimeUpdate(uint64 Playerguid, uint64 Itemguid,uint32 slot,uint32 Duration); + + //Taxi + void SendTaxiStatus( uint64 guid ); + void SendTaxiMenu( Creature* unit ); + void SendDoFlight( uint16 MountId, uint32 path, uint32 pathNode = 0 ); + bool SendLearnNewTaxiNode( Creature* unit ); + + // Guild/Arena Team + void SendGuildCommandResult(uint32 typecmd,std::string str,uint32 cmdresult); + void SendArenaTeamCommandResult(uint32 unk1, std::string str1, std::string str2, uint32 unk3); + void BuildArenaTeamEventPacket(WorldPacket *data, uint8 eventid, uint8 str_count, std::string str1, std::string str2, std::string str3); + void SendNotInArenaTeamPacket(uint8 type); + void SendPetitionShowList( uint64 guid ); + void SendSaveGuildEmblem( uint32 msg ); + + // Looking For Group + // TRUE values set by client sending CMSG_LFG_SET_AUTOJOIN and CMSG_LFM_CLEAR_AUTOFILL before player login + bool LookingForGroup_auto_join; + bool LookingForGroup_auto_add; + + void BuildPartyMemberStatsChangedPacket(Player *player, WorldPacket *data); + + void DoLootRelease( uint64 lguid ); + + // Account mute time + time_t m_muteTime; + + // Locales + LocaleConstant GetSessionDbcLocale() { return m_sessionDbcLocale; } + int GetSessionDbLocaleIndex() { return m_sessionDbLocaleIndex; } + const char *GetMangosString(int32 entry); + + uint32 GetLatency() const { return m_latency; } + void SetLatency(uint32 latency) { m_latency = latency; } + uint32 getDialogStatus(Player *pPlayer, Object* questgiver, uint32 defstatus); + + public: // opcodes handlers + + void Handle_NULL(WorldPacket& recvPacket); // not used + void Handle_EarlyProccess( WorldPacket& recvPacket);// just mark packets processed in WorldSocket::OnRead + void Handle_ServerSide(WorldPacket& recvPacket); // sever side only, can't be accepted from client + void Handle_Depricated(WorldPacket& recvPacket); // never used anymore by client + + void HandleCharEnumOpcode(WorldPacket& recvPacket); + void HandleCharDeleteOpcode(WorldPacket& recvPacket); + void HandleCharCreateOpcode(WorldPacket& recvPacket); + void HandlePlayerLoginOpcode(WorldPacket& recvPacket); + void HandleCharEnum(QueryResult * result); + void HandlePlayerLogin(LoginQueryHolder * holder); + + // played time + void HandlePlayedTime(WorldPacket& recvPacket); + + // new + void HandleMoveUnRootAck(WorldPacket& recvPacket); + void HandleMoveRootAck(WorldPacket& recvPacket); + void HandleLookingForGroup(WorldPacket& recvPacket); + + // new inspect + void HandleInspectOpcode(WorldPacket& recvPacket); + + // new party stats + void HandleInspectHonorStatsOpcode(WorldPacket& recvPacket); + + void HandleMoveWaterWalkAck(WorldPacket& recvPacket); + void HandleFeatherFallAck(WorldPacket &recv_data); + + void HandleMoveHoverAck( WorldPacket & recv_data ); + + void HandleMountSpecialAnimOpcode(WorldPacket &recvdata); + + // character view + void HandleToggleHelmOpcode(WorldPacket& recv_data); + void HandleToggleCloakOpcode(WorldPacket& recv_data); + + // repair + void HandleRepairItemOpcode(WorldPacket& recvPacket); + + // Knockback + void HandleMoveKnockBackAck(WorldPacket& recvPacket); + + void HandleMoveTeleportAck(WorldPacket& recvPacket); + void HandleForceSpeedChangeAck( WorldPacket & recv_data ); + + void HandlePingOpcode(WorldPacket& recvPacket); + void HandleAuthSessionOpcode(WorldPacket& recvPacket); + void HandleRepopRequestOpcode(WorldPacket& recvPacket); + void HandleAutostoreLootItemOpcode(WorldPacket& recvPacket); + void HandleLootMoneyOpcode(WorldPacket& recvPacket); + void HandleLootOpcode(WorldPacket& recvPacket); + void HandleLootReleaseOpcode(WorldPacket& recvPacket); + void HandleLootMasterGiveOpcode(WorldPacket& recvPacket); + void HandleWhoOpcode(WorldPacket& recvPacket); + void HandleLogoutRequestOpcode(WorldPacket& recvPacket); + void HandlePlayerLogoutOpcode(WorldPacket& recvPacket); + void HandleLogoutCancelOpcode(WorldPacket& recvPacket); + void HandleGMTicketGetTicketOpcode(WorldPacket& recvPacket); + void HandleGMTicketCreateOpcode(WorldPacket& recvPacket); + void HandleGMTicketSystemStatusOpcode(WorldPacket& recvPacket); + + void HandleGMTicketDeleteOpcode(WorldPacket& recvPacket); + void HandleGMTicketUpdateTextOpcode(WorldPacket& recvPacket); + + void HandleGMSurveySubmit(WorldPacket& recvPacket); + + void HandleTogglePvP(WorldPacket& recvPacket); + + void HandleZoneUpdateOpcode(WorldPacket& recvPacket); + void HandleSetTargetOpcode(WorldPacket& recvPacket); + void HandleSetSelectionOpcode(WorldPacket& recvPacket); + void HandleStandStateChangeOpcode(WorldPacket& recvPacket); + void HandleEmoteOpcode(WorldPacket& recvPacket); + void HandleFriendListOpcode(WorldPacket& recvPacket); + void HandleAddFriendOpcode(WorldPacket& recvPacket); + void HandleDelFriendOpcode(WorldPacket& recvPacket); + void HandleAddIgnoreOpcode(WorldPacket& recvPacket); + void HandleDelIgnoreOpcode(WorldPacket& recvPacket); + void HandleSetFriendNoteOpcode(WorldPacket& recvPacket); + void HandleBugOpcode(WorldPacket& recvPacket); + void HandleSetAmmoOpcode(WorldPacket& recvPacket); + void HandleItemNameQueryOpcode(WorldPacket& recvPacket); + + void HandleAreaTriggerOpcode(WorldPacket& recvPacket); + + void HandleSetFactionAtWar( WorldPacket & recv_data ); + void HandleSetFactionCheat( WorldPacket & recv_data ); + void HandleSetWatchedFactionIndexOpcode(WorldPacket & recv_data); + void HandleSetWatchedFactionInactiveOpcode(WorldPacket & recv_data); + + void HandleUpdateAccountData(WorldPacket& recvPacket); + void HandleRequestAccountData(WorldPacket& recvPacket); + void HandleSetActionButtonOpcode(WorldPacket& recvPacket); + + void HandleGameObjectUseOpcode(WorldPacket& recPacket); + void HandleMeetingStoneInfo(WorldPacket& recPacket); + + void HandleNameQueryOpcode(WorldPacket& recvPacket); + + void HandleQueryTimeOpcode(WorldPacket& recvPacket); + + void HandleCreatureQueryOpcode(WorldPacket& recvPacket); + + void HandleGameObjectQueryOpcode(WorldPacket& recvPacket); + + void HandleMoveWorldportAckOpcode(WorldPacket& recvPacket); + void HandleMoveWorldportAckOpcode(); // for server-side calls + + void HandleMovementOpcodes(WorldPacket& recvPacket); + void HandleSetActiveMoverOpcode(WorldPacket &recv_data); + void HandleMoveTimeSkippedOpcode(WorldPacket &recv_data); + + void HandleRequestRaidInfoOpcode( WorldPacket & recv_data ); + + void HandleBattlefieldStatusOpcode(WorldPacket &recv_data); + void HandleBattleMasterHelloOpcode(WorldPacket &recv_data); + + void HandleGroupInviteOpcode(WorldPacket& recvPacket); + //void HandleGroupCancelOpcode(WorldPacket& recvPacket); + void HandleGroupAcceptOpcode(WorldPacket& recvPacket); + void HandleGroupDeclineOpcode(WorldPacket& recvPacket); + void HandleGroupUninviteNameOpcode(WorldPacket& recvPacket); + void HandleGroupUninviteGuidOpcode(WorldPacket& recvPacket); + void HandleGroupUninvite(uint64 guid, std::string name); + void HandleGroupSetLeaderOpcode(WorldPacket& recvPacket); + void HandleGroupLeaveOpcode(WorldPacket& recvPacket); + void HandleGroupPassOnLootOpcode( WorldPacket &recv_data ); + void HandleLootMethodOpcode(WorldPacket& recvPacket); + void HandleLootRoll( WorldPacket &recv_data ); + void HandleRequestPartyMemberStatsOpcode( WorldPacket &recv_data ); + void HandleRaidIconTargetOpcode( WorldPacket & recv_data ); + void HandleRaidReadyCheckOpcode( WorldPacket & recv_data ); + void HandleRaidReadyCheckFinishOpcode( WorldPacket & recv_data ); + void HandleRaidConvertOpcode( WorldPacket & recv_data ); + void HandleGroupChangeSubGroupOpcode( WorldPacket & recv_data ); + void HandleGroupAssistantOpcode( WorldPacket & recv_data ); + void HandleGroupPromoteOpcode( WorldPacket & recv_data ); + + void HandlePetitionBuyOpcode(WorldPacket& recv_data); + void HandlePetitionShowSignOpcode(WorldPacket& recv_data); + void HandlePetitionQueryOpcode(WorldPacket& recv_data); + void HandlePetitionRenameOpcode(WorldPacket& recv_data); + void HandlePetitionSignOpcode(WorldPacket& recv_data); + void HandlePetitionDeclineOpcode(WorldPacket& recv_data); + void HandleOfferPetitionOpcode(WorldPacket& recv_data); + void HandleTurnInPetitionOpcode(WorldPacket& recv_data); + + void HandleGuildQueryOpcode(WorldPacket& recvPacket); + void HandleGuildCreateOpcode(WorldPacket& recvPacket); + void HandleGuildInviteOpcode(WorldPacket& recvPacket); + void HandleGuildRemoveOpcode(WorldPacket& recvPacket); + void HandleGuildAcceptOpcode(WorldPacket& recvPacket); + void HandleGuildDeclineOpcode(WorldPacket& recvPacket); + void HandleGuildInfoOpcode(WorldPacket& recvPacket); + void HandleGuildEventLogOpcode(WorldPacket& recvPacket); + void HandleGuildRosterOpcode(WorldPacket& recvPacket); + void HandleGuildPromoteOpcode(WorldPacket& recvPacket); + void HandleGuildDemoteOpcode(WorldPacket& recvPacket); + void HandleGuildLeaveOpcode(WorldPacket& recvPacket); + void HandleGuildDisbandOpcode(WorldPacket& recvPacket); + void HandleGuildLeaderOpcode(WorldPacket& recvPacket); + void HandleGuildMOTDOpcode(WorldPacket& recvPacket); + void HandleGuildSetPublicNoteOpcode(WorldPacket& recvPacket); + void HandleGuildSetOfficerNoteOpcode(WorldPacket& recvPacket); + void HandleGuildRankOpcode(WorldPacket& recvPacket); + void HandleGuildAddRankOpcode(WorldPacket& recvPacket); + void HandleGuildDelRankOpcode(WorldPacket& recvPacket); + void HandleGuildChangeInfoOpcode(WorldPacket& recvPacket); + void HandleGuildSaveEmblemOpcode(WorldPacket& recvPacket); + + void HandleTaxiNodeStatusQueryOpcode(WorldPacket& recvPacket); + void HandleTaxiQueryAvailableNodesOpcode(WorldPacket& recvPacket); + void HandleActivateTaxiOpcode(WorldPacket& recvPacket); + void HandleActivateTaxiFarOpcode(WorldPacket& recvPacket); + void HandleTaxiNextDestinationOpcode(WorldPacket& recvPacket); + + void HandleTabardVendorActivateOpcode(WorldPacket& recvPacket); + void HandleBankerActivateOpcode(WorldPacket& recvPacket); + void HandleBuyBankSlotOpcode(WorldPacket& recvPacket); + void HandleTrainerListOpcode(WorldPacket& recvPacket); + void HandleTrainerBuySpellOpcode(WorldPacket& recvPacket); + void HandlePetitionShowListOpcode(WorldPacket& recvPacket); + void HandleGossipHelloOpcode(WorldPacket& recvPacket); + void HandleGossipSelectOptionOpcode(WorldPacket& recvPacket); + void HandleSpiritHealerActivateOpcode(WorldPacket& recvPacket); + void HandleNpcTextQueryOpcode(WorldPacket& recvPacket); + void HandleBinderActivateOpcode(WorldPacket& recvPacket); + void HandleListStabledPetsOpcode(WorldPacket& recvPacket); + void HandleStablePet(WorldPacket& recvPacket); + void HandleUnstablePet(WorldPacket& recvPacket); + void HandleBuyStableSlot(WorldPacket& recvPacket); + void HandleStableRevivePet(WorldPacket& recvPacket); + void HandleStableSwapPet(WorldPacket& recvPacket); + + void HandleDuelAcceptedOpcode(WorldPacket& recvPacket); + void HandleDuelCancelledOpcode(WorldPacket& recvPacket); + + void HandleAcceptTradeOpcode(WorldPacket& recvPacket); + void HandleBeginTradeOpcode(WorldPacket& recvPacket); + void HandleBusyTradeOpcode(WorldPacket& recvPacket); + void HandleCancelTradeOpcode(WorldPacket& recvPacket); + void HandleClearTradeItemOpcode(WorldPacket& recvPacket); + void HandleIgnoreTradeOpcode(WorldPacket& recvPacket); + void HandleInitiateTradeOpcode(WorldPacket& recvPacket); + void HandleSetTradeGoldOpcode(WorldPacket& recvPacket); + void HandleSetTradeItemOpcode(WorldPacket& recvPacket); + void HandleUnacceptTradeOpcode(WorldPacket& recvPacket); + + void HandleAuctionHelloOpcode(WorldPacket& recvPacket); + void HandleAuctionListItems( WorldPacket & recv_data ); + void HandleAuctionListBidderItems( WorldPacket & recv_data ); + void HandleAuctionSellItem( WorldPacket & recv_data ); + void HandleAuctionRemoveItem( WorldPacket & recv_data ); + void HandleAuctionListOwnerItems( WorldPacket & recv_data ); + void HandleAuctionPlaceBid( WorldPacket & recv_data ); + + void HandleGetMail( WorldPacket & recv_data ); + void HandleSendMail( WorldPacket & recv_data ); + void HandleTakeMoney( WorldPacket & recv_data ); + void HandleTakeItem( WorldPacket & recv_data ); + void HandleMarkAsRead( WorldPacket & recv_data ); + void HandleReturnToSender( WorldPacket & recv_data ); + void HandleMailDelete( WorldPacket & recv_data ); + void HandleItemTextQuery( WorldPacket & recv_data); + void HandleMailCreateTextItem(WorldPacket & recv_data ); + void HandleMsgQueryNextMailtime(WorldPacket & recv_data ); + void HandleCancelChanneling(WorldPacket & recv_data ); + + void SendItemPageInfo( ItemPrototype *itemProto ); + void HandleSplitItemOpcode(WorldPacket& recvPacket); + void HandleSwapInvItemOpcode(WorldPacket& recvPacket); + void HandleDestroyItemOpcode(WorldPacket& recvPacket); + void HandleAutoEquipItemOpcode(WorldPacket& recvPacket); + void HandleItemQuerySingleOpcode(WorldPacket& recvPacket); + void HandleSellItemOpcode(WorldPacket& recvPacket); + void HandleBuyItemInSlotOpcode(WorldPacket& recvPacket); + void HandleBuyItemOpcode(WorldPacket& recvPacket); + void HandleListInventoryOpcode(WorldPacket& recvPacket); + void HandleAutoStoreBagItemOpcode(WorldPacket& recvPacket); + void HandleReadItem(WorldPacket& recvPacket); + void HandleAutoEquipItemSlotOpcode(WorldPacket & recvPacket); + void HandleSwapItem( WorldPacket & recvPacket); + void HandleBuybackItem(WorldPacket & recvPacket); + void HandleAutoBankItemOpcode(WorldPacket& recvPacket); + void HandleAutoStoreBankItemOpcode(WorldPacket& recvPacket); + void HandleWrapItemOpcode(WorldPacket& recvPacket); + + void HandleAttackSwingOpcode(WorldPacket& recvPacket); + void HandleAttackStopOpcode(WorldPacket& recvPacket); + void HandleSetSheathedOpcode(WorldPacket& recvPacket); + + void HandleUseItemOpcode(WorldPacket& recvPacket); + void HandleOpenItemOpcode(WorldPacket& recvPacket); + void HandleCastSpellOpcode(WorldPacket& recvPacket); + void HandleCancelCastOpcode(WorldPacket& recvPacket); + void HandleCancelAuraOpcode(WorldPacket& recvPacket); + void HandleCancelGrowthAuraOpcode(WorldPacket& recvPacket); + void HandleCancelAutoRepeatSpellOpcode(WorldPacket& recvPacket); + + void HandleLearnTalentOpcode(WorldPacket& recvPacket); + void HandleTalentWipeOpcode(WorldPacket& recvPacket); + void HandleUnlearnSkillOpcode(WorldPacket& recvPacket); + + void HandleQuestgiverStatusQueryOpcode(WorldPacket& recvPacket); + void HandleQuestgiverStatusQueryMultipleOpcode(WorldPacket& recvPacket); + void HandleQuestgiverHelloOpcode(WorldPacket& recvPacket); + void HandleQuestgiverAcceptQuestOpcode(WorldPacket& recvPacket); + void HandleQuestgiverQuestQueryOpcode(WorldPacket& recvPacket); + void HandleQuestgiverChooseRewardOpcode(WorldPacket& recvPacket); + void HandleQuestgiverRequestRewardOpcode(WorldPacket& recvPacket); + void HandleQuestQueryOpcode(WorldPacket& recvPacket); + void HandleQuestgiverCancel(WorldPacket& recv_data ); + void HandleQuestLogSwapQuest(WorldPacket& recv_data ); + void HandleQuestLogRemoveQuest(WorldPacket& recv_data); + void HandleQuestConfirmAccept(WorldPacket& recv_data); + void HandleQuestComplete(WorldPacket& recv_data); + void HandleQuestAutoLaunch(WorldPacket& recvPacket); + void HandleQuestPushToParty(WorldPacket& recvPacket); + void HandleQuestPushResult(WorldPacket& recvPacket); + + void HandleMessagechatOpcode(WorldPacket& recvPacket); + void HandleTextEmoteOpcode(WorldPacket& recvPacket); + void HandleChatIgnoredOpcode(WorldPacket& recvPacket); + + void HandleCorpseReclaimOpcode( WorldPacket& recvPacket ); + void HandleCorpseQueryOpcode( WorldPacket& recvPacket ); + void HandleResurrectResponseOpcode(WorldPacket& recvPacket); + void HandleSummonResponseOpcode(WorldPacket& recv_data); + + void HandleChannelJoin(WorldPacket& recvPacket); + void HandleChannelLeave(WorldPacket& recvPacket); + void HandleChannelList(WorldPacket& recvPacket); + void HandleChannelPassword(WorldPacket& recvPacket); + void HandleChannelSetOwner(WorldPacket& recvPacket); + void HandleChannelOwner(WorldPacket& recvPacket); + void HandleChannelModerator(WorldPacket& recvPacket); + void HandleChannelUnmoderator(WorldPacket& recvPacket); + void HandleChannelMute(WorldPacket& recvPacket); + void HandleChannelUnmute(WorldPacket& recvPacket); + void HandleChannelInvite(WorldPacket& recvPacket); + void HandleChannelKick(WorldPacket& recvPacket); + void HandleChannelBan(WorldPacket& recvPacket); + void HandleChannelUnban(WorldPacket& recvPacket); + void HandleChannelAnnounce(WorldPacket& recvPacket); + void HandleChannelModerate(WorldPacket& recvPacket); + void HandleChannelRosterQuery(WorldPacket& recvPacket); + void HandleChannelInfoQuery(WorldPacket& recvPacket); + void HandleChannelJoinNotify(WorldPacket& recvPacket); + + void HandleCompleteCinema(WorldPacket& recvPacket); + void HandleNextCinematicCamera(WorldPacket& recvPacket); + + void HandlePageQuerySkippedOpcode(WorldPacket& recvPacket); + void HandlePageQueryOpcode(WorldPacket& recvPacket); + + void HandleTutorialFlag ( WorldPacket & recv_data ); + void HandleTutorialClear( WorldPacket & recv_data ); + void HandleTutorialReset( WorldPacket & recv_data ); + + //Pet + void HandlePetAction( WorldPacket & recv_data ); + void HandlePetNameQuery( WorldPacket & recv_data ); + void HandlePetSetAction( WorldPacket & recv_data ); + void HandlePetAbandon( WorldPacket & recv_data ); + void HandlePetRename( WorldPacket & recv_data ); + void HandlePetCancelAuraOpcode( WorldPacket& recvPacket ); + void HandlePetUnlearnOpcode( WorldPacket& recvPacket ); + void HandlePetSpellAutocastOpcode( WorldPacket& recvPacket ); + void HandleAddDynamicTargetObsoleteOpcode( WorldPacket& recvPacket ); + + void HandleSetActionBar(WorldPacket& recv_data); + + void HandleChangePlayerNameOpcode(WorldPacket& recv_data); + void HandleDeclinedPlayerNameOpcode(WorldPacket& recv_data); + + void HandleTotemDestroy(WorldPacket& recv_data); + + //BattleGround + void HandleBattleGroundHelloOpcode(WorldPacket &recv_data); + void HandleBattleGroundJoinOpcode(WorldPacket &recv_data); + void HandleBattleGroundPlayerPositionsOpcode(WorldPacket& recv_data); + void HandleBattleGroundPVPlogdataOpcode( WorldPacket &recv_data ); + void HandleBattleGroundPlayerPortOpcode( WorldPacket &recv_data ); + void HandleBattleGroundListOpcode( WorldPacket &recv_data ); + void HandleBattleGroundLeaveOpcode( WorldPacket &recv_data ); + void HandleBattleGroundArenaJoin( WorldPacket &recv_data ); + void HandleBattleGroundReportAFK( WorldPacket &recv_data ); + + void HandleWardenDataOpcode(WorldPacket& recv_data); + void HandleWorldTeleportOpcode(WorldPacket& recv_data); + void HandleMinimapPingOpcode(WorldPacket& recv_data); + void HandleRandomRollOpcode(WorldPacket& recv_data); + void HandleFarSightOpcode(WorldPacket& recv_data); + void HandleSetLfgOpcode(WorldPacket& recv_data); + void HandleDungeonDifficultyOpcode(WorldPacket& recv_data); + void HandleMoveFlyModeChangeAckOpcode(WorldPacket& recv_data); + void HandleLfgAutoJoinOpcode(WorldPacket& recv_data); + void HandleLfgCancelAutoJoinOpcode(WorldPacket& recv_data); + void HandleLfmAutoAddMembersOpcode(WorldPacket& recv_data); + void HandleLfmCancelAutoAddmembersOpcode(WorldPacket& recv_data); + void HandleLfgClearOpcode(WorldPacket& recv_data); + void HandleLfmSetNoneOpcode(WorldPacket& recv_data); + void HandleLfmSetOpcode(WorldPacket& recv_data); + void HandleLfgSetCommentOpcode(WorldPacket& recv_data); + void HandleNewUnknownOpcode(WorldPacket& recv_data); + void HandleChooseTitleOpcode(WorldPacket& recv_data); + void HandleRealmStateRequestOpcode(WorldPacket& recv_data); + void HandleAllowMoveAckOpcode(WorldPacket& recv_data); + void HandleWhoisOpcode(WorldPacket& recv_data); + void HandleResetInstancesOpcode(WorldPacket& recv_data); + + // Arena Team + void HandleInspectArenaStatsOpcode(WorldPacket& recv_data); + void HandleArenaTeamQueryOpcode(WorldPacket& recv_data); + void HandleArenaTeamRosterOpcode(WorldPacket& recv_data); + void HandleArenaTeamAddMemberOpcode(WorldPacket& recv_data); + void HandleArenaTeamInviteAcceptOpcode(WorldPacket& recv_data); + void HandleArenaTeamInviteDeclineOpcode(WorldPacket& recv_data); + void HandleArenaTeamLeaveOpcode(WorldPacket& recv_data); + void HandleArenaTeamRemoveFromTeamOpcode(WorldPacket& recv_data); + void HandleArenaTeamDisbandOpcode(WorldPacket& recv_data); + void HandleArenaTeamPromoteToCaptainOpcode(WorldPacket& recv_data); + + void HandleAreaSpiritHealerQueryOpcode(WorldPacket& recv_data); + void HandleAreaSpiritHealerQueueOpcode(WorldPacket& recv_data); + void HandleDismountOpcode(WorldPacket& recv_data); + void HandleSelfResOpcode(WorldPacket& recv_data); + void HandleReportSpamOpcode(WorldPacket& recv_data); + void HandleRequestPetInfoOpcode(WorldPacket& recv_data); + + // Socket gem + void HandleSocketOpcode(WorldPacket& recv_data); + + void HandleCancelTempItemEnchantmentOpcode(WorldPacket& recv_data); + + void HandleChannelEnableVoiceOpcode(WorldPacket & recv_data); + void HandleVoiceSettingsOpcode(WorldPacket& recv_data); + void HandleChannelVoiceChatQuery(WorldPacket& recv_data); + void HandleSetTaxiBenchmarkOpcode(WorldPacket& recv_data); + + // Guild Bank + void HandleGuildBankGetRights(WorldPacket& recv_data); + void HandleGuildBankGetMoneyAmount(WorldPacket& recv_data); + void HandleGuildBankQuery(WorldPacket& recv_data); + void HandleGuildBankTabColon(WorldPacket& recv_data); + void HandleGuildBankLog(WorldPacket& recv_data); + void HandleGuildBankDeposit(WorldPacket& recv_data); + void HandleGuildBankWithdraw(WorldPacket& recv_data); + void HandleGuildBankDepositItem(WorldPacket& recv_data); + void HandleGuildBankModifyTab(WorldPacket& recv_data); + void HandleGuildBankBuyTab(WorldPacket& recv_data); + void HandleGuildBankTabText(WorldPacket& recv_data); + void HandleGuildBankSetTabText(WorldPacket& recv_data); + private: + // private trade methods + void moveItems(Item* myItems[], Item* hisItems[]); + + // logging helper + void logUnexpectedOpcode(WorldPacket *packet, const char * reason); + Player *_player; + WorldSocket *m_Socket; + std::string m_Address; + + uint32 _security; + uint32 _accountId; + bool m_isTBC; + + time_t _logoutTime; + bool m_playerLoading; // code processed in LoginPlayer + bool m_playerLogout; // code processed in LogoutPlayer + bool m_playerRecentlyLogout; + LocaleConstant m_sessionDbcLocale; + int m_sessionDbLocaleIndex; + uint32 m_latency; + + ZThread::LockedQueue _recvQueue; +}; +#endif +/// @} diff --git a/src/shared/WheatyExceptionReport.cpp b/src/shared/WheatyExceptionReport.cpp index ced12f96d9c..27fae70e4c1 100644 --- a/src/shared/WheatyExceptionReport.cpp +++ b/src/shared/WheatyExceptionReport.cpp @@ -1,964 +1,1014 @@ -//========================================== -// Matt Pietrek -// MSDN Magazine, 2002 -// FILE: WheatyExceptionReport.CPP -//========================================== -#define WIN32_LEAN_AND_MEAN -#pragma warning(disable:4996) -#pragma warning(disable:4312) -#pragma warning(disable:4311) -#include -#include -#include -#define _NO_CVCONST_H -#include -#include "WheatyExceptionReport.h" -#include "svn_revision.h" -#define CrashFolder _T("Crashs") -//#pragma comment(linker, "/defaultlib:dbghelp.lib") - -inline LPTSTR ErrorMessage(DWORD dw) -{ - LPVOID lpMsgBuf; - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - dw, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &lpMsgBuf, - 0, NULL ); - return (LPTSTR)lpMsgBuf; -} - -//============================== Global Variables ============================= - -// -// Declare the static variables of the WheatyExceptionReport class -// -TCHAR WheatyExceptionReport::m_szLogFileName[MAX_PATH]; -LPTOP_LEVEL_EXCEPTION_FILTER WheatyExceptionReport::m_previousFilter; -HANDLE WheatyExceptionReport::m_hReportFile; -HANDLE WheatyExceptionReport::m_hProcess; - -// Declare global instance of class -WheatyExceptionReport g_WheatyExceptionReport; - -//============================== Class Methods ============================= - -WheatyExceptionReport::WheatyExceptionReport( ) // Constructor -{ - // Install the unhandled exception filter function - m_previousFilter = SetUnhandledExceptionFilter(WheatyUnhandledExceptionFilter); - m_hProcess = GetCurrentProcess(); -} - -//============ -// Destructor -//============ -WheatyExceptionReport::~WheatyExceptionReport( ) -{ - if(m_previousFilter) - SetUnhandledExceptionFilter( m_previousFilter ); -} - -//=========================================================== -// Entry point where control comes on an unhandled exception -//=========================================================== -LONG WINAPI WheatyExceptionReport::WheatyUnhandledExceptionFilter( -PEXCEPTION_POINTERS pExceptionInfo ) -{ - TCHAR module_folder_name[MAX_PATH]; - GetModuleFileName( 0, module_folder_name, MAX_PATH ); - TCHAR* pos = _tcsrchr(module_folder_name, '\\'); - if(!pos) - return 0; - pos[0] = '\0'; - ++pos; - - TCHAR crash_folder_path[MAX_PATH]; - sprintf(crash_folder_path, "%s\\%s", module_folder_name, CrashFolder); - if(!CreateDirectory(crash_folder_path, NULL)) - { - if(GetLastError() != ERROR_ALREADY_EXISTS) - return 0; - } - - SYSTEMTIME systime; - GetLocalTime(&systime); - sprintf(m_szLogFileName, "%s\\%s_[%u-%u_%u-%u-%u].txt", - crash_folder_path, pos, systime.wDay, systime.wMonth, systime.wHour, systime.wMinute, systime.wSecond - ); - - m_hReportFile = CreateFile( m_szLogFileName, - GENERIC_WRITE, - 0, - 0, - OPEN_ALWAYS, - FILE_FLAG_WRITE_THROUGH, - 0 ); - - if ( m_hReportFile ) - { - SetFilePointer( m_hReportFile, 0, 0, FILE_END ); - - GenerateExceptionReport( pExceptionInfo ); - - CloseHandle( m_hReportFile ); - m_hReportFile = 0; - } - - if ( m_previousFilter ) - return m_previousFilter( pExceptionInfo ); - else - return EXCEPTION_EXECUTE_HANDLER/*EXCEPTION_CONTINUE_SEARCH*/; -} - -BOOL WheatyExceptionReport::_GetProcessorName(TCHAR* sProcessorName, DWORD maxcount) -{ - if(!sProcessorName) - return FALSE; - - HKEY hKey; - LONG lRet; - lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), - 0, KEY_QUERY_VALUE, &hKey); - if (lRet != ERROR_SUCCESS) - return FALSE; - TCHAR szTmp[2048]; - DWORD cntBytes = sizeof(szTmp); - lRet = ::RegQueryValueEx(hKey, _T("ProcessorNameString"), NULL, NULL, - (LPBYTE)szTmp, &cntBytes); - if (lRet != ERROR_SUCCESS) - return FALSE; - ::RegCloseKey(hKey); - sProcessorName[0] = '\0'; - // Skip spaces - TCHAR* psz = szTmp; - while (iswspace(*psz)) - ++psz; - _tcsncpy(sProcessorName, psz, maxcount); - return TRUE; -} - -BOOL WheatyExceptionReport::_GetWindowsVersion(TCHAR* szVersion, DWORD cntMax) -{ - // Try calling GetVersionEx using the OSVERSIONINFOEX structure. - // If that fails, try using the OSVERSIONINFO structure. - OSVERSIONINFOEX osvi = { 0 }; - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - BOOL bOsVersionInfoEx; - bOsVersionInfoEx = ::GetVersionEx((LPOSVERSIONINFO)(&osvi)); - if (!bOsVersionInfoEx) - { - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - if (!::GetVersionEx((OSVERSIONINFO*)&osvi)) - return FALSE; - } - *szVersion = _T('\0'); - TCHAR wszTmp[128]; - switch (osvi.dwPlatformId) - { - // Windows NT product family. - case VER_PLATFORM_WIN32_NT: - // Test for the specific product family. - if (osvi.dwMajorVersion == 6) - _tcsncat(szVersion, _T("Windows Vista or Windows Server 2008 "), cntMax); - if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) - _tcsncat(szVersion, _T("Microsoft Windows Server 2003 "), cntMax); - if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) - _tcsncat(szVersion, _T("Microsoft Windows XP "), cntMax); - if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) - _tcsncat(szVersion, _T("Microsoft Windows 2000 "), cntMax); - if (osvi.dwMajorVersion <= 4 ) - _tcsncat(szVersion, _T("Microsoft Windows NT "), cntMax); - - // Test for specific product on Windows NT 4.0 SP6 and later. - if (bOsVersionInfoEx) - { - // Test for the workstation type. - #if WINVER < 0x0500 - if (osvi.wReserved[1] == VER_NT_WORKSTATION) - #else - if (osvi.wProductType == VER_NT_WORKSTATION) - #endif // WINVER < 0x0500 - { - if (osvi.dwMajorVersion == 4) - _tcsncat(szVersion, _T("Workstation 4.0 "), cntMax); - #if WINVER < 0x0500 - else if (osvi.wReserved[0] & VER_SUITE_PERSONAL) - #else - else if (osvi.wSuiteMask & VER_SUITE_PERSONAL) - #endif // WINVER < 0x0500 - _tcsncat(szVersion, _T("Home Edition "), cntMax); - #if WINVER < 0x0500 - else if (osvi.wReserved[0] & VER_SUITE_EMBEDDEDNT) - #else - else if (osvi.wSuiteMask & VER_SUITE_EMBEDDEDNT) - #endif // WINVER < 0x0500 - _tcsncat(szVersion, _T("Embedded "), cntMax); - else - _tcsncat(szVersion, _T("Professional "), cntMax); - } - // Test for the server type. - #if WINVER < 0x0500 - else if (osvi.wReserved[1] == VER_NT_SERVER) - #else - else if (osvi.wProductType == VER_NT_SERVER) - #endif // WINVER < 0x0500 - { - if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) - { - #if WINVER < 0x0500 - if (osvi.wReserved[0] & VER_SUITE_DATACENTER) - #else - if (osvi.wSuiteMask & VER_SUITE_DATACENTER) - #endif // WINVER < 0x0500 - _tcsncat(szVersion, _T("Datacenter Edition "), cntMax); - #if WINVER < 0x0500 - else if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE) - #else - else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) - #endif // WINVER < 0x0500 - _tcsncat(szVersion, _T("Enterprise Edition "), cntMax); - #if WINVER < 0x0500 - else if (osvi.wReserved[0] == VER_SUITE_BLADE) - #else - else if (osvi.wSuiteMask == VER_SUITE_BLADE) - #endif // WINVER < 0x0500 - _tcsncat(szVersion, _T("Web Edition "), cntMax); - else - _tcsncat(szVersion, _T("Standard Edition "), cntMax); - } - else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) - { - #if WINVER < 0x0500 - if (osvi.wReserved[0] & VER_SUITE_DATACENTER) - #else - if (osvi.wSuiteMask & VER_SUITE_DATACENTER) - #endif // WINVER < 0x0500 - _tcsncat(szVersion, _T("Datacenter Server "), cntMax); - #if WINVER < 0x0500 - else if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE ) - #else - else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE ) - #endif // WINVER < 0x0500 - _tcsncat(szVersion, _T("Advanced Server "), cntMax); - else - _tcsncat(szVersion, _T("Server "), cntMax); - } - else // Windows NT 4.0 - { - #if WINVER < 0x0500 - if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE) - #else - if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) - #endif // WINVER < 0x0500 - _tcsncat(szVersion, _T("Server 4.0, Enterprise Edition "), cntMax); - else - _tcsncat(szVersion, _T("Server 4.0 "), cntMax); - } - } - } - // Display service pack (if any) and build number. - if (osvi.dwMajorVersion == 4 && _tcsicmp(osvi.szCSDVersion, _T("Service Pack 6")) == 0) - { - HKEY hKey; - LONG lRet; - - // Test for SP6 versus SP6a. - lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009"), 0, KEY_QUERY_VALUE, &hKey); - if (lRet == ERROR_SUCCESS) - { - _stprintf(wszTmp, _T("Service Pack 6a (Version %d.%d, Build %d)"), - osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); - _tcsncat(szVersion, wszTmp, cntMax); - } - else // Windows NT 4.0 prior to SP6a - { - _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), - osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); - _tcsncat(szVersion, wszTmp, cntMax); - } - ::RegCloseKey(hKey); - } - else // Windows NT 3.51 and earlier or Windows 2000 and later - { - if (!_tcslen(osvi.szCSDVersion)) - _stprintf(wszTmp, _T("(Version %d.%d, Build %d)"), - osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); - else - _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), - osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); - _tcsncat(szVersion, wszTmp, cntMax); - } - break; - default: - _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), - osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); - _tcsncat(szVersion, wszTmp, cntMax); - break; - } - - return TRUE; -} - -void WheatyExceptionReport::PrintSystemInfo() -{ - SYSTEM_INFO SystemInfo; - ::GetSystemInfo(&SystemInfo); - - MEMORYSTATUS MemoryStatus; - MemoryStatus.dwLength = sizeof (MEMORYSTATUS); - ::GlobalMemoryStatus(&MemoryStatus); - TCHAR sString[1024]; - _tprintf(_T("//=====================================================\r\n")); - if (_GetProcessorName(sString, countof(sString))) - _tprintf(_T("*** Hardware ***\r\nProcessor: %s\r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"), - sString, SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400); - else - _tprintf(_T("*** Hardware ***\r\nProcessor: \r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"), - SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400); - - if(_GetWindowsVersion(sString, countof(sString))) - _tprintf(_T("\r\n*** Operation System ***\r\n%s\r\n"), sString); - else - _tprintf(_T("\r\n*** Operation System:\r\n\r\n")); -} - -//=========================================================================== -// Open the report file, and write the desired information to it. Called by -// WheatyUnhandledExceptionFilter -//=========================================================================== -void WheatyExceptionReport::GenerateExceptionReport( -PEXCEPTION_POINTERS pExceptionInfo ) -{ - SYSTEMTIME systime; - GetLocalTime(&systime); - - // Start out with a banner - _tprintf(_T("Revision: %s\r\n"), SVN_REVISION); - _tprintf(_T("Date %u:%u:%u. Time %u:%u \r\n"), systime.wDay, systime.wMonth, systime.wYear, systime.wHour, systime.wMinute); - PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; - - PrintSystemInfo(); - // First print information about the type of fault - _tprintf(_T("\r\n//=====================================================\r\n")); - _tprintf( _T("Exception code: %08X %s\r\n"), - pExceptionRecord->ExceptionCode, - GetExceptionString(pExceptionRecord->ExceptionCode) ); - - // Now print information about where the fault occured - TCHAR szFaultingModule[MAX_PATH]; - DWORD section; - DWORD_PTR offset; - GetLogicalAddress( pExceptionRecord->ExceptionAddress, - szFaultingModule, - sizeof( szFaultingModule ), - section, offset ); - -#ifdef _M_IX86 - _tprintf( _T("Fault address: %08X %02X:%08X %s\r\n"), - pExceptionRecord->ExceptionAddress, - section, offset, szFaultingModule ); -#endif -#ifdef _M_X64 - _tprintf( _T("Fault address: %016I64X %02X:%016I64X %s\r\n"), - pExceptionRecord->ExceptionAddress, - section, offset, szFaultingModule ); -#endif - - PCONTEXT pCtx = pExceptionInfo->ContextRecord; - - // Show the registers - #ifdef _M_IX86 // X86 Only! - _tprintf( _T("\r\nRegisters:\r\n") ); - - _tprintf(_T("EAX:%08X\r\nEBX:%08X\r\nECX:%08X\r\nEDX:%08X\r\nESI:%08X\r\nEDI:%08X\r\n") - ,pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, - pCtx->Esi, pCtx->Edi ); - - _tprintf( _T("CS:EIP:%04X:%08X\r\n"), pCtx->SegCs, pCtx->Eip ); - _tprintf( _T("SS:ESP:%04X:%08X EBP:%08X\r\n"), - pCtx->SegSs, pCtx->Esp, pCtx->Ebp ); - _tprintf( _T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), - pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs ); - _tprintf( _T("Flags:%08X\r\n"), pCtx->EFlags ); - #endif - - #ifdef _M_X64 - _tprintf( _T("\r\nRegisters:\r\n") ); - _tprintf(_T("RAX:%016I64X\r\nRBX:%016I64X\r\nRCX:%016I64X\r\nRDX:%016I64X\r\nRSI:%016I64X\r\nRDI:%016I64X\r\n") - _T("R8: %016I64X\r\nR9: %016I64X\r\nR10:%016I64X\r\nR11:%016I64X\r\nR12:%016I64X\r\nR13:%016I64X\r\nR14:%016I64X\r\nR15:%016I64X\r\n") - ,pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx, - pCtx->Rsi, pCtx->Rdi ,pCtx->R9,pCtx->R10,pCtx->R11,pCtx->R12,pCtx->R13,pCtx->R14,pCtx->R15); - _tprintf( _T("CS:RIP:%04X:%016I64X\r\n"), pCtx->SegCs, pCtx->Rip ); - _tprintf( _T("SS:RSP:%04X:%016X RBP:%08X\r\n"), - pCtx->SegSs, pCtx->Rsp, pCtx->Rbp ); - _tprintf( _T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), - pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs ); - _tprintf( _T("Flags:%08X\r\n"), pCtx->EFlags ); - #endif - - SymSetOptions( SYMOPT_DEFERRED_LOADS ); - - // Initialize DbgHelp - if ( !SymInitialize( GetCurrentProcess(), 0, TRUE ) ) - { - _tprintf(_T("\n\rCRITICAL ERROR.\n\r Couldn't initialize the symbol handler for process.\n\rError [%s].\n\r\n\r"), - ErrorMessage(GetLastError())); - } - - CONTEXT trashableContext = *pCtx; - - WriteStackDetails( &trashableContext, false ); - -// #ifdef _M_IX86 // X86 Only! - - _tprintf( _T("========================\r\n") ); - _tprintf( _T("Local Variables And Parameters\r\n") ); - - trashableContext = *pCtx; - WriteStackDetails( &trashableContext, true ); - - _tprintf( _T("========================\r\n") ); - _tprintf( _T("Global Variables\r\n") ); - - SymEnumSymbols( GetCurrentProcess(), - (DWORD64)GetModuleHandle(szFaultingModule), - 0, EnumerateSymbolsCallback, 0 ); - // #endif // X86 Only! - - SymCleanup( GetCurrentProcess() ); - - _tprintf( _T("\r\n") ); -} - -//====================================================================== -// Given an exception code, returns a pointer to a static string with a -// description of the exception -//====================================================================== -LPTSTR WheatyExceptionReport::GetExceptionString( DWORD dwCode ) -{ - #define EXCEPTION( x ) case EXCEPTION_##x: return _T(#x); - - switch ( dwCode ) - { - EXCEPTION( ACCESS_VIOLATION ) - EXCEPTION( DATATYPE_MISALIGNMENT ) - EXCEPTION( BREAKPOINT ) - EXCEPTION( SINGLE_STEP ) - EXCEPTION( ARRAY_BOUNDS_EXCEEDED ) - EXCEPTION( FLT_DENORMAL_OPERAND ) - EXCEPTION( FLT_DIVIDE_BY_ZERO ) - EXCEPTION( FLT_INEXACT_RESULT ) - EXCEPTION( FLT_INVALID_OPERATION ) - EXCEPTION( FLT_OVERFLOW ) - EXCEPTION( FLT_STACK_CHECK ) - EXCEPTION( FLT_UNDERFLOW ) - EXCEPTION( INT_DIVIDE_BY_ZERO ) - EXCEPTION( INT_OVERFLOW ) - EXCEPTION( PRIV_INSTRUCTION ) - EXCEPTION( IN_PAGE_ERROR ) - EXCEPTION( ILLEGAL_INSTRUCTION ) - EXCEPTION( NONCONTINUABLE_EXCEPTION ) - EXCEPTION( STACK_OVERFLOW ) - EXCEPTION( INVALID_DISPOSITION ) - EXCEPTION( GUARD_PAGE ) - EXCEPTION( INVALID_HANDLE ) - } - - // If not one of the "known" exceptions, try to get the string - // from NTDLL.DLL's message table. - - static TCHAR szBuffer[512] = { 0 }; - - FormatMessage( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, - GetModuleHandle( _T("NTDLL.DLL") ), - dwCode, 0, szBuffer, sizeof( szBuffer ), 0 ); - - return szBuffer; -} - -//============================================================================= -// Given a linear address, locates the module, section, and offset containing -// that address. -// -// Note: the szModule paramater buffer is an output buffer of length specified -// by the len parameter (in characters!) -//============================================================================= -BOOL WheatyExceptionReport::GetLogicalAddress( -PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD_PTR& offset ) -{ - MEMORY_BASIC_INFORMATION mbi; - - if ( !VirtualQuery( addr, &mbi, sizeof(mbi) ) ) - return FALSE; - - DWORD_PTR hMod = (DWORD_PTR)mbi.AllocationBase; - - if ( !GetModuleFileName( (HMODULE)hMod, szModule, len ) ) - return FALSE; - - // Point to the DOS header in memory - PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod; - - // From the DOS header, find the NT (PE) header - PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + DWORD_PTR(pDosHdr->e_lfanew)); - - PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr ); - - DWORD_PTR rva = (DWORD_PTR)addr - hMod; // RVA is offset from module load address - - // Iterate through the section table, looking for the one that encompasses - // the linear address. - for ( unsigned i = 0; - i < pNtHdr->FileHeader.NumberOfSections; - i++, pSection++ ) - { - DWORD_PTR sectionStart = pSection->VirtualAddress; - DWORD_PTR sectionEnd = sectionStart - + DWORD_PTR(max(pSection->SizeOfRawData, pSection->Misc.VirtualSize)); - - // Is the address in this section??? - if ( (rva >= sectionStart) && (rva <= sectionEnd) ) - { - // Yes, address is in the section. Calculate section and offset, - // and store in the "section" & "offset" params, which were - // passed by reference. - section = i+1; - offset = rva - sectionStart; - return TRUE; - } - } - - return FALSE; // Should never get here! -} - -// It contains SYMBOL_INFO structure plus additional -// space for the name of the symbol -struct CSymbolInfoPackage : public SYMBOL_INFO_PACKAGE -{ - CSymbolInfoPackage() - { - si.SizeOfStruct = sizeof(SYMBOL_INFO); - si.MaxNameLen = sizeof(name); - } -}; - -//============================================================ -// Walks the stack, and writes the results to the report file -//============================================================ -void WheatyExceptionReport::WriteStackDetails( -PCONTEXT pContext, -bool bWriteVariables ) // true if local/params should be output -{ - _tprintf( _T("\r\nCall stack:\r\n") ); - - _tprintf( _T("Address Frame Function SourceFile\r\n") ); - - DWORD dwMachineType = 0; - // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag - - STACKFRAME64 sf; - memset( &sf, 0, sizeof(sf) ); - - #ifdef _M_IX86 - // Initialize the STACKFRAME structure for the first call. This is only - // necessary for Intel CPUs, and isn't mentioned in the documentation. - sf.AddrPC.Offset = pContext->Eip; - sf.AddrPC.Mode = AddrModeFlat; - sf.AddrStack.Offset = pContext->Esp; - sf.AddrStack.Mode = AddrModeFlat; - sf.AddrFrame.Offset = pContext->Ebp; - sf.AddrFrame.Mode = AddrModeFlat; - - dwMachineType = IMAGE_FILE_MACHINE_I386; - #endif - -#ifdef _M_X64 - sf.AddrPC.Offset = pContext->Rip; - sf.AddrPC.Mode = AddrModeFlat; - sf.AddrStack.Offset = pContext->Rsp; - sf.AddrStack.Mode = AddrModeFlat; - sf.AddrFrame.Offset = pContext->Rbp; - sf.AddrFrame.Mode = AddrModeFlat; - dwMachineType = IMAGE_FILE_MACHINE_AMD64; -#endif - - while ( 1 ) - { - // Get the next stack frame - if ( ! StackWalk64( dwMachineType, - m_hProcess, - GetCurrentThread(), - &sf, - pContext, - 0, - SymFunctionTableAccess64, - SymGetModuleBase64, - 0 ) ) - break; - if ( 0 == sf.AddrFrame.Offset ) // Basic sanity check to make sure - break; // the frame is OK. Bail if not. -#ifdef _M_IX86 - _tprintf( _T("%08X %08X "), sf.AddrPC.Offset, sf.AddrFrame.Offset ); -#endif -#ifdef _M_X64 - _tprintf( _T("%016I64X %016I64X "), sf.AddrPC.Offset, sf.AddrFrame.Offset ); -#endif - - DWORD64 symDisplacement = 0; // Displacement of the input address, - // relative to the start of the symbol - - // Get the name of the function for this stack frame entry - CSymbolInfoPackage sip; - if ( SymFromAddr( - m_hProcess, // Process handle of the current process - sf.AddrPC.Offset, // Symbol address - &symDisplacement, // Address of the variable that will receive the displacement - &sip.si // Address of the SYMBOL_INFO structure (inside "sip" object) - )) - { - _tprintf( _T("%hs+%I64X"), sip.si.Name, symDisplacement ); - - } - else // No symbol found. Print out the logical address instead. - { - TCHAR szModule[MAX_PATH] = _T(""); - DWORD section = 0; - DWORD_PTR offset = 0; - - GetLogicalAddress( (PVOID)sf.AddrPC.Offset, - szModule, sizeof(szModule), section, offset ); -#ifdef _M_IX86 - _tprintf( _T("%04X:%08X %s"), section, offset, szModule ); -#endif -#ifdef _M_X64 - _tprintf( _T("%04X:%016I64X %s"), section, offset, szModule ); -#endif - } - - // Get the source line for this stack frame entry - IMAGEHLP_LINE64 lineInfo = { sizeof(IMAGEHLP_LINE) }; - DWORD dwLineDisplacement; - if ( SymGetLineFromAddr64( m_hProcess, sf.AddrPC.Offset, - &dwLineDisplacement, &lineInfo ) ) - { - _tprintf(_T(" %s line %u"),lineInfo.FileName,lineInfo.LineNumber); - } - - _tprintf( _T("\r\n") ); - - // Write out the variables, if desired - if ( bWriteVariables ) - { - // Use SymSetContext to get just the locals/params for this frame - IMAGEHLP_STACK_FRAME imagehlpStackFrame; - imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset; - SymSetContext( m_hProcess, &imagehlpStackFrame, 0 ); - - // Enumerate the locals/parameters - SymEnumSymbols( m_hProcess, 0, 0, EnumerateSymbolsCallback, &sf ); - - _tprintf( _T("\r\n") ); - } - } - -} - -////////////////////////////////////////////////////////////////////////////// -// The function invoked by SymEnumSymbols -////////////////////////////////////////////////////////////////////////////// - -BOOL CALLBACK -WheatyExceptionReport::EnumerateSymbolsCallback( -PSYMBOL_INFO pSymInfo, -ULONG SymbolSize, -PVOID UserContext ) -{ - - char szBuffer[2048]; - - __try - { - if ( FormatSymbolValue( pSymInfo, (STACKFRAME*)UserContext, - szBuffer, sizeof(szBuffer) ) ) - _tprintf( _T("\t%s\r\n"), szBuffer ); - } - __except( 1 ) - { - _tprintf( _T("punting on symbol %s\r\n"), pSymInfo->Name ); - } - - return TRUE; -} - -////////////////////////////////////////////////////////////////////////////// -// Given a SYMBOL_INFO representing a particular variable, displays its -// contents. If it's a user defined type, display the members and their -// values. -////////////////////////////////////////////////////////////////////////////// -bool WheatyExceptionReport::FormatSymbolValue( -PSYMBOL_INFO pSym, -STACKFRAME * sf, -char * pszBuffer, -unsigned cbBuffer ) -{ - char * pszCurrBuffer = pszBuffer; - - // Indicate if the variable is a local or parameter - if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER ) - pszCurrBuffer += sprintf( pszCurrBuffer, "Parameter " ); - else if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_LOCAL ) - pszCurrBuffer += sprintf( pszCurrBuffer, "Local " ); - - // If it's a function, don't do anything. - if ( pSym->Tag == 5 ) // SymTagFunction from CVCONST.H from the DIA SDK - return false; - - DWORD_PTR pVariable = 0; // Will point to the variable's data in memory - - if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE ) - { - // if ( pSym->Register == 8 ) // EBP is the value 8 (in DBGHELP 5.1) - { // This may change!!! - pVariable = sf->AddrFrame.Offset; - pVariable += (DWORD_PTR)pSym->Address; - } - // else - // return false; - } - else if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGISTER ) - { - return false; // Don't try to report register variable - } - else - { - pVariable = (DWORD_PTR)pSym->Address; // It must be a global variable - } - - // Determine if the variable is a user defined type (UDT). IF so, bHandled - // will return true. - bool bHandled; - pszCurrBuffer = DumpTypeIndex(pszCurrBuffer,pSym->ModBase, pSym->TypeIndex, - 0, pVariable, bHandled, pSym->Name ); - - if ( !bHandled ) - { - // The symbol wasn't a UDT, so do basic, stupid formatting of the - // variable. Based on the size, we're assuming it's a char, WORD, or - // DWORD. - BasicType basicType = GetBasicType( pSym->TypeIndex, pSym->ModBase ); - pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]); - - // Emit the variable name - pszCurrBuffer += sprintf( pszCurrBuffer, "\'%s\'", pSym->Name ); - - pszCurrBuffer = FormatOutputValue(pszCurrBuffer, basicType, pSym->Size, - (PVOID)pVariable ); - } - - return true; -} - -////////////////////////////////////////////////////////////////////////////// -// If it's a user defined type (UDT), recurse through its members until we're -// at fundamental types. When he hit fundamental types, return -// bHandled = false, so that FormatSymbolValue() will format them. -////////////////////////////////////////////////////////////////////////////// -char * WheatyExceptionReport::DumpTypeIndex( -char * pszCurrBuffer, -DWORD64 modBase, -DWORD dwTypeIndex, -unsigned nestingLevel, -DWORD_PTR offset, -bool & bHandled, -char* Name) -{ - bHandled = false; - - // Get the name of the symbol. This will either be a Type name (if a UDT), - // or the structure member name. - WCHAR * pwszTypeName; - if ( SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_GET_SYMNAME, - &pwszTypeName ) ) - { - pszCurrBuffer += sprintf( pszCurrBuffer, " %ls", pwszTypeName ); - LocalFree( pwszTypeName ); - } - - // Determine how many children this type has. - DWORD dwChildrenCount = 0; - SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_GET_CHILDRENCOUNT, - &dwChildrenCount ); - - if ( !dwChildrenCount ) // If no children, we're done - return pszCurrBuffer; - - // Prepare to get an array of "TypeIds", representing each of the children. - // SymGetTypeInfo(TI_FINDCHILDREN) expects more memory than just a - // TI_FINDCHILDREN_PARAMS struct has. Use derivation to accomplish this. - struct FINDCHILDREN : TI_FINDCHILDREN_PARAMS - { - ULONG MoreChildIds[1024]; - FINDCHILDREN(){Count = sizeof(MoreChildIds) / sizeof(MoreChildIds[0]);} - } children; - - children.Count = dwChildrenCount; - children.Start= 0; - - // Get the array of TypeIds, one for each child type - if ( !SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_FINDCHILDREN, - &children ) ) - { - return pszCurrBuffer; - } - - // Append a line feed - pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" ); - - // Iterate through each of the children - for ( unsigned i = 0; i < dwChildrenCount; i++ ) - { - // Add appropriate indentation level (since this routine is recursive) - for ( unsigned j = 0; j <= nestingLevel+1; j++ ) - pszCurrBuffer += sprintf( pszCurrBuffer, "\t" ); - - // Recurse for each of the child types - bool bHandled2; - BasicType basicType = GetBasicType(children.ChildId[i], modBase ); - pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]); - - pszCurrBuffer = DumpTypeIndex( pszCurrBuffer, modBase, - children.ChildId[i], nestingLevel+1, - offset, bHandled2, ""/*Name */); - - // If the child wasn't a UDT, format it appropriately - if ( !bHandled2 ) - { - // Get the offset of the child member, relative to its parent - DWORD dwMemberOffset; - SymGetTypeInfo( m_hProcess, modBase, children.ChildId[i], - TI_GET_OFFSET, &dwMemberOffset ); - - // Get the real "TypeId" of the child. We need this for the - // SymGetTypeInfo( TI_GET_TYPEID ) call below. - DWORD typeId; - SymGetTypeInfo( m_hProcess, modBase, children.ChildId[i], - TI_GET_TYPEID, &typeId ); - - // Get the size of the child member - ULONG64 length; - SymGetTypeInfo(m_hProcess, modBase, typeId, TI_GET_LENGTH,&length); - - // Calculate the address of the member - DWORD_PTR dwFinalOffset = offset + dwMemberOffset; - - // BasicType basicType = GetBasicType(children.ChildId[i], modBase ); - // - // pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]); - // - // Emit the variable name - // pszCurrBuffer += sprintf( pszCurrBuffer, "\'%s\'", Name ); - - pszCurrBuffer = FormatOutputValue( pszCurrBuffer, basicType, - length, (PVOID)dwFinalOffset ); - - pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" ); - } - } - - bHandled = true; - return pszCurrBuffer; -} - -char * WheatyExceptionReport::FormatOutputValue( char * pszCurrBuffer, -BasicType basicType, -DWORD64 length, -PVOID pAddress ) -{ - // Format appropriately (assuming it's a 1, 2, or 4 bytes (!!!) - if ( length == 1 ) - pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", *(PBYTE)pAddress ); - else if ( length == 2 ) - pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", *(PWORD)pAddress ); - else if ( length == 4 ) - { - if ( basicType == btFloat ) - { - pszCurrBuffer += sprintf(pszCurrBuffer," = %f", *(PFLOAT)pAddress); - } - else if ( basicType == btChar ) - { - if ( !IsBadStringPtr( *(PSTR*)pAddress, 32) ) - { - pszCurrBuffer += sprintf( pszCurrBuffer, " = \"%.31s\"", - *(PDWORD)pAddress ); - } - else - pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", - *(PDWORD)pAddress ); - } - else - pszCurrBuffer += sprintf(pszCurrBuffer," = %X", *(PDWORD)pAddress); - } - else if ( length == 8 ) - { - if ( basicType == btFloat ) - { - pszCurrBuffer += sprintf( pszCurrBuffer, " = %lf", - *(double *)pAddress ); - } - else - pszCurrBuffer += sprintf( pszCurrBuffer, " = %I64X", - *(DWORD64*)pAddress ); - } - - return pszCurrBuffer; -} - -BasicType -WheatyExceptionReport::GetBasicType( DWORD typeIndex, DWORD64 modBase ) -{ - BasicType basicType; - if ( SymGetTypeInfo( m_hProcess, modBase, typeIndex, - TI_GET_BASETYPE, &basicType ) ) - { - return basicType; - } - - // Get the real "TypeId" of the child. We need this for the - // SymGetTypeInfo( TI_GET_TYPEID ) call below. - DWORD typeId; - if (SymGetTypeInfo(m_hProcess,modBase, typeIndex, TI_GET_TYPEID, &typeId)) - { - if ( SymGetTypeInfo( m_hProcess, modBase, typeId, TI_GET_BASETYPE, - &basicType ) ) - { - return basicType; - } - } - - return btNoType; -} - -//============================================================================ -// Helper function that writes to the report file, and allows the user to use -// printf style formating -//============================================================================ -int __cdecl WheatyExceptionReport::_tprintf(const TCHAR * format, ...) -{ - TCHAR szBuff[1024]; - int retValue; - DWORD cbWritten; - va_list argptr; - - va_start( argptr, format ); - retValue = vsprintf( szBuff, format, argptr ); - va_end( argptr ); - - WriteFile(m_hReportFile, szBuff, retValue * sizeof(TCHAR), &cbWritten, 0 ); - - return retValue; -} +//========================================== +// Matt Pietrek +// MSDN Magazine, 2002 +// FILE: WheatyExceptionReport.CPP +//========================================== +#define WIN32_LEAN_AND_MEAN +#pragma warning(disable:4996) +#pragma warning(disable:4312) +#pragma warning(disable:4311) +#include +#include +#include +#include +#define _NO_CVCONST_H +#include +#include "WheatyExceptionReport.h" +#include "svn_revision.h" +#define CrashFolder _T("Crashs") +//#pragma comment(linker, "/defaultlib:dbghelp.lib") + +inline LPTSTR ErrorMessage(DWORD dw) +{ + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + return (LPTSTR)lpMsgBuf; +} + +//============================== Global Variables ============================= + +// +// Declare the static variables of the WheatyExceptionReport class +// +TCHAR WheatyExceptionReport::m_szLogFileName[MAX_PATH]; +LPTOP_LEVEL_EXCEPTION_FILTER WheatyExceptionReport::m_previousFilter; +HANDLE WheatyExceptionReport::m_hReportFile; +HANDLE WheatyExceptionReport::m_hProcess; + +// Declare global instance of class +WheatyExceptionReport g_WheatyExceptionReport; + +//============================== Class Methods ============================= + +WheatyExceptionReport::WheatyExceptionReport( ) // Constructor +{ + // Install the unhandled exception filter function + m_previousFilter = SetUnhandledExceptionFilter(WheatyUnhandledExceptionFilter); + m_hProcess = GetCurrentProcess(); +} + +//============ +// Destructor +//============ +WheatyExceptionReport::~WheatyExceptionReport( ) +{ + if(m_previousFilter) + SetUnhandledExceptionFilter( m_previousFilter ); +} + +//=========================================================== +// Entry point where control comes on an unhandled exception +//=========================================================== +LONG WINAPI WheatyExceptionReport::WheatyUnhandledExceptionFilter( +PEXCEPTION_POINTERS pExceptionInfo ) +{ + TCHAR module_folder_name[MAX_PATH]; + GetModuleFileName( 0, module_folder_name, MAX_PATH ); + TCHAR* pos = _tcsrchr(module_folder_name, '\\'); + if(!pos) + return 0; + pos[0] = '\0'; + ++pos; + + TCHAR crash_folder_path[MAX_PATH]; + sprintf(crash_folder_path, "%s\\%s", module_folder_name, CrashFolder); + if(!CreateDirectory(crash_folder_path, NULL)) + { + if(GetLastError() != ERROR_ALREADY_EXISTS) + return 0; + } + + SYSTEMTIME systime; + GetLocalTime(&systime); + sprintf(m_szLogFileName, "%s\\%s_[%u-%u_%u-%u-%u].txt", + crash_folder_path, pos, systime.wDay, systime.wMonth, systime.wHour, systime.wMinute, systime.wSecond + ); + + m_hReportFile = CreateFile( m_szLogFileName, + GENERIC_WRITE, + 0, + 0, + OPEN_ALWAYS, + FILE_FLAG_WRITE_THROUGH, + 0 ); + + if ( m_hReportFile ) + { + SetFilePointer( m_hReportFile, 0, 0, FILE_END ); + + GenerateExceptionReport( pExceptionInfo ); + + CloseHandle( m_hReportFile ); + m_hReportFile = 0; + } + + if ( m_previousFilter ) + return m_previousFilter( pExceptionInfo ); + else + return EXCEPTION_EXECUTE_HANDLER/*EXCEPTION_CONTINUE_SEARCH*/; +} + +BOOL WheatyExceptionReport::_GetProcessorName(TCHAR* sProcessorName, DWORD maxcount) +{ + if(!sProcessorName) + return FALSE; + + HKEY hKey; + LONG lRet; + lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), + 0, KEY_QUERY_VALUE, &hKey); + if (lRet != ERROR_SUCCESS) + return FALSE; + TCHAR szTmp[2048]; + DWORD cntBytes = sizeof(szTmp); + lRet = ::RegQueryValueEx(hKey, _T("ProcessorNameString"), NULL, NULL, + (LPBYTE)szTmp, &cntBytes); + if (lRet != ERROR_SUCCESS) + return FALSE; + ::RegCloseKey(hKey); + sProcessorName[0] = '\0'; + // Skip spaces + TCHAR* psz = szTmp; + while (iswspace(*psz)) + ++psz; + _tcsncpy(sProcessorName, psz, maxcount); + return TRUE; +} + +BOOL WheatyExceptionReport::_GetWindowsVersion(TCHAR* szVersion, DWORD cntMax) +{ + // Try calling GetVersionEx using the OSVERSIONINFOEX structure. + // If that fails, try using the OSVERSIONINFO structure. + OSVERSIONINFOEX osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + BOOL bOsVersionInfoEx; + bOsVersionInfoEx = ::GetVersionEx((LPOSVERSIONINFO)(&osvi)); + if (!bOsVersionInfoEx) + { + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if (!::GetVersionEx((OSVERSIONINFO*)&osvi)) + return FALSE; + } + *szVersion = _T('\0'); + TCHAR wszTmp[128]; + switch (osvi.dwPlatformId) + { + // Windows NT product family. + case VER_PLATFORM_WIN32_NT: + // Test for the specific product family. + if (osvi.dwMajorVersion == 6) + _tcsncat(szVersion, _T("Windows Vista or Windows Server 2008 "), cntMax); + if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) + _tcsncat(szVersion, _T("Microsoft Windows Server 2003 "), cntMax); + if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) + _tcsncat(szVersion, _T("Microsoft Windows XP "), cntMax); + if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) + _tcsncat(szVersion, _T("Microsoft Windows 2000 "), cntMax); + if (osvi.dwMajorVersion <= 4 ) + _tcsncat(szVersion, _T("Microsoft Windows NT "), cntMax); + + // Test for specific product on Windows NT 4.0 SP6 and later. + if (bOsVersionInfoEx) + { + // Test for the workstation type. + #if WINVER < 0x0500 + if (osvi.wReserved[1] == VER_NT_WORKSTATION) + #else + if (osvi.wProductType == VER_NT_WORKSTATION) + #endif // WINVER < 0x0500 + { + if (osvi.dwMajorVersion == 4) + _tcsncat(szVersion, _T("Workstation 4.0 "), cntMax); + #if WINVER < 0x0500 + else if (osvi.wReserved[0] & VER_SUITE_PERSONAL) + #else + else if (osvi.wSuiteMask & VER_SUITE_PERSONAL) + #endif // WINVER < 0x0500 + _tcsncat(szVersion, _T("Home Edition "), cntMax); + #if WINVER < 0x0500 + else if (osvi.wReserved[0] & VER_SUITE_EMBEDDEDNT) + #else + else if (osvi.wSuiteMask & VER_SUITE_EMBEDDEDNT) + #endif // WINVER < 0x0500 + _tcsncat(szVersion, _T("Embedded "), cntMax); + else + _tcsncat(szVersion, _T("Professional "), cntMax); + } + // Test for the server type. + #if WINVER < 0x0500 + else if (osvi.wReserved[1] == VER_NT_SERVER) + #else + else if (osvi.wProductType == VER_NT_SERVER) + #endif // WINVER < 0x0500 + { + if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) + { + #if WINVER < 0x0500 + if (osvi.wReserved[0] & VER_SUITE_DATACENTER) + #else + if (osvi.wSuiteMask & VER_SUITE_DATACENTER) + #endif // WINVER < 0x0500 + _tcsncat(szVersion, _T("Datacenter Edition "), cntMax); + #if WINVER < 0x0500 + else if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE) + #else + else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + #endif // WINVER < 0x0500 + _tcsncat(szVersion, _T("Enterprise Edition "), cntMax); + #if WINVER < 0x0500 + else if (osvi.wReserved[0] == VER_SUITE_BLADE) + #else + else if (osvi.wSuiteMask == VER_SUITE_BLADE) + #endif // WINVER < 0x0500 + _tcsncat(szVersion, _T("Web Edition "), cntMax); + else + _tcsncat(szVersion, _T("Standard Edition "), cntMax); + } + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) + { + #if WINVER < 0x0500 + if (osvi.wReserved[0] & VER_SUITE_DATACENTER) + #else + if (osvi.wSuiteMask & VER_SUITE_DATACENTER) + #endif // WINVER < 0x0500 + _tcsncat(szVersion, _T("Datacenter Server "), cntMax); + #if WINVER < 0x0500 + else if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE ) + #else + else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE ) + #endif // WINVER < 0x0500 + _tcsncat(szVersion, _T("Advanced Server "), cntMax); + else + _tcsncat(szVersion, _T("Server "), cntMax); + } + else // Windows NT 4.0 + { + #if WINVER < 0x0500 + if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE) + #else + if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + #endif // WINVER < 0x0500 + _tcsncat(szVersion, _T("Server 4.0, Enterprise Edition "), cntMax); + else + _tcsncat(szVersion, _T("Server 4.0 "), cntMax); + } + } + } + // Display service pack (if any) and build number. + if (osvi.dwMajorVersion == 4 && _tcsicmp(osvi.szCSDVersion, _T("Service Pack 6")) == 0) + { + HKEY hKey; + LONG lRet; + + // Test for SP6 versus SP6a. + lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009"), 0, KEY_QUERY_VALUE, &hKey); + if (lRet == ERROR_SUCCESS) + { + _stprintf(wszTmp, _T("Service Pack 6a (Version %d.%d, Build %d)"), + osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + _tcsncat(szVersion, wszTmp, cntMax); + } + else // Windows NT 4.0 prior to SP6a + { + _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), + osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + _tcsncat(szVersion, wszTmp, cntMax); + } + ::RegCloseKey(hKey); + } + else // Windows NT 3.51 and earlier or Windows 2000 and later + { + if (!_tcslen(osvi.szCSDVersion)) + _stprintf(wszTmp, _T("(Version %d.%d, Build %d)"), + osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + else + _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), + osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + _tcsncat(szVersion, wszTmp, cntMax); + } + break; + default: + _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), + osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + _tcsncat(szVersion, wszTmp, cntMax); + break; + } + + return TRUE; +} + +void WheatyExceptionReport::PrintSystemInfo() +{ + SYSTEM_INFO SystemInfo; + ::GetSystemInfo(&SystemInfo); + + MEMORYSTATUS MemoryStatus; + MemoryStatus.dwLength = sizeof (MEMORYSTATUS); + ::GlobalMemoryStatus(&MemoryStatus); + TCHAR sString[1024]; + _tprintf(_T("//=====================================================\r\n")); + if (_GetProcessorName(sString, countof(sString))) + _tprintf(_T("*** Hardware ***\r\nProcessor: %s\r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"), + sString, SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400); + else + _tprintf(_T("*** Hardware ***\r\nProcessor: \r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"), + SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400); + + if(_GetWindowsVersion(sString, countof(sString))) + _tprintf(_T("\r\n*** Operation System ***\r\n%s\r\n"), sString); + else + _tprintf(_T("\r\n*** Operation System:\r\n\r\n")); +} + +//=========================================================================== +void WheatyExceptionReport::printTracesForAllThreads() +{ + HANDLE hThreadSnap = INVALID_HANDLE_VALUE; + THREADENTRY32 te32; + + DWORD dwOwnerPID = GetCurrentProcessId(); + m_hProcess = GetCurrentProcess(); + // Take a snapshot of all running threads + hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); + if( hThreadSnap == INVALID_HANDLE_VALUE ) + return; + + // Fill in the size of the structure before using it. + te32.dwSize = sizeof(THREADENTRY32 ); + + // Retrieve information about the first thread, + // and exit if unsuccessful + if( !Thread32First( hThreadSnap, &te32 ) ) + { + CloseHandle( hThreadSnap ); // Must clean up the + // snapshot object! + return; + } + + // Now walk the thread list of the system, + // and display information about each thread + // associated with the specified process + do + { + if( te32.th32OwnerProcessID == dwOwnerPID ) + { + CONTEXT context; + context.ContextFlags = 0xffffffff; + HANDLE threadHandle = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION,false, te32.th32ThreadID); + if(threadHandle && GetThreadContext(threadHandle, &context)) + { + WriteStackDetails( &context, false, threadHandle ); + } + CloseHandle(threadHandle); + } + } while( Thread32Next(hThreadSnap, &te32 ) ); + +// Don't forget to clean up the snapshot object. + CloseHandle( hThreadSnap ); +} + + +//=========================================================================== +// Open the report file, and write the desired information to it. Called by +// WheatyUnhandledExceptionFilter +//=========================================================================== +void WheatyExceptionReport::GenerateExceptionReport( +PEXCEPTION_POINTERS pExceptionInfo ) +{ + SYSTEMTIME systime; + GetLocalTime(&systime); + + // Start out with a banner + _tprintf(_T("Revision: %s\r\n"), SVN_REVISION); + _tprintf(_T("Date %u:%u:%u. Time %u:%u \r\n"), systime.wDay, systime.wMonth, systime.wYear, systime.wHour, systime.wMinute); + PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; + + PrintSystemInfo(); + // First print information about the type of fault + _tprintf(_T("\r\n//=====================================================\r\n")); + _tprintf( _T("Exception code: %08X %s\r\n"), + pExceptionRecord->ExceptionCode, + GetExceptionString(pExceptionRecord->ExceptionCode) ); + + // Now print information about where the fault occured + TCHAR szFaultingModule[MAX_PATH]; + DWORD section; + DWORD_PTR offset; + GetLogicalAddress( pExceptionRecord->ExceptionAddress, + szFaultingModule, + sizeof( szFaultingModule ), + section, offset ); + +#ifdef _M_IX86 + _tprintf( _T("Fault address: %08X %02X:%08X %s\r\n"), + pExceptionRecord->ExceptionAddress, + section, offset, szFaultingModule ); +#endif +#ifdef _M_X64 + _tprintf( _T("Fault address: %016I64X %02X:%016I64X %s\r\n"), + pExceptionRecord->ExceptionAddress, + section, offset, szFaultingModule ); +#endif + + PCONTEXT pCtx = pExceptionInfo->ContextRecord; + + // Show the registers + #ifdef _M_IX86 // X86 Only! + _tprintf( _T("\r\nRegisters:\r\n") ); + + _tprintf(_T("EAX:%08X\r\nEBX:%08X\r\nECX:%08X\r\nEDX:%08X\r\nESI:%08X\r\nEDI:%08X\r\n") + ,pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, + pCtx->Esi, pCtx->Edi ); + + _tprintf( _T("CS:EIP:%04X:%08X\r\n"), pCtx->SegCs, pCtx->Eip ); + _tprintf( _T("SS:ESP:%04X:%08X EBP:%08X\r\n"), + pCtx->SegSs, pCtx->Esp, pCtx->Ebp ); + _tprintf( _T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), + pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs ); + _tprintf( _T("Flags:%08X\r\n"), pCtx->EFlags ); + #endif + + #ifdef _M_X64 + _tprintf( _T("\r\nRegisters:\r\n") ); + _tprintf(_T("RAX:%016I64X\r\nRBX:%016I64X\r\nRCX:%016I64X\r\nRDX:%016I64X\r\nRSI:%016I64X\r\nRDI:%016I64X\r\n") + _T("R8: %016I64X\r\nR9: %016I64X\r\nR10:%016I64X\r\nR11:%016I64X\r\nR12:%016I64X\r\nR13:%016I64X\r\nR14:%016I64X\r\nR15:%016I64X\r\n") + ,pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx, + pCtx->Rsi, pCtx->Rdi ,pCtx->R9,pCtx->R10,pCtx->R11,pCtx->R12,pCtx->R13,pCtx->R14,pCtx->R15); + _tprintf( _T("CS:RIP:%04X:%016I64X\r\n"), pCtx->SegCs, pCtx->Rip ); + _tprintf( _T("SS:RSP:%04X:%016X RBP:%08X\r\n"), + pCtx->SegSs, pCtx->Rsp, pCtx->Rbp ); + _tprintf( _T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), + pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs ); + _tprintf( _T("Flags:%08X\r\n"), pCtx->EFlags ); + #endif + + SymSetOptions( SYMOPT_DEFERRED_LOADS ); + + // Initialize DbgHelp + if ( !SymInitialize( GetCurrentProcess(), 0, TRUE ) ) + { + _tprintf(_T("\n\rCRITICAL ERROR.\n\r Couldn't initialize the symbol handler for process.\n\rError [%s].\n\r\n\r"), + ErrorMessage(GetLastError())); + } + + CONTEXT trashableContext = *pCtx; + + WriteStackDetails( &trashableContext, false, NULL ); + printTracesForAllThreads(); + +// #ifdef _M_IX86 // X86 Only! + + _tprintf( _T("========================\r\n") ); + _tprintf( _T("Local Variables And Parameters\r\n") ); + + trashableContext = *pCtx; + WriteStackDetails( &trashableContext, true, NULL ); + + _tprintf( _T("========================\r\n") ); + _tprintf( _T("Global Variables\r\n") ); + + SymEnumSymbols( GetCurrentProcess(), + (DWORD64)GetModuleHandle(szFaultingModule), + 0, EnumerateSymbolsCallback, 0 ); + // #endif // X86 Only! + + SymCleanup( GetCurrentProcess() ); + + _tprintf( _T("\r\n") ); +} + +//====================================================================== +// Given an exception code, returns a pointer to a static string with a +// description of the exception +//====================================================================== +LPTSTR WheatyExceptionReport::GetExceptionString( DWORD dwCode ) +{ + #define EXCEPTION( x ) case EXCEPTION_##x: return _T(#x); + + switch ( dwCode ) + { + EXCEPTION( ACCESS_VIOLATION ) + EXCEPTION( DATATYPE_MISALIGNMENT ) + EXCEPTION( BREAKPOINT ) + EXCEPTION( SINGLE_STEP ) + EXCEPTION( ARRAY_BOUNDS_EXCEEDED ) + EXCEPTION( FLT_DENORMAL_OPERAND ) + EXCEPTION( FLT_DIVIDE_BY_ZERO ) + EXCEPTION( FLT_INEXACT_RESULT ) + EXCEPTION( FLT_INVALID_OPERATION ) + EXCEPTION( FLT_OVERFLOW ) + EXCEPTION( FLT_STACK_CHECK ) + EXCEPTION( FLT_UNDERFLOW ) + EXCEPTION( INT_DIVIDE_BY_ZERO ) + EXCEPTION( INT_OVERFLOW ) + EXCEPTION( PRIV_INSTRUCTION ) + EXCEPTION( IN_PAGE_ERROR ) + EXCEPTION( ILLEGAL_INSTRUCTION ) + EXCEPTION( NONCONTINUABLE_EXCEPTION ) + EXCEPTION( STACK_OVERFLOW ) + EXCEPTION( INVALID_DISPOSITION ) + EXCEPTION( GUARD_PAGE ) + EXCEPTION( INVALID_HANDLE ) + } + + // If not one of the "known" exceptions, try to get the string + // from NTDLL.DLL's message table. + + static TCHAR szBuffer[512] = { 0 }; + + FormatMessage( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + GetModuleHandle( _T("NTDLL.DLL") ), + dwCode, 0, szBuffer, sizeof( szBuffer ), 0 ); + + return szBuffer; +} + +//============================================================================= +// Given a linear address, locates the module, section, and offset containing +// that address. +// +// Note: the szModule paramater buffer is an output buffer of length specified +// by the len parameter (in characters!) +//============================================================================= +BOOL WheatyExceptionReport::GetLogicalAddress( +PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD_PTR& offset ) +{ + MEMORY_BASIC_INFORMATION mbi; + + if ( !VirtualQuery( addr, &mbi, sizeof(mbi) ) ) + return FALSE; + + DWORD_PTR hMod = (DWORD_PTR)mbi.AllocationBase; + + if ( !GetModuleFileName( (HMODULE)hMod, szModule, len ) ) + return FALSE; + + // Point to the DOS header in memory + PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod; + + // From the DOS header, find the NT (PE) header + PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + DWORD_PTR(pDosHdr->e_lfanew)); + + PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr ); + + DWORD_PTR rva = (DWORD_PTR)addr - hMod; // RVA is offset from module load address + + // Iterate through the section table, looking for the one that encompasses + // the linear address. + for ( unsigned i = 0; + i < pNtHdr->FileHeader.NumberOfSections; + i++, pSection++ ) + { + DWORD_PTR sectionStart = pSection->VirtualAddress; + DWORD_PTR sectionEnd = sectionStart + + DWORD_PTR(max(pSection->SizeOfRawData, pSection->Misc.VirtualSize)); + + // Is the address in this section??? + if ( (rva >= sectionStart) && (rva <= sectionEnd) ) + { + // Yes, address is in the section. Calculate section and offset, + // and store in the "section" & "offset" params, which were + // passed by reference. + section = i+1; + offset = rva - sectionStart; + return TRUE; + } + } + + return FALSE; // Should never get here! +} + +// It contains SYMBOL_INFO structure plus additional +// space for the name of the symbol +struct CSymbolInfoPackage : public SYMBOL_INFO_PACKAGE +{ + CSymbolInfoPackage() + { + si.SizeOfStruct = sizeof(SYMBOL_INFO); + si.MaxNameLen = sizeof(name); + } +}; + +//============================================================ +// Walks the stack, and writes the results to the report file +//============================================================ +void WheatyExceptionReport::WriteStackDetails( +PCONTEXT pContext, +bool bWriteVariables, HANDLE pThreadHandle) // true if local/params should be output +{ + _tprintf( _T("\r\nCall stack:\r\n") ); + + _tprintf( _T("Address Frame Function SourceFile\r\n") ); + + DWORD dwMachineType = 0; + // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag + + STACKFRAME64 sf; + memset( &sf, 0, sizeof(sf) ); + + #ifdef _M_IX86 + // Initialize the STACKFRAME structure for the first call. This is only + // necessary for Intel CPUs, and isn't mentioned in the documentation. + sf.AddrPC.Offset = pContext->Eip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = pContext->Esp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = pContext->Ebp; + sf.AddrFrame.Mode = AddrModeFlat; + + dwMachineType = IMAGE_FILE_MACHINE_I386; + #endif + +#ifdef _M_X64 + sf.AddrPC.Offset = pContext->Rip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = pContext->Rsp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = pContext->Rbp; + sf.AddrFrame.Mode = AddrModeFlat; + dwMachineType = IMAGE_FILE_MACHINE_AMD64; +#endif + + while ( 1 ) + { + // Get the next stack frame + if ( ! StackWalk64( dwMachineType, + m_hProcess, + pThreadHandle != NULL ? pThreadHandle : GetCurrentThread(), + &sf, + pContext, + 0, + SymFunctionTableAccess64, + SymGetModuleBase64, + 0 ) ) + break; + if ( 0 == sf.AddrFrame.Offset ) // Basic sanity check to make sure + break; // the frame is OK. Bail if not. +#ifdef _M_IX86 + _tprintf( _T("%08X %08X "), sf.AddrPC.Offset, sf.AddrFrame.Offset ); +#endif +#ifdef _M_X64 + _tprintf( _T("%016I64X %016I64X "), sf.AddrPC.Offset, sf.AddrFrame.Offset ); +#endif + + DWORD64 symDisplacement = 0; // Displacement of the input address, + // relative to the start of the symbol + + // Get the name of the function for this stack frame entry + CSymbolInfoPackage sip; + if ( SymFromAddr( + m_hProcess, // Process handle of the current process + sf.AddrPC.Offset, // Symbol address + &symDisplacement, // Address of the variable that will receive the displacement + &sip.si // Address of the SYMBOL_INFO structure (inside "sip" object) + )) + { + _tprintf( _T("%hs+%I64X"), sip.si.Name, symDisplacement ); + + } + else // No symbol found. Print out the logical address instead. + { + TCHAR szModule[MAX_PATH] = _T(""); + DWORD section = 0; + DWORD_PTR offset = 0; + + GetLogicalAddress( (PVOID)sf.AddrPC.Offset, + szModule, sizeof(szModule), section, offset ); +#ifdef _M_IX86 + _tprintf( _T("%04X:%08X %s"), section, offset, szModule ); +#endif +#ifdef _M_X64 + _tprintf( _T("%04X:%016I64X %s"), section, offset, szModule ); +#endif + } + + // Get the source line for this stack frame entry + IMAGEHLP_LINE64 lineInfo = { sizeof(IMAGEHLP_LINE) }; + DWORD dwLineDisplacement; + if ( SymGetLineFromAddr64( m_hProcess, sf.AddrPC.Offset, + &dwLineDisplacement, &lineInfo ) ) + { + _tprintf(_T(" %s line %u"),lineInfo.FileName,lineInfo.LineNumber); + } + + _tprintf( _T("\r\n") ); + + // Write out the variables, if desired + if ( bWriteVariables ) + { + // Use SymSetContext to get just the locals/params for this frame + IMAGEHLP_STACK_FRAME imagehlpStackFrame; + imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset; + SymSetContext( m_hProcess, &imagehlpStackFrame, 0 ); + + // Enumerate the locals/parameters + SymEnumSymbols( m_hProcess, 0, 0, EnumerateSymbolsCallback, &sf ); + + _tprintf( _T("\r\n") ); + } + } + +} + +////////////////////////////////////////////////////////////////////////////// +// The function invoked by SymEnumSymbols +////////////////////////////////////////////////////////////////////////////// + +BOOL CALLBACK +WheatyExceptionReport::EnumerateSymbolsCallback( +PSYMBOL_INFO pSymInfo, +ULONG SymbolSize, +PVOID UserContext ) +{ + + char szBuffer[2048]; + + __try + { + if ( FormatSymbolValue( pSymInfo, (STACKFRAME*)UserContext, + szBuffer, sizeof(szBuffer) ) ) + _tprintf( _T("\t%s\r\n"), szBuffer ); + } + __except( 1 ) + { + _tprintf( _T("punting on symbol %s\r\n"), pSymInfo->Name ); + } + + return TRUE; +} + +////////////////////////////////////////////////////////////////////////////// +// Given a SYMBOL_INFO representing a particular variable, displays its +// contents. If it's a user defined type, display the members and their +// values. +////////////////////////////////////////////////////////////////////////////// +bool WheatyExceptionReport::FormatSymbolValue( +PSYMBOL_INFO pSym, +STACKFRAME * sf, +char * pszBuffer, +unsigned cbBuffer ) +{ + char * pszCurrBuffer = pszBuffer; + + // Indicate if the variable is a local or parameter + if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER ) + pszCurrBuffer += sprintf( pszCurrBuffer, "Parameter " ); + else if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_LOCAL ) + pszCurrBuffer += sprintf( pszCurrBuffer, "Local " ); + + // If it's a function, don't do anything. + if ( pSym->Tag == 5 ) // SymTagFunction from CVCONST.H from the DIA SDK + return false; + + DWORD_PTR pVariable = 0; // Will point to the variable's data in memory + + if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE ) + { + // if ( pSym->Register == 8 ) // EBP is the value 8 (in DBGHELP 5.1) + { // This may change!!! + pVariable = sf->AddrFrame.Offset; + pVariable += (DWORD_PTR)pSym->Address; + } + // else + // return false; + } + else if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGISTER ) + { + return false; // Don't try to report register variable + } + else + { + pVariable = (DWORD_PTR)pSym->Address; // It must be a global variable + } + + // Determine if the variable is a user defined type (UDT). IF so, bHandled + // will return true. + bool bHandled; + pszCurrBuffer = DumpTypeIndex(pszCurrBuffer,pSym->ModBase, pSym->TypeIndex, + 0, pVariable, bHandled, pSym->Name ); + + if ( !bHandled ) + { + // The symbol wasn't a UDT, so do basic, stupid formatting of the + // variable. Based on the size, we're assuming it's a char, WORD, or + // DWORD. + BasicType basicType = GetBasicType( pSym->TypeIndex, pSym->ModBase ); + pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]); + + // Emit the variable name + pszCurrBuffer += sprintf( pszCurrBuffer, "\'%s\'", pSym->Name ); + + pszCurrBuffer = FormatOutputValue(pszCurrBuffer, basicType, pSym->Size, + (PVOID)pVariable ); + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// If it's a user defined type (UDT), recurse through its members until we're +// at fundamental types. When he hit fundamental types, return +// bHandled = false, so that FormatSymbolValue() will format them. +////////////////////////////////////////////////////////////////////////////// +char * WheatyExceptionReport::DumpTypeIndex( +char * pszCurrBuffer, +DWORD64 modBase, +DWORD dwTypeIndex, +unsigned nestingLevel, +DWORD_PTR offset, +bool & bHandled, +char* Name) +{ + bHandled = false; + + // Get the name of the symbol. This will either be a Type name (if a UDT), + // or the structure member name. + WCHAR * pwszTypeName; + if ( SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_GET_SYMNAME, + &pwszTypeName ) ) + { + pszCurrBuffer += sprintf( pszCurrBuffer, " %ls", pwszTypeName ); + LocalFree( pwszTypeName ); + } + + // Determine how many children this type has. + DWORD dwChildrenCount = 0; + SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_GET_CHILDRENCOUNT, + &dwChildrenCount ); + + if ( !dwChildrenCount ) // If no children, we're done + return pszCurrBuffer; + + // Prepare to get an array of "TypeIds", representing each of the children. + // SymGetTypeInfo(TI_FINDCHILDREN) expects more memory than just a + // TI_FINDCHILDREN_PARAMS struct has. Use derivation to accomplish this. + struct FINDCHILDREN : TI_FINDCHILDREN_PARAMS + { + ULONG MoreChildIds[1024]; + FINDCHILDREN(){Count = sizeof(MoreChildIds) / sizeof(MoreChildIds[0]);} + } children; + + children.Count = dwChildrenCount; + children.Start= 0; + + // Get the array of TypeIds, one for each child type + if ( !SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_FINDCHILDREN, + &children ) ) + { + return pszCurrBuffer; + } + + // Append a line feed + pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" ); + + // Iterate through each of the children + for ( unsigned i = 0; i < dwChildrenCount; i++ ) + { + // Add appropriate indentation level (since this routine is recursive) + for ( unsigned j = 0; j <= nestingLevel+1; j++ ) + pszCurrBuffer += sprintf( pszCurrBuffer, "\t" ); + + // Recurse for each of the child types + bool bHandled2; + BasicType basicType = GetBasicType(children.ChildId[i], modBase ); + pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]); + + pszCurrBuffer = DumpTypeIndex( pszCurrBuffer, modBase, + children.ChildId[i], nestingLevel+1, + offset, bHandled2, ""/*Name */); + + // If the child wasn't a UDT, format it appropriately + if ( !bHandled2 ) + { + // Get the offset of the child member, relative to its parent + DWORD dwMemberOffset; + SymGetTypeInfo( m_hProcess, modBase, children.ChildId[i], + TI_GET_OFFSET, &dwMemberOffset ); + + // Get the real "TypeId" of the child. We need this for the + // SymGetTypeInfo( TI_GET_TYPEID ) call below. + DWORD typeId; + SymGetTypeInfo( m_hProcess, modBase, children.ChildId[i], + TI_GET_TYPEID, &typeId ); + + // Get the size of the child member + ULONG64 length; + SymGetTypeInfo(m_hProcess, modBase, typeId, TI_GET_LENGTH,&length); + + // Calculate the address of the member + DWORD_PTR dwFinalOffset = offset + dwMemberOffset; + + // BasicType basicType = GetBasicType(children.ChildId[i], modBase ); + // + // pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]); + // + // Emit the variable name + // pszCurrBuffer += sprintf( pszCurrBuffer, "\'%s\'", Name ); + + pszCurrBuffer = FormatOutputValue( pszCurrBuffer, basicType, + length, (PVOID)dwFinalOffset ); + + pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" ); + } + } + + bHandled = true; + return pszCurrBuffer; +} + +char * WheatyExceptionReport::FormatOutputValue( char * pszCurrBuffer, +BasicType basicType, +DWORD64 length, +PVOID pAddress ) +{ + // Format appropriately (assuming it's a 1, 2, or 4 bytes (!!!) + if ( length == 1 ) + pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", *(PBYTE)pAddress ); + else if ( length == 2 ) + pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", *(PWORD)pAddress ); + else if ( length == 4 ) + { + if ( basicType == btFloat ) + { + pszCurrBuffer += sprintf(pszCurrBuffer," = %f", *(PFLOAT)pAddress); + } + else if ( basicType == btChar ) + { + if ( !IsBadStringPtr( *(PSTR*)pAddress, 32) ) + { + pszCurrBuffer += sprintf( pszCurrBuffer, " = \"%.31s\"", + *(PDWORD)pAddress ); + } + else + pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", + *(PDWORD)pAddress ); + } + else + pszCurrBuffer += sprintf(pszCurrBuffer," = %X", *(PDWORD)pAddress); + } + else if ( length == 8 ) + { + if ( basicType == btFloat ) + { + pszCurrBuffer += sprintf( pszCurrBuffer, " = %lf", + *(double *)pAddress ); + } + else + pszCurrBuffer += sprintf( pszCurrBuffer, " = %I64X", + *(DWORD64*)pAddress ); + } + + return pszCurrBuffer; +} + +BasicType +WheatyExceptionReport::GetBasicType( DWORD typeIndex, DWORD64 modBase ) +{ + BasicType basicType; + if ( SymGetTypeInfo( m_hProcess, modBase, typeIndex, + TI_GET_BASETYPE, &basicType ) ) + { + return basicType; + } + + // Get the real "TypeId" of the child. We need this for the + // SymGetTypeInfo( TI_GET_TYPEID ) call below. + DWORD typeId; + if (SymGetTypeInfo(m_hProcess,modBase, typeIndex, TI_GET_TYPEID, &typeId)) + { + if ( SymGetTypeInfo( m_hProcess, modBase, typeId, TI_GET_BASETYPE, + &basicType ) ) + { + return basicType; + } + } + + return btNoType; +} + +//============================================================================ +// Helper function that writes to the report file, and allows the user to use +// printf style formating +//============================================================================ +int __cdecl WheatyExceptionReport::_tprintf(const TCHAR * format, ...) +{ + TCHAR szBuff[1024]; + int retValue; + DWORD cbWritten; + va_list argptr; + + va_start( argptr, format ); + retValue = vsprintf( szBuff, format, argptr ); + va_end( argptr ); + + WriteFile(m_hReportFile, szBuff, retValue * sizeof(TCHAR), &cbWritten, 0 ); + + return retValue; +} diff --git a/src/shared/WheatyExceptionReport.h b/src/shared/WheatyExceptionReport.h index aa77c951612..055da9a7a5d 100644 --- a/src/shared/WheatyExceptionReport.h +++ b/src/shared/WheatyExceptionReport.h @@ -1,117 +1,117 @@ -#ifndef _WHEATYEXCEPTIONREPORT_ -#define _WHEATYEXCEPTIONREPORT_ - -#include - -#if _MSC_VER < 1400 -# define countof(array) (sizeof(array) / sizeof(array[0])) -#else -# include -# define countof _countof -#endif // _MSC_VER < 1400 - -enum BasicType // Stolen from CVCONST.H in the DIA 2.0 SDK -{ - btNoType = 0, - btVoid = 1, - btChar = 2, - btWChar = 3, - btInt = 6, - btUInt = 7, - btFloat = 8, - btBCD = 9, - btBool = 10, - btLong = 13, - btULong = 14, - btCurrency = 25, - btDate = 26, - btVariant = 27, - btComplex = 28, - btBit = 29, - btBSTR = 30, - btHresult = 31 -}; - -const char* const rgBaseType[] = -{ - " ", // btNoType = 0, - " void ", // btVoid = 1, - " char* ", // btChar = 2, - " wchar_t* ", // btWChar = 3, - " signed char ", - " unsigned char ", - " int ", // btInt = 6, - " unsigned int ", // btUInt = 7, - " float ", // btFloat = 8, - " ", // btBCD = 9, - " bool ", // btBool = 10, - " short ", - " unsigned short ", - " long ", // btLong = 13, - " unsigned long ", // btULong = 14, - " __int8 ", - " __int16 ", - " __int32 ", - " __int64 ", - " __int128 ", - " unsigned __int8 ", - " unsigned __int16 ", - " unsigned __int32 ", - " unsigned __int64 ", - " unsigned __int128 ", - " ", // btCurrency = 25, - " ", // btDate = 26, - " VARIANT ", // btVariant = 27, - " ", // btComplex = 28, - " ", // btBit = 29, - " BSTR ", // btBSTR = 30, - " HRESULT " // btHresult = 31 -}; - -class WheatyExceptionReport -{ - public: - - WheatyExceptionReport( ); - ~WheatyExceptionReport( ); - - // entry point where control comes on an unhandled exception - static LONG WINAPI WheatyUnhandledExceptionFilter( - PEXCEPTION_POINTERS pExceptionInfo ); - - private: - - // where report info is extracted and generated - static void GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo ); - static void PrintSystemInfo(); - static BOOL _GetWindowsVersion(TCHAR* szVersion, DWORD cntMax); - static BOOL _GetProcessorName(TCHAR* sProcessorName, DWORD maxcount); - - // Helper functions - static LPTSTR GetExceptionString( DWORD dwCode ); - static BOOL GetLogicalAddress( PVOID addr, PTSTR szModule, DWORD len, - DWORD& section, DWORD_PTR& offset ); - - static void WriteStackDetails( PCONTEXT pContext, bool bWriteVariables ); - - static BOOL CALLBACK EnumerateSymbolsCallback(PSYMBOL_INFO,ULONG, PVOID); - - static bool FormatSymbolValue( PSYMBOL_INFO, STACKFRAME *, char * pszBuffer, unsigned cbBuffer ); - - static char * DumpTypeIndex( char *, DWORD64, DWORD, unsigned, DWORD_PTR, bool & , char*); - - static char * FormatOutputValue( char * pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress ); - - static BasicType GetBasicType( DWORD typeIndex, DWORD64 modBase ); - - static int __cdecl _tprintf(const TCHAR * format, ...); - - // Variables used by the class - static TCHAR m_szLogFileName[MAX_PATH]; - static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter; - static HANDLE m_hReportFile; - static HANDLE m_hProcess; -}; - -extern WheatyExceptionReport g_WheatyExceptionReport; // global instance of class -#endif //WheatyExceptionReport +#ifndef _WHEATYEXCEPTIONREPORT_ +#define _WHEATYEXCEPTIONREPORT_ + +#include + +#if _MSC_VER < 1400 +# define countof(array) (sizeof(array) / sizeof(array[0])) +#else +# include +# define countof _countof +#endif // _MSC_VER < 1400 + +enum BasicType // Stolen from CVCONST.H in the DIA 2.0 SDK +{ + btNoType = 0, + btVoid = 1, + btChar = 2, + btWChar = 3, + btInt = 6, + btUInt = 7, + btFloat = 8, + btBCD = 9, + btBool = 10, + btLong = 13, + btULong = 14, + btCurrency = 25, + btDate = 26, + btVariant = 27, + btComplex = 28, + btBit = 29, + btBSTR = 30, + btHresult = 31 +}; + +const char* const rgBaseType[] = +{ + " ", // btNoType = 0, + " void ", // btVoid = 1, + " char* ", // btChar = 2, + " wchar_t* ", // btWChar = 3, + " signed char ", + " unsigned char ", + " int ", // btInt = 6, + " unsigned int ", // btUInt = 7, + " float ", // btFloat = 8, + " ", // btBCD = 9, + " bool ", // btBool = 10, + " short ", + " unsigned short ", + " long ", // btLong = 13, + " unsigned long ", // btULong = 14, + " __int8 ", + " __int16 ", + " __int32 ", + " __int64 ", + " __int128 ", + " unsigned __int8 ", + " unsigned __int16 ", + " unsigned __int32 ", + " unsigned __int64 ", + " unsigned __int128 ", + " ", // btCurrency = 25, + " ", // btDate = 26, + " VARIANT ", // btVariant = 27, + " ", // btComplex = 28, + " ", // btBit = 29, + " BSTR ", // btBSTR = 30, + " HRESULT " // btHresult = 31 +}; + +class WheatyExceptionReport +{ + public: + + WheatyExceptionReport( ); + ~WheatyExceptionReport( ); + + // entry point where control comes on an unhandled exception + static LONG WINAPI WheatyUnhandledExceptionFilter( + PEXCEPTION_POINTERS pExceptionInfo ); + + static void printTracesForAllThreads(); + private: + // where report info is extracted and generated + static void GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo ); + static void PrintSystemInfo(); + static BOOL _GetWindowsVersion(TCHAR* szVersion, DWORD cntMax); + static BOOL _GetProcessorName(TCHAR* sProcessorName, DWORD maxcount); + + // Helper functions + static LPTSTR GetExceptionString( DWORD dwCode ); + static BOOL GetLogicalAddress( PVOID addr, PTSTR szModule, DWORD len, + DWORD& section, DWORD_PTR& offset ); + + static void WriteStackDetails( PCONTEXT pContext, bool bWriteVariables, HANDLE pThreadHandle); + + static BOOL CALLBACK EnumerateSymbolsCallback(PSYMBOL_INFO,ULONG, PVOID); + + static bool FormatSymbolValue( PSYMBOL_INFO, STACKFRAME *, char * pszBuffer, unsigned cbBuffer ); + + static char * DumpTypeIndex( char *, DWORD64, DWORD, unsigned, DWORD_PTR, bool & , char*); + + static char * FormatOutputValue( char * pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress ); + + static BasicType GetBasicType( DWORD typeIndex, DWORD64 modBase ); + + static int __cdecl _tprintf(const TCHAR * format, ...); + + // Variables used by the class + static TCHAR m_szLogFileName[MAX_PATH]; + static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter; + static HANDLE m_hReportFile; + static HANDLE m_hProcess; +}; + +extern WheatyExceptionReport g_WheatyExceptionReport; // global instance of class +#endif //WheatyExceptionReport diff --git a/src/trinitycore/TrinityCore.rc b/src/trinitycore/TrinityCore.rc index 80206f29567..4e6510c0407 100644 --- a/src/trinitycore/TrinityCore.rc +++ b/src/trinitycore/TrinityCore.rc @@ -52,8 +52,8 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,3,6731,680 - PRODUCTVERSION 0,3,6731,680 + FILEVERSION 0,4,6743,685 + PRODUCTVERSION 0,4,6743,685 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BLOCK "080004b0" BEGIN VALUE "FileDescription", "TrinityCore" - VALUE "FileVersion", "0, 3, 6731, 680" + VALUE "FileVersion", "0, 4, 6743, 685" VALUE "InternalName", "TrinityCore" VALUE "LegalCopyright", "Copyright (C) 2008" VALUE "OriginalFilename", "TrinityCore.exe" VALUE "ProductName", "TrinityCore" - VALUE "ProductVersion", "0, 3, 6731, 680" + VALUE "ProductVersion", "0, 4, 6743, 685" END END BLOCK "VarFileInfo" diff --git a/src/trinityrealm/TrinityRealm.rc b/src/trinityrealm/TrinityRealm.rc index 385908d7e38..33c7eef719a 100644 --- a/src/trinityrealm/TrinityRealm.rc +++ b/src/trinityrealm/TrinityRealm.rc @@ -52,8 +52,8 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,3,6731,680 - PRODUCTVERSION 0,3,6731,680 + FILEVERSION 0,4,6743,685 + PRODUCTVERSION 0,4,6743,685 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BLOCK "080004b0" BEGIN VALUE "FileDescription", "TrinityRealm" - VALUE "FileVersion", "0, 3, 6731, 680" + VALUE "FileVersion", "0, 4, 6743, 685" VALUE "InternalName", "TrinityRealm" VALUE "LegalCopyright", "Copyright (C) 2008" VALUE "OriginalFilename", "TrinityRealm.exe" VALUE "ProductName", "TrinityRealm" - VALUE "ProductVersion", "0, 3, 6731, 680" + VALUE "ProductVersion", "0, 4, 6743, 685" END END BLOCK "VarFileInfo"