0rQ?Zz&cb8pdN0C#Z1}h{e2m<=DU~x%FtVQFp zLV|OL?c8L5tLXKnkSGSPUW0r0nsEDA5TL24Dc2&;!!Ey5I6VhDNxar0(HIs{1B8*m zlU!1x7THJH&a0y1DOCXTHSue;bR;_U<6n#3qBB*Vyv%+AG~1h4x>2wjTfKob_o}rUPuwdvB)J5ot3;c)b1$dmh_ujn1hC z>?jy>EoUhk;$n!RCnRMS@-qN%x$XIs4rVtm(=IvD8U&oIv`B41n}o7LBhQDps=$EF zX|E0bCicP@NwKJ~LiMc=AtiA^)cFBKHYKyZpW9PhQ`I{lTQ?+bL!0EZI_t@c43RM+ zp k^DwD1VdDrfp6=x(RuJQ5qYGi#LCr-09dpw#l zU`N8d1i1R1n&{JB=)ZX=TfNjB4pVkh0C g}n;R;9qyjgb{)7Zxy7_v) zW`@Yerh?(71`u$|zNB`t8e!F~i z9#AQGSiBlBNVI7>84Id?_u+>0K}wan2P&MDnw8CxGG(|%#GmV{+|A*cK6ksTt9RST z?PkIQCo*T_rP} 1&gr^4W#-;adeDa$7g$7` zRI~pRk3(#rB2BNk``6KW8G4a0i~Fqgy{MQF%kH<*m6peMt@%C`Zi1`MU_K|X;3ESG zRo~7WDJ=PwIa0qL87%&yJ9p)E2T3{#-`}gim6ZEovaRF1I}xIDj#Lq<8& `ap+bjbhcedMZ zwdWYSeKmLs#V^KtDmk_pW%BgpXo1$Y1&=+mea$oxFjhRE1kze#c*@<>j42q?$Xb>F z;=3bnXU4q_)NCnv+p4OoIOBV2CBp(G#BCdja=XD~#1P~e10Aop3U|u*xF-XhXSbWV zUm7(4kpKC67T{(T8BkztP1?@j+O;4&6ySJ?`TfTP_LgDOa=*mAW%P y?V6F^3khMvIi=nXcf$$oqFj#aQyIvH7Z<95kp&C=-bFbF6xU61bn#r{=X{m-vn zPff=rmPBfggIoT&d0w5Yg31)NQ+MC$xBpo+8VIeWB#f|+aVf`ZG>{KihVle 8p3PkMZ$3p^eX$M335q27eJb z7Tv2U-xhJNgc`PPI-=_u$`Ut!{=u-pEh&L&+(K1}q5~-OaAQk58CG-*x;)MiyH(5W zI>q^;>kNqW__3>koSXbLOX<0v^iC+)pA;S3IfGv~uO`;bVpHKeR`Tl)Pw3^+-Fpho zCAK^uwd;zOT2sVd6hsZIh&TY8jMw#-k`&?(5N^5fNWLk-jKCE6boaX=+h5zWZpZY2 z@Eg-uc3=P_^vK)j;LfMNbuFj)-%!8|nR^KfvPF4+SzK%=*T?_mc*a9Er1QgofSL(2 zH4`7mzqc_$@>j{0mUL}$t>{VA>sKg?;;+!er?@k}En8B$FGdtH*tUZO1jaqZ!O3H2 z|KeYz5`{S{{V(fI1miDj!#>j%GLZ{NzoOqZgwoh0SCdTcAA1KU?OZO^*zMVxaIwDb z*7;vn8x2ox9^ Z6qRdKRj8C#zoEQ $yETuw$FDWT2-U_?e^Rt@e zt^!k$iAjDG`?{I+82QeheTkp#3CF7GZmJtggV7R$`*0rR0BDOvAp1VK2A1aga(nNq z2ZHdAd`*%Wd|fTo%iE;RF{g>=bgydUH8V{hOakQn)1f4)H?7D`yLr~Uk7^y^Ivz4a z{-uMx_O0h3Rs2s=8A<~NqdR3VuPlO2isEMc6zNjvN{3#o+ycQQOod?vQ6F9TGi&kC z?~Cn^$Ocm=if}gqmf_|$ZzGaJ^BQ(HEX2qAm5(o1ZdLP;J~5=9rQcNCC>)WYb7ZDR zB3rPI?}(ZUJ&Mta$8HqNCgwcn!~zg4WTy~@;L^4IzDS0aC3=eacm?(@cC#{^sb8ws zXVcG;>k~WMs@rZ`FGi`%=T=vLv?Q3l%6SIJyk?Q;3!ts4ujU|SO@gp w9;!{9 zylz|Z(9qPB_4Jy!I70@bz#MfQK(a;m#@{n93~V@44-o?<;aPb*7Ca%>5Y8jS fzTmMxms1Z7vg8=jhF;ku(~@MQ^CXlgzq*p3b_Wu$(ubHAng)9> z3UK3RHp}&GKVB@RKQXc3$=FLAM?ItDB%~wOw}~Cn2vV^BOE`QT!5q&@DVX~85d3MD g{NFksRYIivY|dHt)`aQrVLL!uQ(vR#wr$vd0n{-fNdN!< literal 10050 zcmcJV2T;>bnD>*=d#_4U5T*AHq4y#xRhm+j-a$$ddQ%XkS3yPpR6!v0DqVV&7HTL0 zksb)i#k+gocW&O9o4c92nfx}Joo)H;?q|Qxvzz$PSci(7og4rFQ0eJvJ^}!M_$UxS zN{s)q@%!R}|6%^>iDiJMv%jO?3!i`&-d+GeNdBkku9qNjT4Kb>;bX$K*+=z^ujUO* zlSm?n5(l#>^L`4wbNg `546?)yXE& zHk$I8(}V3aZJbnOVuC@lRVj#zMrWrgRPHGDK$7D=`0?ZVmCEUtEiWKIU&f!UpErg- z007(oJx#SIA^8W(p&?JUmSs^#@{;cc3u$YOL1az@>QDg#+B91FJ4vRr->bgYd%M?D z)JJ&OGisfleVr{UY5z2RQ9t|W^u@lpqZzq5t=5x&=wP(zsttn@l7mhXIXNRihse}5 zLz&8gcO%Lyg{#PBc(**kNde!*#ghNMoTqXjC~i`oJzniAVp{b(@#Iiircw~M=8rD` zQd4mOfcle^|19adKF-m8?wO2hkVvbYD0|*@T6&Xi_Q ~koVCM%e{(cf`nb4I(7n{7vE@{ZDB*c-e?iiR#;~I87-oEyz3SF0XQFEH}13DJ% z->33@YsTm2+{;ymI;2Y%@~jjaLqL1S2WOB41Q-^z#b06anzkWg$k5Czb3!hq9nWvi z?e@2lj*0j>D5s~+Y0S-!Y;26~Q7P5E8ww{!L1xuaB~=(6uvN2fe43n2qI|f!u;l zKAdc2= l6_ae*V z_R8Dr=+D6~pN&{|@vREZ)S8uN5;YQSt}jQ&cg7^cH^;nIdM2r?dK60-o+&JLRuWQg z;oepbF3>I+YsgT`;;1AQM9AdrCk&wL>OEgPkq$KLrxE&9`fpn+N=z3C?(vVa^b13N zr7W2)dJ=T{e%+zKo^9q8N-A(26x_#DBtFGTX^if05OCZ_6(Tb2CHMoUWi$jQ>#Uo$ zhIO#TA7Fp3yYI`oo^E)@(G{S}7;P;Eh{l(FlRg<5-GY?*olGpDL>>nSs*le)Jb(Q7 zaUXIl|IOp0#u#PX-Q-U4&nc@NN(>?BihIA&xv3_Sn p>eVAKsKi(Ef*`V6Q?Ir zCq)sI7qJxS-$<4t`3Q`J7*@@ERgDkCV8;|-L`}IhNXHqa&Uuwc+~B+Mk*Bm+OnL^i z+#iF*zH)n{-@{oX=1fPNzhUncbQW}%?}Hrv-K8vCF0qYI=iob0K`J=@`VUF7i Zvbt ~1tu8xhgnC*Do z!rjcxUy4sk&f)XeUjAUi?bQy}uAC68-Cp5p#6|Q+3I*8zK=LBuxgw(4SJrloEnF2- zY@`3qL=%-8QsxuXv-$Z6swo8Wc1HJR??mc^+;jRx!wb%p3G4&oi~Er}@=ixwibQ2StV@SSit%rlX&N!-V=HXR6xj+4pU3*L zp2NdWUnnbxV JBPWf#=U1Ukp*}m-v0p2t;EB(`7v9} z=1-J3e40!lqafs5(OT~6G{)cjp%OvVW>?OX?R}y3Fr2FluFy!ZjS+xaS+yq{Ns)Uc z^k;G8p&S)RNvxm&as6iX_*x#IK43v`b6<={K$}Q4A;}a-Tk`w~Am$4z6X1U^sgr2b zrS4nA^?7WBp1|=;?@`ZEN0f2fHHZKzKP>BaqwAKVgeosDznhL5)&aS%-SpdrXZ2;C zO|!89oP-A6=(hiKaRj8jf6w?9Q )%J*riV8}A&*_foAs=d3 z=e(x3 HSnKOju zypcyU7mQiZk}N<$L4n6gYpAZCo|?5ad!~pjdCT&sC%XU*wl%-AD1XH1Z=aTqx4OV} zHLO;lOY2pPnZqgBeGHAkTeq90TtKAQ=Pq DfESl#02 zUg(QMV2KP^d~%2eba3CIJK*r_VIftOtJK?=2QSFztF2Uh_Q0FBq%RG~Kl|no-Iky0 z!}pYSDwoI|gF+17&rXfE2Xmfd5YvzmL{XhvLy48@01*m%eWX!6mzoPCje=fYh6Dr$ z$G-~XNuHxFYE+774x65Pn-%-?;~Pw^E(hAVBkcTB55BcW`qohaXIo~1p)O@Dtve0! zZx4^}EN@XA7Ok(>sqTf{6U4e3F+XoU4QYPSd!cqGD+G{@dTY<@2i)A=?%wiyEeOS8 zBb}U_42ND{SrA9Gn=T$BaIM23-@fN{V7Ox6&b+)ihDarD!bH7UkVwSSJlP7wTjd|0 zq916>zC yn&bodH06sC{>rjoB!h(_DU6F#+d;tNgP)oDmO8XN6T=% WDjrOp;d=9DD1)Tq$9mL{br676QMh$I+DBwW$jYn??G zsS117alHALn{ru=ApCNUE!9bV^Qv{Lzo`Ey4Cg!dQ++s0Snzrz$W( k#y9prW6Smv7BQn&SXiC z+OAf nU}L!#@pfQ zs->ZS?q)-@w# uWkvB3}`-kMSRB;G-DQA*Zn&|qb5=38V7xn-O~bj0xx+vo)3Et7ZF4em$vSu@zR+Bi z9l!r0v$8w12YCupW&!H3yaN!{NmtN47*ejQYF zicv8Q51D4$rF6F{P2X?DC1p(&*uPKUWEfut7oQ5;+f4uJ|LmZ#bY%>ZVQ2K7bDvqL z&$o_>gOh= g@_ zHja9lW5p=_#k!duIhs2zHBYXoMeyss7dq;G$@5+P?~Ugld~5i8Se-Gmag0*dPlT+K z&SfarJ&}M^lBi;jXC-N_fa3|sZ-l6{lmEZN4uVZ5vT*^rSHV+_405A3md#m7;w|nO zry6WZP*^^0d;K$Cg6XW1`TN4J=xa#^dQ)lGS478t?NE!A@2h*cPlepR$*3`El9n*X zeSGj`JFTN?&&&LMCX70npmJ2iP0Exuhfu>qXx1za!u;@YQwGyV*WyxFfKkM2=q3p- znH5 (D2dwYIwulz|AbJ^#K}yVLG6i=3v=~R*GG$ zgJxu(>NirA0LV@pa4Mrg08HPE=M^W7B93Ckme!b;s=fabJ26ezCPR+hal0fT;prxT z^_YrTm+MrhP%AGBC&UudO8e+hyJ^vtbeB5ZmtyDmPCGtnM^g#VjU?^^={y3ijj<5X z-Eov#47gpw4Hbu?`AEsjT_3eOjzmYl^Oa62c;Su1Q`Cx=_;QpcL=DQT$9mgOEsa-| z6xZ?(2tY-kS l52K(_qwsqI(Lfnd-+At=$4m(>f7>3Tr-L#m^{9Sg~0=&0knG z-Ae(a3H0?SN~-RSGd01N1gUD-WF@3@AZ|2|glZaw)u)9Zx~S*4IEq{K^qLz@ ZyruE}a;$E oC3%hr Ov5x(qM*Y%sVm&<;wPV@XPM9>DzgpE&4>PuF=eq7ZWz&9<-R)0!tyaTNsML}U7 zc;q6yZqHwEXJG_q4;>ct9&?M6(L2^O<=4>2qb_c6@dj9F3oQSzULmN1x9jMnWNa6@ zEEzL*aezpN49E4)fA8hJ KLs=N7 z;^L$iv)95^9O*qYL@1k=FZW^NCrqL=D(5j=)m@^u!n#xE5Lsqs-i{ucTPN(8+WIU{ z*A#JG2Zu lnC3tkB`ZXT8V!`?o9FN9n#{;uJ{S6C5HP3! 2%4A5wGqZz5m)imtPPP^ zMvh875wcBG*B~ROTCg#2coG>zMlP47y7WQ4p+<>wWz=bh60ek1IWb?qP3TH!`ilZX zQT<7v=Z^yaHML}n#4|lEjBFr!^&kNDgN#7{sO3&T4yy4BC2AfEC{?rLD(o4mkPz2T zO(II3SaH-UEoT_t#Zwn^@>53Yl_Aua1lH62-f@?n7;#`2SC<+l^~kYw&4}2eKm*eQ z>`ekK`GWE}MBD-~TwxQ)XhD5w*)mz13Z`5lbt|YDF-_|8U7nVq91{lK)bWbgGB;1l zjXU(eCv_@Ac;U*&>z6}h({`?snUx;(##k`-z_!cztz<~$m|c@}+Uj!Zc2^-HsiO6G zgPN%p&2kGG)A_5jIR?nPMeUP^7p0IQ$5Vu;V0OPVGdI^wjsZp40s%2FGV$4;lt&_U zP{hP)W%S}+Y!Q&NBPRLt*p>uES}N8$l^F{Jx6&hyFTZh@X-){KS_MkP5pN`}-37;+ zSvlV!+~F;rnFLiXQoNp{Fnvy&Q@iz6;}e~xTLiuJ2(s9>nOnh@HX_msEwPyB6mpQb zbgVu3U9;4lHR>}Tdv4|qBC>-o)}XzLkDa=Jr1_28Wv;Xj6?*MV&msT#On8EI^7?~p zU*|G!@# b!-pDOY?g+fa@=|_u$?IJ@=)eIb9E )VJO-leRV8C`QuxSs@@i=<|es^W2rTk=t`h2@Zz{i&8by(`1#b4t=# zPm0N8ygZ51roybeyYu|jWYeak0eNev#(3io)w@ZwTEH7u&ED)O_$EarBm5+ec8-ps zz?k5*cFaZv@t^G{B4}#^^88Q@-NO`(6g!dv^OzPh%@XH-YWMR7UgNQPppkD!AwjH5 zi9)ZTD7%g>qS1VmlvFTZt6~mXZdF=9*%Vm^FEG7m^@aq{LR#>Ft0uXtYwSb`uPThF zO?+0_92FbGm(du`coMq~!b)vT0~7wn@Ede^-4sP*Dvx%VR!dXf)5zcG@Z>3iYF=(T zuGmABcP0CezAK=vhAM>w#UH>ux!Ajl`(J0t3%_mMtvwJ@lqX6d%KUs7tRS}IyOD7d zp1DI^LBlnW+IxlSIsqW%uc3YZ1wIuXo9U2sXrb9C{60LQZ~l!;Dx1IHvdysxL=7us zj5Z=Ut$1k|S>B%lGDcUDoO=As#xSR{HIX0xKPE0dj1VPoOuO@4CG6u|JlDSwvOunw zD`U(ZHEf(Zh?Kr`HgY@;rHZuL&kpB{=E_zD(yEJupSJvu8}R?1GhjZ39AQ%sU672p z+Nlhro@MXCoa5nU@#iM>^u#;#irOXg>P^Ddt=HNvQtYBTf3<1VXjheBd1oLVRK2ol z?gi1dPaDYRGAq@2iPgdiqCJIL7;g=WXz}WEPhNm0$2zul73Y(lS~&cu6r@e07h`|z zvD_SJ+33ZA7p(6xs=oj>`B#ztKt0ffHwc|)PrE$+@g =P s8F8f4uveh9-=LZ}(5N(!5S>2#6+2UFw zZKNt=hE0pn3?xs(CL|~q_~u)dk#l6umJ+4!hfQmC@#$90v@+!^pEK1$BIsLH$ebnh zn?^vnVBeZyQETEkXbd*U!UtCy6>4+Xfx-0fykW--lW+TmVz)|h^Ii6}uc))Epc8IR z9k?B8-sOcD7_4(|L@!uo?SgB$%bD3hihV&^`|h!Fp+jVQf3$mwwiY_c1t2bLt@}#2 zL?OUO>dK*ks!Mt^zHsj9_W>wefulr`5K%!`rdXpAPFWp(7s0IQX3ZPI-ddyBh6>^9 z^=rj>sS!k!Lw7RQ_|`&}aoNnmHc?P~_}n^ H<0(PMNHQF8W|kWNvM%+`KQ@CvO6r;t6sbuDg>EWmg6>XIzsPykoU?wfP}w z-v}nBp{bum{pvMy$^A} foXYN;V)Vs (wxr-MvPXzZ#Z3c|2wdU|k` z2QSpozeI8T8-ph8Gdx|$Ic^dC=qfws{de*DglS7vM4H9kv?zU006W4)w&-k48ZfC4 z2qk7^9gjlajJrc;IaPE%zP(sIWIa40c2vv?ceq3!`BN5!$@hrc%lILJWdr@kO!W0Z zfP8s>Voy&`o4RTf{ceL)B@kf#;gc9en%fTpl<{KkP^s8;#&MP(j1=$be39rh1ypYF z(2VRsV*-rPZFrO%WM8lT7I*wd{}A%A2r@hsT$4WIAGNq_w7lbaps`okv7PRXAMp~c z gT6iCSx}zR2I9d>eF%yAN%*2p$Pwtl F()T5zc<-;$NDdaDw)xE5+-Bi`E+{`oM_N;n@feBiMm+-S0c6%>Ku^;~;L z`&;1$wo!ba4Dd7xsR~kjS|i6o!HitT7UVs#0C8m=Ic &lZ^W`^;J!Qny(@PLQzQGvO9tL0vGgn z4`0Qk;YZhd4zHdGUC_^meTg50a6%3xs}_zC_NNOxhJr+Yb9Gc!voy5ZZL%==3&Z-0 zmVUJR4!^dnaR5>h6a0;a{P|=7-x`hvD|;#c48IUEc2LyF2~(9VA0n PA!?%ZAGF(c=eybk1*DuvwaPB=bSq>A1c6Pr9pxVN4-H`zr4;sJjl#Ee- zxQ@$k!=+fPyWv~s{bWcLxpmO_)O!Q}_>X0LDl>{7RQmIdhGz_rqJ8)J=c33wGmZzY z(&YJv3zgSsi#LA%%_hz@9dTy%!HpV?2p4J;EdU=kD#IZ^owxAzsl(9+&hyS-3lJ$K zmge;h0O}ptNFGLW7NcyB_Y!ho{GZ{C<&e`vTcN2mj@uZ(_`+-w93Fy~W`Ey gL>l68D#brr7UHK&i&_mV%< xKqlpZ`F z(_^qH3YV}Xxo&bDisl4(nDeX1V^y+jk|!8%pB`I;GCPPLTa`pCd3KkG;&k4Ph|49w zFGHMMTzkaj280`XSS{mLoMd`l*1x$DuxB<_;9&tgjDQ6AtnOV)m5NZruKNS@^z`KX zw3wahv#w 9amq1dT-9eYGzY`85R zOm9xFcJM3BY9eJF(eQOH1`8DY$A!gj5Q1MTd8XOSN_$B5?Jk$V^*F&s|G`!Kq_%DH ziu7qITNAPa&r{m*1HnS8gWKy&zu~@Ahs*9-EE3}s!pvMt-F&zS{-AGg_}Yt(WtZq4 z@q{8QDo6yK3T{dkwypJ _Sm&{$I4= z*YLj^n_>74-9qQ$+Jzx&x#06kaN!XE0Hc@tewV$CDq_AyHgge?^;&tYA1|}|+Asd9 zt*TWF>dXI;SjWS`L3Jx}DwuVMXPX0rP5S_ET SV!^%tLp-|KX0qD=lC|yOL5>v=C>ftQruoNrtU$} zF-_IeAAn*^eIpZ65NoI!n&<+!yxgIy;(J)rlJC6QELr1wPS80`xG|Ut&+LTFY*iRi zr~*__1mzbeH}^uyYx6U90e#bX9a8MD{#A;KSC4JcIx_F;oQXLAhk_FifpbM=F91xy; zh9*2_VuJEsjVq4BE~hQ!Zb$3t>zVpVvs5tLlHDlzsd}spFn}JF2}3`{uU`4|&wBO! z*5|GQjoXojixxt#`NLYgPm;&&1T=XevoERE&G(C2o ogN|BiHn)PERxw z`0jY^=9SBVH$fqrIF->!zejXM?8#pF+I!?c4i49*$}!G-Y^u^#84Gud(**nkw1(<^ zJXX{1R~Q>4L)!?*u4TNB{uO>25VW>J!nvJ})9tRKC5O#7soMAw9>2R#&9Bt>{bM3Q zbVB 7gyWv7hbTa&8cJ(Q(55`)d7 zlQxQ;Z*}W++DFY-C@-A^=1_i{g6&8FI|SZ5j4UJ;UwEbD`={t`I@dcYP1n7DZJCr- zY-9a)NQwew{@I ZD)ex|YFFuC^CgeR11vstpRL^?PQ=PjL$-wr#@1I1ukyj+0xGh}&j;*?zt znui5?|An|)5Rbe9$Kx^+HRKD}U1ho^9o5fPLg9Z1k1(>nCxy2_7dSK; z-h`h8MjeZ!__AB6$PGo?Ymos=8U>>22&e1z)f@JylEvCyYSMplZ6~-}ey4Zr2rl{C zJ638r=%HBqjZ(!{a7ld-3!vC@CR*&5lFbS+=D<6MT)Dtj?mT|2bS1M;;kThZbQSAT z_VO4U;VXyh-&Vi~#0Fzgra1mt1*KfZ1q{i2D}@9^9ZbTFv!#!K?_YyAuT !ji-IHuwtbv{VQg_H0~^TW$$X{bgZelhUwZwB$v=V^74xq8h13 z#=O0S*DB#T9|Aa^=l|VJ-wioQe5j}pf_v6mNAnYs@!(Xn$_eHU=ST6bz9^jtja?@2Sd~b1+Q=6eZiXLT>C%54|Npx8@*57QnM64xUl(eIzZ(b8(=yhqQg?{@ E7lD*MHUIzs From 929687175b86c4864f8a01cabf1e36118b5aabb6 Mon Sep 17 00:00:00 2001 From: dwasint <82520990+dwasint@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:49:04 -0700 Subject: [PATCH 007/158] the medicalling --- code/__DEFINES/~monkestation/blood_datums.dm | 21 + .../~monkestation-helpers/blood_datums.dm | 13 + code/_globalvars/bitfields.dm | 1 + code/datums/components/bloodysoles.dm | 215 +++++----- code/datums/components/cleaner.dm | 14 +- code/datums/components/cult_ritual_item.dm | 2 +- code/datums/dna.dm | 17 +- code/datums/elements/decals/blood.dm | 4 + code/datums/quirks/_quirk.dm | 6 +- code/datums/quirks/negative_quirks.dm | 50 ++- code/datums/records/manifest.dm | 4 +- code/datums/status_effects/debuffs/debuffs.dm | 7 +- code/datums/status_effects/wound_effects.dm | 3 + code/datums/wounds/bones.dm | 14 +- code/datums/wounds/loss.dm | 2 +- code/datums/wounds/pierce.dm | 4 +- code/game/atoms.dm | 21 +- code/game/machinery/computer/dna_console.dm | 6 +- .../machinery/computer/operating_computer.dm | 7 +- .../machinery/computer/records/medical.dm | 2 +- code/game/machinery/medical_kiosk.dm | 2 +- code/game/objects/effects/decals/cleanable.dm | 50 ++- .../effects/decals/cleanable/aliens.dm | 11 +- .../effects/decals/cleanable/humans.dm | 249 ++++++++---- .../effects/decals/cleanable/robots.dm | 25 +- .../objects/effects/spawners/gibspawner.dm | 2 +- .../temporary_visuals/miscellaneous.dm | 3 - .../items/devices/scanners/health_analyzer.dm | 29 +- code/game/objects/items/dna_injector.dm | 6 +- .../items/stacks/sheets/sheet_types.dm | 1 + code/game/objects/items/weaponry.dm | 21 + code/game/turfs/closed/walls.dm | 13 +- code/modules/admin/create_mob.dm | 2 +- code/modules/admin/verbs/list_exposer.dm | 2 +- .../abductor/equipment/glands/blood.dm | 2 +- code/modules/antagonists/brother/brother.dm | 8 +- code/modules/antagonists/cult/blood_magic.dm | 33 +- .../sacrifice_knowledge/sacrifice_buff.dm | 2 +- code/modules/antagonists/obsessed/obsessed.dm | 4 +- code/modules/clothing/clothing.dm | 20 + code/modules/clothing/gloves/_gloves.dm | 5 +- code/modules/clothing/head/_head.dm | 7 +- code/modules/clothing/masks/_masks.dm | 6 +- code/modules/clothing/neck/_neck.dm | 6 +- code/modules/clothing/shoes/_shoes.dm | 6 +- code/modules/clothing/suits/_suits.dm | 4 +- code/modules/clothing/under/_under.dm | 3 +- code/modules/detectivework/scanner.dm | 2 +- code/modules/forensics/forensics_helpers.dm | 11 +- code/modules/hydroponics/grown/replicapod.dm | 2 +- code/modules/hydroponics/seeds.dm | 2 +- code/modules/mob/inventory.dm | 2 +- .../living/basic/bots/cleanbot/cleanbot.dm | 2 - .../basic/space_fauna/demon/demon_items.dm | 6 + code/modules/mob/living/blood.dm | 254 ++++-------- code/modules/mob/living/brain/brain.dm | 2 +- code/modules/mob/living/carbon/carbon.dm | 4 + .../mob/living/carbon/carbon_movement.dm | 37 +- .../mob/living/carbon/carbon_update_icons.dm | 4 +- .../mob/living/carbon/human/_species.dm | 94 ++--- code/modules/mob/living/carbon/human/dummy.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 10 - .../living/carbon/human/human_update_icons.dm | 3 +- .../carbon/human/species_types/jellypeople.dm | 58 ++- .../human/species_types/lizardpeople.dm | 14 +- .../carbon/human/species_types/podpeople.dm | 14 +- .../carbon/human/species_types/snail.dm | 11 +- .../carbon/human/species_types/vampire.dm | 8 +- code/modules/mob/living/carbon/life.dm | 2 +- code/modules/mob/living/damage_procs.dm | 16 +- code/modules/mob/living/living.dm | 59 ++- .../hostile/megafauna/bubblegum.dm | 12 + code/modules/projectiles/projectile.dm | 5 +- .../chemistry/reagents/medicine_reagents.dm | 4 +- .../chemistry/reagents/other_reagents.dm | 69 ++-- .../reagents/reagent_containers/blood_pack.dm | 68 ++-- .../spells/spell_types/jaunt/bloodcrawl.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 8 +- .../surgery/bodyparts/dismemberment.dm | 8 + code/modules/unit_tests/bloody_footprints.dm | 11 +- .../mecha/equipment/tools/mining_tools.dm | 6 +- icons/effects/blood.dmi | Bin 191871 -> 175305 bytes icons/effects/footprints.dmi | Bin 3962 -> 4537 bytes icons/mob/effects/dam_mob.dmi | Bin 16182 -> 16007 bytes .../code/game/machinery/computer/cloning.dm | 2 +- .../code/game/machinery/exp_cloner.dm | 2 +- .../abductor/equipment/glands/blood.dm | 15 - .../code/modules/blood_datum/blood.dm | 327 ++++++++++++++++ .../blood_datum/components/limbless_aid.dm | 130 +++++++ .../blood_datum/elements/easy_ignite.dm | 97 +++++ .../modules/blood_datum/forensics_helpers.dm | 36 ++ .../blood_datum/icons/melee_lefthand.dmi | Bin 0 -> 531 bytes .../blood_datum/icons/melee_righthand.dmi | Bin 0 -> 536 bytes .../code/modules/blood_datum/icons/staff.dmi | Bin 0 -> 479 bytes .../blood_datum/icons/status_display.dmi | Bin 0 -> 1541 bytes .../code/modules/blood_datum/items/crutch.dm | 21 + .../operating_table_additions.dm | 72 ++++ .../blood_datum/vital_monitor/vital_reader.dm | 368 ++++++++++++++++++ .../blood_for_the_blood_gods/particle.dm | 19 +- .../slasher/abilities/blood_walk.dm | 11 - .../slasher/abilities/recall_machette.dm | 10 - .../structures/bloodsucker_objects.dm | 2 +- .../modules/mob/living/carbon/human/human.dm | 3 + .../carbon/human/species_type/arachnid.dm | 1 + .../carbon/human/species_type/ethereal.dm | 12 +- .../carbon/human/species_type/floran.dm | 14 +- .../modules/ranching/chickens/tier1/glass.dm | 4 +- .../reagents/reagent_containers/blood_pack.dm | 3 +- .../modules/slimecore/slime_traits/cleaner.dm | 2 - .../smithing/ipcs/body/base_bodyparts.dm | 12 +- .../ipcs/reagents/medical_supplies.dm | 3 +- .../code/modules/smithing/ipcs/species.dm | 2 +- .../modules/smithing/oozelings/species.dm | 8 +- .../virology/immune_systems/_immune_system.dm | 2 +- tgstation.dme | 10 +- 115 files changed, 1951 insertions(+), 984 deletions(-) create mode 100644 code/__DEFINES/~monkestation/blood_datums.dm create mode 100644 code/__HELPERS/~monkestation-helpers/blood_datums.dm delete mode 100644 monkestation/code/modules/antagonists/abductor/equipment/glands/blood.dm create mode 100644 monkestation/code/modules/blood_datum/blood.dm create mode 100644 monkestation/code/modules/blood_datum/components/limbless_aid.dm create mode 100644 monkestation/code/modules/blood_datum/elements/easy_ignite.dm create mode 100644 monkestation/code/modules/blood_datum/forensics_helpers.dm create mode 100644 monkestation/code/modules/blood_datum/icons/melee_lefthand.dmi create mode 100644 monkestation/code/modules/blood_datum/icons/melee_righthand.dmi create mode 100644 monkestation/code/modules/blood_datum/icons/staff.dmi create mode 100644 monkestation/code/modules/blood_datum/icons/status_display.dmi create mode 100644 monkestation/code/modules/blood_datum/items/crutch.dm create mode 100644 monkestation/code/modules/blood_datum/vital_monitor/operating_table_additions.dm create mode 100644 monkestation/code/modules/blood_datum/vital_monitor/vital_reader.dm diff --git a/code/__DEFINES/~monkestation/blood_datums.dm b/code/__DEFINES/~monkestation/blood_datums.dm new file mode 100644 index 000000000000..e1f906a8b103 --- /dev/null +++ b/code/__DEFINES/~monkestation/blood_datums.dm @@ -0,0 +1,21 @@ +#define COMSIG_HUMAN_ON_HANDLE_BLOOD "human_on_handle_blood" + #define HANDLE_BLOOD_HANDLED (1<<0) + #define HANDLE_BLOOD_NO_NUTRITION_DRAIN (1<<1) + #define HANDLE_BLOOD_NO_EFFECTS (1<<2) + +#define COLOR_BLOOD "#c90000" + +/// Modifier used in math involving bloodiness, so the above values can be adjusted easily +#define BLOOD_PER_UNIT_MODIFIER 0.5 + +/// from /datum/status_effect/limp/proc/check_step() +#define COMSIG_CARBON_LIMPING "mob_limp_check" + #define COMPONENT_CANCEL_LIMP (1<<0) + +/// Mob can walk despite having two disabled/missing legs so long as they have two of this trait. +/// Kind of jank, refactor at a later day when I can think of a better solution. +/// Just be sure to call update_limbless_locomotion() after applying / removal +#define TRAIT_NO_LEG_AID "no_leg_aid" + +/// Updating a mob's movespeed when lacking limbs. (list/modifiers) +#define COMSIG_LIVING_LIMBLESS_MOVESPEED_UPDATE "living_get_movespeed_modifiers" diff --git a/code/__HELPERS/~monkestation-helpers/blood_datums.dm b/code/__HELPERS/~monkestation-helpers/blood_datums.dm new file mode 100644 index 000000000000..7e74ac55592d --- /dev/null +++ b/code/__HELPERS/~monkestation-helpers/blood_datums.dm @@ -0,0 +1,13 @@ +/proc/random_human_blood_type() + var/static/list/human_blood_type_weights = list( + /datum/blood_type/crew/human/o_minus = 4, + /datum/blood_type/crew/human/o_plus = 36, + /datum/blood_type/crew/human/a_minus = 28, + /datum/blood_type/crew/human/a_plus = 3, + /datum/blood_type/crew/human/b_minus = 20, + /datum/blood_type/crew/human/b_plus = 1, + /datum/blood_type/crew/human/ab_minus = 5, + /datum/blood_type/crew/human/ab_plus = 1 + ) + + return pick_weight(human_blood_type_weights) diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index fb117fadab69..b4051cb172b4 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -438,6 +438,7 @@ DEFINE_BITFIELD(organ_flags, list( "ORGAN_EDIBLE" = ORGAN_EDIBLE, "ORGAN_SYNTHETIC_EMP" = ORGAN_SYNTHETIC_EMP, "ORGAN_UNREMOVABLE" = ORGAN_UNREMOVABLE, + "ORGAN_HIDDEN" = ORGAN_HIDDEN, //Monkestation edit: BLOOD_DATUMS, how was this forgotten )) DEFINE_BITFIELD(respiration_type, list( diff --git a/code/datums/components/bloodysoles.dm b/code/datums/components/bloodysoles.dm index 0d950031f229..0e7635d9174b 100644 --- a/code/datums/components/bloodysoles.dm +++ b/code/datums/components/bloodysoles.dm @@ -3,35 +3,44 @@ * Component for clothing items that can pick up blood from decals and spread it around everywhere when walking, such as shoes or suits with integrated shoes. */ /datum/component/bloodysoles + /* /// The type of the last grub pool we stepped in, used to decide the type of footprints to make var/last_blood_state = BLOOD_STATE_NOT_BLOODY /// How much of each grubby type we have on our feet var/list/bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) + */ //Monkestation removal: BLOOD_DATUMS + + // Monkestation Addition: BLOOD_DATUMS + /// What percentage of the bloodiness is deposited on the ground per step + var/blood_dropped_per_step = 3 + /// Bloodiness on our clothines + VAR_FINAL/total_bloodiness = 0 + // Monkestation Addition: BLOOD_DATUMS /// The ITEM_SLOT_* slot the item is equipped on, if it is. var/equipped_slot - /// The parent item but casted into atom type for easier use. - var/atom/parent_atom - /// Either the mob carrying the item, or the mob itself for the /feet component subtype - var/mob/living/carbon/wielder + VAR_FINAL/mob/living/carbon/wielder /// The world.time when we last picked up blood - var/last_pickup + VAR_FINAL/last_pickup var/footprint_sprite = FOOTPRINT_SPRITE_SHOES /datum/component/bloodysoles/Initialize() if(!isclothing(parent)) return COMPONENT_INCOMPATIBLE - parent_atom = parent RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_clean)) +/datum/component/bloodysoles/Destroy() + wielder = null + return ..() + /** * Unregisters from the wielder if necessary */ @@ -55,63 +64,52 @@ var/obj/item/parent_item = parent parent_item.update_slot_icon() - -/datum/component/bloodysoles/proc/reset_bloody_shoes() - bloody_shoes = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) - on_changed_bloody_shoes(BLOOD_STATE_NOT_BLOODY) - -///lowers bloody_shoes[index] by adjust_by -/datum/component/bloodysoles/proc/adjust_bloody_shoes(index, adjust_by) - bloody_shoes[index] = max(bloody_shoes[index] - adjust_by, 0) - on_changed_bloody_shoes() - -/datum/component/bloodysoles/proc/set_bloody_shoes(index, new_value) - bloody_shoes[index] = new_value - on_changed_bloody_shoes(index) - ///called whenever the value of bloody_soles changes -/datum/component/bloodysoles/proc/on_changed_bloody_shoes(index) - if(index && index != last_blood_state) - last_blood_state = index +/datum/component/bloodysoles/proc/change_blood_amount(some_amount) + total_bloodiness = clamp(round(total_bloodiness + some_amount, 0.1), 0, BLOOD_ITEM_MAX) if(!wielder) return - if(bloody_shoes[index] <= BLOOD_FOOTPRINTS_MIN * 2)//need twice that amount to make footprints + if(total_bloodiness <= BLOOD_FOOTPRINTS_MIN * 2)//need twice that amount to make footprints UnregisterSignal(wielder, COMSIG_MOVABLE_MOVED) else RegisterSignal(wielder, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved), override = TRUE) + update_icon() /** * Run to equally share the blood between us and a decal */ /datum/component/bloodysoles/proc/share_blood(obj/effect/decal/cleanable/pool) // Share the blood between our boots and the blood pool - var/total_bloodiness = pool.bloodiness + bloody_shoes[pool.blood_state] - - // We can however be limited by how much blood we can hold - var/new_our_bloodiness = min(BLOOD_ITEM_MAX, total_bloodiness / 2) - - set_bloody_shoes(pool.blood_state, new_our_bloodiness) - pool.bloodiness = total_bloodiness - new_our_bloodiness // Give the pool the remaining blood incase we were limited + var/new_total_bloodiness = min(BLOOD_ITEM_MAX, pool.bloodiness + total_bloodiness / 2) + if(new_total_bloodiness == total_bloodiness || new_total_bloodiness == 0) + return - if(HAS_TRAIT(wielder, TRAIT_LIGHT_STEP)) //the character is agile enough to don't mess their clothing and hands just from one blood splatter at floor - return TRUE + var/delta = new_total_bloodiness - total_bloodiness + pool.adjust_bloodiness(-1 * delta) + change_blood_amount(delta) + var/atom/parent_atom = parent parent_atom.add_blood_DNA(GET_ATOM_BLOOD_DNA(pool)) - update_icon() /** - * Find a blood decal on a turf that matches our last_blood_state + * Adds blood to an existing (or new) footprint */ -/datum/component/bloodysoles/proc/find_pool_by_blood_state(turf/turfLoc, typeFilter = null, footprint_sprite) - for(var/obj/effect/decal/cleanable/blood/pool in turfLoc) - if(pool.blood_state == last_blood_state && pool.footprint_sprite == footprint_sprite && (!typeFilter || istype(pool, typeFilter))) - return pool +/datum/component/bloodysoles/proc/add_blood_to_footprint(obj/effect/decal/cleanable/blood/footprints/footprint, bloodiness_to_add, exiting = FALSE) + var/atom/atom_parent = parent + add_parent_to_footprint(footprint) + footprint.adjust_bloodiness(bloodiness_to_add) + footprint.add_blood_DNA(GET_ATOM_BLOOD_DNA(atom_parent)) + if(exiting) + footprint.exited_dirs |= wielder.dir + else + footprint.entered_dirs |= wielder.dir + footprint.update_appearance() /** * Adds the parent type to the footprint's shoe_types var */ -/datum/component/bloodysoles/proc/add_parent_to_footprint(obj/effect/decal/cleanable/blood/footprints/FP) - FP.shoe_types |= parent.type +/datum/component/bloodysoles/proc/add_parent_to_footprint(obj/effect/decal/cleanable/blood/footprints/footprint) + footprint.shoe_types |= parent.type /** * Called when the parent item is equipped by someone @@ -130,7 +128,7 @@ equipped_slot = slot wielder = equipper - if(bloody_shoes[last_blood_state] > BLOOD_FOOTPRINTS_MIN * 2) + if(total_bloodiness > BLOOD_FOOTPRINTS_MIN * 2) RegisterSignal(wielder, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) RegisterSignal(wielder, COMSIG_STEP_ON_BLOOD, PROC_REF(on_step_blood)) @@ -149,61 +147,51 @@ * * Used to make bloody footprints on the ground */ -/datum/component/bloodysoles/proc/on_moved(datum/source, OldLoc, Dir, Forced) +/datum/component/bloodysoles/proc/on_moved(datum/source, atom/old_loc, Dir, Forced) SIGNAL_HANDLER - if(bloody_shoes[last_blood_state] == 0) + if(total_bloodiness <= 0) return if(QDELETED(wielder) || is_obscured()) return if(wielder.body_position == LYING_DOWN || !wielder.has_gravity(wielder.loc)) return - var/half_our_blood = bloody_shoes[last_blood_state] / 2 - var/footprint_sprite = wielder.get_footprint_sprite() + var/atom/parent_atom = parent + var/blood_used = round(total_bloodiness / 3, 0.01) + // Add footprints in old loc if we have enough cream - if(half_our_blood >= BLOOD_FOOTPRINTS_MIN) - var/turf/oldLocTurf = get_turf(OldLoc) - var/obj/effect/decal/cleanable/blood/footprints/oldLocFP = find_pool_by_blood_state(oldLocTurf, /obj/effect/decal/cleanable/blood/footprints, footprint_sprite) - if(oldLocFP) - // Footprints found in the tile we left, add us to it - add_parent_to_footprint(oldLocFP) - if (!(oldLocFP.exited_dirs & wielder.dir)) - oldLocFP.exited_dirs |= wielder.dir - oldLocFP.update_appearance() - else if(find_pool_by_blood_state(oldLocTurf, footprint_sprite = footprint_sprite)) - // No footprints in the tile we left, but there was some other blood pool there. Add exit footprints on it - adjust_bloody_shoes(last_blood_state, half_our_blood) - update_icon() + if(blood_used >= BLOOD_FOOTPRINTS_MIN) + var/turf/old_loc_turf = get_turf(old_loc) + var/obj/effect/decal/cleanable/blood/footprints/old_loc_prints = locate() in old_loc_turf + if(old_loc_prints) + add_blood_to_footprint(old_loc_prints, 0, TRUE) // Add no actual blood, just update sprite - oldLocFP = new(oldLocTurf, footprint_sprite) - if(!QDELETED(oldLocFP)) ///prints merged - oldLocFP.blood_state = last_blood_state - oldLocFP.exited_dirs |= wielder.dir - add_parent_to_footprint(oldLocFP) - oldLocFP.bloodiness = half_our_blood - oldLocFP.add_blood_DNA(GET_ATOM_BLOOD_DNA(parent_atom)) - oldLocFP.update_appearance() + else if(locate(/obj/effect/decal/cleanable/blood) in old_loc_turf) + // No footprints in the tile we left, but there was some other blood pool there. Add exit footprints on it + change_blood_amount(-1 * blood_used) + old_loc_prints = new(old_loc_turf) + if(!QDELETED(old_loc_prints)) // prints merged + add_blood_to_footprint(old_loc_prints, blood_used, TRUE) - half_our_blood = bloody_shoes[last_blood_state] / 2 + blood_used = round(total_bloodiness / 3, 0.01) // If we picked up the blood on this tick in on_step_blood, don't make footprints at the same place if(last_pickup && last_pickup == world.time) return // Create new footprints - if(half_our_blood >= BLOOD_FOOTPRINTS_MIN) - adjust_bloody_shoes(last_blood_state, half_our_blood) - update_icon() + if(blood_used >= BLOOD_FOOTPRINTS_MIN) + var/turf/new_loc_turf = get_turf(parent_atom) + var/obj/effect/decal/cleanable/blood/footprints/new_loc_prints = locate() in new_loc_turf + if(new_loc_prints) + add_blood_to_footprint(new_loc_prints, 0, FALSE) // Add no actual blood, just update sprite - var/obj/effect/decal/cleanable/blood/footprints/FP = new(get_turf(parent_atom), footprint_sprite) - if(!QDELETED(FP)) ///prints merged - FP.blood_state = last_blood_state - FP.entered_dirs |= wielder.dir - add_parent_to_footprint(FP) - FP.bloodiness = half_our_blood - FP.add_blood_DNA(GET_ATOM_BLOOD_DNA(parent_atom)) - FP.update_appearance() + else + change_blood_amount(-1 * blood_used) + new_loc_prints = new(new_loc_turf) + if(!QDELETED(new_loc_prints)) // prints merged + add_blood_to_footprint(new_loc_prints, blood_used, FALSE) /** @@ -214,20 +202,16 @@ /datum/component/bloodysoles/proc/on_step_blood(datum/source, obj/effect/decal/cleanable/pool) SIGNAL_HANDLER - if(QDELETED(wielder) || is_obscured()) + if(QDELETED(wielder) || is_obscured() || !wielder.has_gravity(wielder.loc)) + return + /// The character is agile enough to not mess their clothing and hands just from one blood splatter at floor + if(HAS_TRAIT(wielder, TRAIT_LIGHT_STEP)) + return + // Don't share from other feetprints, not super realistic but I think it ruins the effect a bit + if(istype(pool, /obj/effect/decal/cleanable/blood/footprints)) return - - if(istype(pool, /obj/effect/decal/cleanable/blood/footprints) && pool.blood_state == last_blood_state) - // The pool we stepped in was actually footprints with the same type - var/obj/effect/decal/cleanable/blood/footprints/pool_FP = pool - add_parent_to_footprint(pool_FP) - if((bloody_shoes[last_blood_state] / 2) >= BLOOD_FOOTPRINTS_MIN && !(pool_FP.entered_dirs & wielder.dir)) - // If our feet are bloody enough, add an entered dir - pool_FP.entered_dirs |= wielder.dir - pool_FP.update_appearance() share_blood(pool) - last_pickup = world.time /** @@ -236,10 +220,10 @@ /datum/component/bloodysoles/proc/on_clean(datum/source, clean_types) SIGNAL_HANDLER - if(!(clean_types & CLEAN_TYPE_BLOOD) || last_blood_state == BLOOD_STATE_NOT_BLOODY) + if(!(clean_types & CLEAN_TYPE_BLOOD)) return NONE - reset_bloody_shoes() + total_bloodiness = 0 update_icon() return COMPONENT_CLEANED @@ -248,12 +232,12 @@ * Like its parent but can be applied to carbon mobs instead of clothing items */ /datum/component/bloodysoles/feet + equipped_slot = ITEM_SLOT_FEET var/static/mutable_appearance/bloody_feet /datum/component/bloodysoles/feet/Initialize() if(!iscarbon(parent)) return COMPONENT_INCOMPATIBLE - parent_atom = parent wielder = parent if(footprint_sprite) src.footprint_sprite = footprint_sprite @@ -266,29 +250,26 @@ RegisterSignal(parent, COMSIG_CARBON_EQUIP_SHOECOVER, PROC_REF(equip_shoecover)) /datum/component/bloodysoles/feet/update_icon() - if(ishuman(wielder)) - var/mob/living/carbon/human/human = wielder - if(NOBLOODOVERLAY in human.dna.species.species_traits) - return - if(bloody_shoes[BLOOD_STATE_HUMAN] > 0 && !is_obscured()) - human.remove_overlay(SHOES_LAYER) - human.overlays_standing[SHOES_LAYER] = bloody_feet - human.apply_overlay(SHOES_LAYER) - else - human.update_worn_shoes() + if(!ishuman(wielder)) + return + wielder.remove_overlay(SHOES_LAYER) + if(total_bloodiness > 0 && !is_obscured()) + bloody_feet.color = wielder.get_blood_dna_color() + wielder.overlays_standing[SHOES_LAYER] = bloody_feet + wielder.apply_overlay(SHOES_LAYER) + else + wielder.update_worn_shoes() -/datum/component/bloodysoles/feet/add_parent_to_footprint(obj/effect/decal/cleanable/blood/footprints/FP) +/datum/component/bloodysoles/feet/add_parent_to_footprint(obj/effect/decal/cleanable/blood/footprints/footprint) if(!ishuman(wielder)) - FP.species_types |= "unknown" + footprint.species_types |= "unknown" return // Find any leg of our human and add that to the footprint, instead of the default which is to just add the human type - for(var/X in wielder.bodyparts) - var/obj/item/bodypart/affecting = X - if(affecting.body_part == LEG_RIGHT || affecting.body_part == LEG_LEFT) - if(!affecting.bodypart_disabled) - FP.species_types |= affecting.limb_id - break + for(var/obj/item/bodypart/affecting as anything in wielder.bodyparts) + if(!affecting.bodypart_disabled && (affecting.body_part == LEG_RIGHT || affecting.body_part == LEG_LEFT)) + footprint.species_types |= affecting.limb_id + break /datum/component/bloodysoles/feet/is_obscured() @@ -297,16 +278,12 @@ return wielder.check_obscured_slots(TRUE) & ITEM_SLOT_FEET /datum/component/bloodysoles/feet/on_moved(datum/source, OldLoc, Dir, Forced) - if(wielder.num_legs < 2) - return - - ..() + if(wielder.num_legs >= 2) + return ..() /datum/component/bloodysoles/feet/on_step_blood(datum/source, obj/effect/decal/cleanable/pool) - if(wielder.num_legs < 2) - return - - ..() + if(wielder.num_legs >= 2) + return ..() /datum/component/bloodysoles/feet/proc/unequip_shoecover(datum/source) SIGNAL_HANDLER diff --git a/code/datums/components/cleaner.dm b/code/datums/components/cleaner.dm index 874af94cc50e..eff8531afafc 100644 --- a/code/datums/components/cleaner.dm +++ b/code/datums/components/cleaner.dm @@ -96,8 +96,8 @@ ADD_TRAIT(target, TRAIT_CURRENTLY_CLEANING, REF(src)) // We need to update our planes on overlay changes RegisterSignal(target, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(cleaning_target_moved)) - var/mutable_appearance/low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, target, GAME_PLANE) - var/mutable_appearance/high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, target, ABOVE_GAME_PLANE) + var/mutable_appearance/low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, target, GAME_PLANE, appearance_flags = RESET_COLOR) // Monkestation edit BLOOD_DATUM + var/mutable_appearance/high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, target, ABOVE_GAME_PLANE, appearance_flags = RESET_COLOR) // Monkestation edit BLOOD_DATUM if(target.plane > low_bubble.plane) //check if the higher overlay is necessary target.add_overlay(high_bubble) else if(target.plane == low_bubble.plane) @@ -140,16 +140,18 @@ REMOVE_TRAIT(target, TRAIT_CURRENTLY_CLEANING, REF(src)) /datum/component/cleaner/proc/cleaning_target_moved(atom/movable/source, turf/old_turf, turf/new_turf, same_z_layer) + SIGNAL_HANDLER + if(same_z_layer) return // First, get rid of the old overlay - var/mutable_appearance/old_low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, old_turf, GAME_PLANE) - var/mutable_appearance/old_high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, old_turf, ABOVE_GAME_PLANE) + var/mutable_appearance/old_low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, old_turf, GAME_PLANE, appearance_flags = RESET_COLOR) // NON-MODULE CHANGE + var/mutable_appearance/old_high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, old_turf, ABOVE_GAME_PLANE, appearance_flags = RESET_COLOR) // NON-MODULE CHANGE source.cut_overlay(old_low_bubble) source.cut_overlay(old_high_bubble) // Now, add the new one - var/mutable_appearance/new_low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, new_turf, GAME_PLANE) - var/mutable_appearance/new_high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, new_turf, ABOVE_GAME_PLANE) + var/mutable_appearance/new_low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, new_turf, GAME_PLANE, appearance_flags = RESET_COLOR) // NON-MODULE CHANGE + var/mutable_appearance/new_high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", FLOOR_CLEAN_LAYER, new_turf, ABOVE_GAME_PLANE, appearance_flags = RESET_COLOR) // NON-MODULE CHANGE source.add_overlay(new_low_bubble) source.add_overlay(new_high_bubble) diff --git a/code/datums/components/cult_ritual_item.dm b/code/datums/components/cult_ritual_item.dm index 584d9c0f5267..8d2e9fc133e0 100644 --- a/code/datums/components/cult_ritual_item.dm +++ b/code/datums/components/cult_ritual_item.dm @@ -303,7 +303,7 @@ span_cult("You [cultist.blood_volume ? "slice open your arm and ":""]begin drawing a sigil of the Geometer.") ) - if(cultist.blood_volume) + if(!HAS_TRAIT(cultist, TRAIT_NOBLOOD)) // Monkestation Edit: BLOOD_DATUM cultist.apply_damage(initial(rune_to_scribe.scribe_damage), BRUTE, pick(GLOB.arm_zones), wound_bonus = CANT_WOUND) // *cuts arm* *bone explodes* ever have one of those days? var/scribe_mod = initial(rune_to_scribe.scribe_delay) diff --git a/code/datums/dna.dm b/code/datums/dna.dm index a40c0097eb10..6a4b152ddb84 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -53,7 +53,10 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) var/unique_enzymes ///Stores the hashed values of traits such as skin tones, hair style, and gender var/unique_identity - var/blood_type + /// So humans have a variety of blood types while other species do not + /// This tracks JUST human blood type. Might seem a bit bias but everyone is a human under their scales and feathers. + /// Essentially only exists so humans have their same blood type swapping from human -> non-human -> human. + var/datum/blood_type/crew/human/human_blood_type ///The type of mutant race the player is if applicable (i.e. potato-man) var/datum/species/species = new /datum/species/human ///first value is mutant color @@ -103,7 +106,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) return destination.dna.unique_enzymes = unique_enzymes destination.dna.unique_identity = unique_identity - destination.dna.blood_type = blood_type + destination.dna.human_blood_type = human_blood_type destination.dna.unique_features = unique_features destination.dna.features = features.Copy() destination.dna.real_name = real_name @@ -120,7 +123,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) new_dna.default_mutation_genes = default_mutation_genes new_dna.unique_identity = unique_identity new_dna.unique_features = unique_features - new_dna.blood_type = blood_type + new_dna.human_blood_type = human_blood_type new_dna.features = features.Copy() //if the new DNA has a holder, transform them immediately, otherwise save it if(new_dna.holder) @@ -393,7 +396,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) && real_name == target_dna.real_name \ && species.type == target_dna.species.type \ && compare_list(features, target_dna.features) \ - && blood_type == target_dna.blood_type \ + && human_blood_type == target_dna.human_blood_type \ ) return TRUE @@ -431,9 +434,9 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) unique_enzymes = generate_unique_enzymes() unique_features = generate_unique_features() -/datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE) +/datum/dna/proc/initialize_dna(newblood_type = random_human_blood_type(), skip_index = FALSE) if(newblood_type) - blood_type = newblood_type + human_blood_type = newblood_type unique_enzymes = generate_unique_enzymes() unique_identity = generate_unique_identity() if(!skip_index) //I hate this @@ -543,7 +546,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) dna.generate_unique_enzymes() if(newblood_type) - dna.blood_type = newblood_type + dna.human_blood_type = newblood_type if(unique_identity) dna.unique_identity = unique_identity diff --git a/code/datums/elements/decals/blood.dm b/code/datums/elements/decals/blood.dm index 889ebb12904b..cd679b80003d 100644 --- a/code/datums/elements/decals/blood.dm +++ b/code/datums/elements/decals/blood.dm @@ -38,6 +38,10 @@ blood_splatter_appearances[index] = pic return TRUE +/datum/element/decal/blood/apply_overlay(obj/item/source, list/overlay_list) + pic.color = source.get_blood_dna_color() || COLOR_BLOOD + return ..() + /datum/element/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override) SIGNAL_HANDLER diff --git a/code/datums/quirks/_quirk.dm b/code/datums/quirks/_quirk.dm index 2b55f0932f04..3c18ddad816d 100644 --- a/code/datums/quirks/_quirk.dm +++ b/code/datums/quirks/_quirk.dm @@ -32,7 +32,7 @@ /// A list of items people can receive from mail who have this quirk enabled /// The base weight for the each quirk's mail goodies list to be selected is 5 /// then the item selected is determined by pick(selected_quirk.mail_goodies) - var/mail_goodies = list() + var/list/mail_goodies = list() //Monkestation Edit BLOOD_DATUM: Why? this is already a list all this does is mess confuse us. /datum/quirk/Destroy() if(quirk_holder) @@ -147,7 +147,7 @@ /// Otherwise, it runs once on the next COMSIG_MOB_LOGIN. /datum/quirk/proc/post_add() return - + /// return additional data that should be remembered by cloning /datum/quirk/proc/clone_data() return @@ -155,7 +155,7 @@ /// create the quirk from clone data /datum/quirk/proc/on_clone(data) return - + /// Subtype quirk that has some bonus logic to spawn items for the player. /datum/quirk/item_quirk /// Lazylist of strings describing where all the quirk items have been spawned. diff --git a/code/datums/quirks/negative_quirks.dm b/code/datums/quirks/negative_quirks.dm index a2528c8c9d92..cb22e6e0118e 100644 --- a/code/datums/quirks/negative_quirks.dm +++ b/code/datums/quirks/negative_quirks.dm @@ -69,31 +69,43 @@ var/min_blood = BLOOD_VOLUME_SAFE - 25 // just barely survivable without treatment /datum/quirk/blooddeficiency/post_add() - if(!ishuman(quirk_holder)) - return + update_mail() - // for making sure the roundstart species has the right blood pack sent to them - var/mob/living/carbon/human/carbon_target = quirk_holder - carbon_target.dna.species.update_quirk_mail_goodies(carbon_target, src) - -/** - * Makes the mob lose blood from having the blood deficiency quirk, if possible - * - * Arguments: - * * seconds_per_tick - */ -/datum/quirk/blooddeficiency/proc/lose_blood(seconds_per_tick) - if(quirk_holder.stat == DEAD) +/datum/quirk/blooddeficiency/add(client/client_source) + . = ..() + RegisterSignal(quirk_holder, COMSIG_HUMAN_ON_HANDLE_BLOOD, PROC_REF(lose_blood)) + RegisterSignal(quirk_holder, COMSIG_SPECIES_GAIN, PROC_REF(update_mail)) + +/datum/quirk/blooddeficiency/remove() + . = ..() + UnregisterSignal(quirk_holder, COMSIG_HUMAN_ON_HANDLE_BLOOD) + UnregisterSignal(quirk_holder, COMSIG_SPECIES_GAIN) + +/datum/quirk/blooddeficiency/proc/lose_blood(mob/living/carbon/human/draining, seconds_per_tick, times_fired) + SIGNAL_HANDLER + if(quirk_holder.stat == DEAD || quirk_holder.blood_volume <= min_blood) return - var/mob/living/carbon/human/carbon_target = quirk_holder - if(HAS_TRAIT(carbon_target, TRAIT_NOBLOOD) && isnull(carbon_target.dna.species.exotic_blood)) //can't lose blood if your species doesn't have any + // Ensures that we don't reduce total blood volume below min_blood. + draining.blood_volume = max(min_blood, draining.blood_volume - draining.dna.species.blood_deficiency_drain_rate * seconds_per_tick) + +/datum/quirk/blooddeficiency/proc/update_mail(datum/source, datum/species/new_species, datum/species/old_species) + SIGNAL_HANDLER + + mail_goodies.Cut() + + var/datum/blood_type/new_type = quirk_holder.get_blood_type() + if(isnull(new_type)) return - if (carbon_target.blood_volume <= min_blood) + if(istype(new_type, /datum/blood_type/crew/human)) + mail_goodies += /obj/item/reagent_containers/blood/o_minus return - // Ensures that we don't reduce total blood volume below min_blood. - carbon_target.blood_volume = max(min_blood, carbon_target.blood_volume - carbon_target.dna.species.blood_deficiency_drain_rate * seconds_per_tick) + + for(var/obj/item/reagent_containers/blood/blood_bag as anything in typesof(/obj/item/reagent_containers/blood)) + if(initial(blood_bag.blood_type) == new_type.type) + mail_goodies += blood_bag + break /datum/quirk/item_quirk/blindness name = "Blind" diff --git a/code/datums/records/manifest.dm b/code/datums/records/manifest.dm index 3bd91072ab8f..786d94ef0f9c 100644 --- a/code/datums/records/manifest.dm +++ b/code/datums/records/manifest.dm @@ -113,7 +113,7 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new) var/datum/record/locked/lockfile = new( age = person.age, - blood_type = person.dna.blood_type, + blood_type = "[person.get_blood_type() || "None"]", character_appearance = character_appearance, dna_string = person.dna.unique_enzymes, fingerprint = md5(person.dna.unique_identity), @@ -130,7 +130,7 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new) var/datum/record/crew/crewfile = new ( age = person.age, - blood_type = person.dna.blood_type, + blood_type = "[person.get_blood_type() || "None"]", character_appearance = character_appearance, dna_string = person.dna.unique_enzymes, fingerprint = md5(person.dna.unique_identity), diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index d1d29681131c..b3933ece3169 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -365,11 +365,10 @@ /datum/status_effect/stacking/saw_bleed/threshold_cross_effect() owner.adjustBruteLoss(bleed_damage) - var/turf/T = get_turf(owner) - new /obj/effect/temp_visual/bleed/explode(T) + new /obj/effect/temp_visual/bleed/explode(owner.loc) for(var/d in GLOB.alldirs) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(T, d) - playsound(T, SFX_DESECRATION, 100, TRUE, -1) + owner.do_splatter_effect(d) + playsound(owner, SFX_DESECRATION, 100, TRUE, -1) /datum/status_effect/stacking/saw_bleed/bloodletting id = "bloodletting" diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm index ed0b7b555e46..f44e71de8db7 100644 --- a/code/datums/status_effects/wound_effects.dm +++ b/code/datums/status_effects/wound_effects.dm @@ -70,6 +70,9 @@ if(!owner.client || owner.body_position == LYING_DOWN || !owner.has_gravity() || (owner.movement_type & FLYING) || forced || owner.buckled) return + if(SEND_SIGNAL(owner, COMSIG_CARBON_LIMPING, (next_leg || right || left)) & COMPONENT_CANCEL_LIMP) + return + // less limping while we have determination still var/determined_mod = owner.has_status_effect(/datum/status_effect/determined) ? 0.5 : 1 diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 1b63e5a4a2a4..de8858e5932d 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -135,12 +135,10 @@ /datum/wound/blunt/bone/receive_damage(wounding_type, wounding_dmg, wound_bonus) if(!victim || wounding_dmg < WOUND_MINIMUM_DAMAGE) return - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(HAS_TRAIT(human_victim, TRAIT_NOBLOOD)) - return + if(HAS_TRAIT(victim, TRAIT_NOBLOOD)) + return - if(limb.body_zone == BODY_ZONE_CHEST && victim.blood_volume && prob(internal_bleeding_chance + wounding_dmg)) + if(limb.body_zone == BODY_ZONE_CHEST && prob(internal_bleeding_chance + wounding_dmg)) var/blood_bled = rand(1, wounding_dmg * (severity == WOUND_SEVERITY_CRITICAL ? 2 : 1.5)) // 12 brute toolbox can cause up to 18/24 bleeding with a severe/critical chest wound switch(blood_bled) if(1 to 6) @@ -150,13 +148,13 @@ victim.bleed(blood_bled, TRUE) if(14 to 19) victim.visible_message("Blood spews out of [victim]'s mouth from the blow to [victim.p_their()] chest!", span_danger("You spit out a string of blood from the blow to your chest!"), vision_distance=COMBAT_MESSAGE_RANGE) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir, COLOR_DARK_RED) + victim.do_splatter_effect(victim.dir) victim.bleed(blood_bled) victim.blood_particles(amount = 1) if(20 to INFINITY) victim.visible_message(span_danger("Blood spurts out of [victim]'s mouth from the blow to [victim.p_their()] chest!"), span_danger("You choke up on a spray of blood from the blow to your chest!"), vision_distance=COMBAT_MESSAGE_RANGE) victim.bleed(blood_bled) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir, COLOR_DARK_RED) + victim.do_splatter_effect(victim.dir) victim.add_splatter_floor(get_step(victim.loc, victim.dir)) victim.blood_particles(amount = 3) @@ -500,6 +498,6 @@ if(limb.body_zone == BODY_ZONE_HEAD) . += "Cranial Trauma Detected: Patient will suffer random bouts of [severity == WOUND_SEVERITY_SEVERE ? "mild" : "severe"] brain traumas until bone is repaired." - else if(limb.body_zone == BODY_ZONE_CHEST && victim.blood_volume) + else if(limb.body_zone == BODY_ZONE_CHEST && !HAS_TRAIT(victim, TRAIT_NOBLOOD)) . += "Ribcage Trauma Detected: Further trauma to chest is likely to worsen internal bleeding until bone is repaired." . += "" diff --git a/code/datums/wounds/loss.dm b/code/datums/wounds/loss.dm index bcad804eba68..d8d0aad5e80e 100644 --- a/code/datums/wounds/loss.dm +++ b/code/datums/wounds/loss.dm @@ -48,7 +48,7 @@ set_limb(dismembered_part) second_wind() log_wound(victim, src) - if(dismembered_part.can_bleed() && wounding_type != WOUND_BURN && victim.blood_volume) + if(dismembered_part.can_bleed() && wounding_type != WOUND_BURN) victim.spray_blood(attack_direction, severity) victim.blood_particles(amount = rand(3, 6), angle = 0, min_deviation = 0, max_deviation = 360) dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE, wounding_type = wounding_type) diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index 4deb88361768..80897c47364f 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -48,12 +48,12 @@ victim.bleed(blood_bled, TRUE) if(14 to 19) victim.visible_message("A small stream of blood spurts from the hole in [victim]'s [limb.plaintext_zone]!", span_danger("You spit out a string of blood from the blow to your [limb.plaintext_zone]!"), vision_distance=COMBAT_MESSAGE_RANGE) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir, COLOR_DARK_RED) + victim.do_splatter_effect(victim.dir) victim.bleed(blood_bled) if(20 to INFINITY) victim.visible_message(span_danger("A spray of blood streams from the gash in [victim]'s [limb.plaintext_zone]!"), span_danger("You choke up on a spray of blood from the blow to your [limb.plaintext_zone]!"), vision_distance=COMBAT_MESSAGE_RANGE) victim.bleed(blood_bled) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir, COLOR_DARK_RED) + victim.do_splatter_effect(victim.dir) victim.add_splatter_floor(get_step(victim.loc, victim.dir)) /datum/wound/pierce/bleed/get_bleed_rate_of_change() diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 95ba030e198a..d95c5ad012af 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1042,20 +1042,19 @@ ///returns the mob's dna info as a list, to be inserted in an object's blood_DNA list /mob/living/proc/get_blood_dna_list() - if(get_blood_id() != /datum/reagent/blood) - return - return list("ANIMAL DNA" = "Y-") + var/datum/blood_type/blood = get_blood_type() + if(!isnull(blood)) + return list("UNKNOWN DNA" = blood.type) + return null ///Get the mobs dna list /mob/living/carbon/get_blood_dna_list() - if(get_blood_id() != /datum/reagent/blood) - return - var/list/blood_dna = list() - if(dna) - blood_dna[dna.unique_enzymes] = dna.blood_type - else - blood_dna["UNKNOWN DNA"] = "X*" - return blood_dna + if(isnull(dna)) // Xenos + return ..() + var/datum/blood_type/blood = get_blood_type() + if(isnull(blood)) // Skeletons? + return null + return list("[dna.unique_enzymes]" = blood.type) /mob/living/carbon/alien/get_blood_dna_list() return list("UNKNOWN DNA" = "X*") diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm index 932444e63a8f..bd28bed8a6dd 100644 --- a/code/game/machinery/computer/dna_console.dm +++ b/code/game/machinery/computer/dna_console.dm @@ -1229,7 +1229,7 @@ "UE"=scanner_occupant.dna.unique_enzymes, "UF"=scanner_occupant.dna.unique_features, "name"=scanner_occupant.real_name, - "blood_type"=scanner_occupant.dna.blood_type) + "blood_type"="[GLOB.blood_types[scanner_occupant.dna.human_blood_type]]") return @@ -1719,7 +1719,7 @@ scanner_occupant.real_name = buffer_slot["name"] scanner_occupant.name = buffer_slot["name"] scanner_occupant.dna.unique_enzymes = buffer_slot["UE"] - scanner_occupant.dna.blood_type = buffer_slot["blood_type"] + scanner_occupant.dna.human_blood_type = blood_name_to_blood_type(buffer_slot["blood_type"]) scanner_occupant.apply_status_effect(/datum/status_effect/genetic_damage, damage_increase) scanner_occupant.domutcheck() return TRUE @@ -1737,7 +1737,7 @@ scanner_occupant.real_name = buffer_slot["name"] scanner_occupant.name = buffer_slot["name"] scanner_occupant.dna.unique_enzymes = buffer_slot["UE"] - scanner_occupant.dna.blood_type = buffer_slot["blood_type"] + scanner_occupant.dna.human_blood_type = blood_name_to_blood_type(buffer_slot["blood_type"]) scanner_occupant.apply_status_effect(/datum/status_effect/genetic_damage, damage_increase) scanner_occupant.domutcheck() return TRUE diff --git a/code/game/machinery/computer/operating_computer.dm b/code/game/machinery/computer/operating_computer.dm index 093c96bf57ec..83692a8af26a 100644 --- a/code/game/machinery/computer/operating_computer.dm +++ b/code/game/machinery/computer/operating_computer.dm @@ -138,12 +138,7 @@ data["patient"]["health"] = patient.health // check here to see if the patient has standard blood reagent, or special blood (like how ethereals bleed liquid electricity) to show the proper name in the computer - var/blood_id = patient.get_blood_id() - if(blood_id == /datum/reagent/blood) - data["patient"]["blood_type"] = patient.dna?.blood_type - else - var/datum/reagent/special_blood = GLOB.chemical_reagents_list[blood_id] - data["patient"]["blood_type"] = special_blood ? special_blood.name : blood_id + data["patient"]["blood_type"] = "[patient.get_blood_type() || "None"]" data["patient"]["maxHealth"] = patient.maxHealth data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD diff --git a/code/game/machinery/computer/records/medical.dm b/code/game/machinery/computer/records/medical.dm index 362c8468aa6f..88f68d9c474a 100644 --- a/code/game/machinery/computer/records/medical.dm +++ b/code/game/machinery/computer/records/medical.dm @@ -140,7 +140,7 @@ return FALSE target.age = 18 - target.blood_type = pick(list("A+", "A-", "B+", "B-", "O+", "O-", "AB+", "AB-")) + target.blood_type = "[GLOB.blood_types[random_human_blood_type()]]" target.dna_string = "Unknown" target.gender = "Unknown" target.major_disabilities = "" diff --git a/code/game/machinery/medical_kiosk.dm b/code/game/machinery/medical_kiosk.dm index d42d4844a0e1..9ccacd777f2e 100644 --- a/code/game/machinery/medical_kiosk.dm +++ b/code/game/machinery/medical_kiosk.dm @@ -211,7 +211,7 @@ var/bleed_status = "Patient is not currently bleeding." var/blood_status = " Patient either has no blood, or does not require it to function." var/blood_percent = round((patient.blood_volume / BLOOD_VOLUME_NORMAL)*100) - var/blood_type = patient.dna.blood_type + var/blood_type = "[patient.get_blood_type() || "None"]" var/blood_warning = " " for(var/thing in patient.diseases) //Disease Information diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 966f668d5eb7..5466f4992a34 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -43,7 +43,7 @@ for(var/datum/disease/D in diseases) if(D.spread_flags & (DISEASE_SPREAD_BLOOD)) src.diseases |= D - + AddElement(/datum/element/beauty, beauty) var/turf/T = get_turf(src) @@ -53,6 +53,8 @@ COMSIG_ATOM_ENTERED = PROC_REF(on_entered), ) AddElement(/datum/element/connect_loc, loc_connections) + if(bloodiness) + update_appearance() /obj/effect/decal/cleanable/Destroy() var/turf/T = get_turf(src) @@ -99,7 +101,7 @@ //This is on /cleanable because fuck this ancient mess /obj/effect/decal/cleanable/proc/on_entered(datum/source, atom/movable/AM) SIGNAL_HANDLER - if(iscarbon(AM) && blood_state && bloodiness >= 40) + if(iscarbon(AM) && bloodiness >= 40) SEND_SIGNAL(AM, COMSIG_STEP_ON_BLOOD, src) update_appearance() @@ -114,27 +116,35 @@ * Checks if this decal is a valid decal that can be blood crawled in. */ /obj/effect/decal/cleanable/proc/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return bloodiness - - return FALSE - -/** - * Gets the color associated with the any blood present on this decal. If there is no blood, returns null. - */ -/obj/effect/decal/cleanable/proc/get_blood_color() - switch(blood_state) - if(BLOOD_STATE_HUMAN) - return rgb(149, 10, 10) - if(BLOOD_STATE_XENO) - return rgb(43, 186, 0) - if(BLOOD_STATE_OIL) - return rgb(22, 22, 22) - - return null + return decal_reagent == /datum/reagent/blood /obj/effect/decal/cleanable/proc/handle_merge_decal(obj/effect/decal/cleanable/merger) if(!merger) return if(merger.reagents && reagents) reagents.trans_to(merger, reagents.total_volume) + +/// Increments or decrements the bloodiness value +/obj/effect/decal/cleanable/proc/adjust_bloodiness(by_amount) + if(by_amount == 0) + return FALSE + if(QDELING(src)) + return FALSE + + bloodiness = clamp((bloodiness + by_amount), 0, BLOOD_POOL_MAX) + update_appearance() + return TRUE + +/// Called before attempting to scoop up reagents from this decal to only load reagents when necessary +/obj/effect/decal/cleanable/proc/lazy_init_reagents() + return + +#ifdef TESTING +/obj/effect/decal/cleanable/update_overlays() + . = ..() + if(bloodiness) + var/mutable_appearance/blah_text = new() + blah_text.maptext = MAPTEXT_TINY_UNICODE("[bloodiness]") + blah_text.appearance_flags |= (KEEP_APART|RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM) + . += blah_text +#endif diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm index 6da917e8aab9..8083249d742d 100644 --- a/code/game/objects/effects/decals/cleanable/aliens.dm +++ b/code/game/objects/effects/decals/cleanable/aliens.dm @@ -7,13 +7,14 @@ icon_state = "xfloor1" random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") bloodiness = BLOOD_AMOUNT_PER_DECAL - blood_state = BLOOD_STATE_XENO beauty = -250 clean_type = CLEAN_TYPE_BLOOD + decal_reagent = /datum/blood_type/xenomorph::reagent_type + reagent_amount = 15 /obj/effect/decal/cleanable/xenoblood/Initialize(mapload) . = ..() - add_blood_DNA(list("UNKNOWN DNA" = "X*")) + add_blood_DNA(list("UNKNOWN DNA" = /datum/blood_type/xenomorph)) /obj/effect/decal/cleanable/xenoblood/xsplatter random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5") @@ -100,10 +101,6 @@ icon_state = "xgiblarvatorso" random_icon_states = list("xgiblarvahead", "xgiblarvatorso") -/obj/effect/decal/cleanable/blood/xtracks +/obj/effect/decal/cleanable/xenoblood/xtracks icon_state = "xtracks" random_icon_states = null - -/obj/effect/decal/cleanable/blood/xtracks/Initialize(mapload) - . = ..() - add_blood_DNA(list("Unknown DNA" = "X*")) diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index 08715536511e..c1ea447f78a6 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -1,6 +1,6 @@ /obj/effect/decal/cleanable/blood name = "blood" - desc = "It's red and gooey. Perhaps it's the chef's cooking?" + desc = "It's weird and gooey. Perhaps it's the chef's cooking?" icon = 'icons/effects/blood.dmi' icon_state = "floor1" random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") @@ -8,64 +8,141 @@ bloodiness = BLOOD_AMOUNT_PER_DECAL beauty = -100 clean_type = CLEAN_TYPE_BLOOD - var/should_dry = TRUE - var/dryname = "dried blood" //when the blood lasts long enough, it becomes dry and gets a new name - var/drydesc = "Looks like it's been here a while. Eew." //as above - var/drytime = 0 + decal_reagent = /datum/reagent/blood + bloodiness = BLOOD_AMOUNT_PER_DECAL + color = COLOR_BLOOD + appearance_flags = parent_type::appearance_flags | KEEP_TOGETHER + /// Can this blood dry out? + var/can_dry = TRUE + /// Is this blood dried out? + var/dried = FALSE + + /// The "base name" of the blood, IE the "pool of" in "pool of blood" + var/base_name = "pool of" + /// When dried, this is prefixed to the name + var/dry_prefix = "dried" + /// When dried, this becomes the desc of the blood + var/dry_desc = "Looks like it's been here a while. Eew." + + /// How long it takes to dry out + var/drying_time = 5 MINUTES + /// The process to drying out, recorded in deciseconds + var/drying_progress = 0 + /// Color matrix applied to dried blood via filter to make it look dried + var/static/list/blood_dry_filter_matrix = list( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + -0.75, -0.75, -0.75, 0, + ) var/count = 0 var/footprint_sprite = null /obj/effect/decal/cleanable/blood/Initialize(mapload) . = ..() - if(!should_dry) - return - if(bloodiness) - start_drying() - else - dry() - -/obj/effect/decal/cleanable/blood/process() - if(world.time > drytime) - dry() + START_PROCESSING(SSblood_drying, src) + if(color && can_dry && !dried) + update_blood_drying_effect() /obj/effect/decal/cleanable/blood/Destroy() - STOP_PROCESSING(SSobj, src) + STOP_PROCESSING(SSblood_drying, src) return ..() -/obj/effect/decal/cleanable/blood/proc/get_timer() - drytime = world.time + 3 MINUTES +#define DRY_FILTER_KEY "dry_effect" + +/obj/effect/decal/cleanable/blood/proc/update_blood_drying_effect() + if(!can_dry) + remove_filter(DRY_FILTER_KEY) // I GUESS + return + + var/existing_filter = get_filter(DRY_FILTER_KEY) + if(dried) + if(existing_filter) + animate(existing_filter) // just stop existing animations and force it to the end state + return + add_filter(DRY_FILTER_KEY, 2, color_matrix_filter(blood_dry_filter_matrix)) + return + + if(existing_filter) + remove_filter(DRY_FILTER_KEY) + + add_filter(DRY_FILTER_KEY, 2, color_matrix_filter()) + transition_filter(DRY_FILTER_KEY, color_matrix_filter(blood_dry_filter_matrix), drying_time - drying_progress) -/obj/effect/decal/cleanable/blood/proc/start_drying() - get_timer() - START_PROCESSING(SSobj, src) +#undef DRY_FILTER_KEY + +/obj/effect/decal/cleanable/blood/proc/get_blood_string() + var/list/all_dna = GET_ATOM_BLOOD_DNA(src) + var/list/all_blood_names = list() + for(var/dna_sample in all_dna) + var/datum/blood_type/blood = GLOB.blood_types[all_dna[dna_sample]] + all_blood_names |= lowertext(initial(blood.reagent_type.name)) + return english_list(all_blood_names) + +/obj/effect/decal/cleanable/blood/process(seconds_per_tick) + if(dried || !can_dry) + return PROCESS_KILL + + adjust_bloodiness(-0.4 * BLOOD_PER_UNIT_MODIFIER * seconds_per_tick) + drying_progress += (seconds_per_tick * 1 SECONDS) + if(drying_progress >= drying_time + SSblood_drying.wait) // Do it next tick when we're done + dry() + +/obj/effect/decal/cleanable/blood/update_name(updates) + . = ..() + name = initial(name) + if(base_name) + name = "[base_name] [get_blood_string()]" + if(dried && dry_prefix) + name = "[dry_prefix] [name]" + +/obj/effect/decal/cleanable/blood/update_desc(updates) + . = ..() + desc = initial(desc) + if(dried && dry_desc) + desc = dry_desc ///This is what actually "dries" the blood. Returns true if it's all out of blood to dry, and false otherwise /obj/effect/decal/cleanable/blood/proc/dry() - if(bloodiness > 20) - bloodiness -= BLOOD_AMOUNT_PER_DECAL - get_timer() + dried = TRUE + reagents?.clear_reagents() + update_appearance() + update_blood_drying_effect() + STOP_PROCESSING(SSblood_drying, src) + return TRUE + +/obj/effect/decal/cleanable/blood/lazy_init_reagents() + var/list/all_dna = GET_ATOM_BLOOD_DNA(src) + var/list/reagents_to_add = list() + for(var/dna_sample in all_dna) + var/datum/blood_type/blood = GLOB.blood_types[all_dna[dna_sample]] + reagents_to_add += blood.reagent_type + + var/num_reagents = length(reagents_to_add) + for(var/reagent_type in reagents_to_add) + reagents.add_reagent(reagent_type, round((bloodiness * 0.2 * BLOOD_PER_UNIT_MODIFIER) / num_reagents, CHEMICAL_VOLUME_ROUNDING)) + +/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/merger) + if(merger.dried) // New blood will lie on dry blood return FALSE - else - name = dryname - desc = drydesc - bloodiness = 0 - color = COLOR_GRAY //not all blood splatters have their own sprites... It still looks pretty nice - STOP_PROCESSING(SSobj, src) - return TRUE - -/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) - C.add_blood_DNA(GET_ATOM_BLOOD_DNA(src)) - if (bloodiness) - C.bloodiness = min((C.bloodiness + bloodiness), BLOOD_AMOUNT_PER_DECAL) return ..() +/obj/effect/decal/cleanable/blood/handle_merge_decal(obj/effect/decal/cleanable/blood/merger) + . = ..() + merger.add_blood_DNA(GET_ATOM_BLOOD_DNA(src)) + merger.adjust_bloodiness(bloodiness) + merger.drying_progress -= (bloodiness * BLOOD_PER_UNIT_MODIFIER) // goes negative = takes longer to dry + merger.update_blood_drying_effect() + /obj/effect/decal/cleanable/blood/old bloodiness = 0 icon_state = "floor1-old" /obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases) - add_blood_DNA(list("Non-human DNA" = random_blood_type())) // Needs to happen before ..() + add_blood_DNA(list("UNKNOWN DNA" = random_human_blood_type())) . = ..() + dry() /obj/effect/decal/cleanable/blood/splatter icon_state = "gibbl1" @@ -83,19 +160,18 @@ desc = "They look like tracks left by wheels." random_icon_states = null beauty = -50 - dryname = "dried tracks" - drydesc = "Some old bloody tracks left by wheels. Machines are evil, perhaps." + base_name = "" + dry_desc = "Some old bloody tracks left by wheels. Machines are evil, perhaps." -/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose - name = "blood" - icon = 'icons/effects/blood.dmi' +/obj/effect/decal/cleanable/blood/trail_holder + name = "trail of blood" desc = "Your instincts say you shouldn't be following these." beauty = -50 + icon_state = null + random_icon_states = null + base_name = "trail of" var/list/existing_dirs = list() -/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() - return TRUE - /obj/effect/decal/cleanable/blood/gibs name = "gibs" desc = "They look bloody and gruesome." @@ -107,8 +183,9 @@ mergeable_decal = FALSE turf_loc_check = FALSE - dryname = "rotting gibs" - drydesc = "They look bloody and gruesome while some terrible smell fills the air." + base_name = "" + dry_prefix = "rotting" + dry_desc = "They look bloody and gruesome while some terrible smell fills the air." decal_reagent = /datum/reagent/consumable/liquidgibs reagent_amount = 5 ///Information about the diseases our streaking spawns @@ -122,6 +199,10 @@ LAZYNULL(streak_diseases) return ..() + +/obj/effect/decal/cleanable/blood/gibs/get_blood_string() + return "" + /obj/effect/decal/cleanable/blood/gibs/replace_decal(obj/effect/decal/cleanable/C) return FALSE //Never fail to place us @@ -203,29 +284,25 @@ desc = "Space Jesus, why didn't anyone clean this up? They smell terrible." icon_state = "gib1-old" bloodiness = 0 - should_dry = FALSE - dryname = "old rotting gibs" - drydesc = "Space Jesus, why didn't anyone clean this up? They smell terrible." + dry_prefix = "" + dry_desc = "" /obj/effect/decal/cleanable/blood/gibs/old/Initialize(mapload, list/datum/disease/diseases) + add_blood_DNA(list("UNKNOWN DNA" = random_human_blood_type())) . = ..() - setDir(pick(1,2,4,8)) - add_blood_DNA(list("Non-human DNA" = random_blood_type())) + setDir(pick(GLOB.cardinals)) AddElement(/datum/element/swabable, CELL_LINE_TABLE_SLUDGE, CELL_VIRUS_TABLE_GENERIC, rand(2,4), 10) dry() /obj/effect/decal/cleanable/blood/drip name = "drips of blood" - desc = "It's red." + desc = "A spattering." icon_state = "drip5" //using drip5 since the others tend to blend in with pipes & wires. random_icon_states = list("drip1","drip2","drip3","drip4","drip5") - bloodiness = 0 - var/drips = 1 - dryname = "drips of blood" - drydesc = "It's red." - -/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in() - return TRUE + bloodiness = BLOOD_AMOUNT_PER_DECAL * 0.2 * BLOOD_PER_UNIT_MODIFIER + base_name = "drips of" + dry_desc = "A dried spattering." + drying_time = 1 MINUTES //BLOODY FOOTPRINTS @@ -235,7 +312,9 @@ icon = 'icons/effects/footprints.dmi' icon_state = "blood_shoes_enter" random_icon_states = null - blood_state = BLOOD_STATE_HUMAN //the icon state to load images from + bloodiness = 0 // set based on the bloodiness of the foot + base_name = "" + dry_desc = "HMM... SOMEONE WAS HERE!" var/entered_dirs = 0 var/exited_dirs = 0 @@ -245,9 +324,6 @@ /// List of species that have made footprints here. var/list/species_types = list() - dryname = "dried footprints" - drydesc = "HMM... SOMEONE WAS HERE!" - /obj/effect/decal/cleanable/blood/footprints/Initialize(mapload, footprint_sprite) src.footprint_sprite = footprint_sprite . = ..() @@ -256,6 +332,10 @@ entered_dirs |= dir //Keep the same appearance as in the map editor update_appearance(mapload ? (ALL) : (UPDATE_NAME | UPDATE_DESC)) + +/obj/effect/decal/cleanable/blood/footprints/get_blood_string() + return "" + //Rotate all of the footprint directions too /obj/effect/decal/cleanable/blood/footprints/setDir(newdir) if(dir == newdir) @@ -284,7 +364,7 @@ name = "footprints" if(FOOTPRINT_SPRITE_PAWS) name = "pawprints" - dryname = "dried [name]" + base_name = "dried [name]" return ..() /obj/effect/decal/cleanable/blood/footprints/update_desc(updates) @@ -293,7 +373,7 @@ /obj/effect/decal/cleanable/blood/footprints/update_icon() . = ..() - alpha = min(BLOODY_FOOTPRINT_BASE_ALPHA + (255 - BLOODY_FOOTPRINT_BASE_ALPHA) * bloodiness / (BLOOD_ITEM_MAX / 2), 255) + alpha = min(BLOODY_FOOTPRINT_BASE_ALPHA + (255 - BLOODY_FOOTPRINT_BASE_ALPHA) * bloodiness / ((BLOOD_ITEM_MAX * BLOOD_PER_UNIT_MODIFIER) / 2), 255) //Cache of bloody footprint images //Key: @@ -303,17 +383,23 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache) /obj/effect/decal/cleanable/blood/footprints/update_overlays() . = ..() + var/icon_state_to_use = "blood" + if(SPECIES_MONKEY in species_types) + icon_state_to_use += "paw" + else if(BODYPART_ID_DIGITIGRADE in species_types) + icon_state_to_use += "claw" + for(var/Ddir in GLOB.cardinals) if(entered_dirs & Ddir) - var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[footprint_sprite]-[blood_state]-[Ddir]"] + var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[icon_state_to_use]-[Ddir]"] if(!bloodstep_overlay) - GLOB.bloody_footprints_cache["entered-[footprint_sprite]-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]_[footprint_sprite]_enter", dir = Ddir) + GLOB.bloody_footprints_cache["entered-[icon_state_to_use]-[Ddir]"] = bloodstep_overlay = image(icon, "[icon_state_to_use]1", dir = Ddir) . += bloodstep_overlay if(exited_dirs & Ddir) - var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[footprint_sprite]-[blood_state]-[Ddir]"] + var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[icon_state_to_use]-[Ddir]"] if(!bloodstep_overlay) - GLOB.bloody_footprints_cache["exited-[footprint_sprite]-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]_[footprint_sprite]_exit", dir = Ddir) + GLOB.bloody_footprints_cache["exited-[icon_state_to_use]-[Ddir]"] = bloodstep_overlay = image(icon, "[icon_state_to_use]2", dir = Ddir) . += bloodstep_overlay @@ -340,21 +426,15 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache) else . += "[icon2html('icons/mob/species/human/bodyparts.dmi', user, "[species]_l_leg")] Some [species] feet." -/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/blood/blood_decal) - if(blood_state != blood_decal.blood_state || footprint_sprite != blood_decal.footprint_sprite) //We only replace footprints of the same type as us - return FALSE - return ..() - -/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return TRUE - return FALSE - /obj/effect/decal/cleanable/blood/hitsplatter name = "blood splatter" pass_flags = PASSTABLE | PASSGRILLE icon_state = "hitsplatter1" random_icon_states = list("hitsplatter1", "hitsplatter2", "hitsplatter3") + + base_name = "" + can_dry = FALSE // No point + /// The turf we just came from, so we can back up when we hit a wall var/turf/prev_loc /// The cached info about the blood @@ -368,8 +448,9 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache) /// Type of squirt decals we should try to create when moving var/line_type = /obj/effect/decal/cleanable/blood/line -/obj/effect/decal/cleanable/blood/hitsplatter/Initialize(mapload, splatter_strength) +/obj/effect/decal/cleanable/blood/hitsplatter/Initialize(mapload, splatter_strength, blood_color = COLOR_BLOOD) . = ..() + color = blood_color prev_loc = loc //Just so we are sure prev_loc exists if(splatter_strength) src.splatter_strength = splatter_strength @@ -419,9 +500,11 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache) if(line_type && isturf(loc)) var/obj/effect/decal/cleanable/line = locate(line_type) in loc if(line) + line.color = color line.add_blood_DNA(blood_dna_info) else line = new line_type(loc, get_dir(prev_loc, loc)) + line.color = color line.add_blood_DNA(blood_dna_info) line.alpha = 0 animate(line, alpha = initial(line.alpha), time = 2) @@ -454,6 +537,8 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache) var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new(prev_loc) final_splatter.pixel_x = (dir == EAST ? 32 : (dir == WEST ? -32 : 0)) final_splatter.pixel_y = (dir == NORTH ? 32 : (dir == SOUTH ? -32 : 0)) + final_splatter.add_blood_DNA(GET_ATOM_BLOOD_DNA(src)) + final_splatter.add_blood_DNA(blood_dna_info) else // This will only happen if prev_loc is not even a turf, which is highly unlikely. abstract_move(bumped_atom) qdel(src) @@ -463,6 +548,8 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache) if(!the_window.fulltile) return var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new + final_splatter.add_blood_DNA(GET_ATOM_BLOOD_DNA(src)) + final_splatter.add_blood_DNA(blood_dna_info) final_splatter.forceMove(the_window) the_window.vis_contents += final_splatter the_window.bloodied = TRUE diff --git a/code/game/objects/effects/decals/cleanable/robots.dm b/code/game/objects/effects/decals/cleanable/robots.dm index d3af1e2846b0..4ba60142f57e 100644 --- a/code/game/objects/effects/decals/cleanable/robots.dm +++ b/code/game/objects/effects/decals/cleanable/robots.dm @@ -7,7 +7,6 @@ icon_state = "gib1" layer = LOW_OBJ_LAYER random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") - blood_state = BLOOD_STATE_OIL bloodiness = BLOOD_AMOUNT_PER_DECAL mergeable_decal = FALSE beauty = -50 @@ -16,6 +15,7 @@ /obj/effect/decal/cleanable/robot_debris/Initialize(mapload) . = ..() RegisterSignal(src, COMSIG_MOVABLE_PIPE_EJECTING, PROC_REF(on_pipe_eject)) + add_blood_DNA(list("UNKNOWN DNA" = /datum/blood_type/oil)) /obj/effect/decal/cleanable/robot_debris/proc/streak(list/directions, mapload=FALSE) var/direction = pick(directions) @@ -76,29 +76,16 @@ icon = 'icons/mob/silicon/robots.dmi' icon_state = "floor1" random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") - blood_state = BLOOD_STATE_OIL - bloodiness = BLOOD_AMOUNT_PER_DECAL + bloodiness = BLOOD_AMOUNT_PER_DECAL * 2 beauty = -100 clean_type = CLEAN_TYPE_BLOOD decal_reagent = /datum/reagent/fuel/oil reagent_amount = 30 -/obj/effect/decal/cleanable/oil/attackby(obj/item/I, mob/living/user) - var/attacked_by_hot_thing = I.get_temperature() - if(attacked_by_hot_thing) - user.visible_message(span_warning("[user] tries to ignite [src] with [I]!"), span_warning("You try to ignite [src] with [I].")) - log_combat(user, src, (attacked_by_hot_thing < 480) ? "tried to ignite" : "ignited", I) - fire_act(attacked_by_hot_thing) - return - return ..() - -/obj/effect/decal/cleanable/oil/fire_act(exposed_temperature, exposed_volume) - if(exposed_temperature < 480) - return - visible_message(span_danger("[src] catches fire!")) - var/turf/T = get_turf(src) - qdel(src) - new /obj/effect/hotspot(T) +/obj/effect/decal/cleanable/oil/Initialize(mapload, list/datum/disease/diseases) + . = ..() + AddElement(/datum/element/easy_ignite) + add_blood_DNA(list("UNKNOWN DNA" = /datum/blood_type/oil)) /obj/effect/decal/cleanable/oil/streak icon_state = "streak1" diff --git a/code/game/objects/effects/spawners/gibspawner.dm b/code/game/objects/effects/spawners/gibspawner.dm index 86dd2bac70bf..20ca27dbdd16 100644 --- a/code/game/objects/effects/spawners/gibspawner.dm +++ b/code/game/objects/effects/spawners/gibspawner.dm @@ -39,7 +39,7 @@ dna_to_add = temp_mob.get_blood_dna_list() qdel(temp_mob) else - dna_to_add = list("Non-human DNA" = random_blood_type()) //else, generate a random bloodtype for it. + dna_to_add = list("UNKNOWN DNA" = random_human_blood_type()) //else, generate a random bloodtype for it. for(var/i in 1 to gibtypes.len) diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index d03cfea3f178..168a3f9b0883 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -73,9 +73,6 @@ target_pixel_y = 8 animate(src, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = duration) -/obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter - splatter_type = "xsplatter" - /obj/effect/temp_visual/dir_setting/speedbike_trail name = "speedbike trails" icon_state = "ion_fade" diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index 0ef37ba652a6..b6ce3135c571 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -374,23 +374,20 @@ " // divs do not need extra linebreak */ // Blood Level - if(target.has_dna()) - var/mob/living/carbon/carbontarget = target - var/blood_id = carbontarget.get_blood_id() - if(blood_id) - if(carbontarget.is_bleeding()) + // NON-MODULE CHANGE + if(target.has_dna() && target.get_blood_type()) + if(iscarbon(target)) + var/mob/living/carbon/bleeder = target + if(bleeder.is_bleeding()) render_list += "Subject is bleeding!\n" - var/blood_percent = round((carbontarget.blood_volume / BLOOD_VOLUME_NORMAL) * 100) - var/blood_type = carbontarget.dna.blood_type - if(blood_id != /datum/reagent/blood) // special blood substance - var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] - blood_type = R ? R.name : blood_id - if(carbontarget.blood_volume <= BLOOD_VOLUME_SAFE && carbontarget.blood_volume > BLOOD_VOLUME_OKAY) - render_list += "Blood level: LOW [blood_percent] %, [carbontarget.blood_volume] cl, [span_info("type: [blood_type]")]\n" - else if(carbontarget.blood_volume <= BLOOD_VOLUME_OKAY) - render_list += "Blood level: CRITICAL [blood_percent] %, [carbontarget.blood_volume] cl, [span_info("type: [blood_type]")]\n" - else - render_list += "Blood level: [blood_percent] %, [carbontarget.blood_volume] cl, type: [blood_type]\n" + var/blood_percent = round((target.blood_volume / BLOOD_VOLUME_NORMAL) * 100) + var/blood_type = "[target.get_blood_type() || "None"]" + if(target.blood_volume <= BLOOD_VOLUME_SAFE && target.blood_volume > BLOOD_VOLUME_OKAY) + render_list += "Blood level: LOW [blood_percent] %, [target.blood_volume] cl, [span_info("type: [blood_type]")]\n" + else if(target.blood_volume <= BLOOD_VOLUME_OKAY) + render_list += "Blood level: CRITICAL [blood_percent] %, [target.blood_volume] cl, [span_info("type: [blood_type]")]\n" + else + render_list += "Blood level: [blood_percent] %, [target.blood_volume] cl, type: [blood_type]\n" // Cybernetics if(iscarbon(target)) diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm index 9bf31fa4158b..2b4b24787326 100644 --- a/code/game/objects/items/dna_injector.dm +++ b/code/game/objects/items/dna_injector.dm @@ -57,7 +57,7 @@ target.real_name = fields["name"] target.dna.unique_enzymes = fields["UE"] target.name = target.real_name - target.dna.blood_type = fields["blood_type"] + target.dna.human_blood_type = blood_name_to_blood_type(fields["blood_type"]) if(fields["UI"]) //UI+UE target.dna.unique_identity = merge_text(target.dna.unique_identity, fields["UI"]) if(fields["UF"]) @@ -131,11 +131,11 @@ if(!target.dna.previous["UE"]) target.dna.previous["UE"] = target.dna.unique_enzymes if(!target.dna.previous["blood_type"]) - target.dna.previous["blood_type"] = target.dna.blood_type + target.dna.previous["blood_type"] = "[initial(target.dna.human_blood_type.name)]" target.real_name = fields["name"] target.dna.unique_enzymes = fields["UE"] target.name = target.real_name - target.dna.blood_type = fields["blood_type"] + target.dna.human_blood_type = blood_name_to_blood_type(fields["blood_type"]) target.dna.temporary_mutations[UE_CHANGED] = endtime if(fields["UI"]) //UI+UE if(!target.dna.previous["UI"]) diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 517a05a58ee7..32bb9ef266a4 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -338,6 +338,7 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \ new/datum/stack_recipe("ore box", /obj/structure/ore_box, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_CONTAINERS),\ new/datum/stack_recipe("wooden crate", /obj/structure/closet/crate/wooden, 6, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE),\ new/datum/stack_recipe("baseball bat", /obj/item/melee/baseball_bat, 5, time = 1.5 SECONDS, check_density = FALSE, category = CAT_WEAPON_MELEE),\ + new/datum/stack_recipe("wooden crutch", /obj/item/cane/crutch/wood, 5, time = 1.5 SECONDS, check_density = FALSE, category = CAT_WEAPON_MELEE),\ new/datum/stack_recipe("loom", /obj/structure/loom, 10, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS), \ new/datum/stack_recipe("mortar", /obj/item/reagent_containers/cup/mortar, 3, category = CAT_CHEMISTRY), \ new/datum/stack_recipe("firebrand", /obj/item/match/firebrand, 2, time = 10 SECONDS, category = CAT_TOOLS), \ diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index 5e0e5b1c4092..90d2e616de9a 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -493,6 +493,21 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT * 0.5) attack_verb_continuous = list("bludgeons", "whacks", "disciplines", "thrashes") attack_verb_simple = list("bludgeon", "whack", "discipline", "thrash") + /// Only exists so the white cane doesn't spawn with its "effects" while unextended + var/start_with_effects = TRUE + +/obj/item/cane/Initialize(mapload) + . = ..() + if(start_with_effects) + add_effects() + +/obj/item/cane/proc/add_effects() + ADD_TRAIT(src, TRAIT_BLIND_TOOL, INNATE_TRAIT) + AddComponent(/datum/component/limbless_aid) + +/obj/item/cane/proc/remove_effects() + REMOVE_TRAIT(src, TRAIT_BLIND_TOOL, INNATE_TRAIT) + qdel(GetComponent(/datum/component/limbless_aid)) /obj/item/cane/white name = "white cane" @@ -504,6 +519,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 1 w_class = WEIGHT_CLASS_SMALL custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 6) + start_with_effects = FALSE /obj/item/cane/white/Initialize(mapload) . = ..() @@ -527,6 +543,11 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/cane/white/proc/on_transform(obj/item/source, mob/user, active) SIGNAL_HANDLER + if(active) + add_effects() + else + remove_effects() + if(user) balloon_alert(user, active ? "extended" : "collapsed") playsound(src, 'sound/weapons/batonextend.ogg', 50, TRUE) diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm index 793447312b94..cafd9556fc4a 100644 --- a/code/game/turfs/closed/walls.dm +++ b/code/game/turfs/closed/walls.dm @@ -63,20 +63,29 @@ ADD_TRAIT(src, TRAIT_UNDENSE, LEANING_TRAIT) ADD_TRAIT(src, TRAIT_EXPANDED_FOV, LEANING_TRAIT) + ADD_TRAIT(src, TRAIT_NO_LEG_AID, LEANING_TRAIT) visible_message(span_notice("[src] leans against \the [wall]!"), \ span_notice("You lean against \the [wall]!")) - RegisterSignals(src, list(COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_DIR_CHANGE), PROC_REF(stop_leaning)) + RegisterSignals(src, list(COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_LIVING_RESIST), PROC_REF(stop_leaning)) + RegisterSignal(src, COMSIG_ATOM_DIR_CHANGE, PROC_REF(stop_leaning_dir)) update_fov() is_leaning = TRUE + update_limbless_locomotion() + +/mob/living/carbon/proc/stop_leaning_dir(datum/source, old_dir, new_dir) + SIGNAL_HANDLER + if(new_dir != old_dir) + stop_leaning() /mob/living/carbon/proc/stop_leaning() SIGNAL_HANDLER - UnregisterSignal(src, list(COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_DIR_CHANGE)) + UnregisterSignal(src, list(COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_DIR_CHANGE, COMSIG_LIVING_RESIST)) is_leaning = FALSE pixel_y = base_pixel_y + body_position_pixel_x_offset pixel_x = base_pixel_y + body_position_pixel_y_offset REMOVE_TRAIT(src, TRAIT_UNDENSE, LEANING_TRAIT) REMOVE_TRAIT(src, TRAIT_EXPANDED_FOV, LEANING_TRAIT) + REMOVE_TRAIT(src, TRAIT_NO_LEG_AID, LEANING_TRAIT) update_fov() /turf/closed/wall/Initialize(mapload) diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index 77fe81fdb7e9..33d902e84b4e 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -26,7 +26,7 @@ human.eye_color_left = random_eye_color human.eye_color_right = random_eye_color - human.dna.blood_type = random_blood_type() + human.dna.human_blood_type = random_human_blood_type() human.dna.features["mcolor"] = "#[random_color()]" human.dna.species.randomize_active_underwear_only(human) diff --git a/code/modules/admin/verbs/list_exposer.dm b/code/modules/admin/verbs/list_exposer.dm index 851bd901c1e5..c2c48963051e 100644 --- a/code/modules/admin/verbs/list_exposer.dm +++ b/code/modules/admin/verbs/list_exposer.dm @@ -33,7 +33,7 @@ for(var/entry in GLOB.human_list) var/mob/living/carbon/human/subject = entry if(subject.ckey) - data += " " + data += " [subject] [subject.dna.unique_enzymes] [subject.dna.blood_type] " data += "" usr << browse(data, "window=DNA;size=440x410") diff --git a/code/modules/antagonists/abductor/equipment/glands/blood.dm b/code/modules/antagonists/abductor/equipment/glands/blood.dm index 40c2b3c4d726..8a6b43a7baf2 100644 --- a/code/modules/antagonists/abductor/equipment/glands/blood.dm +++ b/code/modules/antagonists/abductor/equipment/glands/blood.dm @@ -15,4 +15,4 @@ var/mob/living/carbon/human/H = owner var/datum/species/species = H.dna.species to_chat(H, span_warning("You feel your blood heat up for a moment.")) - species.exotic_blood = get_random_reagent_id() + species.exotic_bloodtype = pick(subtypesof(/datum/blood_type)) diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 611f190ce8d4..1d4ca52b0902 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -123,11 +123,15 @@ brother2.set_species(/datum/species/moth) var/icon/brother1_icon = render_preview_outfit(/datum/outfit/job/quartermaster, brother1) - brother1_icon.Blend(icon('icons/effects/blood.dmi', "maskblood"), ICON_OVERLAY) + var/icon/blood1_icon = icon('icons/effects/blood.dmi', "maskblood") + blood1_icon.Blend(COLOR_BLOOD, ICON_MULTIPLY) + brother1_icon.Blend(blood1_icon, ICON_OVERLAY) brother1_icon.Shift(WEST, 8) var/icon/brother2_icon = render_preview_outfit(/datum/outfit/job/scientist/consistent, brother2) - brother2_icon.Blend(icon('icons/effects/blood.dmi', "uniformblood"), ICON_OVERLAY) + var/icon/blood2_icon = icon('icons/effects/blood.dmi', "uniformblood") + blood2_icon.Blend(COLOR_BLOOD, ICON_MULTIPLY) + brother2_icon.Blend(blood2_icon, ICON_OVERLAY) brother2_icon.Shift(EAST, 8) var/icon/final_icon = brother1_icon diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm index 1acb4fff68a0..35d85efb4edf 100644 --- a/code/modules/antagonists/cult/blood_magic.dm +++ b/code/modules/antagonists/cult/blood_magic.dm @@ -762,30 +762,25 @@ uses = 0 playsound(get_turf(M), 'sound/magic/staff_healing.ogg', 25) user.Beam(M, icon_state="sendbeam", time = 1 SECONDS) - if(istype(target, /obj/effect/decal/cleanable/blood)) + if(istype(target, /obj/effect/decal/cleanable/blood) || isturf(target)) blood_draw(target, user) ..() /obj/item/melee/blood_magic/manipulator/proc/blood_draw(atom/target, mob/living/carbon/human/user) - var/temp = 0 - var/turf/T = get_turf(target) - if(T) - for(var/obj/effect/decal/cleanable/blood/B in view(T, 2)) - if(B.blood_state == BLOOD_STATE_HUMAN) - if(B.bloodiness == 100) //Bonus for "pristine" bloodpools, also to prevent cheese with footprint spam - temp += 30 - else - temp += max((B.bloodiness**2)/800,1) - new /obj/effect/temp_visual/cult/turf/floor(get_turf(B)) - qdel(B) - for(var/obj/effect/decal/cleanable/trail_holder/TH in view(T, 2)) - qdel(TH) - if(temp) - user.Beam(T,icon_state="drainbeam", time = 15) + var/blood_to_gain = 0 + var/turf/our_turf = get_turf(target) + if(our_turf) + for(var/obj/effect/decal/cleanable/blood/blood_around_us in range(our_turf,2)) + if(blood_around_us.decal_reagent == /datum/reagent/blood) + blood_to_gain += max(blood_around_us.bloodiness * 0.12 * BLOOD_PER_UNIT_MODIFIER, 1) + new /obj/effect/temp_visual/cult/turf/floor(get_turf(blood_around_us)) + qdel(blood_around_us) + if(blood_to_gain) + user.Beam(our_turf,icon_state="drainbeam", time = 15) new /obj/effect/temp_visual/cult/sparks(get_turf(user)) - playsound(T, 'sound/magic/enter_blood.ogg', 50) - to_chat(user, span_cultitalic("Your blood rite has gained [round(temp)] charge\s from blood sources around you!")) - uses += max(1, round(temp)) + playsound(our_turf, 'sound/magic/enter_blood.ogg', 50) + to_chat(user, span_cultitalic("Your blood rite has gained [round(blood_to_gain)] charge\s from blood sources around you!")) + uses += max(1, round(blood_to_gain)) /obj/item/melee/blood_magic/manipulator/attack_self(mob/living/user) if(IS_CULTIST(user)) diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm index 7a03bace1050..f82b9b829533 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm @@ -86,7 +86,7 @@ * Slow and stop any blood loss the owner's experiencing. */ /datum/status_effect/unholy_determination/proc/adjust_bleed_wounds() - if(!iscarbon(owner) || !owner.blood_volume) + if(HAS_TRAIT(owner, TRAIT_NOBLOOD)) return if(owner.blood_volume < BLOOD_VOLUME_NORMAL) diff --git a/code/modules/antagonists/obsessed/obsessed.dm b/code/modules/antagonists/obsessed/obsessed.dm index 585867d5767b..d58f9b8a82be 100644 --- a/code/modules/antagonists/obsessed/obsessed.dm +++ b/code/modules/antagonists/obsessed/obsessed.dm @@ -41,7 +41,9 @@ victim_dummy.update_body_parts() var/icon/obsessed_icon = render_preview_outfit(preview_outfit) - obsessed_icon.Blend(icon('icons/effects/blood.dmi', "uniformblood"), ICON_OVERLAY) + var/icon/blood_icon = icon('icons/effects/blood.dmi', "uniformblood") + blood_icon.Blend(COLOR_BLOOD, ICON_MULTIPLY) + obsessed_icon.Blend(blood_icon, ICON_OVERLAY) var/icon/final_icon = finish_preview_icon(obsessed_icon) diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 46995388a78c..b4fb08e2cd75 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -43,6 +43,9 @@ /// How many zones (body parts, not precise) we have disabled so far, for naming purposes var/zones_disabled + /// If supplied, this is what overlay is used when applying blood effects when worn + var/blood_overlay_type = "" + /// A lazily initiated "food" version of the clothing for moths. // This intentionally does not use the edible component, for a few reasons. // 1. Effectively everything that wants something edible, from now and into the future, @@ -591,3 +594,20 @@ BLIND // can't see anything /obj/item/clothing/remove_fantasy_bonuses(bonus) set_armor(get_armor().generate_new_with_modifiers(list(ARMOR_ALL = -bonus))) return ..() + +/obj/item/clothing/proc/appears_bloody() + return GET_ATOM_BLOOD_DNA_LENGTH(src) && can_be_bloody && !(item_flags & NO_BLOOD_ON_ITEM) + +/obj/item/clothing/worn_overlays(mutable_appearance/standing, isinhands, icon_file) + . = ..() + if(isinhands) + return + + if(blood_overlay_type && appears_bloody()) + var/mutable_appearance/blood_overlay + if(clothing_flags & LARGE_WORN_ICON) + blood_overlay = mutable_appearance('icons/effects/64x64.dmi', "[blood_overlay_type]blood_large") + else + blood_overlay = mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood") + blood_overlay.color = get_blood_dna_color() + . += blood_overlay diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm index 3f5503ca0eed..ec842866a6e6 100644 --- a/code/modules/clothing/gloves/_gloves.dm +++ b/code/modules/clothing/gloves/_gloves.dm @@ -16,6 +16,7 @@ attack_verb_simple = list("challenge") strip_delay = 20 equip_delay_other = 40 + blood_overlay_type = "glove" // Path variable. If defined, will produced the type through interaction with wirecutters. var/cut_type = null /// Used for handling bloody gloves leaving behind bloodstains on objects. Will be decremented whenever a bloodstain is left behind, and be incremented when the gloves become bloody. @@ -41,13 +42,11 @@ /obj/item/clothing/gloves/worn_overlays(mutable_appearance/standing, isinhands = FALSE) . = ..() - if(!isinhands) + if(isinhands) return if(damaged_clothes) . += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves") - if(GET_ATOM_BLOOD_DNA_LENGTH(src)) - . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands") /obj/item/clothing/gloves/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) ..() diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm index a67d65b5139d..7693776c7abc 100644 --- a/code/modules/clothing/head/_head.dm +++ b/code/modules/clothing/head/_head.dm @@ -6,6 +6,7 @@ righthand_file = 'icons/mob/inhands/clothing/hats_righthand.dmi' body_parts_covered = HEAD slot_flags = ITEM_SLOT_HEAD + blood_overlay_type = "helmetblood" ///Special throw_impact for hats to frisbee hats at people to place them on their heads/attempt to de-hat them. /obj/item/clothing/head/throw_impact(atom/hit_atom, datum/thrownthing/thrownthing) @@ -64,12 +65,6 @@ if(damaged_clothes) . += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet") - if(GET_ATOM_BLOOD_DNA_LENGTH(src)) - if(clothing_flags & LARGE_WORN_ICON) - . += mutable_appearance('icons/effects/64x64.dmi', "helmetblood_large") - else - . += mutable_appearance('icons/effects/blood.dmi', "helmetblood") - if(!(flags_inv & HIDEHAIR)) if(ismob(loc)) diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index df868a09db20..e760afdc616a 100644 --- a/code/modules/clothing/masks/_masks.dm +++ b/code/modules/clothing/masks/_masks.dm @@ -7,6 +7,7 @@ slot_flags = ITEM_SLOT_MASK strip_delay = 40 equip_delay_other = 40 + blood_overlay_type = "mask" supports_variations_flags = CLOTHING_SNOUTED_VARIATION var/modifies_speech = FALSE var/mask_adjusted = FALSE @@ -55,8 +56,9 @@ if(body_parts_covered & HEAD) if(damaged_clothes) . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") - if(GET_ATOM_BLOOD_DNA_LENGTH(src)) - . += mutable_appearance('icons/effects/blood.dmi', "maskblood") + +/obj/item/clothing/mask/appears_bloody() + return ..() && (body_parts_covered & HEAD) /obj/item/clothing/mask/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) ..() diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm index d4d5379c6e3c..8431673288a6 100644 --- a/code/modules/clothing/neck/_neck.dm +++ b/code/modules/clothing/neck/_neck.dm @@ -5,6 +5,7 @@ slot_flags = ITEM_SLOT_NECK strip_delay = 40 equip_delay_other = 40 + blood_overlay_type = "mask" /obj/item/clothing/neck/worn_overlays(mutable_appearance/standing, isinhands = FALSE) . = ..() @@ -14,8 +15,9 @@ if(body_parts_covered & HEAD) if(damaged_clothes) . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") - if(GET_ATOM_BLOOD_DNA_LENGTH(src)) - . += mutable_appearance('icons/effects/blood.dmi', "maskblood") + +/obj/item/clothing/neck/appears_bloody() + return ..() && (body_parts_covered & HEAD) /obj/item/clothing/neck/bowtie name = "bow tie" diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index c4e8f9133175..68de16d25337 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -13,6 +13,7 @@ armor_type = /datum/armor/clothing_shoes slowdown = SHOES_SLOWDOWN strip_delay = 1 SECONDS + blood_overlay_type = "shoe" var/offset = 0 var/equipped_before_drop = FALSE ///Whether these shoes have laces that can be tied/untied @@ -53,11 +54,6 @@ if(damaged_clothes) . += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe") - if(GET_ATOM_BLOOD_DNA_LENGTH(src)) - if(clothing_flags & LARGE_WORN_ICON) - . += mutable_appearance('icons/effects/64x64.dmi', "shoeblood_large") - else - . += mutable_appearance('icons/effects/blood.dmi', "shoeblood") /obj/item/clothing/shoes/examine(mob/user) . = ..() diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm index 0e9efa5b3fb9..8630f573f31e 100644 --- a/code/modules/clothing/suits/_suits.dm +++ b/code/modules/clothing/suits/_suits.dm @@ -14,7 +14,7 @@ drop_sound = 'sound/items/handling/cloth_drop.ogg' pickup_sound = 'sound/items/handling/cloth_pickup.ogg' slot_flags = ITEM_SLOT_OCLOTHING - var/blood_overlay_type = "suit" + blood_overlay_type = "suit" limb_integrity = 0 // disabled for most exo-suits var/suittoggled = FALSE // sec duster toggling and more supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION @@ -30,8 +30,6 @@ if(damaged_clothes) . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]") - if(GET_ATOM_BLOOD_DNA_LENGTH(src)) - . += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood") var/mob/living/carbon/human/wearer = loc if(!ishuman(wearer) || !wearer.w_uniform) diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index 9b6b70f9dd7c..2e5cae08d5da 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -11,6 +11,7 @@ drop_sound = 'sound/items/handling/cloth_drop.ogg' pickup_sound = 'sound/items/handling/cloth_pickup.ogg' limb_integrity = 30 + blood_overlay_type = "uniform" /// Has this undersuit been freshly laundered and, as such, imparts a mood bonus for wearing var/freshly_laundered = FALSE @@ -88,8 +89,6 @@ if(damaged_clothes) . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform") - if(GET_ATOM_BLOOD_DNA_LENGTH(src)) - . += mutable_appearance('icons/effects/blood.dmi', "uniformblood") if(accessory_overlay) . += accessory_overlay diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm index 47734b398ab8..7da9153548ad 100644 --- a/code/modules/detectivework/scanner.dm +++ b/code/modules/detectivework/scanner.dm @@ -169,7 +169,7 @@ for(var/bloodtype in blood) LAZYADD(det_data[DETSCAN_CATEGORY_BLOOD], \ - "Type: [blood[bloodtype]] DNA (UE): [bloodtype]") + "Type: [GLOB.blood_types[blood[bloodtype]]] DNA (UE): [bloodtype]") // sends it off to be modified by the items SEND_SIGNAL(scanned_atom, COMSIG_DETECTIVE_SCANNED, user, det_data) diff --git a/code/modules/forensics/forensics_helpers.dm b/code/modules/forensics/forensics_helpers.dm index 081d6f25d4f1..6b837b711a43 100644 --- a/code/modules/forensics/forensics_helpers.dm +++ b/code/modules/forensics/forensics_helpers.dm @@ -67,13 +67,13 @@ return FALSE /obj/add_blood_DNA(list/blood_DNA_to_add) - . = ..() if (isnull(blood_DNA_to_add)) - return . + return FALSE if (forensics) forensics.inherit_new(blood_DNA = blood_DNA_to_add) else forensics = new(src, blood_DNA = blood_DNA_to_add) + cached_blood_dna_color = null return TRUE /obj/item/add_blood_DNA(list/blood_DNA_to_add) @@ -82,8 +82,10 @@ return ..() /obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - transfer_blood = rand(2, 4) - return ..() + . = ..() + if(.) + transfer_blood = rand(2, 4) + return . /turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) var/obj/effect/decal/cleanable/blood/splatter/blood_splatter = locate() in src @@ -109,6 +111,7 @@ forensics = new(src) forensics.inherit_new(blood_DNA = blood_DNA_to_add) blood_in_hands = rand(2, 4) + cached_blood_dna_color = null update_worn_gloves() return TRUE diff --git a/code/modules/hydroponics/grown/replicapod.dm b/code/modules/hydroponics/grown/replicapod.dm index be51fe2d9e98..788ce008c295 100644 --- a/code/modules/hydroponics/grown/replicapod.dm +++ b/code/modules/hydroponics/grown/replicapod.dm @@ -208,6 +208,6 @@ most_plentiful_reagent.Cut() most_plentiful_reagent[reagent] = reagents_add[reagent] - podman.dna.species.exotic_blood = most_plentiful_reagent[1] + //podman.dna.species.exotic_blood = most_plentiful_reagent[1] //Monkestation edit BLOOD_DATUM: This needs to be looked into investigate_log("[key_name(mind)] cloned as a podman via [src] in [parent]", INVESTIGATE_BOTANY) return result diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index e5f2a15b6f81..64232a63aad5 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -342,7 +342,7 @@ var/amount = max(1, round((edible_vol)*(potency/100) * reagent_overflow_mod, 1)) //the plant will always have at least 1u of each of the reagents in its reagent production traits var/list/data if(rid == /datum/reagent/blood) // Hack to make blood in plants always O- - data = list("blood_type" = "O-") + data = list("blood_type" = /datum/blood_type/crew/human/o_minus) if(istype(grown_edible) && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin)) data = grown_edible.tastes // apple tastes of apple. T.reagents.add_reagent(rid, amount, data) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index d6a9075abf80..3e8254563d6c 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -187,7 +187,7 @@ return FALSE //nonliving mobs don't have hands /mob/living/put_in_hand_check(obj/item/I) - if(istype(I) && (((mobility_flags & MOBILITY_PICKUP) || ((stat >= SOFT_CRIT && (stat != DEAD && stat != UNCONSCIOUS)))) || (I.item_flags & ABSTRACT)) \ + if(istype(I) && (((mobility_flags & MOBILITY_PICKUP) || ((stat >= SOFT_CRIT && (stat != DEAD && stat != UNCONSCIOUS && stat != HARD_CRIT)))) || (I.item_flags & ABSTRACT)) \ && !(SEND_SIGNAL(src, COMSIG_LIVING_TRY_PUT_IN_HAND, I) & COMPONENT_LIVING_CANT_PUT_IN_HAND)) return TRUE return FALSE diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm index 5da570369f17..2cdf99b9d2ed 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -98,9 +98,7 @@ )) ///blood we can clean var/static/list/cleanable_blood = typecacheof(list( - /obj/effect/decal/cleanable/xenoblood, /obj/effect/decal/cleanable/blood, - /obj/effect/decal/cleanable/trail_holder, )) ///pests we hunt var/static/list/huntable_pests = typecacheof(list( diff --git a/code/modules/mob/living/basic/space_fauna/demon/demon_items.dm b/code/modules/mob/living/basic/space_fauna/demon/demon_items.dm index 8cdd7a6cf53f..804aba100d8b 100644 --- a/code/modules/mob/living/basic/space_fauna/demon/demon_items.dm +++ b/code/modules/mob/living/basic/space_fauna/demon/demon_items.dm @@ -54,3 +54,9 @@ icon = 'icons/obj/medical/organs/organs.dmi' icon_state = "innards" random_icon_states = null + base_name = "" + can_dry = FALSE + +/obj/effect/decal/cleanable/blood/innards/Initialize(mapload, list/datum/disease/diseases) + . = ..() + add_blood_DNA(list("DEMON BLOOD" = /datum/blood_type/animal)) diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 4c9635ef9742..74c65fe4fccb 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -7,46 +7,53 @@ // Takes care blood loss and regeneration /mob/living/carbon/human/handle_blood(seconds_per_tick, times_fired) - if(HAS_TRAIT(src, TRAIT_NOBLOOD) || (HAS_TRAIT(src, TRAIT_FAKEDEATH))) + if(HAS_TRAIT(src, TRAIT_NOBLOOD) || HAS_TRAIT(src, TRAIT_FAKEDEATH)) return - if(bodytemperature < BLOOD_STOP_TEMP || (HAS_TRAIT(src, TRAIT_HUSK))) //cold or husked people do not pump the blood. + if(bodytemperature < BLOOD_STOP_TEMP || HAS_TRAIT(src, TRAIT_HUSK)) //cold or husked people do not pump the blood. return - //Blood regeneration if there is some space - if(blood_volume < BLOOD_VOLUME_NORMAL && !HAS_TRAIT(src, TRAIT_NOHUNGER)) - var/nutrition_ratio = 0 - switch(nutrition) - if(0 to NUTRITION_LEVEL_STARVING) - nutrition_ratio = 0.2 - if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) - nutrition_ratio = 0.4 - if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) - nutrition_ratio = 0.6 - if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) - nutrition_ratio = 0.8 - else - nutrition_ratio = 1 - if(satiety > 80) - nutrition_ratio *= 1.25 - adjust_nutrition(-nutrition_ratio * HUNGER_FACTOR * seconds_per_tick) - blood_volume = min(blood_volume + (BLOOD_REGEN_FACTOR * nutrition_ratio * seconds_per_tick), BLOOD_VOLUME_NORMAL) - - // we call lose_blood() here rather than quirk/process() to make sure that the blood loss happens in sync with life() - if(HAS_TRAIT(src, TRAIT_BLOOD_DEFICIENCY)) - var/datum/quirk/blooddeficiency/blooddeficiency = get_quirk(/datum/quirk/blooddeficiency) - if(!isnull(blooddeficiency)) - blooddeficiency.lose_blood(seconds_per_tick) + var/sigreturn = SEND_SIGNAL(src, COMSIG_HUMAN_ON_HANDLE_BLOOD, seconds_per_tick, times_fired) + if(sigreturn & HANDLE_BLOOD_HANDLED) + return + + if(!(sigreturn & HANDLE_BLOOD_NO_NUTRITION_DRAIN)) + if(blood_volume < BLOOD_VOLUME_NORMAL && !HAS_TRAIT(src, TRAIT_NOHUNGER)) + var/nutrition_ratio = 0 + switch(nutrition) + if(0 to NUTRITION_LEVEL_STARVING) + nutrition_ratio = 0.2 + if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) + nutrition_ratio = 0.4 + if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) + nutrition_ratio = 0.6 + if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) + nutrition_ratio = 0.8 + else + nutrition_ratio = 1 + if(satiety > 80) + nutrition_ratio *= 1.25 + adjust_nutrition(-nutrition_ratio * HUNGER_FACTOR * seconds_per_tick) + blood_volume = min(blood_volume + (BLOOD_REGEN_FACTOR * nutrition_ratio * seconds_per_tick), BLOOD_VOLUME_NORMAL) + + // // we call lose_blood() here rather than quirk/process() to make sure that the blood loss happens in sync with life() + // if(HAS_TRAIT(src, TRAIT_BLOOD_DEFICIENCY)) + // var/datum/quirk/blooddeficiency/blooddeficiency = get_quirk(/datum/quirk/blooddeficiency) + // if(!isnull(blooddeficiency)) + // blooddeficiency.lose_blood(seconds_per_tick) //Effects of bloodloss - var/word = pick("dizzy","woozy","faint") - if(!HAS_TRAIT(src, TRAIT_NO_BLOODLOSS_DAMAGE)) //monkestation addition + if(!(sigreturn & HANDLE_BLOOD_NO_EFFECTS)) + var/word = pick("dizzy","woozy","faint") switch(blood_volume) - if(BLOOD_VOLUME_EXCESS to BLOOD_VOLUME_MAX_LETHAL) + if(BLOOD_VOLUME_MAX_LETHAL to INFINITY) if(SPT_PROB(7.5, seconds_per_tick)) to_chat(src, span_userdanger("Blood starts to tear your skin apart. You're going to burst!")) investigate_log("has been gibbed by having too much blood.", INVESTIGATE_DEATHS) inflate_gib() + if(BLOOD_VOLUME_EXCESS to BLOOD_VOLUME_MAX_LETHAL) + if(SPT_PROB(5, seconds_per_tick)) + to_chat(src, span_warning("You feel your skin swelling.")) if(BLOOD_VOLUME_MAXIMUM to BLOOD_VOLUME_EXCESS) if(SPT_PROB(5, seconds_per_tick)) to_chat(src, span_warning("You feel terribly bloated.")) @@ -93,7 +100,7 @@ //Makes a blood drop, leaking amt units of blood from the mob /mob/living/carbon/proc/bleed(amt, no_visual = FALSE) - if(!blood_volume || (status_flags & GODMODE) || HAS_TRAIT(src, TRAIT_NOBLOOD)) + if((status_flags & GODMODE) || HAS_TRAIT(src, TRAIT_NOBLOOD)) return blood_volume = max(blood_volume - amt, 0) @@ -107,7 +114,7 @@ /// A helper to see how much blood we're losing per tick /mob/living/carbon/proc/get_bleed_rate() - if(!blood_volume || HAS_TRAIT(src, TRAIT_NOBLOOD)) + if(HAS_TRAIT(src, TRAIT_NOBLOOD)) return 0 var/bleed_amt = 0 for(var/X in bodyparts) @@ -116,7 +123,8 @@ return bleed_amt /mob/living/carbon/human/get_bleed_rate() - return ..() * physiology.bleed_mod + . = ..() + . *= physiology.bleed_mod /** * bleed_warn() is used to for carbons with an active client to occasionally receive messages warning them about their bleeding status (if applicable) @@ -126,7 +134,7 @@ * * forced- */ /mob/living/carbon/proc/bleed_warn(bleed_amt = 0, forced = FALSE) - if(!blood_volume || !client) + if(!client || HAS_TRAIT(src, TRAIT_NOBLOOD)) return if(!COOLDOWN_FINISHED(src, bleeding_message_cd) && !forced) return @@ -177,10 +185,6 @@ to_chat(src, span_warning("[bleeding_severity][rate_of_change]")) COOLDOWN_START(src, bleeding_message_cd, next_cooldown) -/mob/living/carbon/human/bleed_warn(bleed_amt = 0, forced = FALSE) - if(!HAS_TRAIT(src, TRAIT_NOBLOOD)) - return ..() - /mob/living/proc/restore_blood() blood_volume = initial(blood_volume) @@ -196,7 +200,8 @@ //Gets blood from mob to a container or other mob, preserving all data in it. /mob/living/proc/transfer_blood_to(atom/movable/AM, amount, forced) - if(!blood_volume || !AM.reagents) + var/datum/blood_type/blood = get_blood_type() + if(isnull(blood) || !AM.reagents) return FALSE if(blood_volume < BLOOD_VOLUME_BAD && !forced) return FALSE @@ -204,35 +209,12 @@ if(blood_volume < amount) amount = blood_volume - var/blood_id = get_blood_id() - if(!blood_id) - return FALSE - blood_volume -= amount - var/list/blood_data = get_blood_data(blood_id) - - if(iscarbon(AM)) - var/mob/living/carbon/C = AM - if(blood_id == C.get_blood_id())//both mobs have the same blood substance - if(blood_id == /datum/reagent/blood) //normal blood - if(blood_data["viruses"]) - for(var/thing in blood_data["viruses"]) - var/datum/disease/advanced/D = thing - if((D.spread_flags & DISEASE_SPREAD_SPECIAL) || (D.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS)) - continue - C.infect_disease(D, TRUE, "Infected [key_name(C)] (Infected Blood 100% Infection)") - if(!(blood_data["blood_type"] in get_safe_blood(C.dna.blood_type))) - C.reagents.add_reagent(/datum/reagent/toxin, amount * 0.5) - return TRUE - - C.blood_volume = min(C.blood_volume + round(amount, 0.1), BLOOD_VOLUME_MAX_LETHAL) - return TRUE - - AM.reagents.add_reagent(blood_id, amount, blood_data, bodytemperature) + AM.reagents.add_reagent(blood.reagent_type, amount, blood.get_blood_data(src), bodytemperature) return TRUE - +/* /mob/living/proc/get_blood_data(blood_id) return @@ -276,129 +258,46 @@ var/datum/quirk/T = V blood_data["quirks"] += T.type return blood_data +*/ -//get the id of the substance this mob use as blood. -/mob/proc/get_blood_id() - return +/mob/living/proc/get_blood_type() + RETURN_TYPE(/datum/blood_type) + if(HAS_TRAIT(src, TRAIT_NOBLOOD)) + return null + return GLOB.blood_types[/datum/blood_type/animal] -/mob/living/simple_animal/get_blood_id() - if(blood_volume) - return /datum/reagent/blood +/mob/living/silicon/get_blood_type() + return GLOB.blood_types[/datum/blood_type/oil] -/mob/living/carbon/human/get_blood_id() - if(HAS_TRAIT(src, TRAIT_HUSK)) - return - if(check_holidays(APRIL_FOOLS) && is_clown_job(mind?.assigned_role)) - return /datum/reagent/colorful_reagent - if(dna.species.exotic_blood) - return dna.species.exotic_blood - else if(HAS_TRAIT(src, TRAIT_NOBLOOD)) - return - return /datum/reagent/blood - -// This is has more potential uses, and is probably faster than the old proc. -/proc/get_safe_blood(bloodtype) - . = list() - if(!bloodtype) - return +/mob/living/simple_animal/bot/get_blood_type() + return GLOB.blood_types[/datum/blood_type/oil] - var/static/list/bloodtypes_safe = list( - "A-" = list("A-", "O-"), - "A+" = list("A-", "A+", "O-", "O+"), - "B-" = list("B-", "O-"), - "B+" = list("B-", "B+", "O-", "O+"), - "AB-" = list("A-", "B-", "O-", "AB-"), - "AB+" = list("A-", "A+", "B-", "B+", "O-", "O+", "AB-", "AB+"), - "O-" = list("O-"), - "O+" = list("O-", "O+"), - "L" = list("L"), - "U" = list("A-", "A+", "B-", "B+", "O-", "O+", "AB-", "AB+", "L", "U") - ) - - var/safe = bloodtypes_safe[bloodtype] - if(safe) - . = safe +/mob/living/basic/bot/get_blood_type() + return GLOB.blood_types[/datum/blood_type/oil] -//to add a splatter of blood or other mob liquid. -/mob/living/proc/add_splatter_floor(turf/splattered, small_drip) - if((get_blood_id() != /datum/reagent/blood) || HAS_TRAIT(src, TRAIT_NOBLOOD)) - return - if(!splattered) - splattered = get_turf(src) - if(isclosedturf(splattered) || (isgroundlessturf(splattered) && !GET_TURF_BELOW(splattered))) - return +/mob/living/carbon/alien/get_blood_type() + if(HAS_TRAIT(src, TRAIT_HUSK) || HAS_TRAIT(src, TRAIT_NOBLOOD)) + return null + return GLOB.blood_types[/datum/blood_type/xenomorph] - var/datum/reagent/blood_type = get_blood_id() - var/list/temp_blood_DNA - if(small_drip) - if(!QDELETED(splattered.liquids)) - var/list/blood_drop = list(get_blood_id() = 0.1) - splattered.add_liquid_list(blood_drop, FALSE, 300) - return - // Only a certain number of drips (or one large splatter) can be on a given turf. - var/obj/effect/decal/cleanable/blood/drip/drop = locate() in splattered - if(drop) - if(drop.drips < 5) - splattered?.pollute_turf(/datum/pollutant/metallic_scent, 5) - drop.drips++ - drop.add_overlay(pick(drop.random_icon_states)) - drop.transfer_mob_blood_dna(src) - return - else - temp_blood_DNA = GET_ATOM_BLOOD_DNA(drop) //we transfer the dna from the drip to the splatter - qdel(drop)//the drip is replaced by a bigger splatter - else - splattered?.pollute_turf(/datum/pollutant/metallic_scent, 5) - drop = new(splattered, get_static_viruses()) - drop.transfer_mob_blood_dna(src) - return +/mob/living/carbon/human/get_blood_type() + if(HAS_TRAIT(src, TRAIT_HUSK) || isnull(dna) || HAS_TRAIT(src, TRAIT_NOBLOOD)) + return null + if(check_holidays(APRIL_FOOLS) && is_clown_job(mind?.assigned_role)) + return GLOB.blood_types[/datum/blood_type/clown] + if(dna.species.exotic_bloodtype) + return GLOB.blood_types[dna.species.exotic_bloodtype] + return GLOB.blood_types[dna.human_blood_type] +//to add a splatter of blood or other mob liquid. +/mob/living/proc/add_splatter_floor(turf/blood_turf = get_turf(src), small_drip) // Create a bit of metallic pollution, as that's how blood smells - splattered.pollute_turf(/datum/pollutant/metallic_scent, 30) + blood_turf.pollute_turf(/datum/pollutant/metallic_scent, 30) // TODO Move to blood_datum + return get_blood_type()?.make_blood_splatter(src, blood_turf, small_drip) - // Find a blood decal or create a new one. - var/obj/effect/decal/cleanable/blood/B = locate() in splattered - if(!B) - B = new /obj/effect/decal/cleanable/blood/splatter(splattered, get_static_viruses()) - if(QDELETED(B)) //Give it up - return - B.bloodiness = min((B.bloodiness + BLOOD_AMOUNT_PER_DECAL), BLOOD_POOL_MAX) - B.transfer_mob_blood_dna(src) //give blood info to the blood decal. - if(temp_blood_DNA) - B.add_blood_DNA(temp_blood_DNA) - - if(B.count < 10 ) - if(blood_type) - B.color = initial(blood_type.color) - B.count ++ - B.transfer_mob_blood_dna(src) - B.transfer_mob_blood_dna(src) //give blood info to the blood decal. - if(temp_blood_DNA) - B.add_blood_DNA(temp_blood_DNA) - - if(B.count > 9) - qdel(B) - var/list/blood_large = list(get_blood_id() = 20) - splattered.add_liquid_list(blood_large, FALSE, 300) - -/mob/living/carbon/human/add_splatter_floor(turf/T, small_drip) - if(!HAS_TRAIT(src, TRAIT_NOBLOOD)) - ..() - -/mob/living/carbon/alien/add_splatter_floor(turf/T, small_drip) - if(!T) - T = get_turf(src) - var/obj/effect/decal/cleanable/xenoblood/B = locate() in T.contents - if(!B) - B = new(T) - B.add_blood_DNA(list("UNKNOWN DNA" = "X*")) - -/mob/living/silicon/robot/add_splatter_floor(turf/T, small_drip) - if(!T) - T = get_turf(src) - var/obj/effect/decal/cleanable/oil/B = locate() in T.contents - if(!B) - B = new(T) +/mob/living/proc/do_splatter_effect(splat_dir = pick(GLOB.cardinals)) + var/obj/effect/temp_visual/dir_setting/bloodsplatter/splatter = new(get_turf(src), splat_dir, get_blood_type()?.color) + splatter.color = get_blood_type()?.color /** * This proc is a helper for spraying blood for things like slashing/piercing wounds and dismemberment. @@ -425,6 +324,7 @@ return for(var/i in 1 to amount) var/obj/effect/decal/cleanable/blood/particle/droplet = new(loc) + droplet.color = get_blood_type()?.color droplet.add_blood_DNA(GET_ATOM_BLOOD_DNA(src)) droplet.pixel_z = rand(min_pixel_z, max_pixel_z) droplet.start_movement(angle + rand(min_deviation, max_deviation)) diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm index 3dc911e206f9..3b4b834e67d9 100644 --- a/code/modules/mob/living/brain/brain.dm +++ b/code/modules/mob/living/brain/brain.dm @@ -10,7 +10,7 @@ /mob/living/brain/Initialize(mapload) . = ..() create_dna(src) - stored_dna.initialize_dna(random_blood_type()) + stored_dna.initialize_dna() if(isturf(loc)) //not spawned in an MMI or brain organ (most likely adminspawned) var/obj/item/organ/internal/brain/OB = new(loc) //we create a new brain organ for it. OB.brainmob = src diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 8ed56358f71e..5a0325146bbd 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1224,12 +1224,16 @@ /// if any of our bodyparts are bleeding /mob/living/carbon/proc/is_bleeding() + if(HAS_TRAIT(src, TRAIT_NOBLOOD)) + return FALSE for(var/obj/item/bodypart/part as anything in bodyparts) if(part.get_modified_bleed_rate()) return TRUE /// get our total bleedrate /mob/living/carbon/proc/get_total_bleed_rate() + if(HAS_TRAIT(src, TRAIT_NOBLOOD)) + return 0 var/total_bleed_rate = 0 for(var/obj/item/bodypart/part as anything in bodyparts) total_bleed_rate += part.get_modified_bleed_rate() diff --git a/code/modules/mob/living/carbon/carbon_movement.dm b/code/modules/mob/living/carbon/carbon_movement.dm index b6e0615ab77f..dd32d275108b 100644 --- a/code/modules/mob/living/carbon/carbon_movement.dm +++ b/code/modules/mob/living/carbon/carbon_movement.dm @@ -17,52 +17,23 @@ adjust_nutrition(-(HUNGER_FACTOR) * 0.1) -/mob/living/carbon/set_usable_legs(new_value) - . = ..() - if(isnull(.)) - return - if(. == 0) - if(usable_legs != 0) //From having no usable legs to having some. - REMOVE_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) - REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) - else if(usable_legs == 0 && !(movement_type & (FLYING | FLOATING))) //From having usable legs to no longer having them. - ADD_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) - if(!usable_hands) - ADD_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) - - /mob/living/carbon/set_usable_hands(new_value) . = ..() if(isnull(.)) return if(. == 0) REMOVE_TRAIT(src, TRAIT_HANDS_BLOCKED, LACKING_MANIPULATION_APPENDAGES_TRAIT) - if(usable_hands != 0) //From having no usable hands to having some. - REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) else if(usable_hands == 0 && default_num_hands > 0) //From having usable hands to no longer having them. ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, LACKING_MANIPULATION_APPENDAGES_TRAIT) - if(!usable_legs && !(movement_type & (FLYING | FLOATING))) - ADD_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) /mob/living/carbon/on_movement_type_flag_enabled(datum/source, flag, old_movement_type) . = ..() if(movement_type & (FLYING | FLOATING) && !(old_movement_type & (FLYING | FLOATING))) - remove_movespeed_modifier(/datum/movespeed_modifier/limbless) - remove_traits(list(TRAIT_FLOORED, TRAIT_IMMOBILIZED), LACKING_LOCOMOTION_APPENDAGES_TRAIT) + update_limbless_locomotion() + update_limbless_movespeed_mod() /mob/living/carbon/on_movement_type_flag_disabled(datum/source, flag, old_movement_type) . = ..() if(old_movement_type & (FLYING | FLOATING) && !(movement_type & (FLYING | FLOATING))) - var/limbless_slowdown = 0 - if(usable_legs < default_num_legs) - limbless_slowdown += (default_num_legs - usable_legs) * 3 - if(!usable_legs) - ADD_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) - if(usable_hands < default_num_hands) - limbless_slowdown += (default_num_hands - usable_hands) * 3 - if(!usable_hands) - ADD_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) - if(limbless_slowdown) - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/limbless, multiplicative_slowdown = limbless_slowdown) - else - remove_movespeed_modifier(/datum/movespeed_modifier/limbless) + update_limbless_locomotion() + update_limbless_movespeed_mod() diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index 2485d23eabef..1cc850789f0b 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -299,7 +299,9 @@ if(isnull(damage_overlay) && (iter_part.brutestate || iter_part.burnstate)) damage_overlay = mutable_appearance('icons/mob/effects/dam_mob.dmi', "blank", -DAMAGE_LAYER, appearance_flags = KEEP_TOGETHER) if(iter_part.brutestate) - damage_overlay.add_overlay("[iter_part.dmg_overlay_type]_[iter_part.body_zone]_[iter_part.brutestate]0") //we're adding icon_states of the base image as overlays + var/image/brute_overlay = image('icons/mob/effects/dam_mob.dmi', "[iter_part.dmg_overlay_type]_[iter_part.body_zone]_[iter_part.brutestate]0") + brute_overlay.color = iter_part.damage_color + damage_overlay.add_overlay(brute_overlay) //we're adding icon_states of the base image as overlays //we're adding icon_states of the base image as overlays if(iter_part.burnstate) damage_overlay.add_overlay("[iter_part.dmg_overlay_type]_[iter_part.body_zone]_0[iter_part.burnstate]") diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index a979550ce806..f5b8cd5c7b89 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -65,10 +65,8 @@ GLOBAL_LIST_EMPTY(features_by_species) var/digitigrade_customization = DIGITIGRADE_NEVER ///Does the species use skintones or not? As of now only used by humans. var/use_skintones = FALSE - ///If your race bleeds something other than bog standard blood, change this to reagent id. For example, ethereals bleed liquid electricity. - var/datum/reagent/exotic_blood - ///If your race uses a non standard bloodtype (A+, O-, AB-, etc). For example, lizards have L type blood. - var/exotic_bloodtype = "" + /// If your race uses a non standard bloodtype (typepath) + var/datum/blood_type/exotic_bloodtype ///The rate at which blood is passively drained by having the blood deficiency quirk. Some races such as slimepeople can regen their blood at different rates so this is to account for that var/blood_deficiency_drain_rate = BLOOD_REGEN_FACTOR + BLOOD_DEFICIENCY_MODIFIER // slightly above the regen rate so it slowly drains instead of regenerates. ///What the species drops when gibbed by a gibber machine. @@ -539,14 +537,6 @@ GLOBAL_LIST_EMPTY(features_by_species) INVOKE_ASYNC(src, PROC_REF(worn_items_fit_body_check), C, TRUE) - //Assigns exotic blood type if the species has one - if(exotic_bloodtype && C.dna.blood_type != exotic_bloodtype) - C.dna.blood_type = exotic_bloodtype - //Otherwise, check if the previous species had an exotic bloodtype and we do not have one and assign a random blood type - //(why the fuck is blood type not tied to a fucking DNA block?) - else if(old_species.exotic_bloodtype && !exotic_bloodtype) - C.dna.blood_type = random_blood_type() - if(ishuman(C)) var/mob/living/carbon/human/human = C for(var/obj/item/organ/external/organ_path as anything in external_organs) @@ -598,7 +588,7 @@ GLOBAL_LIST_EMPTY(features_by_species) /datum/species/proc/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) SHOULD_CALL_PARENT(TRUE) if(C.dna.species.exotic_bloodtype) - C.dna.blood_type = random_blood_type() + C.dna.human_blood_type = random_human_blood_type() if(NOMOUTH in species_traits) for(var/obj/item/bodypart/head/head in C.bodyparts) head.mouth = TRUE @@ -629,45 +619,6 @@ GLOBAL_LIST_EMPTY(features_by_species) SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src) -/** - * Proc called when mail goodies need to be updated for this species. - * - * Updates the mail goodies if that is required. e.g. for the blood deficiency quirk, which sends bloodbags to quirk holders, update the sent bloodpack to match the species' exotic blood. - * This is currently only used for the blood deficiency quirk but more can be added as needed. - * Arguments: - * * mob/living/carbon/human/recipient - the mob receiving the mail goodies - */ -/datum/species/proc/update_mail_goodies(mob/living/carbon/human/recipient) - update_quirk_mail_goodies(recipient, recipient.get_quirk(/datum/quirk/blooddeficiency)) - -/** - * Updates the mail goodies of a specific quirk. - * - * Updates the mail goodies belonging to a specific quirk. - * Add implementation as needed for each individual species. The base species proc should give the species the 'default' version of whatever mail goodies are required. - * Arguments: - * * mob/living/carbon/human/recipient - the mob receiving the mail goodies - * * datum/quirk/quirk - the quirk to update the mail goodies of. Use get_quirk(datum/quirk/some_quirk) to get the actual mob's quirk to pass. - * * list/mail_goodies - a list of mail goodies. Generally speaking you should not be using this argument on the initial function call. You should instead add to the species' implementation of this proc. - */ -/datum/species/proc/update_quirk_mail_goodies(mob/living/carbon/human/recipient, datum/quirk/quirk, list/mail_goodies) - if(isnull(quirk)) - return - if(length(mail_goodies)) - quirk.mail_goodies = mail_goodies - return - if(istype(quirk, /datum/quirk/blooddeficiency)) - if(HAS_TRAIT(recipient, TRAIT_NOBLOOD) && isnull(recipient.dna.species.exotic_blood)) // no blood packs should be sent in this case (like if a mob transforms into a plasmaman) - quirk.mail_goodies = list() - return - - - // The default case if no species implementation exists. Set quirk's mail_goodies to initial. - var/datum/quirk/readable_quirk = new quirk.type - quirk.mail_goodies = readable_quirk.mail_goodies - qdel(readable_quirk) // We have to do it this way because initial will not work on lists in this version of DM - return - /** * Handles the body of a human * @@ -1137,18 +1088,26 @@ GLOBAL_LIST_EMPTY(features_by_species) * Return True to not run the normal metabolism effects. * NOTE: If you return TRUE, that reagent will not be removed liike normal! You must handle it manually. */ -/datum/species/proc/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H, seconds_per_tick, times_fired) +/datum/species/proc/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/affected, seconds_per_tick, times_fired) SHOULD_CALL_PARENT(TRUE) - if(chem.type == exotic_blood) - H.blood_volume = min(H.blood_volume + round(chem.volume, 0.1), BLOOD_VOLUME_MAXIMUM) - H.reagents.del_reagent(chem.type) - return TRUE + // Cringe but blood handles this on its own + // This also has problems of its own but that's better fixed later I think + if(!istype(chem, /datum/reagent/blood)) + var/datum/blood_type/blood = affected.get_blood_type() + if(chem.type == blood?.reagent_type) + affected.blood_volume = min(affected.blood_volume + round(chem.volume, 0.1), BLOOD_VOLUME_MAXIMUM) + affected.reagents.del_reagent(chem.type) + return TRUE + if(chem.type == blood?.restoration_chem && affected.blood_volume < BLOOD_VOLUME_NORMAL) + affected.blood_volume += 0.25 * seconds_per_tick + affected.reagents.remove_reagent(chem.type, chem.metabolization_rate * seconds_per_tick) + return TRUE //This handles dumping unprocessable reagents. var/dump_reagent = TRUE - if((chem.process_flags & SYNTHETIC) && (H.dna.species.reagent_tag & PROCESS_SYNTHETIC)) //SYNTHETIC-oriented reagents require PROCESS_SYNTHETIC + if((chem.process_flags & SYNTHETIC) && (affected.dna.species.reagent_tag & PROCESS_SYNTHETIC)) //SYNTHETIC-oriented reagents require PROCESS_SYNTHETIC dump_reagent = FALSE - if((chem.process_flags & ORGANIC) && (H.dna.species.reagent_tag & PROCESS_ORGANIC)) //ORGANIC-oriented reagents require PROCESS_ORGANIC + if((chem.process_flags & ORGANIC) && (affected.dna.species.reagent_tag & PROCESS_ORGANIC)) //ORGANIC-oriented reagents require PROCESS_ORGANIC dump_reagent = FALSE if(dump_reagent) chem.holder.remove_reagent(chem.type, chem.metabolization_rate) @@ -1156,8 +1115,8 @@ GLOBAL_LIST_EMPTY(features_by_species) if(!chem.overdosed && chem.overdose_threshold && chem.volume >= chem.overdose_threshold) chem.overdosed = TRUE - chem.overdose_start(H) - H.log_message("has started overdosing on [chem.name] at [chem.volume] units.", LOG_GAME) + chem.overdose_start(affected) + affected.log_message("has started overdosing on [chem.name] at [chem.volume] units.", LOG_GAME) /** * Equip the outfit required for life. Replaces items currently worn. @@ -2158,22 +2117,13 @@ GLOBAL_LIST_EMPTY(features_by_species) SPECIES_PERK_DESC = "[plural_form] do not have blood.", )) - // Otherwise, check if their exotic blood is a valid typepath - else if(ispath(exotic_blood)) - to_add += list(list( - SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, - SPECIES_PERK_ICON = "tint", - SPECIES_PERK_NAME = initial(exotic_blood.name), - SPECIES_PERK_DESC = "[name] blood is [initial(exotic_blood.name)], which can make recieving medical treatment harder.", - )) - // Otherwise otherwise, see if they have an exotic bloodtype set else if(exotic_bloodtype) to_add += list(list( SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, SPECIES_PERK_ICON = "tint", - SPECIES_PERK_NAME = "Exotic Blood", - SPECIES_PERK_DESC = "[plural_form] have \"[exotic_bloodtype]\" type blood, which can make recieving medical treatment harder.", + SPECIES_PERK_NAME = initial(exotic_bloodtype.name), + SPECIES_PERK_DESC = "[name] blood is [initial(exotic_bloodtype.name)], which can make recieving medical treatment", )) return to_add diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index fb6762f4863f..3a5fd1e55c6f 100644 --- a/code/modules/mob/living/carbon/human/dummy.dm +++ b/code/modules/mob/living/carbon/human/dummy.dm @@ -94,7 +94,7 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy) return /proc/create_consistent_human_dna(mob/living/carbon/human/target) - target.dna.initialize_dna(skip_index = TRUE) + target.dna.initialize_dna(/datum/blood_type/crew/human/o_plus, skip_index = TRUE) target.dna.features["body_markings"] = "None" target.dna.features["ears"] = "None" target.dna.features["ethcolor"] = COLOR_WHITE diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index c67ff29b4e13..6a3a3552851b 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -928,16 +928,6 @@ return FALSE return ..() -/mob/living/carbon/human/is_bleeding() - if(HAS_TRAIT(src, TRAIT_NOBLOOD)) - return FALSE - return ..() - -/mob/living/carbon/human/get_total_bleed_rate() - if(HAS_TRAIT(src, TRAIT_NOBLOOD)) - return FALSE - return ..() - /mob/living/carbon/human/get_exp_list(minutes) . = ..() diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index ec7f482ae1d9..c3ee8c1eda32 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -177,7 +177,8 @@ There are several things that need to be remembered: if(isnull(gloves)) if(blood_in_hands && num_hands > 0) // When byond gives us filters that respect dirs we can just use an alpha mask for this but until then, two icons weeeee - var/mutable_appearance/hands_combined = mutable_appearance(layer = -GLOVES_LAYER, appearance_flags = KEEP_TOGETHER) + var/mutable_appearance/hands_combined = mutable_appearance(layer = -GLOVES_LAYER) + hands_combined.color = get_blood_dna_color() if(has_left_hand(check_disabled = FALSE)) hands_combined.overlays += mutable_appearance('icons/effects/blood.dmi', "bloodyhands_left") if(has_right_hand(check_disabled = FALSE)) diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index fc2719f8f7b5..ad7e3f870f1e 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -16,14 +16,13 @@ inherent_traits = list( TRAIT_CAN_USE_FLIGHT_POTION, TRAIT_TOXINLOVER, - TRAIT_NOBLOOD, ) mutanttongue = /obj/item/organ/internal/tongue/jelly mutantlungs = /obj/item/organ/internal/lungs/slime mutanteyes = /obj/item/organ/internal/eyes/jelly mutantheart = null meat = /obj/item/food/meat/slab/human/mutant/slime - exotic_blood = /datum/reagent/toxin/slimejelly + exotic_bloodtype = /datum/blood_type/slime blood_deficiency_drain_rate = JELLY_REGEN_RATE + BLOOD_DEFICIENCY_MODIFIER var/datum/action/innate/regenerate_limbs/regenerate_limbs liked_food = MEAT | BUGS @@ -55,53 +54,50 @@ if(ishuman(new_jellyperson)) regenerate_limbs = new regenerate_limbs.Grant(new_jellyperson) - update_mail_goodies(new_jellyperson) new_jellyperson.AddElement(/datum/element/soft_landing) + RegisterSignal(new_jellyperson, COMSIG_HUMAN_ON_HANDLE_BLOOD, PROC_REF(slime_blood)) /datum/species/jelly/on_species_loss(mob/living/carbon/former_jellyperson, datum/species/new_species, pref_load) if(regenerate_limbs) regenerate_limbs.Remove(former_jellyperson) former_jellyperson.RemoveElement(/datum/element/soft_landing) + UnregisterSignal(former_jellyperson, COMSIG_HUMAN_ON_HANDLE_BLOOD) return ..() -/datum/species/jelly/update_quirk_mail_goodies(mob/living/carbon/human/recipient, datum/quirk/quirk, list/mail_goodies = list()) - if(istype(quirk, /datum/quirk/blooddeficiency)) - mail_goodies += list( - /obj/item/reagent_containers/blood/toxin - ) - return ..() +/datum/species/jelly/proc/slime_blood(mob/living/carbon/human/slime, seconds_per_tick, times_fired) + SIGNAL_HANDLER -/datum/species/jelly/spec_life(mob/living/carbon/human/H, seconds_per_tick, times_fired) - if(H.stat == DEAD) //can't farm slime jelly from a dead slime/jelly person indefinitely - return + if(slime.stat == DEAD) + return NONE - if(!H.blood_volume) - H.blood_volume += JELLY_REGEN_RATE_EMPTY * seconds_per_tick - H.adjustBruteLoss(2.5 * seconds_per_tick) - to_chat(H, span_danger("You feel empty!")) + . = HANDLE_BLOOD_NO_NUTRITION_DRAIN|HANDLE_BLOOD_NO_EFFECTS - if(H.blood_volume < BLOOD_VOLUME_NORMAL) - if(H.nutrition >= NUTRITION_LEVEL_STARVING) - H.blood_volume += JELLY_REGEN_RATE * seconds_per_tick - if(H.blood_volume <= BLOOD_VOLUME_LOSE_NUTRITION) // don't lose nutrition if we are above a certain threshold, otherwise slimes on IV drips will still lose nutrition - H.adjust_nutrition(-1.25 * seconds_per_tick) + if(slime.blood_volume <= 0) + slime.blood_volume += JELLY_REGEN_RATE_EMPTY * seconds_per_tick + slime.adjustBruteLoss(2.5 * seconds_per_tick) + to_chat(slime, span_danger("You feel empty!")) - // we call lose_blood() here rather than quirk/process() to make sure that the blood loss happens in sync with life() - if(HAS_TRAIT(H, TRAIT_BLOOD_DEFICIENCY)) - var/datum/quirk/blooddeficiency/blooddeficiency = H.get_quirk(/datum/quirk/blooddeficiency) + if(slime.blood_volume < BLOOD_VOLUME_NORMAL) + if(slime.nutrition >= NUTRITION_LEVEL_STARVING) + slime.blood_volume += JELLY_REGEN_RATE * seconds_per_tick + if(slime.blood_volume <= BLOOD_VOLUME_LOSE_NUTRITION) // don't lose nutrition if we are above a certain threshold, otherwise slimes on IV drips will still lose nutrition + slime.adjust_nutrition(-1.25 * seconds_per_tick) + + if(HAS_TRAIT(slime, TRAIT_BLOOD_DEFICIENCY)) + var/datum/quirk/blooddeficiency/blooddeficiency = slime.get_quirk(/datum/quirk/blooddeficiency) if(!isnull(blooddeficiency)) blooddeficiency.lose_blood(seconds_per_tick) - if(H.blood_volume < BLOOD_VOLUME_OKAY) + if(slime.blood_volume < BLOOD_VOLUME_OKAY) if(SPT_PROB(2.5, seconds_per_tick)) - to_chat(H, span_danger("You feel drained!")) + to_chat(slime, span_danger("You feel drained!")) - if(H.blood_volume < BLOOD_VOLUME_BAD) - Cannibalize_Body(H) + if(slime.blood_volume < BLOOD_VOLUME_BAD) + Cannibalize_Body(slime) - if(regenerate_limbs) - regenerate_limbs.build_all_button_icons() + regenerate_limbs?.build_all_button_icons(UPDATE_BUTTON_STATUS) + return . /datum/species/jelly/proc/Cannibalize_Body(mob/living/carbon/human/H) var/list/limbs_to_consume = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - H.get_missing_limbs() @@ -126,7 +122,7 @@ SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, SPECIES_PERK_ICON = "tint", SPECIES_PERK_NAME = "Jelly Blood", - SPECIES_PERK_DESC = "[plural_form] don't have blood, but instead have toxic [initial(exotic_blood.name)]! \ + SPECIES_PERK_DESC = "[plural_form] don't have blood, but instead have toxic-to-humans Jelly! \ Jelly is extremely important, as losing it will cause you to lose limbs. Having low jelly will make medical treatment very difficult.", )) diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index a122cbb6f292..0423d940adcb 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -29,7 +29,7 @@ species_cookie = /obj/item/food/meat/slab meat = /obj/item/food/meat/slab/human/mutant/lizard skinned_type = /obj/item/stack/sheet/animalhide/lizard - exotic_bloodtype = "L" + exotic_bloodtype = /datum/blood_type/crew/lizard disliked_food = GRAIN | DAIRY | CLOTH | GROSS liked_food = GORE | MEAT | SEAFOOD | NUTS | BUGS inert_mutation = /datum/mutation/human/firebreath @@ -54,18 +54,6 @@ BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/lizard, ) -/datum/species/lizard/on_species_gain(mob/living/carbon/new_lizard, datum/species/old_species, pref_load) - . = ..() - if(ishuman(new_lizard)) - update_mail_goodies(new_lizard) - -/datum/species/lizard/update_quirk_mail_goodies(mob/living/carbon/human/recipient, datum/quirk/quirk, list/mail_goodies = list()) - if(istype(quirk, /datum/quirk/blooddeficiency)) - mail_goodies += list( - /obj/item/reagent_containers/blood/lizard - ) - return ..() - /// Lizards are cold blooded and do not stabilize body temperature naturally /datum/species/lizard/body_temperature_core(mob/living/carbon/human/humi, seconds_per_tick, times_fired) return diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index c451d48110b0..31f2c81c6ab4 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -19,7 +19,7 @@ heatmod = 1.5 payday_modifier = 0.75 meat = /obj/item/food/meat/slab/human/mutant/plant - exotic_blood = /datum/reagent/water + exotic_bloodtype = /datum/blood_type/water disliked_food = MEAT | DAIRY | SEAFOOD | BUGS liked_food = VEGETABLES | FRUIT | GRAIN changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT @@ -36,18 +36,6 @@ ass_image = 'icons/ass/asspodperson.png' -/datum/species/pod/on_species_gain(mob/living/carbon/new_podperson, datum/species/old_species, pref_load) - . = ..() - if(ishuman(new_podperson)) - update_mail_goodies(new_podperson) - -/datum/species/pod/update_quirk_mail_goodies(mob/living/carbon/human/recipient, datum/quirk/quirk, list/mail_goodies = list()) - if(istype(quirk, /datum/quirk/blooddeficiency)) - mail_goodies += list( - /obj/item/reagent_containers/blood/podperson - ) - return ..() - /datum/species/pod/spec_life(mob/living/carbon/human/H, seconds_per_tick, times_fired) if(H.stat == DEAD) return diff --git a/code/modules/mob/living/carbon/human/species_types/snail.dm b/code/modules/mob/living/carbon/human/species_types/snail.dm index 6dc24addcf86..0c66cd7bbe6e 100644 --- a/code/modules/mob/living/carbon/human/species_types/snail.dm +++ b/code/modules/mob/living/carbon/human/species_types/snail.dm @@ -18,7 +18,7 @@ mutanteyes = /obj/item/organ/internal/eyes/snail mutanttongue = /obj/item/organ/internal/tongue/snail - exotic_blood = /datum/reagent/lube + exotic_bloodtype = /datum/blood_type/snail bodypart_overrides = list( BODY_ZONE_HEAD = /obj/item/bodypart/head/snail, @@ -44,8 +44,6 @@ if(new_snailperson.dropItemToGround(bag)) //returns TRUE even if its null new_snailperson.equip_to_slot_or_del(new /obj/item/storage/backpack/snail(new_snailperson), ITEM_SLOT_BACK) new_snailperson.AddElement(/datum/element/snailcrawl) - if(ishuman(new_snailperson)) - update_mail_goodies(new_snailperson) /datum/species/snail/on_species_loss(mob/living/carbon/former_snailperson, datum/species/new_species, pref_load) . = ..() @@ -56,13 +54,6 @@ former_snailperson.temporarilyRemoveItemFromInventory(bag, TRUE) qdel(bag) -/datum/species/snail/update_quirk_mail_goodies(mob/living/carbon/human/recipient, datum/quirk/quirk, list/mail_goodies = list()) - if(istype(quirk, /datum/quirk/blooddeficiency)) - mail_goodies += list( - /obj/item/reagent_containers/blood/snail - ) - return ..() - /obj/item/storage/backpack/snail name = "snail shell" desc = "Worn by snails as armor and storage compartment." diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 72f06a970033..a7711da1a8cd 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -20,7 +20,7 @@ inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID mutant_bodyparts = list("wings" = "None") changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN - exotic_bloodtype = "U" + exotic_bloodtype = /datum/blood_type/universal blood_deficiency_drain_rate = BLOOD_DEFICIENCY_MODIFIER // vampires already passively lose blood, so this just makes them lose it slightly more quickly when they have blood deficiency. use_skintones = TRUE mutantheart = /obj/item/organ/internal/heart/vampire @@ -165,8 +165,8 @@ if(victim.stat == DEAD) to_chat(H, span_warning("You need a living victim!")) return - if(!victim.blood_volume || (victim.dna && (HAS_TRAIT(victim, TRAIT_NOBLOOD) || victim.dna.species.exotic_blood))) - to_chat(H, span_warning("[victim] doesn't have blood!")) + if(!istype(victim.get_blood_type(), /datum/blood_type/crew/human)) + to_chat(H, span_warning("[victim] doesn't have valid blood!")) return COOLDOWN_START(V, drain_cooldown, 3 SECONDS) if(victim.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 0)) @@ -186,7 +186,7 @@ playsound(H, 'sound/items/drink.ogg', 30, TRUE, -2) victim.blood_volume = clamp(victim.blood_volume - drained_blood, 0, BLOOD_VOLUME_MAXIMUM) H.blood_volume = clamp(H.blood_volume + drained_blood, 0, BLOOD_VOLUME_MAXIMUM) - if(!victim.blood_volume) + if(victim.blood_volume <= 0) to_chat(H, span_notice("You finish off [victim]'s blood supply.")) /obj/item/organ/internal/heart/vampire diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 93f4391e3b34..446f666cf0bf 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -559,7 +559,7 @@ dna.unique_enzymes = dna.previous["UE"] dna.previous.Remove("UE") if(dna.previous["blood_type"]) - dna.blood_type = dna.previous["blood_type"] + dna.human_blood_type = blood_name_to_blood_type(dna.previous["blood_type"]) dna.previous.Remove("blood_type") dna.temporary_mutations.Remove(mut) continue diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 9793371343fa..5453ee265380 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -70,7 +70,7 @@ update_damage_overlays() damage_dealt = actual_hit.get_damage() - delta // Unfortunately bodypart receive_damage doesn't return damage dealt so we do it manually else - damage_dealt = adjustBruteLoss(damage_amount, forced = forced) + damage_dealt = -1 * adjustBruteLoss(damage_amount, forced = forced) if(BURN) if(isbodypart(def_zone)) var/obj/item/bodypart/actual_hit = def_zone @@ -86,19 +86,19 @@ damage_source = attacking_item, )) update_damage_overlays() - damage_dealt = delta - actual_hit.get_damage() // See above + damage_dealt = actual_hit.get_damage() - delta // See above else - damage_dealt = adjustFireLoss(damage_amount, forced = forced) + damage_dealt = -1 * adjustFireLoss(damage_amount, forced = forced) if(TOX) - damage_dealt = adjustToxLoss(damage_amount, forced = forced) + damage_dealt = -1 * adjustToxLoss(damage_amount, forced = forced) if(OXY) - damage_dealt = adjustOxyLoss(damage_amount, forced = forced) + damage_dealt = -1 * adjustOxyLoss(damage_amount, forced = forced) if(CLONE) - damage_dealt = adjustCloneLoss(damage_amount, forced = forced) + damage_dealt = -1 * adjustCloneLoss(damage_amount, forced = forced) if(STAMINA) - damage_dealt = stamina.adjust(-damage) + damage_dealt = -1 * stamina.adjust(-damage) if(BRAIN) - damage_dealt = adjustOrganLoss(ORGAN_SLOT_BRAIN, damage_amount) + damage_dealt = -1 * adjustOrganLoss(ORGAN_SLOT_BRAIN, damage_amount) SEND_SIGNAL(src, COMSIG_MOB_AFTER_APPLY_DAMAGE, damage_dealt, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item) return damage_dealt diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index d487b8191266..72e23dd32788 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -17,6 +17,8 @@ update_fov() gravity_setup() voice_type = pick(voice_type2sound) //monkestation edit + if(!blood_volume) + ADD_TRAIT(src, TRAIT_NOBLOOD, INNATE_TRAIT) /mob/living/prepare_huds() ..() @@ -524,7 +526,7 @@ * * IGNORE_GRAB - mob that is agressively grabbed is not considered incapacitated **/ /mob/living/incapacitated(flags) - if((flags & IGNORE_CRIT) && ((stat >= SOFT_CRIT && (stat != DEAD && stat != UNCONSCIOUS)) && !src.pulledby)) + if((flags & IGNORE_CRIT) && ((stat >= SOFT_CRIT && (stat != DEAD && stat != UNCONSCIOUS && stat != HARD_CRIT)) && !src.pulledby)) return FALSE if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) @@ -1019,10 +1021,10 @@ return /mob/living/proc/makeTrail(turf/target_turf, turf/start, direction) - if(!has_gravity() || !isturf(start) || !blood_volume) + if(!has_gravity() || !isturf(start) || HAS_TRAIT(src, TRAIT_NOBLOOD)) return - var/blood_exists = locate(/obj/effect/decal/cleanable/trail_holder) in start + var/blood_exists = locate(/obj/effect/decal/cleanable/blood/trail_holder) in start var/trail_type = getTrail() if(!trail_type) @@ -1044,18 +1046,18 @@ if((newdir in GLOB.cardinals) && (prob(50))) newdir = turn(get_dir(target_turf, start), 180) if(!blood_exists) - new /obj/effect/decal/cleanable/trail_holder(start, get_static_viruses()) + new /obj/effect/decal/cleanable/blood/trail_holder(start, get_static_viruses()) - for(var/obj/effect/decal/cleanable/trail_holder/TH in start) + for(var/obj/effect/decal/cleanable/blood/trail_holder/TH in start) if((!(newdir in TH.existing_dirs) || trail_type == "trails_1" || trail_type == "trails_2") && TH.existing_dirs.len <= 16) //maximum amount of overlays is 16 (all light & heavy directions filled) TH.existing_dirs += newdir TH.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir)) TH.transfer_mob_blood_dna(src) -/mob/living/carbon/human/makeTrail(turf/T) - if(HAS_TRAIT(src, TRAIT_NOBLOOD) || !is_bleeding() || HAS_TRAIT(src, TRAIT_NOBLOOD)) +/mob/living/carbon/human/makeTrail(turf/target_turf, turf/start, direction) + if(!is_bleeding()) return - ..() + return ..() ///Returns how much blood we're losing from being dragged a tile, from [/mob/living/proc/makeTrail] /mob/living/proc/bleedDragAmount() @@ -2255,27 +2257,38 @@ GLOBAL_LIST_EMPTY(fire_appearances) stack_trace("[src] had set_usable_legs() called on them with a negative value!") new_value = 0 - . = usable_legs + var/old_value = usable_legs usable_legs = new_value - if(new_value > .) // Gained leg usage. + update_limbless_locomotion() + update_limbless_movespeed_mod() + + return old_value + +/// Updates whether the mob is floored or immobilized based on how many limbs they have or are missing. +/mob/living/proc/update_limbless_locomotion() + if(usable_legs > 0 || (movement_type & (FLYING|FLOATING)) || COUNT_TRAIT_SOURCES(src, TRAIT_NO_LEG_AID) >= 2) REMOVE_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) - else if(!(movement_type & (FLYING | FLOATING))) //Lost leg usage, not flying. - if(!usable_legs) - ADD_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) - if(!usable_hands) - ADD_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) + return + ADD_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) + if(usable_hands == 0) + ADD_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) +/// Updates the mob's movespeed based on how many limbs they have or are missing. +/mob/living/proc/update_limbless_movespeed_mod() if(usable_legs < default_num_legs) var/limbless_slowdown = (default_num_legs - usable_legs) * 3 if(!usable_legs && usable_hands < default_num_hands) limbless_slowdown += (default_num_hands - usable_hands) * 3 + var/list/slowdown_mods = list() + SEND_SIGNAL(src, COMSIG_LIVING_LIMBLESS_MOVESPEED_UPDATE, slowdown_mods) + for(var/num in slowdown_mods) + limbless_slowdown *= num add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/limbless, multiplicative_slowdown = limbless_slowdown) else remove_movespeed_modifier(/datum/movespeed_modifier/limbless) - ///Proc to modify the value of num_hands and hook behavior associated to this event. /mob/living/proc/set_num_hands(new_value) if(num_hands == new_value) @@ -2288,14 +2301,18 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/proc/set_usable_hands(new_value) if(usable_hands == new_value) return - . = usable_hands + if(new_value < 0) // Sanity check + stack_trace("[src] had set_usable_hands() called on them with a negative value!") + new_value = 0 + + var/old_value = usable_hands usable_hands = new_value - if(new_value > .) // Gained hand usage. - REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) - else if(!(movement_type & (FLYING | FLOATING)) && !usable_hands && !usable_legs) //Lost a hand, not flying, no hands left, no legs. - ADD_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) + if(usable_legs < default_num_legs) + update_limbless_locomotion() + update_limbless_movespeed_mod() + return old_value /// Whether or not this mob will escape from storages while being picked up/held. /mob/living/proc/will_escape_storage() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index ec90fa9fa7bb..7a6750309572 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -356,6 +356,12 @@ Difficulty: Hard /obj/effect/decal/cleanable/blood/bubblegum bloodiness = 0 + base_name = "" + can_dry = FALSE + +/obj/effect/decal/cleanable/blood/bubblegum/Initialize(mapload, list/datum/disease/diseases) + . = ..() + add_blood_DNA(list("DEMON BLOOD" = /datum/blood_type/animal)) /obj/effect/decal/cleanable/blood/bubblegum/can_bloodcrawl_in() return TRUE @@ -365,6 +371,12 @@ Difficulty: Hard desc = "Thick, splattered blood." random_icon_states = list("gib3", "gib5", "gib6") bloodiness = 20 + base_name = "" + can_dry = FALSE + +/obj/effect/decal/cleanable/blood/gibs/bubblegum/Initialize(mapload, list/datum/disease/diseases) + . = ..() + add_blood_DNA(list("DEMON BLOOD" = /datum/blood_type/animal)) /obj/effect/decal/cleanable/blood/gibs/bubblegum/can_bloodcrawl_in() return TRUE diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 28166514b241..77a695647a86 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -311,10 +311,7 @@ var/splatter_dir = dir if(starting) splatter_dir = get_dir(starting, target_turf) - if(isalien(living_target)) - new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_turf, splatter_dir) - else - new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_turf, splatter_dir) + living_target.do_splatter_effect(splatter_dir) if(prob(damage)) living_target.blood_particles(amount = rand(1, 1 + round(damage/20, 1)), angle = src.Angle) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index f7dd0808abba..3127f3db80e2 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -1551,7 +1551,7 @@ /datum/reagent/medicine/coagulant/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(!affected_mob.blood_volume || !affected_mob.all_wounds) + if(HAS_TRAIT(affected_mob, TRAIT_NOBLOOD) || !LAZYLEN(affected_mob.all_wounds)) return var/datum/wound/bloodiest_wound @@ -1572,7 +1572,7 @@ /datum/reagent/medicine/coagulant/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) . = ..() - if(!affected_mob.blood_volume) + if(!HAS_TRAIT(affected_mob, TRAIT_NOBLOOD)) return if(SPT_PROB(7.5, seconds_per_tick)) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 257a184a2278..6c09b960c9da 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1,21 +1,24 @@ /datum/reagent/blood data = list( - "viruses"=null, - "blood_DNA"=null, - "blood_type"=null, - "resistances"=null, - "trace_chem"=null, - "mind"=null, - "ckey"=null, - "gender"=null, - "real_name"=null, - "cloneable"=null, - "factions"=null, - "quirks"=null, + // Actually Relevant + "viruses" = null, // Refernces to virus datums in this blood + "blood_DNA" = null, // DNA of the guy who the blood came from + "blood_type" = null, // /datum/blood_type of the blood + "resistances" = null, // Viruses the blood is vaccinated against "immunity" = null, + // Unused? (but cool) + "trace_chem" = null, // Param list of all chems in the blood at the time the sample was taken (type to volume) + // Used for podperson shit + "mind" = null, // Ref to the mind of the guy who the blood came from + "ckey" = null, // Ckey of the guy who the blood came from + "gender" = null, // Gender of the guy when the blood was taken + "real_name" = null, // Real name of the guy when the blood was taken + "cloneable" = null, // Tracks if the guy who the blood came from suicided or not + "factions" = null, // Factions the guy who the blood came from was in + "quirks" = null, // Quirk typepaths of the guy who the blood came from had ) name = "Blood" - color = "#9e0101" // rgb: 200, 0, 0 + color = COLOR_BLOOD metabolization_rate = 12.5 * REAGENTS_METABOLISM //fast rate so it disappears fast. taste_description = "iron" taste_mult = 1.3 @@ -24,6 +27,7 @@ default_container = /obj/item/reagent_containers/blood opacity = 230 turf_exposure = TRUE + chemical_flags = REAGENT_IGNORE_STASIS|REAGENT_DEAD_PROCESS /datum/glass_style/shot_glass/blood required_drink_type = /datum/reagent/blood @@ -37,33 +41,30 @@ /datum/reagent/blood/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message=TRUE, touch_protection=0) . = ..() - if(data && data["viruses"]) - for(var/thing in data["viruses"]) - var/datum/disease/strain = thing - - if(istype(strain, /datum/disease/advanced)) - var/datum/disease/advanced/advanced = strain - if(methods & (INJECT|INGEST|PATCH)) - exposed_mob.infect_disease(advanced, TRUE, "(Contact, splashed with infected blood)") - if((methods & (TOUCH | VAPOR)) && (advanced.spread_flags & DISEASE_SPREAD_BLOOD)) - if(exposed_mob.check_bodypart_bleeding(BODY_ZONE_EVERYTHING)) - exposed_mob.infect_disease(advanced, notes="(Blood, splashed with infected blood)") - - if(iscarbon(exposed_mob)) - var/mob/living/carbon/exposed_carbon = exposed_mob - if(exposed_carbon.get_blood_id() == type && ((methods & INJECT) || ((methods & INGEST) && exposed_carbon.dna && exposed_carbon.dna.species && (DRINKSBLOOD in exposed_carbon.dna.species.species_traits)))) - if(!data || !(data["blood_type"] in get_safe_blood(exposed_carbon.dna.blood_type))) - exposed_carbon.reagents.add_reagent(/datum/reagent/toxin, reac_volume * 0.5) - else - exposed_carbon.blood_volume = min(exposed_carbon.blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM) + for(var/datum/disease/strain as anything in data?["viruses"]) + if(istype(strain, /datum/disease/advanced)) + var/datum/disease/advanced/advanced = strain + if(methods & (INJECT|INGEST|PATCH)) + exposed_mob.infect_disease(advanced, TRUE, "(Contact, splashed with infected blood)") + if((methods & (TOUCH | VAPOR)) && (advanced.spread_flags & DISEASE_SPREAD_BLOOD)) + if(exposed_mob.check_bodypart_bleeding(BODY_ZONE_EVERYTHING)) + exposed_mob.infect_disease(advanced, notes="(Blood, splashed with infected blood)") + + var/datum/blood_type/blood = exposed_mob.get_blood_type() + if(blood?.reagent_type == type && ((methods & INJECT) || ((methods & INGEST)))) + if(data["blood_type"] in blood.compatible_types) + exposed_mob.blood_volume = min(exposed_mob.blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM) + else + exposed_mob.reagents.add_reagent(/datum/reagent/toxin, reac_volume * 0.5) - exposed_carbon.reagents.remove_reagent(type, reac_volume) // Because we don't want blood to just lie around in the patient's blood, makes no sense. + exposed_mob.reagents.remove_reagent(type, reac_volume) // Because we don't want blood to just lie around in the patient's blood, makes no sense. /datum/reagent/blood/on_new(list/data) . = ..() if(istype(data)) SetViruses(src, data) + color = GLOB.blood_types[data["blood_type"]]?.color || COLOR_BLOOD /datum/reagent/blood/on_merge(list/mix_data) if(data && mix_data) @@ -273,7 +274,7 @@ /datum/reagent/water/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(affected_mob.blood_volume) + if(!HAS_TRAIT(affected_mob, TRAIT_NOBLOOD)) affected_mob.blood_volume += 0.1 * REM * seconds_per_tick // water is good for you! /datum/reagent/water/salt diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm index 1274a3bb91e1..067c9cf1e464 100644 --- a/code/modules/reagents/reagent_containers/blood_pack.dm +++ b/code/modules/reagents/reagent_containers/blood_pack.dm @@ -5,85 +5,84 @@ icon_state = "bloodpack" volume = 200 var/blood_type = null - var/unique_blood = null var/labelled = FALSE fill_icon_thresholds = list(10, 20, 30, 40, 50, 60, 70, 80, 90, 100) /obj/item/reagent_containers/blood/Initialize(mapload, vol) . = ..() - if(blood_type != null) - reagents.add_reagent(unique_blood ? unique_blood : /datum/reagent/blood, 200, list("viruses"=null,"blood_DNA"=null,"blood_type"=blood_type,"resistances"=null,"trace_chem"=null)) + if(!isnull(blood_type)) + var/datum/blood_type/blood = GLOB.blood_types[blood_type] + reagents.add_reagent(blood.reagent_type, 200, list("viruses" = null,"blood_DNA" = null,"blood_type" = blood_type, "resistances" = null, "trace_chem" = null)) update_appearance() /// Handles updating the container when the reagents change. /obj/item/reagent_containers/blood/on_reagent_change(datum/reagents/holder, ...) - var/datum/reagent/blood/new_reagent = holder.has_reagent(/datum/reagent/blood) - if(new_reagent && new_reagent.data && new_reagent.data["blood_type"]) - blood_type = new_reagent.data["blood_type"] - else if(holder.has_reagent(/datum/reagent/consumable/liquidelectricity)) - blood_type = "LE" - else if(holder.has_reagent(/datum/reagent/lube)) - blood_type = "S" - else if(holder.has_reagent(/datum/reagent/water)) - blood_type = "H2O" - else if(holder.has_reagent(/datum/reagent/toxin/slimejelly)) - blood_type = "TOX" - else if(holder.has_reagent(/datum/reagent/toxin/slimeooze)) - blood_type = "OOZE" + blood_type = null + + var/datum/reagent/master_reagent = holder.get_master_reagent() + if(istype(master_reagent, /datum/reagent/blood)) + blood_type = master_reagent.data?["blood_type"] + else - blood_type = null + for(var/blood_type in GLOB.blood_types) + var/datum/blood_type/blood = GLOB.blood_types[blood_type] + if(blood.reagent_type == master_reagent.type) + blood_type = blood_type + break + return ..() /obj/item/reagent_containers/blood/update_name(updates) . = ..() if(labelled) return - name = "blood pack[blood_type ? " - [blood_type]" : null]" + var/datum/blood_type/blood = GLOB.blood_types[blood_type] + name = "blood pack[blood ? " - [blood.name]" : null]" /obj/item/reagent_containers/blood/random icon_state = "random_bloodpack" /obj/item/reagent_containers/blood/random/Initialize(mapload, vol) icon_state = "bloodpack" - blood_type = pick("A+", "A-", "B+", "B-", "O+", "O-", "L") + blood_type = pick(subtypesof(/datum/blood_type/crew) - /datum/blood_type/crew/human) return ..() /obj/item/reagent_containers/blood/a_plus - blood_type = "A+" + blood_type = /datum/blood_type/crew/human/a_plus /obj/item/reagent_containers/blood/a_minus - blood_type = "A-" + blood_type = /datum/blood_type/crew/human/a_minus /obj/item/reagent_containers/blood/b_plus - blood_type = "B+" + blood_type = /datum/blood_type/crew/human/b_plus /obj/item/reagent_containers/blood/b_minus - blood_type = "B-" + blood_type = /datum/blood_type/crew/human/b_minus /obj/item/reagent_containers/blood/o_plus - blood_type = "O+" + blood_type = /datum/blood_type/crew/human/o_plus /obj/item/reagent_containers/blood/o_minus - blood_type = "O-" + blood_type = /datum/blood_type/crew/human/o_minus /obj/item/reagent_containers/blood/lizard - blood_type = "L" + blood_type = /datum/blood_type/crew/lizard /obj/item/reagent_containers/blood/ethereal - blood_type = "LE" - unique_blood = /datum/reagent/consumable/liquidelectricity + blood_type = /datum/blood_type/crew/ethereal + +/obj/item/reagent_containers/blood/skrell + blood_type = /datum/blood_type/crew/skrell /obj/item/reagent_containers/blood/snail - blood_type = "S" - unique_blood = /datum/reagent/lube + blood_type = /datum/blood_type/snail /obj/item/reagent_containers/blood/snail/examine() . = ..() . += span_notice("It's a bit slimy... The label indicates that this is meant for snails.") /obj/item/reagent_containers/blood/podperson - blood_type = "H2O" - unique_blood = /datum/reagent/water + blood_type = /datum/blood_type/water /obj/item/reagent_containers/blood/podperson/examine() . = ..() @@ -91,15 +90,14 @@ // for slimepeople /obj/item/reagent_containers/blood/toxin - blood_type = "TOX" - unique_blood = /datum/reagent/toxin/slimejelly + blood_type = /datum/blood_type/slime /obj/item/reagent_containers/blood/toxin/examine() . = ..() . += span_notice("There is a toxin warning on the label. This is for slimepeople.") /obj/item/reagent_containers/blood/universal - blood_type = "U" + blood_type = /datum/blood_type/universal /obj/item/reagent_containers/blood/attackby(obj/item/tool, mob/user, params) if (istype(tool, /obj/item/pen) || istype(tool, /obj/item/toy/crayon)) diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm index 836bfd98dca4..0afb21953152 100644 --- a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm +++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm @@ -144,7 +144,7 @@ // Make the mob have the color of the blood pool it came out of var/obj/effect/decal/cleanable/came_from = locate() in landing_turf - var/new_color = came_from?.get_blood_color() + var/new_color = came_from?.get_blood_dna_color() if(!new_color) return diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 5178752d63af..764ac9e05367 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -105,6 +105,8 @@ var/should_draw_greyscale = TRUE ///An "override" color that can be applied to ANY limb, greyscale or not. var/variable_color = "" + /// Color of the damage overlay + var/damage_color = COLOR_BLOOD var/px_x = 0 var/px_y = 0 @@ -893,6 +895,8 @@ else draw_color = null + damage_color = owner?.get_blood_type()?.color || COLOR_BLOOD + if(!is_creating || !owner) return @@ -951,7 +955,9 @@ image_dir = SOUTH if(dmg_overlay_type) if(brutestate) - . += image('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0", -DAMAGE_LAYER, image_dir) + var/image/bruteimage = image('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0", -DAMAGE_LAYER, image_dir) + bruteimage.color = damage_color + . += bruteimage if(burnstate) . += image('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_0[burnstate]", -DAMAGE_LAYER, image_dir) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 6991a2bdd46a..d732da816693 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -253,6 +253,14 @@ arm_owner.dropItemToGround(arm_owner.gloves, TRUE, violent = violent) arm_owner.update_worn_gloves() //to remove the bloody hands overlay +/obj/item/bodypart/arm/try_attach_limb(mob/living/carbon/new_arm_owner, special = FALSE) + . = ..() + + if(!.) + return + + new_arm_owner.update_worn_gloves() + /obj/item/bodypart/leg/drop_limb(special, dismembered, violent) if(owner && !special) if(owner.legcuffed) diff --git a/code/modules/unit_tests/bloody_footprints.dm b/code/modules/unit_tests/bloody_footprints.dm index 76b86590861e..2a786414fa20 100644 --- a/code/modules/unit_tests/bloody_footprints.dm +++ b/code/modules/unit_tests/bloody_footprints.dm @@ -21,9 +21,8 @@ blood_master.forceMove(run_loc_floor_bottom_left) var/datum/component/bloodysoles/soles = holds_blood.GetComponent(/datum/component/bloodysoles) - var/blood_type = pool.blood_state - TEST_ASSERT(soles.bloody_shoes[blood_type], "Shoes didn't become stained after stepping in a pool of [blood_type]") + TEST_ASSERT(soles.total_bloodiness, "Shoes didn't become stained after stepping in a pool of blood") //The bloody soles component handles the order of stepping on blood/stepping on a bloody tile in a constranating way //Which means it needs to check and see if any time has passed between steps, so it can be sure the player is stepping onto a new tile (that should become bloody) @@ -39,10 +38,9 @@ var/footprint_total = 0 for(var/obj/effect/decal/cleanable/blood/footprints/print_set in move_to) - if(print_set.blood_state == blood_type) - footprint_total += 1 + footprint_total += 1 - TEST_ASSERT(footprint_total, "The floor didn't get covered in [blood_type] after being walked over") + TEST_ASSERT(footprint_total, "The floor didn't get covered in blood after being walked over") soles.last_pickup -= 1 @@ -54,8 +52,7 @@ footprint_total = 0 for(var/obj/effect/decal/cleanable/blood/footprints/print_set in move_to) - if(print_set.blood_state == blood_type) - footprint_total += 1 + footprint_total += 1 TEST_ASSERT(footprint_total, "The floor somehow lost its footprints after being walked over") TEST_ASSERT_EQUAL(footprint_total, 1, "The floor had more than one set of footprints in it, something is fucked") diff --git a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm index cadccb6fe86c..0e147ec37665 100644 --- a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm @@ -130,11 +130,7 @@ target.apply_damage(10, BRUTE, BODY_ZONE_CHEST, target.run_armor_check(target_part, MELEE)) //blood splatters - var/splatter_dir = get_dir(chassis, target) - if(isalien(target)) - new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target.drop_location(), splatter_dir, COLOR_DARK_PURPLE) - else - new /obj/effect/temp_visual/dir_setting/bloodsplatter(target.drop_location(), splatter_dir, COLOR_DARK_RED) + target.do_splatter_effect(get_dir(chassis, target)) //organs go everywhere if(target_part && prob(10 * drill_level)) diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi index 9bf25b8de2bbf9546e8feaa08eb4c4fe488a487d..3d8e46c3837fda9f224f0b573df0f1332cfc372a 100644 GIT binary patch literal 175305 zcmY(q1ymiuwk^7GLU4C?4Fq=xZo%DxySux)JHdjx1&81e++BjZyS(PSbN~Hs40^Du zcXf5is=4Nxt9H1&oHzm;E*uC1LXeaYQ3Qb?h=B_P3k}>^U4_sE3UwaJ>P{lYj)o5A zc24HDHXx8&R%QB_%?dYi@WP!E@+mum!JvKX [subject] [subject.dna.unique_enzymes] [subject.get_blood_type()] j*^i(>D_*OT< >lG&qyHgcVi1Cp<>-lGUVUFn%*lcg9fELcVew8>QF6Pe|Tv&lp2KBH^r zZxX;&$zKpc1 Yb3t>lqh1!HYtp oNb zIik~$mG_*j15+@tw?;LJ?kwtgyy-~BxGkUg(lOxH3pR1V5h53V; 1##0)#p{5hKwm7EGqMy7!c zxy<1w_|A)yO`62Yqqi?L-L@aec0TCFORq<-L9e7Q$A_aY1zci$IKApAA+@{+hUvHi z4s7s7qZH=bUM*Zvjkg#&^HTxh40f}L-ul@d2pE3OM{M(C^Q8;7?6q t_L@#+l_v ze+6NR89-g74|Ym(B3)fzg9od1jQtcpNvf$Q_tb8xa@Oct|4ez}>lF0P)Q#8j^Zoi7 zl6}6dTc~UZL#uZ5EO%|dUmtU;04(L+$i79*?gl*jJQ=S#8HN16<9 M~Eq)!^U&=85qLv^EVRTY(5tWEHpjr zspg ?+&Tptaz%rI%~Z}Ry@^XD9RHl|s_Pj5oa z{SDjeTu%3){%?qcS(*|&MmvI^Kk-ORn)``C+~E8i1L=ra^JGi}B&|v*;5mCbdOGeN zEHd6uX%Kz!!s=I>qRTvh{nTo P)HidqENYfH+gf^R-4~^3Y1vNecFe+cX!e&;kC^?eP^1hhv!WI0X89&O1Y6HE zaUScK^OY(w-6( 0;<%l&CjSvaCc@LNK7U z|F+{E_!;^CHV@oL tmzh?`BG*Giu)a$Gj?aL^*tpsc72B$tz#res=JK#H>T;k7>Mq;fNmP;ueCa} zKB^orBP4-+ic>9j&bhhHTZIpGWN~ECepfBOomVYyY?`y+3uP$3^WXsMp7ZbC6OKNX z*<>rQ|LQm2g@v(JpncY$P6^`NX;2T@A8DE~a6jG_+_cN6c5OFdT~DA}!H;)Z=+Y{9 zyzciG{Ki-<0ODjwp<+aY 3!zPY? `0zg`?hVLy*noNSUB_ zJYQK59UWa9g^UviD!ud2OZIsq=jh$a`dirgR;hZt4fL5A)Lt~RyEEWBVepu2WQpmw zP~NdV26~&bXdFQt+=voeqR}*GB^>GJ9CwOnSROp~?(_|&(K&BWf5QXmGGm*S5^K-n zgnOx;UlR<6{}67PeSRG)hurGM3Yh$78#`mbRlR<`p;b8N(u~T&Om)X)MiGQ*-Wa>^ z{953t?JM+?KiXu6R_J%$`nfMCh&M424B5f3W8=M9CBSaBcxqA&vIhi`i`>2XQZ#t< z`gR@JuuM}K#G8ip46HmE3D=_&unG+Y1!UDzq0YziQyTRqKY%+OoteNCWMpK-9bfVq zvd@_Q-K(%RA{W%GoL}!3>R8xPh@VMvTFCtrYf*i_1gsTF+me?h;|UT*P$Muii&bW` z^BB=bK=o;+KR>J+7mm#vHKoBe@p?85r_%^=^5>dBelrH?*^!r7<`t~CNL&n Fo$K@Wx?0gpg6iiUKcM8|)K5PAa02 &} z1{L>n&t4pjnw7B-4>FbZS>eDIS(1zFS1z2Zg1VTd$j)BR<6dI{QBX7b{Go+?cn(MF zik0^W6)NDG#)y`4)TH;triEoHWdR9O#uPTJnX?O=K-)BnlMa$5$@!#DUqE+sDr+g< z zL^I9*pWag}%nqvVGWX%BXwU~4Qg17p{<>s+k z^~$D_1DtZ=O=BrjG3t(9!tf{;2$$j|UMupVW1%5(6= gB`_jiREq-w}gTaw_K%fEM?mom 919AAodFj=PVe&1krP! zz2(c;gIzkcD(44jiZDbqPh&wZd>eOA(R&lU!g;}D`BU%1<*#B`r Qz|10{Wf&8a3VhpUuNK2t%))JiR+ZIdI`qy%|tR{HUd7Ti@g&jsL3nyd%u% zo-?73+Pq{=soOgfBLj;RqR&eR;Am` 3D?}7*Sl@3e1uEP#8P-~oZ*jW8rniZfXRk;e z?s# 0Qov?JhD;ScqSg8`%_=vDR!TF`r z;=iYln*#GXR}b!Qi%S;2p|f{CaXf1wz;DGG^UjO^1{U}G4f1D<*-g7g*ZY*56w^lj z+#6I7Z%(qau+3`zoQty}cmDeMdj{}g5-?J3;lBbA{deG#zJ!QsuN@_3ZpErRJHU(8 z1|<4;>5$z_ffplz*l4pL vf!X+16fmhv3yW~ezrpj^ zP^tg?CHw4Lsq5tm2L0h%=S*y+$153O^sgJbyh#}nLcN~+qGGzgTeenKedF0YLNrj3 zAv@TCGl#tVj(ajM7oh@>Aen4#48U4MzI}tBlts sXg4pYxCd>;nO0FV*EP$@%h)lK)qz@VTjTP*!5X&M{H!pzY`2-fAkH_j}+zm-`cW z_$Oe oYK|Js-7L<7%ST9r?*$iQ6IbHUO|d3 z po((O>*w^C4b=7NRO`X@LO_<{6R^2-1Kn#5H9lcZ;kRbo3MWVLzYZ) zPfn}a&jGg;`WF|>^-J8!$l!Cj)uxTQ8)lJ%&G>BCF$>RL&$GN#Suax-jfjBjk8Reh zY_h0cNHocI1m=DsA!W?)HiC;V_oZvtytdtr7qdJQ4?gB^ RZpadDW|wzfQUA~jXD(Uqu;n!G{ICvPd8 z(_1kx^}_qy^r!)DA>_By1iOb&KWbj;3u$9?02>Ju!|bTVy%Vat#0t2^tKP)|{aLnJ ztcT(@YOL9D;eC~lKO7ES+5-p_D0ck>5VWc)Gbini39JvmslR!`_Cr<0rnBzt7f+de z_KJm{IgF{F09HCeQON93rg~$mb0+}@vb)yoCcqab8gDyEbIGG9;KIC=7~S-rS4i&< zZ;4{hh4Ps-7|_ofl5h0vtL1ZYkbWwfr(Zx1GSvd*+65ZGAZ%d#@}PS!5G!3gIq~0& zYMQu6x9r@$g{o@t&gws45bM4>M4Kp0-@ccrmA|HvVDi8uy`h2Hxpgdo=IQVeb2E!x zzYBA(F;~l}frEZF3?oM-do(Uz8BQIvKvKKvIY>U6dU)Hnh&hrtDi(PX1LXNVc@1Zi zdf1i0tZ~6p`-vtJN%fkf=;@7?Fm~fEMNS-mW#w9(@%~UF@wW|STG-GzR3fA(!VWl4 z7G@;CYCA9_X0>STHR+ioHW=bCgXdF|I{+@bCq;0qT>l (Nw0?ei#s!D7{hunV9^Y=5P0} z{2u!R6`c0q*HTr1*4NegD=&2PzwN;YJGPnIeNG$QUY9M6FHvA2?XJa%wz^Wi@w5=2 zKOoG;l1`?w((U43EDAnz{vg{C2Y3S9@Z}EpKX|~Lbns!JeFgZ?No^0El8SV}46x{T z_wko<7cio8RA^ys!8xHpI#UO!pb)@V^!BFj(g9lKo;Q2mby+j@`^=7&yI!$+FkZrF z!Y Yy38+>9)0uFB`*cQn`tth<{ko^ zy~syk9Omu$x7a^i%*a>f1w+oVIYxZcSld@v&Shiv)P~UBKd?d%3t{>-zelb3%h&fE zcRk?oYcW}o;SrDNuJh(B*Z>6e-q*je{ G>?#7Alvvs16OwFjfh!H&xwphAgb~8vC zFS-E>-35$%99&-K{JMk7?715$r&0av2`SeW1MGk@u!ofaz4i86 k zKx5S#%%;9ia35Fb>Du)`;g+r>;afi}FLb(ePV7Is5kHS50rrM;@6qAZ7lwjRZ#t2} z^J=|R2L(co9pt$`UEw$$z_Wd!=Ax+Rf*o4N`Feo&{sO7TXGuP=@s6wd;tsWp*cn}> z;8?Rc0Oj`n%s2YT+bHm1fSaYSI4*DhP {`xKZg_Kz=AIM4I1=4mJZN~%`^25jaO zJE)n&_%2Eo_96v5=%c2-KUfL7D3KSF=vJ~IM~(#;FjpE?kgZI`3&eOx-Y-N7IQCd> z^ONJ^*@a+RX!zcOiQ%(RqcN?gEkad7dUy4(quZ)KLAuS-eYax8X8b(xPoRYtwFMv9 zQs`Fmv>D)h<#T0#1Nzs3JLu8z)}_uKqRGjCsd#>^aM;nbz8_h8-lS2<;UZqZBL(#X z+Rp|=p?IAP1@kR0*M|)IMXKqJ2%GOo1=OSG^Y-`$;QRJ!tgSG9p`hl6f0yYCkl TyyQ<7gHU=SORZ^>d%>bMb4t}PlD zp0_V?79az=%%Ij~ngmF(SC1DgI9y@>RDmt2MMM~p#D{|prM;+|6<9!EfBInPc)PYE z==51}!9W4T#MtlyfjvL>CiA}eKHW8Uc{^%pt;pJTKJI 7$Z zZhlyze5a+U`Niwuf&}o6gJ8A#Idc`PY;3n&DIH%;Ce_9k40@sszMNJIln5Ce#VS>K z&NXl?zRj|Nx;-}@px<5$@>gfxo5gtVvju*Pw!4G;1iBtbZQ8e>LEZ6W;umLa=ZN%B zbU4N <{5daU`u6yreQb2xwFf;+D{bR)zV`ASdC9DWZ6$P7lLhOy^O@u7 z2Dm?Z1hr|7-n= wT+ z7*G`M*tg@`r+08rhw5{s^2)BTm(}F4fPuN1)MY%atofHOmjGE|G}$fyfOgWNkze=U z?Hf+FJE-*?195{kJeC6>E*LUi)Qb7gqdEUE2N Yek}kt0K(!e OkiFZ+hYf**l2oXQaUhij;bNa|K)FRll6-^%3n?qwU>VVT2F1Ajp$n znHAhzw+!eBKF5jnJv0_TZRxXRfil>-AX2Hx<$4WA!T_iZDd22A$VgS|QCZrQSyV2k zeSlbjLKfg^aHA<1VZjHY@mwE2Jk=nP?@MqtkE_WXN_lN HraQ!r51<=Jp>fum3UxH%SGvU=h2Y0Zbrj>3oB^d2)mhv=ip^ietF8VEtE- z1TA=o1z<@$ao Omp zDxeA7dd<9!fy!9a&il)6=`ui+2E_X=(&xSy;bMcjC)aD;og4aJtcbfIB9u!=KyoEZ z*=2JUa{vW%h`$vJ+^Vyqzl)eNXvt+5?cX55!~obup)xYzafFyS;1xMK%vmZqELa8T zF@m5TD|nJ{1L%P`3mQtkbO8d?K1JP`Tz=ywoMR5v`L31tDK@D0+{fsPc4@?`6)AB# zY#f-FAfQYf%&F;l&%h9QUF=6Pbv}rtj^2708}E_ K0`UG*7Y<;U8Ec~`0oog zJTfF$xoTy?=NSz=?NULn?uGo1wTxT_tL;ArYJ2$$65~hqhsa9>BMH19&&|!5Sz8D9 zZiOA+dOSTHS6hybjtarz$ZtJ 6vg9c zv1%TaSUl&T#SpkZp}E1rSdi2VEe$0H^RGRd=PGC _`Ny*^9mVT4^R|eoI z0s;a(CMKr$7O{d)s>;d_>mJ9Iri*UOAh4UvXTtoM&W5dy_eU_7s8T49%sSH@Dsk!? z>T}y!Pule04r}Fom%t}F3(L#147T4bA}46uod=}6ZfCb?SI@rRPwv-Y?8nC|e;bsi z6&mn*aWYd%(fD4fi7~0!*D{P<^}7=eat)8pr;TL@=jP75_E%lw)<4n(|9(4;9w1TB zPjb*`Bi6=)8P8AJHpRgBj`f6}Jzo7y^HdX#w|-2&q{uH^5e|<2ZPK766ntXG7{B`n zi6LbQy(E=iuWn5Li@g325RYaH=N5duY}74&4(o8mz!FHJ<2gBpufL4V&)=vySN#M& zR%G3ejrltwl1R^xy=#+}(I-nWf+K3+wi+**2)K<^e%X8hyx+A2bOcV#V{_PO7ri7y zU8D}wt{Jdm^yxLzJ_kR*txxYXX6fh}u a%FwD=?B}!=M)~!%Tz~YU@QYPp`A%wjE>It{8kSVLV*i9V{)xZsC3o zLr@gVmt;k$r6;Qn8l_*WB`>9Wzd_32*ksX$w0@&Swdx7;6Av#Z>HOJezy8#5BqkKvK0xIdx( z00zYS#p8woylCHohOb21CbmE1t-0U8s!`JxvW9GI4 )gne@ z!1@cW_ak;MbipN!BRLB^$np|@KJ5L#37~3F>CsOz m1~T5;17I zf(QG8DL83fD>E_BssBaUh8dgty=clQ`}{hZ8wGJ8CRRlEF;eW>8{n0kRa3t!Y8N#= z)Q@R-v-=CXhYERKpB0a2lMu`>gn6Gl8YU(xSy|aD=}VP3 In^+JJim89-bgjtcPwALXvm88tCHa7l}5(UY#a}4Gj&e&g`zZ38b z%>KD+tnP7op0Hawx|g> k5J+#&*JZc*3+&q<6JO;6R@z=U|U8}1_mV+%XB6`-ZOv%LbTHcK(k~!CQ|ET z=uEi*vQ=4~oaFVIxwd9Y`ChMMSo^o-&%S4yyy3r{oR7p1l0j|Tz6j|t9Y8+>&6|_* zYh5FICb`(KwR4V{t(b9{vT)(N!MeeuHlRtmY!{Aw@0;YX+SSXm6Gr8%vJz9Z3k86L zgJH)+u?(9o{N>^O9UxnHQxp2G|D5^_q-Zj;Fh@TpP!D;4uYPc(+tCSu_b0(vN!(t_ zL!qe)kKA*Kcder4E&ze`6DICQm5&=xg?E3U1$kj4z0FX-ww?MtpQxXZ6)(MvMxE|+ zLrDj-w-@s%+CQuxei|#uV!Z$)8*0#lS~YYWGX6xG44=z2UpXhi;*} 7#c@$3F!La%7gs$-lwN6EQ?f!mKDkj4`^S6;T%jl&F<8xnCg<7djLIc=N-i=bX z7Ztx_B(GfXSSr1D0_fg@*s(nr2lIEs`o~V7$i=8nsC8E#5z&H ^v*3Yg^OoMfZtHYck zs1xY!c51F~r+iDrs1>2^`L#2;gH_h5(!nv15-XE_eG{EaV*D{m2m~#iwuGF>CBJSs zTAC)f+|3#_0r^|nk#9jF#E?b;0=*P(p3^@rykGMa50C?oneHF=_D0_}#`g3y$l{OC z0{mEn*Lg&SC?J|P7FG$VdU%PmR(mW3IW>Cw2h)2X3dE~DD)YBVKm))E`vfug0);a` z+8p}$=E%@-oJKCM`W-0fDIVQt>Xnx)R;s%Y`R!J#D8JnLPZdZzn&&z?^ejJPN_o{h z@&h@UK`^H-)7lF9K2A^r^gnye)7z?{;s=l)XHmb8)Sx9 ^hH&;OSVzJkUg#+O5M+c4VB6a8<}8T|sC{{NpK2FUO*3NKuOz!}Bn{*%mr z=)^9q#vCA8zg(Y?Hm?y<|e2tFYi4uLl?Bo`=eAR z$Bg?``q*LK@5~ZYuZ0km%J=tV+SYAFo?!jWb{6C8{Mgumhg?PWw_{BDU(TN~uaA9k zYYVP_MO78I)#tSGoEM&r^E}=X5o-f|#JTttJSnk;@!R{wO<7s_=kMRx7#J8eOIE<& z$GzFPxgUSin6KDLb!i;_=PUp{+!fF-cPUjHyZN2@!&K2=a 247=Ef}qIr1DPp1H&5~9Xz6Bl)o@5 zHtRE2W_m_iIU1beTQslu>(?wa1y@PaG`xCko>fd7GP5Z3^o6xu%N@N@ld!I4-=d$< zcCUG^5RCtsFG64Tq0FiS4-$|aM;hMOD4tQc%iwjt!f`~{3xR?J2Sdle_;GtQM@>T$ z-(_iTE~KD<0`3pW@jT-aAw|(J`?A#o$g Tn_?&W>xV+`C zrf3~XW|XnQJ9 z%*2HY !)PmB{cBZ)^os+8t^e|k;^ z#!|rkgo?vqw#Q6llEK4!t%umf+K1=3c42<3C{s%{p%p69d^EyT5|~soDSr|MOWT!z zk+Rb8-5%nAwKb667z5q44`cE_YmLhjzmo}#nzT;7ZfiJyYR JB}(IQcqJ5mb1)oB*dH zaOIX@VGHu}1J-$+s5{&bCJCjE>ID5Rs3lzr9de|lq~sM9!4roeLDY0~y=%}AI;9J_ zDf^o4NUtTN;GmMy(w+*9dJw4IWK39AHu~i??^`Hrq#L@hR}?;1LS3p{F%Lua@M1_# zuAU2Mj1enI0YyKKMj`=%zexz41ToCV)X~r&`4NG5VFO&-_wyG$R3bx)eJ8VePQv2P zI16z`G8wNlmhyFV)pZX;I&kU}c->nL-E$|x6D32NigBojZh_RoiwK*FtDqQY#XytU zpE!u2)co2q*_;_$c3hi&mLN9r^mrfN2;3z4lX3=rhh%V*<`qvblXy5|Wk?XEt~!VQ zvkYrPg &wCr?95pCV9kN5)+Lx51pFG%qe}|AO zP0ytP=v3tFXq)$O^_ 8V+<^ZIQomPL_+vz!>tYYZ|MMOmCk`~Gvj#2>Ipa_6Dry||&M44=K`*OdgKLG+; z50JY=i5 RuDrk&~YJ@qO*a1f1r3Z>6Tr<^ba)+ z@kIO;&p}nTaw<3p1jB4J!)(-Li$sNX9$clc0#r2mRU)`aqg$v1)}f!x%s@5K)CA`z zN^!WblY*OO#CFs+Agnw;h?PR-ZYc8UMqH3MgnbVfD~G8mKgIuTY-xq4!Or29{?CLc zTpB$bG570QLcPw%C^I)Gr3QT$?oujFnt^rySo@FVUYy|h9^)a?UK3L{@psFVbVyet zJSBM|5w!rt#?>=Ld<9R{H@`xbnRePgnu5YiFUt2lKYP3KWtUZ=O?oy{u4ODxc(u@pshc$M2|%rlSf-nQf Q4%eW+ z31uZjM8J6*{P|1*8!q>!K89$bbWH91cc2()d;s?n8l}unV`F1%4jX(Q@5_G9R`;(C ze1w!c>nM=zWUvo2Qw0xGd-t23N0Rud 6=Eq9V-1Y`#8A;0jGd7I7>s)fHOc~L?eo#XImS=x8MI3p% =>w1L_1Z#6WAIOa%7%jbS2IBhO>gwO%T0ZwYJ7iaJe={+K zecjt0kW|a(wqua^hstNFqo~vWiSb@E9KiM&Fl)ef*iZ1e<4E)WI8=6heEog)FP3@f z8VWe~01irHF@~e`?CgGfeg4cn;2KNR3w-*)lK#bhTjaru-)gNDKr{?&nR-7J6e(Up zr0q*i8OlWbR|S{H5vp$#xie6kIcoj)Ogw$K@Pg>1f00)9 1;L zg&JJ=>=j@@rs^_-4)b9s8MTa;&gXlxqF1rg@H5%yF)!-FJ*MFQiWdC6^&j81%Tr%D zU`+IAoEEuGRJ=B_pH^Ao6C~jl{Y 54K1R&( #nvM9EMJ2zX zfL=4msDXx3{lk-mv5sKqqCXy7%lRdArIne|GvQd2iUFY3wKIM)8VI>q!~9+Z`UH`X zG>?A8f-+PTxW%8U(%N{4y)%390-h&?ZYMcZ@)V!4o0{506pRAL`0?2BJEc* a5O5rITbaM$4?r+WO8`Zfr01)$2cw? zz0x@CNo6R}?#?%20kc**7ku*Pnz&sN%wqe=#L;S$x8-+mQ_o5rk`ey>7LmDQRD*0j zu)PAA;~77kwZJn6Yfy;^;Y@c}Z~Yg b|LiE%Kv$hfq=i7!cC4l; z;3;_^!|~^;G228SyeaX#KNDeg`vUvyz8@absepo|&^eI+_(vDdB2L5t1$2%CL+gf> zT~;yx74=#3Me4@h&d!$Ch3jZyOckKG>PWV1Y(rO0Q!M2dnemFAuvP2pEU#CLi2E;O zc52RaPupDY53c&}l2? JxhR%D*G|-FX`@)pN)GQ*>@$nZQH_F z-7PubDjO8Y#5VczVuLBGgvWFcC^q!FU=bJTsHpIPN#qY9Vq*A@F~I%u9LXZj+ jMv&RYV9($GFHuBZI0%SFWJGvRtZ|C`n8A`1uqpue%I$t-^wPDB z__A&dR)n7G^Hh3rvtK{IZ4>Cfj*eJxjknRgV%xPw2A()-ziRzu@-$T!PS9)n{jfN9 zyW1avOTdP`g@BAOHc%06Fa)8b2;H%F3-}EH1zJufM=WlqW+ oV`}#8Ge-RoFqqq70F0)g$(^$<>K|k~J!9jML-Tu1SBTvVu96 W9u?f%2UB_%(7_-UDR7N|=%{Eel080ps5p^x?>O=TxWx%6Y0jstc u*yfYH)v9DpV^!ccjYB lkDu^QHl zca^8hi$hQ_vMPp-lAMjs5uetd<8?!+&Fi$VG@dKM1C+jG9wTZgwSbrrWF`__0JgFT zuFd!xA3?C5JYaeaq)$rzjA$$<99P4r4hy~I(xyNf4uMmEa&~to O|kPTYK7qeg&oSXFQU_d*EA!vew|0$hjMaw1rGk~ z%g*jKg`;4*CZmTd6uK83ML~W4&sdnSy1qrgmd;{Wg ~9XG$AziUg_XqZH8101sXv@^$NlxhH6uUe z_Z%6WuO`HVvAxH)+kN3^d=}pwWL;f3fOB8KK^+h%J3ITgRKXzy|9C@{UP$%)kJXB! zfTedQYdYa~tKm$(-ScIHuHVr?Z%YAyf#v`D1>I${unl0H-TUkPC_$@m9 C8f`|Srj6WQSmx8*K*xM9AtP~2f=CEN*yYZ zpSmDltqUUC@~Oc{GC8+;nVRQHitcux^B
LNf1)Vit9ENFXt?DC7cOnS-SX9;6<9WEW40MQkuhJBbB<9Wu5R zl&|70IE$Ck+r13G5Zss7QFv61N%%pve#>hb->qxHY1IB6rowfK@{IUX^!bfhL&ztq z;Hq;!8w3@|#(PjkAUt*?y+JA)&jnioHrW@_Tla9W$mi$XM9nV`rO)2SjfThLQi};~ z?GO}_9ot#;tqa`fG>*jVh+53>F*QuA0K3JT2N9ohvV6>pKc6@@RJd@~hRV6RLyQn9 z_-gvh>1t=V0XUr)guL3h>;3Z3wh^*$+~4W_e09~wff^ony3%a;e0RE7XZ#ruV=ivi zJ3t=Xo+qz|jKWC-**Wf}TqfGgpBoz+`C_<;M5pPYj@qg_Gp?VBA2aHnUxCk3HV^&Q zM%Q~|g+|Aei&pGb>+LvxoE(zU(k9!~Yv-|w-G6~&koOzD0-e_hp8=Z%Ojit3pW|hl zy=Av?xhs+_yCeI+5W#?8$ne@jj6Z~3I(UKLh_mB%rH@hF403H@^?K{9KsJ2;6!8Wf zGRoWA=qLO k zduj}x1Y=JE#d*BnxHV&Qez@X_JH1oz;@{9L@DWS?-Pn)`LZA>l=5Y9ZTf!XkCk!AK zYSO9kSTSXzi2>r>LQ(oYwO_^I8^$ZDY7Iz&{DV~{rLBEi0qSWJkm(PuEg{xNKSiQj zG!*Ah7U}3vAoU?VMEC(FSfi%8-CkB&_V?3=cPupSuP+2*4(I0P`!M#8u)>QSSIH$S z;aC-VcrPWn(0KQ{(5A;=g!Ge8GoFoWTbc(&IH93rICTViKBU+`zkM)CQzB46!lFZ) z=#L+eJ2qNhaCcMMMrP~J=nKC1DVt?SwZTQMs;HZMkMLSt0kooMW0y=?$AHDrED VAk?&0bU-yS%kzfS9;AuXGGa99Rx#0y-$FKhdAQ-$*V)y^{|zGTX&ljjC1m$DVl6h>8GoB&MhAcqA3fTW9K7Y3;&n?K3lLcl>OPR`lW zgJtdWrBzyGo 0GlF3(0@r#V;f)=FO(5 zku7y<|0BUjs}Jzv?2|;>MF%4Cvi>0(haTO*SZJC* QIIHXWZEI6+VQ+a0hS^!HW8tT-O|Y>mSE;$~0^_FZI;WXu=f( zfkp1MjwB0V)JJS-Zo^_>U%(#N>-7{#fggIMs3 V@jNHdDu;n` OopcT|c+zNFau}Wr%uf5-* zq^yrsqN>n+*pyGNk;3}N8R&AU9056^ZKbK(rdp)>3I4Dc7X_K(?;|2@hDQx|EFdY< zph5DHve3o^&IOP!czwaEP$g?o*I^?e&-N5N(eIBKO3sh;o-j$Am^Wdua=Du0^jaV- zg%(05u3#j+REmZTLsgCJ!S@o35Yz8NV_!Ton1baZfFI&UsL*@1!An_Mu}fx!CY}o6 zs4e0)v;!39^cv5K`kZyLQzebW-cDE7`575&lT|3XMCD>HZm-}p^W}e+H(iQ|3WV3g zP7GI2Xhf8WpFWPeD+QbZKWl2a@R%-?{O!K@1ypRqtywpdlXX Bx>MFYZ4pfPaUD~`jl_kkKm(V5w`N@@UjhW{b2%v4bM7DmIL+9ljUv)l zK$b*n?1PCbX#w+asaamFOP~h&SGU~S@;Z*kf~-!bj%Vc>PEc$@*Ghj|F(L2C7-wW9 zF{?q{FJ7ix;{P|CS^wEZAEj`<4YUcZDsAm1%KS42q=?zJQLtF7b5vIp#a$a?{5pmQ z4o?j3P!~Tz^&+*;kVrM0wR$;%pk%Hj&P*wn1uIA&V Ohvq^IdsNp2# z081BEIDP$lhU0d2V6*bxuQ~!LxDUw@Sl=3-3CmUq^QNxqdsO+9yWY6ZU73A11%#t2 zvd<{)8Ww?{!`O(d7~?Wb``FtB5tZ_uY9@|^9a8U{P&Y}Fie85}(Hjw^Oq<0O8Zr#y zmN$jV9^>}F&4cLnOXa AJW$7f()L@UG7rW4a zLxeda$=uYyLPjRl4Ef _4SAap?daUM|AkMC8)$5cJU7Fl26)m zB>6#tP_0@sOskJez#$thdE{*ox|E-s%KUq Ri DS-psNJ`x+N3k1p6nhNTCQ|Ias>_Z$l7Q1nNK$~42g3Wba6 zY?(Y0;K@%9jkc3eh_k-N8chx3g7^Ki|4{6J-yQ%b13`XssYtRGu6R=Lzq z)4=4*P>h#ikYVSaT2nTn5%=jGWfC^H?wKg*s^Bm`o0;3HVEurdoc;dGG8#mWMb|)^ zdaAq8h#gN60p+lEcy#0Fp-uCY>5&9de8eI{ra3P_{%^skf`i&T9Gv^k0EtFk1#RAL z5fSKgSHwKTE~*^f4j;Gv50~@A+}9zf0ANrK(Gca2@|e2ieCHz7_B0W#$&JWYFXYL| zb;Lim^?@{PnI=P69VVH1mrNQplzNlsL7 ~JhWWe-StSaf z92@P|IpLxHr8C;a2{kqR!g{7yp}OG|W33>5?1aHNEFc{%$E{d&dEmtTw07>J#>n5i z)JH`KXsT;#Yga+;08k06{{BUrJ;Hg^V?pO9;shZfGc*_!X7Kij>niSjDuUBe@cX`U z$%~jq-!hya${GkA?TVR%bV496j2Aru*TbV&c$aBpnk;%a=fIbi=Pm*H$9Pj6EDzB? z>3%?IW?ei@(c0M~!%h5tg<~8KHx5RfkLSlrLWD+Y_4bKU%7NhZ;4`2k^QkE1v5;UX zU@{7FQeg}$Al;?UQ8Y9(Y!V1{16lj~<-5ZU&r4<2McYXA@!{CIUxsE85-@RT#OF^Y zSh)Czw>)|X0bbmPBd`)ZN;R`F4L2o+2Ly-63-OdgmNuAFeLOUZaaa3=_c7h*XAg5R z4dlg46yn^Vp=*67>BNoFwMUCDB{-+pVU@zg`ePrt%}SFt9b>Ti4%fnO+L9T>)s_E; zsILrbBkHyeR)V{GaF cXxLv6n81^QlPj)aW537xD)gX@BQ9;^JIR^ zWM-0_z0X=}@3YSwv}PvPy_exMpArdL853mBr{LH|2bf-dT4-fMK|`S$e^W)mp?H>1 zq7Y)(oMEJIRTfU+pTMkQL$s7+TD*UnrFW#b>h83V$Nnrrvnp9Xjo2Ayry_~$e>77) zb+)H54CDW;1-M!bDHeb+Lu3FrJ+3AfzYCfmD2VaH)u+sgqL~u!X=koGOLaE$-~d@! z>%i?=$b|~vZNi2)&-9UCX8mOdkqwmTWdsO^0{0aq8NtZH_{uM)5+Q=;r4&R{aJ_$V zVcA6Z09-f}Oj9k1j2{w13@t;#3du7fFq-yPhf|29G~WsTah?O=T;xCakPvZQ^tKya zIXKMGLJ!>fkYrq@gP00qemgvb|KG%YQsqCJSeTP?Q{@7M{4&1P^lX0YSv{@ak-A^3 zI7UCi?T?#7GZB8zD%2j(Qjh SHB z-E?7^Go(DfkL|9wf6QW?y_U!icUbv_0JKt7oIm_rzwIr+*GrF5KKZr%h6|6$hSRWS zYT0!U6q3x0Z*{SA>C_My9Shr+^=$kov4b8R_=lr1;$?+k^k}1tBn(fDCvJIwu3hH$ z){*x6ZuZOZlkaQuK{rijGGT;=B@QlPx6b?v8f5eE!!xft^bkeK!+ZQ*ERpPGXspVb z2s~5?sde@fi0H;Y)J>8C63}M36k#LyB84hS_3!TGs5FlO_+s11$L3?|9k?Wk96c@$ zkYFPU@-WRI@+@&k(}aLc==bhUC#)q-;Z~MzrvMH^;i3a9m{`y+4}Z)Qd k;rE% z zKb|m;_Vmk?Fn$cSH0M*~3G1WLFikvcue^o&t2*mZwB}Jc-Um6?s <9=VOIWHa9TnLX@ zX04hkr4ulHai(Ho0YZP+N~9WWd{ohaOR`Zk@VTClV--6!;Grt~(c+> i;BD(( zVJh%>m?UK{hAU~NPCI-_IlB3fW)Q>9zoJ^WxX<#Chl_t05_U;UE2HqvJ6jzI(ehf7 z2<^~zRS`rhHZ{jgM+$Y%0v5#$%-E8cpf3^98Y{ARazv=|7vETb{Gp|yfr(54(>kof zEfeqBmneT<)ahbi&6MlzFDH0RG2Z#MEsM=Y;w#@W9I?$*C7*dEus0F^T)svIY$kt3 z#VO!C|BmU}W+-`}9bv855$nYu1$^wvgVT(m%03v;M$MkIU=>SaV;(z1`vpe8p;q{& zRI2d$o{AfKtnhO#pz-d{?c_&fZ)g0KE@B$N*3lLCrX!38@0r|x)-J~8a&EYYf|EPj zxfQe1#GN?{2RE9sobhqrRYo$DcKK4yG|0S}WcX( $id9T@F{b7o)QF~>KHVd>#M3FV+nH%qx5ORFFYDXJ>-y+I8rBG+T&pp;H z&xTDIomS9Esluf?99&%VdryWT*adACX}O2>>$?=6+I?9@aME3>T!n%0kjQ+(z12uR z0%g>@TM;_4_tAa~-5OEoyLa*=8ATR$5z }P7z?;%a>G|^=7 zYT;WT>g)aZ@Rz5H*6Rhx )oX;hPA8nFjpCw{55|0gvosNGX1cosxwb?W6f8m(a zX{Uc4QF+wpRN*}hjHB`tZ1%V8H~+||ID^#SqWrn}P!KMIghoTgLxJJKg;OsE5P2XK zgQ*u$yF0?Z+dP7mq^MEP11_uws+CyGL+5hNE!;+Ujbp-(_P zi?XE z^WIN*J@lWTFeG$ySF)?XU+hLwLXiUw@aKyc!r%DYf~(Q!^Saxj$uFY2>D5%%0 Gv9Xxdph=h~mnl46^=-nLb>@8!1S_BC*hR z>}it8kBI^VD~Ov(SdA3KT0s2{Hx)Bf=8?`bn^tr~Hm8?dC{&nGiysZ(knp@(wic{* zVo4ZUSRr_Qc)M8mjuz!nSjy-Y_4=t){)7ArIzS)K^Uempg8u8Qag^5OqYGBx7Z61T zi$as~r3WHc*cWQzkw KR+Z;6Qt%&|%Hs{P3~}S}C7&Ou0K)>;m&z=djw(5nK<` z*pZl~WB3QS+iD8|Me^Oyi?}eC+9qk{Sv^Ub$!kbP&=6gqRd%$cN}TUj%AtMsr0(qQ ziK<@b$4Qf@Wq)v~$!>VQab^^BLk8T}KAXg!T`nixLDBaOfz9u&PFmY9J^}*zj9so6 zBBB%*F-O6_fS5jhGz)!t+Fjje9;);035 P+;a&u}GY(^0=RYbf9Nc*<`_kMQ=_nCC QC0!pj^;m` zUsqzD)AuYJ+DU#cRME7-05?i3s*HfQ=0-Ootaba!v!^lBK}Xy6j@J7z7*Bb0h0w%g zUo`uDu}fF)% v6(Svuhi-Cz3W5r-TER= zd733CDXCDBafmV!Lo6nuA2`BimZ<1d1V3zlDPgHo=~AvWx@&`%_x=H=@$giQP3RIX zTHt#BX@ej>*+l|Ov@H8gI{hI}YO))bfQ9-eV!Ch_TQjtjY4Zu)x&*ZbArf5Z`6+ zd70D(B=~cs9O~Nis&^O(c?B_0bNIhLkKJE_G41@l;0EgW*zWIfd_Z`G&h_E*{`WBg z2jTQ<^DYAiHqzjnIE~(ZmHM)b5`yy*_7u0(BW_|ZOq#EZx+4W&+QU@8vByw%B|NnY zm#5Lri`Xw>R;&%~qTE#ks=e1wsskiuugY(hj|xY9r5jdxl5*st+Pw|lg#y6AjHRUu z)T?R!?x^(L*xqeXhS3(hJIQpY^-U(_Cko|?`Tl{`&SpuBURg2qU0TKeR>_P^4Wu;n zlmrwzrRk1pHKmNs5XY9VMk&wJGU1Sv%j)=J7jy5T0N_JsGqc)L?T{7QiDN9N(wHP> zH#qNTLF(DzibslE+2fe9MW%JQEVrJuATYCGvZgC2SX_@J$pEK7v0+r}!qXoOZANOF z4`S}aLbqsdV9#^SB&MR1k`q-ygOxN1V`Xg!x9ra52` nbvh7};@ZH))0BYZmR_UA9XP1fjO>vR=?f`k WyBch@rf1%z? z_sI+qGjvKjVj6_s$qxi|KVXAZh8WeVQ6jMNu+&*mqXBhu^*gu2$5xlEj{+~_)Rc-Y zjc}rA|GzqdZaD6qAw|3@t+mhr7jcn1Rg|Lg7? Oe|m z@Ik)@v^*mBus_mwzNn~Dd}QW->Obn=>OT}-odqUn5EK~$W+KCCK?QmA@Kw{FYs{`J z=nYQjyW|lCp!K{LRG2{~LTvsNT40-*;wjV+O$5m3l%s?f|1 ii)vIO~>y?v105j2%iW(O) z#TZU=yHM@zX5Dl!WAm5$-+7K`3eU ~9{=w2IxReC6Fn0?G?jZQbThGW-Riql=MA()mC3MCQ@ZP-YC;~`=M|R~kB>_) zi%YUCyls{aQRrBBLJK`sDL@Z@iQ6zcyY1?mCbAwiH(aI6_&`!(>Pj(XxD;@)LA48g zm4Mvh{0UByvdbmie~S#N%YTfw9qT!jksjN$^aqWem$j?M2J_bFKZfHiF;lBTZJ1%s z^Baq@9%yPV>n{9&M>+0kR9}|NVyG>R(fn0j=%NN*T1z>3HqT-upBRx7HT{DTsZ240 zSGu_fvZd4a>G)MC7#GYw0HAPX;JOqF93x3m*}*USzyGI5;>*eV#J=Mgn~hAq+|(^s zfLGM_;;|BX5P7OMmpqIHa$eU=nQmwJ%psb_%26>-xqvNWDB|OIKce@PEUm;0nXQ1j z6n2Vv6i$$bdWG6058NT mh+&1!}VvH~ssqwpswEU$`F-O5&jWmW}7i6w7KWStVJ zgAC+|G}({?ptw`Mz+*ME)_!?1!4HAz8d>7Av0Xy)fSv!j3yZ4>dQH7UmXzqjcLoPa z(BA;%*}~7+^JyH2PXWqs#SOV<2fw^uFg>RzKu~+mmL>ahUG%{BQxl_2Sj2o0E)Fn# z)K~%ZZ-3ezUJjJ^!H)g=Ml0*F6tDpX&T6F5)O?Vi2AO9)(K0HSbU^@0K8O(Xq hFTR{?xcVl;#`~V0Kkvzj%!+dVHP#Qy z n>HgtYj{MF-XLo*B@!;}924@uXB~aTg^c3EPN=byJkt6gp=V;@r~J z53029+dE(Y0unoCb1XlVv?TLm90)&bQ4E+ExM0Ie?0k+;k0?4yNsWAVQK`;2P)#GQ zKh-@HptSU6dr;_>w=~ZixE~crc%AyQDx;&d4(ZF4#Kw*USHmmG@6duG4hT`znVRq{ z^pFK6QA>(#N{1W#u(AdUo4a!(XZ{tkez#r4BCrkF)3bE2VI_trWk+GS5;hj!n``0@ zx)=W)-~bz;h7`a9A0gX2-lun`$IhpoqP!z7m8q%TQ^Ho=XTDvz&%VoIU$;yMcB707 zAKTI35G1ZaWI{eQI9>X>3>Jva8F>kGAz3q!z_2GHepb=*iji}{Q*Nj26{>+PQIRt) zd;$XG(~gT1=q{#Wy#uf$Kv9IaP&wOCN{p{3Zy#VHI7=tg2Y)&+5NbA8{yuGiHsoI$ zEWPm#sme|9 ?(QBg@s6@uz*m)aU@CQZ{!{Vdg?$etE^(M8cG?d`%{UAc6bF5v;y;mK$wkA+ z=f^nR7uFQfT{Oxu_*2CZ6U&t~mI=Gngn%&wyVN!u6C30bkbx+s)=LV3$?5s*SJPep zJ4#6(Rjwt0yO8m`ce%VkvTOuxS+gK{g{!rU_k&8o?~r))G3Zr^-8|3FHb~W3)GgQf zJU3B2;On+&rCRU% d241g;a}*Xbbl7I!~dO;7_k`{0l^Q!qHF3VoNd zArs^K72~0tV4=|lct?doO!b$v?Pun2$DR*5dKTd=Fo`KD4h*Um%%h^oU-=o}x#<11 zi72=W9Wwm*YPOO&w}o-21TV#dTzdHT^BM~K+H-*s$@l{`3q8ue8{Q9q)Fr?neKnX2 zi>5a5zpJ90F9%1G!*T^WMsiCTTM{#WTEMJKymzD1a4YIPJ;vyMp(q;I+!2>jj0ka< z`MXm~4qwsSf*P2N}0oo1=yoCCEQ7hlS7_vl7E+1pKzT{F@LN@)5f?cU4#2BJsP^ zH60~$hcXE`Drhbm2WHvSDDD=)0_C|)7QTjeof!vnUf6y{BfFuuNyf?W=l362FpPp7 z{n#GH_XF)Lu&S;Lo4QD{J&QKA{u@{+$M=A2gkV+IG)5O3qH}pMKts+oYWBTI`J Ki*%RmG4vOu*Uf4>}hqj)p+A;rAe#!oj z*>w-^87aW{Edm!JyOE}Dz?*N34H^WTyj}{DwmP6zXZjHZd;vMBnFbaMLwxrM!VF}1 zWrF}aqoU}rV9$iM ~0z0n1~h`Us> N~H3=TDqsc>SfOrqDj*QeGMtkv%(-L*NB!1JR5S`qqEm7jOqP+uS32(9fAF@ z44Vd(;PBM&lvB7f`9)NwS)w$!I)0UFNm#I|OqMS(UV_Rib)#ycTce158D3WE_gcD# zC#PhC;Ad6zI%d(n?jJy;&^y=B%x#rY6_U%t{Y!Uc5ew(a+RY-ldg_ShR2NKpXb9$e z1H3p0!m7)loSqapc}fNtIB#j<^S9oO6ZIV)#~V<>yuLxlaO2V1sXeo34I=4YZ>QBH z;q_2AT!~nB{ZV7j>jA$OzlVPPFLq<+cA1;ZtWpCu<=dV1`-@bnSOC(hzfS=|FgscN zQHk* Ya~%9RSxYtd9VLQX7kRjAAiuRtp2Hy|F-Yz z9O}7&fSKo{-exI^)z&4VRI7+Xqp%GC++&I&o(Jcwd#FST{6ao_?8HE0-Z@Fnp>VfS zOgeq~Llt~Vt5L==IiGOV;v?ihn1&Dh1Ya>kk;TS>Q@Os|dIgjG6P{6~=D|G3lWsKq zN5J|ga6JWJI6c(&Gd9hqG^y#jpJKF%xwx>UA!3KMrhI-oKzT&VD;+l6)eJa(DSLH( zovR~*hWyLNO`s%7Qfdn*SHg|5FgYWIAW(B&SsDGD%c-d*lgq)OR}8*?^1|Ro5DV); z$<4_fMG(7JMPh%tE+W0Gky<>DxKwJkTX~1=GU<^ zGb$>un1IomjWscFaYJNk#t-lZT}m?%vl=2hk&(9R;{SYd#ggxTUc6lehXs=yf=MI* znUqP}+v?vXFh38k0tDR1h)yw)Kzy>YRPBoA^*DxS3bO9@*R_k-<)dXW*--#7FN1R| zLA|TNi|=(Z*Mx^LbAQz;`nJkpS(5;1c1AzR_+|al+FjDq4A^kZ=8;|UYkziOuR`sf zvhB+97+%4jw|aOXRt$Xo0NBUJ+pOpb-Ly(s^F9ulp_gXMTo(olFihyEK&wki+pB~u zV^yl_$NnY>V MD{4SW8iw#>G{O)Gu zgx(Ytc#5+{^V#`5OU9R_M2v3O{0h=iG`L^@YSNwx;U2G>;Hfg02HBZH?;}<2i5exi z#@2;QHMfZEb+8A6w2?VED#8I -7Xnf%uO3+~KL}L?by7x9qS8x;-#L zhXND3w{3(VVLGN?Ni2fYM_V<(+4}{qWRLz^8Y~Bf4$Y;-H$Q%pP%gT%fA_Bvp-fc7 @;jDCl_7=eptO6kZunFNaaD zm|xp5B)aqchuGwW*ty2w$rb(k8YH6Fe)}tqUXBRB)P@3?>_=S9$9(Lr$cO~UJf3<# z)oIG9Hl_16cp@J}RB =*Bphb+?QahWnbBS~z0k*Ik z) +-YPzr>Lj>+8*3WG)+9TM`#~;(=8O)m~vf zVy*8&;O*Id4T+1%&PuKj=q-KoRX-p)rF d&oGcYm3L03+80lSW_18SHaU{xu0U_D~!;s^_ 8uHEvKWLcauNH=2yg5OuA8mdYY&jnO}P^s*MECdC`bz*aB(`R1jQy- cD&E@O|po6@~Yfc^WJQEmxI*{wu37!B@8G(6ilg5W(3J-x9q z*Fi&+$Gzi1+L7J2KXX07p`-{3mO &qvKD~Wc=YzP2@`-?}tZVbWY zYz^nj2Giwy!B>YTnlOCy-Z~Waq2Hos0D!^udUsD|&8fuhrV0!o`NfMyIr| D64a?9~E#NQnTAf0_WXbDz~B5Rg>auMBXYM!_v9cXhz8^j9)s^^b*z2g1A>+4 z6`^M|Ix FIh1e)X)IzGyOyT|6?SvZUxvdZNP%C@AtNky z;ZYHM;vyKq!$1g&0~%0R5*}=Mx{c`a4$;& =P%{qmiL~Q->b7rWKv5h z_^>fAJ%{ <`Ark$j$i<@@ X$Uy4HbQ?Zg`r-`^3l55rR?lqGAuf`v{}%SRoJ023$G@nRh?rkg$?Q z)~#yqR(_~9LqzSaXzhkEhH}~eK#FjTiM6A!c*pypM(ypITH&}{V={1@*Y$b&vGGY* z2E4Nr`fYpG@HMopw1e~b53}RHeuAZkB|*N9kDKfW@2b1* b#eT z(y@zR(H-l6?8J*LMjpWTn*FyHfZ?3%$pAhiB3e A3G!<(pi02A2%zg zCnq!nfh87*xwbQcZtp`?z#UpYw!su^af=(aH^98()P~SvMZc5*J5pNo!w36mr-sYf z5qt1iz_Noe`sqoN&IiM+zb|&XHJ7Vwl;$p;X@jAOiYs3g66#O}j3Jo_k761A4qZzm z!subViJS{YE%dG_nB=mj8*%zcSl58T%@yTo(R1H)rzcpi`-H?4J2?>%!%RN@N#wZb z;a8_Y?X2IE!xO}8Xj83qtD+7NK-T?0*3SM@Gh?tnTDrilFB%ZQ&N~N-QD4UAu3XDa zDJ_Q-H6s{+sF+`Fd#AkCu$NbJAs56&Pa!6qK$nYXpLPRHIRq&NTWHYYMR@FhC^d5! zRK#?!;P{JJ4Wlbil~q&ws>j7I|Mp%$`(&@{EPDewjnLUt2TXde(;T#~@PLCvk)Cl+ zd5(Q{gkZdtU`4Tvx`s}d4HnmT_UCSmCbr0l^g+#JqLI&-o!peykE_L{-Vi>A5XKOV z7hG5!yc}L+SWx&7?Ma{QF*$F(e`jN8rnj8nz!*Rqy~Wg*3<;Qsq|gDQi+G8JHNq!i z#f_m%Bpo@F%R=D0jysB^X03M&?>+C! F* z;Mh*GIckZ2F|pwq^nV`H(~sr#`T`@TaH$ 4WqVCt^kUqF)`e7mJg%qG}ukgNo?(n;NLdIA2N z)xb=Pc@9LMoUY@PuZZlf>Vd%F4uoKby)BdmA9N_iT}RdTzj{xMu?{yz0Ef@ol}R=j z1Ql?JrOSvhE5eP3?D*HJR)c}-e`;Xe%kEYYX~y9?3t87y4H!I%7(2+k?BjMzQ3J}K zmz<;t-|{T2a01WOTq?*KSHHSN-J}U|0fD*qlajbN$E3TRdn(q$^zBzT8z-X!V|L%d zg<(c64-cvi0VZS#;W{F{2#X=kw=$~z*2uKBCP|gu#!Au2X@OlhhkT6ilc$kBsnMk8 zjoFt@9p+#533H<(hAo|D@!dSf4yA}fN2`c3J~p&*Bp$I~i0Wh1P+l>o=0<$^F4Z4w zEAf;Sce)dCN`Qe+fXo%_Rokj6B AwE*a7?b~rEkQ|acq14HbRTw`2^#*6L78a+ zt~#j=UTu)x!C9TMblsaShc1zEz{|gaUkrCGvdKx%dzqNeQHNWOG<;7oM;uoC9P!=5 z;rFDdlsgEvms)Wkmf?uPY84v>0yYhzwI%)ucDqDgIjjlE7n&CQ!Wyl?f8boL+UcT# z3+X_rJEHZMp}>ToBER2ZU8Oa*GV=-go0wtfDb Aqih z#ud2l*~$lH{O_k&8km34VDpks@=Sno6VLu|kp=rTqkGqMjz-04L3Jj8g^MZ~n$`1n z_pKuIE&ykU(kTwU^@g@$4VIJPq3#D(x(%b)o_PNVV+>A};I8s&)ZFISp|aOuQuN30 zHl4huZVWS@A94~ #mbU1pI_-JjOD2X)^wKI(r`64x^HV>y`TtLjz_R*Vz0O)4%W+QFNR;83 }pAgK#kUqmnag zJD5DC1487)vbf3CN~NlK>FL}oA-4%;e+OR&PTp4tdA51ba0=c}$0Ude-DOb)p^q0f zu-Tx&Ii?$7S-dZ7RqTF;y%P*X;>9IEZn3fpF%2tb(n%K~FbxW)DtYxol1sN)d=Y zC@GRO!?lylsOcY+dmyTo+7k;&%XI3%BX$n$RZ?GSk?cyPXKLllRhOFBQa&`As40NY zFm=Rs8u-REFdU3n_cg>i>E>+eXK2u~Hvq7nTFB|>g8<00aiV>B&S+6J;(-qr5N$c7 z!nFST$>@@w5-iP!)V8INy0yiQHW(S@6w_7-8!o~ZGn;!nR7dt$f4R{B5p`_ZLGAW* zfN7ru3 Tb z;}VIV6`t-U9dA%h4A_Dy`q2T0r3wZKo&Il5_y*eh3XOD2MdUUgQ-JOYYHB(0^SdRS zj@qM>jDV-9Rn&_H|H+!L#fW#N*|}JF?+O6b)1p016iiNe?f7(ld@nwnTibAeda_x_ z8O1Sw%s!M{c<~6L_Ps`XMTgKz8~7fAn#HIIlX$$hAU0cA6mRtJ6zV>IK=PXy6Z*tr z=!@htS7CmlXdcH;$33DjqHoe)Tce9h^YimLf7Ln_a+ rlt{)vi+vJb+kuN< ztDh5-f!5R4BvP (y+}`S!%b z#LtN3GxFm86&tgxUK9UN&K1Q@KcJeH-n2sRRpg8C57F0bm|5`Rbjj(d?+Im~5Jfsd zh9Cyhd2%$XzVhwvV))9=i5RT?H%hHkRE`NM)rpSD-&QZ^HcI8ap Ypll m z>q_-06Baj~DBQNoSGneKFYANunk``vH;4ZF0xVp6_wMyyH33X09*6jns4n^J)ns0J zn>xFaJAG9nX~wPd!{?9dYd@Ge79W a z#y*AH!b-qC$0dO@g*edg0B$eSPj(MWG1^6+ZHNLKxN%aX)kgF_`#UhNsQ5+QBDNH4 zk4WlSf{qy&dpovNKSOL$zc^6E!E>>t4^UZ5E2GnstDT(QWASCLfR1nToA)i)*0Hm1 z15vizz`^lYdIxuc%2_L11pMa(smW)_O)Nh}&|Ga;Bi40~&-NnP6vYbq#i-rEA{N;7 zPK9lBGp%zp#jgboeMYMSu6~6#8GF)8nJ#d?v+PXwmoWUEMQ~?vtTN6=;&1>uqTMAm zb$GyiX_G%sz&ST&JkYdJv=uKvj>9blH@{&2ec=9w5r#8YCCNT^3z%e-PmZgv-n8UK z1QP~=&rB1+8Xt!x$~N58kp^fyQi|v9!sqEz=-ClpD=RuiHAicB#8XQZ(bp2tc~^c; z#{Ie515sVxwGKl@W5W*q{geL^d&bwb0kttt;=0bi(!e0gn_iEtQ{ LIhWP#jG =tPubF2pj<7eOz`Pjr67#}_>m0P9N{`xcM{(x lpo>wz SUEYaHW^)WH z+AjS*mzCA4c*W_W*&KmCB0+09cyjx2s+Wv)d8X(5o1voZ7YLvhGPr9UNgY1j{91O& z%Otr3^-R=ZiDda&Iv8Eo8u StkzuL9(yExV>g)*fyOttE1c|q83=TeeY*J8@5}w zGv8W5))%G?0OWLazXOPM0lwPn*{(0D(#O0|vQ)^X*pQXh5z-%SS?aD-{_#Lrc_ACR zBaw+~+E!f^i4NgrEmKW;UR>bO`z{Nj2w%DVJ6z}HLb^fmu#&*tLpzgk{e%IRqgcB_ zu@>d8|7gX0%^Er+-1LkCm_~ZOBMyUhl8`!HBNM}I3+kIrC)|01-$xk?qXpCcrdC_M z{&4jO(!3oT;$u<}$E2RuAB~`T)uRC)1v-2;I_*m3Pf@3Z#wIxN|ZJ3`T#5Y ztC*)miLUF^SxLnz+`0r`2U2GDjVF_?)@^&bCQsRV-OFDPnjZ}4^yV?2JNVJ?av)$5 z7hXGp*d&OxT8}H%>%RszkrSBR)4c0KRiV@6>S@&wfD3uW=Yb*g-bNsN`CD$ZNWqIb zeX&FJ^Fq91m!M!IJWOW^slvxyhbLHVeQMTebO=SW!hQaXY4!&tyP2RUoS=g^{}&w6 z9DluvmQ7|9h<&IZzg?-7=Hglcjhi4Nf65r!9 z*+dgujd3mC5a!WdGcy1%%msra_%*LZyQZOU4xFID!{5K*ZjNx;Q3rd7PhDSs?Bh>D zyx91WW^?+ZZT+5KdXNL#)wFlcO5Fa8vzk_Rn`q@v%i$MVh0OOGCp>{qW_q0*(y?8{ z+jI*BKW%7o6s8dP*LYdWe%0n@bZaTtIB4`e=nfM!omJ3&z=r`