From 5ca544c8bc1563b3fc8294bd9649f3c7b3819762 Mon Sep 17 00:00:00 2001 From: ss13-beebot <56381746+ss13-beebot@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:13:48 +0000 Subject: [PATCH 001/198] Automatic changelog compile [ci skip] --- html/changelog.html | 51 --------------------------------------------- 1 file changed, 51 deletions(-) diff --git a/html/changelog.html b/html/changelog.html index 12c1fe5607fb7..664a876c0d137 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -758,57 +758,6 @@
f zf@RIG#6o|z73-=?{oypi#hvr1&~#U}S;o zopaWdj$Jb ~0Ri=@#nk ql>w*Po^){(TSpj_v+-fi!j3` zTu{&Dv*$W?Rdk5@J^mPHZyb#dDjtHCmX-&vKUMjt;)UykMdM`WQzNxfphvN?3cXS1 zjaz-m9l0lb!i$wLW%toz+NZTB7KR@+U03^~&)kiW4TM}}bZd%Vd?}iy%&xgwKWTdu z%hhRyYDFYF@ppVUpcxjCh|N&7wfe|8H9I4?FCP0<<(}Ge%zRhFN?SeLZE;`Jcv{H% zNk7So^dY++UeB}Nw!$q!)Rg&M%lE*ksZJU49h&~lVG5`60 i$1J%j(^`cZpqi>X>lu!M)>O0lxE>ugT=k^H1Pk%-{dtyP$INpC%3EN4yXR;6|Kw zWOQ&UuZ}k9u3UhNUg C%;JlZbW*_ED {&n01ar}D?2_*R?eJn(f_Mng8iw{Qi t-n`cH1Rr)(?F$Jp4n}?(P9~Y)}?l0YeDay{3A8eB) =BJDXqFqtVNq##B{3!jB(a;cUF#GMrb>5^ zEB;)isDuN?(z~{;?MZx-J(xIFz>YBNS{NCouZIqh!q?^{iQkQ1=CBhIG*;VmEF~LR z 9C7a<^>6{YSR#J~o8B(4dlJv4_0K)ps#{NPf*uU+*54#%-IYWxRFk zLR*U+Yjz+~sLugD?^&TVQbz7Ci6?UB0sp?gcws!_;<<1ipY(Sw&AN{0u(-ccQcg@P zCb^?LNS>YFT|Vy=-;=a24%4(A=arD l%j=j+1|n^KnLFR|$cb4l(41fixNG zXu66nQuE%&Kc*_Rw1`6;X9GTgyWB*lJl==^t lvUD+fsNpTf*8 h1!x{zC80_Iou!GY*%Z`Tl^sy-Y5bpY33Q zUp_g?@^ii6@wQ^3_p>cA+6VwBIh1E%k+VzS$iVPJ^k3s(8VV!eXA@#7D|(`4X;%bi z7QFBtfb3>%B&?b>{-Z`&cIXHd3-L^0J&E1409q=35KEgM?|@Jz*4_6roAQpilVqZI z3amqPQ@dX?FLIR^R;rXHeK<_mZ6o-|qSI&DTdvr&;L@k{JM>nOmY1};yxZHbUZJgR zc$2xg0Je#q(G6mFy-}D1dVSDxRx^*x^6Ts){nXE7U^<{rHw-fA(gD*)nNjwho@z%* z1j6gYWn32k-~B(lTuloIr-b)3hv-eXGnH?(;m<`#UzGtg%tUX{rNetD!VR(ww}}eb zwGtN=g-E2EJQL?t7BU09irPG CnM)ys(bV6-B7>TE2DBnvro_%=>?iBky+)R_)b&Dv_oROy$ iuL6XL+EU-K-e Fz|`#3((f*% zQA T*EqANHt~F8&{g z3vmCk2}*00yRB&9hhbLPvXC<#g6FKMdD!V1?*5f!$5Yv4SL84j lBie67uYA>Hp7#9pD7ZH zha)omLI^b|yMO3`Ce2{Zm8$g$B|CxJOB#S>nj$dcvT;510{N| sJE6;j>_ zg!srumK}|SAH)3JE^VO_@{!ay3qXSW+iC~J%S_@Q_8U27UrSmGnLL arZr#V6#N<<2Y=xo$0J1JX=cOI1x )>v@w^Zf2{ zENLAC4ABY}Q4o(LY;pyFx!0worEM^mh9TpXr%#W+zc#Daf^&SHhO_Kykqy!fjXmO~ z_cI@k)gKlFHz`4p+c-17=f>hzO+YgJ!q2s5*;6P6>CU-ct@k##1a(O3^4uNOyL?FD zyh2Bnb>1x`>aF5n?LwGTI#C;J&rUjzc-%FHvPIYfcK868^AHT4iJAsU78Ih7`eU|* zuRox$IEL;{9$-2_O4Xw%r?^;qo8WK^CX|)DbK3rM*uGCm0J%*+Zp1RM bcX*%*~g8Y&!lsXrKlF(9}!fQODtE$5?}3)R7N91BoQR z&cZe%na}4(0fumY6SV{+onI(cyj${e)7ekD#%3H3MeGi-QY(_7PLfqQl(GsDK*{RU zFR$}JxUCaTp}6o~O>jJr&MwCDkGjS(X>j+Q#LwU^++`%sHo=csJa(~IKOOrtN%9wm ziTu%WSjj)^eZo2&%R8 #<#!38MvRDaXc;cO4&|wM^ zERi!Nr=_=Qd})FPT^AHL?x{;hGU1NjP)k|NX3WkVd4vHUFi|ZWCrSRtYcV-Rw%|nP z1mDo&$r5Gy!+znt>s-SJl_b4j8lMyl?m0>Ng(jOE`1&|DqaY_@0Uk)GG?FM|22te( zWW((mTBqlW83%B%a;t(z#YaC-5ErZhZfO6!cA}aBewKm>8#R>PCW9tD#NWwy@8p!K z$Zqw>PTa?I=8atze<(%!*@?tW+2{%*K1}_V7k9SfPWv9f8X@ro9!nA)pZh_ze(T16 z@BT+iV}n82KMU&5HL(eWoHp -H!Zv}c~UJW z7P<3ioLwX3{o9Baz%Vl^OMS{K)3 h>Jw>S&Eg{DOy zDR4&V{~C?ipQmiC;L6!0X!b44Co3A*l7BzD5al)WLN1wvqHj27vTJc;I>F~2>0Rkd z(=Dyq!S9|^a?ffKY9>5dv=;z?dneVwraySJY|#B9ci#(TGq$gSE4BS|oU$93MF#eu zFi@$TGXc?XGL?X$!KPnPuaSp!o5G9nn8{lbe6h&cSJ0N5X6oCvc~R^<+W2r&nuFJb zm22)?XLs;La5WruAzNYb#!7I{7#2bIMGOkcslLyZlRnAWrWoJ-6C(ntW>`R0F<2)e z< cS()77XW(IIc`aImwSMMs%_HIr$JBnO4 z3{!f0s%`y>4C6{F&4|X5tj0H(LFAdoCMj(>p67=hFYz@rG$gxY)i2$5p59qMomF?U zBT(`4RZmT2d!DaFaLvOlJp0bt$M+)6pC4QO{Um1qdAP-Uj$eCThWO`}F9NUl5t;WS zgr9cL8cTD6)10h9i_lN|voz<-LC=6XCnq?J>hh#@5^LoC2G7j0#2hjt!1^C4tCqW> z;J2DGqO>(j1IK&bf`=;E`~wLk{Uvj-xiU%QK@IyX(5NBB@~ikW*E}+1;8~l>ftp7S zx4u4KGr%95oB#|M?;MYP(OJ8kikh1wDbwa%z2)af{qzCNphWP4^=!rTKUSB-890?5 z4u)nnB#q4*VAg zIyd4mF3$W1ST7{Dj~58SKciA zxyDV{b4 C=bf*=pmG~cAeuu-ZQlpG!e6=gr#@+z63cEojP3P9~D;eB}#XlmVq zNlwWRZm!d;YjE9LvUF*O%WztE;Mp_W;!uAuQOpF+{nbQwFlB GOsK(+X z?2&tn4IlfCY!EcNanA$jUN2_HEeshyVI!-=Y9?SXGM<&c9Gh_J$MSsJ@sZ!4*V7GT z)?kU+mKTUx`J?Ra9{RR?FkQhP^vWd|sw83q#RQjJucoXGE_oxW+? ETt!4xwwjYR}l~P=5gx6pF^14q({A`JtP$h2Wxtdx1q=Ioi7(dcrIgos;hyhYe zm2l< v^Cfy~1%=p^HCgJD3K0%Q5Z9{v!Dh;Irk?n!YXzwSHSb zY7*3+VH1rfuU^t$wHgvV=FFZ4$KBcjy>ggV(>3i7_Jlo=@{xi>iWng(VE?BL }>@ zIs>dPBM{90I}s10@E@LtFJty~ZE-58AMzz!I{oHxgAS1SpOxq-*M?tC$e@mppd!`` z=OO%*iO)5f4>*BYObC>;gYX15)#YphSd-Z? &ngVwvpkO2> zm2+J5kw-y4MAeiADa8L@d*2z>RQB~7z>%?x3^qiCI3S`T#zIkAaKvGh(O5x12!kLN z1d%57;5aiPiYN*wC9w=5HPSo55s)4QX+nZSfJhAx0;HYuu7ke+`{6$K(|zCPdGCGx zA1s_4a`xGK?Nxs3x7M~~jl8%~Y5iD9u`=^s=F)gwe)e7DmPAJ){*o`B$@i%5Z7(#3 zj+1`E554|{0$z*S9mJ!nhHTGyo=v{munUb;M6|aDbmWl@(SKHvnyr^T?866nl}mJ- zbj~GfxiLuI>XO^O3_ZyStURf^rMNYtOzc{NJx?GSl|Bqn{#9l;h|Q%igE_aXIHx4W z_OS0jy4mpndYz6{;GAtMA|L7{*}i4gt0~`3U_;CswaFfJ=X%O`ju>3#yd}#91feC8 zk^Jrvvuk4kFdiGl!k5CqR=J5Y7q>Cf{^rFRV}d;LshO4t`l0qodQ*v#V-N@b%h9)V zT8QX9o^lJ8dY&ZDYHpf6m{O3;-W7%DH=jZ;s{5QkV^=yg9U;6}_M1uvajBcp4v$#o z$r|Lv9WL~Zp#Lix$JTRuM_aJr4MnKM2CGH)h88^8%1leAYsB*yq}PZ1Gq}`x`wh^I zgVig&uR=xymX3|DP`99w6SDQ;wK3#YF6Pc hv^w2}VU}7qyE1{v`7r0kVitAD zk~(RAxo9Q=?oqhO2-8va`jb2T*0JcWBLp+VyX-Vpk3}Nas&5x!)g?A11G(mQskZ2$ zE!C9jIYS#n-y-#!iR{`~W)~MZt9rV`opoKfD+ bnCEYo`+%M*IeX>G70=Sb6m~z|3>fRtO~?pSB(oiy*S&p0 z>f>#4SJeoX{l;?Vw}qW+peGaR4ba@7_7# B7@kafdmC 7ikJb8&ZuZN3wwxr1I8Ed%bFU)9hG3wso-r$_x)po95{__=`c^q6GizDI=@py zG7X5!!t4)*irgf2LtUCzdGmYHaYz;CxA6Ako8ax#>L;+W#JoOu-%TQlhg4m3Vt}$6 zOEHt0YB=+aIg@vBUo&9<+2TBP n98N$@7*rYWYqNc{a&1!*Y3r=o>M`1UoT6rznPH zZUCj%&^t8t`C((1W`7)l_5%*wKDYWw_%%P=hiyAZ_5EZ&e9$RZNuGhd;~!U&>b#97 z*Uh=nu`Yo<_Nz>rh7C=zQWPq!?@`esp8K`y=!VGPs4^ranN!4$Kj~gxFR1>UUt~JH zs#-XTz^bJV-{QMR GV=1B@m (6shu&hQ_U-Q}@Csmzr`}%I@zvE$;hW`^B zu*9fbPPM0WFwG_nL3{-J-TbfGix<~r`Yyfa(;%HZlM zywRMwMEGS5@Oz%Po`IV<^KisF)vT{@#DD&~#7;y;MoI1ZA>F-GSkcbvo3GticwO c6t7B) zA3q~pr|Gz@ctJgl7+y5<@sht`DOMpFw{IE0eBJu$gz*C*Wi>>$?PS~2*lt^l%}DmV zekXix-a_&`@2aFPKhIWVn0j(;#)EbikUtI+*{jUZArf6>a-efAM&fv8_Vp#!?eN-% z#9hODK60)j%Yp1MlYjmCb+#&PL9RN%#duBPS5w~rzMyfr+L*J9 drW-6Zlc#%F+ufpS zqAJ~C_XYa{Xpe|@i2I<|)-6eYY(z54kAkOX1!_FHWVx5H*&s;vy662e!XcB1bZHKk z+EVcf&BVS1Fq!Tmqu5;a8$qH0*=5}n_D< &>HK^dOx? y+US1LGLHw4%=VU=l1uW$tw5XseT1mxmQQ6P7~081kg*vDf42UJ z&hN>oEVZ42McFIcuDj#%(e~Q2s! Qc!YuMk;7}I&!4T`*X>O8J|GoN7!2WQn%7=MJpo}2~g)3;$8KR zLKvL4@u^8UU>V_D19i(lCBs>ol8bsrF@^LXX(rZ#`%dcc{iSc2AYCROS5b&;>Cuj0 z^pp-nOmtVK{2{qLg=tn#HP2OO9M3tYNYLVWnh+l8BEBH~an}ov7fjM4g~*&=9caU$ zoJh<)1A9|w)$A#w#9fxUos|(Q*x({qhlNO6sb(ba*So0#BEpK=ZtBC{60{Z@pZ4?p zvAsw6CU0L#;f-A%hy<67kZb62B_gnltnHFH9ANQd8=*kC>JQdM2J3^HsWIx2h}#2w zf2|<2xP9&umK_z6aVd1gA!EHkYSEQnQw);98ZglU{4D`_1jNY{g%}3l)VFhm{fl3T z?9j#-(IezqUz?V!qiTA#- W#!Q8~m? z{}np_A_a^Xn(5zjKmv8A>X-&qN!MTigAJY)?T#DtMr)# aLT`o-T|PX#(l+4$;7F`fkN|L |NGFbGSLXiF8zr9Jzk}tJ(F7e#mv!hV;Qw&Q1SCmxcPQ!K3Uztmp1b) zYL;hgtqA2#7v5aH^hwa>O6X* 2FlNi16VU9%OqDsxarOqt9W@ z&eN3%v3e3iLQ*nrlm wBZ+x7n-e*>ldC61Msxu4KYPDtc}fXwRjE$t(I<2}~vv zdkj&btf;GF;lhOxy^37|%YGWBJhk6o|Ni|e6ciM^@FE>fc@P)>-r})>?oDZk(s$n- z?m;k_;Mscdsm4Mn pI94fME?tLW7$ZZCz zDU-~qlq e&P7ZyXBCy$|CS= dt!&ZLAw$_+yaB*6u&)`+5@gmG zjeC#~I@aGx370TMlml@a@O?Y7cJ12umy2ndU4@=hcXo+>$ t{96NJ_Z!%~^+AceRi@*zXXU zxM?6XX;VENR-_t&^}gShY=6o2?{M&66|nT$U%;c`?MoFU)ahXfk0~OGne2B}q s$b^P7!IBZrQ4yKrJDi-vi9PZ% z2f;6Za5gHgGjRW$%KXIp883X^1IC^E2HHhi+y>;;z`gURPA)B2Y;P&$cr=iC-<;;I zFn?g9fRuqf)}s(|2UyPYzQHp2wC=7cLryFe-IjrHd#~9;0zs$kl#}v6oO#np2!958 z^DlKorRyJ+*`wo4)EG0cJP(Tr^@%c@Z0G6-+CW1bv31tbugg`J6F;-ArzD>@mEu>e zt)ynuXTL$SL z$PKO+I zkc&MvC>4^zT=eKuHr1Bg-Y%Ezs760_>Wt6(18=88U^hAVuKI9BR2gHV4h!-*OS Z8U*Q+|n^XPQ>Q8QKWH~PjPBk%gl8V6iQTh$aoG|GE4TY z$P{g&OV_Edam+!7Q8pt)FcHGro@inkd|{MVjV&|>laKkDiuxbm^->=e)4*9Q8i=*; zg9CcP*`?al4!errfKhg-p-0Kk3)(6jYoBYkdF{bXy*cuD{~6Em7m9O!5AYHX{ tU5mo9F~*7#eZ*>*eOz6NoWeub%bz#mgEjDpp2ro8;QNfrze_rK z9336FDJ>STV!m5v;B#uS)q@=moz>?flQ55~XHpDtdrq)B_-``we}y6c-)ZHS_svi7 zW(s)e7D7G% g}#OEFZSk23owQ3{xkg zYDH <~=bhoWAlwT6g13CgFAM5(8XM8E&e$AEKA96qzNs4Y*-^Jx-W|$t zSKuA*weLdRK{QCQU@+>D$wVsFIy816?&;H~x5PHmbaI>7nzd`UhEh8f19)T?ruc%3 z%f{8Zb}OSyXHM%P<#6KA*^g#h56?_`J{$_D>@ zGQ(?Js*qAj CsXQT9`cYD(x3iQ%PhvKI(;o+)5x`AB|3Z zKo5V^sIEL&oyyQRUbOYtV931?)UrrS(%_q25w`xNO%I-WsbyZ(FZL`maxvSD2K1sA zN0oLf-ZfFws2?W}o<;X=sf0;+mkWgf*Tm8TlhdNxFJjcekIB5y+kH8%vgwa=BGw-6 zowFZ_T6IWB7ytk@k=XctFD_GZG;=O~YzE5B zPq?7r(1m`WPMdxQPB%K8jUXAFOr~GX2~nvEJ(bN27ELX??0U(St=Pq5;u80A5Rt_< z`v_0C;!D#IM~VW;R}%(#Le;l3XIX5BbT?o@Z!4UaxtqS=>*izU8 z+9<+5|NL_kUX1>++X26K?&$hEB=}F>+Z*7)yagEK`Co `H$?U+&6* zr9cMdUq!>~mWxw44=84;w^!>%;4cO8_ 19cTyxQmD2buN>A)zyQ&MPA%IB{V&PA?{A01zyY+DK1?2 zqC7u&YsIi96oRZwAYT#z(%+wQ9BWTGP6^%mcy=E!8OsTGYRi>&QDrB)zPcvg8?t>) zP=vNu7-z(s&RjRj$#3@EvOz4M&}v?v!AiAR63V1)x!h55LfPW9V|6^NbqZsbCXC+z z-`5R%-`5z-nX_nWpw(;v$nZe6Kc90t+xY;+@OQcusD7qYwIM2eNSlnWd2xTV)kR39 z9GkAAXbH?}0d-diQ!bv}H<(Tz)<4(10#XQlFA_Y4ZDC&>qsvZ^By(=tujfRCRIlG- zq6m5W@_4b7$CP%ZPckw0!yP<&u6%~k97CQ1jdJiuU&y?Yst6Gx{@}e0QHOGGlnx8R z4w;Ek<9vxOfXpwc4DXR_5VTR60HU2uEazRCd} e!_SGKjv8N~q zs(ibrZs1J>-|NPe`{Jc)bZrE;+%kJ)JDgMaj6#TqAc_uWd@VZqlkM!j8W9 _(p5}&I bKvH%?o#BP0bg&~r>LhcQ{R2#eM8_@yPTq! ze021OqaLlx%N{NBzcz9I_4f;|hi$n1?ZfYHJat;Cx>Wtg-D^nC*8jde+3Ea08(i|^ zmfpHlr1|dY+Qo*S7xGTI9E?>;GmM5Jc)3YC#vNg(@X(Ybw6|9Wu#D6%Y`>qsB2E{- z I3PTD^Y_@xbkWIVFoO@@H~H1_F%HpPhK zSO)gHy1II9x|PC(!<#2Tx9u@8xuZgItm^A?E(?_z&CJfa)dccRnByoT`B}W^<^}zZ z?uIB@^9)71{LC+RdFJ|g@goYZ*wpism}|Yafw?J?@ajm|##q r}`-q-yTbKcvcSj@WaE zx(cMv%K{`GEw^_ZJ0Q2m-Dt>Fayla8RQ;JwJNpZsnVEU^O+r6UbTsNf_9XU+^Q#Fw zQ2D(~)YR?2KWeGlRZb0b<&< PF(r !0d6&0*}9-`i3e5aJ^a674-J3cMD|0+*23}(rX|I<;v&n0f&n#M$O z-{^UUb3b+Rkqc3-UD88(0W*}$B46YdcIc@sQcwBSm|1T8W3_NpMAAS|X>}0w>1^PP zRE7y|URH2vMwB6zW-i6BnwcNw8X6jQ=i6llg$#~(QkFcSEr0ncFHbM4G-<6&WSROS zx|M-FBbZ)jR=N{*yOvINo>=+G@l-AOP>uBYdbK=mjj(tQP3bLdg&`+QJI7qSbU7Jc z@5m8vFP|$$ohkly$@jeublE(lDihUjk47ZQjDLG9uo)^g{pCL59rQFv)z36t1KHWf zKo&S*FCV%0WA~8GZy%-g^F!E53Fii6z<+L-loCe@&9`rxUJyhLZX~bmQFe52hy?c9 zPX(U)jY$aqGb4O>fNzs5edEGHgw1uu15^uCs!|t^nHUGXJ?7b%R2+=8@`nsp-d*;Q zol#y*-MM&s5c(rOkm)7s(|m6G!GG4xf99@cpU-oW_mD5`*A&93gE0c?pK4EbUG^YV zu^CxOb?hM1QqKD$Z?bIm#ZbXY&0;LZM+0;0QpiU~3H{E963k@t@bE>8MB0omXNyz5 zo$l(@t6P%fH^Oe9H<3rDSzG&~!s`c!$!o^+_iQvm ^8z%Vc(7@arCz!o>~_5e zD+q)Il7klePSPSf6DIzSafdl&Gsh{0VMmBj&(_zc)gmJ^qvmMZ=(l%160&}j+lJ%| zdP_`$#)TkMVsRYvqbMB}6J6I%MO|Ld2|8pw^xd(Aqwb3kEL19Z&*YLYndP?a+x?If z>-1{yP0t1d1Z?VVh7H(&7$BL}511Mv(p8^# xDWIuh7V6qY=Jz zSzd<#mS2%J+eKASQ5$Lw-FPNcgw>A4B}u2S``sjC5xIYaER+=T7%7{u&c&~w9R4xn z`277R)Xjp|kf;@R@GEwitAVZ}W>S<(?tDT2V8O}{9IYB6*Yrg@Ad9S%N0o1knEfqg z{D_Lj(Z*)Z 93_2!la`w=iW{3%l|ArevM>XG)ecO%XNao%*7; zGrIRwF6gaI+L|=eN|{ftq=^dKtryPBjg(4a)-2ZF76iZyubBy2N!4K3)6O8YVH12? zLoeK&{s=*vkP U|-{{m-yxGj)HdIJH#3`yC|7;{(;&^`#@t|EFg5t4ksIYt;C> zeuuG45KA;o+9Ca2`!A%J(`-TySilGSE&s%C ztt^V!C7-dG3Cel2xx2@4O_9gO`I}vJTo-gE6!JCt^7_~69Q<;0-6_hhbdQ%mrl zqEe^j-5)ytHGlWR IDoV+@ zAdkiCI{Mnjy3TKR*y}BMzt7lj=I--~Qoh0je&gm-FwAz)T5y&7`H7mgAsRerFrWe- z5r|f)Kq!9b1gcL#&`?IjuWf!fqw4g>vr%#Ws4>3sVLxmV>)yjZIco4f$)~~;!UtTP U%Nz#w;A1;@;D|-mk50e+2XiHP!T tn=-xv(}vRVLm+X;sy3^|MuR`^SkWZ zCp?@q)PGWkKp+~<$B!Z*5NMkM>(+wb9NPUd9(=LK`uHat4U0b$cOf?6LQFISaykFr zKyBLVeH&7~G+t3(KQyPZIr`gGd!x0NRgE-uy{@Db2hnN3lY~@9q5o{U9raR94MLW0 z7JHc7Q|)OLr+O#~@GIFGI-vUO3oi2a@k!IA0jGsp(TP8wE~1i804E 0BM5$PSI z{ld;s!^ZnyVeD@|S%mreO;?S!6r7S1lcZas>rd~#ar^@Yc7xD6CC+%totgz7o6?WF zSS{Wvp3+V~&f8$xw9y8x^?gH_RJSW3w41zT`VeXOP0o+oGCQhn@4=XbVa>v9_toxI zHO!&6y{Ws53yIjdCkba3cEv1=xhXK<#T!3A(^}tAZc3TJ`BIDbvzJU`pZA*s-#T`I z4|B!CAnwhASy9?&X4v2l&CPc=X%@}EJPUd}m`)hAj&7(Xn 32VqT@&6GmABkIy~|wA{QM-v#cIbI=pJ<| zdt#ZUSlR7v%5S#Csp@`5Cu%lVR%H8KGAQmjO&@W%cN-h~{%Q8mNxgM m*U>kD?k*lIVDMcq;NmYlw4aHRNX0d+PXR`fucwBR<;B4emI7EdP_yP)j+3W0u&= zq+Hr+LeyRJ?gYhsxuuD@fW+sOSjt}>(W>-|j0 IX3pgBq9b3}uM*=+>q9$Y@w%L@D~FnHWzJ%ckoB!7=k^>=wrZfpJ-9Kn@J zh E zzKK0#r+BLhBy;nQ|2qipVIgf}yipRoG8@@tNmw3C9m6L|7pl42P(1b&Dy3~pdh+)# z^p#@KJ5^XP2k*v}R?8%UYt4sRPm>S^MC!ooUF?Q7eZInkAa7~y6#=^%rbk1`8MC>> zFLaov-=}m0?=w Y zdn&Rx{M K9nc|1 #>A8J*cm%GQHpvs=9Au0A z)sR#~;32g%tX|Ph`DHd}Fy(j}1bVCZe}RKbh+B+knhs8sXkActnuvyc?mMx*=3mM+ zT@9=png}*|#V$;~z}bdKp0fvl $1S1YDJt}0H2k$!`UOjRH=M)L|G1$NVpGuPL#5B*Si#oPfUdyOHt-Cg zL$sFk$otK`*;xQ#*GuMP%HpAV5yW*^Vi(e~L4*|3De$Wq3g3sbwO*r~lIdZVt!##` zX*^Lu{0g%cQF=sFvx972l5zf-QwpU5wq}9;k>Q+pGy|)$gP24(-}FRh{H3cLQZE@6 zKj~DcTXG-@veOvU`#%XBYE?ah^R_^LRFMX3@;_vvWd-^MfTVeUj@R*Cpqg+dG&FQv z8Rh49!|s6>2`zJ&$^~XO@om 4oG@?fs4?bI@zv+ zha}7lv?A6E4j-e;iZOfbA?J6(u0eL(S5o;0;Vj9!)Wb9y(>x1m-!lhK&rWJ?nbg9T z4m|v6A@-P&t-Q>;bPN1c(?Q=;tOc1ND1PF!IK00~u&}yf8Lo9VbZ=2c4iPPFfAi)| zn`%`>#RmMoqMSncV43$F^6DsiWdZgoHmLwq44EjLANlg hsn(-*o#R@HFVcVu<@u$a SqYV09iz?DdL(f*p8o#jPpUNS?xW&D z`SRi 6=SpDKVJHD1@^Dm72xmh ze-cD_Ziu%=kP7LtichXYDI_#>BE* )5}?#KeTEaNR8#>a$(2 z6z#naGn~=Nt<$9%u`5^0XS{bGU>=#$pB7Nn% zT`*4|ayGq`H7!qt1db_J?A^Qfs0=cQ7Nd~se?DYctOwP&&r=HN{vjlB?l~rJqEx4~ zeh{ZCco0JK(I#>&yj;|lNIY&U1wu6d#xDN;*BVwikt;iIxnu*uV5vM+Z73bP_-Jr6 zJ4=$M{(Yu5PxV{wii`;xSy*|EAtpS2^tIf%2CZw64T{=^iFVKSWbMf_%f3+ssURWD zsol&8wC {O?l4H(Q(%Cxc$K@ND2)MkYA1dTao`-+h z)D@G#O_tqm$QnRP-;oOdCbmD+8!csNi1P}F9ZD)GhCHi(R+@D&ru6jmBskgLV1m`% zFMK|qse?|g&9}is7pZ*v#AdU%q0&I+1_1 R*1`)7O{NoeRgrFq;}`iP6zjhvsU> zOe#G}pI@3>5qyj6NZa20Vf8T;Rc)8GPpylbN1XBIye%T)h^*BG;`i;-s&XOgUZKi# ze=h=o0K8%u0l~p$xp1_Zkw^Y{H)wRe()NR5umh<*(-FO7bKv}atSp}0hhG|PjE7Y& zxc(QZ(T(y~n5f2ic^f(%>)$*hQ)PMS<3i =@ zT-IlRD3_ViU_{B{v23(4-Xj&+_0T+A){rIG^sLJ9cXALezAOuAC;6&Oq6>B*8ig}R z7hz*# 1%3)!yAR6@)v5_k3orr+tGvn;IGo6iQ_Rt ztex^|xzzCt3NqA5en>obuB7?pOaGG6((WgYH@iAXM%}~D!@KihS_8J|d_w3Ru(dta zmv1%jV1gQTQpMXn&M95rHdZi4zvW9zktu&KMl9!``%Z#MY|s(1Rn6&dw*EhPtC)R0k(1OMc#4}~ zUFns!CIwd-106DwBI^ug4<9~U7#$6wW(nA*FY^42DOt*{C?{(($U>!isXeIYX0Ve1 ze6Ak`8NiW~FWeA|#S3@ft^R!lHZ)^4Qp{ Y74A2QnBQ#M{-m5IG69;Vr>EnD^TTlyZ`7QK z5RF(CxcG0p>29zg=YgGJjC{7wk4hJ1ohSK-OG`^Fg@SJU)Vs{MukW8Q_4Of+acE=8 zAHtDH&~}bC=&b_L82-t0(EX_SYMo!b6~EdP%JzmyztS+N8z7Ujt;nJDs|vQ?+h%ED zxav)`Rxi&5?SNq}ExO(jIgJhu9$Tz^{``5^xpT+Vl 6e%lV0b9+%zVXk?2U;QSY za`6SQb8p4?0J6Qaq7=XTb>N+vmliZ+*V-*RbT{wF=wd1Vy* Date: Tue, 24 Dec 2024 09:18:57 -0600 Subject: [PATCH 012/198] Automatic changelog generation for PR #11968 [ci skip] --- html/changelogs/AutoChangeLog-pr-11968.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-11968.yml diff --git a/html/changelogs/AutoChangeLog-pr-11968.yml b/html/changelogs/AutoChangeLog-pr-11968.yml new file mode 100644 index 0000000000000..f14202778e8cd --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-11968.yml @@ -0,0 +1,9 @@ +author: rkz, haukeschaumann, JohnFulpWillard, jlsnow301, Crumpaloo, San7890, MrStonedOne, + Lilah Novi +delete-after: true +changes: + - bugfix: fix custom typing indicators. Cyborgs, xenos, and lawyers (among others) + now have unique typing indicators again after 3 years. + - rscadd: new *Animated* typing indicator sprites + - rscadd: typing in the command bar will now activate typing indicator. + - code_imp: traitified a typing indicator var From 78cc231925507117e35be3c986797efb998b0cf1 Mon Sep 17 00:00:00 2001 From: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com> Date: Tue, 24 Dec 2024 10:13:09 -0500 Subject: [PATCH 013/198] Condenses language holders, unit tests them (#11971) * tongue language privatization * alright looks done https://github.com/tgstation/tgstation/pull/76612 https://github.com/tgstation/tgstation/pull/72080 * oops * sorry, but its runtiming https://github.com/tgstation/tgstation/pull/56047 * our parent handles this ill include testing evidence tho --- code/__DEFINES/language.dm | 22 +- code/_onclick/hud/screen_objects.dm | 4 +- .../configuration/configuration.dm | 5 + code/datums/action.dm | 14 - code/datums/brain_damage/mrat.dm | 2 +- code/datums/brain_damage/severe.dm | 10 +- .../diseases/advance/symptoms/voice_change.dm | 2 +- code/datums/dna.dm | 5 - code/datums/mind.dm | 60 ++- code/datums/mutations/speech.dm | 4 +- code/datums/status_effects/debuffs.dm | 4 +- code/datums/traits/positive_quirk.dm | 12 +- code/game/atoms_movable.dm | 76 ++- code/game/objects/items/debug_items.dm | 4 +- code/game/objects/items/holy_weapons.dm | 3 +- code/modules/admin/topic.dm | 3 +- .../antagonists/abductor/equipment/gland.dm | 4 +- .../antagonists/changeling/changeling.dm | 3 +- .../clock_cult/servant_of_ratvar.dm | 4 +- code/modules/antagonists/cult/cult.dm | 4 +- code/modules/antagonists/pirate/pirate.dm | 4 +- code/modules/antagonists/revenant/revenant.dm | 2 +- .../antagonists/traitor/datum_traitor.dm | 2 +- .../antagonists/wizard/equipment/soulstone.dm | 6 +- code/modules/clothing/head/pirate.dm | 4 +- code/modules/error_handler/error_viewer.dm | 2 +- code/modules/events/sentience.dm | 2 +- .../holoparasite/abilities/lesser/babel.dm | 2 +- .../holoparasite/holoparasite_language.dm | 18 +- code/modules/jobs/job_types/curator.dm | 2 +- code/modules/language/codespeak.dm | 4 +- code/modules/language/language_holder.dm | 488 +++++++++++------- code/modules/language/language_menu.dm | 129 +++-- .../mining/lavaland/necropolis_chests.dm | 2 +- .../mob/living/carbon/human/species.dm | 45 +- .../mob/living/carbon/human/status_procs.dm | 6 +- code/modules/mob/living/carbon/say.dm | 10 +- code/modules/mob/living/say.dm | 14 +- .../mob/living/silicon/pai/software.dm | 2 +- .../mob/living/simple_animal/slime/powers.dm | 2 +- code/modules/mob/mob.dm | 5 +- .../chemistry/reagents/other_reagents.dm | 4 +- .../crossbreeding/transformative.dm | 2 +- code/modules/surgery/organs/tongue.dm | 82 ++- code/modules/tgui/states/language_menu.dm | 4 +- code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/language_transfer.dm | 0 47 files changed, 615 insertions(+), 474 deletions(-) create mode 100644 code/modules/unit_tests/language_transfer.dm diff --git a/code/__DEFINES/language.dm b/code/__DEFINES/language.dm index 77e8060c06532..42002e5ab433c 100644 --- a/code/__DEFINES/language.dm +++ b/code/__DEFINES/language.dm @@ -22,9 +22,22 @@ // LANGUAGE SOURCE DEFINES -#define LANGUAGE_ALL "all" // For use in full removal only. +/// For use in full removal only. +#define LANGUAGE_ALL "all" + +// Generic language sources. +/// Language is linked to the movable directly. #define LANGUAGE_ATOM "atom" +/// Language is linked to the mob's mind. +/// If a mind transfer happens, language follows. #define LANGUAGE_MIND "mind" +/// Language is linked to the mob's species. +/// If a species change happens, language goes away. +/// If applied to a non-human (no species) atom, this is effectively the same as [LANGUAGE_ATOM]. +#define LANGUAGE_SPECIES "species" + +// More specific language sources. +// Only ever goes away when dismissed directly. #define LANGUAGE_FRIEND "friend" #define LANGUAGE_ABSORB "absorb" #define LANGUAGE_APHASIA "aphasia" @@ -46,6 +59,13 @@ #define LANGUAGE_MULTILINGUAL "multilingual" #define LANGUAGE_EMP "emp" #define LANGUAGE_HOLOPARA "holoparasite" +#define LANGUAGE_BABEL "babel" + +// Language flags. Used in granting and removing languages. +/// This language can be spoken. +#define SPOKEN_LANGUAGE (1<<0) +/// This language can be understood. +#define UNDERSTOOD_LANGUAGE (1<<1) // Languages available from Multilingual GLOBAL_LIST_INIT(multilingual_language_list, typecacheof(list( diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 05a5407037edc..ce03494737e75 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -96,9 +96,7 @@ screen_loc = ui_language_menu /atom/movable/screen/language_menu/Click() - var/mob/M = usr - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) + usr.get_language_holder().open_language_menu(usr) /atom/movable/screen/inventory var/slot_id diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index 0e1208e019332..4e2df5017792f 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -20,6 +20,9 @@ var/motd + /// If the configuration is loaded + var/loaded = FALSE + var/static/regex/ic_filter_regex var/static/regex/ooc_filter_regex @@ -60,6 +63,8 @@ LoadProtectedIDs() LoadChatFilter() + loaded = TRUE + if (Master) Master.OnConfigLoad() diff --git a/code/datums/action.dm b/code/datums/action.dm index 53d4359d77f1e..589658362f8d5 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -718,20 +718,6 @@ icon_icon = 'icons/hud/actions/actions_items.dmi' button_icon_state = "jetboot" -/datum/action/language_menu - name = "Language Menu" - desc = "Open the language menu to review your languages, their keys, and select your default language." - button_icon_state = "language_menu" - check_flags = NONE - -/datum/action/language_menu/Trigger() - if(!..()) - return FALSE - if(ismob(owner)) - var/mob/M = owner - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) - /datum/action/item_action/wheelys name = "Toggle Wheely-Heel's Wheels" desc = "Pops out or in your wheely-heel's wheels." diff --git a/code/datums/brain_damage/mrat.dm b/code/datums/brain_damage/mrat.dm index d93a5a35372fe..5fb29ddf11797 100644 --- a/code/datums/brain_damage/mrat.dm +++ b/code/datums/brain_damage/mrat.dm @@ -123,7 +123,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/imaginary_friend/mrat) leave = new leave.Grant(src) - grant_all_languages(spoken=FALSE) // they understand all language, but doesn't have to speak that + grant_all_languages(UNDERSTOOD_LANGUAGE) // they understand all language, but doesn't have to speak that // mentor rats default language is set to metalanguage from imaginary friend init // everything mrat says will be understandable to all people diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm index e91a588b846ff..c670462932fc8 100644 --- a/code/datums/brain_damage/severe.dm +++ b/code/datums/brain_damage/severe.dm @@ -28,13 +28,15 @@ lose_text = "You suddenly remember how languages work." /datum/brain_trauma/severe/aphasia/on_gain() - owner.add_blocked_language(subtypesof(/datum/language/) - /datum/language/aphasia, LANGUAGE_APHASIA) - owner.grant_language(/datum/language/aphasia, TRUE, TRUE, LANGUAGE_APHASIA) + owner.add_blocked_language(subtypesof(/datum/language) - /datum/language/aphasia, LANGUAGE_APHASIA) + owner.grant_language(/datum/language/aphasia, source = LANGUAGE_APHASIA) ..() /datum/brain_trauma/severe/aphasia/on_lose() - owner.remove_blocked_language(subtypesof(/datum/language/), LANGUAGE_APHASIA) - owner.remove_language(/datum/language/aphasia, TRUE, TRUE, LANGUAGE_APHASIA) + if(!QDELING(owner)) + owner.remove_blocked_language(subtypesof(/datum/language), LANGUAGE_APHASIA) + owner.remove_language(/datum/language/aphasia, LANGUAGE_APHASIA) + ..() /datum/brain_trauma/severe/blindness diff --git a/code/datums/diseases/advance/symptoms/voice_change.dm b/code/datums/diseases/advance/symptoms/voice_change.dm index 313b34f13d3b6..f6d5a27821b7b 100644 --- a/code/datums/diseases/advance/symptoms/voice_change.dm +++ b/code/datums/diseases/advance/symptoms/voice_change.dm @@ -70,7 +70,7 @@ Bonus if(scramble_language && !current_language) // Last part prevents rerolling language with small amounts of cure. current_language = pick(subtypesof(/datum/language) - /datum/language/common) H.add_blocked_language(subtypesof(/datum/language) - current_language, LANGUAGE_VOICECHANGE) - H.grant_language(current_language, TRUE, TRUE, LANGUAGE_VOICECHANGE) + H.grant_language(current_language, source = LANGUAGE_VOICECHANGE) /datum/symptom/voice_change/End(datum/disease/advance/A) ..() diff --git a/code/datums/dna.dm b/code/datums/dna.dm index cb5f1b1cd4bd9..4313073edb97a 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -344,11 +344,6 @@ dna.species = new_race dna.species.on_species_gain(src, old_species, pref_load) SEND_SIGNAL(src, COMSIG_CARBON_SPECIESCHANGE, new_race) - if(ishuman(src)) - qdel(language_holder) - var/species_holder = initial(mrace.species_language_holder) - language_holder = new species_holder(src) - update_atom_languages() if(icon_update) update_mutations_overlay()// no lizard with human hulk overlay please. diff --git a/code/datums/mind.dm b/code/datums/mind.dm index baac761495270..d05a0a0b82427 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -1,24 +1,24 @@ -/* Note from Carnie: +/* Note from Carnie: The way datum/mind stuff works has been changed a lot. Minds now represent IC characters rather than following a client around constantly. Guidelines for using minds properly: - - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! + - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! ghost.mind is however used as a reference to the ghost's corpse - - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) - the existing mind of the old mob should be transfered to the new mob like so: + - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) + the existing mind of the old mob should be transferred to the new mob like so: mind.transfer_to(new_mob) - - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. + - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. By setting key or ckey explicitly after transferring the mind with transfer_to you will cause bugs like DCing the player. - - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. + - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. - - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting + - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. new_mob.key = key @@ -30,22 +30,28 @@ */ /datum/mind + /// Key of the mob var/key - var/name //replaces mob/var/original_name - var/ghostname //replaces name for observers name if set - /// The last living mob this mind occupied - if the player is dead, this is their body. + /// The name linked to this mind + var/name + /// replaces name for observers name if set + var/ghostname + /// Current mob this mind datum is attached to var/mob/living/current - var/active = 0 + /// Is this mind active? + var/active = FALSE var/memory var/list/quirks = list() - var/assigned_role + /// Job datum indicating the mind's role. This should always exist after initialization, as a reference to a singleton. + var/datum/job/assigned_role var/special_role var/list/restricted_roles = list() var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. var/linglink + /// Martial art on this mind var/datum/martial_art/martial_art var/static/default_martial_art = new/datum/martial_art var/miming = 0 // Mime's vow of silence @@ -61,7 +67,6 @@ var/no_cloning_at_all = FALSE var/datum/mind/enslaved_to //If this mind's master is another mob (i.e. adamantine golems) - var/datum/language_holder/language_holder var/unconvertable = FALSE var/late_joiner = FALSE @@ -97,7 +102,6 @@ /datum/mind/Destroy() SSticker.minds -= src QDEL_LIST(antag_datums) - QDEL_NULL(language_holder) soulOwner = null set_current(null) return ..() @@ -115,11 +119,6 @@ SIGNAL_HANDLER set_current(null) -/datum/mind/proc/get_language_holder() - if(!language_holder) - language_holder = new (src) - return language_holder - /datum/mind/proc/transfer_to(mob/new_character, var/force_key_move = 0) if(current) // remove ourself from our old body's mind variable current.mind = null @@ -137,9 +136,22 @@ var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list var/mob/living/old_current = current - if(current) - current.transfer_observers_to(new_character, TRUE) //transfer anyone observing the old character to the new one - set_current(new_character) //associate ourself with our new body + if(old_current) + //transfer anyone observing the old character to the new one + old_current.transfer_observers_to(new_character) + + // Offload all mind languages from the old holder to a temp one + var/datum/language_holder/empty/temp_holder = new() + var/datum/language_holder/old_holder = old_current.get_language_holder() + var/datum/language_holder/new_holder = new_character.get_language_holder() + // Off load mind languages to the temp holder momentarily + new_holder.transfer_mind_languages(temp_holder) + // Transfer the old holder's mind languages to the new holder + old_holder.transfer_mind_languages(new_holder) + // And finally transfer the temp holder's mind languages back to the old holder + temp_holder.transfer_mind_languages(old_holder) + + set_current(new_character) //associate ourself with our new body new_character.mind = src //and associate our new body with ourself for(var/datum/quirk/T as() in quirks) //Retarget all traits this mind has @@ -151,13 +163,13 @@ if(iscarbon(new_character)) var/mob/living/carbon/C = new_character C.last_mind = src - transfer_antag_huds(hud_to_transfer) //inherit the antag HUD + transfer_antag_huds(hud_to_transfer) //inherit the antag HUD transfer_actions(new_character) transfer_martial_arts(new_character) RegisterSignal(new_character, COMSIG_MOB_DEATH, PROC_REF(set_death_time)) if(active || force_key_move) new_character.key = key //now transfer the key to link the client to our new body - current.update_atom_languages() + SEND_SIGNAL(src, COMSIG_MIND_TRANSFER_TO, old_current, new_character) // Update SSD indicators if(isliving(old_current)) diff --git a/code/datums/mutations/speech.dm b/code/datums/mutations/speech.dm index 4bd9acdc358d9..7ebdb959fcf4c 100644 --- a/code/datums/mutations/speech.dm +++ b/code/datums/mutations/speech.dm @@ -237,12 +237,12 @@ /datum/mutation/stoner/on_acquiring(mob/living/carbon/owner) ..() - owner.grant_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_STONER) + owner.grant_language(/datum/language/beachbum, source = LANGUAGE_STONER) owner.add_blocked_language(subtypesof(/datum/language) - /datum/language/beachbum, LANGUAGE_STONER) /datum/mutation/stoner/on_losing(mob/living/carbon/owner) ..() - owner.remove_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_STONER) + owner.remove_language(/datum/language/beachbum, source = LANGUAGE_STONER) owner.remove_blocked_language(subtypesof(/datum/language) - /datum/language/beachbum, LANGUAGE_STONER) /datum/mutation/medieval diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index 75f70585a4c3d..fc585e65218a9 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -1048,11 +1048,11 @@ . = ..() to_chat(owner, "Alert: Vocal cords are malfunctioning.") owner.add_blocked_language(subtypesof(/datum/language/) - /datum/language/uncommon, LANGUAGE_EMP) - owner.grant_language(/datum/language/uncommon, FALSE, TRUE, LANGUAGE_EMP) + owner.grant_language(/datum/language/uncommon, SPOKEN_LANGUAGE, source = LANGUAGE_EMP) /datum/status_effect/spanish/on_remove() owner.remove_blocked_language(subtypesof(/datum/language/), LANGUAGE_EMP) - owner.remove_language(/datum/language/uncommon, TRUE, TRUE, LANGUAGE_EMP) + owner.remove_language(/datum/language/uncommon, source = LANGUAGE_EMP) to_chat(owner, "Alert: Vocal cords restored to normal function.") return ..() diff --git a/code/datums/traits/positive_quirk.dm b/code/datums/traits/positive_quirk.dm index d1bc2dd5d16e7..efc0249eba8c2 100644 --- a/code/datums/traits/positive_quirk.dm +++ b/code/datums/traits/positive_quirk.dm @@ -125,11 +125,11 @@ var/datum/language/known_language /datum/quirk/multilingual/proc/set_up_language() - var/datum/language_holder/LH = quirk_holder.get_language_holder() + var/datum/language_holder/LH = quirk_target.get_language_holder() if(quirk_holder.assigned_role == JOB_NAME_CURATOR) return var/obj/item/organ/tongue/T = quirk_target.getorganslot(ORGAN_SLOT_TONGUE) - var/list/languages_possible = T.languages_possible + var/list/languages_possible = T.get_possible_languages() languages_possible = languages_possible - typecacheof(/datum/language/codespeak) - typecacheof(/datum/language/narsie) - typecacheof(/datum/language/ratvar) languages_possible = languages_possible - LH.understood_languages languages_possible = languages_possible - LH.spoken_languages @@ -142,14 +142,14 @@ known_language = read_choice_preference(/datum/preference/choiced/quirk/multilingual_language) if(!known_language) // default to random set_up_language() - var/datum/language_holder/LH = quirk_holder.get_language_holder() - LH.grant_language(known_language, TRUE, TRUE, LANGUAGE_MULTILINGUAL) + var/datum/language_holder/LH = quirk_target.get_language_holder() + LH.grant_language(known_language, source = LANGUAGE_MULTILINGUAL) /datum/quirk/multilingual/remove() if(!known_language) return - var/datum/language_holder/LH = quirk_holder.get_language_holder() - LH.remove_language(known_language, TRUE, TRUE, LANGUAGE_MULTILINGUAL) + var/datum/language_holder/LH = quirk_target.get_language_holder() + LH.remove_language(known_language, source = LANGUAGE_MULTILINGUAL) /datum/quirk/night_vision name = "Night Vision" diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 084427779275b..52c19f6d38b54 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -14,8 +14,10 @@ var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported var/throw_range = 7 var/mob/pulledby = null + /// What language holder type to init as var/initial_language_holder = /datum/language_holder - var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence. + /// Holds all languages this mob can speak and understand + VAR_PRIVATE/datum/language_holder/language_holder var/verb_say = "says" var/verb_ask = "asks" @@ -1025,84 +1027,80 @@ */ /// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one. -/atom/movable/proc/get_language_holder(get_minds = TRUE) +/atom/movable/proc/get_language_holder() + RETURN_TYPE(/datum/language_holder) + if(QDELING(src)) + CRASH("get_language_holder() called on a QDELing atom, \ + this will try to re-instantiate the language holder that's about to be deleted, which is bad.") + if(!language_holder) language_holder = new initial_language_holder(src) return language_holder /// Grants the supplied language and sets omnitongue true. -/atom/movable/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ATOM) - var/datum/language_holder/LH = get_language_holder() - return LH.grant_language(language, understood, spoken, source) +/atom/movable/proc/grant_language(language, language_flags = ALL, source = LANGUAGE_ATOM) + return get_language_holder().grant_language(language, language_flags, source) /// Grants every language. -/atom/movable/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND) - var/datum/language_holder/LH = get_language_holder() - return LH.grant_all_languages(understood, spoken, grant_omnitongue, source) +/atom/movable/proc/grant_all_languages(language_flags = ALL, grant_omnitongue = TRUE, source = LANGUAGE_MIND) + return get_language_holder().grant_all_languages(language_flags, grant_omnitongue, source) /// Removes a single language. -/atom/movable/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL) - var/datum/language_holder/LH = get_language_holder() - return LH.remove_language(language, understood, spoken, source) +/atom/movable/proc/remove_language(language, language_flags = ALL, source = LANGUAGE_ALL) + return get_language_holder().remove_language(language, language_flags, source) /// Removes every language and sets omnitongue false. /atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE) - var/datum/language_holder/LH = get_language_holder() - return LH.remove_all_languages(source, remove_omnitongue) + return get_language_holder().remove_all_languages(source, remove_omnitongue) /// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later. /atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM) - var/datum/language_holder/LH = get_language_holder() - return LH.add_blocked_language(language, source) + return get_language_holder().add_blocked_language(language, source) /// Removes a language from the blocked language list. /atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM) - var/datum/language_holder/LH = get_language_holder() - return LH.remove_blocked_language(language, source) + return get_language_holder().remove_blocked_language(language, source) /// Checks if atom has the language. If spoken is true, only checks if atom can speak the language. -/atom/movable/proc/has_language(language, spoken = FALSE) - var/datum/language_holder/LH = get_language_holder() - return LH.has_language(language, spoken) +/atom/movable/proc/has_language(language, flags_to_check) + return get_language_holder().has_language(language, flags_to_check) /// Checks if atom can speak the language. /atom/movable/proc/can_speak_language(language) - var/datum/language_holder/LH = get_language_holder() - return LH.can_speak_language(language) + return get_language_holder().can_speak_language(language) /// Returns the result of tongue specific limitations on spoken languages. -/atom/movable/proc/could_speak_language(language) +/atom/movable/proc/could_speak_language(datum/language/language_path) return TRUE /// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible. /atom/movable/proc/get_selected_language() - var/datum/language_holder/LH = get_language_holder() - return LH.get_selected_language() + return get_language_holder().get_selected_language() /// Gets a random understood language, useful for hallucinations and such. /atom/movable/proc/get_random_understood_language() - var/datum/language_holder/LH = get_language_holder() - return LH.get_random_understood_language() + return get_language_holder().get_random_understood_language() /// Gets a random spoken language, useful for forced speech and such. /atom/movable/proc/get_random_spoken_language() - var/datum/language_holder/LH = get_language_holder() - return LH.get_random_spoken_language() + return get_language_holder().get_random_spoken_language() /// Copies all languages into the supplied atom/language holder. Source should be overridden when you /// do not want the language overwritten by later atom updates or want to avoid blocked languages. -/atom/movable/proc/copy_languages(from_holder, source_override=FALSE, spoken=TRUE, understood=TRUE, blocked=TRUE) - if(isatom(from_holder)) +/atom/movable/proc/copy_languages(datum/language_holder/from_holder, source_override=FALSE, spoken=TRUE, understood=TRUE, blocked=TRUE) + if(ismovable(from_holder)) var/atom/movable/thing = from_holder from_holder = thing.get_language_holder() - var/datum/language_holder/LH = get_language_holder() - return LH.copy_languages(from_holder, source_override, spoken, understood, blocked) - -/// Empties out the atom specific languages and updates them according to the current atoms language holder. -/// As a side effect, it also creates missing language holders in the process. -/atom/movable/proc/update_atom_languages() - var/datum/language_holder/LH = get_language_holder() - return LH.update_atom_languages(src) + + return get_language_holder().copy_languages(from_holder, source_override, spoken, understood, blocked) + +/// Sets the passed path as the active language +/// Returns the currently selected language if successful, if the language was not valid, returns null +/atom/movable/proc/set_active_language(language_path) + var/datum/language_holder/our_holder = get_language_holder() + our_holder.selected_language = language_path + + return our_holder.get_selected_language() // verifies its validity, returns it if successful. /* End language procs */ diff --git a/code/game/objects/items/debug_items.dm b/code/game/objects/items/debug_items.dm index 2100e9a95c919..78325763604a4 100644 --- a/code/game/objects/items/debug_items.dm +++ b/code/game/objects/items/debug_items.dm @@ -359,8 +359,8 @@ . = ..() for(var/each in traits_to_give) ADD_TRAIT(user, each, "debug") - user.grant_all_languages(TRUE, TRUE, TRUE, "debug") - user.grant_language(/datum/language/metalanguage, TRUE, TRUE, "debug") + grant_all_languages(source = "debug") + user.grant_language(/datum/language/metalanguage, source = "debug") var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] hud.add_hud_to(user) diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm index 718e93d430268..b69b88f6f0d56 100644 --- a/code/game/objects/items/holy_weapons.dm +++ b/code/game/objects/items/holy_weapons.dm @@ -524,8 +524,7 @@ S.fully_replace_character_name(null, "The spirit of [name]") S.status_flags |= GODMODE S.copy_languages(user, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the user. - S.update_atom_languages() - grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue + S.get_language_holder().omnitongue = TRUE //Grants omnitongue var/input = sanitize_name(stripped_input(S,"What are you named?", ,"", MAX_NAME_LEN)) if(src && input) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 944211ece19e1..d3bd3381f5f24 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1319,8 +1319,7 @@ if(!ismob(M)) to_chat(usr, "This can only be used on instances of type /mob.") return - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) + M.get_language_holder().open_language_menu(usr) else if(href_list["traitor"]) if(!check_rights(R_ADMIN)) diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm index 403ea59a3a549..c75541bd0e1b2 100644 --- a/code/modules/antagonists/abductor/equipment/gland.dm +++ b/code/modules/antagonists/abductor/equipment/gland.dm @@ -135,12 +135,12 @@ /obj/item/organ/heart/gland/slime/Insert(mob/living/carbon/M, special = 0, pref_load = FALSE) ..() owner.faction |= "slime" - owner.grant_language(/datum/language/slime, TRUE, TRUE, LANGUAGE_GLAND) + owner.grant_language(/datum/language/slime, source = LANGUAGE_GLAND) /obj/item/organ/heart/gland/slime/Remove(mob/living/carbon/M, special = 0, pref_load = FALSE) ..() owner.faction -= "slime" - owner.remove_language(/datum/language/slime, TRUE, TRUE, LANGUAGE_GLAND) + owner.remove_language(/datum/language/slime, source = LANGUAGE_GLAND) /obj/item/organ/heart/gland/slime/activate() to_chat(owner, "You feel nauseated!") diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index f37fc8cd758d8..5b042e87c0201 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -96,7 +96,8 @@ if(give_objectives) forge_objectives() handle_clown_mutation(owner.current, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.") - owner.current.grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue. We are able to transform our body after all. + owner.current.get_language_holder().omnitongue = TRUE + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ling_aler.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) . = ..() /datum/antagonist/changeling/on_removal() diff --git a/code/modules/antagonists/clock_cult/servant_of_ratvar.dm b/code/modules/antagonists/clock_cult/servant_of_ratvar.dm index 155e456db44e9..ca40507c138f2 100644 --- a/code/modules/antagonists/clock_cult/servant_of_ratvar.dm +++ b/code/modules/antagonists/clock_cult/servant_of_ratvar.dm @@ -74,7 +74,7 @@ owner.current.throw_alert("clockinfo", /atom/movable/screen/alert/clockwork/clocksense) SSticker.mode.update_clockcult_icons_added(owner) var/datum/language_holder/LH = owner.current.get_language_holder() - LH.grant_language(/datum/language/ratvar, TRUE, TRUE, LANGUAGE_CULTIST) + LH.grant_language(/datum/language/ratvar, source = LANGUAGE_CULTIST) /datum/antagonist/servant_of_ratvar/remove_innate_effects(mob/living/M) owner.current.faction -= "ratvar" @@ -86,7 +86,7 @@ owner_mob.remove_overlay(forbearance) qdel(forbearance) var/datum/language_holder/LH = owner.current.get_language_holder() - LH.remove_language(/datum/language/ratvar, TRUE, TRUE, LANGUAGE_CULTIST) + LH.remove_language(/datum/language/ratvar, source = LANGUAGE_CULTIST) . = ..() /datum/antagonist/servant_of_ratvar/proc/equip_servant_conversion() diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index 0f5fe03d59f8a..a778d6cf0bd65 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -114,7 +114,7 @@ if(mob_override) current = mob_override current.faction |= "cult" - current.grant_language(/datum/language/narsie, TRUE, TRUE, LANGUAGE_CULTIST) + current.grant_language(/datum/language/narsie, source = LANGUAGE_CULTIST) if(!cult_team.cult_master) vote.Grant(current) communion.Grant(current) @@ -142,7 +142,7 @@ if(mob_override) current = mob_override current.faction -= "cult" - current.remove_language(/datum/language/narsie, TRUE, TRUE, LANGUAGE_CULTIST) + current.remove_language(/datum/language/narsie, source = LANGUAGE_CULTIST) vote.Remove(current) communion.Remove(current) magic.Remove(current) diff --git a/code/modules/antagonists/pirate/pirate.dm b/code/modules/antagonists/pirate/pirate.dm index 4c43bd564af0d..ad245568a6d72 100644 --- a/code/modules/antagonists/pirate/pirate.dm +++ b/code/modules/antagonists/pirate/pirate.dm @@ -65,12 +65,12 @@ . = ..() var/mob/living/owner_mob = mob_override || owner.current var/datum/language_holder/holder = owner_mob.get_language_holder() - holder.grant_language(/datum/language/piratespeak, TRUE, TRUE, LANGUAGE_PIRATE) + holder.grant_language(/datum/language/piratespeak, source = LANGUAGE_PIRATE) holder.selected_language = /datum/language/piratespeak /datum/antagonist/pirate/remove_innate_effects(mob/living/mob_override) var/mob/living/owner_mob = mob_override || owner.current - owner_mob.remove_language(/datum/language/piratespeak, TRUE, TRUE, LANGUAGE_PIRATE) + owner_mob.remove_language(/datum/language/piratespeak, source = LANGUAGE_PIRATE) return ..() /datum/team/pirate diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm index d420e06b81032..984ef90c9fb0d 100644 --- a/code/modules/antagonists/revenant/revenant.dm +++ b/code/modules/antagonists/revenant/revenant.dm @@ -84,7 +84,7 @@ check_rev_teleport() // they're spawned in non-station for some reason... random_revenant_name() AddComponent(/datum/component/tracking_beacon, "ghost", null, null, TRUE, "#9e4d91", TRUE, TRUE, "#490066") - grant_all_languages(TRUE, FALSE, FALSE, LANGUAGE_REVENANT) // rev can understand every langauge + grant_all_languages(UNDERSTOOD_LANGUAGE, grant_omnitongue = FALSE, source = LANGUAGE_REVENANT) // rev can understand every langauge ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) AddElement(/datum/element/movetype_handler) ADD_TRAIT(src, TRAIT_MOVE_FLOATING, "ghost") diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index c90dbffffa045..8ffaca9fcd298 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -96,7 +96,7 @@ if(TRAITOR_AI) add_law_zero() owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', vol = 100, vary = FALSE, channel = CHANNEL_ANTAG_GREETING, pressure_affected = FALSE, use_reverb = FALSE) - owner.current.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MALF) + owner.current.grant_language(/datum/language/codespeak, source = LANGUAGE_MALF) if(TRAITOR_HUMAN) ui_interact(owner.current) owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', vol = 100, vary = FALSE, channel = CHANNEL_ANTAG_GREETING, pressure_affected = FALSE, use_reverb = FALSE) diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index 411269dfffb60..4b72737714311 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -353,9 +353,9 @@ S.real_name = "Shade of [T.real_name]" S.key = shade_controller.key S.copy_languages(T, LANGUAGE_MIND)//Copies the old mobs languages into the new mob holder. - S.copy_languages(user, LANGUAGE_MASTER) - S.update_atom_languages() - grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue + if(user) + S.copy_languages(user, LANGUAGE_MASTER) + S.get_language_holder().omnitongue = TRUE //Grants omnitongue if(user) S.faction |= "[REF(user)]" //Add the master as a faction, allowing inter-mob cooperation if(user && iscultist(user)) diff --git a/code/modules/clothing/head/pirate.dm b/code/modules/clothing/head/pirate.dm index 5ddb96ae5ff77..bc377306b94bb 100644 --- a/code/modules/clothing/head/pirate.dm +++ b/code/modules/clothing/head/pirate.dm @@ -13,7 +13,7 @@ if(!ishuman(user)) return if(slot == ITEM_SLOT_HEAD) - user.grant_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) + user.grant_language(/datum/language/piratespeak/, source = LANGUAGE_HAT) to_chat(user, "You suddenly know how to speak like a pirate!") /obj/item/clothing/head/costume/pirate/dropped(mob/user) @@ -22,7 +22,7 @@ return var/mob/living/carbon/human/H = user if(H.get_item_by_slot(ITEM_SLOT_HEAD) == src && !QDELETED(src)) //This can be called as a part of destroy - user.remove_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) + user.remove_language(/datum/language/piratespeak/, source = LANGUAGE_HAT) to_chat(user, "You can no longer speak like a pirate.") /obj/item/clothing/head/costume/pirate/captain diff --git a/code/modules/error_handler/error_viewer.dm b/code/modules/error_handler/error_viewer.dm index 4197b5e5da6e2..a72e9d2e5c4e3 100644 --- a/code/modules/error_handler/error_viewer.dm +++ b/code/modules/error_handler/error_viewer.dm @@ -128,7 +128,7 @@ GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache) var/const/viewtext = "\[view]" // Nesting these in other brackets went poorly //log_debug("Runtime in [e.file], line [e.line]: [html_encode(e.name)] [error_entry.make_link(viewtext)]") var/err_msg_delay - if(config) + if(config?.loaded) err_msg_delay = CONFIG_GET(number/error_msg_delay) else var/datum/config_entry/CE = /datum/config_entry/number/error_msg_delay diff --git a/code/modules/events/sentience.dm b/code/modules/events/sentience.dm index 7139a65377a6d..940ddbef01ebe 100644 --- a/code/modules/events/sentience.dm +++ b/code/modules/events/sentience.dm @@ -82,7 +82,7 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( SA.key = SG.key - SA.grant_all_languages(TRUE, FALSE, FALSE) + SA.grant_all_languages(UNDERSTOOD_LANGUAGE, grant_omnitongue = FALSE, source = LANGUAGE_ATOM) SA.sentience_act() diff --git a/code/modules/holoparasite/abilities/lesser/babel.dm b/code/modules/holoparasite/abilities/lesser/babel.dm index 036e2c6f1c3fc..c785f72a1902d 100644 --- a/code/modules/holoparasite/abilities/lesser/babel.dm +++ b/code/modules/holoparasite/abilities/lesser/babel.dm @@ -6,7 +6,7 @@ /datum/holoparasite_ability/lesser/babelfish/apply() ..() - owner.grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_HOLOPARA) + owner.grant_all_languages(source = LANGUAGE_HOLOPARA) /datum/holoparasite_ability/lesser/babelfish/remove() ..() diff --git a/code/modules/holoparasite/holoparasite_language.dm b/code/modules/holoparasite/holoparasite_language.dm index b2fe09a276ad8..5c33ca28e2adc 100644 --- a/code/modules/holoparasite/holoparasite_language.dm +++ b/code/modules/holoparasite/holoparasite_language.dm @@ -7,36 +7,30 @@ /// The parent holder of the holoparasite, which is the holder of the summoner. var/datum/holoparasite_holder/holder -/datum/language_holder/holoparasite/New(atom/owner, datum/holoparasite_holder/_holder) - if(!istype(_holder)) - CRASH("Attempted to create holoparasite language holder for [key_name(owner)] without a valid holoparasite holder!") - holder = _holder +/datum/language_holder/holoparasite/New(atom/owner, datum/holoparasite_holder/new_holder) + holder = new_holder return ..() /** * Ensures that the holoparasite can understand any language that its summoner can understand. */ -/datum/language_holder/holoparasite/has_language(language, spoken) - var/datum/language_holder/summoner_mind_language_holder = holder.owner.get_language_holder() +/datum/language_holder/holoparasite/has_language(language, flags_to_check) var/datum/language_holder/summoner_body_language_holder = holder.owner.current?.get_language_holder() - return ..() || summoner_mind_language_holder.has_language(language, spoken) || summoner_body_language_holder?.has_language(language, spoken) + return ..() || summoner_body_language_holder?.has_language(language, flags_to_check) /** * Ensures that the holoparasite can speak any language that its summoner can understand. */ /datum/language_holder/holoparasite/can_speak_language(language) - var/datum/language_holder/summoner_mind_language_holder = holder.owner.get_language_holder() var/datum/language_holder/summoner_body_language_holder = holder.owner.current?.get_language_holder() - return ..() || summoner_mind_language_holder.can_speak_language(language) || summoner_body_language_holder?.can_speak_language(language) + return ..() || summoner_body_language_holder?.can_speak_language(language) /** * Picks a random understood language, combining the holoparasite's understood languages with that of the summoner's mind and body. */ /datum/language_holder/holoparasite/get_random_understood_language() - var/datum/language_holder/summoner_mind_language_holder = holder.owner.get_language_holder() var/datum/language_holder/summoner_body_language_holder = holder.owner.current?.get_language_holder() var/list/choices = understood_languages.Copy() - choices |= summoner_mind_language_holder.understood_languages if(summoner_body_language_holder) choices |= summoner_body_language_holder.understood_languages return pick(choices) @@ -45,10 +39,8 @@ * Picks a random spoken language, combining the holoparasite's spoken languages with that of the summoner's mind and body. */ /datum/language_holder/holoparasite/get_random_spoken_language() - var/datum/language_holder/summoner_mind_language_holder = holder.owner.get_language_holder() var/datum/language_holder/summoner_body_language_holder = holder.owner.current?.get_language_holder() var/list/choices = spoken_languages.Copy() - choices |= summoner_mind_language_holder.spoken_languages if(summoner_body_language_holder) choices |= summoner_body_language_holder.spoken_languages return pick(choices) diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm index a50627fbe99a8..2fa8559a6db82 100644 --- a/code/modules/jobs/job_types/curator.dm +++ b/code/modules/jobs/job_types/curator.dm @@ -57,4 +57,4 @@ if(visualsOnly) return - H.grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_CURATOR) + H.grant_all_languages(source = LANGUAGE_CURATOR) diff --git a/code/modules/language/codespeak.dm b/code/modules/language/codespeak.dm index 0f1bd035a3a25..0b84b8f84027f 100644 --- a/code/modules/language/codespeak.dm +++ b/code/modules/language/codespeak.dm @@ -47,7 +47,7 @@ return to_chat(user, "You start skimming through [src], and suddenly your mind is filled with codewords and responses.") - user.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MIND) + user.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND) use_charge(user) @@ -66,7 +66,7 @@ M.visible_message("[user] beats [M] over the head with [src]!", "[user] beats you over the head with [src]!", "You hear smacking.") else M.visible_message("[user] teaches [M] by beating [M.p_them()] over the head with [src]!", "As [user] hits you with [src], codewords and responses flow through your mind.", "You hear smacking.") - M.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MIND) + M.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND) use_charge(user) /obj/item/codespeak_manual/proc/use_charge(mob/user) diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm index e5c6e65c03d49..d3c6a80640bb4 100644 --- a/code/modules/language/language_holder.dm +++ b/code/modules/language/language_holder.dm @@ -1,4 +1,4 @@ -/*!Language holders will either exist in an atom/movable or a mind. Creation of language holders happens +/*!Language holders will either exist in an atom/movable. Creation of language holders happens automatically when they are needed, for example when something tries to speak. Where a mind is available, the mind language holder will be the one "in charge". The mind holder will update its languages based on the atom holder, and will get updated as part of @@ -33,35 +33,36 @@ Key procs * [has_language](atom/movable.html#proc/has_language) * [can_speak_language](atom/movable.html#proc/can_speak_language) * [get_selected_language](atom/movable.html#proc/get_selected_language) -* [update_atom_languages](atom/movable.html#proc/update_atom_languages) */ /datum/language_holder - /// Understood languages. - var/list/understood_languages = list(/datum/language/common = list(LANGUAGE_MIND)) - /// A list of languages that can be spoken. Tongue organ may also set limits beyond this list. + /// Lazyassoclist of all understood languages + var/list/understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) + /// Lazyassoclist of languages that can be spoken. + /// Tongue organ may also set limits beyond this list. var/list/spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) - /// A list of blocked languages. Used to prevent understanding and speaking certain languages, ie for certain mobs, mutations etc. - var/list/blocked_languages = list() - /// If true, overrides tongue limitations. + /// Lazyassoclist of blocked languages. + /// Used to prevent understanding and speaking certain languages, ie for certain mobs, mutations etc. + var/list/blocked_languages + /// If true, overrides tongue aforementioned limitations. var/omnitongue = FALSE /// Handles displaying the language menu UI. var/datum/language_menu/language_menu /// Currently spoken language var/selected_language /// Tracks the entity that owns the holder. - var/atom/owner + var/atom/movable/owner /// Initializes, and copies in the languages from the current atom if available. -/datum/language_holder/New(atom/_owner) - if(_owner && QDELING(_owner)) - CRASH("Langauge holder added to a qdeleting thing, what the fuck [REF(_owner)]") - owner = _owner - if(istype(owner, /datum/mind)) - var/datum/mind/M = owner - if(M.current) - update_atom_languages(M.current) - grant_language(/datum/language/metalanguage, understood=TRUE, spoken=FALSE, source=LANGUAGE_MIND) // Gets metalanguage that you can only understand +/datum/language_holder/New(atom/new_owner) + if(new_owner) + if(QDELETED(new_owner)) + CRASH("Langauge holder added to a qdeleting thing, what the fuck [text_ref(new_owner)]") + if(!ismovable(new_owner)) + CRASH("Language holder being added to a non-movable thing, this is invalid (was: [new_owner] / [new_owner.type])") + + owner = new_owner + grant_language(/datum/language/metalanguage, language_flags = ALL, source = LANGUAGE_MIND) // Gets metalanguage that you can only understand // If we have an owner, we'll set a default selected language if(owner) get_selected_language() @@ -72,54 +73,50 @@ Key procs return ..() /// Grants the supplied language. -/datum/language_holder/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_MIND) - if(understood) - if(!understood_languages[language]) - understood_languages[language] = list() - understood_languages[language] |= source +/datum/language_holder/proc/grant_language(language, language_flags = ALL, source = LANGUAGE_MIND) + if(language_flags & UNDERSTOOD_LANGUAGE) + LAZYORASSOCLIST(understood_languages, language, source) . = TRUE - if(spoken) - if(!spoken_languages[language]) - spoken_languages[language] = list() - spoken_languages[language] |= source + if(language_flags & SPOKEN_LANGUAGE) + LAZYORASSOCLIST(spoken_languages, language, source) . = TRUE + return . + /// Grants every language to understood and spoken, and gives omnitongue. -/datum/language_holder/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND) +/datum/language_holder/proc/grant_all_languages(language_flags = ALL, grant_omnitongue = TRUE, source = LANGUAGE_MIND) for(var/language in GLOB.all_languages) if(language == /datum/language/metalanguage) continue // metalanguage shouldn't be given by this method - grant_language(language, understood, spoken, source) - if(grant_omnitongue) // Overrides tongue limitations. + grant_language(language, language_flags, source) + if(grant_omnitongue) // Overrides tongue limitations. omnitongue = TRUE return TRUE /// Removes a single language or source, removing all sources returns the pre-removal state of the language. -/datum/language_holder/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL) - if(understood && understood_languages[language]) +/datum/language_holder/proc/remove_language(language, language_flags = ALL, source = LANGUAGE_ALL) + if(language_flags & UNDERSTOOD_LANGUAGE) if(source == LANGUAGE_ALL) - understood_languages -= language + LAZYREMOVE(understood_languages, language) else - understood_languages[language] -= source - if(!length(understood_languages[language])) - understood_languages -= language + LAZYREMOVEASSOC(understood_languages, language, source) . = TRUE - if(spoken && spoken_languages[language]) + if(language_flags & SPOKEN_LANGUAGE) if(source == LANGUAGE_ALL) - spoken_languages -= language + LAZYREMOVE(spoken_languages, language) else - spoken_languages[language] -= source - if(!length(spoken_languages[language])) - spoken_languages -= language + LAZYREMOVEASSOC(spoken_languages, language, source) . = TRUE + return . + /// Removes every language and optionally sets omnitongue false. If a non default source is supplied, only removes that source. /datum/language_holder/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE) for(var/language in GLOB.all_languages) if(language == /datum/language/metalanguage) // this language is important. Don't remove it by remove_all continue - remove_language(language, TRUE, TRUE, source) + remove_language(language, ALL, source) if(remove_omnitongue) omnitongue = FALSE return TRUE @@ -128,41 +125,41 @@ Key procs /datum/language_holder/proc/add_blocked_language(languages, source = LANGUAGE_MIND) if(!islist(languages)) languages = list(languages) + for(var/language in languages) - if(!blocked_languages[language]) - blocked_languages[language] = list() - blocked_languages[language] |= source + LAZYORASSOCLIST(blocked_languages, language, source) return TRUE /// Removes a single language or list of languages from the blocked language list. /datum/language_holder/proc/remove_blocked_language(languages, source = LANGUAGE_MIND) if(!islist(languages)) languages = list(languages) + for(var/language in languages) - if(blocked_languages[language]) - if(source == LANGUAGE_ALL) - blocked_languages -= language - else - blocked_languages[language] -= source - if(!length(blocked_languages[language])) - blocked_languages -= language + if(source == LANGUAGE_ALL) + LAZYREMOVE(blocked_languages, language) + else + LAZYREMOVEASSOC(blocked_languages, language, source) + return TRUE -/// Checks if you have the language. If spoken is true, only checks if you can speak the language. -/datum/language_holder/proc/has_language(language, spoken = FALSE) +/// Checks if you have the language passed. +/datum/language_holder/proc/has_language(language, flag_to_check = UNDERSTOOD_LANGUAGE) if(language in blocked_languages) return FALSE - if(spoken) - return language in spoken_languages - return language in understood_languages + + var/list/langs_to_check = list() + if(flag_to_check & SPOKEN_LANGUAGE) + langs_to_check |= spoken_languages + if(flag_to_check & UNDERSTOOD_LANGUAGE) + langs_to_check |= understood_languages + + return language in langs_to_check /// Checks if you can speak the language. Tongue limitations should be supplied as an argument. /datum/language_holder/proc/can_speak_language(language) - var/atom/movable/our_atom = get_atom() - var/tongue = our_atom.could_speak_language(language) - if((omnitongue || tongue) && has_language(language, TRUE)) - return TRUE - return FALSE + var/can_speak_language_path = omnitongue || owner.could_speak_language(language) + return (can_speak_language_path && has_language(language, SPOKEN_LANGUAGE)) /// Returns selected language if it can be spoken, or decides, sets and returns a new selected language if possible. /datum/language_holder/proc/get_selected_language() @@ -195,40 +192,15 @@ Key procs language_menu = new (src) language_menu.ui_interact(user) -/// Gets the atom, since we some times need to check if the tongue has limitations. -/datum/language_holder/proc/get_atom() - if(owner) - if(istype(owner, /datum/mind)) - var/datum/mind/M = owner - return M.current - return owner - return FALSE - -/// Empties out the atom specific languages and updates them according to the supplied atoms language holder. -/datum/language_holder/proc/update_atom_languages(atom/movable/thing) - var/datum/language_holder/from_atom = thing.get_language_holder(FALSE) //Gets the atoms language holder - if(from_atom == src) //This could happen if called on an atom without a mind. - return FALSE - for(var/language in understood_languages) - remove_language(language, TRUE, FALSE, LANGUAGE_ATOM) - for(var/language in spoken_languages) - remove_language(language, FALSE, TRUE, LANGUAGE_ATOM) - for(var/language in blocked_languages) - remove_blocked_language(language, LANGUAGE_ATOM) - - copy_languages(from_atom, blocked=TRUE) // full-copy - get_selected_language() - return TRUE - /// Copies all languages from the supplied atom/language holder. Source should be overridden when you /// do not want the language overwritten by later atom updates or want to avoid blocked languages. /datum/language_holder/proc/copy_languages(var/datum/language_holder/from_holder, source_override=FALSE, spoken=TRUE, understood=TRUE, blocked=FALSE) if(understood) for(var/language in from_holder.understood_languages) - grant_language(language, TRUE, FALSE, source_override || from_holder.understood_languages[language]) // if you don't have 'source_override' argument, source from 'from_holder' will be used. + grant_language(language, UNDERSTOOD_LANGUAGE, source_override || from_holder.understood_languages[language]) // if you don't have 'source_override' argument, source from 'from_holder' will be used. if(spoken) for(var/language in from_holder.spoken_languages) - grant_language(language, FALSE, TRUE, source_override || from_holder.spoken_languages[language]) + grant_language(language, SPOKEN_LANGUAGE, source_override || from_holder.spoken_languages[language]) if(blocked) // blocked is set to FALSE by default because there's no reason to copy blocked languages in standard situations. // 'blocked=TRUE' is recommanded when 'source_override=FALSE' because it means full-copy @@ -236,55 +208,117 @@ Key procs add_blocked_language(language, source_override || from_holder.blocked_languages[language]) return TRUE +/// Transfers all mind languages to the supplied language holder. +/datum/language_holder/proc/transfer_mind_languages(datum/language_holder/to_holder) + for(var/language in understood_languages) + if(LANGUAGE_MIND in understood_languages[language]) + remove_language(language, UNDERSTOOD_LANGUAGE, LANGUAGE_MIND) + to_holder.grant_language(language, UNDERSTOOD_LANGUAGE, LANGUAGE_MIND) + for(var/language in spoken_languages) + if(LANGUAGE_MIND in spoken_languages[language]) + remove_language(language, SPOKEN_LANGUAGE, LANGUAGE_MIND) + to_holder.grant_language(language, SPOKEN_LANGUAGE, LANGUAGE_MIND) + for(var/language in blocked_languages) + if(LANGUAGE_MIND in blocked_languages[language]) + remove_blocked_language(language, LANGUAGE_MIND) + to_holder.add_blocked_language(language, LANGUAGE_MIND) + + if(owner) + get_selected_language() + if(to_holder.owner) + to_holder.get_selected_language() + +/// A global assoc list containing prototypes of all language holders +/// [Language holder typepath] to [language holder instance] +/// Used for easy reference of what can speak what without needing to constantly recreate language holders. +GLOBAL_LIST_INIT(prototype_language_holders, init_language_holder_prototypes()) -//************************************************ -//* Specific language holders * -//* Use atom language sources only. * -//************************************************/ +/// Inits the global list of language holder prototypes. +/proc/init_language_holder_prototypes() + var/list/prototypes = list() + for(var/holdertype in typesof(/datum/language_holder)) + prototypes[holdertype] = new holdertype() + return prototypes + +/* + * Specific language holders presets + * + * Prefer to use [LANGUGAE_ATOM]. Atom languages will stick through species changes but not mindswaps. + */ /datum/language_holder/alien - understood_languages = list(/datum/language/xenocommon = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/xenocommon = list(LANGUAGE_ATOM)) - blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/xenocommon = list(LANGUAGE_ATOM) + ) + spoken_languages = list( + /datum/language/xenocommon = list(LANGUAGE_ATOM) + ) + blocked_languages = list( + /datum/language/common = list(LANGUAGE_ATOM) + ) /datum/language_holder/apid - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/apidite = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/apidite = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/apidite = list(LANGUAGE_ATOM) + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/apidite = list(LANGUAGE_ATOM) + ) /datum/language_holder/clockmob - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/ratvar = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/ratvar = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/ratvar = list(LANGUAGE_ATOM) + ) + spoken_languages = list( + /datum/language/ratvar = list(LANGUAGE_ATOM) + ) /datum/language_holder/construct - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/narsie = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/narsie = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/narsie = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/narsie = list(LANGUAGE_ATOM), + ) /datum/language_holder/drone understood_languages = list(/datum/language/drone = list(LANGUAGE_ATOM), - /datum/language/machine = list(LANGUAGE_ATOM)) + /datum/language/machine = list(LANGUAGE_ATOM)) spoken_languages = list(/datum/language/drone = list(LANGUAGE_ATOM)) blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) /datum/language_holder/drone/syndicate - blocked_languages = list() + blocked_languages = null + +/datum/language_holder/human_basic + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM) + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM) + ) /datum/language_holder/jelly - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/slime = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/slime = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/slime = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/slime = list(LANGUAGE_ATOM), + ) /datum/language_holder/oozeling understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/slime = list(LANGUAGE_ATOM)) + /datum/language/slime = list(LANGUAGE_ATOM)) spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/slime = list(LANGUAGE_ATOM)) + /datum/language/slime = list(LANGUAGE_ATOM)) /datum/language_holder/lightbringer understood_languages = list(/datum/language/slime = list(LANGUAGE_ATOM)) @@ -292,10 +326,14 @@ Key procs blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) /datum/language_holder/lizard - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/draconic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/draconic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/draconic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/draconic = list(LANGUAGE_ATOM), + ) /datum/language_holder/lizard/ash understood_languages = list(/datum/language/draconic = list(LANGUAGE_ATOM)) @@ -303,19 +341,29 @@ Key procs blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) /datum/language_holder/monkey - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/monkey = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/monkey = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/monkey = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/monkey = list(LANGUAGE_ATOM), + ) /datum/language_holder/mushroom - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/mushroom = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/mushroom = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/mushroom = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/mushroom = list(LANGUAGE_ATOM), + ) /datum/language_holder/slime - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/slime = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/slime = list(LANGUAGE_ATOM), + ) spoken_languages = list(/datum/language/slime = list(LANGUAGE_ATOM)) /datum/language_holder/swarmer @@ -330,7 +378,7 @@ Key procs /datum/language_holder/spider understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/buzzwords = list(LANGUAGE_ATOM)) + /datum/language/buzzwords = list(LANGUAGE_ATOM)) spoken_languages = list(/datum/language/buzzwords = list(LANGUAGE_ATOM)) /datum/language_holder/venus @@ -339,95 +387,143 @@ Key procs blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) /datum/language_holder/synthetic - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/uncommon = list(LANGUAGE_ATOM), - /datum/language/machine = list(LANGUAGE_ATOM), - /datum/language/draconic = list(LANGUAGE_ATOM), - /datum/language/moffic = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM), - /datum/language/voltaic = list(LANGUAGE_ATOM), - /datum/language/apidite = list(LANGUAGE_ATOM), - /datum/language/sonus = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/uncommon = list(LANGUAGE_ATOM), - /datum/language/machine = list(LANGUAGE_ATOM), - /datum/language/draconic = list(LANGUAGE_ATOM), - /datum/language/moffic = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM), - /datum/language/voltaic = list(LANGUAGE_ATOM), - /datum/language/apidite = list(LANGUAGE_ATOM), - /datum/language/sonus = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/uncommon = list(LANGUAGE_ATOM), + /datum/language/machine = list(LANGUAGE_ATOM), + /datum/language/draconic = list(LANGUAGE_ATOM), + /datum/language/moffic = list(LANGUAGE_ATOM), + /datum/language/calcic = list(LANGUAGE_ATOM), + /datum/language/voltaic = list(LANGUAGE_ATOM), + /datum/language/apidite = list(LANGUAGE_ATOM), + /datum/language/sonus = list(LANGUAGE_ATOM) + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/uncommon = list(LANGUAGE_ATOM), + /datum/language/machine = list(LANGUAGE_ATOM), + /datum/language/draconic = list(LANGUAGE_ATOM), + /datum/language/moffic = list(LANGUAGE_ATOM), + /datum/language/calcic = list(LANGUAGE_ATOM), + /datum/language/voltaic = list(LANGUAGE_ATOM), + /datum/language/apidite = list(LANGUAGE_ATOM), + /datum/language/sonus = list(LANGUAGE_ATOM) + ) /datum/language_holder/moth - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/moffic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/moffic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/moffic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/moffic = list(LANGUAGE_ATOM), + ) /datum/language_holder/skeleton - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/calcic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/calcic = list(LANGUAGE_ATOM), + ) /datum/language_holder/ethereal - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/voltaic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/voltaic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/voltaic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/voltaic = list(LANGUAGE_ATOM), + ) /datum/language_holder/golem - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/terrum = list(LANGUAGE_ATOM) + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/terrum = list(LANGUAGE_ATOM) + ) /datum/language_holder/golem/bone - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/terrum = list(LANGUAGE_ATOM), + /datum/language/calcic = list(LANGUAGE_ATOM) + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/terrum = list(LANGUAGE_ATOM), + /datum/language/calcic = list(LANGUAGE_ATOM) + ) /datum/language_holder/golem/runic - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM), - /datum/language/narsie = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM), - /datum/language/narsie = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/terrum = list(LANGUAGE_ATOM), + /datum/language/narsie = list(LANGUAGE_ATOM) + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/terrum = list(LANGUAGE_ATOM), + /datum/language/narsie = list(LANGUAGE_ATOM) + ) /datum/language_holder/fly - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/buzzwords = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/buzzwords = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/buzzwords = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/buzzwords = list(LANGUAGE_ATOM), + ) /datum/language_holder/diona - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/sylvan = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/sylvan = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/sylvan = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/sylvan = list(LANGUAGE_ATOM), + ) /datum/language_holder/shadowpeople - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/shadowtongue = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/shadowtongue = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/shadowtongue = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/shadowtongue = list(LANGUAGE_ATOM), + ) /datum/language_holder/psyphoza - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/sonus = list(LANGUAGE_ATOM), - /datum/language/sylvan = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/sonus = list(LANGUAGE_ATOM), - /datum/language/sylvan = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/sonus = list(LANGUAGE_ATOM), + /datum/language/sylvan = list(LANGUAGE_ATOM) + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/sonus = list(LANGUAGE_ATOM), + /datum/language/sylvan = list(LANGUAGE_ATOM) + ) /datum/language_holder/empty - understood_languages = list() - spoken_languages = list() + understood_languages = null + spoken_languages = null + +/datum/language_holder/universal + understood_languages = null + spoken_languages = null /datum/language_holder/universal/New() - ..() + . = ..() grant_all_languages() diff --git a/code/modules/language/language_menu.dm b/code/modules/language/language_menu.dm index 47af82f578924..fbc9986768295 100644 --- a/code/modules/language/language_menu.dm +++ b/code/modules/language/language_menu.dm @@ -8,7 +8,6 @@ language_holder = null . = ..() - /datum/language_menu/ui_state(mob/user) return GLOB.language_menu_state @@ -43,46 +42,40 @@ var/list/data = list() var/is_admin = check_rights_for(user.client, R_ADMIN) || check_rights_for(user.client, R_DEBUG) - var/atom/movable/AM = language_holder.get_atom() - if(isliving(AM)) - data["is_living"] = TRUE - else - data["is_living"] = FALSE + var/atom/movable/speaker = language_holder.owner + data["is_living"] = isliving(speaker) data["known_languages"] = list() - for(var/lang in GLOB.all_languages) - var/result = language_holder.has_language(lang) || language_holder.has_language(lang, TRUE) + for(var/datum/language/language as anything in GLOB.all_languages) + var/result = language_holder.has_language(language) || language_holder.has_language(language, SPOKEN_LANGUAGE) if(!result) continue - var/datum/language/language = lang - var/list/L = list() + var/list/lang_data = list() - L["name"] = initial(language.name) - L["is_default"] = (language == language_holder.selected_language) - if(AM) - L["can_speak"] = AM.can_speak_language(language) - L["can_understand"] = AM.has_language(language) + lang_data["name"] = initial(language.name) + lang_data["is_default"] = (language == language_holder.selected_language) + if(speaker) + lang_data["can_speak"] = speaker.can_speak_language(language) + lang_data["can_understand"] = speaker.has_language(language) - if(lang == /datum/language/metalanguage) // metalanguage is only visible to admins + if(language == /datum/language/metalanguage) // metalanguage is only visible to admins if(!(is_admin || HAS_TRAIT(user, TRAIT_METALANGUAGE_KEY_ALLOWED))) continue - data["known_languages"] += list(L) + UNTYPED_LIST_ADD(data["known_languages"], lang_data) - if(is_admin || isobserver(AM)) + if(is_admin || isobserver(speaker)) data["admin_mode"] = TRUE data["omnitongue"] = language_holder.omnitongue - data["unknown_languages"] = list() - for(var/lang in GLOB.all_languages) - if(language_holder.has_language(lang) || language_holder.has_language(lang, TRUE)) + for(var/datum/language/language as anything in GLOB.all_languages) + if(language_holder.has_language(language) || language_holder.has_language(language, TRUE)) continue - var/datum/language/language = lang - var/list/L = list() + var/list/lang_data = list() - L["name"] = initial(language.name) + lang_data["name"] = initial(language.name) - data["unknown_languages"] += list(L) + UNTYPED_LIST_ADD(data["unknown_languages"], lang_data) else data["admin_mode"] = null data["omnitongue"] = null @@ -90,18 +83,17 @@ return data /datum/language_menu/ui_act(action, params) - if(..()) + . = ..() + if(.) return var/mob/user = usr - var/atom/movable/AM = language_holder.get_atom() - + var/atom/movable/speaker = language_holder.owner + var/is_admin = check_rights_for(user.client, R_ADMIN) || check_rights_for(user.client, R_DEBUG) var/language_name = params["language_name"] var/datum/language/language_datum - for(var/lang in GLOB.all_languages) - var/datum/language/language = lang + for(var/datum/language/language as anything in GLOB.all_languages) if(language_name == initial(language.name)) language_datum = language - var/is_admin = check_rights_for(user.client, R_ADMIN) || check_rights_for(user.client, R_DEBUG) switch(action) if("select_default") @@ -111,67 +103,68 @@ language_datum != /datum/language/metalanguage && \ !HAS_TRAIT(user, TRAIT_METALANGUAGE_KEY_ALLOWED) && \ !is_admin) - var/no = alert(user, "You're giving up your power to speak in a powerful language that everyone understands. Do you really wish to do that?", "WARNING!", "Yes", "No") - if(no != "Yes") + var/metachoice = tgui_alert(user, "You're giving up your power to speak in a powerful language that everyone understands. Do you really wish to do that?", "WARNING!", list("Yes", "No")) + if(metachoice != "Yes") return - if(AM.can_speak_language(language_datum)) + if(speaker.can_speak_language(language_datum)) language_holder.selected_language = language_datum . = TRUE if("grant_language") - if((is_admin || isobserver(AM)) && language_datum) + if((is_admin || isobserver(speaker)) && language_datum) var/list/choices = list("Only Spoken", "Only Understood", "Both") - var/choice = input(user,"How do you want to add this language?","[language_datum]",null) as null|anything in choices - var/spoken = FALSE - var/understood = FALSE + var/choice = tgui_input_list(user, "How do you want to add this language?", "[language_datum]", choices) + if(isnull(choice)) + return + var/adding_flags = NONE switch(choice) if("Only Spoken") - spoken = TRUE + adding_flags |= SPOKEN_LANGUAGE if("Only Understood") - understood = TRUE + adding_flags |= UNDERSTOOD_LANGUAGE if("Both") - spoken = TRUE - understood = TRUE - if(language_holder.blocked_languages.Find(language_datum)) - var/blocked_language_choice = alert(user,"The [language_name] language is in this mob's list of blocked languages. Do you wish to remove it so you may give the mob the [language_name] language?","[language_datum]", "Yes", "No") - if(blocked_language_choice == "Yes") - language_holder.remove_blocked_language(language_datum) - message_admins("[key_name_admin(user)] removed the [language_name] language from [key_name_admin(AM)]'s blocked languages list.") - log_admin("[key_name(user)] removed the language [language_name] from [key_name(AM)]'s blocked languages list.") - language_holder.grant_language(language_datum, understood, spoken) - if(spoken && language_datum == /datum/language/metalanguage) - var/yes = alert(user, "You have added speakable Metalanguage. Do you wish to give them a trait that they can use language key(,`) to say that? Otherwise, they'll have no way to say that, or, instead, you should set their default language to metalanguage.", "Give Metalangauge trait?", "Yes", "No") + adding_flags |= ALL + if(LAZYACCESS(language_holder.blocked_languages, language_datum)) + choice = tgui_alert(user, "Do you want to lift the blockage that's also preventing the language to be spoken or understood?", "[language_datum]", list("Yes", "No")) + if(choice == "Yes") + language_holder.remove_blocked_language(language_datum, LANGUAGE_ALL) + message_admins("[key_name_admin(user)] removed the [language_name] language from [key_name_admin(speaker)]'s blocked languages list.") + log_admin("[key_name(user)] removed the language [language_name] from [key_name(speaker)]'s blocked languages list.") + language_holder.grant_language(language_datum, adding_flags) + if((adding_flags & SPOKEN_LANGUAGE) && (language_datum == /datum/language/metalanguage)) + var/yes = tgui_alert(user, "You have added speakable Metalanguage. Do you wish to give them a trait that they can use language key(,`) to say that? Otherwise, they'll have no way to say that, or, instead, you should set their default language to metalanguage.", "Give Metalangauge trait?", list("Yes", "No")) if(yes == "Yes") ADD_TRAIT(user, TRAIT_METALANGUAGE_KEY_ALLOWED, "lang_added_by_admin") if(is_admin) - message_admins("[key_name_admin(user)] granted the [language_name] language to [key_name_admin(AM)].") - log_admin("[key_name(user)] granted the language [language_name] to [key_name(AM)].") + message_admins("[key_name_admin(user)] granted the [language_name] language to [key_name_admin(speaker)].") + log_admin("[key_name(user)] granted the language [language_name] to [key_name(speaker)].") . = TRUE if("remove_language") - if((is_admin || isobserver(AM)) && language_datum) + if((is_admin || isobserver(speaker)) && language_datum) var/list/choices = list("Only Spoken", "Only Understood", "Both") - var/choice = input(user,"Which part do you wish to remove?","[language_datum]",null) as null|anything in choices - var/spoken = FALSE - var/understood = FALSE + var/choice = tgui_input_list(user, "Which part do you wish to remove?", "[language_datum]", choices) + if(isnull(choice)) + return + var/removing_flags = NONE switch(choice) if("Only Spoken") - spoken = TRUE + removing_flags |= SPOKEN_LANGUAGE if("Only Understood") - understood = TRUE + removing_flags |= UNDERSTOOD_LANGUAGE if("Both") - spoken = TRUE - understood = TRUE - language_holder.remove_language(language_datum, understood, spoken) - if(spoken && language_datum == /datum/language/metalanguage) + removing_flags |= ALL + + language_holder.remove_language(language_datum, removing_flags) + if((removing_flags & SPOKEN_LANGUAGE) && (language_datum == /datum/language/metalanguage)) REMOVE_TRAIT(user, TRAIT_METALANGUAGE_KEY_ALLOWED, "lang_added_by_admin") if(is_admin) - message_admins("[key_name_admin(user)] removed the [language_name] language to [key_name_admin(AM)].") - log_admin("[key_name(user)] removed the language [language_name] to [key_name(AM)].") + message_admins("[key_name_admin(user)] removed the [language_name] language to [key_name_admin(speaker)].") + log_admin("[key_name(user)] removed the language [language_name] to [key_name(speaker)].") . = TRUE if("toggle_omnitongue") - if(is_admin || isobserver(AM)) + if(is_admin || isobserver(speaker)) language_holder.omnitongue = !language_holder.omnitongue if(is_admin) - message_admins("[key_name_admin(user)] [language_holder.omnitongue ? "enabled" : "disabled"] the ability to speak all languages (that they know) of [key_name_admin(AM)].") - log_admin("[key_name(user)] [language_holder.omnitongue ? "enabled" : "disabled"] the ability to speak all languages (that_they know) of [key_name(AM)].") + message_admins("[key_name_admin(user)] [language_holder.omnitongue ? "enabled" : "disabled"] the ability to speak all languages (that they know) of [key_name_admin(speaker)].") + log_admin("[key_name(user)] [language_holder.omnitongue ? "enabled" : "disabled"] the ability to speak all languages (that_they know) of [key_name(speaker)].") . = TRUE diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index 2ec126b7947dd..e4ace5ad7ceab 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -582,7 +582,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/shared_storage/blue) if(!user.can_read(src)) return FALSE to_chat(user, "You flip through the pages of the book, quickly and conveniently learning every language in existence. Somewhat less conveniently, the aging book crumbles to dust in the process. Whoops.") - user.grant_all_languages() + user.grant_all_languages(source = LANGUAGE_BABEL) new /obj/effect/decal/cleanable/ash(get_turf(user)) qdel(src) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 06d5bf93df7b0..f47c375bf9a2f 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -32,7 +32,10 @@ GLOBAL_LIST_EMPTY(features_by_species) var/skinned_type var/list/no_equip = list() // slots the race can't equip stuff to var/nojumpsuit = 0 // this is sorta... weird. it basically lets you equip stuff that usually needs jumpsuits without one, like belts and pockets and ids - var/species_language_holder = /datum/language_holder + /// What languages this species can understand and say. + /// Use a [language holder datum][/datum/language_holder] typepath in this var. + /// Should never be null. + var/datum/language_holder/species_language_holder = /datum/language_holder/human_basic /** * Visible CURRENT bodyparts that are unique to a species. * DO NOT USE THIS AS A LIST OF ALL POSSIBLE BODYPARTS AS IT WILL FUCK @@ -481,6 +484,16 @@ GLOBAL_LIST_EMPTY(features_by_species) C.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/species, multiplicative_slowdown=speedmod) + // All languages associated with this language holder are added with source [LANGUAGE_SPECIES] + // rather than source [LANGUAGE_ATOM], so we can track what to remove if our species changes again + var/datum/language_holder/gaining_holder = GLOB.prototype_language_holders[species_language_holder] + for(var/language in gaining_holder.understood_languages) + C.grant_language(language, UNDERSTOOD_LANGUAGE, LANGUAGE_SPECIES) + for(var/language in gaining_holder.spoken_languages) + C.grant_language(language, SPOKEN_LANGUAGE, LANGUAGE_SPECIES) + for(var/language in gaining_holder.blocked_languages) + C.add_blocked_language(language, LANGUAGE_SPECIES) + SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species) @@ -511,6 +524,16 @@ GLOBAL_LIST_EMPTY(features_by_species) for(var/i in inherent_factions) C.faction -= i C.remove_movespeed_modifier(/datum/movespeed_modifier/species) + + // Removes all languages previously associated with [LANGUAGE_SPECIES], gaining our new species will add new ones back + var/datum/language_holder/losing_holder = GLOB.prototype_language_holders[species_language_holder] + for(var/language in losing_holder.understood_languages) + C.remove_language(language, UNDERSTOOD_LANGUAGE, LANGUAGE_SPECIES) + for(var/language in losing_holder.spoken_languages) + C.remove_language(language, SPOKEN_LANGUAGE, LANGUAGE_SPECIES) + for(var/language in losing_holder.blocked_languages) + C.remove_blocked_language(language, LANGUAGE_SPECIES) + SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src) /datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour) @@ -2937,21 +2960,23 @@ GLOBAL_LIST_EMPTY(features_by_species) * Returns a list containing perks, or an empty list. */ /datum/species/proc/create_pref_language_perk() - var/list/to_add = list() // Grab galactic common as a path, for comparisons var/datum/language/common_language = /datum/language/common // Now let's find all the languages they can speak that aren't common var/list/bonus_languages = list() - var/datum/language_holder/temp_holder = new species_language_holder() - for(var/datum/language/language_type as anything in temp_holder.spoken_languages) + var/datum/language_holder/basic_holder = GLOB.prototype_language_holders[species_language_holder] + for(var/datum/language/language_type as anything in basic_holder.spoken_languages) if(ispath(language_type, common_language)) continue bonus_languages += initial(language_type.name) - // If we have any languages we can speak: create a perk for them all - if(length(bonus_languages)) + if(!length(bonus_languages)) + return // You're boring + + var/list/to_add = list() + if(common_language in basic_holder.spoken_languages) to_add += list(list( SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, SPECIES_PERK_ICON = "comment", @@ -2959,7 +2984,13 @@ GLOBAL_LIST_EMPTY(features_by_species) SPECIES_PERK_DESC = "Alongside [initial(common_language.name)], [plural_form] gain the ability to speak [english_list(bonus_languages)].", )) - qdel(temp_holder) + else + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = "comment", + SPECIES_PERK_NAME = "Foreign Speaker", + SPECIES_PERK_DESC = "[plural_form] may not speak [initial(common_language.name)], but they can speak [english_list(bonus_languages)].", + )) return to_add diff --git a/code/modules/mob/living/carbon/human/status_procs.dm b/code/modules/mob/living/carbon/human/status_procs.dm index 337cba0d5413e..66175a6d3eb75 100644 --- a/code/modules/mob/living/carbon/human/status_procs.dm +++ b/code/modules/mob/living/carbon/human/status_procs.dm @@ -42,12 +42,12 @@ /mob/living/carbon/human/set_drugginess(amount) ..() if(!amount) - remove_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_DRUGGY) + remove_language(/datum/language/beachbum, source = LANGUAGE_DRUGGY) /mob/living/carbon/human/adjust_drugginess(amount) ..() if(!dna.check_mutation(STONER)) if(druggy) - grant_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_DRUGGY) + grant_language(/datum/language/beachbum, source = LANGUAGE_DRUGGY) else - remove_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_DRUGGY) + remove_language(/datum/language/beachbum, source = LANGUAGE_DRUGGY) diff --git a/code/modules/mob/living/carbon/say.dm b/code/modules/mob/living/carbon/say.dm index 398f794bac0da..18e96feca1a30 100644 --- a/code/modules/mob/living/carbon/say.dm +++ b/code/modules/mob/living/carbon/say.dm @@ -18,10 +18,10 @@ return FALSE return ..() -/mob/living/carbon/could_speak_language(datum/language/dt) - if(CHECK_BITFIELD(initial(dt.flags), TONGUELESS_SPEECH)) +/mob/living/carbon/could_speak_language(datum/language/language_path) + if(CHECK_BITFIELD(initial(language_path.flags), TONGUELESS_SPEECH)) return TRUE - var/obj/item/organ/tongue/T = getorganslot(ORGAN_SLOT_TONGUE) - if(T) - return T.could_speak_language(dt) + var/obj/item/organ/tongue/spoken_with = getorganslot(ORGAN_SLOT_TONGUE) + if(spoken_with) + return spoken_with.could_speak_language(language_path) return FALSE diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 7458db72d9a22..78fcdc3b789c2 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -124,10 +124,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list( return TRUE if(!language) // get_message_mods() proc finds a language key, and add the language to LANGUAGE_EXTENSION - language = message_mods[LANGUAGE_EXTENSION] - - if(!language) // there's no language argument and LANGUAGE_EXTENSION has no language. failsafe. - language = get_selected_language() + language = message_mods[LANGUAGE_EXTENSION] || get_selected_language() // if you add a new language that works like everyone doesn't understand (i.e. anti-metalanguage), add an additional condition after this // i.e.) if(!language) language = /datum/language/nobody_understands @@ -436,12 +433,3 @@ GLOBAL_LIST_INIT(department_radio_keys, list( /mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced) - -/mob/living/get_language_holder(shadow=TRUE) - if(mind && shadow) - // Mind language holders shadow mob holders. - . = mind.get_language_holder() - if(.) - return . - - . = ..() diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 5c498dd31ad53..66131fc677c49 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -180,7 +180,7 @@ sec.remove_hud_from(src) if("universal_translator") if(!languages_granted) - grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_SOFTWARE) + grant_all_languages(source = LANGUAGE_SOFTWARE) languages_granted = TRUE if("wipe_core") var/confirm = alert(src, "Are you certain you want to wipe yourself?", "Personality Wipe", "Yes", "No") diff --git a/code/modules/mob/living/simple_animal/slime/powers.dm b/code/modules/mob/living/simple_animal/slime/powers.dm index 131ac488b324d..588289cb1767a 100644 --- a/code/modules/mob/living/simple_animal/slime/powers.dm +++ b/code/modules/mob/living/simple_animal/slime/powers.dm @@ -244,7 +244,7 @@ M.maxHealth = round(M.maxHealth * 1.3) M.health = M.maxHealth if(transformeffects & SLIME_EFFECT_PINK) - M.grant_language(/datum/language/common, TRUE, TRUE) + M.grant_language(/datum/language/common) var/datum/language_holder/LH = M.get_language_holder() LH.selected_language = /datum/language/common if(transformeffects & SLIME_EFFECT_BLUESPACE) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 4ba48a2af5a5e..0b04e5ecbd70c 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1279,12 +1279,11 @@ fully_replace_character_name(real_name, new_name) ///Show the language menu for this mob -/mob/verb/open_language_menu() +/mob/verb/open_language_menu_verb() set name = "Open Language Menu" set category = "IC" - var/datum/language_holder/H = get_language_holder() - H.open_language_menu(usr) + get_language_holder().open_language_menu(usr) ///Adjust the nutrition of a mob /mob/proc/adjust_nutrition(var/change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index eafa82b4c8f10..fe71fbc591037 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -2191,12 +2191,12 @@ /datum/reagent/consumable/ratlight/on_mob_metabolize(mob/living/L) L.add_blocked_language(subtypesof(/datum/language/) - /datum/language/ratvar, LANGUAGE_REAGENT) - L.grant_language(/datum/language/ratvar, TRUE, TRUE, LANGUAGE_REAGENT) + L.grant_language(/datum/language/ratvar, source = LANGUAGE_REAGENT) ..() /datum/reagent/consumable/ratlight/on_mob_end_metabolize(mob/living/L) L.remove_blocked_language(subtypesof(/datum/language/), LANGUAGE_REAGENT) - L.remove_language(/datum/language/ratvar, TRUE, TRUE, LANGUAGE_REAGENT) + L.remove_language(/datum/language/ratvar, source = LANGUAGE_REAGENT) L.set_light(-1) ..() diff --git a/code/modules/research/xenobiology/crossbreeding/transformative.dm b/code/modules/research/xenobiology/crossbreeding/transformative.dm index 6f5ba4ce7ecdf..40b5cd0207658 100644 --- a/code/modules/research/xenobiology/crossbreeding/transformative.dm +++ b/code/modules/research/xenobiology/crossbreeding/transformative.dm @@ -128,7 +128,7 @@ transformative extracts: /obj/item/slimecross/transformative/pink/do_effect(mob/living/simple_animal/slime/S) ..() - S.grant_language(/datum/language/common, TRUE, TRUE) + S.grant_language(/datum/language/common) var/datum/language_holder/LH = S.get_language_holder() LH.selected_language = /datum/language/common diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm index 29b39806745f5..4523b3587140c 100644 --- a/code/modules/surgery/organs/tongue.dm +++ b/code/modules/surgery/organs/tongue.dm @@ -7,7 +7,15 @@ slot = ORGAN_SLOT_TONGUE attack_verb_continuous = list("licks", "slobbers", "slaps", "frenches", "tongues") attack_verb_simple = list("lick", "slobber", "slap", "french", "tongue") - var/list/languages_possible + /** + * A cached list of paths of all the languages this tongue is capable of speaking + * + * Relates to a mob's ability to speak a language - a mob must be able to speak the language + * and have a tongue able to speak the language (or omnitongue) in order to actually speak said language + * + * To modify this list for subtypes, see [/obj/item/organ/internal/tongue/proc/get_possible_languages]. Do not modify directly. + */ + VAR_PRIVATE/list/languages_possible var/say_mod = "says" var/ask_mod = "asks" var/yell_mod = "yells" @@ -15,9 +23,33 @@ var/liked_food = JUNKFOOD | FRIED var/disliked_food = GROSS | RAW | CLOTH | GORE var/toxic_food = TOXIC - var/taste_sensitivity = 15 // lower is more sensitive. + // Determines how "sensitive" this tongue is to tasting things, lower is more sensitive. + /// See [/mob/living/proc/get_taste_sensitivity]. + var/taste_sensitivity = 15 + /// Whether this tongue modifies speech via signal var/modifies_speech = FALSE - var/static/list/languages_possible_base = typecacheof(list( + +/obj/item/organ/tongue/Initialize(mapload) + . = ..() + // Setup the possible languages list + // - get_possible_languages gives us a list of language paths + // - then we cache it via string list + // this results in tongues with identical possible languages sharing a cached list instance + languages_possible = string_list(get_possible_languages()) + +/** + * Used in setting up the "languages possible" list. + * + * Override to have your tongue be only capable of speaking certain languages + * Extend to hvae a tongue capable of speaking additional languages to the base tongue + * + * While a user may be theoretically capable of speaking a language, they cannot physically speak it + * UNLESS they have a tongue with that language possible, UNLESS UNLESS they have omnitongue enabled. + */ +/obj/item/organ/tongue/proc/get_possible_languages() + RETURN_TYPE(/list) + // This is the default list of languages most humans should be capable of speaking + return list( /datum/language/aphasia, /datum/language/apidite, /datum/language/beachbum, @@ -36,11 +68,8 @@ /datum/language/sylvan, /datum/language/terrum, /datum/language/uncommon, - /datum/language/sonus)) - -/obj/item/organ/tongue/Initialize(mapload) - . = ..() - languages_possible = languages_possible_base + /datum/language/sonus, + ) /obj/item/organ/tongue/proc/handle_speech(datum/source, list/speech_args) SIGNAL_HANDLER @@ -56,8 +85,8 @@ M.RegisterSignal(M, COMSIG_MOB_SAY, TYPE_PROC_REF(/mob/living/carbon, handle_tongueless_speech)) return ..() -/obj/item/organ/tongue/could_speak_language(datum/language/dt) - return is_type_in_typecache(dt, languages_possible) +/obj/item/organ/tongue/could_speak_language(datum/language/language_path) + return (language_path in languages_possible) /obj/item/organ/tongue/lizard name = "forked tongue" @@ -191,16 +220,17 @@ say_mod = "hisses" taste_sensitivity = 10 // LIZARDS ARE ALIENS CONFIRMED modifies_speech = TRUE // not really, they just hiss - var/static/list/languages_possible_alien = typecacheof(list( + +// Aliens can only speak alien and a few other languages. +/obj/item/organ/tongue/alien/get_possible_languages() + return list( /datum/language/xenocommon, /datum/language/common, + /datum/language/uncommon, /datum/language/draconic, /datum/language/ratvar, - /datum/language/monkey)) - -/obj/item/organ/tongue/alien/Initialize(mapload) - . = ..() - languages_possible = languages_possible_alien + /datum/language/monkey, + ) /obj/item/organ/tongue/alien/handle_speech(datum/source, list/speech_args) playsound(owner, "hiss", 25, 1, 1) @@ -264,9 +294,8 @@ modifies_speech = TRUE taste_sensitivity = 25 // not as good as an organic tongue -/obj/item/organ/tongue/robot/Initialize(mapload) - . = ..() - languages_possible = languages_possible_base += typecacheof(/datum/language/machine) + typecacheof(/datum/language/voltaic) +/obj/item/organ/tongue/robot/get_possible_languages() + return ..() + /datum/language/machine + /datum/language/voltaic /obj/item/organ/tongue/robot/emp_act(severity) owner.emote("scream") @@ -301,9 +330,8 @@ taste_sensitivity = 101 // Not a tongue, they can't taste shit toxic_food = NONE -/obj/item/organ/tongue/ethereal/Initialize(mapload) - . = ..() - languages_possible = languages_possible_base += typecacheof(/datum/language/voltaic) +/obj/item/organ/tongue/ethereal/get_possible_languages() + return ..() + /datum/language/voltaic /obj/item/organ/tongue/golem name = "mineral tongue" @@ -312,9 +340,8 @@ taste_sensitivity = 101 //They don't eat. icon_state = "adamantine_cords" -/obj/item/organ/tongue/golem/Initialize(mapload) - . = ..() - languages_possible = languages_possible_base += typecacheof(/datum/language/terrum) +/obj/item/organ/tongue/golem/get_possible_languages() + return ..() + /datum/language/terrum /obj/item/organ/tongue/golem/bananium name = "bananium tongue" @@ -344,9 +371,8 @@ toxic_food = NONE disliked_food = NONE -/obj/item/organ/tongue/slime/Initialize(mapload) - . = ..() - languages_possible = languages_possible_base += typecacheof(/datum/language/slime) +/obj/item/organ/tongue/slime/get_possible_languages() + return ..() + /datum/language/slime /obj/item/organ/tongue/moth name = "mothic tongue" diff --git a/code/modules/tgui/states/language_menu.dm b/code/modules/tgui/states/language_menu.dm index 6389b05cd5e80..72f1c230c93e7 100644 --- a/code/modules/tgui/states/language_menu.dm +++ b/code/modules/tgui/states/language_menu.dm @@ -12,6 +12,6 @@ GLOBAL_DATUM_INIT(language_menu_state, /datum/ui_state/language_menu, new) if(check_rights_for(user.client, R_ADMIN)) . = UI_INTERACTIVE else if(istype(src_object, /datum/language_menu)) - var/datum/language_menu/LM = src_object - if(LM.language_holder.get_atom() == user) + var/datum/language_menu/my_languages = src_object + if(my_languages.language_holder.owner == user) . = UI_INTERACTIVE diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 6a8bf96075e3f..7788bda1ba68c 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -41,6 +41,7 @@ #include "heretic_rituals.dm" #include "icon_smoothing_unit_test.dm" #include "keybinding_init.dm" +#include "language_transfer.dm" #include "merge_type.dm" #include "metabolizing.dm" #include "missing_icons.dm" diff --git a/code/modules/unit_tests/language_transfer.dm b/code/modules/unit_tests/language_transfer.dm new file mode 100644 index 0000000000000..e69de29bb2d1d From 08e2967c148401107948d84d7642ddb5ef05628a Mon Sep 17 00:00:00 2001 From: ss13-beebot <56381746+ss13-beebot@users.noreply.github.com> Date: Tue, 24 Dec 2024 09:33:38 -0600 Subject: [PATCH 014/198] Automatic changelog generation for PR #11971 [ci skip] --- html/changelogs/AutoChangeLog-pr-11971.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-11971.yml diff --git a/html/changelogs/AutoChangeLog-pr-11971.yml b/html/changelogs/AutoChangeLog-pr-11971.yml new file mode 100644 index 0000000000000..77fa365e08562 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-11971.yml @@ -0,0 +1,9 @@ +author: rkz, MrMelbert, Cyberboss +delete-after: true +changes: + - refactor: refactored language holders, species changes will no longer delete all + known languages + - refactor: refactored tongue language lists to kill typecache nonsense and handle + subtypes sanely + - code_imp: unit tests language transfers + - code_imp: fix config errors before /world/New() From 72604abf0eb7724bd571d5d6373e370993dc9f9c Mon Sep 17 00:00:00 2001 From: XeonMations <62395746+XeonMations@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:14:13 +0200 Subject: [PATCH 015/198] Update ai.dm (#11993) --- code/modules/mob/living/silicon/ai/ai.dm | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index d5ac9e90095f7..b355c0c9f4864 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -629,19 +629,21 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/silicon/ai) if(incapacitated()) return + var/mob/user = src var/input - switch(alert("Would you like to select a hologram based on a crew member, an animal, or switch to a unique avatar?",,"Crew Member","Unique","Animal")) + var/list/hologram_choice = list("Crew Member","Unique","Animal") + switch(tgui_alert(user, "Would you like to select a hologram based on a crew member, an animal, or switch to a unique avatar?", "Choices", hologram_choice)) if("Crew Member") var/list/personnel_list = list() - for(var/datum/record/crew/record in GLOB.manifest.locked)//Look in data core locked. + for(var/datum/record/locked/record in GLOB.manifest.locked)//Look in data core locked. personnel_list["[record.name]: [record.rank]"] = record.character_appearance//Pull names, rank, and image. if(!length(personnel_list)) alert("No suitable records found. Aborting.") return - if(personnel_list.len) - input = input("Select a crew member:") as null|anything in sort_list(personnel_list) + if(length(personnel_list)) + input = tgui_input_list(user, "Select a crew member", "AI Hologram Selection", sort_list(personnel_list)) var/mutable_appearance/character_icon = personnel_list[input] if(character_icon) qdel(holo_icon)//Clear old icon so we're not storing it in memory. @@ -667,7 +669,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/silicon/ai) "spider" = 'icons/mob/animal.dmi' ) - input = input("Please select a hologram:") as null|anything in sort_list(icon_list) + input = tgui_input_list(user, "Please select a hologram:", "Animal Choice", sort_list(icon_list)) if(input) qdel(holo_icon) switch(input) @@ -687,7 +689,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/silicon/ai) "horror" = 'icons/mob/ai.dmi' ) - input = input("Please select a hologram:") as null|anything in sort_list(icon_list) + input = tgui_input_list(user, "Please select a hologram", "Hologram Choice", sort_list(icon_list)) if(input) qdel(holo_icon) switch(input) From d30f2f30b1490e7b94f706989e40a22c54d41299 Mon Sep 17 00:00:00 2001 From: ss13-beebot <56381746+ss13-beebot@users.noreply.github.com> Date: Tue, 24 Dec 2024 09:52:35 -0600 Subject: [PATCH 016/198] Automatic changelog generation for PR #11993 [ci skip] --- html/changelogs/AutoChangeLog-pr-11993.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-11993.yml diff --git a/html/changelogs/AutoChangeLog-pr-11993.yml b/html/changelogs/AutoChangeLog-pr-11993.yml new file mode 100644 index 0000000000000..2da0351959607 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-11993.yml @@ -0,0 +1,5 @@ +author: XeonMations +delete-after: true +changes: + - bugfix: Fixed AIs being unable to select a crew member as a hologram. + - refactor: TGUI-ified the AI hologram selection menu. From a57623bad910ef569ddd4a184bd42081ec0d2bc9 Mon Sep 17 00:00:00 2001 From: ss13-beebot <56381746+ss13-beebot@users.noreply.github.com> Date: Tue, 24 Dec 2024 16:04:02 +0000 Subject: [PATCH 017/198] Automatic changelog compile [ci skip] --- html/changelog.html | 19 +++++++++++++++++++ html/changelogs/.all_changelog.yml | 16 ++++++++++++++++ html/changelogs/AutoChangeLog-pr-11968.yml | 9 --------- html/changelogs/AutoChangeLog-pr-11971.yml | 9 --------- html/changelogs/AutoChangeLog-pr-11993.yml | 5 ----- 5 files changed, 35 insertions(+), 23 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-11968.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-11971.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-11993.yml diff --git a/html/changelog.html b/html/changelog.html index 04bf70b4dfd6a..d15fb6d4cc7cd 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -65,10 +65,29 @@ Therealdoooc213 updated:
+
- the blackbox is now a large item
XeonMations updated:
++
- Fixed AIs being unable to select a crew member as a hologram.
+- TGUI-ified the AI hologram selection menu.
+XeonMations, ManiacalAnxiety, therealdoooc213 updated:
+
- Split personalities can no longer throw out their original body's mind out of their own body permenantly.
rkz, MrMelbert, Cyberboss updated:
++
+- refactored language holders, species changes will no longer delete all known languages
+- refactored tongue language lists to kill typecache nonsense and handle subtypes sanely
+- unit tests language transfers
+- fix config errors before /world/New()
+rkz, haukeschaumann, JohnFulpWillard, jlsnow301, Crumpaloo, San7890, MrStonedOne, Lilah Novi updated:
++
- fix custom typing indicators. Cyborgs, xenos, and lawyers (among others) now have unique typing indicators again after 3 years.
+- new *Animated* typing indicator sprites
+- typing in the command bar will now activate typing indicator.
+- traitified a typing indicator var
+22 December 2024
BarteG44 updated:
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml index 08324287d2bcf..88983ce38a62f 100644 --- a/html/changelogs/.all_changelog.yml +++ b/html/changelogs/.all_changelog.yml @@ -45240,6 +45240,22 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py. - tweak: Removes the second wire from the c4 charge Therealdoooc213: - tweak: the blackbox is now a large item + XeonMations: + - bugfix: Fixed AIs being unable to select a crew member as a hologram. + - refactor: TGUI-ified the AI hologram selection menu. XeonMations, ManiacalAnxiety, therealdoooc213: - bugfix: Split personalities can no longer throw out their original body's mind out of their own body permenantly. + rkz, MrMelbert, Cyberboss: + - refactor: refactored language holders, species changes will no longer delete all + known languages + - refactor: refactored tongue language lists to kill typecache nonsense and handle + subtypes sanely + - code_imp: unit tests language transfers + - code_imp: fix config errors before /world/New() + rkz, haukeschaumann, JohnFulpWillard, jlsnow301, Crumpaloo, San7890, MrStonedOne, Lilah Novi: + - bugfix: fix custom typing indicators. Cyborgs, xenos, and lawyers (among others) + now have unique typing indicators again after 3 years. + - rscadd: new *Animated* typing indicator sprites + - rscadd: typing in the command bar will now activate typing indicator. + - code_imp: traitified a typing indicator var diff --git a/html/changelogs/AutoChangeLog-pr-11968.yml b/html/changelogs/AutoChangeLog-pr-11968.yml deleted file mode 100644 index f14202778e8cd..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-11968.yml +++ /dev/null @@ -1,9 +0,0 @@ -author: rkz, haukeschaumann, JohnFulpWillard, jlsnow301, Crumpaloo, San7890, MrStonedOne, - Lilah Novi -delete-after: true -changes: - - bugfix: fix custom typing indicators. Cyborgs, xenos, and lawyers (among others) - now have unique typing indicators again after 3 years. - - rscadd: new *Animated* typing indicator sprites - - rscadd: typing in the command bar will now activate typing indicator. - - code_imp: traitified a typing indicator var diff --git a/html/changelogs/AutoChangeLog-pr-11971.yml b/html/changelogs/AutoChangeLog-pr-11971.yml deleted file mode 100644 index 77fa365e08562..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-11971.yml +++ /dev/null @@ -1,9 +0,0 @@ -author: rkz, MrMelbert, Cyberboss -delete-after: true -changes: - - refactor: refactored language holders, species changes will no longer delete all - known languages - - refactor: refactored tongue language lists to kill typecache nonsense and handle - subtypes sanely - - code_imp: unit tests language transfers - - code_imp: fix config errors before /world/New() diff --git a/html/changelogs/AutoChangeLog-pr-11993.yml b/html/changelogs/AutoChangeLog-pr-11993.yml deleted file mode 100644 index 2da0351959607..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-11993.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: XeonMations -delete-after: true -changes: - - bugfix: Fixed AIs being unable to select a crew member as a hologram. - - refactor: TGUI-ified the AI hologram selection menu. From a6427cbf071f2d465abb989688d434aa495ec34e Mon Sep 17 00:00:00 2001 From: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com> Date: Tue, 24 Dec 2024 10:17:44 -0500 Subject: [PATCH 018/198] Fixes Bandanas. Adds GAGS support for Bandanas (#11125) * bandana banaza * yuh * prophet * hmmm * fix, renable coloring * remove coloring * prophet * ugh --- beestation.dme | 1 + code/__DEFINES/dye_keys.dm | 1 + code/__DEFINES/flags.dm | 2 +- code/_globalvars/bitfields.dm | 5 +- code/datums/greyscale/greyscale_configs.dm | 59 ++++++ .../greyscale/json_configs/bandana.json | 10 + .../greyscale/json_configs/bandana_up.json | 10 + .../greyscale/json_configs/bandana_worn.json | 10 + .../json_configs/bandana_worn_up.json | 10 + .../greyscale/json_configs/bandskull.json | 16 ++ .../greyscale/json_configs/bandskull_up.json | 16 ++ .../json_configs/bandskull_worn.json | 16 ++ .../json_configs/bandskull_worn_up.json | 16 ++ .../greyscale/json_configs/bandstriped.json | 16 ++ .../json_configs/bandstriped_up.json | 16 ++ .../json_configs/bandstriped_worn.json | 16 ++ .../json_configs/bandstriped_worn_up.json | 16 ++ code/game/machinery/washing_machine.dm | 10 + code/game/objects/items.dm | 2 + .../stacks/sheets/organic/cloths_recipes.dm | 5 +- .../crates_lockers/closets/job_closets.dm | 6 +- .../crates_lockers/closets/wardrobe.dm | 7 +- code/modules/cargo/packs.dm | 2 +- .../client/loadout/loadout_accessories.dm | 2 +- code/modules/clothing/head/misc_special.dm | 9 +- code/modules/clothing/masks/_masks.dm | 2 +- code/modules/clothing/masks/bandana.dm | 192 ++++++++++++++++++ code/modules/clothing/masks/miscellaneous.dm | 84 -------- code/modules/clothing/neck/_neck.dm | 28 --- code/modules/clothing/suits/chaplainsuits.dm | 10 +- .../mob/living/carbon/human/update_icons.dm | 43 ++-- code/modules/vending/_vending.dm | 10 +- code/modules/vending/clothesmate.dm | 3 + code/modules/vending/wardrobes.dm | 58 +++--- icons/mob/clothing/head/costume.dmi | Bin 48613 -> 47102 bytes icons/mob/clothing/head/helmet.dmi | Bin 29619 -> 31017 bytes icons/mob/clothing/mask.dmi | Bin 46089 -> 45375 bytes icons/mob/large-worn-icons/64x64/head.dmi | Bin 1236 -> 0 bytes icons/obj/clothing/masks.dmi | Bin 38813 -> 37647 bytes 39 files changed, 522 insertions(+), 187 deletions(-) create mode 100644 code/datums/greyscale/json_configs/bandana.json create mode 100644 code/datums/greyscale/json_configs/bandana_up.json create mode 100644 code/datums/greyscale/json_configs/bandana_worn.json create mode 100644 code/datums/greyscale/json_configs/bandana_worn_up.json create mode 100644 code/datums/greyscale/json_configs/bandskull.json create mode 100644 code/datums/greyscale/json_configs/bandskull_up.json create mode 100644 code/datums/greyscale/json_configs/bandskull_worn.json create mode 100644 code/datums/greyscale/json_configs/bandskull_worn_up.json create mode 100644 code/datums/greyscale/json_configs/bandstriped.json create mode 100644 code/datums/greyscale/json_configs/bandstriped_up.json create mode 100644 code/datums/greyscale/json_configs/bandstriped_worn.json create mode 100644 code/datums/greyscale/json_configs/bandstriped_worn_up.json create mode 100644 code/modules/clothing/masks/bandana.dm delete mode 100644 icons/mob/large-worn-icons/64x64/head.dmi diff --git a/beestation.dme b/beestation.dme index 24546f62a5a09..b2d63768a1b02 100644 --- a/beestation.dme +++ b/beestation.dme @@ -2477,6 +2477,7 @@ #include "code\modules\clothing\head\tinfoilhat.dm" #include "code\modules\clothing\head\tophat.dm" #include "code\modules\clothing\masks\_masks.dm" +#include "code\modules\clothing\masks\bandana.dm" #include "code\modules\clothing\masks\boxing.dm" #include "code\modules\clothing\masks\breath.dm" #include "code\modules\clothing\masks\cluwne.dm" diff --git a/code/__DEFINES/dye_keys.dm b/code/__DEFINES/dye_keys.dm index 9efe387bc636e..8d9496b5a10c8 100644 --- a/code/__DEFINES/dye_keys.dm +++ b/code/__DEFINES/dye_keys.dm @@ -2,6 +2,7 @@ #define DYE_REGISTRY_JUMPSKIRT "jumpskirt" #define DYE_REGISTRY_SUITS "suit" #define DYE_REGISTRY_GLOVES "gloves" +#define DYE_REGISTRY_BANDANA "bandana" #define DYE_REGISTRY_SNEAKERS "sneakers" #define DYE_REGISTRY_CAP "softcap" #define DYE_REGISTRY_BERET "beret" diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 04e7d943ddb6e..ee67cde96ac79 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -55,7 +55,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 /// Should we use the initial icon for display? Mostly used by overlay only objects #define HTML_USE_INITAL_ICON_1 (1<<13) /// Prevents direct access for anything in the contents of this atom. -#define NO_DIRECT_ACCESS_FROM_CONTENTS_1 (1<<14) +#define NO_DIRECT_ACCESS_FROM_CONTENTS_1 (1<<15) //turf-only flags. These use flags_1 too. // These exist to cover /turf and /area at the same time diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 8d75b40d3dd9d..43a1497a6ec07 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -183,16 +183,17 @@ DEFINE_BITFIELD(flags_1, list( "HOLOGRAM_1" = HOLOGRAM_1, "INITIALIZED_1" = INITIALIZED_1, "IS_ONTOP_1" = IS_ONTOP_1, + //"IS_PLAYER_COLORABLE_1" = IS_PLAYER_COLORABLE_1, "NODECONSTRUCT_1" = NODECONSTRUCT_1, "NOJAUNT_1" = NOJAUNT_1, "NO_RUINS_1" = NO_RUINS_1, "NO_LAVA_GEN_1" = NO_LAVA_GEN_1, - "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, "OVERLAY_QUEUED_1" = OVERLAY_QUEUED_1, "ON_BORDER_1" = ON_BORDER_1, "PREVENT_CLICK_UNDER_1" = PREVENT_CLICK_UNDER_1, - "TESLA_IGNORE_1" = TESLA_IGNORE_1, "PREVENT_CONTENTS_EXPLOSION_1" = PREVENT_CONTENTS_EXPLOSION_1, + "TESLA_IGNORE_1" = TESLA_IGNORE_1, + "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, "UNPAINTABLE_1" = UNPAINTABLE_1, )) diff --git a/code/datums/greyscale/greyscale_configs.dm b/code/datums/greyscale/greyscale_configs.dm index dd93c881bc20a..16ba9efad921d 100644 --- a/code/datums/greyscale/greyscale_configs.dm +++ b/code/datums/greyscale/greyscale_configs.dm @@ -201,6 +201,65 @@ icon_file = 'icons/obj/plushes.dmi' json_config = 'code/datums/greyscale/json_configs/plushie_spacelizard.json' +/datum/greyscale_config/bandana + name = "Bandana" + icon_file = 'icons/obj/clothing/masks.dmi' + json_config = 'code/datums/greyscale/json_configs/bandana.json' + +/datum/greyscale_config/bandana_up + name = "Bandana Up" + icon_file = 'icons/obj/clothing/masks.dmi' + json_config = 'code/datums/greyscale/json_configs/bandana_up.json' + +/datum/greyscale_config/bandana_worn + name = "Worn Bandana" + icon_file = 'icons/mob/clothing/mask.dmi' + json_config = 'code/datums/greyscale/json_configs/bandana_worn.json' + +/datum/greyscale_config/bandana_worn_up + name = "Worn Bandana Up" + icon_file = 'icons/mob/clothing/mask.dmi' + json_config = 'code/datums/greyscale/json_configs/bandana_worn_up.json' + +/datum/greyscale_config/bandstriped + name = "Striped Bandana" + icon_file = 'icons/obj/clothing/masks.dmi' + json_config = 'code/datums/greyscale/json_configs/bandstriped.json' + +/datum/greyscale_config/bandstriped_up + name = "Striped Bandana Up" + icon_file = 'icons/obj/clothing/masks.dmi' + json_config = 'code/datums/greyscale/json_configs/bandstriped_up.json' + +/datum/greyscale_config/bandstriped_worn + name = "Worn Striped Bandana" + icon_file = 'icons/mob/clothing/mask.dmi' + json_config = 'code/datums/greyscale/json_configs/bandstriped_worn.json' + +/datum/greyscale_config/bandstriped_worn_up + name = "Worn Striped Bandana Up" + icon_file = 'icons/mob/clothing/mask.dmi' + json_config = 'code/datums/greyscale/json_configs/bandstriped_worn_up.json' + +/datum/greyscale_config/bandskull + name = "Skull Bandana" + icon_file = 'icons/obj/clothing/masks.dmi' + json_config = 'code/datums/greyscale/json_configs/bandskull.json' + +/datum/greyscale_config/bandskull_up + name = "Skull Bandana Up" + icon_file = 'icons/obj/clothing/masks.dmi' + json_config = 'code/datums/greyscale/json_configs/bandskull_up.json' + +/datum/greyscale_config/bandskull_worn + name = "Worn Skull Bandana" + icon_file = 'icons/mob/clothing/mask.dmi' + json_config = 'code/datums/greyscale/json_configs/bandskull_worn.json' + +/datum/greyscale_config/bandskull_worn_up + name = "Worn Skull Bandana Up" + icon_file = 'icons/mob/clothing/mask.dmi' + json_config = 'code/datums/greyscale/json_configs/bandskull_worn_up.json' /datum/greyscale_config/ctf_standard name = "CTF Standard Vest" icon_file = 'icons/obj/clothing/suits/ctf.dmi' diff --git a/code/datums/greyscale/json_configs/bandana.json b/code/datums/greyscale/json_configs/bandana.json new file mode 100644 index 0000000000000..a56d9b182658a --- /dev/null +++ b/code/datums/greyscale/json_configs/bandana.json @@ -0,0 +1,10 @@ +{ + "bandana": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandana_up.json b/code/datums/greyscale/json_configs/bandana_up.json new file mode 100644 index 0000000000000..393d7b0710f66 --- /dev/null +++ b/code/datums/greyscale/json_configs/bandana_up.json @@ -0,0 +1,10 @@ +{ + "bandana_up": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth_up", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandana_worn.json b/code/datums/greyscale/json_configs/bandana_worn.json new file mode 100644 index 0000000000000..bc0e6c002d515 --- /dev/null +++ b/code/datums/greyscale/json_configs/bandana_worn.json @@ -0,0 +1,10 @@ +{ + "bandana_worn": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth_worn", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandana_worn_up.json b/code/datums/greyscale/json_configs/bandana_worn_up.json new file mode 100644 index 0000000000000..b5375d5dfa07c --- /dev/null +++ b/code/datums/greyscale/json_configs/bandana_worn_up.json @@ -0,0 +1,10 @@ +{ + "bandana_worn_up": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth_worn_up", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandskull.json b/code/datums/greyscale/json_configs/bandskull.json new file mode 100644 index 0000000000000..18344d5d13a19 --- /dev/null +++ b/code/datums/greyscale/json_configs/bandskull.json @@ -0,0 +1,16 @@ +{ + "bandskull": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bandana_skull", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandskull_up.json b/code/datums/greyscale/json_configs/bandskull_up.json new file mode 100644 index 0000000000000..c01dc7bcf4351 --- /dev/null +++ b/code/datums/greyscale/json_configs/bandskull_up.json @@ -0,0 +1,16 @@ +{ + "bandskull_up": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth_up", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bandana_skull_up", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandskull_worn.json b/code/datums/greyscale/json_configs/bandskull_worn.json new file mode 100644 index 0000000000000..3d2e34688c94b --- /dev/null +++ b/code/datums/greyscale/json_configs/bandskull_worn.json @@ -0,0 +1,16 @@ +{ + "bandskull_worn": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth_worn", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bandana_skull_worn", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandskull_worn_up.json b/code/datums/greyscale/json_configs/bandskull_worn_up.json new file mode 100644 index 0000000000000..be37d2ee2b857 --- /dev/null +++ b/code/datums/greyscale/json_configs/bandskull_worn_up.json @@ -0,0 +1,16 @@ +{ + "bandskull_worn_up": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth_worn_up", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bandana_skull_worn_up", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandstriped.json b/code/datums/greyscale/json_configs/bandstriped.json new file mode 100644 index 0000000000000..e31fb50309f0a --- /dev/null +++ b/code/datums/greyscale/json_configs/bandstriped.json @@ -0,0 +1,16 @@ +{ + "bandstriped": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bandana_stripe", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandstriped_up.json b/code/datums/greyscale/json_configs/bandstriped_up.json new file mode 100644 index 0000000000000..da464432cd4a1 --- /dev/null +++ b/code/datums/greyscale/json_configs/bandstriped_up.json @@ -0,0 +1,16 @@ +{ + "bandstriped_up": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth_up", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bandana_stripe_up", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandstriped_worn.json b/code/datums/greyscale/json_configs/bandstriped_worn.json new file mode 100644 index 0000000000000..89d729aa4c97b --- /dev/null +++ b/code/datums/greyscale/json_configs/bandstriped_worn.json @@ -0,0 +1,16 @@ +{ + "bandstriped_worn": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth_worn", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bandana_stripe_worn", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/bandstriped_worn_up.json b/code/datums/greyscale/json_configs/bandstriped_worn_up.json new file mode 100644 index 0000000000000..7d08ea651ac8b --- /dev/null +++ b/code/datums/greyscale/json_configs/bandstriped_worn_up.json @@ -0,0 +1,16 @@ +{ + "bandstriped_worn_up": [ + { + "type": "icon_state", + "icon_state": "bandana_cloth_worn_up", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bandana_stripe_worn_up", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 2aa9564b3f17f..eb582cc0defdd 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -81,6 +81,16 @@ GLOBAL_LIST_INIT(dye_registry, list( DYE_SYNDICATE = /obj/item/clothing/under/syndicate, DYE_CENTCOM = /obj/item/clothing/under/rank/centcom/commander ), + DYE_REGISTRY_BANDANA = list( + DYE_RED = /obj/item/clothing/mask/bandana/red, + DYE_ORANGE = /obj/item/clothing/mask/bandana/orange, + DYE_YELLOW = /obj/item/clothing/mask/bandana/gold, + DYE_GREEN = /obj/item/clothing/mask/bandana/green, + DYE_BLUE = /obj/item/clothing/mask/bandana/blue, + DYE_PURPLE = /obj/item/clothing/mask/bandana/purple, + DYE_BLACK = /obj/item/clothing/mask/bandana/black, + DYE_WHITE = /obj/item/clothing/mask/bandana/white + ), DYE_REGISTRY_SNEAKERS = list( DYE_RED = /obj/item/clothing/shoes/sneakers/red, DYE_ORANGE = /obj/item/clothing/shoes/sneakers/orange, diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index efe12677ab434..adb388c5d03c1 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -36,6 +36,8 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) var/inhand_x_dimension = 32 /// y dimension of the inhand sprite var/inhand_y_dimension = 32 + /// Worn overlay will be shifted by this along y axis + var/worn_y_offset = 0 //Not on /clothing because for some reason any /obj/item can technically be "worn" with enough fuckery. /// If this is set, update_icons() will find on mob (WORN, NOT INHANDS) states in this file instead, primary use: badminnery/events diff --git a/code/game/objects/items/stacks/sheets/organic/cloths_recipes.dm b/code/game/objects/items/stacks/sheets/organic/cloths_recipes.dm index 1987c3979daae..2f08e99675fc6 100644 --- a/code/game/objects/items/stacks/sheets/organic/cloths_recipes.dm +++ b/code/game/objects/items/stacks/sheets/organic/cloths_recipes.dm @@ -4,7 +4,8 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \ new/datum/stack_recipe("white jumpsuit", /obj/item/clothing/under/color/white, 3, time = 4 SECONDS), \ new/datum/stack_recipe("white jumpskirt", /obj/item/clothing/under/color/jumpskirt/white, 3, time = 4 SECONDS), \ new/datum/stack_recipe("white shoes", /obj/item/clothing/shoes/sneakers/white, 2, time = 4 SECONDS), \ - new/datum/stack_recipe("white scarf", /obj/item/clothing/neck/scarf, 1, time = 4 SECONDS), \ + new/datum/stack_recipe("white scarf", /obj/item/clothing/neck/scarf, 1), \ + new/datum/stack_recipe("white bandana", /obj/item/clothing/mask/bandana/white, 2), \ new/datum/stack_recipe("white hoodie", /obj/item/clothing/suit/hooded/hoodie, 5, time = 4 SECONDS), \ null, \ new/datum/stack_recipe("backpack", /obj/item/storage/backpack, 4, time = 6 SECONDS), \ @@ -50,7 +51,7 @@ GLOBAL_LIST_INIT(durathread_recipes, list ( \ new/datum/stack_recipe("durathread jumpskirt", /obj/item/clothing/under/color/jumpskirt/durathread, 4, time = 4 SECONDS), \ new/datum/stack_recipe("durathread beret", /obj/item/clothing/head/beret/durathread, 2, time = 4 SECONDS), \ new/datum/stack_recipe("durathread beanie", /obj/item/clothing/head/beanie/durathread, 2, time = 4 SECONDS), \ - new/datum/stack_recipe("durathread bandana", /obj/item/clothing/mask/bandana/durathread, 1, time = 2.5 SECONDS), \ + new/datum/stack_recipe("durathread bandana", /obj/item/clothing/mask/bandana/durathread, 1, time = 25), \ new/datum/stack_recipe("durathread hoodie", /obj/item/clothing/suit/hooded/hoodie/durathread, 5, time = 5 SECONDS), \ )) diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm index 0fdd01228f0de..5c109dd40fec8 100644 --- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm @@ -237,9 +237,9 @@ /obj/item/clothing/head/beret/sci = 2) generate_items_inside(items_inside,src) if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) + new /obj/item/clothing/mask/bandana/skull/black(src) if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) + new /obj/item/clothing/mask/bandana/skull/black(src) return @@ -321,7 +321,7 @@ /obj/item/clothing/suit/apron = 2, /obj/item/clothing/suit/apron/overalls = 2, /obj/item/clothing/under/rank/civilian/hydroponics = 3, - /obj/item/clothing/mask/bandana/botany = 3) + /obj/item/clothing/mask/bandana/striped/botany = 3) generate_items_inside(items_inside,src) /obj/structure/closet/wardrobe/curator diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm index 824c549fbe094..151f62ba0727d 100644 --- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm +++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm @@ -58,7 +58,7 @@ new /obj/item/clothing/mask/bandana/black(src) new /obj/item/clothing/mask/bandana/black(src) if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) + new /obj/item/clothing/mask/bandana/skull/black(src) return @@ -120,6 +120,8 @@ new /obj/item/clothing/shoes/sneakers/white(src) for(var/i in 1 to 3) new /obj/item/clothing/head/soft(src) + new /obj/item/clothing/mask/bandana/white(src) + new /obj/item/clothing/mask/bandana/white(src) return /obj/structure/closet/wardrobe/pjs @@ -178,11 +180,8 @@ new /obj/item/storage/box/suitbox/wardrobe/mixed(src) new /obj/item/storage/box/suitbox/wardrobe/mixed/jumpskirt(src) new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/blue(src) new /obj/item/clothing/mask/bandana/blue(src) new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/mask/bandana/gold(src) new /obj/item/clothing/shoes/sneakers/black(src) new /obj/item/clothing/shoes/sneakers/brown(src) new /obj/item/clothing/shoes/sneakers/white(src) diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm index be7bf34de53a6..128f6ce75e683 100644 --- a/code/modules/cargo/packs.dm +++ b/code/modules/cargo/packs.dm @@ -77,7 +77,7 @@ /obj/item/clothing/suit/jacket/leather/overcoat, /obj/item/clothing/gloves/color/black, /obj/item/clothing/head/soft/cargo, - /obj/item/clothing/mask/bandana/skull)//so you can properly #cargoniabikergang + /obj/item/clothing/mask/bandana/skull/black)//so you can properly #cargoniabikergang crate_name = "Biker Kit" crate_type = /obj/structure/closet/crate/large diff --git a/code/modules/client/loadout/loadout_accessories.dm b/code/modules/client/loadout/loadout_accessories.dm index 60c18fcbc94be..2c6cf051a21d0 100644 --- a/code/modules/client/loadout/loadout_accessories.dm +++ b/code/modules/client/loadout/loadout_accessories.dm @@ -209,7 +209,7 @@ /datum/gear/accessory/bandana/skull display_name = "skull bandana" - path = /obj/item/clothing/mask/bandana/skull + path = /obj/item/clothing/mask/bandana/skull/black cost = 2000 //LIPSTICK diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index 8fc44b0c65e68..9e1f03a9daf06 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -148,17 +148,16 @@ /obj/item/clothing/head/costume/speedwagon name = "hat of ultimate masculinity" desc = "Even the mere act of wearing this makes you want to pose menacingly." - worn_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' icon_state = "speedwagon" item_state = "speedwagon" - worn_x_dimension = 64 - worn_y_dimension = 64 + worn_y_offset = 4 /obj/item/clothing/head/costume/speedwagon/cursed name = "ULTIMATE HAT" desc = "You feel weak and pathetic in comparison to this exceptionally beautiful hat." - icon_state = "speedwagon_cursed" - item_state = "speedwagon_cursed" + icon_state = "speedwagon" + item_state = "speedwagon" + worn_y_offset = 6 /obj/item/clothing/head/franks_hat name = "Frank's Hat" diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index 48816303b8c96..50dc44d8e44f8 100644 --- a/code/modules/clothing/masks/_masks.dm +++ b/code/modules/clothing/masks/_masks.dm @@ -6,7 +6,7 @@ strip_delay = 40 equip_delay_other = 40 var/modifies_speech = FALSE - var/mask_adjusted = 0 + var/mask_adjusted = FALSE var/adjusted_flags = null var/voice_change = FALSE //Used to mask/change the user's voice, only specific masks can set this to TRUE var/obj/item/organ/tongue/chosen_tongue = null diff --git a/code/modules/clothing/masks/bandana.dm b/code/modules/clothing/masks/bandana.dm new file mode 100644 index 0000000000000..f2a560ef4d234 --- /dev/null +++ b/code/modules/clothing/masks/bandana.dm @@ -0,0 +1,192 @@ +/obj/item/clothing/mask/bandana + w_class = WEIGHT_CLASS_TINY + flags_cover = MASKCOVERSMOUTH + flags_inv = HIDEFACE|HIDEFACIALHAIR|HIDESNOUT + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR|HIDESNOUT + visor_flags_cover = MASKCOVERSMOUTH + slot_flags = ITEM_SLOT_MASK + adjusted_flags = ITEM_SLOT_HEAD + dying_key = DYE_REGISTRY_BANDANA + //flags_1 = IS_PLAYER_COLORABLE_1 + name = "bandana" + desc = "A fine bandana with nanotech lining." + icon_state = "bandana" + worn_icon_state = "bandana_worn" + greyscale_config = /datum/greyscale_config/bandana + greyscale_config_worn = /datum/greyscale_config/bandana_worn + var/greyscale_config_up = /datum/greyscale_config/bandana_up + var/greyscale_config_worn_up = /datum/greyscale_config/bandana_worn_up + greyscale_colors = "#2e2e2e" + +/obj/item/clothing/mask/bandana/attack_self(mob/user) + if(slot_flags & ITEM_SLOT_NECK) + to_chat(user, "You must undo [src] in order to push it into a hat!") + return + adjustmask(user) + if(greyscale_config == initial(greyscale_config) && greyscale_config_worn == initial(greyscale_config_worn)) + worn_icon_state += "_up" + undyeable = TRUE + set_greyscale( + new_config = greyscale_config_up, + new_worn_config = greyscale_config_worn_up + ) + else + worn_icon_state = initial(worn_icon_state) + undyeable = initial(undyeable) + set_greyscale( + new_config = initial(greyscale_config), + new_worn_config = initial(greyscale_config_worn) + ) + +/obj/item/clothing/mask/bandana/AltClick(mob/user) + . = ..() + if(iscarbon(user)) + var/mob/living/carbon/C = user + var/matrix/widen = matrix() + if(!user.is_holding(src)) + to_chat(user, "You must be holding [src] in order to tie it!") + return + if((C.get_item_by_slot(ITEM_SLOT_HEAD == src)) || (C.get_item_by_slot(ITEM_SLOT_MASK) == src)) + to_chat(user, "You can't tie [src] while wearing it!") + return + if(slot_flags & ITEM_SLOT_HEAD) + to_chat(user, "You must undo [src] before you can tie it into a neckerchief!") + return + if(slot_flags & ITEM_SLOT_MASK) + undyeable = TRUE + slot_flags = ITEM_SLOT_NECK + worn_y_offset = -3 + widen.Scale(1.25, 1) + transform = widen + user.visible_message("[user] ties [src] up like a neckerchief.", "You tie [src] up like a neckerchief.") + else + undyeable = initial(undyeable) + slot_flags = initial(slot_flags) + worn_y_offset = initial(worn_y_offset) + transform = initial(transform) + user.visible_message("[user] unties the neckerchief.", "You untie the neckerchief.") + +/obj/item/clothing/mask/bandana/red + name = "red bandana" + desc = "A fine red bandana with nanotech lining." + greyscale_colors = "#A02525" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/blue + name = "blue bandana" + desc = "A fine blue bandana with nanotech lining." + greyscale_colors = "#294A98" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/purple + name = "purple bandana" + desc = "A fine purple bandana with nanotech lining." + greyscale_colors = "#8019a0" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/green + name = "green bandana" + desc = "A fine green bandana with nanotech lining." + greyscale_colors = "#3D9829" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/gold + name = "gold bandana" + desc = "A fine gold bandana with nanotech lining." + greyscale_colors = "#DAC20E" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/orange + name = "orange bandana" + desc = "A fine orange bandana with nanotech lining." + greyscale_colors = "#da930e" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/black + name = "black bandana" + desc = "A fine black bandana with nanotech lining." + greyscale_colors = "#2e2e2e" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/white + name = "white bandana" + desc = "A fine white bandana with nanotech lining." + greyscale_colors = "#DCDCDC" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/durathread + name = "durathread bandana" + desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." + greyscale_colors = "#5c6d80" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/striped + name = "striped bandana" + desc = "A fine bandana with nanotech lining and a stripe across." + icon_state = "bandstriped" + worn_icon_state = "bandstriped_worn" + greyscale_config = /datum/greyscale_config/bandstriped + greyscale_config_worn = /datum/greyscale_config/bandstriped_worn + greyscale_config_up = /datum/greyscale_config/bandstriped_up + greyscale_config_worn_up = /datum/greyscale_config/bandstriped_worn_up + greyscale_colors = "#2e2e2e#C6C6C6" + undyeable = TRUE + +/obj/item/clothing/mask/bandana/striped/black + name = "striped bandana" + desc = "A fine black and white bandana with nanotech lining and a stripe across." + greyscale_colors = "#2e2e2e#C6C6C6" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/striped/security + name = "striped security bandana" + desc = "A fine bandana with nanotech lining, a stripe across and security colors." + greyscale_colors = "#A02525#2e2e2e" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/striped/science + name = "striped science bandana" + desc = "A fine bandana with nanotech lining, a stripe across and science colors." + greyscale_colors = "#DCDCDC#8019a0" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/striped/engineering + name = "striped engineering bandana" + desc = "A fine bandana with nanotech lining, a stripe across and engineering colors." + greyscale_colors = "#dab50e#ec7404" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/striped/medical + name = "striped medical bandana" + desc = "A fine bandana with nanotech lining, a stripe across and medical colors." + greyscale_colors = "#DCDCDC#5995BA" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/striped/cargo + name = "striped cargo bandana" + desc = "A fine bandana with nanotech lining, a stripe across and cargo colors." + greyscale_colors = "#967032#5F350B" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/striped/botany + name = "striped botany bandana" + desc = "A fine bandana with nanotech lining, a stripe across and botany colors." + greyscale_colors = "#3D9829#294A98" + flags_1 = NONE + +/obj/item/clothing/mask/bandana/skull + name = "skull bandana" + desc = "A fine bandana with nanotech lining and a skull emblem." + icon_state = "bandskull" + worn_icon_state = "bandskull_worn" + greyscale_config = /datum/greyscale_config/bandskull + greyscale_config_worn = /datum/greyscale_config/bandskull_worn + greyscale_config_up = /datum/greyscale_config/bandskull_up + greyscale_config_worn_up = /datum/greyscale_config/bandskull_worn_up + greyscale_colors = "#2e2e2e#C6C6C6" + undyeable = TRUE + +/obj/item/clothing/mask/bandana/skull/black + desc = "A fine black bandana with nanotech lining and a skull emblem." + greyscale_colors = "#2e2e2e#C6C6C6" + flags_1 = NONE diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 1cc8af863b241..d472999cf14df 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -255,90 +255,6 @@ icon_state = "bumba" item_state = "bumba" -/obj/item/clothing/mask/bandana - name = "white bandana" - desc = "A fine white bandana with nanotech lining." - w_class = WEIGHT_CLASS_TINY - flags_cover = MASKCOVERSMOUTH - flags_inv = HIDEFACE|HIDEFACIALHAIR|HIDESNOUT - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR|HIDESNOUT - visor_flags_cover = MASKCOVERSMOUTH - slot_flags = ITEM_SLOT_MASK - adjusted_flags = ITEM_SLOT_HEAD - icon_state = "bandwhite" - -/obj/item/clothing/mask/bandana/attack_self(mob/user) - adjustmask(user) - if (mask_adjusted) - worn_icon = 'icons/mob/clothing/head/costume.dmi' - else - worn_icon = 'icons/mob/clothing/mask.dmi' - -/obj/item/clothing/mask/bandana/AltClick(mob/user) - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(iscarbon(user)) - var/mob/living/carbon/C = user - if((C.get_item_by_slot(ITEM_SLOT_HEAD == src)) || (C.get_item_by_slot(ITEM_SLOT_MASK) == src)) - to_chat(user, "You can't tie [src] while wearing it!") - return - if(slot_flags & ITEM_SLOT_HEAD) - to_chat(user, "You must undo [src] before you can tie it into a neckerchief!") - else - if(user.is_holding(src)) - var/obj/item/clothing/neck/neckerchief/nk = new(src) - nk.name = "[name] neckerchief" - nk.desc = "[desc] It's tied up like a neckerchief." - nk.icon_state = icon_state - nk.sourceBandanaType = src.type - var/currentHandIndex = user.get_held_index_of_item(src) - user.transferItemToLoc(src, null) - user.put_in_hand(nk, currentHandIndex) - user.visible_message("[user] ties [src] up like a neckerchief.", "You tie [src] up like a neckerchief.") - qdel(src) - else - to_chat(user, "You must be holding [src] in order to tie it!") - -/obj/item/clothing/mask/bandana/red - name = "red bandana" - desc = "A fine red bandana with nanotech lining." - icon_state = "bandred" - -/obj/item/clothing/mask/bandana/blue - name = "blue bandana" - desc = "A fine blue bandana with nanotech lining." - icon_state = "bandblue" - -/obj/item/clothing/mask/bandana/green - name = "green bandana" - desc = "A fine green bandana with nanotech lining." - icon_state = "bandgreen" - -/obj/item/clothing/mask/bandana/gold - name = "gold bandana" - desc = "A fine gold bandana with nanotech lining." - icon_state = "bandgold" - -/obj/item/clothing/mask/bandana/black - name = "black bandana" - desc = "A fine black bandana with nanotech lining." - icon_state = "bandblack" - -/obj/item/clothing/mask/bandana/skull - name = "skull bandana" - desc = "A fine black bandana with nanotech lining and a skull emblem." - icon_state = "bandskull" - -/obj/item/clothing/mask/bandana/botany - name = "botany bandana" - desc = "A fine bandana with nanotech lining and a hydroponics pattern." - icon_state = "bandbotany" - -/obj/item/clothing/mask/bandana/durathread - name = "durathread bandana" - desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." - icon_state = "banddurathread" - /obj/item/clothing/mask/mummy name = "mummy mask" desc = "Ancient bandages." diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm index 55505bbae412a..88a6144890a51 100644 --- a/code/modules/clothing/neck/_neck.dm +++ b/code/modules/clothing/neck/_neck.dm @@ -198,34 +198,6 @@ desc = "In nomine Patris, et Filii, et Spiritus Sancti." icon_state = "cross" -/obj/item/clothing/neck/neckerchief - worn_icon = "empty_bandana" - w_class = WEIGHT_CLASS_TINY - var/sourceBandanaType - -/obj/item/clothing/neck/neckerchief/worn_overlays(mutable_appearance/standing, isinhands = FALSE, icon_file, item_layer, atom/origin) - . = ..() - if(!isinhands) - var/mutable_appearance/realOverlay = mutable_appearance('icons/mob/clothing/mask.dmi', icon_state, item_layer) - realOverlay.pixel_y = -3 - . += realOverlay - -/obj/item/clothing/neck/neckerchief/AltClick(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - if(C.get_item_by_slot(ITEM_SLOT_NECK) == src) - to_chat(user, "You can't untie [src] while wearing it!") - return - if(user.is_holding(src)) - var/obj/item/clothing/mask/bandana/newBand = new sourceBandanaType(user) - var/currentHandIndex = user.get_held_index_of_item(src) - var/oldName = src.name - qdel(src) - user.put_in_hand(newBand, currentHandIndex) - user.visible_message("[user] unties [oldName] back into a [newBand.name]", "You untie [oldName] back into a [newBand.name]") - else - to_chat(user, "You must be holding [src] in order to untie it!") - ///////////////// //DONATOR ITEMS// ///////////////// diff --git a/code/modules/clothing/suits/chaplainsuits.dm b/code/modules/clothing/suits/chaplainsuits.dm index 8ac269fe03a6d..03bddfa26507d 100644 --- a/code/modules/clothing/suits/chaplainsuits.dm +++ b/code/modules/clothing/suits/chaplainsuits.dm @@ -156,6 +156,7 @@ icon = 'icons/obj/clothing/head/chaplain.dmi' worn_icon = 'icons/mob/clothing/head/chaplain.dmi' icon_state = "crusader" + item_state = null w_class = WEIGHT_CLASS_NORMAL flags_inv = HIDEHAIR|HIDEEARS|HIDEFACE armor_type = /datum/armor/plate_crusader @@ -185,12 +186,11 @@ name = "Prophet's Hat" desc = "A religious-looking hat." icon_state = null - worn_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' + worn_icon = 'icons/mob/clothing/head/helmet.dmi' item_state = null flags_1 = 0 armor_type = /datum/armor/crusader_prophet - worn_x_dimension = 64 - worn_y_dimension = 64 + worn_y_offset = 6 /datum/armor/crusader_prophet @@ -218,12 +218,10 @@ name = "cage" desc = "A cage that restrains the will of the self, allowing one to see the profane world for what it is." flags_inv = NONE - worn_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' icon_state = "cage" item_state = null - worn_x_dimension = 64 - worn_y_dimension = 64 dynamic_hair_suffix = "" + worn_y_offset = 7 /obj/item/clothing/head/helmet/chaplain/ancient name = "ancient helmet" diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 9cd3a1486c1ba..8803ff19701e5 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -736,31 +736,36 @@ generate/load female uniform sprites matching all previously decided variables standing = center_image(standing, isinhands ? inhand_x_dimension : worn_x_dimension, isinhands ? inhand_y_dimension : worn_y_dimension) - //Handle held offsets - var/mob/M = loc - if(istype(M)) - var/list/L = get_held_offsets() - if(L) - standing.pixel_x += L["x"] //+= because of center()ing - standing.pixel_y += L["y"] + //Worn offsets + var/list/offsets = get_worn_offsets(isinhands) + standing.pixel_x += offsets[1] + standing.pixel_y += offsets[2] standing.alpha = alpha standing.color = color return standing -/obj/item/proc/get_held_offsets() - var/list/L - if(ismob(loc)) - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - L = H.dna?.species.get_item_offsets_for_index(src) - if(L) - return L - var/mob/M = loc - L = M.get_item_offsets_for_index(M.get_held_index_of_item(src)) - - return L +/// Returns offsets used for equipped item overlays in list(px_offset,py_offset) form. +/obj/item/proc/get_worn_offsets(isinhands) + . = list(0,0) //(px,py) + if(isinhands) + //Handle held offsets + var/mob/holder = loc + var/list/offsets + if(ismob(loc)) + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + offsets = H.dna?.species.get_item_offsets_for_index(src) + if(offsets) + return offsets + if(istype(holder)) + offsets = holder.get_item_offsets_for_index(holder.get_held_index_of_item(src)) + if(offsets) + .[1] = offsets["x"] + .[2] = offsets["y"] + else + .[2] = worn_y_offset //Can't think of a better way to do this, sadly diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 947d4163f75b9..d8c49e9a55811 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -32,10 +32,8 @@ var/custom_price ///Does the item have a custom premium price override var/custom_premium_price - /** GAGs recolorability ///Whether the product can be recolored by the GAGS system - var/colorable - **/ + //var/colorable ///List of items that have been returned to the vending machine. var/list/returned_products /// The category the product was in, if any. @@ -348,9 +346,7 @@ R.max_amount = amount R.custom_price = initial(temp.custom_price) R.custom_premium_price = initial(temp.custom_premium_price) - /* GAGS recolorability //R.colorable = !!(initial(temp.greyscale_config) && initial(temp.greyscale_colors) && (initial(temp.flags_1) & IS_PLAYER_COLORABLE_1)) - */ R.category = product_to_category[typepath] recordlist += R @@ -907,8 +903,8 @@ switch(action) if("vend") . = vend(params) - //if("select_colors") - // . = select_colors(params) + if("select_colors") + . = select_colors(params) /obj/machinery/vending/proc/can_vend(user, silent=FALSE) . = FALSE diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm index ac09f4ba2563c..aba6405de02bf 100644 --- a/code/modules/vending/clothesmate.dm +++ b/code/modules/vending/clothesmate.dm @@ -28,6 +28,9 @@ /obj/item/clothing/head/beanie/rasta = 3, /obj/item/clothing/head/beret = 3, /obj/item/clothing/head/beret/black = 3, + /obj/item/clothing/mask/bandana = 3, + /obj/item/clothing/mask/bandana/striped = 3, + /obj/item/clothing/mask/bandana/skull = 3, /obj/item/clothing/neck/scarf/pink = 3, /obj/item/clothing/neck/scarf/red = 3, /obj/item/clothing/neck/scarf/green = 3, diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm index c1cfd17d79391..85812f4d13bf4 100644 --- a/code/modules/vending/wardrobes.dm +++ b/code/modules/vending/wardrobes.dm @@ -27,7 +27,7 @@ /obj/item/clothing/shoes/jackboots = 3, /obj/item/clothing/head/beret/sec = 3, /obj/item/clothing/head/soft/sec = 3, - /obj/item/clothing/mask/bandana/red = 3, + /obj/item/clothing/mask/bandana/striped/security = 3, /obj/item/clothing/mask/gas/sechailer = 6, /obj/item/clothing/under/rank/security/officer/skirt = 3, /obj/item/clothing/under/rank/security/officer/white = 3, @@ -68,6 +68,7 @@ /obj/item/clothing/under/rank/medical/doctor/nurse = 4, /obj/item/clothing/head/costume/nursehat = 4, /obj/item/clothing/head/beret/med = 4, + /obj/item/clothing/mask/bandana/striped/medical = 4, /obj/item/clothing/under/rank/medical/doctor/blue = 4, /obj/item/clothing/under/rank/medical/doctor/green = 4, /obj/item/clothing/under/rank/medical/doctor/purple = 4, @@ -106,9 +107,11 @@ /obj/item/clothing/under/rank/engineering/engineer/skirt = 3, /obj/item/clothing/suit/hazardvest = 3, /obj/item/clothing/shoes/workboots = 3, + /obj/item/clothing/head/beret/eng = 3, + /obj/item/clothing/mask/bandana/striped/engineering = 3, /obj/item/clothing/head/utility/hardhat = 3, /obj/item/clothing/head/utility/hardhat/welding = 3, - /obj/item/clothing/head/beret/eng = 3) + ) contraband = list(/obj/item/clothing/suit/hooded/wintercoat/engineering/old = 3) refill_canister = /obj/item/vending_refill/wardrobe/engi_wardrobe dept_req_for_free = ACCOUNT_ENG_BITFLAG @@ -149,26 +152,33 @@ product_ads = "Upgraded Assistant Style! Pick yours today!;These shorts are comfy and easy to wear, get yours now!" vend_reply = "Thank you for using the CargoDrobe!" extra_price = 50 - products = list(/obj/item/clothing/suit/hooded/wintercoat/cargo = 3, - /obj/item/clothing/under/rank/cargo/tech = 3, - /obj/item/clothing/under/rank/cargo/tech/skirt = 3, - /obj/item/clothing/under/plasmaman/cargo = 3, - /obj/item/clothing/head/helmet/space/plasmaman/cargo = 3, - /obj/item/clothing/shoes/sneakers/black = 3, - /obj/item/clothing/gloves/fingerless = 3, - /obj/item/clothing/head/soft/cargo = 3, - /obj/item/clothing/head/beret/supply = 3, - /obj/item/radio/headset/headset_cargo = 3) - premium = list( /obj/item/clothing/under/rank/cargo/miner = 3, - /obj/item/clothing/head/costume/mailman = 2, - /obj/item/clothing/under/misc/mailman/skirt = 2, - /obj/item/clothing/under/misc/mailman = 2, - /obj/item/storage/backpack/satchel/mail = 2, - /obj/item/clothing/under/plasmaman/mailman = 2, - /obj/item/clothing/head/helmet/space/plasmaman/mailman = 2 + products = list( + /obj/item/clothing/suit/hooded/wintercoat/cargo = 3, + /obj/item/clothing/under/rank/cargo/tech = 3, + /obj/item/clothing/under/rank/cargo/tech/skirt = 3, + /obj/item/clothing/under/plasmaman/cargo = 3, + /obj/item/clothing/head/helmet/space/plasmaman/cargo = 3, + /obj/item/clothing/shoes/sneakers/black = 3, + /obj/item/clothing/gloves/fingerless = 3, + /obj/item/clothing/mask/bandana/striped/cargo = 3, + /obj/item/clothing/head/soft/cargo = 3, + /obj/item/clothing/head/beret/supply = 3, + /obj/item/radio/headset/headset_cargo = 3 + ) + + premium = list( + /obj/item/clothing/under/rank/cargo/miner = 3, + /obj/item/clothing/head/costume/mailman = 2, + /obj/item/clothing/under/misc/mailman/skirt = 2, + /obj/item/clothing/under/misc/mailman = 2, + /obj/item/storage/backpack/satchel/mail = 2, + /obj/item/clothing/under/plasmaman/mailman = 2, + /obj/item/clothing/head/helmet/space/plasmaman/mailman = 2 + ) + contraband = list( + /obj/item/radio/headset/headset_quartermaster = 1, + /obj/item/clothing/suit/hooded/wintercoat/cargo/old = 3 ) - contraband = list(/obj/item/radio/headset/headset_quartermaster = 1, - /obj/item/clothing/suit/hooded/wintercoat/cargo/old = 3) refill_canister = /obj/item/vending_refill/wardrobe/cargo_wardrobe dept_req_for_free = ACCOUNT_CAR_BITFLAG @@ -192,7 +202,7 @@ /obj/item/clothing/shoes/sneakers/black = 2, /obj/item/clothing/gloves/fingerless = 2, /obj/item/clothing/head/soft/black = 2, - /obj/item/clothing/mask/bandana/skull = 2, + /obj/item/clothing/mask/bandana/skull/black = 2, /obj/item/clothing/head/beret/sci = 2) contraband = list(/obj/item/clothing/suit/hooded/techpriest = 2, @@ -219,6 +229,7 @@ /obj/item/storage/backpack/satchel/tox = 3, /obj/item/storage/backpack/duffelbag/science = 3, /obj/item/clothing/suit/hooded/wintercoat/science = 3, + /obj/item/clothing/mask/bandana/striped/science = 3, /obj/item/clothing/under/rank/rnd/scientist = 3, /obj/item/clothing/under/rank/rnd/scientist/skirt = 3, /obj/item/clothing/under/plasmaman/science = 3, @@ -250,9 +261,9 @@ /obj/item/clothing/suit/apron/overalls = 3, /obj/item/clothing/under/rank/civilian/hydroponics = 3, /obj/item/clothing/under/rank/civilian/hydroponics/skirt = 3, + /obj/item/clothing/mask/bandana/striped/botany = 3, /obj/item/clothing/under/plasmaman/botany = 3, /obj/item/clothing/head/helmet/space/plasmaman/botany = 3, - /obj/item/clothing/mask/bandana/botany = 3, /obj/item/clothing/accessory/armband/hydro = 3, /obj/item/clothing/head/cowboy = 3) contraband = list(/obj/item/clothing/suit/hooded/wintercoat/hydro/old = 3) @@ -370,6 +381,7 @@ /obj/item/computer_hardware/hard_drive/role/janitor = 2, /obj/item/clothing/gloves/color/black = 2, /obj/item/clothing/head/soft/purple = 2, + /obj/item/clothing/mask/bandana/purple = 2, /obj/item/pushbroom = 2, /obj/item/paint/paint_remover = 2, /obj/item/melee/flyswatter = 2, diff --git a/icons/mob/clothing/head/costume.dmi b/icons/mob/clothing/head/costume.dmi index 5c7146529650ff62ed8c06a03348c762835bc3d9..13a743de03452f8c1c0f0d003f42de085453c538 100644 GIT binary patch literal 47102 zcmcfoXIK+Y)Hn=>j?z`6cNG;Wih$H0AgCxssv-mgq!W4%p@Sk|1(6a2DbjlCxW9=E~Gojz9
bDa-T8^HZ*@QpT#*_g%AD=$msF{wzOWt7l;08F)~lee3nW10LJ%gH{b6 zh-U4xMqOs%rxzl6o{JPDN|eeJ``#kh*#wl0W+HPIcthj=j=9LlNfn zlFQ%heC(!UBY*nn+FqJ-2)=30_WIhK$iK^M?umVqo9joYF*>eU@__S6n6tKC@KMwk zTH)PxMXQ->Ds;xbN*N5Y6hHnV=Cl}Coh!cnDWRnxQDl!b8n@1 $rXY+z%dh7!%GmwxpgcFK48_G(V%@<8FG*LOo!MiiT?*m(u9 z)p(UrU%G5Zg+0k59{Wzwn+594OHG2GJS!E`(fG*uI`$jebwWSfHOZ;$8<&&3FY0rs z U>d* z?>?;iCWx#5TXeCXSa`9pnVdAb_vqhabMJ|`k=H4YE=&JRrn}ml0BJg${t eQ_URzNSM2LlA`6es=+${i}@3$?sY~o53v2U&2Wg7>y?y#rl%oSKW++g zPt&~U`|4#YUjhGQzy{>i$n!rP<9=(nz5YR$*QJX2@A}1J(`t)hagjN$vbn+AP^i3; zAh3`@H+TBO6PCZSoeDRyDlfb=kNm0f!`ZN_=Cd@!XuILx{VMykcF%l2!Rp%wUlTXZ zEn2t#U5c&t?KT|!)Up3m9q7VOHmLSiN5)3gx0?Q$|4^B5EhtChrAon*m(mWOip7<` zrTpDPB?0AArVU0%xQAS<1vcdC%O82xtI1g4Qvz4P5fqX`OY|G5 91Ws|dSrCrNrXJ3>( zD?3_$QT*V(^jkBX644lG=6?SA_gUMaKH< +Hv{)e{ct=fTjeiJz~irxrdas2KATlIkAlT*S=6#%&I=__g!g z8sun2w{xWayumZ;y3`FW$E#Og%{TjZ7Gu~`3Gk_LV~ap_!+Q4_@l$2sC`KU6RWcVI zg`RPOC2?pR19o z{dLq)`Qqflm>uhiO0jE6DcDhfNnePsv0!!8PR~vZph<4?TGFx?MGD=*L0kj?Y2OZN zC;k1AhnMdhX7uEWZUJ5oeRKTh4}VMO>+35jE!{RokDqAl7?NCiug-ApFU=QXW+RkR zHdw~>b+w;7y{?1}I_2lt3RT>Vy|#f*Do`g!WwmQUfM!^Sp`CX;r#AJ{IHk>(7JZ z9tFKx9CRgD=+$F_>5qc^yw=i7E&74Ejzf3La5Rf=^Aj`Iv!=)21!85#|HK_)A74!W zl$TiuqILuLe01N_vz+y%xT-2DI9y*pQUChD(9ka?k+7WFa03H&Ku%4~cz*cf$8W0% z61wj=O$we+{~$v}(1+~o m?-0U;K|SAL;qMbMS$sv0sU~F&nX$ V2Tv{WLI)&E!wvtM&MJE>#KsD*4 U%Q C;6>FI>Ewb#sIqA{BPVfb}CyO0kf^|589_isKxMBgV# z`;I7v>9){@>MC?wsM6(ImS+#?BIHl|jlwoY#a5T3)YJsWjE%M8Dl12gxP-W`c6N1r zVQk;q^9m~VC>IeHE|=cxZ*F#L!N)cM{bCk{Vq?{5)3#=&G=G9qQ$N@>B0uk#jIkRw zNRQ|b^p28q!2_w%TI6Z2mu1CRi=Mu8^dPUO25oN=6Z<5Me_8t*U7UOel}2v~t{$u2 zs3x8kr LB9nT6WsbPsSr8Aj!j( zDC#rD?mfhomRr_bL!?o@O+Qi*q&;wJn~L{0+rhW*9k@sor__mgJKS4wv6inqBl(X$ zT~*{JQgV7zjqY-{a8FK=XEZB;O2(Dc1pq`apoQa$wLKm?Ev>R%2i(1THwO-12!8(Z zWu|-rmGlJ6M>J@YXGR7Gp8%56K>*Iq{b?uOIM#kDADZ5wQ+?IVeB1sv>-xKznU%gz zAHHq`X*N>G#4ikcm6dYdNb5ivKygV)mD5OeHqpu9O1%fFz=74_7$YrO!k!Ib@Do}> znoN7RdQS0c<>uvv{Ci+!m9Yw0)Ib#zD70l7O>x63D-KsACGm}=)z#G?ea`K2Eb>Ca z#U#M1h-Utt%_tv^G^vSe5q}D@m*j8!;R2xUwqCv+k5->lCu-K|u~-|f7Y2;b( iC4DOlbO9yZrRLi79a6r&g+bl&jbi-lTPQBW|+OJboNb=xE|p!&J(9> zmnT^&X4`X3 LTIM zvf7Q+x^)c=0(dkO0f=bXZDwf+#Qy$#!z9yVjEmgLFjTHtaO!a~%kfb?8x|HWJ*%qX z-Xy!hj;Jmu g;mgo8-Ng8rmXfpO;6*=?ic*`Jt*gv8()E;7`N2(SY}nro(^| z=Fse~P9XpC;G;srIqhn*71lj$ILU1V7W_H|l_qA1WA%zpdp|xib51O#ns4!J`IlmO za;vJizJLD?5ERi2J=*W7I({qld8qM7QMd#Y%z$n>dA YRZ6f1Q4FMiP2S7OT0&Yer|$4?(M$_I^bg;A zVo|2)fl1(!2qAfyfzqsmqS(5;WTH1VHjFNZt25YU=|T4LL4U@_zc4NK7m9tXz>}x{ zAXm*NQnKH#?N=AHIT}4jSqb>Xc!EHeHn!-_FYHOB)B^y@Jr|Ad=JEqF*GtZKw8f$$ zV;0|cC~|^@Bgd{q*yED<(Dpb&C-}*|J+g66%KQ@YWlQeQ7VFzf1PxB-LaKDV?DHt9 z{6%y=*z{ qkeuIcKxk^d$3@38 zXBew(7+g--n$@9qn)cA}WA-=E9t+zrs`x%}z>{OWghCz??>K{`Z9cMn5Z &QI+@m4i#Q1PQFW=K8_YUrA<~8{<%8**tHV`5>d;z N zxk1$ta#X?OA%D *?v>hWTWS)x_6~8GYJYu>fu%e!$qc2CA zuHsba5*5U81v<5_uc23mX+IV3Q57;aqz#y!re~>e@s?9KYIb&XY%DS}loF8}8h*Zg zzaDX_`R&KwF0X9%$TbZX_v}c>;gHg#Amjc={g^^N=R>6~8s0xO?K%#?!~H-rCeRmL z*S*i$Qq)P;XP?t!wz mb;YEvI35Gon7jufFVUDvEul^?V-Qod6CUWCfaaZyx zee{jfZ`ykwUJL`q+dE;W2Z`_BbC$lSD@GDOp7= MNP0!hlb{A#}Ig^SBieL%{)cdXO5?=+NA#oo=Xdt6*}a$IbW)&O6! zvXUw*D{Y)iyE;2{0s`tC?Sk;T^MD8(2C16zkCQsz+ly54%gV~KM1;2uP&jl#LtCos zx)`09JzZQj>tsudW-OpVm_TI=in GXpT%8pB&u!2rPw)hBR6DdpjPlto<({QN)eB6vmh6HtA{+!WPTCy~A$c z44xICqtmv`D#sN(v9RFzqp(o5%N>MyJhY^wt`2DzE2Ww%U+*}cU%71&%bD=z&3)6) z%laHz+S*+F{BObPNAH&>TU~_qsM7luTja^f$uV{Ouw9m{K@z!xNkkL~gaz90A54{5 zNlvre<;=V)nro94PT2fgGos;pu3#5=TlU<&o!areK1rD6(exevEz=LFsc}U`*VmUu z2O}+*mrh&q^0fQX?0Pxqr>3TIb8_0 n25A17i^yUcjJU%=;dSy7yoDKY#uJ0FI81!xUqjhu0l?@NDgk9AVY< z!E_1U@Eg-wmF0V?(y|;be0 tI0x%5aD6Bfn>LxA+*jz17JGRIaFOdIP9#bq_x`VO z9{VE8gGEm{!RL{BP*HJlasDX3!J`XY|Nk7u6xI!^=rgAUU^`X%ygrQhUI+&Rbo)z7 z10oW7AJzttNKYKDc=Fpq309Q!-iI#>n=}BmwY3Rp5)%7E<+BIa-rnAvB13vWij1nL z=X1p=-YuMnbwK+vPs()DPUNUYGSn*t>L{+`qu`FAYr&xA8Kqp~*{*~c6g}1pdm?x? zwqN|Bv{ik==~`8hTWLc+`9tr+cNjv}Ja@PAM#h~J(+ad!edvgc#8(W)IeH!!JtjTy zo4;_V@kJ(d&7X!F;3#mjIJq$szpFR0SmPnP7v8=v`E<#|iL<^sU|N&^g2=`As3N-h zLBPm2g@Ng6$Hd+qOF;e$Hv|vgn{%FpH|}*}Ki^mmX?bhJ*Um_dV|Y}yzzZd9TD02p z2G|qQ78>=eNadPhYq0u>U>lS9+g946XD xe@`{9(w4?~U*Tu~)!|g|7buJBFUh!p);gM-n3hmn9nyPvW?k&Y4Fea* z$=}?vpVN+0Kg)hvx*$SXfFFm`8`)rGF=0}z-^(smEs4!KMGY-Ad@mc%_b8UOy4Ec{ zz?!MJ?7AKd^9ZFGNIh5Oo^H2M48^Aay|M!bs;g$VGJ2pVu#w-@j zOlQ=fL&eT57n;;`f>zHlo#rZ9Or<8Gk3y@`Vju~%K;J4YO#uV4Dga-89ZkSaWkqEe zi@@|orAp8YHx38Z(`=g58pHt;;HH$+zF%-{>htL{qAVQ9GoIlN8}cxIPEpoWV@k-f zw!^IUK0NLEhc_7a_nS-^vlIac1G?!V?$NZ5r?iG8OeqaUVkVI`*!>0~&UCV2BZQRt z`BJ>^u*L|aB{b8#!{jMnxIcYG{O&O`(Lhko!zAy}3H@d{Xd^l0^GLUU?2~nuL)2Kq zErF6&vJ=Wij!Jl!1}I^Q08vFX`alhz%K(Uc$J$=#x`dOMqFOiKZ|Vo8n<(@d3#OiV ztDeVF8mObFeKZ^Nmy3Kk6<2Yo`$|k2%H%-GF`YCHNPW2kXAhdWLx&Cj$4YKls+jE@ zfhwJHpEXmZeTB9TM_LU|4&wU}+THKyo?U2xzWS{^jO5mEB*`OZOc3$|`+<+eIk}gP z=hO2G*{b19ilY>Qo%!9Z4q1NhjWHN4e!kF=>E6E$!~I2RXghY~;Z*%NMBpfV_RlT> z;8i%}(I^if006_vM70zs(?!0hd1C&LA39d(fpL}2idt;9_odTiu}i!RjZ0P)w!bJX zv?t;sjYm#j?_ARlhVX^2u=@Gx3+0M0Tfe6FucHA7H|}WPt@Zy4gf >y*Vh}%4I)68N}q|;B>IYp#W;#+{ Onw|Z8H7^}6I8cs_OmVtVP zo<^XmuK>`uZ!iWOJX#SnG;O}5C490(RYde3JSQh5tH9``U8XDJ2O0hSk4-IL6ED!? ztIZgTX6gISX5zNCJ~4A}V2#JaUigc!CMIuk{X74THWOn1uAcZkbo7(hLntXIO=C2_ z{d&6vbgu36zTTcchwW~mVTQN=XgK2or(?Fy8ax7GN1i^XYw{AKwKRWw6&DA9_n(}; z`Nm0Jg^AwW609q29tx)#=KYV?FC $?T{>j-j4P}4ugcpgWa6r3@%WMKj7RSB3! zhoB@)@g1k3wwHKR7Ca5VF)!NnU t*IUVp#fl7DK&akuyXsJ1K| zQRPqMmV{W43T;d%C-Z0Ux;TG-KT<2^< Glaq;sIPj*`mNsIHU zwCSZ5 XcK*w)V|U142*z?-MK1*(A)9K&$& s4s4=V(OqbB1#&v>h>BDtoq3HsU|1sbAqa z)VX?H7q<(eVeTlgssUt F+J52_%--2l<^4e}Jo!NKehUVF zbY)IVrEUV@j&&6GoO4gxu@KUXY1i$+D%0MJTpNC^6RSf5&}yFj?n7b8BqXEtkE|7Y zAQZP<_o;dg#Xv>;G$n1@THHJd3i;H}sdfnuh0#?ihYg+g=x$@^1i1dnVCZc0_Q|_$ zBnSv)fi}|?HN6Itz84>5wEa8T }p zQ&szCgptC IanA#bb|SQ{Kmu|N&phCNXP9OBT5B1U##8gm?1|k;@)eJ{IBAD zNlxx+fS1F458Cf=)}@l)_)O=7^KUu_>_NJp6P`6wGP)2LGL*JGQ#r1xPcGhnm(s?v zFB!(YUG~7)p80ay=xaZwJGNsi9=g=#`J*W *hRHgm3S l4q!!(PyqSp7Cdx)#l zDT&f67N1Ao+#=S||NY%(7u1|ngB(ELayAKI>Xp-yllj@QN!dA1O(%{FXaW4Lv%~6M z**yDdNcuiI&XCo38$Fg9{K)Rube{zv&TsipWWz<&!}hB1r?u2e$+@B(+UhR52d^ zY1`!0=s0w|^SSs%XlZeHuk9?~fItM~OERM8D$RO9yxq1}&nC4wU^cK3!T?9y^ddZ{ zzuLuX+Vv><31|uPxzi-h4S>di;PHb40o5m31;-1UYS+r<8Lgh7M|S#1W!z-p>C`D{|TY6)0a~@B9)olz3BU zz3}(exuwWc+Q^6V?hmnUr~t!x57t!zJjQ5JzCD0(1u5C;IkAnJSbkKb?U(x+eQi}w zyt0MLr$(x%e}ouh5?J`S;6_Y9{0Q~tnrQ{Ft_42VGlvPp!jb_6x#4)Gy*r`7G&$jv z3N)gr=VWBbe*%jxb)+;j!_hRyONZKvYV6>&Gl3dEVgbwdy}*|F#ojhBSn}KK(&>!# z!kZc&)|D&8(9=+R!}$G~DuXbasm(f|pB(;V-7)KU+p@6Uo`$NL=0CLbsVDGT`_!+y z3vS`qy5w3J#;y`muR|^bK{_#QQ|1ufg3KLdfd(Z{bZyVQ*1j;Z@bX^4P7+G#=PvaB zFzuIkz;hJUf92A2py _51 zZGh1l%1GzkG!po70dAUN+RAKXjK;EC`T92oHMc9cTdOFWzJ#GkOZUEfH;gla%9yOC z@Wdv1KT57^Z0;)DJ|vm7mw_8@AAg+XfuVdAFF%$2<~E6Av?9ShLH^b)sP{GMPWA`2 zhs0K!)#SkMuBSVtXbV+SWL*mzWWHg=u3>wYVur+)2;EF+!?=}c&Qi*DIm~cG?o631 z!JBh*UUDNEAtA;&5~s!C9ru5z&M~|qX}ZPlDiR1Qc{dYgtkKAT4d#w!l&I66*4T50 zPcmAQzu-GGi?N#lLpa~S6=yz*_W0XH_|Ps93L?G!1@UAtx4rD?rMy#S9r*$Q!d#w< zwy8wm;2W`u+D;;^RfS!)$N8G l;A3GHc=H(b9LU=guhP~25*(j) zhjs7^@Sv62H{%2)(iF5%&pCaJ_5M{zcm`VKw*@QYLsO(Q%!b2Xj9r5sA)YsOoY?n} z(J+FVJAmu?Du1&yKT<9nq5?P z*gp=V>7Ga2)(08NtPPZXjje0Zz(bKF!=u8?B!Ac!TrGv|O3;ioqQQ3SC%nQgMD%_u zVuyEw9O(SQxPpkjTx!%zDJeedJe9#%6$mIS4i{x`P}Rx;Vjd?J@C?<3T<+Z^%u%fI zKzzfZ SzrD;wRE}Em5O*#Rn zc8oaB8i!T7_SZtWX;geULE!vOId+rcH;J!bcy6 0V#r?;qeo$l=Z&aufL2Ek~XJ_YBTyXE-P{!{sjP z;L!Ys<$Vp6_cMK(Mc4t4KFuwcQ=$nPWj7p>v4gwHS<(s`;PWz+eMwuVaTWr+clwjv zHrno7x6v21ov>s6R#W=BEf}VfIU!{kv5B 3;*T0NrmrCPX2iG|v$Vp=>3n5?Te~LAmH)7I(MX!H- zo7Vh^wqW*0%a$E`eQV$#oE#)MnOCq3Q^5|q{|Du7I k z;~U6n^3vI_UlRChI!w`|0J; za1I%JX(+#$ojjQ1O0jqjqxqUZ__e!SI%@{xOp$d%E{$!ohXu5X`c;%HOTG86??5)} zgtm-4Kk2BU^6w3LD^f=by#5zb@n4}KY18n~t{2e0#evxTj|fQN;=4QljLJk3L~3jD zo(ACQfO|!q#a$ zGvpZ&@&i;~gHEKKeyCo`ND~M;9|?)A?65(Pr-6c4`pl=Wm;kfz8*#vRYYax)HPZ5I z63*}A4H;iO>l18s;PscJD}F0_`5{idOf`Tb$9xjMsyI*7?y;K*JC|{jN}do#TPmS% zeQ9VQ`wKB4cL|U-A)skbCuD5~ L zZ` -MxH*xGaW zC^N|%3b;9n$v{*YnDGW6cHlOwmXnuf8sOnvZzeGjX2^9OJ5FQR_s-!8@%k3{q=(>> zItO(S{MB@D)(-Z?tVRolXlgN@2JRKjAxOc7 v4}s@Yh^t zLSV?B@vZ`>T?}l(*t)RmM6R7j?rnxcKiPk*X;Y1P(MI{7)zXNUF+>lpkv*Q?)ULF~ zwbebCs>USBsntD*T}W;!!AM2yl!`Db?399gkYJ`~B+VihC&(^^>bU&xakr6oo0ei- zkH51NzJR%CqZE~|(epnJ1e27QP6}CxwTn5G&>?dpEG9uNmy7QL3!n&7q-loo)^>v6 z5B;V_5_ly6_Zmb1FH{7K{xpO13NG<+Er(1nMtqzig(G+I{I;qhF+1gLuBSKGHx0(# za7xhYAIWg3%-T}b0L4+e}-WqVLWJ^@Uj1smB@y z!;?8(14)|9O94{`=S9SxiC6DCk|do&)Xw=rZwowliuxdU``HHb_v-M^Tv)Y==Myv$ zqnDy$mBVN?wJcQti#h`uMZ%0?^aZ~_5kmghoX!0!PvGh(!MIoi&D;i-?mh97SNw(! zi9FOVk|2W$O-~R|68Ij6;hJ+j^7!)%HU25(@X^D4x~h3xsGA AKjm?TdlgjO0t1(Ow>^r9+Nt1DJ>1p9giotyQ!&ceX6Ez zsHu0?dUnh#NUY?VKL4&&fw6}?o$ys8dpj_D%MjqX>Fmd7$QLE`B4y+2xuaQ15{y-E za8vTj6!_GkF+rJhg&)Yr?jF014|ke s;++0HzVrnQ38*QTiy+2wrJuwYN&s!@$Zrb4MY!AQv=77 zpfonZ#A-t^57DKbDvIoeFGVs`t8r_;p``vsgm`Dd@2?DfyocisJ!s%lUFsw}?2S<7 z$*IGPpX-GE$P@Kn+^iTsaNTCl%PYVgr-vETv-aNneILO2X{0Y;>RVW7eNBLFcVqax z^H*q~ru)lkjv)4mWmIEn&+3RkN#s7@b1UJu?jn7}q_7(>9$7`7{@CVZfRMJkQ>*Z9 z+8R+2j=S`vY)zl7L@R}=%QbfmcR>Ta1Of&9wuhEGMKZcB9E{a`5UZn{rd?F&fc|KM zso+PP*G1C5pNHsb8&V ?R9MwN^Y@Yaoa}Yy3!zP`}*BGg_R6e|P9T(gF+icm;xdMyvDC39HX2VsC zEg)e0ugIeZa#IaA)iCyWp7#v8qy^e7O@Fi^Vms+yiL0uopyz_!+4;G4SLyK!yZ{a7 z!$%f?-Ju3=KB{s4Md_HoYI>QqlKoBu@q(5ay>|;H5M8jI;koN>{tO*vwZ>n*c5`Mo zGHx@nUh*r3;~!L+rReW%uX89}9oF_|VEO9eY2(AQU;q{F+$P_)9wrc;Bf?Uh#W4`V z*Qq{<*I8GYTN}m|@Gwt#QP_|F*Q8(BZ@#G!!|jY?5|Ek&!`XVs4c%_X&u}!gR5glB z9HqLctNNlLC1nIS3K?H&3S}PHCFp1;Rp)71HeE)BSp^6) ~D1Irb&t(7PglaVK)`kpq!pEsoMh+cgT-Sc;a~D#+n#Z z*LnlR1C~awiRnC?9QMz8FrK(p{370yY8dPXLM3%~qqWAI04pCj=R`uYyD+oA5up?e zxjo5_SC=ZJH>jqghS3Z*02wGm$<~WZIR6~bHT~oGI>1-QcRj=^nRa=W9Z5z~{tm+l z_rotSpI-xH{;xG1nh9!JwobzqS#U>?T%sopHQ;jrXM#Vzfcv?y+D l&e$?!Bys9H Q+ z%H4e6+p 8 z*6h%{%POOEg95iXJvo3z*TJ7tO{NI4?f0 @AqkS<3b_mWWD@ zTmaAGrNAQ9&v2bxyM*kc63yd?6 R&z#KCA-LPaA-1Rvw;%kcp6Lr0m!4(aX zJYtn&VJDhSc68t2naa3)pufhslUZS@Dabb`kYi(JtUxAkp|g;?rjjbScVa6F2?0j) zmk;0NHUZn(BqWgctrRZ01d L%ww1NqvwYfIYPS9*Y+Wc^^yv!QGoF2I#lcu0n*O6A&Pg)xJHcKVZTj{ZE zKL{QDBnC&2y=a 1m6chUyf$DzZ_K+sxaY zN>x9G#~T8yOP^@c6K-*|!{39EB|(2bH=tlfaz<@K#m!8z+_0GXmT*wyfSP{U!MWa6 zk76|8E>j3J_2O|zcU*@N8cjAMEwzx{QJTAw2k K?9zeR#2Jk$pq zNlp~2T!;YbMHq2)Lf97CgmRtakjN8CK*OX}V~tNHqZOnCTNoLDTA;N67czyAG%E~s z?tYFvxu_{5FPAM_F}E+{^C7RbBboEG9H}XqLibagUg(?oikeq75rs`2f1rtEsu1k? z;kSeM4iW(fBKQ5SD?oSMb|a^n=zD5cI>)^u&0?;s-+{Tb-x3ITMs;U;rmP_A&C4#X zC~-^>iZYLtp!>N>rm2O)Hy->_1qMvvr-Ti0{;?j v9&>t)lho`}FTLGl!L%92LsZF{{Y3vjSrE&_FkzQ8V+A=Y_K=SB z1`1a-!QIooU^NB5na4<5F3l-|(2>(o+^Jz=iSxK2y+3>T@WP6vdX*S_-P2vXw$v1* ze=q$^5`p6 N2 z=@9A`EbLFiK}k@XdX8$R=7%1C7jqe!s+RDS`s2|Cb0ijXWExA}oqZsTbz9?zZEpBg z96+xN89&ZAf*SVxHOJBRj+5Hk;Gr80xzY jkH7yK6J32)T607bQaAf00Z?5 zQkHEof>2e=0A!T(E1p_Kvc^M|r3n(8$+_5D-ei)a`=tdxpgYc8L`-{f}jV;5oOT z&IfrqsWU1wKCa+U+L^8EO-u# zGgS}jWp7|1BgHwt2vcynOUxZ}g3Tc;w#C =KG4RLQ!quIkiY+ z*ZZHZ0!bZmEQJTr@5$j@zl=(-WywMVTwyMBPDyWy&@_z|OkCg^pbBg*L{+#_2*X$w zeuvi`M<>l}MT)_|=YEfK-8R)&LYbja8u`R%=eEhI;BKZq$eJ zPp%lMicFKsKQ32>go?lK5#RCbX?d(wdCI8P6TpoNv2NNi7ruO*a|`b;bZGa+F9{0v zFJ;$C25W8;OA@k>GvxC8_}J $i` zCXKIWn;&cz4DsRTZc9FJ>PjPd+CT7lB^CaUbaErWf1wS5_7{~