From f82f9499639729e33284e734f97300e4a5c90ca8 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 11 Aug 2025 17:18:17 -0400 Subject: [PATCH 001/140] Fixed docs typo --- CONVARS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONVARS.md b/CONVARS.md index 528fa3cd6..29e0db501 100644 --- a/CONVARS.md +++ b/CONVARS.md @@ -300,7 +300,7 @@ ttt_vampire_show_target_icon 0 // Whether Vampires have ttt_vampire_damage_reduction 0 // The fraction an attacker's bullet damage will be reduced by when they are shooting a Vampire ttt_vampire_fang_timer 5 // The amount of time fangs must be used to fully drain a target's blood ttt_vampire_fang_dead_timer 0 // The amount of time fangs must be used to fully drain a dead target's blood. Set to 0 to use the same time as "ttt_vampire_fang_timer" -ttt_vampire_fang_heal 50 // The amount of health a vVampire will heal by when they fully drain a target's blood +ttt_vampire_fang_heal 50 // The amount of health a Vampire will heal by when they fully drain a target's blood ttt_vampire_fang_overheal 25 // The amount over the Vampire's normal maximum health (e.g. 100 + this ConVar) that the Vampire can heal to by drinking blood. ttt_vampire_fang_overheal_living -1 // The amount of overheal (see "ttt_vampire_fang_overheal") to give if the Vampire's target is living. Set to -1 to use the same amount as "ttt_vampire_fang_overheal" instead ttt_vampire_fang_unfreeze_delay 2 // The number of seconds before players who were frozen in place by the fangs should be released if the Vampire stops using the fangs on them From a447786b37da9c484879330af716d7b4b37b4337 Mon Sep 17 00:00:00 2001 From: NoxxFlame Date: Tue, 12 Aug 2025 14:40:02 +0100 Subject: [PATCH 002/140] Copied taskmaster progress to current dev branch --- .luacheckrc | 2 + .../materials/vgui/ttt/roles/tsk/icon_tsk.vmt | 7 + .../materials/vgui/ttt/roles/tsk/icon_tsk.vtf | Bin 0 -> 5712 bytes .../vgui/ttt/roles/tsk/score_tsk.png | Bin 0 -> 4640 bytes .../vgui/ttt/roles/tsk/sprite_tsk.vmt | 11 + .../vgui/ttt/roles/tsk/sprite_tsk.vtf | Bin 0 -> 5712 bytes .../vgui/ttt/roles/tsk/sprite_tsk_noz.vmt | 11 + .../materials/vgui/ttt/roles/tsk/tab_tsk.png | Bin 0 -> 2591 bytes .../targeticons/down/sprite_target_task.vmt | 11 + .../targeticons/down/sprite_target_task.vtf | Bin 0 -> 5712 bytes .../down/sprite_target_task_noz.vmt | 11 + .../ttt/targeticons/up/sprite_target_task.vmt | 11 + .../ttt/targeticons/up/sprite_target_task.vtf | Bin 0 -> 5712 bytes .../targeticons/up/sprite_target_task_noz.vmt | 11 + .../roles/taskmaster/cl_taskmaster.lua | 435 ++++++++++++++++++ .../gamemode/roles/taskmaster/shared.lua | 77 ++++ .../gamemode/roles/taskmaster/taskmaster.lua | 247 ++++++++++ gamemodes/terrortown/gamemode/shared.lua | 23 +- 18 files changed, 849 insertions(+), 8 deletions(-) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/icon_tsk.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/icon_tsk.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/score_tsk.png create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk_noz.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/tab_tsk.png create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task_noz.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/targeticons/up/sprite_target_task.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/targeticons/up/sprite_target_task.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/targeticons/up/sprite_target_task_noz.vmt create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua diff --git a/.luacheckrc b/.luacheckrc index 3b71d230b..2dae956eb 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1142,6 +1142,7 @@ globals = { "ROLE_SPONGE", "ROLE_SPY", "ROLE_SWAPPER", + "ROLE_TASKMASTER", "ROLE_TRACKER", "ROLE_TRICKSTER", "ROLE_TURNCOAT", @@ -1210,6 +1211,7 @@ globals = { "CRHUD", "DEATHRADAR", "SYNC", + "TASKMASTER", -- TTTPAP Namespaces "TTTPAP", diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/icon_tsk.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/icon_tsk.vmt new file mode 100644 index 000000000..cbf40a7ad --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/icon_tsk.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/roles/tsk/icon_tsk" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/icon_tsk.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/icon_tsk.vtf new file mode 100644 index 0000000000000000000000000000000000000000..ccacf7431a54821ba0adc56a5cd832143d5faed3 GIT binary patch literal 5712 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/score_tsk.png b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/score_tsk.png new file mode 100644 index 0000000000000000000000000000000000000000..87e26daf69cef5d82b00ee68da4b63b9de16833d GIT binary patch literal 4640 zcmcgw4OmoF8onTlqGq6&l!fD{`Nw7E{?8p9L>UCs0pw@F#Mb*aa2ICoG&8_Jnxt8# zY-T8%N^7Wr;X{_0iMZ&lQJM>86y_2_nV^DLmV%blxiiCPyP`Z>&(8DA+Q#MOd(d03@XI7RH4I^}Fx zp(51~uZWPWf`Sf3U{0+@6%-xHkPL~@WQYP)qqs_qsSp%aQ(9c3MKOi@qXg=#yiuE= zpX*izo^;9#yWOHysd963LvyiE$(pW0Xqr~3Q5A~9KmxYqneB`NHrpojA?O7gXB91W zQ8FuJM23~J?K&ke)#Zc9;(;~W+&lrsR1U_XLPFKDPfkIe^T=7UtyxaxJf{+}1e0L4 z+khcj9t(v<#`xEV3bZmP%VLKQT22L(W=!hQG>*b z#=JgJ6o(NtjB1h)TuV?|Lj8nW3M4Vl*qM9D@toEuSxpQeUo@XDDuEEf!1IM36nI%!WfFPutu$>U;)DgmgRYp zG;)3G^%9pYXPLa-RdKw;0gT&Pd5s2R8H|S+k|SY^1ZXtN(ZEeaL+~tuYETywz?wL# z2#TJ`x)W8liU)`!MzIVH3X(xJJWL}v0W+)-fZ-IwkVYfNsDW2s2Y4!i2UoGUv!`O+tbNr5m1aO6>_4uSWN^?3(!gu1V*^W<3)#%m8us3 zJ2s~wSle<{aXAYC@zRMEB?#{NFKro7-1(?Sf7G#}3 z!L)|MVWWmdQ3}B^lwxP9ED#?8<#`O|d4hCfku<=fG^~K5Y7S?Sfno_H zC2+Dyz%Igt#S#Juvpm8uxIln7-~+~@IF29~1ZD{}4^srr!7NQ7u!bUd3ZpcbVAKp4 zi(_e?04Dtob{R>EL@5j%AeJ)siJ!vRaf{RV$nhNX=f42MzB zkYN_VDVRk`0%nZ_PV*eXaU|WBHGTfvZ9^K6D<~0)pm*+zu2lnZgu8D7+$ZL8T6l7# zf5LddD&CO-ccz)g1cfuX+d_rPgR3yu)?IaCZ}o0BtHMw5Q)p+asTP8#kwu=fptw*dLp^Zo%IJZ~_98Puy4 zyq{EUpAJKiPg9J3c2er<+LR?3fhmE#ZSD2F&mDNB&WsY3(;xROJQeue_$NwVnfHcr z@y4~$f;es`+@tu*>hR#_zFv2Eo$BJMz;*K{J^R_pPxpn4o)j~GP15%0gUxfP6`f^< zcZ=$JjqUX%VHy9f<(tnmHa^&O;>)V`Ww!d=gbJ{22&5hk(9erV} zboS7qSz%!{r__ac^?A>)*%dz}=s2Gc_eMfq1AbgR9NStK8W$UTRw%qY#CKKQgTXT$ z7cYIj+YF6_N|oMKXxhdx+1c9KI`)-`$zBlG83{>VJtM=f#>|@J_iCBn!k?<=RaaL(nANnuGRNWQiB)bG z(s8iMfu6i}?b^r3jvXs*Y;0`XlzwB`lRj?`IznY?r)^p}r}Xtr!`>eq5)$HHAbB0y zTfA-U*oEk3{KuO&+p?GJ*}3}DbMFlm{QLt#(+(_s?kIBt~5>KE?(y8B@+BQvw;a?aCv zzJJ8k6!lSZ@~uMTp;xzVUFRPlpqp1VT@D^>85Hh+WK3dxKJ$K4rFTKIIeq!^sHMHz!0zMG*ymt^8t&-}I?Ne7tEW7y7}w=cjLu7e$ONW{wA@ zPCI($%o#n$om-!nm}vOurN_p;({%Yydpb9lRsA{Hd;ZjyCr(|wWXWUM+SjU~=-~B} zg&^Nq&-~PL5@gZtVj<73plam8$b}!Q8>1q-zN~7f`n2Nb(a##Tl2c3Lr=0)J5#|&B z^!blBJv7_@ZRY6ng>Q8*drr?7e`0UV)tWkE(V-`=%z#SHXAMhYdp^lt!WC8yF0gA& zu^{9J(n1Y^k=E+wt)nB~*=aR2>?$j3n*p`2x$wf|ZaA{_>iH9^dwY8yp6zd@aQvGQ zi)5N5xsasvHpvemTiv&2fx&F>zNykm5Xx+`}&$wv%~efAK$^Iu7$Xe=CfzR zY?nTJv&%6vI7j>1;eZWinwqNpU(}aupK_^|O$?0GJoUByy^4wix~X``7g^+lz@c?L z%W6)SmzQVOZ<}?67`|$o?&<60(G&L$X-$FhPX(0j*l})2axxWGH0?rXXHxf-mK8_Z zzrXd*prZX!OHEXDHDRkB;zK|3OVz(EPHc85tKE+S=OI2XP&m_PFG$4VIsKmyZe$KC)$8(%t~S zmEU?7oQ67UceI2CO!}|_ozi+L^7`n2$Z6wWhjw|z4_^AYPny?o=#he;03l>l&*5(; YpH7cpl9Yi8`9GYP$XNYbbC$0B1%7j&asU7T literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk.vmt new file mode 100644 index 000000000..3df238e0a --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/roles/tsk/sprite_tsk" + "$nocull" 1 + "$ignorez" 0 + "$nodecal" 1 + "$nolod" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk.vtf new file mode 100644 index 0000000000000000000000000000000000000000..84d0c09f5648eb00265051db3cf3ea9c3c2094c1 GIT binary patch literal 5712 zcmeHLe^6A{6+Zj+2Mf5eRo1~o+oi5BQcO*1j1VR)OeC|WiL2o)I||cfDQ(mtrKZ1P zL^ivarX6M^1ceb1o@PK7GJ@>7Tq1$w< zsigkV%YE$o_PlrR{m!}PyZ5doI}0KJKuMR4E+hNX1IXx}74N5M>CzSY#*rRFHxU~8 zr`3v&aUp~FpM7DYYYkn4bRoW84h|#T)%kgynRg3xtF-EU>=PnYh;G4aG#Y)v+Qtcp zRU;le^C%>xBW9>oRNMlhj!K*G_KUG=)7BzxE(aZuJzlf}3?R?zbO0rOo@Z@_J`n*_ zDy7n%Z>ls<4fq1SW-8`A+IN$w=K1@5AMhXWvHpYQPd=|$RN}u_<@I(xe*;XFdmh%x z0o;O)=Xu57Jb4|HhkNS$cCAdW*Q(;)dHf2Sw^aZkbHl3!-tFOGs5<+HQ?IbS4$ z%E4ulpY*-^miN7~yifAiPB2v{s7ih-9QW^P@b7AV`^7dPBY%7T*=v}74ePINE=S~z z{0_rqdRcrwgNDdiKVRg2;Y@s{(jfU;7UEAB zk@;J~LM!#h^DDzJaI^l(P*bB@l_KVP*vz5lX+cl-Tt?2zo?{YiM~oc3)to4!HB z$>jjKLp0m@htRX*SivFkzaEq_2TVo{t{CQ2)O&;0YKuK-@f1fddEs2Vk^Dkmt=cG~L>uFVtlajG#5Wu(FRrt%peptM+d%2a z&uIt)m|U=?*a&#$dPSy8P3;%1N{DpA^c$czm9QZ+XrY-0JOFxGik|S%IEd1K;;ruC z1ysLm|1-s)YVt-Uv-}+#ZYiGl)`Q0AbE8j}_~X^lD#gB2M0>o(k>;8WYs!s2MVZ`kl9mPF8&yIZl?Y}&^>D>~)o|;#bb$`q}SB~^Gj)L zh_Q_W2n@G&GKd4XwvkEh1V_QIk*AjU0kX5RE#dJF$J_(!?AvI{viK{NI=)9<_mh4~ zDK?|1IbP9XdZ!3%WdF~w^35Y_IvlLMHd1gSAKu^paY(ON%;xIfoiOu1g?l!iZ{u0L z`*%2`mx~rlc6BtCPd)*f6Ks7`Qh5sYW+q;D%^Uy7 zV_7d;rONd5i};6+&le!7h!Jc*KpwF5)5^%!r{n@rKeEH0zgzrLAZE-=6(CRKugO_@ zxP$6^#s5zHgGXp{^nfzeC&a7OiPhP`1CCcuov5bOQV-_b>Xjc(b#{{jo=PwQd_txw0`3!JrcbX2D)z)l>%$?v=P z;~{Gd)T~Hlv$qMRb2gnSx`kKPo7f81Oy`65f|H2B{dnN5d!v8%UHo6M#_S&4mk_TG z_$a>4z=oOqE3I1+^C%y_XEtwj`=(Cfgp2gALCbMY`|tlwsqoAnRV%u8Q2 z|AmvZ`FYh|z~;9?VW_$yuJ8L30zNSwO_ZNhDhz2uB8Gd81FU`HMZ|s|-IVSgOCNhK z=D^<7itYb9lcA-E zmM{r-t-iOoGpxNoeyu0<@)=B&`g=@v?K_EkN5u2;eDJ69Mg{wQQshtbQv?1g3A6dS kkOlv?{j0zl(ALfEj{~dt`>7az8jo~7Vi`2|d?w+40P;;bIsgCw literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk_noz.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk_noz.vmt new file mode 100644 index 000000000..1c098b498 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/sprite_tsk_noz.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/roles/tsk/sprite_tsk" + "$nocull" 1 + "$ignorez" 1 + "$nodecal" 1 + "$nolod" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/tab_tsk.png b/gamemodes/terrortown/content/materials/vgui/ttt/roles/tsk/tab_tsk.png new file mode 100644 index 0000000000000000000000000000000000000000..56f9eb8252878b2e2efab0fe48864718c8fc2d7e GIT binary patch literal 2591 zcmc&$du$X%7(b#Spx_glpiMX1S}U@*kK5bZT{)okfEK;BmTP#l5@vVj+O@Y2cDKE2 ziINnG5~u_&1R^RLB}OXLU_b~2yaF1M2BUu{iJ}-ZN}?751dIaC-m8Taslmic=5}WH zoA38Me&08QW8gu}b&|st(!muDFcxghWCj4(l&Dx2nw|*IxlNB8;)T& z8#hs%|9l+)ifuTY#~9WYP0K^r&a5`%Mm8YBO52fk6Owh%Hd-wi&T2zu7U#UNojN!e z&k<}#;_1z%SYbinzz`Zi9A(f)P(Fg;BD7DWInl>GNz)=lxj3d6HWW45o)={anj#oJ zLbIZu7b#&;Rtix^0S@>lIYkzuhNS`IvZ{e5NSb;Rj^zU>Rv0nELU70?Us`K8ELQ2Z z19TZyg@QP;#;dA|$U>hKkY$#L`T!sVj%5jn=lldC&@_ZlqW%78Zhy#-Tix8c`!hUO z3>oPdL}fTmQY48cqC7_v0Ut{f5>0UgCqMvzKudvWUR#Z&qEZ8~p;}$56r|D5Lm)$e zCm4KMM7Y`g@n(orUY~G=6KOt|@C>$XM4dRWV0jaYT&@k&=IQ}sSg#K&o3HAM z5x1Yn*(Nswy$PcBL*nGkoRJN^4O-5iLng2Q>N?P}gLu?3Gz^$#OqGG_JK3fy*;>wL z3v&!?kUKZUQJ|#`3ENPVyb0KUhc>c z6aD`?Nw;)nWc7E0GsE8%?P|FD^35xcN=rU`Ai3z3 zRN1+A-d~*B(V!I_4jKnGT@sHSJAdWi_x78YUjO;gsW`*+xoV)@x6mYq+dz~*1!A1qSW*QDSy9o$Cj?(w?`W5 z_Y@W1*|S-n`0>iIzox_q@~-bXPTccN!`S4+%lB?wTCh%JC-Ign}>8Fypxm9-q&!g=hWN{a@XnD?w-!VE|U5raG@tK!e{g4FMQcVF&$nP0K& zME%M+YcA9*e&$H`gTF*h_LjE4rc623xZzC6rYX@KEgQFl8+*^E+Ip8StkI-~BcJWq mGok*G;9q3}1%1iVGlALFk(mSa-V^Q>u4-0wXy?;QI{yabK6+vR literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task.vmt new file mode 100644 index 000000000..d80b4dcfe --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/targeticons/down/sprite_target_task" + "$nocull" 1 + "$ignorez" 0 + "$nodecal" 1 + "$nolod" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task.vtf new file mode 100644 index 0000000000000000000000000000000000000000..3e8a453d02fee319fe2b4681209eb3252e2d8c96 GIT binary patch literal 5712 zcmeHLeN0nV6hD1XEFYyst#eXmsUV8ZxsMP@Dm+NkRWq%I%Qh!jhcnSg(YeH!B`$?b zH$Mo4fMXj7m~3%G7d4?-*sMgd8TW@XnINVc#c5JEogkoXq2=t{mX~2evN6a43zv6G zfA60Aaen9Ho!7kFH82AJ@HtqzI_p@HBh zzhS1nf_fKqq}E6cvAG?-J-r9O;AWdNn!oOc#$8x`tX^k3>+mEg#C4(=KJM@U=pYwd zsDpJP8?mmm{^DuakubrPGN7HI^)79Lrbu&HF8yr#hzi%&eB04*@hS~57 z7%TQimzxkh7pzvRcuQu~r@mH_!pEbPD%DkpF26(T0iA@&8PnWtP(Oy5S{@M26wi?WVNOx zc<`i`!j^nF zgZzsDv-V~ATQmHvJd(KlSoaH;58vOnp)osZZ^Im|+nw>$EXcg*#5#+-hYoSR5?CWx zeOWaVxqo5rzTKXEyO$ykcItFGlYfQtTp}4dzkBKz-aK3=$%`}|&gV29Up!3n_s{s_{z>jf z&*hN3gy(+1^QC3sFg7WhIIfKeKA#v%5rXn1LH{0Ye%N{@3IE&tI??kNq)7T1+>{nf zu^)20_)dN|P`JwDmfc=cl=(v+y0AbkD{cYYZhhe`(zesnxLG``g!nUOS9LV>tmXNe zv0dh+K|X}v0A9aJ>uE;vW1=?E=O6ESLkpiaL8M65H@IVk2w7mGS615+Px&!hvAEg{ zf&HY#JKtCUwz~QA;;S2EIZ9~~9L56+w8Tf9HDH&OxlD_&`Qp;I+hpWe|D& z$8nq2N8BNP+Rpi8Y^v%1xKMp`(aPe>i7tRVd(;;LE48)=RhP*1Qz})Z)dQrQF-L}& zqyT7S#XgSpMx=P*81HcZQNwUOnLE|=_rAD8c=_5#ZCc8Pd2lMv*xT}x|Ne6UhbLPR z4KebVe8_}{WK$;#n2)*3iFMS@WZ}4~ihDP_h$^I1oQ%(Pe3v>r(-Yvc9>;#C4 z$~*1PjP4TS`-Kx^e^ptju;mff*=O7A-2b`5Z}Fka4%h75KR9mpQm}YK{?8}?CcFO6 zJfe4xLDeExFy9GZqK{k8mVCS^R1b_5KOo%^>muW}1?Q)&x>!8Fqd32V$eEP+|ETPt z?Pp5q{Cxb0pHNM~OeKJXmwjv$U<|mDex7iSSTAx<)w7}Zcdq|*eLvoMWqSUT^~W?H O(|nBlJ@EhT4}SvJ!%zwU literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task_noz.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task_noz.vmt new file mode 100644 index 000000000..566d39d73 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/down/sprite_target_task_noz.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/targeticons/down/sprite_target_task" + "$nocull" 1 + "$ignorez" 1 + "$nodecal" 1 + "$nolod" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/up/sprite_target_task.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/up/sprite_target_task.vmt new file mode 100644 index 000000000..aba37bf74 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/up/sprite_target_task.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/targeticons/up/sprite_target_task" + "$nocull" 1 + "$ignorez" 0 + "$nodecal" 1 + "$nolod" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/up/sprite_target_task.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/targeticons/up/sprite_target_task.vtf new file mode 100644 index 0000000000000000000000000000000000000000..32fc5b0d933f8f9153c0493eeb4a8ee8b9deea7c GIT binary patch literal 5712 zcmeHLZ%kWN6hE&}D6rBF0ZD)`kfnfTnSWrKu=IgrTjz&$+SlO3q!L|V1{95pGn*Ft zmqj)zVM+-9bZ+UY6P)r)X4Vkk1H?F|4u)w$o#2`c2MB|8V%ba8UW_m8I?gcDEqx5fD$L0JQJCbG#FepD}k^EOvLMg5SmS|Lw1-w}64Z zLjC+3fAhUJPuG}DwaoR}2wO*`QrA~Qwa|5LWSec9O+6r14ORdC1EOOL(h3uE-xaGI zV%0rqE<`~Pk{HrCl8RUNcEsl27^#TF={U$ssu{^?h+}YJAhwL~%%di$!oYlWItgOU>VTE&f{e zJI^|tgqO)T@m5t+nZxABRM_1~NqNFj^TYisYY)qnXa2WjKLr&owln2&GVnRE^1jIo zv2B`s00&l&Y=726kfTE!PbvMf*S5Zi#^?Hf0&pVFmy|4*|E1xA>T#-eH)07Dt8ce& z%Rs_byZdYtB;+evrgQ<+Jpg0w4|-^gi)OFZ->+@$XX|C-SDOF)R?FrnUu{g8E2lW` zt_(%sAUUl?6>&TEGS~z#&k_Ge{42(P@X~>5*MZykQFxx;k;T#Oel9mPl~5jmi`Wi;L?Pm5w*lV_!J~bh-(}vY`L2%MN`nBwu#E>8XV1 z)uKN?3?UcEvMIY;PXPR&s!i5MTM>&C@j1<=z^e?2`fJLWD1Q>*^Dee>mZ3>>ARi(= z>)jrYSf@74v~CQ@F~UT>POMdw(|uT`o6SCyrCF``(aNcTY9Zxg1}ZoYlx}u%h+>bK z($K&+RdJy1R0XX86w!E@;u$KwF+`?t$Lt^z2$eKf4C}yu`zI&7l%=7%R`C0u>3r*| zejQnBo!Vt+hVI!P8?-n_`1(^~>U!a+o7f>HT7vOBPy|`#L8k~8MZ23}7hu?f9q2&} z=CcNau-5SVUI6%_;^VsuDT4u{80Fsty_|N2b_dZ)r3$@t2VNug1oL%}Xv@Uq^Adeq z|G^m%YYo3A-FhToj;u;`0-Q2}b;o>PT;X@$2igh$(HMw5bm5ATHU~P9{N1Mjuea!A zJTrer+W;o6dpd$7y3cd8Q`Q>(+3Zr~<2kis=SE2VQBg*pc8dk#CwQh!xvJsllWZ$g zKE{nPdmw|~J4!UU1SStgkC5%7w?VRh!C+YJ`1hUmcA&Ql4_-ht8jXereIMPF^f$S8 zfe<}?EaczbuV?=4=c)BGte^QmEBU`sXTq#B{_ylLlR!ZFXOF#O=jm$4KM5I@4~IG^ so&q>c=bK*+d?QM maxWidth then maxWidth = (margin * 3) + checkboxSize + nameWidth end + + height = height + nameHeight + (margin / 2) + + surface.SetFont("UseHint") + surface.SetTextPos(xPos + (margin * 2) + offset, height + offset) + surface.DrawText(desc) + + local descWidth, descHeight = surface.GetTextSize(desc) + if completed then + -- Shifting the strikethrough lines down one pixel makes it look much nicer with lowercase letters + surface.DrawRect(xPos + (margin * 2) + offset, height + (descHeight / 2) + 1 + offset, descWidth, 1) + end + + if (margin * 3) + descWidth > maxWidth then maxWidth = (margin * 3) + descWidth end + + height = height + descHeight + margin + return height +end + +hook.Add("HUDPaintBackground", "Taskmaster_HUDPaintBackground", function() + if GetRoundState() ~= ROUND_ACTIVE then return end + if maxHeight == 0 or maxWidth == 0 then return end + + if not client then + client = LocalPlayer() + end + if not client:IsActiveTaskmaster() then return end + + -- Add 2 to the maxWidth here to account for the text shadows + draw.RoundedBox(8, xOffset:GetInt(), yOffset:GetInt(), maxWidth + 2, maxHeight, Color(0, 0, 10, 200)) + maxWidth, maxHeight = 0, 0 +end) + +hook.Add("HUDPaint", "Taskmaster_HUDPaint", function() + if GetRoundState() ~= ROUND_ACTIVE then return end + + if not client then + client = LocalPlayer() + end + if not client:IsActiveTaskmaster() then return end + + local height = yOffset:GetInt() + margin + + for _, id in ipairs(client.taskmasterKillTasks) do + DrawTask(TASKMASTER.killTasks[id], height, true) + height = DrawTask(TASKMASTER.killTasks[id], height) + end + + for _, id in ipairs(client.taskmasterMiscTasks) do + DrawTask(TASKMASTER.miscTasks[id], height, true) + height = DrawTask(TASKMASTER.miscTasks[id], height) + end + + maxHeight = height - yOffset:GetInt() +end) + +hook.Add("TTTHUDInfoPaint", "Taskmaster_TTTHUDInfoPaint", function(client, label_left, label_top, active_labels) + if client:IsActiveTaskmaster() then + local blockEnd = GetGlobalFloat("taskmaster_block_end", 0) + if blockEnd > 0 then + surface.SetFont("TabLarge") + surface.SetTextColor(255, 255, 255, 230) + + local remaining = math.max(0, blockEnd - CurTime()) + local text = LANG.GetParamTranslation("taskmaster_hud", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local _, h = surface.GetTextSize(text) + + -- Move this up based on how many other labels here are + label_top = label_top + (20 * #active_labels) + + surface.SetTextPos(label_left, ScrH() - label_top - h) + surface.DrawText(text) + + -- Track that the label was added so others can position accurately + table.insert(active_labels, "taskmaster") + end + end +end) + +---------------------- +-- TASK REROLL MENU -- +---------------------- + +local dtasks = {} +local dtasksHeight = 0 + +local scrollBarWidth = 15 +local creditsIconSize = 32 + +local CreateTaskList + +local function CreateTaskReroll(task, dscrollpanel) + if table.HasValue(client.taskmasterCompletedTasks, task.id) then return false end + + local name = task.Name(client) + local desc = task.Description(client) + + local width, _ = dscrollpanel:GetSize() + + local dpanel = vgui.Create("DPanel", dscrollpanel) + dpanel:SetPaintBackground(false) + dpanel:SetWidth(width) + dpanel:SetPos(0, dtasksHeight) + + local buttonWidth, buttonHeight = 80, 45 + + local dname = vgui.Create("DLabel", dpanel) + dname:SetFont("TraitorStateSmall") + dname:SetText(name) + dname:SetPos(margin, margin) + dname:SetWidth(width - (margin * 2) - buttonWidth - scrollBarWidth) + + local _, nameHeight = dname:GetSize() + + local ddesc = vgui.Create("DLabel", dpanel) + ddesc:SetFont("UseHint") + ddesc:SetText(desc) + ddesc:SetPos(margin, (margin * 1.5) + nameHeight) + ddesc:SetWidth(width - (margin * 2) - buttonWidth - scrollBarWidth) + ddesc:SetWrap(true) + ddesc:SetAutoStretchVertical(true) + + local _, descHeight = ddesc:GetSize() + local height = (margin * 2.5) + nameHeight + descHeight + + dpanel:SetHeight(height) + + local dreroll = vgui.Create("DButton", dpanel) + dreroll:SetSize(buttonWidth, buttonHeight) + dreroll:SetPos(width - margin - buttonWidth, (height - buttonHeight) / 2) + dreroll:SetText(" Reroll\n (1 credit)") + + dreroll.DoClick = function() + if client:GetCredits() == 0 then return end + + net.Start("TTT_TaskmasterRerollTask") + net.WriteString(task.id) + net.SendToServer() + end + + dreroll.Think = function() + dreroll:SetDisabled(client:GetCredits() == 0) + end + + dpanel.rerollButton = dreroll + + dtasksHeight = dtasksHeight + height + + local dline = vgui.Create("DPanel", dscrollpanel) + dline:SetSize(width, 1) + dline:SetPos(0, dtasksHeight) + + dtasksHeight = dtasksHeight + 1 + + return dpanel, dline +end + +function CreateTaskList(dscrollpanel) + if not dscrollpanel:IsValid() then return end + + for _, dtask in ipairs(dtasks) do + dtask:Remove() + end + dtasks = {} + dtasksHeight = 0 + + for _, id in ipairs(client.taskmasterKillTasks) do + local dtask, dline = CreateTaskReroll(TASKMASTER.killTasks[id], dscrollpanel) + if dtask then + table.insert(dtasks, dtask) + table.insert(dtasks, dline) + end + end + + for _, id in ipairs(client.taskmasterMiscTasks) do + local dtask, dline = CreateTaskReroll(TASKMASTER.miscTasks[id], dscrollpanel) + if dtask then + table.insert(dtasks, dtask) + table.insert(dtasks, dline) + end + end + + if #dtasks > 0 then + dtasks[#dtasks]:Remove() + + local spacerWidth, _ = dscrollpanel:GetSize() + local spacerHeight = (margin * 2) + creditsIconSize + + local dspacer = vgui.Create("DPanel", dscrollpanel) + dspacer:SetPaintBackground(false) + dspacer:SetSize(spacerWidth, spacerHeight) + dspacer:SetPos(0, dtasksHeight) + + dtasksHeight = dtasksHeight + spacerHeight + end + + if dtasksHeight > dscrollpanel:GetTall() then + for _, dtask in ipairs(dtasks) do + if dtask.rerollButton then + local xPos, yPos = dtask.rerollButton:GetPos() + dtask.rerollButton:SetPos(xPos - scrollBarWidth, yPos) + end + end + end +end + +hook.Add("TTTEquipmentTabs", "Taskmaster_TTTEquipmentTabs", function(dsheet, dframe) + if not client then + client = LocalPlayer() + end + + if client:IsActiveTaskmaster() then + local padding = dsheet:GetPadding() + local tabHeight = 20 + + local dpanel = vgui.Create("DPanel", dsheet) + dpanel:SetBackgroundColor(Color(90, 90, 95)) + dpanel:StretchToParent(padding, padding + tabHeight, padding, padding) + + local _, panelHeight = dpanel:GetSize() + + local dscrollpanel = vgui.Create("DScrollPanel", dpanel) + dscrollpanel:SetPaintBackground(false) + dscrollpanel:StretchToParent(0, 0, 0, 0) + + local dcredits = vgui.Create("Panel", dsheet) + dcredits.Paint = function(panel, w, h) + draw.RoundedBoxEx(8, 0, 0, w, h, Color(151, 155, 159), false, true, false, false) + end + + local dcreditsicon = vgui.Create("DImage", dcredits) + dcreditsicon:SetSize(creditsIconSize, creditsIconSize) + dcreditsicon:SetImage("vgui/ttt/equip/coin.png") + + local dcreditsamount = vgui.Create("DLabel", dcredits) + dcreditsamount:SetFont("DermaLarge") + + dcreditsamount.Think = function() + local credits = client:GetCredits() + local noCreditsColor = Color(220, 60, 60, 255) + + dcreditsamount:SetText(" " .. credits) + dcreditsamount:SetColor(credits == 0 and noCreditsColor or COLOR_WHITE) + dcreditsamount:SizeToContents() + + dcreditsicon:SetImageColor(credits == 0 and noCreditsColor or COLOR_WHITE) + + local creditsAmountWidth, _ = dcreditsamount:GetSize() + + local creditsWidth = padding + margin + creditsAmountWidth + creditsIconSize + local creditsHeight = padding + margin + creditsIconSize + dcredits:SetSize(creditsWidth, creditsHeight) + dcredits:SetPos(padding, panelHeight - creditsHeight + padding + tabHeight) + + dcreditsamount:SetPos(padding + creditsIconSize, margin) + dcreditsicon:SetPos(padding, margin) + + dcredits:MoveToFront() + dcreditsamount:MoveToFront() + dcreditsicon:MoveToFront() + end + + CreateTaskList(dscrollpanel) + + net.Receive("TTT_TaskmasterUpdateTaskList", function(len, ply) + CreateTaskList(dscrollpanel) + end) + + dsheet:AddSheet(LANG.GetTranslation("taskmaster_reroll_name"), dscrollpanel, "icon16/table_edit.png", false, false, LANG.GetTranslation("equip_tooltip_taskmaster_reroll")) + return true + end +end) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua new file mode 100644 index 000000000..73004d14e --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -0,0 +1,77 @@ +AddCSLuaFile() + +ROLE_STARTING_CREDITS[ROLE_TASKMASTER] = 1 +ROLE_HAS_PASSIVE_WIN[ROLE_TASKMASTER] = true +cvars.AddChangeCallback("ttt_taskmaster_is_passive", function(_, _, newValue) + ROLE_HAS_PASSIVE_WIN[ROLE_TASKMASTER] = util.tobool(newValue) +end) + +------------------ +-- ROLE CONVARS -- +------------------ + +CreateConVar("ttt_taskmaster_kill_tasks", "1", FCVAR_REPLICATED, "The number of kill tasks assigned to the Taskmaster", 0, 10) +CreateConVar("ttt_taskmaster_misc_tasks", "2", FCVAR_REPLICATED, "The number of miscellaneous tasks assigned to the Taskmaster", 0, 10) +CreateConVar("ttt_taskmaster_completion_bonus", "1", FCVAR_REPLICATED, "How many credits the Taskmaster should get whenever they complete a task", 0, 10) +CreateConVar("ttt_taskmaster_blocks_team_wins", "1", FCVAR_REPLICATED, "Whether the Taskmaster should block teams (innocent, traitor, monster) from winning if they are alive and haven't finished their tasks.") +CreateConVar("ttt_taskmaster_win_block_length", "60", FCVAR_REPLICATED, "How long (in seconds) the Taskmaster should block teams (innocent, traitor, monster) from winning for (if 'ttt_taskmaster_blocks_team_wins' is enabled). Set to 0 to block until time runs out", 0, 300) +CreateConVar("ttt_taskmaster_wins_with_others", "1", FCVAR_REPLICATED, "If the Taskmaster should be allowed to win alongside other teams/players") +CreateConVar("ttt_taskmaster_is_passive", "1", FCVAR_REPLICATED, "Whether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if 'ttt_taskmaster_wins_with_others' is enabled)") + +ROLE_CONVARS[ROLE_TASKMASTER] = {} + +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_kill_tasks", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_misc_tasks", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +----------------------- +-- TASK REGISTRATION -- +----------------------- + +TASKMASTER = { + killTasks = {}, + miscTasks = {} +} + +function TASKMASTER.RegisterTask(task) + task.id = task.id or task.Id or task.ID + + if TASKMASTER.killTasks[task.id] or TASKMASTER.miscTasks[task.id] then + ErrorNoHalt("ERROR: Attempted to register Taskmaster task '" .. task.id .. "' with duplicate task ID.\n") + return + end + + local cvarName = "ttt_taskmaster_" .. task.id .. "_enabled" + local enabled = CreateConVar(cvarName, 1, FCVAR_REPLICATED) + task.Enabled = function() + return enabled:GetBool() + end + table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = cvarName, + type = ROLE_CONVAR_TYPE_BOOL + }) + + if task.isKillTask then + TASKMASTER.killTasks[task.id] = task + else + TASKMASTER.miscTasks[task.id] = task + end +end + +local function AddTaskFiles(root) + local taskFiles, _ = file.Find(root .. "*.lua", "LUA") + for _, fil in ipairs(taskFiles) do + include(root .. fil) + if SERVER then AddCSLuaFile(root .. fil) end + end +end + +AddTaskFiles("terrortown/gamemode/roles/taskmaster/tasks/") -- Internal tasks +AddTaskFiles("taskmastertasks/") -- External tasks \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua new file mode 100644 index 000000000..dc71bdf62 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -0,0 +1,247 @@ +AddCSLuaFile() + +local plymeta = FindMetaTable("Player") + +util.AddNetworkString("TTT_TaskmasterRerollTask") +util.AddNetworkString("TTT_TaskmasterUpdateTaskList") + +------------- +-- CONVARS -- +------------- + +local taskmaster_kill_tasks = GetConVar("ttt_taskmaster_kill_tasks") +local taskmaster_misc_tasks = GetConVar("ttt_taskmaster_misc_tasks") +local taskmaster_completion_bonus = GetConVar("ttt_taskmaster_completion_bonus") +local taskmaster_blocks_team_wins = GetConVar("ttt_taskmaster_blocks_team_wins") +local taskmaster_win_block_length = GetConVar("ttt_taskmaster_win_block_length") +local taskmaster_wins_with_others = GetConVar("ttt_taskmaster_wins_with_others") + +--------------------- +-- TASK ASSIGNMENT -- +--------------------- + +function plymeta:AssignTask(isKillTask, index) + if not self:IsTaskmaster() then return end + + local taskList = isKillTask and TASKMASTER.killTasks or TASKMASTER.miscTasks + local taskIds = table.GetKeys(taskList) + local activeTasksName = isKillTask and "taskmasterKillTasks" or "taskmasterMiscTasks" + + for _, activeId in ipairs(self[activeTasksName]) do + table.RemoveByValue(taskIds, activeId) + end + for _, rerolledId in ipairs(self.taskmasterRerolledTasks) do + table.RemoveByValue(taskIds, rerolledId) + end + table.Shuffle(taskIds) + + for _, id in ipairs(taskIds) do + if taskList[id].CanAssignTask(self) then + taskList[id].OnTaskAssigned(self) + + if index then + table.insert(self[activeTasksName], index, id) + else + table.insert(self[activeTasksName], id) + end + self:SetProperty(activeTasksName, self[activeTasksName], self) + return taskList[id] + end + end + -- TODO: Handle edge case where there are no valid tasks. Maybe we need to change their role? Or just have a free fallback task? + return false +end + +function plymeta:RemoveTask(taskId) + if not self:IsTaskmaster() then return end + + local isKillTask = TASKMASTER.killTasks[taskId] and true or false + local taskList = isKillTask and TASKMASTER.killTasks or TASKMASTER.miscTasks + local activeTasksName = isKillTask and "taskmasterKillTasks" or "taskmasterMiscTasks" + + taskList[taskId].OnTaskRemoved(self) + + table.RemoveByValue(self[activeTasksName], taskId) + self:SetProperty(activeTasksName, self[activeTasksName], self) +end + +function plymeta:RerollTask(taskId, free) + if not self:IsTaskmaster() then return end + if not free and self:GetCredits() == 0 then return end + + local isKillTask = TASKMASTER.killTasks[taskId] and true or false + local activeTasksName = isKillTask and "taskmasterKillTasks" or "taskmasterMiscTasks" + local index = table.KeyFromValue(self[activeTasksName], taskId) + if not index then return end + + table.insert(self.taskmasterRerolledTasks, taskId) + self:SetProperty("taskmasterRerolledTasks", self.taskmasterRerolledTasks, self) + + self:AssignTask(isKillTask, index) + self:RemoveTask(taskId) + + if not free then + self:SubtractCredits(1) + end + + net.Start("TTT_TaskmasterUpdateTaskList") + net.Send(self) +end + +net.Receive("TTT_TaskmasterRerollTask", function(len, ply) + ply:RerollTask(net.ReadString()) +end) + +function plymeta:CompleteTask(taskId) + if not self:IsActiveTaskmaster() then return end + + local isKillTask = TASKMASTER.killTasks[taskId] and true or false + local taskList = isKillTask and TASKMASTER.killTasks or TASKMASTER.miscTasks + local activeTasksName = isKillTask and "taskmasterKillTasks" or "taskmasterMiscTasks" + if table.HasValue(self[activeTasksName], taskId) then + taskList[taskId].OnTaskComplete(self) + table.insert(self.taskmasterCompletedTasks, taskId) + self:SetProperty("taskmasterCompletedTasks", self.taskmasterCompletedTasks, self) + + local activeTasksList = table.Copy(self.taskmasterKillTasks) + table.Add(activeTasksList, self.taskmasterMiscTasks) + for _, id in ipairs(self.taskmasterCompletedTasks) do + if table.HasValue(activeTasksList, id) then + table.RemoveByValue(activeTasksList, id) + else + break + end + end + if #activeTasksList == 0 then + self:SetProperty("taskmasterShouldWin", true) + -- TODO: Alert the player that they have finished all their tasks + end + + net.Start("TTT_TaskmasterUpdateTaskList") + net.Send(self) + + local bonus = taskmaster_completion_bonus:GetInt() + if bonus > 0 then + self:AddCredits(bonus) + LANG.Msg(self, "taskmaster_credit_bonus", { + role = ROLE_STRINGS[ROLE_TASKMASTER], + num = bonus + }) + end + + return true + end + return false +end + +ROLE_ON_ROLE_ASSIGNED[ROLE_TASKMASTER] = function(ply) + ply:SetProperty("taskmasterKillTasks", {}, ply) + ply:SetProperty("taskmasterMiscTasks", {}, ply) + ply:SetProperty("taskmasterCompletedTasks", {}, ply) + ply:SetProperty("taskmasterRerolledTasks", {}, ply) + for _ = 1, taskmaster_kill_tasks:GetInt() do + ply:AssignTask(true) + end + for _ = 1, taskmaster_misc_tasks:GetInt() do + ply:AssignTask(false) + end +end + +---------------- +-- WIN CHECKS -- +---------------- + +hook.Add("TTTWinCheckBlocks", "Taskmaster_TTTWinCheckBlocks", function(win_blocks) + table.insert(win_blocks, function(win_type) + if win_type == WIN_NONE or win_type == WIN_TASKMASTER then return win_type end + + local taskmaster = player.GetLivingRole(ROLE_TASKMASTER) + if not IsPlayer(taskmaster) then return win_type end + + if taskmaster.taskmasterShouldWin then return win_type end + + if not taskmaster_blocks_team_wins:GetBool() then return win_type end + + if win_type == WIN_TRAITOR or win_type == WIN_INNOCENT or win_type == WIN_MONSTER then + local win_block_length = taskmaster_win_block_length:GetInt() + if win_block_length > 0 then + local winBlockEnd = GetGlobalFloat("taskmaster_block_end", 0) + if winBlockEnd == 0 then + local roundEnd = GetGlobalFloat("ttt_round_end", 0) + local blockEnd = CurTime() + win_block_length + if blockEnd > roundEnd then + win_block_length = roundEnd - CurTime() + end + SetGlobalFloat("taskmaster_block_end", CurTime() + win_block_length) + local teamName + if win_type == WIN_TRAITOR then teamName = "traitor" + elseif win_type == WIN_INNOCENT then teamName = "innocent" + elseif win_type == WIN_MONSTER then teamName = "monster" end + + for _, ply in player.Iterator() do + if ply:IsActiveTaskmaster() then + ply:QueueMessage(MSG_PRINTBOTH, "The " .. teamName .. " team have won! You have " .. math.Round(win_block_length) .. " seconds left to finish your tasks before the round ends!") + end + end + elseif CurTime() > winBlockEnd then + return win_type + end + end + + return WIN_NONE + end + end) +end) + +hook.Add("TTTCheckForWin", "Taskmaster_TTTCheckForWin", function() + local winning_taskmaster_alive = false + local other_alive = false + for _, v in player.Iterator() do + if v:IsActive() then + if v:IsTaskmaster() and v.taskmasterShouldWin then + winning_taskmaster_alive = true + elseif not v:ShouldActLikeJester() and not ROLE_HAS_PASSIVE_WIN[v:GetRole()] then + other_alive = true + end + end + end + + if winning_taskmaster_alive and (not taskmaster_wins_with_others:GetBool() or not other_alive) then + return WIN_TASKMASTER + end +end) + +hook.Add("TTTPrintResultMessage", "Taskmaster_TTTPrintResultMessage", function(type) + if type == WIN_TASKMASTER then + LANG.Msg("win_taskmaster", { role = ROLE_STRINGS[ROLE_TASKMASTER] }) + ServerLog("Result: " .. ROLE_STRINGS[ROLE_TASKMASTER] .. " wins.\n") + return true + end +end) + +------------- +-- CLEANUP -- +------------- + +local function CleanupTasks(ply) + ply:ClearProperty("taskmasterKillTasks", ply) + ply:ClearProperty("taskmasterMiscTasks", ply) + ply:ClearProperty("taskmasterCompletedTasks", ply) + ply:ClearProperty("taskmasterRerolledTasks", ply) + ply:ClearProperty("taskmasterShouldWin") +end + +hook.Add("TTTPrepareRound", "Taskmaster_TTTPrepareRound", function() + SetGlobalFloat("taskmaster_block_end", 0) + for _, ply in player.Iterator() do + CleanupTasks(ply) + end +end) + +hook.Add("TTTPlayerRoleChanged", "Taskmaster_TTTPlayerRoleChanged", function(ply, oldRole, newRole) + if not ply:Alive() or ply:IsSpec() then return end + + if oldRole == ROLE_TASKMASTER and oldRole ~= newRole then + CleanupTasks(ply) + end +end) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/shared.lua b/gamemodes/terrortown/gamemode/shared.lua index ead963f1e..0293a0feb 100644 --- a/gamemodes/terrortown/gamemode/shared.lua +++ b/gamemodes/terrortown/gamemode/shared.lua @@ -184,8 +184,9 @@ ROLE_EVILTWIN = 48 ROLE_PLAGUEMASTER = 49 ROLE_ILLUSIONIST = 50 ROLE_CANNIBAL = 51 +ROLE_TASKMASTER = 52 -ROLE_MAX = 51 +ROLE_MAX = 52 ROLE_EXTERNAL_START = ROLE_MAX + 1 local function AddRoleAssociations(tbl, roles) @@ -222,7 +223,7 @@ JESTER_ROLES = {} AddRoleAssociations(JESTER_ROLES, {ROLE_JESTER, ROLE_SWAPPER, ROLE_CLOWN, ROLE_BEGGAR, ROLE_BODYSNATCHER, ROLE_LOOTGOBLIN, ROLE_CUPID, ROLE_SPONGE, ROLE_GUESSER, ROLE_CANNIBAL}) INDEPENDENT_ROLES = {} -AddRoleAssociations(INDEPENDENT_ROLES, {ROLE_DRUNK, ROLE_OLDMAN, ROLE_KILLER, ROLE_ZOMBIE, ROLE_MADSCIENTIST, ROLE_SHADOW, ROLE_ARSONIST, ROLE_HIVEMIND, ROLE_PLAGUEMASTER}) +AddRoleAssociations(INDEPENDENT_ROLES, {ROLE_DRUNK, ROLE_OLDMAN, ROLE_KILLER, ROLE_ZOMBIE, ROLE_MADSCIENTIST, ROLE_SHADOW, ROLE_ARSONIST, ROLE_HIVEMIND, ROLE_PLAGUEMASTER, ROLE_TASKMASTER}) MONSTER_ROLES = {} AddRoleAssociations(MONSTER_ROLES, {}) @@ -593,7 +594,8 @@ ROLE_STRINGS_RAW = { [ROLE_EVILTWIN] = "eviltwin", [ROLE_PLAGUEMASTER] = "plaguemaster", [ROLE_ILLUSIONIST] = "illusionist", - [ROLE_CANNIBAL] = "cannibal" + [ROLE_CANNIBAL] = "cannibal", + [ROLE_TASKMASTER] = "taskmaster" } ROLE_STRINGS = { @@ -648,7 +650,8 @@ ROLE_STRINGS = { [ROLE_EVILTWIN] = "Evil Twin", [ROLE_PLAGUEMASTER] = "Plaguemaster", [ROLE_ILLUSIONIST] = "Illusionist", - [ROLE_CANNIBAL] = "Cannibal" + [ROLE_CANNIBAL] = "Cannibal", + [ROLE_TASKMASTER] = "Taskmaster" } ROLE_STRINGS_PLURAL = { @@ -703,7 +706,8 @@ ROLE_STRINGS_PLURAL = { [ROLE_EVILTWIN] = "Evil Twins", [ROLE_PLAGUEMASTER] = "Plaguemasters", [ROLE_ILLUSIONIST] = "Illusionists", - [ROLE_CANNIBAL] = "Cannibals" + [ROLE_CANNIBAL] = "Cannibals", + [ROLE_TASKMASTER] = "Taskmasters" } ROLE_STRINGS_EXT = { @@ -759,7 +763,8 @@ ROLE_STRINGS_EXT = { [ROLE_EVILTWIN] = "an Evil Twin", [ROLE_PLAGUEMASTER] = "a Plaguemaster", [ROLE_ILLUSIONIST] = "an Illusionist", - [ROLE_CANNIBAL] = "a Cannibal" + [ROLE_CANNIBAL] = "a Cannibal", + [ROLE_TASKMASTER] = "a Taskmaster" } ROLE_STRINGS_SHORT = { @@ -815,7 +820,8 @@ ROLE_STRINGS_SHORT = { [ROLE_EVILTWIN] = "etw", [ROLE_PLAGUEMASTER] = "plm", [ROLE_ILLUSIONIST] = "ill", - [ROLE_CANNIBAL] = "can" + [ROLE_CANNIBAL] = "can", + [ROLE_TASKMASTER] = "tsk" } function StartsWithVowel(word) @@ -1400,8 +1406,9 @@ WIN_VINDICATOR = 18 WIN_INFECTED = 19 WIN_PLAGUEMASTER = 20 WIN_CANNIBAL = 21 +WIN_TASKMASTER = 22 -WIN_MAX = WIN_MAX or 21 +WIN_MAX = WIN_MAX or 22 WINS_BY_ROLE = WINS_BY_ROLE or {} if SERVER then From c3183fec8565d77f3544d07893336dead0217303 Mon Sep 17 00:00:00 2001 From: NoxxFlame Date: Tue, 12 Aug 2025 16:58:36 +0100 Subject: [PATCH 003/140] Added the ability for tasks that require specific features to block other tasks that require the same features --- .../gamemode/roles/taskmaster/shared.lua | 4 +- .../gamemode/roles/taskmaster/taskmaster.lua | 20 ++++++++++ .../roles/taskmaster/tasks/testkilltask.lua | 38 +++++++++++++++++++ .../roles/taskmaster/tasks/testmisctask.lua | 37 ++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua index 73004d14e..c8006afdf 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -1,7 +1,7 @@ AddCSLuaFile() ROLE_STARTING_CREDITS[ROLE_TASKMASTER] = 1 -ROLE_HAS_PASSIVE_WIN[ROLE_TASKMASTER] = true +ROLE_HAS_PASSIVE_WIN[ROLE_TASKMASTER] = false cvars.AddChangeCallback("ttt_taskmaster_is_passive", function(_, _, newValue) ROLE_HAS_PASSIVE_WIN[ROLE_TASKMASTER] = util.tobool(newValue) end) @@ -16,7 +16,7 @@ CreateConVar("ttt_taskmaster_completion_bonus", "1", FCVAR_REPLICATED, "How many CreateConVar("ttt_taskmaster_blocks_team_wins", "1", FCVAR_REPLICATED, "Whether the Taskmaster should block teams (innocent, traitor, monster) from winning if they are alive and haven't finished their tasks.") CreateConVar("ttt_taskmaster_win_block_length", "60", FCVAR_REPLICATED, "How long (in seconds) the Taskmaster should block teams (innocent, traitor, monster) from winning for (if 'ttt_taskmaster_blocks_team_wins' is enabled). Set to 0 to block until time runs out", 0, 300) CreateConVar("ttt_taskmaster_wins_with_others", "1", FCVAR_REPLICATED, "If the Taskmaster should be allowed to win alongside other teams/players") -CreateConVar("ttt_taskmaster_is_passive", "1", FCVAR_REPLICATED, "Whether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if 'ttt_taskmaster_wins_with_others' is enabled)") +CreateConVar("ttt_taskmaster_is_passive", "0", FCVAR_REPLICATED, "Whether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if 'ttt_taskmaster_wins_with_others' is enabled)") ROLE_CONVARS[ROLE_TASKMASTER] = {} diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index dc71bdf62..8b74b70d5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -37,6 +37,26 @@ function plymeta:AssignTask(isKillTask, index) for _, id in ipairs(taskIds) do if taskList[id].CanAssignTask(self) then + local blockedByFeature = false + for _, feature in pairs(taskList[id].RequiredFeatures) do + for _, killTaskId in pairs(self.taskmasterKillTasks) do + if table.HasValue(TASKMASTER.killTasks[killTaskId].RequiredFeatures, feature) then + blockedByFeature = true + break + end + end + if blockedByFeature then break end + + for _, miscTaskId in pairs(self.taskmasterMiscTasks) do + if table.HasValue(TASKMASTER.miscTasks[miscTaskId].RequiredFeatures, feature) then + blockedByFeature = true + break + end + end + if blockedByFeature then break end + end + if blockedByFeature then continue end + taskList[id].OnTaskAssigned(self) if index then diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua new file mode 100644 index 000000000..fc1b1c01f --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua @@ -0,0 +1,38 @@ +local TASK = {} + +TASK.id = "testkilltask" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Test Kill Task" +end + +TASK.Description = function(ply) + return "Test Kill Task Description" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return false + end + + TASK.RequiredFeatures = { + --"TargetIDPlayerText", + --"TargetIDPlayerTargetIcon", + --"ProgressBar" + } + + TASK.OnTaskAssigned = function(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Task Assigned: " .. TASK.Name(ply)) + end + + TASK.OnTaskRemoved = function(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Task Removed: " .. TASK.Name(ply)) + end + + TASK.OnTaskComplete = function(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Task Completed: " .. TASK.Name(ply)) + end +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua new file mode 100644 index 000000000..506b0bdf6 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua @@ -0,0 +1,37 @@ +local TASK = {} + +TASK.id = "testmisctask" + +TASK.Name = function(ply) + return "Test Misc Task" +end + +TASK.Description = function(ply) + return "Test Misc Task Description" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return false + end + + TASK.RequiredFeatures = { + --"TargetIDPlayerText", + --"TargetIDPlayerTargetIcon", + --"ProgressBar" + } + + TASK.OnTaskAssigned = function(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Task Assigned: " .. TASK.Name(ply)) + end + + TASK.OnTaskRemoved = function(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Task Removed: " .. TASK.Name(ply)) + end + + TASK.OnTaskComplete = function(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Task Completed: " .. TASK.Name(ply)) + end +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From b84a3a3dbef95dc18d8767347addc07c32ae66bd Mon Sep 17 00:00:00 2001 From: NoxxFlame Date: Wed, 13 Aug 2025 15:33:00 +0100 Subject: [PATCH 004/140] Added 'Kill Nearby Player' and 'Kill Faraway Player' tasks --- .../gamemode/roles/taskmaster/taskmaster.lua | 49 ++++-- .../roles/taskmaster/tasks/killfaraway.lua | 159 ++++++++++++++++++ .../roles/taskmaster/tasks/killnearby.lua | 159 ++++++++++++++++++ 3 files changed, 351 insertions(+), 16 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index 8b74b70d5..3f9907e86 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -40,32 +40,37 @@ function plymeta:AssignTask(isKillTask, index) local blockedByFeature = false for _, feature in pairs(taskList[id].RequiredFeatures) do for _, killTaskId in pairs(self.taskmasterKillTasks) do - if table.HasValue(TASKMASTER.killTasks[killTaskId].RequiredFeatures, feature) then - blockedByFeature = true - break + if not table.HasValue(self.taskmasterCompletedTasks, killTaskId) and not table.HasValue(self.taskmasterRerolledTasks, killTaskId) then + if table.HasValue(TASKMASTER.killTasks[killTaskId].RequiredFeatures, feature) then + blockedByFeature = true + break + end end end if blockedByFeature then break end for _, miscTaskId in pairs(self.taskmasterMiscTasks) do - if table.HasValue(TASKMASTER.miscTasks[miscTaskId].RequiredFeatures, feature) then - blockedByFeature = true - break + if not table.HasValue(self.taskmasterCompletedTasks, miscTaskId) and not table.HasValue(self.taskmasterRerolledTasks, miscTaskId) then + if table.HasValue(TASKMASTER.miscTasks[miscTaskId].RequiredFeatures, feature) then + blockedByFeature = true + break + end end end if blockedByFeature then break end end - if blockedByFeature then continue end - taskList[id].OnTaskAssigned(self) + if not blockedByFeature then + taskList[id].OnTaskAssigned(self) - if index then - table.insert(self[activeTasksName], index, id) - else - table.insert(self[activeTasksName], id) + if index then + table.insert(self[activeTasksName], index, id) + else + table.insert(self[activeTasksName], id) + end + self:SetProperty(activeTasksName, self[activeTasksName], self) + return taskList[id] end - self:SetProperty(activeTasksName, self[activeTasksName], self) - return taskList[id] end end -- TODO: Handle edge case where there are no valid tasks. Maybe we need to change their role? Or just have a free fallback task? @@ -73,8 +78,6 @@ function plymeta:AssignTask(isKillTask, index) end function plymeta:RemoveTask(taskId) - if not self:IsTaskmaster() then return end - local isKillTask = TASKMASTER.killTasks[taskId] and true or false local taskList = isKillTask and TASKMASTER.killTasks or TASKMASTER.miscTasks local activeTasksName = isKillTask and "taskmasterKillTasks" or "taskmasterMiscTasks" @@ -149,6 +152,8 @@ function plymeta:CompleteTask(taskId) }) end + -- TODO: Play a sound and/or send a message when a task is completed + return true end return false @@ -244,6 +249,18 @@ end) ------------- local function CleanupTasks(ply) + if ply.taskmasterKillTasks then + for _, id in pairs(ply.taskmasterKillTasks) do + ply:RemoveTask(id) + end + end + + if ply.taskmasterMiscTasks then + for _, id in pairs(ply.taskmasterMiscTasks) do + ply:RemoveTask(id) + end + end + ply:ClearProperty("taskmasterKillTasks", ply) ply:ClearProperty("taskmasterMiscTasks", ply) ply:ClearProperty("taskmasterCompletedTasks", ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua new file mode 100644 index 000000000..c104e9a38 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua @@ -0,0 +1,159 @@ +local TASK = {} + +TASK.id = "killfaraway" +TASK.isKillTask = true + +local taskmaster_killfaraway_range = CreateConVar("ttt_taskmaster_killfaraway_range", "25", FCVAR_REPLICATED, "The minimum distance (in meters) away a player can be to count for the 'Kill Faraway Player' task", 0, 100) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_killfaraway_range", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + return "Kill Faraway Player" +end + +TASK.Description = function(ply) + local unit + if SERVER then + unit = 1 + else + unit = cvars.Number("ttt_distance_unit", 1) + end + local range = taskmaster_killfaraway_range:GetInt() + local description = "Kill a player from outside " + if unit == 1 then + description = description .. range .. " meter" + if range ~= 1 then + description = description .. "s" + end + elseif unit == 2 then + description = description .. math.ceil(range / UNITS_PER_FOOT) .. " feet" + else + local convertedRange = math.ceil(range * UNITS_PER_METER) + description = description .. convertedRange .. " unit" + if convertedRange ~= 1 then + description = description .. "s" + end + end + return description +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_KillFaraway_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillFaraway_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + "TargetIDPlayerText", + "ParticleRadius" + } + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillFaraway_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + local distanceSqr = victim:GetPos():DistToSqr(attacker:GetPos()) + local range = taskmaster_killfaraway_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + + if (distanceSqr >= rangeSqr) then + ply:CompleteTask(TASK.id) + end + end) + + net.Start("TTT_Taskmaster_KillFaraway_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillFaraway_PlayerDeath_" .. ply:SteamID64()) + net.Start("TTT_Taskmaster_KillFaraway_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillFaraway_PlayerDeath_" .. ply:SteamID64()) + net.Start("TTT_Taskmaster_KillFaraway_Cleanup") + net.Send(ply) + end +end + +if CLIENT then + net.Receive("TTT_Taskmaster_KillFaraway_Assigned", function() + local sid64 = LocalPlayer():SteamID64() + + hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillFaraway_TTTTargetIDPlayerText_" .. sid64, function(ent, cli, text, col, secondaryText) + if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end + + local distanceSqr = ent:GetPos():DistToSqr(cli:GetPos()) + local range = taskmaster_killfaraway_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + + if distanceSqr >= rangeSqr then + return "IN RANGE", ROLE_COLORS_RADAR[ROLE_INNOCENT] + else + return "TOO CLOSE", ROLE_COLORS_RADAR[ROLE_TRAITOR] + end + end) + + hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillFaraway_TTTPlayerAliveClientThink_" .. sid64, function(client, ply) + local shouldDraw = false + if ply == client and client:IsActiveTaskmaster() then + local pos = ply:GetPos() + if not ply.TaskmasterRadiusEmitter then ply.TaskmasterRadiusEmitter = ParticleEmitter(pos) end + if not ply.TaskmasterRadiusNextPart then ply.TaskmasterRadiusNextPart = CurTime() end + if not ply.TaskmasterRadiusDir then ply.TaskmasterRadiusDir = 0 end + if ply.TaskmasterRadiusNextPart < CurTime() then + for _ = 1, 48 do + ply.TaskmasterRadiusEmitter:SetPos(pos) + ply.TaskmasterRadiusNextPart = CurTime() + 0.005 + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + math.pi / 24 + local radius = taskmaster_killfaraway_range:GetInt() * UNITS_PER_METER + local vec = Vector(math.sin(ply.TaskmasterRadiusDir) * radius, math.cos(ply.TaskmasterRadiusDir) * radius, 10) + local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) + particle:SetVelocity(Vector(0, 0, 80)) + particle:SetDieTime(0.5) + particle:SetStartAlpha(200) + particle:SetEndAlpha(0) + particle:SetStartSize(3) + particle:SetEndSize(2) + particle:SetRoll(math.Rand(0, math.pi)) + particle:SetRollDelta(0) + particle:SetColor(255, 255, 255) + end + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + 0.02 + end + shouldDraw = true + end + + if not shouldDraw and ply.TaskmasterRadiusEmitter then + ply.TaskmasterRadiusEmitter:Finish() + ply.TaskmasterRadiusEmitter = nil + ply.TaskmasterRadiusDir = nil + ply.TaskmasterRadiusNextPart = nil + end + end) + end) + + net.Receive("TTT_Taskmaster_KillFaraway_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillFaraway_TTTTargetIDPlayerText_" .. sid64) + hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_KillFaraway_TTTPlayerAliveClientThink_" .. sid64) + + if client.TaskmasterRadiusEmitter then + client.TaskmasterRadiusEmitter:Finish() + client.TaskmasterRadiusEmitter = nil + client.TaskmasterRadiusDir = nil + client.TaskmasterRadiusNextPart = nil + end + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua new file mode 100644 index 000000000..aec39579e --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua @@ -0,0 +1,159 @@ +local TASK = {} + +TASK.id = "killnearby" +TASK.isKillTask = true + +local taskmaster_killnearby_range = CreateConVar("ttt_taskmaster_killnearby_range", "5", FCVAR_REPLICATED, "The maximum distance (in meters) away a player can be to count for the 'Kill Nearby Player' task", 0, 10) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_killnearby_range", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + return "Kill Nearby Player" +end + +TASK.Description = function(ply) + local unit + if SERVER then + unit = 1 + else + unit = cvars.Number("ttt_distance_unit", 1) + end + local range = taskmaster_killnearby_range:GetInt() + local description = "Kill a player from within " + if unit == 1 then + description = description .. range .. " meter" + if range ~= 1 then + description = description .. "s" + end + elseif unit == 2 then + description = description .. math.ceil(range / UNITS_PER_FOOT) .. " feet" + else + local convertedRange = math.ceil(range * UNITS_PER_METER) + description = description .. convertedRange .. " unit" + if convertedRange ~= 1 then + description = description .. "s" + end + end + return description +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_KillNearby_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillNearby_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + "TargetIDPlayerText", + "ParticleRadius" + } + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillNearby_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + local distanceSqr = victim:GetPos():DistToSqr(attacker:GetPos()) + local range = taskmaster_killnearby_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + + if (distanceSqr <= rangeSqr) then + ply:CompleteTask(TASK.id) + end + end) + + net.Start("TTT_Taskmaster_KillNearby_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillNearby_PlayerDeath_" .. ply:SteamID64()) + net.Start("TTT_Taskmaster_KillNearby_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillNearby_PlayerDeath_" .. ply:SteamID64()) + net.Start("TTT_Taskmaster_KillNearby_Cleanup") + net.Send(ply) + end +end + +if CLIENT then + net.Receive("TTT_Taskmaster_KillNearby_Assigned", function() + local sid64 = LocalPlayer():SteamID64() + + hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillNearby_TTTTargetIDPlayerText_" .. sid64, function(ent, cli, text, col, secondaryText) + if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end + + local distanceSqr = ent:GetPos():DistToSqr(cli:GetPos()) + local range = taskmaster_killnearby_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + + if distanceSqr <= rangeSqr then + return "IN RANGE", ROLE_COLORS_RADAR[ROLE_INNOCENT] + else + return "TOO FAR", ROLE_COLORS_RADAR[ROLE_TRAITOR] + end + end) + + hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillNearby_TTTPlayerAliveClientThink_" .. sid64, function(client, ply) + local shouldDraw = false + if ply == client and client:IsActiveTaskmaster() then + local pos = ply:GetPos() + if not ply.TaskmasterRadiusEmitter then ply.TaskmasterRadiusEmitter = ParticleEmitter(pos) end + if not ply.TaskmasterRadiusNextPart then ply.TaskmasterRadiusNextPart = CurTime() end + if not ply.TaskmasterRadiusDir then ply.TaskmasterRadiusDir = 0 end + if ply.TaskmasterRadiusNextPart < CurTime() then + for _ = 1, 24 do + ply.TaskmasterRadiusEmitter:SetPos(pos) + ply.TaskmasterRadiusNextPart = CurTime() + 0.02 + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + math.pi / 12 + local radius = taskmaster_killnearby_range:GetInt() * UNITS_PER_METER + local vec = Vector(math.sin(ply.TaskmasterRadiusDir) * radius, math.cos(ply.TaskmasterRadiusDir) * radius, 10) + local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) + particle:SetVelocity(Vector(0, 0, 40)) + particle:SetDieTime(0.25) + particle:SetStartAlpha(60) + particle:SetEndAlpha(0) + particle:SetStartSize(3) + particle:SetEndSize(2) + particle:SetRoll(math.Rand(0, math.pi)) + particle:SetRollDelta(0) + particle:SetColor(255, 255, 255) + end + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + 0.02 + end + shouldDraw = true + end + + if not shouldDraw and ply.TaskmasterRadiusEmitter then + ply.TaskmasterRadiusEmitter:Finish() + ply.TaskmasterRadiusEmitter = nil + ply.TaskmasterRadiusDir = nil + ply.TaskmasterRadiusNextPart = nil + end + end) + end) + + net.Receive("TTT_Taskmaster_KillNearby_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillNearby_TTTTargetIDPlayerText_" .. sid64) + hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_KillNearby_TTTPlayerAliveClientThink_" .. sid64) + + if client.TaskmasterRadiusEmitter then + client.TaskmasterRadiusEmitter:Finish() + client.TaskmasterRadiusEmitter = nil + client.TaskmasterRadiusDir = nil + client.TaskmasterRadiusNextPart = nil + end + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From d4a31d7611b887e67107a2c5e9ca1548e27c5eb6 Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 13 Aug 2025 18:30:20 -0400 Subject: [PATCH 005/140] Added new conversion constant --- gamemodes/terrortown/gamemode/shared.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gamemodes/terrortown/gamemode/shared.lua b/gamemodes/terrortown/gamemode/shared.lua index 0293a0feb..2fdb8b4d4 100644 --- a/gamemodes/terrortown/gamemode/shared.lua +++ b/gamemodes/terrortown/gamemode/shared.lua @@ -1507,6 +1507,8 @@ UNITS_PER_METER = 39.37 UNITS_PER_FIVE_METERS = UNITS_PER_METER * 5 UNITS_PER_SIX_METERS = UNITS_PER_METER * 6 +FEET_PER_METER = 3.28084 + -- Message queue modes MSG_PRINTBOTH = 1 MSG_PRINTTALK = 3 -- Keep this the same value as HUD_PRINTTALK just in case From 5efb1d2c9c4fc87379d8347ba8bd56fd2b9a2a9b Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 13 Aug 2025 18:30:54 -0400 Subject: [PATCH 006/140] Cleanup --- .../gamemode/roles/shadow/cl_shadow.lua | 7 +-- .../roles/taskmaster/cl_taskmaster.lua | 5 +- .../roles/taskmaster/tasks/killfaraway.lua | 46 ++++++++++++------- .../roles/taskmaster/tasks/killnearby.lua | 46 ++++++++++++------- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua b/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua index 5f51224ca..ff5decdeb 100644 --- a/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua +++ b/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua @@ -280,11 +280,12 @@ end) -- PARTICLES -- --------------- -local function DrawRadius(ply, ent, r) +local function DrawRadius(ply, ent, radius) if not ent.RadiusEmitter then ent.RadiusEmitter = ParticleEmitter(ent:GetPos()) end if not ent.RadiusNextPart then ent.RadiusNextPart = CurTime() end if not ent.RadiusDir then ent.RadiusDir = 0 end local pos = ent:GetPos() + Vector(0, 0, 30) + local radiusSqr = radius * radius -- Use DistToSqr as it's more efficient and this is called very frequently -- 9000000 = 3000^2 @@ -293,7 +294,7 @@ local function DrawRadius(ply, ent, r) ent.RadiusEmitter:SetPos(pos) ent.RadiusNextPart = CurTime() + 0.02 ent.RadiusDir = ent.RadiusDir + MathPi / 12 - local vec = Vector(MathSin(ent.RadiusDir) * r, MathCos(ent.RadiusDir) * r, 10) + local vec = Vector(MathSin(ent.RadiusDir) * radius, MathCos(ent.RadiusDir) * radius, 10) local particle = ent.RadiusEmitter:Add("particle/wisp.vmt", ent:GetPos() + vec) particle:SetVelocity(Vector(0, 0, 80)) particle:SetDieTime(0.5) @@ -304,7 +305,7 @@ local function DrawRadius(ply, ent, r) particle:SetRoll(MathRand(0, MathPi)) particle:SetRollDelta(0) local color = ROLE_COLORS[ROLE_TRAITOR] - if ply:GetPos():DistToSqr(ent:GetPos()) <= (r * r) then + if ply:GetPos():DistToSqr(ent:GetPos()) <= radiusSqr then color = ROLE_COLORS[ROLE_INNOCENT] end particle:SetColor(color.r, color.g, color.b) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 9bce5e9d2..78c96574b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -40,7 +40,6 @@ hook.Add("TTTSettingsRolesTabSections", "Taskmaster_TTTSettingsRolesTabSections" return true end) - ---------------- -- WIN CHECKS -- ---------------- @@ -217,8 +216,8 @@ hook.Add("HUDPaint", "Taskmaster_HUDPaint", function() maxHeight = height - yOffset:GetInt() end) -hook.Add("TTTHUDInfoPaint", "Taskmaster_TTTHUDInfoPaint", function(client, label_left, label_top, active_labels) - if client:IsActiveTaskmaster() then +hook.Add("TTTHUDInfoPaint", "Taskmaster_TTTHUDInfoPaint", function(cli, label_left, label_top, active_labels) + if cli:IsActiveTaskmaster() then local blockEnd = GetGlobalFloat("taskmaster_block_end", 0) if blockEnd > 0 then surface.SetFont("TabLarge") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua index c104e9a38..405956cc8 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua @@ -1,3 +1,15 @@ +local CurTime = CurTime +local cvars = cvars +local hook = hook +local math = math +local net = net +local table = table + +local MathCos = math.cos +local MathPi = math.pi +local MathRand = math.Rand +local MathSin = math.sin + local TASK = {} TASK.id = "killfaraway" @@ -29,7 +41,13 @@ TASK.Description = function(ply) description = description .. "s" end elseif unit == 2 then - description = description .. math.ceil(range / UNITS_PER_FOOT) .. " feet" + local convertedRange = math.ceil(range * FEET_PER_METER) + description = description .. convertedRange + if convertedRange == 1 then + description = description .. " foot" + else + description = description .. " feet" + end else local convertedRange = math.ceil(range * UNITS_PER_METER) description = description .. convertedRange .. " unit" @@ -62,7 +80,7 @@ if SERVER then local range = taskmaster_killfaraway_range:GetInt() * UNITS_PER_METER local rangeSqr = range * range - if (distanceSqr >= rangeSqr) then + if distanceSqr >= rangeSqr then ply:CompleteTask(TASK.id) end end) @@ -77,24 +95,19 @@ if SERVER then net.Send(ply) end - TASK.OnTaskComplete = function(ply) - hook.Remove("PlayerDeath", "Taskmaster_KillFaraway_PlayerDeath_" .. ply:SteamID64()) - net.Start("TTT_Taskmaster_KillFaraway_Cleanup") - net.Send(ply) - end + TASK.OnTaskComplete = TASK.OnTaskRemoved end if CLIENT then net.Receive("TTT_Taskmaster_KillFaraway_Assigned", function() local sid64 = LocalPlayer():SteamID64() + local range = taskmaster_killfaraway_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillFaraway_TTTTargetIDPlayerText_" .. sid64, function(ent, cli, text, col, secondaryText) if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end local distanceSqr = ent:GetPos():DistToSqr(cli:GetPos()) - local range = taskmaster_killfaraway_range:GetInt() * UNITS_PER_METER - local rangeSqr = range * range - if distanceSqr >= rangeSqr then return "IN RANGE", ROLE_COLORS_RADAR[ROLE_INNOCENT] else @@ -102,9 +115,9 @@ if CLIENT then end end) - hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillFaraway_TTTPlayerAliveClientThink_" .. sid64, function(client, ply) + hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillFaraway_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) local shouldDraw = false - if ply == client and client:IsActiveTaskmaster() then + if ply == cli and cli:IsActiveTaskmaster() then local pos = ply:GetPos() if not ply.TaskmasterRadiusEmitter then ply.TaskmasterRadiusEmitter = ParticleEmitter(pos) end if not ply.TaskmasterRadiusNextPart then ply.TaskmasterRadiusNextPart = CurTime() end @@ -113,9 +126,8 @@ if CLIENT then for _ = 1, 48 do ply.TaskmasterRadiusEmitter:SetPos(pos) ply.TaskmasterRadiusNextPart = CurTime() + 0.005 - ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + math.pi / 24 - local radius = taskmaster_killfaraway_range:GetInt() * UNITS_PER_METER - local vec = Vector(math.sin(ply.TaskmasterRadiusDir) * radius, math.cos(ply.TaskmasterRadiusDir) * radius, 10) + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + MathPi / 24 + local vec = Vector(MathSin(ply.TaskmasterRadiusDir) * range, MathCos(ply.TaskmasterRadiusDir) * range, 10) local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) particle:SetVelocity(Vector(0, 0, 80)) particle:SetDieTime(0.5) @@ -123,7 +135,7 @@ if CLIENT then particle:SetEndAlpha(0) particle:SetStartSize(3) particle:SetEndSize(2) - particle:SetRoll(math.Rand(0, math.pi)) + particle:SetRoll(MathRand(0, MathPi)) particle:SetRollDelta(0) particle:SetColor(255, 255, 255) end @@ -146,7 +158,7 @@ if CLIENT then local sid64 = client:SteamID64() hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillFaraway_TTTTargetIDPlayerText_" .. sid64) hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_KillFaraway_TTTPlayerAliveClientThink_" .. sid64) - + if client.TaskmasterRadiusEmitter then client.TaskmasterRadiusEmitter:Finish() client.TaskmasterRadiusEmitter = nil diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua index aec39579e..5bdfcec15 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua @@ -1,3 +1,15 @@ +local CurTime = CurTime +local cvars = cvars +local hook = hook +local math = math +local net = net +local table = table + +local MathCos = math.cos +local MathPi = math.pi +local MathRand = math.Rand +local MathSin = math.sin + local TASK = {} TASK.id = "killnearby" @@ -29,7 +41,13 @@ TASK.Description = function(ply) description = description .. "s" end elseif unit == 2 then - description = description .. math.ceil(range / UNITS_PER_FOOT) .. " feet" + local convertedRange = math.ceil(range * FEET_PER_METER) + description = description .. convertedRange + if convertedRange == 1 then + description = description .. " foot" + else + description = description .. " feet" + end else local convertedRange = math.ceil(range * UNITS_PER_METER) description = description .. convertedRange .. " unit" @@ -62,7 +80,7 @@ if SERVER then local range = taskmaster_killnearby_range:GetInt() * UNITS_PER_METER local rangeSqr = range * range - if (distanceSqr <= rangeSqr) then + if distanceSqr <= rangeSqr then ply:CompleteTask(TASK.id) end end) @@ -77,24 +95,19 @@ if SERVER then net.Send(ply) end - TASK.OnTaskComplete = function(ply) - hook.Remove("PlayerDeath", "Taskmaster_KillNearby_PlayerDeath_" .. ply:SteamID64()) - net.Start("TTT_Taskmaster_KillNearby_Cleanup") - net.Send(ply) - end + TASK.OnTaskComplete = TASK.OnTaskRemoved end if CLIENT then net.Receive("TTT_Taskmaster_KillNearby_Assigned", function() local sid64 = LocalPlayer():SteamID64() + local range = taskmaster_killnearby_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillNearby_TTTTargetIDPlayerText_" .. sid64, function(ent, cli, text, col, secondaryText) if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end local distanceSqr = ent:GetPos():DistToSqr(cli:GetPos()) - local range = taskmaster_killnearby_range:GetInt() * UNITS_PER_METER - local rangeSqr = range * range - if distanceSqr <= rangeSqr then return "IN RANGE", ROLE_COLORS_RADAR[ROLE_INNOCENT] else @@ -102,9 +115,9 @@ if CLIENT then end end) - hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillNearby_TTTPlayerAliveClientThink_" .. sid64, function(client, ply) + hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillNearby_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) local shouldDraw = false - if ply == client and client:IsActiveTaskmaster() then + if ply == cli and cli:IsActiveTaskmaster() then local pos = ply:GetPos() if not ply.TaskmasterRadiusEmitter then ply.TaskmasterRadiusEmitter = ParticleEmitter(pos) end if not ply.TaskmasterRadiusNextPart then ply.TaskmasterRadiusNextPart = CurTime() end @@ -113,9 +126,8 @@ if CLIENT then for _ = 1, 24 do ply.TaskmasterRadiusEmitter:SetPos(pos) ply.TaskmasterRadiusNextPart = CurTime() + 0.02 - ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + math.pi / 12 - local radius = taskmaster_killnearby_range:GetInt() * UNITS_PER_METER - local vec = Vector(math.sin(ply.TaskmasterRadiusDir) * radius, math.cos(ply.TaskmasterRadiusDir) * radius, 10) + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + MathPi / 12 + local vec = Vector(MathSin(ply.TaskmasterRadiusDir) * range, MathCos(ply.TaskmasterRadiusDir) * range, 10) local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) particle:SetVelocity(Vector(0, 0, 40)) particle:SetDieTime(0.25) @@ -123,7 +135,7 @@ if CLIENT then particle:SetEndAlpha(0) particle:SetStartSize(3) particle:SetEndSize(2) - particle:SetRoll(math.Rand(0, math.pi)) + particle:SetRoll(MathRand(0, MathPi)) particle:SetRollDelta(0) particle:SetColor(255, 255, 255) end @@ -146,7 +158,7 @@ if CLIENT then local sid64 = client:SteamID64() hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillNearby_TTTTargetIDPlayerText_" .. sid64) hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_KillNearby_TTTPlayerAliveClientThink_" .. sid64) - + if client.TaskmasterRadiusEmitter then client.TaskmasterRadiusEmitter:Finish() client.TaskmasterRadiusEmitter = nil From 210773983351cab4e15db76d0d8b97fad03d6987 Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 13 Aug 2025 18:32:59 -0400 Subject: [PATCH 007/140] Luacheck fixes --- .luacheckrc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.luacheckrc b/.luacheckrc index 2dae956eb..19594c979 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -897,6 +897,7 @@ globals = { "EVENT_VINDICATORFAIL", "EVENT_VINDICATORSUCCESS", "EVENT_ZOMBIFIED", + "FEET_PER_METER", "GLITCH_CHAT_NONE", "GLITCH_CHAT_BLOCK_ALWAYS", "GLITCH_CHAT_BLOCK_WHILE_ALIVE", @@ -1078,6 +1079,7 @@ globals = { "WIN_PLAGUEMASTER", "WIN_SHADOW", "WIN_SPONGE", + "WIN_TASKMASTER", "WIN_TIMELIMIT", "WIN_TRAITOR", "WIN_VAMPIRE", From b1fc57cdb487ae3813bdb85a5e6613ed7d9b7337 Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 13 Aug 2025 18:33:46 -0400 Subject: [PATCH 008/140] Added Stay Near Target task (without progress bar) --- .../roles/taskmaster/tasks/stayneartarget.lua | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua new file mode 100644 index 000000000..fc0e7bfd9 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -0,0 +1,223 @@ +local CurTime = CurTime +local cvars = cvars +local hook = hook +local math = math +local net = net +local player = player +local table = table + +local MathCos = math.cos +local MathPi = math.pi +local MathRand = math.Rand +local MathSin = math.sin + +local TASK = {} + +TASK.id = "stayneartarget" + +local taskmaster_stayneartarget_range = CreateConVar("ttt_taskmaster_stayneartarget_range", "5", FCVAR_REPLICATED, "The distance (in meters) away a player must stay within to count for the 'Stay Near Target' task", 0, 100) +local taskmaster_stayneartarget_time = CreateConVar("ttt_taskmaster_stayneartarget_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay near their target to count for the 'Stay Near Target' task", 0, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_stayneartarget_range", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_stayneartarget_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local name = "Player" + if IsPlayer(ply.StayNearTargetPlayer) then + name = ply.StayNearTargetPlayer:Nick() + end + return "Stay Near " .. name +end + +TASK.Description = function(ply) + local unit + if SERVER then + unit = 1 + else + unit = cvars.Number("ttt_distance_unit", 1) + end + local range = taskmaster_stayneartarget_range:GetInt() + local description = "Stay within " + if unit == 1 then + description = description .. range .. " meter" + if range ~= 1 then + description = description .. "s" + end + elseif unit == 2 then + local convertedRange = math.ceil(range * FEET_PER_METER) + description = description .. convertedRange + if convertedRange == 1 then + description = description .. " foot" + else + description = description .. " feet" + end + else + local convertedRange = math.ceil(range * UNITS_PER_METER) + description = description .. convertedRange .. " unit" + if convertedRange ~= 1 then + description = description .. "s" + end + end + + local name = "Target" + if IsPlayer(ply.StayNearTargetPlayer) then + name = ply.StayNearTargetPlayer:Nick() + end + + local time = taskmaster_stayneartarget_time:GetInt() + description = description .. " of " .. name .. " for " .. time .. " second" + if time == 1 then + return description + end + return description .. "s" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_StayNearTarget_Assigned") + util.AddNetworkString("TTT_Taskmaster_StayNearTarget_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + "TargetIDPlayerTargetIcon", + "ProgressBar", + "ParticleRadius" + } + + TASK.OnTaskAssigned = function(ply) + local target + -- Find the first random living player + for _, p in RandomPairs(player.GetAll()) do + if not p:Alive() or p:IsSpec() then continue end + if p == ply then continue end + target = p + break + end + + ply:SetProperty("StayNearTargetPlayer", target, ply) + + local range = taskmaster_stayneartarget_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + local time = taskmaster_stayneartarget_time:GetInt() + timer.Create("TTTTaskmasterStayNearTargetTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + if not IsPlayer(target) then return end + + -- Within range + if ply:GetPos():DistToSqr(target:GetPos()) <= rangeSqr then + -- Just starting + if not ply.StayNearTargetStart then + ply.StayNearTargetStart = CurTime() + -- Long enough + elseif CurTime() > ply.StayNearTargetStart + time then + ply:CompleteTask(TASK.id) + end + -- Not within range + else + ply:ClearProperty("StayNearTargetStart") + end + end) + + net.Start("TTT_Taskmaster_StayNearTarget_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterStayNearTargetTimer") + + ply:ClearProperty("StayNearTargetPlayer") + ply:ClearProperty("StayNearTargetStart") + + net.Start("TTT_Taskmaster_StayNearTarget_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_StayNearTarget_Assigned", function() + local sid64 = LocalPlayer():SteamID64() + local range = taskmaster_stayneartarget_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + + hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_StayNearTarget_TTTTargetIDPlayerTargetIcon_" .. sid64, function(ply, cli, showJester) + if cli:IsActiveTaskmaster() and ply == cli.StayNearTargetPlayer then + local iconColor = ROLE_COLORS_SPRITE[ROLE_TRAITOR] + if cli:GetPos():DistToSqr(ply:GetPos()) <= rangeSqr then + iconColor = ROLE_COLORS_SPRITE[ROLE_INNOCENT] + end + return "task", true, iconColor, "up" + end + end) + + hook.Add("TTTPlayerAliveClientThink", "Taskmaster_StayNearTarget_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) + local shouldDraw = false + local target = cli.StayNearTargetPlayer + if ply == cli and cli:IsActiveTaskmaster() and IsPlayer(target) then + local pos = target:GetPos() + if not ply.TaskmasterRadiusEmitter then ply.TaskmasterRadiusEmitter = ParticleEmitter(pos) end + if not ply.TaskmasterRadiusNextPart then ply.TaskmasterRadiusNextPart = CurTime() end + if not ply.TaskmasterRadiusDir then ply.TaskmasterRadiusDir = 0 end + -- Use DistToSqr as it's more efficient and this is called very frequently + -- 9000000 = 3000^2 + if ply.TaskmasterRadiusNextPart < CurTime() and cli:GetPos():DistToSqr(pos) <= 9000000 then + for _ = 1, 48 do + ply.TaskmasterRadiusEmitter:SetPos(pos) + ply.TaskmasterRadiusNextPart = CurTime() + 0.005 + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + MathPi / 24 + local vec = Vector(MathSin(ply.TaskmasterRadiusDir) * range, MathCos(ply.TaskmasterRadiusDir) * range, 10) + local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) + particle:SetVelocity(Vector(0, 0, 80)) + particle:SetDieTime(0.5) + particle:SetStartAlpha(200) + particle:SetEndAlpha(0) + particle:SetStartSize(3) + particle:SetEndSize(2) + particle:SetRoll(MathRand(0, MathPi)) + particle:SetRollDelta(0) + local color = ROLE_COLORS[ROLE_TRAITOR] + if ply:GetPos():DistToSqr(target:GetPos()) <= rangeSqr then + color = ROLE_COLORS[ROLE_INNOCENT] + end + particle:SetColor(color.r, color.g, color.b) + end + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + 0.02 + end + shouldDraw = true + end + + if not shouldDraw and ply.TaskmasterRadiusEmitter then + ply.TaskmasterRadiusEmitter:Finish() + ply.TaskmasterRadiusEmitter = nil + ply.TaskmasterRadiusDir = nil + ply.TaskmasterRadiusNextPart = nil + end + end) + end) + + net.Receive("TTT_Taskmaster_StayNearTarget_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("TTTTargetIDPlayerTargetIcon", "Taskmaster_StayNearTarget_TTTTargetIDPlayerTargetIcon_" .. sid64) + hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_StayNearTarget_TTTPlayerAliveClientThink_" .. sid64) + + if client.TaskmasterRadiusEmitter then + client.TaskmasterRadiusEmitter:Finish() + client.TaskmasterRadiusEmitter = nil + client.TaskmasterRadiusDir = nil + client.TaskmasterRadiusNextPart = nil + end + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 88d654cc3b1998ca8ebd2915eee92502a5fa78da Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 13 Aug 2025 21:41:00 -0400 Subject: [PATCH 009/140] Added `state` parameter to `TTTTeamVoiceChatTargets` hook --- API/HOOKS.md | 1 + RELEASE.md | 6 ++++++ docs/api/hooks.html | 3 ++- gamemodes/terrortown/gamemode/gamemsg.lua | 6 +++--- gamemodes/terrortown/gamemode/shared.lua | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/API/HOOKS.md b/API/HOOKS.md index b625e6d55..a551b5b50 100644 --- a/API/HOOKS.md +++ b/API/HOOKS.md @@ -1270,6 +1270,7 @@ Called before a team voice state message is sent. Used to modify the targets of *Parameters:* - *speaker* - The player trying to send their team voice state message - *targets* - The table of players that this message will be sent to. Add or remove players from this table to change the message recipients +- *state* - Whether the speaker is speaking globally (`true`) or only to their team (`false`) *(Added in 2.3.5)* *Return:* Whether or not this team voice state message should be sent (Defaults to `true`) diff --git a/RELEASE.md b/RELEASE.md index 7c92fd40c..b3ab9f9a9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,11 @@ # Release Notes +## 2.3.5 (Beta) +**Released:** + +### Developer +- Added `state` parameter to `TTTTeamVoiceChatTargets` hook + ## 2.3.4 (Beta) **Released: August 9th, 2025** diff --git a/docs/api/hooks.html b/docs/api/hooks.html index b2735c9f3..d6e0997d6 100644 --- a/docs/api/hooks.html +++ b/docs/api/hooks.html @@ -1889,7 +1889,7 @@

TTTTeamVoiceChatTargets(speaker, targets)

+

TTTTeamVoiceChatTargets(speaker, targets, state)

Called before a team voice state message is sent. Used to modify the targets of the team voice state message.
Realm: Server
@@ -1899,6 +1899,7 @@

Date: Wed, 13 Aug 2025 21:41:21 -0400 Subject: [PATCH 010/140] Fixed illusionist not blocking traitor team voice chat --- RELEASE.md | 3 +++ .../gamemode/roles/illusionist/illusionist.lua | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index b3ab9f9a9..f15c6284f 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,6 +3,9 @@ ## 2.3.5 (Beta) **Released:** +### Fixes +- Fixed illusionist not blocking traitor team voice chat + ### Developer - Added `state` parameter to `TTTTeamVoiceChatTargets` hook diff --git a/gamemodes/terrortown/gamemode/roles/illusionist/illusionist.lua b/gamemodes/terrortown/gamemode/roles/illusionist/illusionist.lua index dc17d664f..c93255a3d 100644 --- a/gamemodes/terrortown/gamemode/roles/illusionist/illusionist.lua +++ b/gamemodes/terrortown/gamemode/roles/illusionist/illusionist.lua @@ -90,7 +90,14 @@ end) --------------- hook.Add("TTTTeamChatTargets", "Illusionist_TTTTeamChatTargets", function(sender, msg, targets, from_chat) - if IsIllusionistBlocking() and (sender:IsTraitorTeam() or (sender:IsMonsterTeam() and illusionist_hides_monsters:GetBool())) then + if (sender:IsTraitorTeam() or (sender:IsMonsterTeam() and illusionist_hides_monsters:GetBool())) and IsIllusionistBlocking() then + sender:PrintMessage(HUD_PRINTTALK, "The " .. ROLE_STRINGS[ROLE_ILLUSIONIST] .. " is preventing you from communicating with your allies.") + return false + end +end) + +hook.Add("TTTTeamVoiceChatTargets", "Illusionist_TTTTeamVoiceChatTargets", function(sender, targets, state) + if not state and (sender:IsTraitorTeam() or (sender:IsMonsterTeam() and illusionist_hides_monsters:GetBool())) and IsIllusionistBlocking() then sender:PrintMessage(HUD_PRINTTALK, "The " .. ROLE_STRINGS[ROLE_ILLUSIONIST] .. " is preventing you from communicating with your allies.") return false end From 76fa64a81c71ac7fb8f8c7409468eaaa4cbce9f7 Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 13 Aug 2025 22:14:51 -0400 Subject: [PATCH 011/140] Added progress bar to Stay Near Target task --- .../roles/taskmaster/cl_taskmaster.lua | 4 ++ .../roles/taskmaster/tasks/stayneartarget.lua | 42 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 78c96574b..42aaf4c98 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -15,6 +15,10 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() -- Bonus credit popup LANG.AddToLanguage("english", "taskmaster_credit_bonus", "{role}, you have been awarded {num} credit(s) for completing a task.") + -- Tasks + -- Stay Near Target + LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") + -- Win conditions LANG.AddToLanguage("english", "win_taskmaster", "The {role} finished off their list!") LANG.AddToLanguage("english", "ev_win_taskmaster", "The methodical {role} has won the round!") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index fc0e7bfd9..cee48da9b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -5,8 +5,10 @@ local math = math local net = net local player = player local table = table +local util = util local MathCos = math.cos +local MathMax = math.max local MathPi = math.pi local MathRand = math.Rand local MathSin = math.sin @@ -116,7 +118,7 @@ if SERVER then if ply:GetPos():DistToSqr(target:GetPos()) <= rangeSqr then -- Just starting if not ply.StayNearTargetStart then - ply.StayNearTargetStart = CurTime() + ply:SetProperty("StayNearTargetStart", CurTime()) -- Long enough elseif CurTime() > ply.StayNearTargetStart + time then ply:CompleteTask(TASK.id) @@ -146,9 +148,11 @@ end if CLIENT then net.Receive("TTT_Taskmaster_StayNearTarget_Assigned", function() - local sid64 = LocalPlayer():SteamID64() + local client = LocalPlayer() + local sid64 = client:SteamID64() local range = taskmaster_stayneartarget_range:GetInt() * UNITS_PER_METER local rangeSqr = range * range + local time = taskmaster_stayneartarget_time:GetInt() hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_StayNearTarget_TTTTargetIDPlayerTargetIcon_" .. sid64, function(ply, cli, showJester) if cli:IsActiveTaskmaster() and ply == cli.StayNearTargetPlayer then @@ -174,12 +178,12 @@ if CLIENT then for _ = 1, 48 do ply.TaskmasterRadiusEmitter:SetPos(pos) ply.TaskmasterRadiusNextPart = CurTime() + 0.005 - ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + MathPi / 24 + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + MathPi / 12 local vec = Vector(MathSin(ply.TaskmasterRadiusDir) * range, MathCos(ply.TaskmasterRadiusDir) * range, 10) local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) - particle:SetVelocity(Vector(0, 0, 80)) - particle:SetDieTime(0.5) - particle:SetStartAlpha(200) + particle:SetVelocity(Vector(0, 0, 40)) + particle:SetDieTime(0.25) + particle:SetStartAlpha(60) particle:SetEndAlpha(0) particle:SetStartSize(3) particle:SetEndSize(2) @@ -203,6 +207,31 @@ if CLIENT then ply.TaskmasterRadiusNextPart = nil end end) + + hook.Add("HUDPaint", "Taskmaster_StayNearTarget_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local target = client.StayNearTargetPlayer + if not IsPlayer(target) then return end + + local startTime = client.StayNearTargetStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_stayneartarget", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = 1 - (elapsed / time) + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) end) net.Receive("TTT_Taskmaster_StayNearTarget_Cleanup", function() @@ -210,6 +239,7 @@ if CLIENT then local sid64 = client:SteamID64() hook.Remove("TTTTargetIDPlayerTargetIcon", "Taskmaster_StayNearTarget_TTTTargetIDPlayerTargetIcon_" .. sid64) hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_StayNearTarget_TTTPlayerAliveClientThink_" .. sid64) + hook.Remove("HUDPaint", "Taskmaster_StayNearTarget_HUDPaint_" .. sid64) if client.TaskmasterRadiusEmitter then client.TaskmasterRadiusEmitter:Finish() From 21b242c926b592a897608da80038ca0c648549dd Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 07:22:37 -0400 Subject: [PATCH 012/140] Changed progress bar to fill rather than drain --- .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index cee48da9b..7a064595d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -164,6 +164,7 @@ if CLIENT then end end) + local particleVelocity = Vector(0, 0, 40) hook.Add("TTTPlayerAliveClientThink", "Taskmaster_StayNearTarget_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) local shouldDraw = false local target = cli.StayNearTargetPlayer @@ -181,7 +182,7 @@ if CLIENT then ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + MathPi / 12 local vec = Vector(MathSin(ply.TaskmasterRadiusDir) * range, MathCos(ply.TaskmasterRadiusDir) * range, 10) local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) - particle:SetVelocity(Vector(0, 0, 40)) + particle:SetVelocity(particleVelocity) particle:SetDieTime(0.25) particle:SetStartAlpha(60) particle:SetEndAlpha(0) @@ -228,7 +229,7 @@ if CLIENT then y = y + (y / 3) local w = 300 - local progress = 1 - (elapsed / time) + local progress = elapsed / time CRHUD:PaintProgressBar(x, y, w, color, message, progress) end) From 4fcdedbe627ade6c2ed34559a0884b300bd8a3a3 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 07:22:41 -0400 Subject: [PATCH 013/140] Cleanup --- gamemodes/terrortown/gamemode/roles/paladin/cl_paladin.lua | 3 ++- gamemodes/terrortown/gamemode/roles/sapper/cl_sapper.lua | 3 ++- gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua | 3 ++- gamemodes/terrortown/gamemode/roles/sponge/cl_sponge.lua | 5 +++-- .../gamemode/roles/taskmaster/tasks/killfaraway.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/killnearby.lua | 3 ++- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/paladin/cl_paladin.lua b/gamemodes/terrortown/gamemode/roles/paladin/cl_paladin.lua index 129d9b6b6..9cee1dbf3 100644 --- a/gamemodes/terrortown/gamemode/roles/paladin/cl_paladin.lua +++ b/gamemodes/terrortown/gamemode/roles/paladin/cl_paladin.lua @@ -35,6 +35,7 @@ end) -- ROLE FEATURES -- ------------------- +local particleVelocity = Vector(0, 0, 20) hook.Add("TTTPlayerAliveClientThink", "Paladin_RoleFeatures_TTTPlayerAliveClientThink", function(client, ply) if ply:GetDisplayedRole() == ROLE_PALADIN and ply:GetObserverMode() == OBS_MODE_NONE then if not ply.AuraEmitter then ply.AuraEmitter = ParticleEmitter(ply:GetPos()) end @@ -50,7 +51,7 @@ hook.Add("TTTPlayerAliveClientThink", "Paladin_RoleFeatures_TTTPlayerAliveClient local radius = paladin_aura_radius:GetFloat() * UNITS_PER_METER local vec = Vector(MathSin(ply.AuraDir) * radius, MathCos(ply.AuraDir) * radius, 10) local particle = ply.AuraEmitter:Add("particle/shield.vmt", ply:GetPos() + vec) - particle:SetVelocity(Vector(0, 0, 20)) + particle:SetVelocity(particleVelocity) particle:SetDieTime(1) particle:SetStartAlpha(200) particle:SetEndAlpha(0) diff --git a/gamemodes/terrortown/gamemode/roles/sapper/cl_sapper.lua b/gamemodes/terrortown/gamemode/roles/sapper/cl_sapper.lua index cf0698ead..084041559 100644 --- a/gamemodes/terrortown/gamemode/roles/sapper/cl_sapper.lua +++ b/gamemodes/terrortown/gamemode/roles/sapper/cl_sapper.lua @@ -36,10 +36,11 @@ end) -- ROLE FEATURES -- ------------------- +local particleVelocity = Vector(0, 0, 20) local function CreateParticle(auraDir, pos, radius, emitter) local vec = Vector(MathSin(auraDir) * radius, MathCos(auraDir) * radius, 10) local particle = emitter:Add("particle/sap_barrel.vmt", pos + vec) - particle:SetVelocity(Vector(0, 0, 20)) + particle:SetVelocity(particleVelocity) particle:SetDieTime(1) particle:SetStartAlpha(200) particle:SetEndAlpha(0) diff --git a/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua b/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua index ff5decdeb..ac7d4401e 100644 --- a/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua +++ b/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua @@ -280,6 +280,7 @@ end) -- PARTICLES -- --------------- +local radiusVelocity = Vector(0, 0, 80) local function DrawRadius(ply, ent, radius) if not ent.RadiusEmitter then ent.RadiusEmitter = ParticleEmitter(ent:GetPos()) end if not ent.RadiusNextPart then ent.RadiusNextPart = CurTime() end @@ -296,7 +297,7 @@ local function DrawRadius(ply, ent, radius) ent.RadiusDir = ent.RadiusDir + MathPi / 12 local vec = Vector(MathSin(ent.RadiusDir) * radius, MathCos(ent.RadiusDir) * radius, 10) local particle = ent.RadiusEmitter:Add("particle/wisp.vmt", ent:GetPos() + vec) - particle:SetVelocity(Vector(0, 0, 80)) + particle:SetVelocity(radiusVelocity) particle:SetDieTime(0.5) particle:SetStartAlpha(200) particle:SetEndAlpha(0) diff --git a/gamemodes/terrortown/gamemode/roles/sponge/cl_sponge.lua b/gamemodes/terrortown/gamemode/roles/sponge/cl_sponge.lua index 88da157f2..3fbc82fa2 100644 --- a/gamemodes/terrortown/gamemode/roles/sponge/cl_sponge.lua +++ b/gamemodes/terrortown/gamemode/roles/sponge/cl_sponge.lua @@ -44,6 +44,7 @@ end) -- ROLE FEATURES -- ------------------- +local particleVelocity = Vector(0, 0, 20) hook.Add("TTTPlayerAliveClientThink", "Sponge_RoleFeatures_TTTPlayerAliveClientThink", function(client, ply) local shouldDraw = false if ply:GetRole() == ROLE_SPONGE and ply:GetObserverMode() == OBS_MODE_NONE then @@ -61,7 +62,7 @@ hook.Add("TTTPlayerAliveClientThink", "Sponge_RoleFeatures_TTTPlayerAliveClientT local radius = GetGlobalFloat("ttt_sponge_aura_radius", UNITS_PER_SIX_METERS) local vec = Vector(MathSin(ply.SpongeAuraDir) * radius, MathCos(ply.SpongeAuraDir) * radius, 10) local particle = ply.SpongeAuraEmitter:Add("particle/sponge.vmt", ply_pos + vec) - particle:SetVelocity(Vector(0, 0, 20)) + particle:SetVelocity(particleVelocity) particle:SetDieTime(1) particle:SetStartAlpha(200) particle:SetEndAlpha(0) @@ -104,7 +105,7 @@ hook.Add("TTTPlayerAliveClientThink", "Sponge_RoleFeatures_TTTPlayerAliveClientT local auraRadius = 20 local vec = Vector(MathSin(ply.SpongeAuraDir) * auraRadius, MathCos(ply.SpongeAuraDir) * auraRadius, 10) local particle = ply.SpongeAuraEmitter:Add("particle/sponge.vmt", ply_pos + vec) - particle:SetVelocity(Vector(0, 0, 20)) + particle:SetVelocity(particleVelocity) particle:SetDieTime(1) particle:SetStartAlpha(200) particle:SetEndAlpha(0) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua index 405956cc8..e1470d2aa 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua @@ -115,6 +115,7 @@ if CLIENT then end end) + local particleVelocity = Vector(0, 0, 80) hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillFaraway_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) local shouldDraw = false if ply == cli and cli:IsActiveTaskmaster() then @@ -129,7 +130,7 @@ if CLIENT then ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + MathPi / 24 local vec = Vector(MathSin(ply.TaskmasterRadiusDir) * range, MathCos(ply.TaskmasterRadiusDir) * range, 10) local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) - particle:SetVelocity(Vector(0, 0, 80)) + particle:SetVelocity(particleVelocity) particle:SetDieTime(0.5) particle:SetStartAlpha(200) particle:SetEndAlpha(0) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua index 5bdfcec15..389c2ca2c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua @@ -115,6 +115,7 @@ if CLIENT then end end) + local particleVelocity = Vector(0, 0, 40) hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillNearby_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) local shouldDraw = false if ply == cli and cli:IsActiveTaskmaster() then @@ -129,7 +130,7 @@ if CLIENT then ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + MathPi / 12 local vec = Vector(MathSin(ply.TaskmasterRadiusDir) * range, MathCos(ply.TaskmasterRadiusDir) * range, 10) local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) - particle:SetVelocity(Vector(0, 0, 40)) + particle:SetVelocity(particleVelocity) particle:SetDieTime(0.25) particle:SetStartAlpha(60) particle:SetEndAlpha(0) From 6ca9794447c18f210386650815d751659c25e5c7 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 07:26:17 -0400 Subject: [PATCH 014/140] Changed task features to use constants --- .luacheckrc | 4 ++++ gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua | 8 +++++++- .../gamemode/roles/taskmaster/tasks/killfaraway.lua | 4 ++-- .../gamemode/roles/taskmaster/tasks/killnearby.lua | 4 ++-- .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 6 +++--- .../gamemode/roles/taskmaster/tasks/testkilltask.lua | 6 +++--- .../gamemode/roles/taskmaster/tasks/testmisctask.lua | 6 +++--- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 19594c979..9fd690ca5 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1053,6 +1053,10 @@ globals = { "SPECIAL_DETECTIVE_HIDE_NONE", "SPONGE_ALL_PLAYERS", "SPONGE_ATTACKER_AND_VICTIM", + "TASKMASTER_TF_TARGETID_PLAYERICON", + "TASKMASTER_TF_TARGETID_PLAYERTEXT", + "TASKMASTER_TF_PROGRESSBAR", + "TASKMASTER_TF_PARTICLERADIUS", "TRACKRADAR", "TRAITOR_BUTTON_ROLES", "TRAITOR_ROLES", diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua index c8006afdf..f7a5fd7ea 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -1,9 +1,15 @@ AddCSLuaFile() +-- Task Features +TASKMASTER_TF_TARGETID_PLAYERICON = 0 +TASKMASTER_TF_TARGETID_PLAYERTEXT = 1 +TASKMASTER_TF_PROGRESSBAR = 2 +TASKMASTER_TF_PARTICLERADIUS = 3 + ROLE_STARTING_CREDITS[ROLE_TASKMASTER] = 1 ROLE_HAS_PASSIVE_WIN[ROLE_TASKMASTER] = false cvars.AddChangeCallback("ttt_taskmaster_is_passive", function(_, _, newValue) - ROLE_HAS_PASSIVE_WIN[ROLE_TASKMASTER] = util.tobool(newValue) + ROLE_HAS_PASSIVE_WIN[ROLE_TASKMASTER] = tobool(newValue) end) ------------------ diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua index e1470d2aa..1ffeca71c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua @@ -67,8 +67,8 @@ if SERVER then end TASK.RequiredFeatures = { - "TargetIDPlayerText", - "ParticleRadius" + TASKMASTER_TF_TARGETID_PLAYERTEXT, + TASKMASTER_TF_PARTICLERADIUS } TASK.OnTaskAssigned = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua index 389c2ca2c..c011ae869 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua @@ -67,8 +67,8 @@ if SERVER then end TASK.RequiredFeatures = { - "TargetIDPlayerText", - "ParticleRadius" + TASKMASTER_TF_TARGETID_PLAYERTEXT, + TASKMASTER_TF_PARTICLERADIUS } TASK.OnTaskAssigned = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 7a064595d..634472f05 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -90,9 +90,9 @@ if SERVER then end TASK.RequiredFeatures = { - "TargetIDPlayerTargetIcon", - "ProgressBar", - "ParticleRadius" + TASKMASTER_TF_TARGETID_PLAYERICON, + TASKMASTER_TF_PROGRESSBAR, + TASKMASTER_TF_PARTICLERADIUS } TASK.OnTaskAssigned = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua index fc1b1c01f..16fe384a2 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua @@ -17,9 +17,9 @@ if SERVER then end TASK.RequiredFeatures = { - --"TargetIDPlayerText", - --"TargetIDPlayerTargetIcon", - --"ProgressBar" + --TASKMASTER_TF_TARGETID_PLAYERTEXT, + --TASKMASTER_TF_TARGETID_PLAYERICON, + --TASKMASTER_TF_PROGRESSBAR } TASK.OnTaskAssigned = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua index 506b0bdf6..7f0bcad1e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua @@ -16,9 +16,9 @@ if SERVER then end TASK.RequiredFeatures = { - --"TargetIDPlayerText", - --"TargetIDPlayerTargetIcon", - --"ProgressBar" + --TASKMASTER_TF_TARGETID_PLAYERTEXT, + --TASKMASTER_TF_TARGETID_PLAYERICON, + --TASKMASTER_TF_PROGRESSBAR } TASK.OnTaskAssigned = function(ply) From 50d96ad17c50331bd725fd139f1b00a9d4d616bf Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 09:57:18 -0400 Subject: [PATCH 015/140] Disallow 0 for Stay Near Target convars Changed Stay Near Target player properties to be scoped instead of shared --- .../roles/taskmaster/tasks/stayneartarget.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 634472f05..aac13b41b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -17,8 +17,8 @@ local TASK = {} TASK.id = "stayneartarget" -local taskmaster_stayneartarget_range = CreateConVar("ttt_taskmaster_stayneartarget_range", "5", FCVAR_REPLICATED, "The distance (in meters) away a player must stay within to count for the 'Stay Near Target' task", 0, 100) -local taskmaster_stayneartarget_time = CreateConVar("ttt_taskmaster_stayneartarget_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay near their target to count for the 'Stay Near Target' task", 0, 240) +local taskmaster_stayneartarget_range = CreateConVar("ttt_taskmaster_stayneartarget_range", "5", FCVAR_REPLICATED, "The distance (in meters) away a player must stay within to count for the 'Stay Near Target' task", 1, 100) +local taskmaster_stayneartarget_time = CreateConVar("ttt_taskmaster_stayneartarget_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay near their target to count for the 'Stay Near Target' task", 1, 240) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_stayneartarget_range", type = ROLE_CONVAR_TYPE_NUM, @@ -118,14 +118,14 @@ if SERVER then if ply:GetPos():DistToSqr(target:GetPos()) <= rangeSqr then -- Just starting if not ply.StayNearTargetStart then - ply:SetProperty("StayNearTargetStart", CurTime()) + ply:SetProperty("StayNearTargetStart", CurTime(), ply) -- Long enough elseif CurTime() > ply.StayNearTargetStart + time then ply:CompleteTask(TASK.id) end -- Not within range else - ply:ClearProperty("StayNearTargetStart") + ply:ClearProperty("StayNearTargetStart", ply) end end) @@ -136,8 +136,8 @@ if SERVER then TASK.OnTaskRemoved = function(ply) timer.Remove("TTTTaskmasterStayNearTargetTimer") - ply:ClearProperty("StayNearTargetPlayer") - ply:ClearProperty("StayNearTargetStart") + ply:ClearProperty("StayNearTargetPlayer", ply) + ply:ClearProperty("StayNearTargetStart", ply) net.Start("TTT_Taskmaster_StayNearTarget_Cleanup") net.Send(ply) From d698f319f3b8957ae4ac798b70615dc9d55bb485 Mon Sep 17 00:00:00 2001 From: NoxxFlame Date: Thu, 14 Aug 2025 15:20:23 +0100 Subject: [PATCH 016/140] Added 'Kill a Player From Behind' task --- .../roles/taskmaster/tasks/killbehind.lua | 93 +++++++++++++++++++ .../roles/taskmaster/tasks/killfaraway.lua | 6 +- .../roles/taskmaster/tasks/killnearby.lua | 6 +- .../roles/taskmaster/tasks/testkilltask.lua | 6 +- .../roles/taskmaster/tasks/testmisctask.lua | 6 +- 5 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua new file mode 100644 index 000000000..ccfef07f2 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua @@ -0,0 +1,93 @@ +local hook = hook +local math = math +local net = net +local table = table + +local MathCos = math.cos +local MathRad = math.rad + +local TASK = {} + +TASK.id = "killbehind" +TASK.isKillTask = true + +local taskmaster_killbehind_angle = CreateConVar("ttt_taskmaster_killbehind_view_angle", "75", FCVAR_REPLICATED, "The angle (in degrees) from a player's eye angle within which the Taskmaster is 'spotted' for the 'Kill a Player From Behind' task", 0, 180) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_killbehind_view_angle", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + return "Kill a Player From Behind" +end + +TASK.Description = function(ply) + return "Kill another player while they are looking away from you" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_KillBehind_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillBehind_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERTEXT + } + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillBehind_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + local maxAngleCos = MathCos(MathRad(taskmaster_killbehind_angle:GetInt())) + local aimVector = victim:GetAimVector() + local attackVector = attacker:GetPos() - victim:GetShootPos() + local angleCos = aimVector:Dot(attackVector) / attackVector:Length() + + if angleCos <= maxAngleCos then + ply:CompleteTask(TASK.id) + end + end) + + net.Start("TTT_Taskmaster_KillBehind_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillBehind_PlayerDeath_" .. ply:SteamID64()) + net.Start("TTT_Taskmaster_KillBehind_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_KillBehind_Assigned", function() + local maxAngleCos = MathCos(MathRad(taskmaster_killbehind_angle:GetInt())) + + hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillBehind_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64(), function(ent, cli, text, col, secondaryText) + if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end + + local aimVector = ent:GetAimVector() + local attackVector = cli:GetPos() - ent:GetShootPos() + local angleCos = aimVector:Dot(attackVector) / attackVector:Length() + + if angleCos <= maxAngleCos then + return "HIDDEN", ROLE_COLORS_RADAR[ROLE_INNOCENT] + else + return "SPOTTED", ROLE_COLORS_RADAR[ROLE_TRAITOR] + end + end) + end) + + net.Receive("TTT_Taskmaster_KillBehind_Cleanup", function() + hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillBehind_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua index 1ffeca71c..9ce441172 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua @@ -15,7 +15,7 @@ local TASK = {} TASK.id = "killfaraway" TASK.isKillTask = true -local taskmaster_killfaraway_range = CreateConVar("ttt_taskmaster_killfaraway_range", "25", FCVAR_REPLICATED, "The minimum distance (in meters) away a player can be to count for the 'Kill Faraway Player' task", 0, 100) +local taskmaster_killfaraway_range = CreateConVar("ttt_taskmaster_killfaraway_range", "25", FCVAR_REPLICATED, "The minimum distance (in meters) away a player can be to count for the 'Kill a Faraway Player' task", 0, 100) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_killfaraway_range", type = ROLE_CONVAR_TYPE_NUM, @@ -23,7 +23,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - return "Kill Faraway Player" + return "Kill a Faraway Player" end TASK.Description = function(ply) @@ -34,7 +34,7 @@ TASK.Description = function(ply) unit = cvars.Number("ttt_distance_unit", 1) end local range = taskmaster_killfaraway_range:GetInt() - local description = "Kill a player from outside " + local description = "Kill another player from outside " if unit == 1 then description = description .. range .. " meter" if range ~= 1 then diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua index c011ae869..ffd313b8c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua @@ -15,7 +15,7 @@ local TASK = {} TASK.id = "killnearby" TASK.isKillTask = true -local taskmaster_killnearby_range = CreateConVar("ttt_taskmaster_killnearby_range", "5", FCVAR_REPLICATED, "The maximum distance (in meters) away a player can be to count for the 'Kill Nearby Player' task", 0, 10) +local taskmaster_killnearby_range = CreateConVar("ttt_taskmaster_killnearby_range", "5", FCVAR_REPLICATED, "The maximum distance (in meters) away a player can be to count for the 'Kill a Nearby Player' task", 0, 10) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_killnearby_range", type = ROLE_CONVAR_TYPE_NUM, @@ -23,7 +23,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - return "Kill Nearby Player" + return "Kill a Nearby Player" end TASK.Description = function(ply) @@ -34,7 +34,7 @@ TASK.Description = function(ply) unit = cvars.Number("ttt_distance_unit", 1) end local range = taskmaster_killnearby_range:GetInt() - local description = "Kill a player from within " + local description = "Kill another player from within " if unit == 1 then description = description .. range .. " meter" if range ~= 1 then diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua index 16fe384a2..2100bcb33 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua @@ -16,11 +16,7 @@ if SERVER then return false end - TASK.RequiredFeatures = { - --TASKMASTER_TF_TARGETID_PLAYERTEXT, - --TASKMASTER_TF_TARGETID_PLAYERICON, - --TASKMASTER_TF_PROGRESSBAR - } + TASK.RequiredFeatures = {} TASK.OnTaskAssigned = function(ply) ply:QueueMessage(MSG_PRINTBOTH, "Task Assigned: " .. TASK.Name(ply)) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua index 7f0bcad1e..c3fd97bc1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua @@ -15,11 +15,7 @@ if SERVER then return false end - TASK.RequiredFeatures = { - --TASKMASTER_TF_TARGETID_PLAYERTEXT, - --TASKMASTER_TF_TARGETID_PLAYERICON, - --TASKMASTER_TF_PROGRESSBAR - } + TASK.RequiredFeatures = {} TASK.OnTaskAssigned = function(ply) ply:QueueMessage(MSG_PRINTBOTH, "Task Assigned: " .. TASK.Name(ply)) From a66db084096aa092ddd14c1a106566b82618fc73 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 10:21:09 -0400 Subject: [PATCH 017/140] Added Crouch Near Body task --- .../roles/taskmaster/cl_taskmaster.lua | 2 +- .../roles/taskmaster/tasks/crouchnearbody.lua | 169 ++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 42aaf4c98..c37313a32 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -16,8 +16,8 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_credit_bonus", "{role}, you have been awarded {num} credit(s) for completing a task.") -- Tasks - -- Stay Near Target LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") + LANG.AddToLanguage("english", "taskmaster_crouchnearbody", "CROUCH NEAR BODY - {time}") -- Win conditions LANG.AddToLanguage("english", "win_taskmaster", "The {role} finished off their list!") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua new file mode 100644 index 000000000..bc9cc0260 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -0,0 +1,169 @@ +local CurTime = CurTime +local cvars = cvars +local ents = ents +local hook = hook +local math = math +local net = net +local table = table +local util = util + +local EntsFindByClass = ents.FindByClass +local MathMax = math.max + +local TASK = {} + +TASK.id = "crouchnearbody" + +local taskmaster_crouchnearbody_range = CreateConVar("ttt_taskmaster_crouchnearbody_range", "1", FCVAR_REPLICATED, "The distance (in meters) away a player must stay within to count for the 'Crouch Near Body' task", 1, 100) +local taskmaster_crouchnearbody_time = CreateConVar("ttt_taskmaster_crouchnearbody_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay near a body to count for the 'Crouch Near Body' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_crouchnearbody_range", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_crouchnearbody_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + return "Crouch Near Body" +end + +TASK.Description = function(ply) + local unit + if SERVER then + unit = 1 + else + unit = cvars.Number("ttt_distance_unit", 1) + end + local range = taskmaster_crouchnearbody_range:GetInt() + local description = "Crouch within " + if unit == 1 then + description = description .. range .. " meter" + if range ~= 1 then + description = description .. "s" + end + elseif unit == 2 then + local convertedRange = math.ceil(range * FEET_PER_METER) + description = description .. convertedRange + if convertedRange == 1 then + description = description .. " foot" + else + description = description .. " feet" + end + else + local convertedRange = math.ceil(range * UNITS_PER_METER) + description = description .. convertedRange .. " unit" + if convertedRange ~= 1 then + description = description .. "s" + end + end + local time = taskmaster_crouchnearbody_time:GetInt() + description = description .. " of a body for " .. time .. " second" + if time == 1 then + return description + end + return description .. "s" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_CrouchNearBody_Assigned") + util.AddNetworkString("TTT_Taskmaster_CrouchNearBody_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + TASK.OnTaskAssigned = function(ply) + local range = taskmaster_crouchnearbody_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + local time = taskmaster_crouchnearbody_time:GetInt() + timer.Create("TTTTaskmasterCrouchNearBodyTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + if not ply:Crouching() then + if ply.CrouchNearBodyStart then + ply:ClearProperty("CrouchNearBodyStart", ply) + end + return + end + + for _, corpse in ipairs(EntsFindByClass("prop_ragdoll")) do + local sourcePlayer = CORPSE.GetPlayer(corpse) + if not IsPlayer(sourcePlayer) then continue end + if sourcePlayer:Alive() or not sourcePlayer:IsSpec() then continue end + + -- Within range + if ply:GetPos():DistToSqr(corpse:GetPos()) <= rangeSqr then + -- Just starting + if not ply.CrouchNearBodyStart then + ply:SetProperty("CrouchNearBodyStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.CrouchNearBodyStart + time then + ply:CompleteTask(TASK.id) + end + -- Not within range + else + ply:ClearProperty("CrouchNearBodyStart", ply) + end + end + end) + + net.Start("TTT_Taskmaster_CrouchNearBody_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterCrouchNearBodyTimer") + + ply:ClearProperty("CrouchNearBodyStart", ply) + + net.Start("TTT_Taskmaster_CrouchNearBody_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_CrouchNearBody_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local time = taskmaster_crouchnearbody_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_CrouchNearBody_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.CrouchNearBodyStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_crouchnearbody", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_CrouchNearBody_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("HUDPaint", "Taskmaster_CrouchNearBody_HUDPaint_" .. sid64) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From f1942b41bdf0a529ed78f768599142b6da96ea9b Mon Sep 17 00:00:00 2001 From: NoxxFlame Date: Thu, 14 Aug 2025 16:03:30 +0100 Subject: [PATCH 018/140] Added 'Kill an Airbourne Player' and 'Kill a Player While Midair' task --- .../roles/taskmaster/tasks/killairborne.lua | 70 +++++++++++++++++++ .../roles/taskmaster/tasks/killmidair.lua | 41 +++++++++++ 2 files changed, 111 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killmidair.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua new file mode 100644 index 000000000..4d87c05bc --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua @@ -0,0 +1,70 @@ +local hook = hook +local net = net + +local TASK = {} + +TASK.id = "killairbourne" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Kill an Airbourne Player" +end + +TASK.Description = function(ply) + return "Kill another player while they are in the air" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_KillAirbourne_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillAirbourne_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERTEXT + } + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillAirbourne_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if not victim:OnGround() then + ply:CompleteTask(TASK.id) + end + end) + + net.Start("TTT_Taskmaster_KillAirbourne_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillAirbourne_PlayerDeath_" .. ply:SteamID64()) + net.Start("TTT_Taskmaster_KillAirbourne_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_KillAirbourne_Assigned", function() + hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillAirbourne_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64(), function(ent, cli, text, col, secondaryText) + if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end + + if ent:OnGround() then + return "GROUNDED", ROLE_COLORS_RADAR[ROLE_TRAITOR] + else + return "AIRBORNE", ROLE_COLORS_RADAR[ROLE_INNOCENT] + end + end) + end) + + net.Receive("TTT_Taskmaster_KillAirbourne_Cleanup", function() + hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillAirbourne_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killmidair.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killmidair.lua new file mode 100644 index 000000000..a3dab87ce --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killmidair.lua @@ -0,0 +1,41 @@ +local hook = hook + +local TASK = {} + +TASK.id = "killmidair" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Kill a Player While Midair" +end + +TASK.Description = function(ply) + return "Kill another player while you are in the air" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillMidair_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if not attacker:OnGround() then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillMidair_PlayerDeath_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From c35dc607c87f44f972e6b356c1d7fce4e84f640d Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 12:08:17 -0400 Subject: [PATCH 019/140] Added `TTTDetectiveCalledToBody` hook which is called when a player calls a detective to a body --- API/HOOKS.md | 9 +++++++++ RELEASE.md | 6 ++++++ docs/api/hooks.html | 13 +++++++++++++ gamemodes/terrortown/gamemode/corpse.lua | 2 ++ gamemodes/terrortown/gamemode/shared.lua | 2 +- 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/API/HOOKS.md b/API/HOOKS.md index b625e6d55..6398a7e79 100644 --- a/API/HOOKS.md +++ b/API/HOOKS.md @@ -199,6 +199,15 @@ Called before the name and role of a player's killer is shown to the victim. Use - *killerName* - The new killerName value to use or the original passed into the hook - *role* - The new role value to use or the original passed into the hook. Use `ROLE_NONE` to hide the attacker's role from the victim +### TTTDetectiveCalledToBody(ply, owner, rag) +Called when a player calls a detective to a player ragdoll.\ +*Realm:* Server\ +*Added in:* 2.3.5\ +*Parameters:* +- *ply* - The player who is calling for a detective +- *owner* - The player who owns the corpse the detective is being called to +- *rag* - The ragdoll representing the corpse being called to + ### TTTDetectiveLikePromoted(ply) Called when a detective-like (deputy, impersonator, etc.) player is promoted.\ *Realm:* Server\ diff --git a/RELEASE.md b/RELEASE.md index 7c92fd40c..f6b0e70e9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,11 @@ # Release Notes +## 2.3.5 (Beta) +**Released:** + +### Developer +- Added `TTTDetectiveCalledToBody` hook which is called when a player calls a detective to a body + ## 2.3.4 (Beta) **Released: August 9th, 2025** diff --git a/docs/api/hooks.html b/docs/api/hooks.html index b2735c9f3..f4027a8bc 100644 --- a/docs/api/hooks.html +++ b/docs/api/hooks.html @@ -308,6 +308,19 @@

TTTDetectiveCalledToBody(ply, owner, rag) +

+ Called when a player calls a detective to a player ragdoll.
+ Realm: Server
+ Added in: 2.3.5
+ Parameters: +

+
    +
  • ply - The player who is calling for a detective
  • +
  • owner - The player who owns the corpse the detective is being called to
  • +
  • rag - The ragdoll representing the corpse being called to
  • +
+

TTTDetectiveLikePromoted(ply)

Called when a detective-like (deputy, impersonator, etc.) player is promoted.
diff --git a/gamemodes/terrortown/gamemode/corpse.lua b/gamemodes/terrortown/gamemode/corpse.lua index 3b58bc84a..1132862e9 100644 --- a/gamemodes/terrortown/gamemode/corpse.lua +++ b/gamemodes/terrortown/gamemode/corpse.lua @@ -316,6 +316,7 @@ local function CallDetective(ply, cmd, args) rag.last_detective_call = CurTime() if CORPSE.GetFound(rag, false) then + hook.Call("TTTDetectiveCalledToBody", nil, ply, owner, rag) -- show indicator to detectives net.Start("TTT_CorpseCall") net.WriteVector(rag:GetPos()) @@ -389,6 +390,7 @@ function CORPSE.ShowSearch(ply, rag, covert, long_range) elseif IsValid(ownerEnt) and not ply:IsSpec() and not ownerEnt:GetNWBool("det_called", false) and not ownerEnt:GetNWBool("body_searched", false) then if IsValid(rag) and rag:GetPos():Distance(ply:GetPos()) < 128 then hook.Call("TTTBodyFound", GAMEMODE, ply, ownerEnt, rag) + hook.Call("TTTDetectiveCalledToBody", nil, ply, ownerEnt, rag) net.Start("TTT_CorpseCall") net.WriteVector(rag:GetPos()) net.WriteString(rag.sid) diff --git a/gamemodes/terrortown/gamemode/shared.lua b/gamemodes/terrortown/gamemode/shared.lua index 2fdb8b4d4..db572cb50 100644 --- a/gamemodes/terrortown/gamemode/shared.lua +++ b/gamemodes/terrortown/gamemode/shared.lua @@ -24,7 +24,7 @@ local StringSub = string.sub include("player_class/player_ttt.lua") -- Version string for display and function for version checks -CR_VERSION = "2.3.4" +CR_VERSION = "2.3.5" CR_BETA = true CR_WORKSHOP_ID = CR_BETA and "2404251054" or "2421039084" From 7e16191ae93bc60fbc5e8a5ea8b9b7ae78c6515f Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 12:52:57 -0400 Subject: [PATCH 020/140] Added Call Detective to Body task --- .../roles/taskmaster/tasks/calldetective.lua | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/calldetective.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/calldetective.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/calldetective.lua new file mode 100644 index 000000000..40922233e --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/calldetective.lua @@ -0,0 +1,35 @@ +local TASK = {} + +TASK.id = "calldetective" + +TASK.Name = function(ply) + return "Call Detective to Body" +end + +TASK.Description = function(ply) + return "Call a detective to the body of a dead player" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + hook.Add("TTTDetectiveCalledToBody", "Taskmaster_CallDetective_TTTDetectiveCalledToBody_" .. ply:SteamID64(), function(caller, owner, rag) + if not IsPlayer(caller) then return end + if caller ~= ply then return end + ply:CompleteTask(TASK.id) + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("TTTDetectiveCalledToBody", "Taskmaster_CallDetective_TTTDetectiveCalledToBody_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 504ef06dab16f8dcda7ccda5e02b53a43bed415e Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 13:28:36 -0400 Subject: [PATCH 021/140] Added Pick Up Shop Item task --- .../roles/taskmaster/tasks/pickupshopitem.lua | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua new file mode 100644 index 000000000..90db16a6f --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua @@ -0,0 +1,36 @@ +local TASK = {} + +TASK.id = "pickupshopitem" + +TASK.Name = function(ply) + return "Pick Up Shop Item" +end + +TASK.Description = function(ply) + return "Pick up a shop item dropped on the ground" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + hook.Add("WeaponEquip", "Taskmaster_PickUpShopItem_WeaponEquip_" .. ply:SteamID64(), function(wep, caller) + if not IsPlayer(caller) then return end + if caller ~= ply then return end + if not IsValid(wep) or not wep.CanBuy or #wep.CanBuy == 0 then return end + ply:CompleteTask(TASK.id) + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("WeaponEquip", "Taskmaster_PickUpShopItem_WeaponEquip_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From acf1447f73dcca52c9add71cd325f0c458b7bb86 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 13:38:43 -0400 Subject: [PATCH 022/140] Added Jump task --- .../gamemode/roles/taskmaster/tasks/jump.lua | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua new file mode 100644 index 000000000..96e894cc1 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -0,0 +1,52 @@ +local TASK = {} + +TASK.id = "jump" + +local taskmaster_jump_times = CreateConVar("ttt_taskmaster_jump_times", "25", FCVAR_REPLICATED, "The jump of times a player must jump to complete the 'Jump X Times' task", 1, 100) + +TASK.Name = function(ply) + local times = taskmaster_jump_times:GetInt() + local name = "Jump " .. times .. " Time" + if times ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local times = taskmaster_jump_times:GetInt() + local name = "Jump in the air " .. times .. " time" + if times ~= 1 then + name = name .. "s" + end + return name +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + local times = taskmaster_jump_times:GetInt() + local count = 0 + hook.Add("OnPlayerJump", "Taskmaster_Jump_OnPlayerJump_" .. ply:SteamID64(), function(caller) + if not IsPlayer(caller) then return end + if caller ~= ply then return end + count = count + 1 + if count >= times then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("OnPlayerJump", "Taskmaster_Jump_OnPlayerJump_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 6c61a8f50c0aee1079282bb9ffce8e27c89cc8f8 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 13:43:00 -0400 Subject: [PATCH 023/140] Added Jump times convar to ULX --- .../terrortown/gamemode/roles/taskmaster/tasks/jump.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua index 96e894cc1..8e3298d5c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -3,6 +3,11 @@ local TASK = {} TASK.id = "jump" local taskmaster_jump_times = CreateConVar("ttt_taskmaster_jump_times", "25", FCVAR_REPLICATED, "The jump of times a player must jump to complete the 'Jump X Times' task", 1, 100) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_jump_times", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) TASK.Name = function(ply) local times = taskmaster_jump_times:GetInt() From 60b3d5fb3d85af6392b42f3a3bf4aeec222a5b0e Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 13:58:10 -0400 Subject: [PATCH 024/140] Added Crouch task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/crouch.lua | 122 ++++++++++++++++++ .../gamemode/roles/taskmaster/tasks/jump.lua | 6 +- 3 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index c37313a32..f27b48a9f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -18,6 +18,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() -- Tasks LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") LANG.AddToLanguage("english", "taskmaster_crouchnearbody", "CROUCH NEAR BODY - {time}") + LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") -- Win conditions LANG.AddToLanguage("english", "win_taskmaster", "The {role} finished off their list!") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua new file mode 100644 index 000000000..aa7233344 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -0,0 +1,122 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local table = table +local util = util + +local MathMax = math.max + +local TASK = {} + +TASK.id = "crouch" + +local taskmaster_crouch_time = CreateConVar("ttt_taskmaster_crouch_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay crouched to complete the 'Crouch Near Body' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_crouch_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local time = taskmaster_crouch_time:GetInt() + local name = "Crouch For " .. time .. " Second" + if time ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local time = taskmaster_crouch_time:GetInt() + local desc = "Crouch continuously for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_Crouch_Assigned") + util.AddNetworkString("TTT_Taskmaster_Crouch_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + TASK.OnTaskAssigned = function(ply) + local time = taskmaster_crouch_time:GetInt() + timer.Create("TTTTaskmasterCrouchTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + + if ply:Crouching() then + -- Just starting + if not ply.CrouchStart then + ply:SetProperty("CrouchStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.CrouchStart + time then + ply:CompleteTask(TASK.id) + end + -- Not crouching anymore + else + ply:ClearProperty("CrouchStart", ply) + end + end) + + net.Start("TTT_Taskmaster_Crouch_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterCrouchTimer") + + ply:ClearProperty("CrouchStart", ply) + + net.Start("TTT_Taskmaster_Crouch_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_Crouch_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local time = taskmaster_crouch_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_Crouch_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.CrouchStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_crouch", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_Crouch_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("HUDPaint", "Taskmaster_Crouch_HUDPaint_" .. sid64) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua index 8e3298d5c..59a8ea293 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -20,11 +20,11 @@ end TASK.Description = function(ply) local times = taskmaster_jump_times:GetInt() - local name = "Jump in the air " .. times .. " time" + local desc = "Jump in the air " .. times .. " time" if times ~= 1 then - name = name .. "s" + desc = desc .. "s" end - return name + return desc end if SERVER then From 600c488df4e492890c22f30eb0011675d371f56b Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 14:00:24 -0400 Subject: [PATCH 025/140] Prefix task property names to ensure uniqueness --- .../roles/taskmaster/tasks/crouch.lua | 12 ++++---- .../roles/taskmaster/tasks/crouchnearbody.lua | 16 +++++----- .../roles/taskmaster/tasks/stayneartarget.lua | 30 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index aa7233344..38ccd5d98 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -55,15 +55,15 @@ if SERVER then if ply:Crouching() then -- Just starting - if not ply.CrouchStart then - ply:SetProperty("CrouchStart", CurTime(), ply) + if not ply.Task_CrouchStart then + ply:SetProperty("Task_CrouchStart", CurTime(), ply) -- Long enough - elseif CurTime() > ply.CrouchStart + time then + elseif CurTime() > ply.Task_CrouchStart + time then ply:CompleteTask(TASK.id) end -- Not crouching anymore else - ply:ClearProperty("CrouchStart", ply) + ply:ClearProperty("Task_CrouchStart", ply) end end) @@ -74,7 +74,7 @@ if SERVER then TASK.OnTaskRemoved = function(ply) timer.Remove("TTTTaskmasterCrouchTimer") - ply:ClearProperty("CrouchStart", ply) + ply:ClearProperty("Task_CrouchStart", ply) net.Start("TTT_Taskmaster_Crouch_Cleanup") net.Send(ply) @@ -92,7 +92,7 @@ if CLIENT then hook.Add("HUDPaint", "Taskmaster_Crouch_HUDPaint_" .. sid64, function() if not client:IsActiveTaskmaster() then return end - local startTime = client.CrouchStart + local startTime = client.Task_CrouchStart if not startTime then return end local PT = LANG.GetParamTranslation diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index bc9cc0260..015b6f2fd 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -87,8 +87,8 @@ if SERVER then timer.Create("TTTTaskmasterCrouchNearBodyTimer", 0.1, 0, function() if not IsPlayer(ply) then return end if not ply:Crouching() then - if ply.CrouchNearBodyStart then - ply:ClearProperty("CrouchNearBodyStart", ply) + if ply.Task_CrouchNearBodyStart then + ply:ClearProperty("Task_CrouchNearBodyStart", ply) end return end @@ -101,15 +101,15 @@ if SERVER then -- Within range if ply:GetPos():DistToSqr(corpse:GetPos()) <= rangeSqr then -- Just starting - if not ply.CrouchNearBodyStart then - ply:SetProperty("CrouchNearBodyStart", CurTime(), ply) + if not ply.Task_CrouchNearBodyStart then + ply:SetProperty("Task_CrouchNearBodyStart", CurTime(), ply) -- Long enough - elseif CurTime() > ply.CrouchNearBodyStart + time then + elseif CurTime() > ply.Task_CrouchNearBodyStart + time then ply:CompleteTask(TASK.id) end -- Not within range else - ply:ClearProperty("CrouchNearBodyStart", ply) + ply:ClearProperty("Task_CrouchNearBodyStart", ply) end end end) @@ -121,7 +121,7 @@ if SERVER then TASK.OnTaskRemoved = function(ply) timer.Remove("TTTTaskmasterCrouchNearBodyTimer") - ply:ClearProperty("CrouchNearBodyStart", ply) + ply:ClearProperty("Task_CrouchNearBodyStart", ply) net.Start("TTT_Taskmaster_CrouchNearBody_Cleanup") net.Send(ply) @@ -139,7 +139,7 @@ if CLIENT then hook.Add("HUDPaint", "Taskmaster_CrouchNearBody_HUDPaint_" .. sid64, function() if not client:IsActiveTaskmaster() then return end - local startTime = client.CrouchNearBodyStart + local startTime = client.Task_CrouchNearBodyStart if not startTime then return end local PT = LANG.GetParamTranslation diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index aac13b41b..540624946 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -32,8 +32,8 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local name = "Player" - if IsPlayer(ply.StayNearTargetPlayer) then - name = ply.StayNearTargetPlayer:Nick() + if IsPlayer(ply.Task_StayNearTargetPlayer) then + name = ply.Task_StayNearTargetPlayer:Nick() end return "Stay Near " .. name end @@ -69,8 +69,8 @@ TASK.Description = function(ply) end local name = "Target" - if IsPlayer(ply.StayNearTargetPlayer) then - name = ply.StayNearTargetPlayer:Nick() + if IsPlayer(ply.Task_StayNearTargetPlayer) then + name = ply.Task_StayNearTargetPlayer:Nick() end local time = taskmaster_stayneartarget_time:GetInt() @@ -105,7 +105,7 @@ if SERVER then break end - ply:SetProperty("StayNearTargetPlayer", target, ply) + ply:SetProperty("Task_StayNearTargetPlayer", target, ply) local range = taskmaster_stayneartarget_range:GetInt() * UNITS_PER_METER local rangeSqr = range * range @@ -117,15 +117,15 @@ if SERVER then -- Within range if ply:GetPos():DistToSqr(target:GetPos()) <= rangeSqr then -- Just starting - if not ply.StayNearTargetStart then - ply:SetProperty("StayNearTargetStart", CurTime(), ply) + if not ply.Task_StayNearTargetStart then + ply:SetProperty("Task_StayNearTargetStart", CurTime(), ply) -- Long enough - elseif CurTime() > ply.StayNearTargetStart + time then + elseif CurTime() > ply.Task_StayNearTargetStart + time then ply:CompleteTask(TASK.id) end -- Not within range else - ply:ClearProperty("StayNearTargetStart", ply) + ply:ClearProperty("Task_StayNearTargetStart", ply) end end) @@ -136,8 +136,8 @@ if SERVER then TASK.OnTaskRemoved = function(ply) timer.Remove("TTTTaskmasterStayNearTargetTimer") - ply:ClearProperty("StayNearTargetPlayer", ply) - ply:ClearProperty("StayNearTargetStart", ply) + ply:ClearProperty("Task_StayNearTargetPlayer", ply) + ply:ClearProperty("Task_StayNearTargetStart", ply) net.Start("TTT_Taskmaster_StayNearTarget_Cleanup") net.Send(ply) @@ -155,7 +155,7 @@ if CLIENT then local time = taskmaster_stayneartarget_time:GetInt() hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_StayNearTarget_TTTTargetIDPlayerTargetIcon_" .. sid64, function(ply, cli, showJester) - if cli:IsActiveTaskmaster() and ply == cli.StayNearTargetPlayer then + if cli:IsActiveTaskmaster() and ply == cli.Task_StayNearTargetPlayer then local iconColor = ROLE_COLORS_SPRITE[ROLE_TRAITOR] if cli:GetPos():DistToSqr(ply:GetPos()) <= rangeSqr then iconColor = ROLE_COLORS_SPRITE[ROLE_INNOCENT] @@ -167,7 +167,7 @@ if CLIENT then local particleVelocity = Vector(0, 0, 40) hook.Add("TTTPlayerAliveClientThink", "Taskmaster_StayNearTarget_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) local shouldDraw = false - local target = cli.StayNearTargetPlayer + local target = cli.Task_StayNearTargetPlayer if ply == cli and cli:IsActiveTaskmaster() and IsPlayer(target) then local pos = target:GetPos() if not ply.TaskmasterRadiusEmitter then ply.TaskmasterRadiusEmitter = ParticleEmitter(pos) end @@ -212,10 +212,10 @@ if CLIENT then hook.Add("HUDPaint", "Taskmaster_StayNearTarget_HUDPaint_" .. sid64, function() if not client:IsActiveTaskmaster() then return end - local target = client.StayNearTargetPlayer + local target = client.Task_StayNearTargetPlayer if not IsPlayer(target) then return end - local startTime = client.StayNearTargetStart + local startTime = client.Task_StayNearTargetStart if not startTime then return end local PT = LANG.GetParamTranslation From 7a530a2ad65a6a00599083edd444817a3fa418a9 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 14:18:34 -0400 Subject: [PATCH 026/140] Added Fire Bullets task --- .../roles/taskmaster/tasks/firebullets.lua | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua new file mode 100644 index 000000000..c699ec543 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua @@ -0,0 +1,57 @@ +local TASK = {} + +TASK.id = "firebullets" + +local taskmaster_firebullets_times = CreateConVar("ttt_taskmaster_firebullets_times", "100", FCVAR_REPLICATED, "The jump of times a player must fire a bullet to complete the 'Fire X Bullets' task", 1, 1000) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_firebullets_times", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local times = taskmaster_firebullets_times:GetInt() + local name = "Fire " .. times .. " Bullet" + if times ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local times = taskmaster_firebullets_times:GetInt() + local desc = "Fire " .. times .. " time" + if times ~= 1 then + desc = desc .. "s" + end + return desc .. " from a gun" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + local times = taskmaster_firebullets_times:GetInt() + local count = 0 + hook.Add("PostEntityFireBullets", "Taskmaster_FireBullets_PostEntityFireBullets_" .. ply:SteamID64(), function(entity, data) + if not IsPlayer(entity) then return end + if entity ~= ply then return end + count = count + 1 + if count >= times then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PostEntityFireBullets", "Taskmaster_FireBullets_PostEntityFireBullets_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 81a0b48f1d4b4280f89f4b5c4e09a03d2eb3b815 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 14:38:22 -0400 Subject: [PATCH 027/140] Added Do Damage task --- .../roles/taskmaster/tasks/dodamage.lua | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua new file mode 100644 index 000000000..52006f54b --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua @@ -0,0 +1,53 @@ +local TASK = {} + +TASK.id = "dodamage" + +local taskmaster_dodamage_amount = CreateConVar("ttt_taskmaster_dodamage_amount", "200", FCVAR_REPLICATED, "The amount of damage a player must do to complete the 'Do X Damage' task", 1, 1000) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_dodamage_amount", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local amount = taskmaster_dodamage_amount:GetInt() + return "Do " .. amount .. " Damage" +end + +TASK.Description = function(ply) + local amount = taskmaster_dodamage_amount:GetInt() + return "Do " .. amount .. " damage to props and other players" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + local amount = taskmaster_dodamage_amount:GetInt() + local total = 0 + hook.Add("PostEntityTakeDamage", "Taskmaster_DoDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken) + if not wasDamageTaken then return end + if entity == ply then return end + + local attacker = dmginfo:GetAttacker() + if not IsPlayer(attacker) or attacker ~= ply then return end + + total = total + dmginfo:GetDamage() + if total >= amount then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PostEntityTakeDamage", "Taskmaster_DoDamage_PostEntityTakeDamage_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 736481cb03b5872e09e6c19c0fe5531d15b57d92 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 14:38:54 -0400 Subject: [PATCH 028/140] Removed test tasks --- .../roles/taskmaster/tasks/testkilltask.lua | 34 ------------------- .../roles/taskmaster/tasks/testmisctask.lua | 33 ------------------ 2 files changed, 67 deletions(-) delete mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua delete mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua deleted file mode 100644 index 2100bcb33..000000000 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testkilltask.lua +++ /dev/null @@ -1,34 +0,0 @@ -local TASK = {} - -TASK.id = "testkilltask" -TASK.isKillTask = true - -TASK.Name = function(ply) - return "Test Kill Task" -end - -TASK.Description = function(ply) - return "Test Kill Task Description" -end - -if SERVER then - TASK.CanAssignTask = function(ply) - return false - end - - TASK.RequiredFeatures = {} - - TASK.OnTaskAssigned = function(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Task Assigned: " .. TASK.Name(ply)) - end - - TASK.OnTaskRemoved = function(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Task Removed: " .. TASK.Name(ply)) - end - - TASK.OnTaskComplete = function(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Task Completed: " .. TASK.Name(ply)) - end -end - -TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua deleted file mode 100644 index c3fd97bc1..000000000 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/testmisctask.lua +++ /dev/null @@ -1,33 +0,0 @@ -local TASK = {} - -TASK.id = "testmisctask" - -TASK.Name = function(ply) - return "Test Misc Task" -end - -TASK.Description = function(ply) - return "Test Misc Task Description" -end - -if SERVER then - TASK.CanAssignTask = function(ply) - return false - end - - TASK.RequiredFeatures = {} - - TASK.OnTaskAssigned = function(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Task Assigned: " .. TASK.Name(ply)) - end - - TASK.OnTaskRemoved = function(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Task Removed: " .. TASK.Name(ply)) - end - - TASK.OnTaskComplete = function(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Task Completed: " .. TASK.Name(ply)) - end -end - -TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 0d386087cf4c03d2fc149cb293d71a1eedc96b7c Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 14:55:48 -0400 Subject: [PATCH 029/140] Fixed Stay Near Target not handling the case where the target died --- .../roles/taskmaster/tasks/stayneartarget.lua | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 540624946..1880f6a20 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -95,15 +95,18 @@ if SERVER then TASKMASTER_TF_PARTICLERADIUS } - TASK.OnTaskAssigned = function(ply) - local target + local function GetRandomTarget(ply) -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do if not p:Alive() or p:IsSpec() then continue end if p == ply then continue end - target = p - break + return p end + return nil + end + + TASK.OnTaskAssigned = function(ply) + local target = GetRandomTarget(ply) ply:SetProperty("Task_StayNearTargetPlayer", target, ply) @@ -129,6 +132,17 @@ if SERVER then end end) + hook.Add("PostPlayerDeath", "Taskmaster_StayNearTarget_PostPlayerDeath_" .. ply:SteamID64(), function(victim) + if ply.Task_StayNearTargetPlayer ~= victim then return end + + local newTarget = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. newTarget:Nick()) + + -- Overwrite the previous target + target = newTarget + ply:SetProperty("Task_StayNearTargetPlayer", newTarget, ply) + end) + net.Start("TTT_Taskmaster_StayNearTarget_Assigned") net.Send(ply) end @@ -139,6 +153,8 @@ if SERVER then ply:ClearProperty("Task_StayNearTargetPlayer", ply) ply:ClearProperty("Task_StayNearTargetStart", ply) + hook.Remove("PostPlayerDeath", "Taskmaster_StayNearTarget_PostPlayerDeath_" .. ply:SteamID64()) + net.Start("TTT_Taskmaster_StayNearTarget_Cleanup") net.Send(ply) end From eb13a33bde429c3af55a13c145f539d39454d340 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 15:00:05 -0400 Subject: [PATCH 030/140] Fixed Stay Near Target task not handling target player disconnect --- .../roles/taskmaster/tasks/stayneartarget.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 1880f6a20..d91818fb7 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -143,6 +143,17 @@ if SERVER then ply:SetProperty("Task_StayNearTargetPlayer", newTarget, ply) end) + hook.Add("PlayerDisconnected", "Taskmaster_StayNearTarget_PlayerDisconnected_" .. ply:SteamID64(), function(leaver) + if ply.Task_StayNearTargetPlayer ~= leaver then return end + + local newTarget = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. newTarget:Nick()) + + -- Overwrite the previous target + target = newTarget + ply:SetProperty("Task_StayNearTargetPlayer", newTarget, ply) + end) + net.Start("TTT_Taskmaster_StayNearTarget_Assigned") net.Send(ply) end From be296f83f56e1cc27a5c9f634b25e1d85bbfaf9f Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 15:08:03 -0400 Subject: [PATCH 031/140] Added Chat task --- .../gamemode/roles/taskmaster/tasks/chat.lua | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua new file mode 100644 index 000000000..f548b4b42 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua @@ -0,0 +1,57 @@ +local TASK = {} + +TASK.id = "chat" + +local taskmaster_chat_times = CreateConVar("ttt_taskmaster_chat_times", "25", FCVAR_REPLICATED, "The number of text messages a player must sent to complete the 'Send X Messages' task", 1, 100) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_chat_times", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local times = taskmaster_chat_times:GetInt() + local name = "Send " .. times .. " Message" + if times ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local times = taskmaster_chat_times:GetInt() + local desc = "Send " .. times .. " message" + if times ~= 1 then + desc = desc .. "s" + end + return desc .. " in text chat" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + local times = taskmaster_chat_times:GetInt() + local count = 0 + hook.Add("PlayerSay", "Taskmaster_Chat_PlayerSay_" .. ply:SteamID64(), function(sender, text, teamChat) + if not IsPlayer(sender) then return end + if sender ~= ply then return end + count = count + 1 + if count >= times then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerSay", "Taskmaster_Chat_PlayerSay_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 043bbcb3dfd8092397749384d40d1450b7fafe26 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 15:44:01 -0400 Subject: [PATCH 032/140] Added Health Under task --- .../roles/taskmaster/cl_taskmaster.lua | 3 +- .../roles/taskmaster/tasks/healthunder.lua | 126 ++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index f27b48a9f..729f344c2 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -16,9 +16,10 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_credit_bonus", "{role}, you have been awarded {num} credit(s) for completing a task.") -- Tasks - LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") LANG.AddToLanguage("english", "taskmaster_crouchnearbody", "CROUCH NEAR BODY - {time}") LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") + LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") + LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") -- Win conditions LANG.AddToLanguage("english", "win_taskmaster", "The {role} finished off their list!") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua new file mode 100644 index 000000000..6c526e6f5 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -0,0 +1,126 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local table = table +local util = util + +local MathMax = math.max + +local TASK = {} + +TASK.id = "healthunder" + +local taskmaster_healthunder_amount = CreateConVar("ttt_taskmaster_healthunder_amount", "15", FCVAR_REPLICATED, "The amount of health a player must stay under to complete the 'Health Under' task", 1, 240) +local taskmaster_healthunder_time = CreateConVar("ttt_taskmaster_healthunder_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must stay under the target health to complete the 'Health Under' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_healthunder_amount", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_healthunder_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local amount = taskmaster_healthunder_amount:GetInt() + return "Stay Under " .. amount .. " HP" +end + +TASK.Description = function(ply) + local time = taskmaster_healthunder_time:GetInt() + local amount = taskmaster_healthunder_amount:GetInt() + local desc = "Stay under " .. amount .. " health for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_HealthUnder_Assigned") + util.AddNetworkString("TTT_Taskmaster_HealthUnder_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + TASK.OnTaskAssigned = function(ply) + local amount = taskmaster_healthunder_amount:GetInt() + local time = taskmaster_healthunder_time:GetInt() + timer.Create("TTTTaskmasterHealthUnderTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + + if ply:Health() < amount then + -- Just starting + if not ply.Task_HealthUnderStart then + ply:SetProperty("Task_HealthUnderStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_HealthUnderStart + time then + ply:CompleteTask(TASK.id) + end + -- Not under target health anymore + else + ply:ClearProperty("Task_HealthUnderStart", ply) + end + end) + + net.Start("TTT_Taskmaster_HealthUnder_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterHealthUnderTimer") + + ply:ClearProperty("Task_HealthUnderStart", ply) + + net.Start("TTT_Taskmaster_HealthUnder_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_HealthUnder_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local time = taskmaster_healthunder_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_HealthUnder_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_HealthUnderStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_healthunder", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_HealthUnder_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("HUDPaint", "Taskmaster_HealthUnder_HUDPaint_" .. sid64) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 4bde7ad2dbc4a498cc16d230a1989163c78b9cc3 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 16:17:01 -0400 Subject: [PATCH 033/140] Cleanup --- .../terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 729f344c2..fbf074da1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -255,8 +255,6 @@ local dtasksHeight = 0 local scrollBarWidth = 15 local creditsIconSize = 32 -local CreateTaskList - local function CreateTaskReroll(task, dscrollpanel) if table.HasValue(client.taskmasterCompletedTasks, task.id) then return false end @@ -323,7 +321,7 @@ local function CreateTaskReroll(task, dscrollpanel) return dpanel, dline end -function CreateTaskList(dscrollpanel) +local function CreateTaskList(dscrollpanel) if not dscrollpanel:IsValid() then return end for _, dtask in ipairs(dtasks) do From 5ab34b2f1d7174cbac27f78877e9f2616ace6ab2 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 16:38:27 -0400 Subject: [PATCH 034/140] Added Holstered task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/holstered.lua | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index fbf074da1..3e1e46580 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -19,6 +19,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_crouchnearbody", "CROUCH NEAR BODY - {time}") LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") + LANG.AddToLanguage("english", "taskmaster_holstered", "STAY HOLSTERED - {time}") LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") -- Win conditions diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua new file mode 100644 index 000000000..7d5ac2c36 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -0,0 +1,123 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local table = table +local util = util + +local MathMax = math.max + +local TASK = {} + +TASK.id = "holstered" + +local taskmaster_holstered_time = CreateConVar("ttt_taskmaster_holstered_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must stay under the target health to complete the 'Health Under' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_holstered_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local time = taskmaster_holstered_time:GetInt() + local name = "Stay Holstered for " .. time .. " Second" + if time ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local time = taskmaster_holstered_time:GetInt() + local desc = "Use the 'Holstered' weapon for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s consecutively" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_Holstered_Assigned") + util.AddNetworkString("TTT_Taskmaster_Holstered_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + TASK.OnTaskAssigned = function(ply) + local time = taskmaster_holstered_time:GetInt() + timer.Create("TTTTaskmasterHolsteredTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + + local wepClass = WEPS.GetClass(ply:GetActiveWeapon()) + if wepClass == "weapon_ttt_unarmed" then + -- Just starting + if not ply.Task_HolsteredStart then + ply:SetProperty("Task_HolsteredStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_HolsteredStart + time then + ply:CompleteTask(TASK.id) + end + -- Not using "holstered" anymore + else + ply:ClearProperty("Task_HolsteredStart", ply) + end + end) + + net.Start("TTT_Taskmaster_Holstered_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterHolsteredTimer") + + ply:ClearProperty("Task_HolsteredStart", ply) + + net.Start("TTT_Taskmaster_Holstered_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_Holstered_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local time = taskmaster_holstered_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_Holstered_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_HolsteredStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_holstered", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_Holstered_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("HUDPaint", "Taskmaster_Holstered_HUDPaint_" .. sid64) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 0b55badc8c9ba4509b8b0635d9da677a60d2bb1f Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 17:30:09 -0400 Subject: [PATCH 035/140] Fixed Holstered convar description --- .../terrortown/gamemode/roles/taskmaster/tasks/holstered.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index 7d5ac2c36..f727a6f29 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -11,7 +11,7 @@ local TASK = {} TASK.id = "holstered" -local taskmaster_holstered_time = CreateConVar("ttt_taskmaster_holstered_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must stay under the target health to complete the 'Health Under' task", 1, 240) +local taskmaster_holstered_time = CreateConVar("ttt_taskmaster_holstered_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must stay holstered to complete the 'Holstered' task", 1, 240) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_holstered_time", type = ROLE_CONVAR_TYPE_NUM, From 4add9c4b8ade3587b1d3db300306128f7ec7ef09 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 17:41:12 -0400 Subject: [PATCH 036/140] Added Crowbar task --- .../roles/taskmaster/tasks/crowbar.lua | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua new file mode 100644 index 000000000..6e9dce28e --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua @@ -0,0 +1,91 @@ +local table = table + +local TASK = {} + +TASK.id = "crowbar" + +local taskmaster_crowbar_times = CreateConVar("ttt_taskmaster_crowbar_times", "50", FCVAR_REPLICATED, "The number of times a player must use their crowbar to complete the 'Swing Crowbar' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_crowbar_times", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local times = taskmaster_crowbar_times:GetInt() + local name = "Swing Crowbar " .. times .. " Time" + if times ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local times = taskmaster_crowbar_times:GetInt() + local desc = "Use the crowbar weapon " .. times .. " time" + if times ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + local function SetupPrimaryAttack(ply, wep) + if wep.Task_CrowbarPrimaryAttack then return end + + local times = taskmaster_crowbar_times:GetInt() + local total = 0 + wep.Task_CrowbarPrimaryAttack = wep.PrimaryAttack + wep.PrimaryAttack = function(this, worldsnd) + wep.Task_CrowbarPrimaryAttack(wep, worldsnd) + total = total + 1 + if total >= times then + ply:CompleteTask(TASK.id) + end + end + end + + local function ClearPrimaryAttack(wep) + if not wep.Task_CrowbarPrimaryAttack then return end + wep.PrimaryAttack = wep.Task_CrowbarPrimaryAttack + wep.Task_CrowbarPrimaryAttack = nil + end + + TASK.OnTaskAssigned = function(ply) + local crowbar = ply:GetWeapon("weapon_zm_improvised") + if IsValid(crowbar) then + SetupPrimaryAttack(ply, crowbar) + end + + local sid64 = ply:SteamID64() + hook.Add("WeaponEquip", "Taskmaster_Crowbar_WeaponEquip_" .. sid64, function(wep, owner) + if owner ~= ply then return end + if WEPS.GetClass(wep) ~= "weapon_zm_improvised" then return end + SetupPrimaryAttack(ply, wep) + end) + + hook.Add("PlayerDroppedWeapon", "Taskmaster_Crowbar_PlayerDroppedWeapon_" .. sid64, function( owner, wep) + if owner ~= ply then return end + if WEPS.GetClass(wep) ~= "weapon_zm_improvised" then return end + ClearPrimaryAttack(wep) + end) + end + + TASK.OnTaskRemoved = function(ply) + ClearPrimaryAttack(ply) + + local sid64 = ply:SteamID64() + hook.Remove("WeaponEquip", "Taskmaster_Crowbar_WeaponEquip_" .. sid64) + hook.Remove("PlayerDroppedWeapon", "Taskmaster_Crowbar_PlayerDroppedWeapon_" .. sid64) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From dc628b525ec8f90d227fa4d9a2306270f5c91c6b Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 17:51:33 -0400 Subject: [PATCH 037/140] Added Pick Up Weapons task --- .../roles/taskmaster/tasks/weaponpickups.lua | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua new file mode 100644 index 000000000..362540c06 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua @@ -0,0 +1,61 @@ +local table = table + +local TASK = {} + +TASK.id = "weaponpickups" + +local taskmaster_weaponpickups_count = CreateConVar("ttt_taskmaster_weaponpickups_count", "20", FCVAR_REPLICATED, "The number of unique weapons a player must pick up to complete the 'Pick Up Weapons' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_weaponpickups_count", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local count = taskmaster_weaponpickups_count:GetInt() + local name = "Pick Up " .. count .. " Weapon" + if count ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local count = taskmaster_weaponpickups_count:GetInt() + local desc = "Pick up " .. count + if count == 1 then + return desc .. "weapon" + end + return desc .. " unique weapons from the ground" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + local count = taskmaster_weaponpickups_count:GetInt() + local total = 0 + hook.Add("WeaponEquip", "Taskmaster_WeaponPickup_WeaponEquip_" .. ply:SteamID64(), function(wep, owner) + if owner ~= ply then return end + if not IsValid(wep) then return end + if wep.Task_WeaponPickupsUsed then return end + wep.Task_WeaponPickupsUsed = true + total = total + 1 + if total >= count then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("WeaponEquip", "Taskmaster_WeaponPickup_WeaponEquip_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 49dc4c3235b15a88955761494e770e2979a6101b Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 17:58:02 -0400 Subject: [PATCH 038/140] Added Stand on Player task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/standonplayer.lua | 122 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 3e1e46580..4e279c40f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -20,6 +20,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") LANG.AddToLanguage("english", "taskmaster_holstered", "STAY HOLSTERED - {time}") + LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") -- Win conditions diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua new file mode 100644 index 000000000..12a79542e --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -0,0 +1,122 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local table = table +local util = util + +local MathMax = math.max + +local TASK = {} + +TASK.id = "standonplayer" + +local taskmaster_standonplayer_time = CreateConVar("ttt_taskmaster_standonplayer_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay stand on top of another player to complete the 'Stand On Player' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_standonplayer_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local time = taskmaster_standonplayer_time:GetInt() + local name = "Stand On Player For " .. time .. " Second" + if time ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local time = taskmaster_standonplayer_time:GetInt() + local desc = "Stand on another player continuously for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_StandOnPlayer_Assigned") + util.AddNetworkString("TTT_Taskmaster_StandOnPlayer_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + TASK.OnTaskAssigned = function(ply) + local time = taskmaster_standonplayer_time:GetInt() + timer.Create("TTTTaskmasterStandOnPlayerTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + + if IsPlayer(ply:GetGroundEntity()) then + -- Just starting + if not ply.Task_StandOnPlayerStart then + ply:SetProperty("Task_StandOnPlayerStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_StandOnPlayerStart + time then + ply:CompleteTask(TASK.id) + end + -- Not standing on another player anymore + else + ply:ClearProperty("Task_StandOnPlayerStart", ply) + end + end) + + net.Start("TTT_Taskmaster_StandOnPlayer_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterStandOnPlayerTimer") + + ply:ClearProperty("Task_StandOnPlayerStart", ply) + + net.Start("TTT_Taskmaster_StandOnPlayer_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_StandOnPlayer_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local time = taskmaster_standonplayer_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_StandOnPlayer_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_StandOnPlayerStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_standonplayer", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_StandOnPlayer_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("HUDPaint", "Taskmaster_StandOnPlayer_HUDPaint_" .. sid64) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From a0b9803822901f12dd8731e495de9b0c21b769d5 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 14 Aug 2025 18:11:28 -0400 Subject: [PATCH 039/140] Added Carry Corpse task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/carrycorpse.lua | 124 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 4e279c40f..8e7af4307 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -16,6 +16,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_credit_bonus", "{role}, you have been awarded {num} credit(s) for completing a task.") -- Tasks + LANG.AddToLanguage("english", "taskmaster_carrycorpse", "CARRY CORPSE - {time}") LANG.AddToLanguage("english", "taskmaster_crouchnearbody", "CROUCH NEAR BODY - {time}") LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua new file mode 100644 index 000000000..d32c232eb --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -0,0 +1,124 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local table = table +local util = util + +local MathMax = math.max + +local TASK = {} + +TASK.id = "carrycorpse" + +local taskmaster_carrycorpse_time = CreateConVar("ttt_taskmaster_carrycorpse_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must carry a player corpse to complete the 'Carry Corpse' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_carrycorpse_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local time = taskmaster_carrycorpse_time:GetInt() + local name = "Carry Corpse for " .. time .. " Second" + if time ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local time = taskmaster_carrycorpse_time:GetInt() + local desc = "Carry a player corpse for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s consecutively" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_CarryCorpse_Assigned") + util.AddNetworkString("TTT_Taskmaster_CarryCorpse_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + TASK.OnTaskAssigned = function(ply) + local time = taskmaster_carrycorpse_time:GetInt() + timer.Create("TTTTaskmasterCarryCorpseTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + + local wep = ply:GetActiveWeapon() + local wepClass = WEPS.GetClass(wep) + if wepClass == "weapon_zm_carry" and IsRagdoll(wep.EntHolding) and IsPlayer(CORPSE.GetPlayer(wep.EntHolding)) then + -- Just starting + if not ply.Task_CarryCorpseStart then + ply:SetProperty("Task_CarryCorpseStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_CarryCorpseStart + time then + ply:CompleteTask(TASK.id) + end + -- Not carrying a corpse anymore + else + ply:ClearProperty("Task_CarryCorpseStart", ply) + end + end) + + net.Start("TTT_Taskmaster_CarryCorpse_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterCarryCorpseTimer") + + ply:ClearProperty("Task_CarryCorpseStart", ply) + + net.Start("TTT_Taskmaster_CarryCorpse_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_CarryCorpse_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local time = taskmaster_carrycorpse_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_CarryCorpse_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_CarryCorpseStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_carrycorpse", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_CarryCorpse_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("HUDPaint", "Taskmaster_CarryCorpse_HUDPaint_" .. sid64) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 9a3b217c3d5f08b19f9eb0941373d352ba07a759 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 10:25:17 -0400 Subject: [PATCH 040/140] Changed `SYNC` methods to not clear values that are already `nil` --- RELEASE.md | 1 + gamemodes/terrortown/gamemode/sync.lua | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index f6b0e70e9..660d507f0 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -5,6 +5,7 @@ ### Developer - Added `TTTDetectiveCalledToBody` hook which is called when a player calls a detective to a body +- Changed `SYNC` methods to not clear values that are already `nil` ## 2.3.4 (Beta) **Released: August 9th, 2025** diff --git a/gamemodes/terrortown/gamemode/sync.lua b/gamemodes/terrortown/gamemode/sync.lua index 91c5b4c72..816ee660f 100644 --- a/gamemodes/terrortown/gamemode/sync.lua +++ b/gamemodes/terrortown/gamemode/sync.lua @@ -20,6 +20,8 @@ function SYNC:SetPlayerProperty(ply, propertyName, propertyValue, targets) end function SYNC:ClearPlayerProperty(ply, propertyName, targets) + if ply[propertyName] == nil then return end + ply[propertyName] = nil net.Start("TTT_ClearPlayerProperty") @@ -47,6 +49,8 @@ function SYNC:SetEntityProperty(ent, propertyName, propertyValue, targets) end function SYNC:ClearEntityProperty(ent, propertyName, targets) + if ent[propertyName] == nil then return end + ent[propertyName] = nil net.Start("TTT_ClearEntityProperty") From 56eb3e89b710c874a1303e0fce54dfe0ef8f86d8 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 10:25:39 -0400 Subject: [PATCH 041/140] Fixed some Taskmaster tasks being doable while dead --- .../terrortown/gamemode/roles/taskmaster/tasks/chat.lua | 2 ++ .../terrortown/gamemode/roles/taskmaster/tasks/crouch.lua | 2 +- .../gamemode/roles/taskmaster/tasks/crouchnearbody.lua | 6 ++---- .../gamemode/roles/taskmaster/tasks/healthunder.lua | 2 +- .../gamemode/roles/taskmaster/tasks/holstered.lua | 4 ++++ .../gamemode/roles/taskmaster/tasks/standonplayer.lua | 2 +- .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 2 +- 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua index f548b4b42..5385f4e6b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua @@ -40,6 +40,8 @@ if SERVER then hook.Add("PlayerSay", "Taskmaster_Chat_PlayerSay_" .. ply:SteamID64(), function(sender, text, teamChat) if not IsPlayer(sender) then return end if sender ~= ply then return end + if not ply:Alive() or ply:IsSpec() then return end + count = count + 1 if count >= times then ply:CompleteTask(TASK.id) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 38ccd5d98..d6556975d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -53,7 +53,7 @@ if SERVER then timer.Create("TTTTaskmasterCrouchTimer", 0.1, 0, function() if not IsPlayer(ply) then return end - if ply:Crouching() then + if ply:Alive() and not ply:IsSpec() and ply:Crouching() then -- Just starting if not ply.Task_CrouchStart then ply:SetProperty("Task_CrouchStart", CurTime(), ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index 015b6f2fd..3323aec98 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -86,10 +86,8 @@ if SERVER then local time = taskmaster_crouchnearbody_time:GetInt() timer.Create("TTTTaskmasterCrouchNearBodyTimer", 0.1, 0, function() if not IsPlayer(ply) then return end - if not ply:Crouching() then - if ply.Task_CrouchNearBodyStart then - ply:ClearProperty("Task_CrouchNearBodyStart", ply) - end + if not ply:Alive() or ply:IsSpec() or not ply:Crouching() then + ply:ClearProperty("Task_CrouchNearBodyStart", ply) return end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 6c526e6f5..797fa5f7c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -57,7 +57,7 @@ if SERVER then timer.Create("TTTTaskmasterHealthUnderTimer", 0.1, 0, function() if not IsPlayer(ply) then return end - if ply:Health() < amount then + if ply:Alive() and not ply:IsSpec() and ply:Health() < amount then -- Just starting if not ply.Task_HealthUnderStart then ply:SetProperty("Task_HealthUnderStart", CurTime(), ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index f727a6f29..7c5629ef5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -52,6 +52,10 @@ if SERVER then local time = taskmaster_holstered_time:GetInt() timer.Create("TTTTaskmasterHolsteredTimer", 0.1, 0, function() if not IsPlayer(ply) then return end + if not ply:Alive() or ply:IsSpec() then + ply:ClearProperty("Task_HolsteredStart", ply) + return + end local wepClass = WEPS.GetClass(ply:GetActiveWeapon()) if wepClass == "weapon_ttt_unarmed" then diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index 12a79542e..815b7530d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -53,7 +53,7 @@ if SERVER then timer.Create("TTTTaskmasterStandOnPlayerTimer", 0.1, 0, function() if not IsPlayer(ply) then return end - if IsPlayer(ply:GetGroundEntity()) then + if ply:Alive() and not ply:IsSpec() and IsPlayer(ply:GetGroundEntity()) then -- Just starting if not ply.Task_StandOnPlayerStart then ply:SetProperty("Task_StandOnPlayerStart", CurTime(), ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index d91818fb7..5094c65e2 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -118,7 +118,7 @@ if SERVER then if not IsPlayer(target) then return end -- Within range - if ply:GetPos():DistToSqr(target:GetPos()) <= rangeSqr then + if ply:Alive() and not ply:IsSpec() and ply:GetPos():DistToSqr(target:GetPos()) <= rangeSqr then -- Just starting if not ply.Task_StayNearTargetStart then ply:SetProperty("Task_StayNearTargetStart", CurTime(), ply) From 55ef53bdfac14be5f2ade4c49eb8af1c8fbd626e Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 11:00:33 -0400 Subject: [PATCH 042/140] Added Stay Higher and Stay Lower tasks --- .../roles/taskmaster/cl_taskmaster.lua | 2 + .../roles/taskmaster/tasks/stayhigher.lua | 146 ++++++++++++++++++ .../roles/taskmaster/tasks/staylower.lua | 146 ++++++++++++++++++ 3 files changed, 294 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 8e7af4307..8421be96e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -23,6 +23,8 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_holstered", "STAY HOLSTERED - {time}") LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") + LANG.AddToLanguage("english", "taskmaster_stayhigher", "STAY HIGHEST - {time}") + LANG.AddToLanguage("english", "taskmaster_staylower", "STAY LOWEST - {time}") -- Win conditions LANG.AddToLanguage("english", "win_taskmaster", "The {role} finished off their list!") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua new file mode 100644 index 000000000..defc6296c --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -0,0 +1,146 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local player = player +local table = table +local util = util + +local MathMax = math.max +local PlayerIterator = player.Iterator + +local TASK = {} + +TASK.id = "stayhigher" + +local taskmaster_stayhigher_time = CreateConVar("ttt_taskmaster_stayhigher_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay higher up to complete the 'Highest Player' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_stayhigher_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local time = taskmaster_stayhigher_time:GetInt() + local name = "Highest For " .. time .. " Second" + if time ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local time = taskmaster_stayhigher_time:GetInt() + local desc = "Stay higher up than all players for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_StayHigher_Assigned") + util.AddNetworkString("TTT_Taskmaster_StayHigher_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + local function GetHighestPlayer() + local highest = nil + for _, p in PlayerIterator() do + if not p:Alive() or p:IsSpec() then continue end + + local pos = p:GetPos() + if not highest or pos.z > highest.z then + highest = { + ply = p, + z = pos.z + } + end + end + + return highest.ply + end + + TASK.OnTaskAssigned = function(ply) + local time = taskmaster_stayhigher_time:GetInt() + timer.Create("TTTTaskmasterStayHigherTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + if not ply:Alive() or ply:IsSpec() then + ply:ClearProperty("Task_StayHigherStart", ply) + return + end + + local highest = GetHighestPlayer() + if IsPlayer(highest) and highest == ply then + -- Just starting + if not ply.Task_StayHigherStart then + ply:SetProperty("Task_StayHigherStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_StayHigherStart + time then + ply:CompleteTask(TASK.id) + end + -- Not highest anymore + else + ply:ClearProperty("Task_StayHigherStart", ply) + end + end) + + net.Start("TTT_Taskmaster_StayHigher_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterStayHigherTimer") + + ply:ClearProperty("Task_StayHigherStart", ply) + + net.Start("TTT_Taskmaster_StayHigher_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_StayHigher_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local time = taskmaster_stayhigher_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_StayHigher_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_StayHigherStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_stayhigher", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_StayHigher_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("HUDPaint", "Taskmaster_StayHigher_HUDPaint_" .. sid64) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua new file mode 100644 index 000000000..09766071d --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -0,0 +1,146 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local player = player +local table = table +local util = util + +local MathMax = math.max +local PlayerIterator = player.Iterator + +local TASK = {} + +TASK.id = "staylower" + +local taskmaster_staylower_time = CreateConVar("ttt_taskmaster_staylower_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay lower down to complete the 'Lowest Player' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_staylower_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local time = taskmaster_staylower_time:GetInt() + local name = "Lowest For " .. time .. " Second" + if time ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local time = taskmaster_staylower_time:GetInt() + local desc = "Stay lower down than all players for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_StayLower_Assigned") + util.AddNetworkString("TTT_Taskmaster_StayLower_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + local function GetLowestPlayer() + local lowest = nil + for _, p in PlayerIterator() do + if not p:Alive() or p:IsSpec() then continue end + + local pos = p:GetPos() + if not lowest or pos.z < lowest.z then + lowest = { + ply = p, + z = pos.z + } + end + end + + return lowest.ply + end + + TASK.OnTaskAssigned = function(ply) + local time = taskmaster_staylower_time:GetInt() + timer.Create("TTTTaskmasterStayLowerTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + if not ply:Alive() or ply:IsSpec() then + ply:ClearProperty("Task_StayLowerStart", ply) + return + end + + local lowest = GetLowestPlayer() + if IsPlayer(lowest) and lowest == ply then + -- Just starting + if not ply.Task_StayLowerStart then + ply:SetProperty("Task_StayLowerStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_StayLowerStart + time then + ply:CompleteTask(TASK.id) + end + -- Not lowest anymore + else + ply:ClearProperty("Task_StayLowerStart", ply) + end + end) + + net.Start("TTT_Taskmaster_StayLower_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterStayLowerTimer") + + ply:ClearProperty("Task_StayLowerStart", ply) + + net.Start("TTT_Taskmaster_StayLower_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_StayLower_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local time = taskmaster_staylower_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_StayLower_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_StayLowerStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_staylower", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_StayLower_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("HUDPaint", "Taskmaster_StayLower_HUDPaint_" .. sid64) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From c07323b84289b71eaa345c036b29f51b4ed653ea Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 12:13:10 -0400 Subject: [PATCH 043/140] Added Damage Everyone task --- .../roles/taskmaster/tasks/damageeveryone.lua | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua new file mode 100644 index 000000000..d76d7d414 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua @@ -0,0 +1,100 @@ +local hook = hook +local net = net +local player = player +local table = table +local util = util + +local PlayerIterator = player.Iterator +local TableHasValue = table.HasValue +local TableInsert = table.insert + +local TASK = {} + +TASK.id = "damageeveryone" + +TASK.Name = function(ply) + return "Damage All Players" +end + +TASK.Description = function(ply) + return "Damage all other living players" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_DamageEveryone_Assigned") + util.AddNetworkString("TTT_Taskmaster_DamageEveryone_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON + } + + TASK.OnTaskAssigned = function(ply) + hook.Add("PostEntityTakeDamage", "Taskmaster_DamageEveryone_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken) + if not wasDamageTaken then return end + if entity == ply then return end + + local attacker = dmginfo:GetAttacker() + if not IsPlayer(attacker) or attacker ~= ply then return end + + local vicSid64 = entity:SteamID64() + local damaged = ply.Task_DamageEveryoneDamaged or {} + if TableHasValue(damaged, vicSid64) then return end + + TableInsert(damaged, vicSid64) + ply:SetProperty("Task_DamageEveryoneDamaged", damaged, ply) + end) + + timer.Create("TTTTaskmasterDamageEveryoneTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + + local damaged = ply.Task_DamageEveryoneDamaged or {} + for _, p in PlayerIterator() do + if ply == p then continue end + if not p:Alive() or p:IsSpec() then continue end + + -- If this player hasn't been damaged yet, we're not done + if not TableHasValue(damaged, p:SteamID64()) then return end + end + + ply:CompleteTask(TASK.id) + end) + + net.Start("TTT_Taskmaster_DamageEveryone_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PostEntityTakeDamage", "Taskmaster_DamageEveryone_PostEntityTakeDamage_" .. ply:SteamID64()) + timer.Remove("TTTTaskmasterDamageEveryoneTimer") + + ply:ClearProperty("Task_DamageEveryoneDamaged", ply) + + net.Start("TTT_Taskmaster_DamageEveryone_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_DamageEveryone_Assigned", function() + local client = LocalPlayer() + hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_DamageEveryone_TTTTargetIDPlayerTargetIcon_" .. client:SteamID64(), function(ply, cli, showJester) + if cli:IsActiveTaskmaster() and not TableHasValue(cli.Task_DamageEveryoneDamaged, ply:SteamID64()) then + local iconColor = ROLE_COLORS_SPRITE[ROLE_TRAITOR] + return "task", true, iconColor, "up" + end + end) + end) + + net.Receive("TTT_Taskmaster_DamageEveryone_Cleanup", function() + local client = LocalPlayer() + hook.Remove("TTTTargetIDPlayerTargetIcon", "Taskmaster_DamageEveryone_TTTTargetIDPlayerTargetIcon_" .. client:SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 420e6156107c3e2f2764032ff919c3d623425dc8 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 12:59:25 -0400 Subject: [PATCH 044/140] Update names for consistency --- gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua | 2 +- .../taskmaster/tasks/{killairborne.lua => killairbourne.lua} | 0 .../gamemode/roles/taskmaster/tasks/standonplayer.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/staylower.lua | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename gamemodes/terrortown/gamemode/roles/taskmaster/tasks/{killairborne.lua => killairbourne.lua} (100%) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index d6556975d..0b8aee2fb 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -20,7 +20,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_crouch_time:GetInt() - local name = "Crouch For " .. time .. " Second" + local name = "Crouch for " .. time .. " Second" if time ~= 1 then name = name .. "s" end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairbourne.lua similarity index 100% rename from gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua rename to gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairbourne.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index 815b7530d..54d1ff118 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -20,7 +20,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_standonplayer_time:GetInt() - local name = "Stand On Player For " .. time .. " Second" + local name = "Stand on Player for " .. time .. " Second" if time ~= 1 then name = name .. "s" end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index defc6296c..e1b24fac5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -22,7 +22,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_stayhigher_time:GetInt() - local name = "Highest For " .. time .. " Second" + local name = "Highest for " .. time .. " Second" if time ~= 1 then name = name .. "s" end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index 09766071d..062a37167 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -22,7 +22,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_staylower_time:GetInt() - local name = "Lowest For " .. time .. " Second" + local name = "Lowest for " .. time .. " Second" if time ~= 1 then name = name .. "s" end From c52e97c7816cd0bb152f623deecbdfe34926d611 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 13:01:17 -0400 Subject: [PATCH 045/140] More consistency --- .../gamemode/roles/taskmaster/tasks/pickupshopitem.lua | 2 +- .../gamemode/roles/taskmaster/tasks/weaponpickups.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua index 90db16a6f..16bb0298f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua @@ -3,7 +3,7 @@ local TASK = {} TASK.id = "pickupshopitem" TASK.Name = function(ply) - return "Pick Up Shop Item" + return "Pick up Shop Item" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua index 362540c06..07f255b46 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua @@ -13,7 +13,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local count = taskmaster_weaponpickups_count:GetInt() - local name = "Pick Up " .. count .. " Weapon" + local name = "Pick up " .. count .. " Weapon" if count ~= 1 then name = name .. "s" end From de8a77e1f83a2d52f96aad2a32236b0ff4dc7f5a Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 13:09:38 -0400 Subject: [PATCH 046/140] Cleanup --- .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 5094c65e2..6b693881c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -194,15 +194,16 @@ if CLIENT then local particleVelocity = Vector(0, 0, 40) hook.Add("TTTPlayerAliveClientThink", "Taskmaster_StayNearTarget_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) local shouldDraw = false - local target = cli.Task_StayNearTargetPlayer - if ply == cli and cli:IsActiveTaskmaster() and IsPlayer(target) then + local target = ply.Task_StayNearTargetPlayer + if ply == cli and ply:IsActiveTaskmaster() and IsPlayer(target) then local pos = target:GetPos() if not ply.TaskmasterRadiusEmitter then ply.TaskmasterRadiusEmitter = ParticleEmitter(pos) end if not ply.TaskmasterRadiusNextPart then ply.TaskmasterRadiusNextPart = CurTime() end if not ply.TaskmasterRadiusDir then ply.TaskmasterRadiusDir = 0 end -- Use DistToSqr as it's more efficient and this is called very frequently + local distance = ply:GetPos():DistToSqr(pos) -- 9000000 = 3000^2 - if ply.TaskmasterRadiusNextPart < CurTime() and cli:GetPos():DistToSqr(pos) <= 9000000 then + if ply.TaskmasterRadiusNextPart < CurTime() and distance <= 9000000 then for _ = 1, 48 do ply.TaskmasterRadiusEmitter:SetPos(pos) ply.TaskmasterRadiusNextPart = CurTime() + 0.005 @@ -218,7 +219,7 @@ if CLIENT then particle:SetRoll(MathRand(0, MathPi)) particle:SetRollDelta(0) local color = ROLE_COLORS[ROLE_TRAITOR] - if ply:GetPos():DistToSqr(target:GetPos()) <= rangeSqr then + if distance <= rangeSqr then color = ROLE_COLORS[ROLE_INNOCENT] end particle:SetColor(color.r, color.g, color.b) From f4c7f46d581115bd5e07bdea72d5a92912c420eb Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 13:18:47 -0400 Subject: [PATCH 047/140] Added "Stay in Area" task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/stayinarea.lua | 196 ++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 8421be96e..f2248d6ea 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -24,6 +24,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") LANG.AddToLanguage("english", "taskmaster_stayhigher", "STAY HIGHEST - {time}") + LANG.AddToLanguage("english", "taskmaster_stayinarea", "STAY IN AREA - {time}") LANG.AddToLanguage("english", "taskmaster_staylower", "STAY LOWEST - {time}") -- Win conditions diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua new file mode 100644 index 000000000..fd91b020a --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -0,0 +1,196 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local table = table +local util = util + +local MathCos = math.cos +local MathMax = math.max +local MathPi = math.pi +local MathRand = math.Rand +local MathSin = math.sin + +local TASK = {} + +TASK.id = "stayinarea" + +local taskmaster_stayinarea_range = CreateConVar("ttt_taskmaster_stayinarea_range", "5", FCVAR_REPLICATED, "The distance (in meters) away from the location that a player must stay within to count for the 'Stay in Area' task", 1, 100) +local taskmaster_stayinarea_time = CreateConVar("ttt_taskmaster_stayinarea_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay inside the target area to count for the 'Stay in Area' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_stayinarea_range", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_stayinarea_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local time = taskmaster_stayinarea_time:GetInt() + local name = "Stay in Area for " .. time .. " Second" + if time ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local time = taskmaster_stayinarea_time:GetInt() + local desc = "Stay in the marked area for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_StayInArea_Assigned") + util.AddNetworkString("TTT_Taskmaster_StayInArea_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR, + TASKMASTER_TF_PARTICLERADIUS + } + + TASK.OnTaskAssigned = function(ply) + ply:SetProperty("Task_StayInAreaLocation", ply:GetPos(), ply) + + local range = taskmaster_stayinarea_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + local time = taskmaster_stayinarea_time:GetInt() + timer.Create("TTTTaskmasterStayInAreaTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + + -- Within range + if ply:Alive() and not ply:IsSpec() and ply:GetPos():DistToSqr(ply.Task_StayInAreaLocation) <= rangeSqr then + -- Just starting + if not ply.Task_StayInAreaStart then + ply:SetProperty("Task_StayInAreaStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_StayInAreaStart + time then + ply:CompleteTask(TASK.id) + end + -- Not within range + else + ply:ClearProperty("Task_StayInAreaStart", ply) + end + end) + + net.Start("TTT_Taskmaster_StayInArea_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterStayInAreaTimer") + + ply:ClearProperty("Task_StayInAreaLocation", ply) + ply:ClearProperty("Task_StayInAreaStart", ply) + + hook.Remove("PostPlayerDeath", "Taskmaster_StayInArea_PostPlayerDeath_" .. ply:SteamID64()) + + net.Start("TTT_Taskmaster_StayInArea_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_StayInArea_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local range = taskmaster_stayinarea_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + local time = taskmaster_stayinarea_time:GetInt() + + local particleVelocity = Vector(0, 0, 40) + hook.Add("TTTPlayerAliveClientThink", "Taskmaster_StayInArea_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) + local shouldDraw = false + local pos = ply.Task_StayInAreaLocation + if ply == cli and ply:IsActiveTaskmaster() and pos then + if not ply.TaskmasterRadiusEmitter then ply.TaskmasterRadiusEmitter = ParticleEmitter(pos) end + if not ply.TaskmasterRadiusNextPart then ply.TaskmasterRadiusNextPart = CurTime() end + if not ply.TaskmasterRadiusDir then ply.TaskmasterRadiusDir = 0 end + -- Use DistToSqr as it's more efficient and this is called very frequently + local distance = ply:GetPos():DistToSqr(pos) + -- 9000000 = 3000^2 + if ply.TaskmasterRadiusNextPart < CurTime() and distance <= 9000000 then + for _ = 1, 48 do + ply.TaskmasterRadiusEmitter:SetPos(pos) + ply.TaskmasterRadiusNextPart = CurTime() + 0.005 + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + MathPi / 12 + local vec = Vector(MathSin(ply.TaskmasterRadiusDir) * range, MathCos(ply.TaskmasterRadiusDir) * range, 10) + local particle = ply.TaskmasterRadiusEmitter:Add("particle/wisp.vmt", pos + vec) + particle:SetVelocity(particleVelocity) + particle:SetDieTime(0.25) + particle:SetStartAlpha(60) + particle:SetEndAlpha(0) + particle:SetStartSize(3) + particle:SetEndSize(2) + particle:SetRoll(MathRand(0, MathPi)) + particle:SetRollDelta(0) + local color = ROLE_COLORS[ROLE_TRAITOR] + if distance <= rangeSqr then + color = ROLE_COLORS[ROLE_INNOCENT] + end + particle:SetColor(color.r, color.g, color.b) + end + ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + 0.02 + end + shouldDraw = true + end + + if not shouldDraw and ply.TaskmasterRadiusEmitter then + ply.TaskmasterRadiusEmitter:Finish() + ply.TaskmasterRadiusEmitter = nil + ply.TaskmasterRadiusDir = nil + ply.TaskmasterRadiusNextPart = nil + end + end) + + hook.Add("HUDPaint", "Taskmaster_StayInArea_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_StayInAreaStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_stayinarea", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_StayInArea_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_StayInArea_TTTPlayerAliveClientThink_" .. sid64) + hook.Remove("HUDPaint", "Taskmaster_StayInArea_HUDPaint_" .. sid64) + + if client.TaskmasterRadiusEmitter then + client.TaskmasterRadiusEmitter:Finish() + client.TaskmasterRadiusEmitter = nil + client.TaskmasterRadiusDir = nil + client.TaskmasterRadiusNextPart = nil + end + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 568c13654ef7c3260d07b12920c7ac2ae00f244d Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 13:56:39 -0400 Subject: [PATCH 048/140] Added Stay Away task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/stayaway.lua | 154 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index f2248d6ea..f9ab4a829 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -22,6 +22,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") LANG.AddToLanguage("english", "taskmaster_holstered", "STAY HOLSTERED - {time}") LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") + LANG.AddToLanguage("english", "taskmaster_stayaway", "STAY AWAY - {time}") LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") LANG.AddToLanguage("english", "taskmaster_stayhigher", "STAY HIGHEST - {time}") LANG.AddToLanguage("english", "taskmaster_stayinarea", "STAY IN AREA - {time}") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua new file mode 100644 index 000000000..7969fbf73 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua @@ -0,0 +1,154 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local player = player +local table = table +local util = util + +local MathMax = math.max +local PlayerIterator = player.Iterator + +local TASK = {} + +TASK.id = "stayaway" + +local taskmaster_stayaway_range = CreateConVar("ttt_taskmaster_stayaway_range", "25", FCVAR_REPLICATED, "The distance (in meters) a player must stay away from other people to count for the 'Stay Away' task", 1, 100) +local taskmaster_stayaway_time = CreateConVar("ttt_taskmaster_stayaway_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay away from other people to count for the 'Stay Away' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_stayaway_range", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_stayaway_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local time = taskmaster_stayaway_time:GetInt() + local name = "Stay Away for " .. time .. " Second" + if time ~= 1 then + name = name .. "s" + end + return name +end + +TASK.Description = function(ply) + local time = taskmaster_stayaway_time:GetInt() + local desc = "Stay away from other players for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_StayAway_Assigned") + util.AddNetworkString("TTT_Taskmaster_StayAway_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + local function GetNearestPlayer(ply) + local pos = ply:GetPos() + local nearest = nil + for _, p in PlayerIterator() do + if p == ply then continue end + if not p:Alive() or p:IsSpec() then continue end + + local dist = pos:DistToSqr(p:GetPos()) + if not nearest or dist < nearest.dist then + nearest = { + ply = p, + dist = dist + } + end + end + + return nearest + end + + TASK.OnTaskAssigned = function(ply) + local range = taskmaster_stayaway_range:GetInt() * UNITS_PER_METER + local rangeSqr = range * range + local time = taskmaster_stayaway_time:GetInt() + timer.Create("TTTTaskmasterStayAwayTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + + -- Within range + local nearest = GetNearestPlayer(ply) + if ply:Alive() and not ply:IsSpec() and nearest.dist >= rangeSqr then + -- Just starting + if not ply.Task_StayAwayStart then + ply:SetProperty("Task_StayAwayStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_StayAwayStart + time then + ply:CompleteTask(TASK.id) + end + -- Not within range + else + ply:ClearProperty("Task_StayAwayStart", ply) + end + end) + + net.Start("TTT_Taskmaster_StayAway_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterStayAwayTimer") + + ply:ClearProperty("Task_StayAwayLocation", ply) + ply:ClearProperty("Task_StayAwayStart", ply) + + hook.Remove("PostPlayerDeath", "Taskmaster_StayAway_PostPlayerDeath_" .. ply:SteamID64()) + + net.Start("TTT_Taskmaster_StayAway_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_StayAway_Assigned", function() + local client = LocalPlayer() + local time = taskmaster_stayaway_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_StayAway_HUDPaint_" .. client:SteamID64(), function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_StayAwayStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_stayaway", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_StayAway_Cleanup", function() + local client = LocalPlayer() + hook.Remove("HUDPaint", "Taskmaster_StayAway_HUDPaint_" .. client:SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 25eb6e7e69b455ad26b85c795e8f07b91bccbd1a Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 14:49:20 -0400 Subject: [PATCH 049/140] Fixed Stay Near Target not cleaning up enough --- .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 6b693881c..ff8564445 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -165,6 +165,7 @@ if SERVER then ply:ClearProperty("Task_StayNearTargetStart", ply) hook.Remove("PostPlayerDeath", "Taskmaster_StayNearTarget_PostPlayerDeath_" .. ply:SteamID64()) + hook.Remove("PlayerDisconnected", "Taskmaster_StayNearTarget_PlayerDisconnected_" .. ply:SteamID64()) net.Start("TTT_Taskmaster_StayNearTarget_Cleanup") net.Send(ply) From 299401cf40754a6fe0390ec9af1e6d561ce92c3e Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 14:50:26 -0400 Subject: [PATCH 050/140] Minor cleanup --- .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index ff8564445..2456bb46a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -106,6 +106,7 @@ if SERVER then end TASK.OnTaskAssigned = function(ply) + local sid64 = ply:SteamID64() local target = GetRandomTarget(ply) ply:SetProperty("Task_StayNearTargetPlayer", target, ply) @@ -132,7 +133,7 @@ if SERVER then end end) - hook.Add("PostPlayerDeath", "Taskmaster_StayNearTarget_PostPlayerDeath_" .. ply:SteamID64(), function(victim) + hook.Add("PostPlayerDeath", "Taskmaster_StayNearTarget_PostPlayerDeath_" .. sid64, function(victim) if ply.Task_StayNearTargetPlayer ~= victim then return end local newTarget = GetRandomTarget(ply) @@ -143,7 +144,7 @@ if SERVER then ply:SetProperty("Task_StayNearTargetPlayer", newTarget, ply) end) - hook.Add("PlayerDisconnected", "Taskmaster_StayNearTarget_PlayerDisconnected_" .. ply:SteamID64(), function(leaver) + hook.Add("PlayerDisconnected", "Taskmaster_StayNearTarget_PlayerDisconnected_" .. sid64, function(leaver) if ply.Task_StayNearTargetPlayer ~= leaver then return end local newTarget = GetRandomTarget(ply) From b36ffad3f1c492b21835d8cd54153ad9b79a6ba3 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 15:28:52 -0400 Subject: [PATCH 051/140] Added Get Player Killed task --- .../taskmaster/tasks/getplayerkilled.lua | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua new file mode 100644 index 000000000..9e03202b3 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua @@ -0,0 +1,107 @@ +local hook = hook +local net = net +local player = player +local util = util + +local TASK = {} + +TASK.id = "getplayerkilled" + +TASK.Name = function(ply) + local name = "Target" + if IsPlayer(ply.Task_GetPlayerKilledPlayer) then + name = ply.Task_GetPlayerKilledPlayer:Nick() + end + return "Get " .. name .. " Killed" +end + +TASK.Description = function(ply) + local name = "Target" + if IsPlayer(ply.Task_GetPlayerKilledPlayer) then + name = ply.Task_GetPlayerKilledPlayer:Nick() + end + + return "Get " .. name .. " killed by another player" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_GetPlayerKilled_Assigned") + util.AddNetworkString("TTT_Taskmaster_GetPlayerKilled_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON + } + + local function GetRandomTarget(ply) + -- Find the first random living player + for _, p in RandomPairs(player.GetAll()) do + if not p:Alive() or p:IsSpec() then continue end + if p == ply then continue end + return p + end + return nil + end + + TASK.OnTaskAssigned = function(ply) + local target = GetRandomTarget(ply) + + ply:SetProperty("Task_GetPlayerKilledPlayer", target, ply) + + hook.Add("PlayerDeath", "Taskmaster_GetPlayerKilled_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if ply.Task_GetPlayerKilledPlayer ~= victim then return end + if attacker == ply then return end + ply:CompleteTask(TASK.id) + end) + + hook.Add("PlayerDisconnected", "Taskmaster_GetPlayerKilled_PlayerDisconnected_" .. ply:SteamID64(), function(leaver) + if ply.Task_GetPlayerKilledPlayer ~= leaver then return end + + local newTarget = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. newTarget:Nick()) + + -- Overwrite the previous target + target = newTarget + ply:SetProperty("Task_GetPlayerKilledPlayer", newTarget, ply) + end) + + net.Start("TTT_Taskmaster_GetPlayerKilled_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterGetPlayerKilledTimer") + + ply:ClearProperty("Task_GetPlayerKilledPlayer", ply) + + hook.Remove("PlayerDeath", "Taskmaster_GetPlayerKilled_PlayerDeath_" .. ply:SteamID64()) + hook.Remove("PlayerDisconnected", "Taskmaster_GetPlayerKilled_PlayerDisconnected_" .. ply:SteamID64()) + + net.Start("TTT_Taskmaster_GetPlayerKilled_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_GetPlayerKilled_Assigned", function() + local client = LocalPlayer() + hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_GetPlayerKilled_TTTTargetIDPlayerTargetIcon_" .. client:SteamID64(), function(ply, cli, showJester) + if cli:IsActiveTaskmaster() and ply == cli.Task_GetPlayerKilledPlayer then + local iconColor = ROLE_COLORS_SPRITE[ROLE_TRAITOR] + return "task", true, iconColor, "up" + end + end) + end) + + net.Receive("TTT_Taskmaster_GetPlayerKilled_Cleanup", function() + local client = LocalPlayer() + hook.Remove("TTTTargetIDPlayerTargetIcon", "Taskmaster_GetPlayerKilled_TTTTargetIDPlayerTargetIcon_" .. client:SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 39a4427d48a93bdcccdad588962207e6bb496357 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 15:36:46 -0400 Subject: [PATCH 052/140] Cleanup --- .../taskmaster/tasks/getplayerkilled.lua | 13 +++------ .../roles/taskmaster/tasks/stayneartarget.lua | 29 +++++++------------ 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua index 9e03202b3..cb4dd98ca 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua @@ -47,9 +47,7 @@ if SERVER then end TASK.OnTaskAssigned = function(ply) - local target = GetRandomTarget(ply) - - ply:SetProperty("Task_GetPlayerKilledPlayer", target, ply) + ply:SetProperty("Task_GetPlayerKilledPlayer", GetRandomTarget(ply), ply) hook.Add("PlayerDeath", "Taskmaster_GetPlayerKilled_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) if ply.Task_GetPlayerKilledPlayer ~= victim then return end @@ -60,12 +58,9 @@ if SERVER then hook.Add("PlayerDisconnected", "Taskmaster_GetPlayerKilled_PlayerDisconnected_" .. ply:SteamID64(), function(leaver) if ply.Task_GetPlayerKilledPlayer ~= leaver then return end - local newTarget = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. newTarget:Nick()) - - -- Overwrite the previous target - target = newTarget - ply:SetProperty("Task_GetPlayerKilledPlayer", newTarget, ply) + local target = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_GetPlayerKilledPlayer", target, ply) end) net.Start("TTT_Taskmaster_GetPlayerKilled_Assigned") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 2456bb46a..5b19c8021 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -107,19 +107,18 @@ if SERVER then TASK.OnTaskAssigned = function(ply) local sid64 = ply:SteamID64() - local target = GetRandomTarget(ply) - - ply:SetProperty("Task_StayNearTargetPlayer", target, ply) - local range = taskmaster_stayneartarget_range:GetInt() * UNITS_PER_METER local rangeSqr = range * range local time = taskmaster_stayneartarget_time:GetInt() + + ply:SetProperty("Task_StayNearTargetPlayer", GetRandomTarget(ply), ply) + timer.Create("TTTTaskmasterStayNearTargetTimer", 0.1, 0, function() if not IsPlayer(ply) then return end - if not IsPlayer(target) then return end + if not IsPlayer(ply.Task_StayNearTargetPlayer) then return end -- Within range - if ply:Alive() and not ply:IsSpec() and ply:GetPos():DistToSqr(target:GetPos()) <= rangeSqr then + if ply:Alive() and not ply:IsSpec() and ply:GetPos():DistToSqr(ply.Task_StayNearTargetPlayer:GetPos()) <= rangeSqr then -- Just starting if not ply.Task_StayNearTargetStart then ply:SetProperty("Task_StayNearTargetStart", CurTime(), ply) @@ -136,23 +135,17 @@ if SERVER then hook.Add("PostPlayerDeath", "Taskmaster_StayNearTarget_PostPlayerDeath_" .. sid64, function(victim) if ply.Task_StayNearTargetPlayer ~= victim then return end - local newTarget = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. newTarget:Nick()) - - -- Overwrite the previous target - target = newTarget - ply:SetProperty("Task_StayNearTargetPlayer", newTarget, ply) + local target = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. target:Nick()) + ply:SetProperty("Task_StayNearTargetPlayer", target, ply) end) hook.Add("PlayerDisconnected", "Taskmaster_StayNearTarget_PlayerDisconnected_" .. sid64, function(leaver) if ply.Task_StayNearTargetPlayer ~= leaver then return end - local newTarget = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. newTarget:Nick()) - - -- Overwrite the previous target - target = newTarget - ply:SetProperty("Task_StayNearTargetPlayer", newTarget, ply) + local target = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_StayNearTargetPlayer", target, ply) end) net.Start("TTT_Taskmaster_StayNearTarget_Assigned") From a10cbafb72e92a9800a81726df30db4bf994d2de Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 16:12:33 -0400 Subject: [PATCH 053/140] Added Get Player to Kill task --- .../taskmaster/tasks/getplayertokill.lua | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua new file mode 100644 index 000000000..37ddcdc8c --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua @@ -0,0 +1,102 @@ +local hook = hook +local net = net +local player = player +local util = util + +local TASK = {} + +TASK.id = "getplayertokill" + +TASK.Name = function(ply) + local name = "Target" + if IsPlayer(ply.Task_GetPlayerToKillPlayer) then + name = ply.Task_GetPlayerToKillPlayer:Nick() + end + return "Get " .. name .. " to Kill" +end + +TASK.Description = function(ply) + local name = "Target" + if IsPlayer(ply.Task_GetPlayerToKillPlayer) then + name = ply.Task_GetPlayerToKillPlayer:Nick() + end + + return "Get " .. name .. " to kill another player" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_GetPlayerToKill_Assigned") + util.AddNetworkString("TTT_Taskmaster_GetPlayerToKill_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON + } + + local function GetRandomTarget(ply) + -- Find the first random living player + for _, p in RandomPairs(player.GetAll()) do + if not p:Alive() or p:IsSpec() then continue end + if p == ply then continue end + return p + end + return nil + end + + TASK.OnTaskAssigned = function(ply) + ply:SetProperty("Task_GetPlayerToKillPlayer", GetRandomTarget(ply), ply) + + hook.Add("PlayerDeath", "Taskmaster_GetPlayerToKill_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if ply.Task_GetPlayerToKillPlayer ~= attacker then return end + if victim == ply then return end + ply:CompleteTask(TASK.id) + end) + + hook.Add("PlayerDisconnected", "Taskmaster_GetPlayerToKill_PlayerDisconnected_" .. ply:SteamID64(), function(leaver) + if ply.Task_GetPlayerToKillPlayer ~= leaver then return end + + local target = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_GetPlayerToKillPlayer", target, ply) + end) + + net.Start("TTT_Taskmaster_GetPlayerToKill_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterGetPlayerToKillTimer") + + ply:ClearProperty("Task_GetPlayerToKillPlayer", ply) + + hook.Remove("PlayerDeath", "Taskmaster_GetPlayerToKill_PlayerDeath_" .. ply:SteamID64()) + hook.Remove("PlayerDisconnected", "Taskmaster_GetPlayerToKill_PlayerDisconnected_" .. ply:SteamID64()) + + net.Start("TTT_Taskmaster_GetPlayerToKill_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_GetPlayerToKill_Assigned", function() + local client = LocalPlayer() + hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_GetPlayerToKill_TTTTargetIDPlayerTargetIcon_" .. client:SteamID64(), function(ply, cli, showJester) + if cli:IsActiveTaskmaster() and ply == cli.Task_GetPlayerToKillPlayer then + local iconColor = ROLE_COLORS_SPRITE[ROLE_TRAITOR] + return "task", true, iconColor, "up" + end + end) + end) + + net.Receive("TTT_Taskmaster_GetPlayerToKill_Cleanup", function() + local client = LocalPlayer() + hook.Remove("TTTTargetIDPlayerTargetIcon", "Taskmaster_GetPlayerToKill_TTTTargetIDPlayerTargetIcon_" .. client:SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From dcc5c15b97125855c9b3474b9610ba4d1d55c853 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 16:20:36 -0400 Subject: [PATCH 054/140] Standardize --- .../gamemode/roles/taskmaster/tasks/getplayerkilled.lua | 2 +- .../gamemode/roles/taskmaster/tasks/getplayertokill.lua | 2 +- .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua index cb4dd98ca..5fdbad5f3 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua @@ -16,7 +16,7 @@ TASK.Name = function(ply) end TASK.Description = function(ply) - local name = "Target" + local name = "target" if IsPlayer(ply.Task_GetPlayerKilledPlayer) then name = ply.Task_GetPlayerKilledPlayer:Nick() end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua index 37ddcdc8c..4c7442c96 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua @@ -16,7 +16,7 @@ TASK.Name = function(ply) end TASK.Description = function(ply) - local name = "Target" + local name = "target" if IsPlayer(ply.Task_GetPlayerToKillPlayer) then name = ply.Task_GetPlayerToKillPlayer:Nick() end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 5b19c8021..5aa0a758e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -31,7 +31,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - local name = "Player" + local name = "Target" if IsPlayer(ply.Task_StayNearTargetPlayer) then name = ply.Task_StayNearTargetPlayer:Nick() end @@ -68,7 +68,7 @@ TASK.Description = function(ply) end end - local name = "Target" + local name = "target" if IsPlayer(ply.Task_StayNearTargetPlayer) then name = ply.Task_StayNearTargetPlayer:Nick() end From e833bffaa085e446a86c1006485762793a9712f9 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 16:24:15 -0400 Subject: [PATCH 055/140] Standardize more --- .../terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua | 1 + gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua | 1 + .../gamemode/roles/taskmaster/tasks/crouchnearbody.lua | 1 + .../gamemode/roles/taskmaster/tasks/damageeveryone.lua | 1 + .../terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua | 1 + .../terrortown/gamemode/roles/taskmaster/tasks/holstered.lua | 1 + .../terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua | 1 + .../terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua | 1 + .../terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua | 1 + .../terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua | 1 + .../terrortown/gamemode/roles/taskmaster/tasks/staylower.lua | 1 + .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 1 + 12 files changed, 12 insertions(+) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua index d32c232eb..a5a8ec5cd 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -3,6 +3,7 @@ local hook = hook local math = math local net = net local table = table +local timer = timer local util = util local MathMax = math.max diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 0b8aee2fb..51d67a9e7 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -3,6 +3,7 @@ local hook = hook local math = math local net = net local table = table +local timer = timer local util = util local MathMax = math.max diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index 3323aec98..6c20ddf9d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -5,6 +5,7 @@ local hook = hook local math = math local net = net local table = table +local timer = timer local util = util local EntsFindByClass = ents.FindByClass diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua index d76d7d414..21cf87801 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua @@ -2,6 +2,7 @@ local hook = hook local net = net local player = player local table = table +local timer = timer local util = util local PlayerIterator = player.Iterator diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 797fa5f7c..63818420c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -3,6 +3,7 @@ local hook = hook local math = math local net = net local table = table +local timer = timer local util = util local MathMax = math.max diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index 7c5629ef5..446d90f1b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -3,6 +3,7 @@ local hook = hook local math = math local net = net local table = table +local timer = timer local util = util local MathMax = math.max diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index 54d1ff118..9c2b19cce 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -3,6 +3,7 @@ local hook = hook local math = math local net = net local table = table +local timer = timer local util = util local MathMax = math.max diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua index 7969fbf73..055b9a832 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua @@ -4,6 +4,7 @@ local math = math local net = net local player = player local table = table +local timer = timer local util = util local MathMax = math.max diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index e1b24fac5..c3c02966f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -4,6 +4,7 @@ local math = math local net = net local player = player local table = table +local timer = timer local util = util local MathMax = math.max diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index fd91b020a..e53fb1b18 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -3,6 +3,7 @@ local hook = hook local math = math local net = net local table = table +local timer = timer local util = util local MathCos = math.cos diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index 062a37167..febcd63dc 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -4,6 +4,7 @@ local math = math local net = net local player = player local table = table +local timer = timer local util = util local MathMax = math.max diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 5aa0a758e..493959139 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -5,6 +5,7 @@ local math = math local net = net local player = player local table = table +local timer = timer local util = util local MathCos = math.cos From 7d7eb9ea7d5c8b8ad617c0afc80c273c4d19c291 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 16:34:20 -0400 Subject: [PATCH 056/140] Added Look at Player task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/crouch.lua | 6 +- .../roles/taskmaster/tasks/lookatplayer.lua | 166 ++++++++++++++++++ 3 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index f9ab4a829..fa0aeb33c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -21,6 +21,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") LANG.AddToLanguage("english", "taskmaster_holstered", "STAY HOLSTERED - {time}") + LANG.AddToLanguage("english", "taskmaster_lookatplayer", "KEEP LOOKING - {time}") LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") LANG.AddToLanguage("english", "taskmaster_stayaway", "STAY AWAY - {time}") LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 51d67a9e7..121f26b2b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -87,10 +87,9 @@ end if CLIENT then net.Receive("TTT_Taskmaster_Crouch_Assigned", function() local client = LocalPlayer() - local sid64 = client:SteamID64() local time = taskmaster_crouch_time:GetInt() - hook.Add("HUDPaint", "Taskmaster_Crouch_HUDPaint_" .. sid64, function() + hook.Add("HUDPaint", "Taskmaster_Crouch_HUDPaint_" .. client:SteamID64(), function() if not client:IsActiveTaskmaster() then return end local startTime = client.Task_CrouchStart @@ -115,8 +114,7 @@ if CLIENT then net.Receive("TTT_Taskmaster_Crouch_Cleanup", function() local client = LocalPlayer() - local sid64 = client:SteamID64() - hook.Remove("HUDPaint", "Taskmaster_Crouch_HUDPaint_" .. sid64) + hook.Remove("HUDPaint", "Taskmaster_Crouch_HUDPaint_" .. client:SteamID64()) end) end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua new file mode 100644 index 000000000..e8459be3f --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -0,0 +1,166 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local table = table +local timer = timer +local util = util + +local MathMax = math.max + +local TASK = {} + +TASK.id = "lookatplayer" + +local taskmaster_lookatplayer_time = CreateConVar("ttt_taskmaster_lookatplayer_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must look at their target to complete for the 'Look at Player' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_lookatplayer_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + local name = "Target" + if IsPlayer(ply.Task_LookAtPlayerPlayer) then + name = ply.Task_LookAtPlayerPlayer:Nick() + end + return "Look at " .. name +end + +TASK.Description = function(ply) + local name = "target" + if IsPlayer(ply.Task_LookAtPlayerPlayer) then + name = ply.Task_LookAtPlayerPlayer:Nick() + end + local time = taskmaster_lookatplayer_time:GetInt() + local desc = "Look at " .. name .. " continuously for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_LookAtPlayer_Assigned") + util.AddNetworkString("TTT_Taskmaster_LookAtPlayer_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON, + TASKMASTER_TF_PROGRESSBAR + } + + local function GetRandomTarget(ply) + -- Find the first random living player + for _, p in RandomPairs(player.GetAll()) do + if not p:Alive() or p:IsSpec() then continue end + if p == ply then continue end + return p + end + return nil + end + + TASK.OnTaskAssigned = function(ply) + ply:SetProperty("Task_LookAtPlayerPlayer", GetRandomTarget(ply), ply) + + local time = taskmaster_lookatplayer_time:GetInt() + timer.Create("TTTTaskmasterLookAtPlayerTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + if not IsPlayer(ply.Task_LookAtPlayerPlayer) then return end + + if ply:Alive() and not ply:IsSpec() and ply:IsLineOfSightClear(ply.Task_LookAtPlayerPlayer) then + -- Just starting + if not ply.Task_LookAtPlayerStart then + ply:SetProperty("Task_LookAtPlayerStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_LookAtPlayerStart + time then + ply:CompleteTask(TASK.id) + end + -- Not within range + else + ply:ClearProperty("Task_LookAtPlayerStart", ply) + end + end) + + hook.Add("PlayerDeath", "Taskmaster_LookAtPlayer_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if ply.Task_LookAtPlayerPlayer ~= victim then return end + if attacker == ply then return end + ply:CompleteTask(TASK.id) + end) + + hook.Add("PlayerDisconnected", "Taskmaster_LookAtPlayer_PlayerDisconnected_" .. ply:SteamID64(), function(leaver) + if ply.Task_LookAtPlayerPlayer ~= leaver then return end + + local target = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_LookAtPlayerPlayer", target, ply) + end) + + net.Start("TTT_Taskmaster_LookAtPlayer_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterLookAtPlayerTimer") + + hook.Remove("PlayerDeath", "Taskmaster_LookAtPlayer_PlayerDeath_" .. ply:SteamID64()) + hook.Remove("PlayerDisconnected", "Taskmaster_LookAtPlayer_PlayerDisconnected_" .. ply:SteamID64()) + + ply:ClearProperty("Task_LookAtPlayerStart", ply) + ply:ClearProperty("Task_LookAtPlayerPlayer", ply) + + net.Start("TTT_Taskmaster_LookAtPlayer_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_LookAtPlayer_Assigned", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + local time = taskmaster_lookatplayer_time:GetInt() + + hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_LookAtPlayer_TTTTargetIDPlayerTargetIcon_" .. sid64, function(ply, cli, showJester) + if cli:IsActiveTaskmaster() and ply == cli.Task_LookAtPlayerPlayer then + local iconColor = ROLE_COLORS_SPRITE[ROLE_INNOCENT] + return "task", true, iconColor, "up" + end + end) + + hook.Add("HUDPaint", "Taskmaster_LookAtPlayer_HUDPaint_" .. sid64, function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_LookAtPlayerStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_lookatplayer", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_LookAtPlayer_Cleanup", function() + local client = LocalPlayer() + local sid64 = client:SteamID64() + hook.Remove("TTTTargetIDPlayerTargetIcon", "Taskmaster_LookAtPlayer_TTTTargetIDPlayerTargetIcon_" .. sid64) + hook.Remove("HUDPaint", "Taskmaster_LookAtPlayer_HUDPaint_" .. sid64) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 387cfdd34d5a7ac2df71c26d8049d1ef1aea0c35 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 16:52:12 -0400 Subject: [PATCH 057/140] Changed Look at Player to work more like the Informant scan --- .../gamemode/roles/taskmaster/tasks/lookatplayer.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index e8459be3f..b948b592e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -63,6 +63,13 @@ if SERVER then return nil end + local function IsTargetInView(ply) + if not IsValid(ply) then return false end + + local tr = ply:GetEyeTrace(MASK_SHOT) + return tr.Entity == ply.Task_LookAtPlayerPlayer + end + TASK.OnTaskAssigned = function(ply) ply:SetProperty("Task_LookAtPlayerPlayer", GetRandomTarget(ply), ply) @@ -71,7 +78,8 @@ if SERVER then if not IsPlayer(ply) then return end if not IsPlayer(ply.Task_LookAtPlayerPlayer) then return end - if ply:Alive() and not ply:IsSpec() and ply:IsLineOfSightClear(ply.Task_LookAtPlayerPlayer) then + -- Check IsOnScreen first because math is more efficient then sending traces this often + if ply:Alive() and not ply:IsSpec() and ply:IsOnScreen(ply.Task_LookAtPlayerPlayer, 0.35) and IsTargetInView(ply) then -- Just starting if not ply.Task_LookAtPlayerStart then ply:SetProperty("Task_LookAtPlayerStart", CurTime(), ply) From 5d76a23fd3b6417645a3c5940ddcbdff99510e2a Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 17:05:41 -0400 Subject: [PATCH 058/140] Cleanup --- .../gamemode/roles/taskmaster/tasks/lookatplayer.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index b948b592e..bd60517cb 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -73,6 +73,7 @@ if SERVER then TASK.OnTaskAssigned = function(ply) ply:SetProperty("Task_LookAtPlayerPlayer", GetRandomTarget(ply), ply) + local sid64 = ply:SteamID64() local time = taskmaster_lookatplayer_time:GetInt() timer.Create("TTTTaskmasterLookAtPlayerTimer", 0.1, 0, function() if not IsPlayer(ply) then return end @@ -93,13 +94,13 @@ if SERVER then end end) - hook.Add("PlayerDeath", "Taskmaster_LookAtPlayer_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + hook.Add("PlayerDeath", "Taskmaster_LookAtPlayer_PlayerDeath_" .. sid64, function(victim, inflictor, attacker) if ply.Task_LookAtPlayerPlayer ~= victim then return end if attacker == ply then return end ply:CompleteTask(TASK.id) end) - hook.Add("PlayerDisconnected", "Taskmaster_LookAtPlayer_PlayerDisconnected_" .. ply:SteamID64(), function(leaver) + hook.Add("PlayerDisconnected", "Taskmaster_LookAtPlayer_PlayerDisconnected_" .. sid64, function(leaver) if ply.Task_LookAtPlayerPlayer ~= leaver then return end local target = GetRandomTarget(ply) @@ -114,8 +115,9 @@ if SERVER then TASK.OnTaskRemoved = function(ply) timer.Remove("TTTTaskmasterLookAtPlayerTimer") - hook.Remove("PlayerDeath", "Taskmaster_LookAtPlayer_PlayerDeath_" .. ply:SteamID64()) - hook.Remove("PlayerDisconnected", "Taskmaster_LookAtPlayer_PlayerDisconnected_" .. ply:SteamID64()) + local sid64 = ply:SteamID64() + hook.Remove("PlayerDeath", "Taskmaster_LookAtPlayer_PlayerDeath_" .. sid64) + hook.Remove("PlayerDisconnected", "Taskmaster_LookAtPlayer_PlayerDisconnected_" .. sid64) ply:ClearProperty("Task_LookAtPlayerStart", ply) ply:ClearProperty("Task_LookAtPlayerPlayer", ply) From db446646dd533308f0cb44506da0c6867093b501 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 17:08:14 -0400 Subject: [PATCH 059/140] Fixed comment --- .../terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index bd60517cb..0c4d9b711 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -88,7 +88,7 @@ if SERVER then elseif CurTime() > ply.Task_LookAtPlayerStart + time then ply:CompleteTask(TASK.id) end - -- Not within range + -- Not looking at the player else ply:ClearProperty("Task_LookAtPlayerStart", ply) end From e95f5a169097d26cf1a566788ff572ef4c6fb8a7 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 17:13:10 -0400 Subject: [PATCH 060/140] Added Stay Hidden task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/stayhidden.lua | 137 ++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index fa0aeb33c..0b3e71e4b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -25,6 +25,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") LANG.AddToLanguage("english", "taskmaster_stayaway", "STAY AWAY - {time}") LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") + LANG.AddToLanguage("english", "taskmaster_stayhidden", "STAY HIDDEN - {time}") LANG.AddToLanguage("english", "taskmaster_stayhigher", "STAY HIGHEST - {time}") LANG.AddToLanguage("english", "taskmaster_stayinarea", "STAY IN AREA - {time}") LANG.AddToLanguage("english", "taskmaster_staylower", "STAY LOWEST - {time}") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua new file mode 100644 index 000000000..b3c9ae4ee --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua @@ -0,0 +1,137 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local player = player +local table = table +local timer = timer +local util = util + +local MathMax = math.max +local PlayerIterator = player.Iterator + +local TASK = {} + +TASK.id = "stayhidden" + +local taskmaster_stayhidden_time = CreateConVar("ttt_taskmaster_stayhidden_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must hide to complete for the 'Stay Hidden' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_stayhidden_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + return "Stay Hidden" +end + +TASK.Description = function(ply) + local time = taskmaster_stayhidden_time:GetInt() + local desc = "Stay hidden from other players for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_StayHidden_Assigned") + util.AddNetworkString("TTT_Taskmaster_StayHidden_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + local function IsTargetInView(ply, target) + if not IsValid(ply) then return false end + + local tr = ply:GetEyeTrace(MASK_SHOT) + return tr.Entity == target + end + + local function IsHidden(ply) + for _, p in PlayerIterator() do + if ply == p then continue end + + -- Check IsOnScreen first because math is more efficient then sending traces this often + if p:IsOnScreen(ply, 0.35) and IsTargetInView(p, ply) then + return false + end + end + return true + end + + TASK.OnTaskAssigned = function(ply) + local time = taskmaster_stayhidden_time:GetInt() + timer.Create("TTTTaskmasterStayHiddenTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + + if ply:Alive() and not ply:IsSpec() and IsHidden(ply) then + -- Just starting + if not ply.Task_StayHiddenStart then + ply:SetProperty("Task_StayHiddenStart", CurTime(), ply) + -- Long enough + elseif CurTime() > ply.Task_StayHiddenStart + time then + ply:CompleteTask(TASK.id) + end + -- Not within hidden + else + ply:ClearProperty("Task_StayHiddenStart", ply) + end + end) + + net.Start("TTT_Taskmaster_StayHidden_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterStayHiddenTimer") + + ply:ClearProperty("Task_StayHiddenStart", ply) + + net.Start("TTT_Taskmaster_StayHidden_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_StayHidden_Assigned", function() + local client = LocalPlayer() + local time = taskmaster_stayhidden_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_StayHidden_HUDPaint_" .. client:SteamID64(), function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_StayHiddenStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_stayhidden", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_StayHidden_Cleanup", function() + local client = LocalPlayer() + hook.Remove("HUDPaint", "Taskmaster_StayHidden_HUDPaint_" .. client:SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 78b51ea562031803923f4413d5905afb90000821 Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 17:31:54 -0400 Subject: [PATCH 061/140] Added Take Damage task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/healthunder.lua | 6 +- .../roles/taskmaster/tasks/takedamage.lua | 116 ++++++++++++++++++ 3 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 0b3e71e4b..2102738d2 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -29,6 +29,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_stayhigher", "STAY HIGHEST - {time}") LANG.AddToLanguage("english", "taskmaster_stayinarea", "STAY IN AREA - {time}") LANG.AddToLanguage("english", "taskmaster_staylower", "STAY LOWEST - {time}") + LANG.AddToLanguage("english", "taskmaster_takedamage", "AVOID DAMAGE - {time}") -- Win conditions LANG.AddToLanguage("english", "win_taskmaster", "The {role} finished off their list!") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 63818420c..56c9f92e1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -91,10 +91,9 @@ end if CLIENT then net.Receive("TTT_Taskmaster_HealthUnder_Assigned", function() local client = LocalPlayer() - local sid64 = client:SteamID64() local time = taskmaster_healthunder_time:GetInt() - hook.Add("HUDPaint", "Taskmaster_HealthUnder_HUDPaint_" .. sid64, function() + hook.Add("HUDPaint", "Taskmaster_HealthUnder_HUDPaint_" .. client:SteamID64(), function() if not client:IsActiveTaskmaster() then return end local startTime = client.Task_HealthUnderStart @@ -119,8 +118,7 @@ if CLIENT then net.Receive("TTT_Taskmaster_HealthUnder_Cleanup", function() local client = LocalPlayer() - local sid64 = client:SteamID64() - hook.Remove("HUDPaint", "Taskmaster_HealthUnder_HUDPaint_" .. sid64) + hook.Remove("HUDPaint", "Taskmaster_HealthUnder_HUDPaint_" .. client:SteamID64()) end) end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua new file mode 100644 index 000000000..c48785d98 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -0,0 +1,116 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local table = table +local timer = timer +local util = util + +local MathMax = math.max + +local TASK = {} + +TASK.id = "takedamage" + +local taskmaster_takedamage_time = CreateConVar("ttt_taskmaster_takedamage_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must survive without taking further damage to complete the 'Take Damage' task", 1, 240) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_takedamage_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + return "Take Damage" +end + +TASK.Description = function(ply) + local time = taskmaster_takedamage_time:GetInt() + local desc = "Take damage and survive for " .. time .. " second" + if time ~= 1 then + desc = desc .. "s" + end + return desc +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_TakeDamage_Assigned") + util.AddNetworkString("TTT_Taskmaster_TakeDamage_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + TASK.OnTaskAssigned = function(ply) + local time = taskmaster_takedamage_time:GetInt() + timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function() + if not IsPlayer(ply) then return end + if not ply:Alive() or ply:IsSpec() then return end + if not ply.Task_TakeDamageStart then return end + if CurTime() <= ply.Task_TakeDamageStart + time then return end + + ply:CompleteTask(TASK.id) + end) + + hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken) + if not wasDamageTaken then return end + if entity ~= ply then return end + ply:SetProperty("Task_TakeDamageStart", CurTime(), ply) + end) + + net.Start("TTT_Taskmaster_TakeDamage_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + timer.Remove("TTTTaskmasterTakeDamageTimer") + + hook.Remove("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64()) + + ply:ClearProperty("Task_TakeDamageStart", ply) + + net.Start("TTT_Taskmaster_TakeDamage_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_TakeDamage_Assigned", function() + local client = LocalPlayer() + local time = taskmaster_takedamage_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64(), function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_TakeDamageStart + if not startTime then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_takedamage", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_TakeDamage_Cleanup", function() + local client = LocalPlayer() + hook.Remove("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From cd1ffce805c9e3008775e3b05fb3e2920958d47f Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 18:10:31 -0400 Subject: [PATCH 062/140] Added ability for tasks to be marked as not blocking the round from ending Added Survive task --- .../gamemode/roles/taskmaster/taskmaster.lua | 30 ++++++++++++++- .../roles/taskmaster/tasks/survive.lua | 38 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index 3f9907e86..e1796f433 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -100,13 +100,22 @@ function plymeta:RerollTask(taskId, free) table.insert(self.taskmasterRerolledTasks, taskId) self:SetProperty("taskmasterRerolledTasks", self.taskmasterRerolledTasks, self) - self:AssignTask(isKillTask, index) + local newTask = self:AssignTask(isKillTask, index) self:RemoveTask(taskId) if not free then self:SubtractCredits(1) end + -- Reset their win state if they rerolled. + -- If they have already "won" and they are re-rolling, that means + -- they only had tasks left that don't block a win condition. + -- Rerolling one of those tasks no longer guarantees that they are winning, + -- unless the newly rolled task ALSO does not block a win condition. + if not newTask.allowRoundEnd and self.taskmasterShouldWin then + self:SetProperty("taskmasterShouldWin", false) + end + net.Start("TTT_TaskmasterUpdateTaskList") net.Send(self) end @@ -135,7 +144,24 @@ function plymeta:CompleteTask(taskId) break end end - if #activeTasksList == 0 then + + local tasksRemaining = #activeTasksList + for _, id in pairs(activeTasksList) do + local task = TASKMASTER.killTasks[id] + if not task then + task = TASKMASTER.miscTasks[id] + end + + if not task then continue end + + -- Tasks that allow the round to end should not count for this check + -- since the Taskmaster wins even if they are still active + if task.allowRoundEnd then + tasksRemaining = tasksRemaining - 1 + end + end + + if tasksRemaining <= 0 then self:SetProperty("taskmasterShouldWin", true) -- TODO: Alert the player that they have finished all their tasks end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua new file mode 100644 index 000000000..7626f3cdb --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua @@ -0,0 +1,38 @@ +local hook = hook + +local TASK = {} + +TASK.id = "survive" +TASK.allowRoundEnd = true + +TASK.Name = function(ply) + return "Survive" +end + +TASK.Description = function(ply) + return "Survive until the end of the round" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + hook.Add("TTTWinCheckComplete", "Taskmaster_Survive_TTTWinCheckComplete_" .. ply:SteamID64(), function(win) + if not IsPlayer(ply) then return end + if not ply:Alive() or ply:IsSpec() then return end + ply:CompleteTask(TASK.id) + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("TTTWinCheckComplete", "Taskmaster_Survive_TTTWinCheckComplete_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 8ca4c3c677f2a33e1b9a80381c407f56786f634d Mon Sep 17 00:00:00 2001 From: Malivil Date: Fri, 15 Aug 2025 21:43:40 -0400 Subject: [PATCH 063/140] Fixed promoted impersonator and deputy who are searched by a detective not always showing their true role on the scoreboard --- RELEASE.md | 1 + gamemodes/terrortown/gamemode/vgui/sb_row.lua | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index f15c6284f..51f7f98f2 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -5,6 +5,7 @@ ### Fixes - Fixed illusionist not blocking traitor team voice chat +- Fixed promoted impersonator and deputy who are searched by a detective not always showing their true role on the scoreboard ### Developer - Added `state` parameter to `TTTTeamVoiceChatTargets` hook diff --git a/gamemodes/terrortown/gamemode/vgui/sb_row.lua b/gamemodes/terrortown/gamemode/vgui/sb_row.lua index db050a151..8fc28c7ac 100644 --- a/gamemodes/terrortown/gamemode/vgui/sb_row.lua +++ b/gamemodes/terrortown/gamemode/vgui/sb_row.lua @@ -189,6 +189,12 @@ local function ShouldSpectatorSeeRoles(cli) return cli:GetRole() == ROLE_NONE and cli:IsSpec() and GetConVar("ttt_spectators_see_roles"):GetBool() end +local function IsSearchedRoleKnown(ply) + -- If we have the role saved and it's not restricted to detectives only, show it + return ply.search_result and ply.search_result.role > ROLE_NONE and + (not cvars.Bool("ttt_detectives_search_only_role", false) or ply:GetNWBool("body_searched_det", false)) +end + function GM:TTTScoreboardRowColorForPlayer(ply) if not IsValid(ply) or GetRoundState() == ROUND_WAIT or GetRoundState() == ROUND_PREP then return defaultcolor end @@ -197,9 +203,7 @@ function GM:TTTScoreboardRowColorForPlayer(ply) return ply:GetRole() end - -- If we have the role saved and it's not restricted to detectives only, show it - if ply.search_result and ply.search_result.role > ROLE_NONE and - (not cvars.Bool("ttt_detectives_search_only_role", false) or ply:GetNWBool("body_searched_det", false)) then + if IsSearchedRoleKnown(ply) then return ply.search_result.role end @@ -315,7 +319,7 @@ function PANEL:Paint(width, height) -- Swap the deputy/impersonator icons depending on which settings are enabled -- Only do this if we haven't set a value above - if not color and ply:IsDetectiveLike() then + if not IsSearchedRoleKnown(ply) and not color and ply:IsDetectiveLike() then if ply:IsDetectiveTeam() then local disp_role, changed = ply:GetDisplayedRole() -- If the displayed role was changed, use it for the color but use the question mark for the icon From 2ebc2907fb1d731da15e4b7464ea6241233879dd Mon Sep 17 00:00:00 2001 From: Malivil Date: Sat, 16 Aug 2025 08:47:24 -0400 Subject: [PATCH 064/140] Added `table.GetFirstItemWithPropertyValue` static method --- API/METHODS_TABLE.md | 11 ++++++++++- RELEASE.md | 1 + docs/api/methods_table.html | 15 ++++++++++++++- gamemodes/terrortown/gamemode/util.lua | 10 +++++++--- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/API/METHODS_TABLE.md b/API/METHODS_TABLE.md index b188e5577..ccb878331 100644 --- a/API/METHODS_TABLE.md +++ b/API/METHODS_TABLE.md @@ -9,8 +9,17 @@ Returns new table that contains the keys not present as values in in the given e - *tbl* - The table whose keys are being inspected - *excludes* - Table of values to exclude +### table.GetFirstItemWithPropertyValue(tbl, key, val) +Returns the first item in the given table that has an item whose property specified by the provided `key` has a value that matches the given `val`.\ +*Realm:* Client and Server\ +*Added in:* 2.3.5\ +*Parameters:* +- *tbl* - The table whose values are being inspected +- *key* - The property name whose value is being compared +- *val* - The value to find in the given property + ### table.HasItemWithPropertyValue(tbl, key, val) -Returns whether the given table has an item whose property specified by the provided `key` has a value that makes the given `val`.\ +Returns whether the given table has an item whose property specified by the provided `key` has a value that matches the given `val`.\ *Realm:* Client and Server\ *Added in:* 1.8.7\ *Parameters:* diff --git a/RELEASE.md b/RELEASE.md index 51f7f98f2..2eab2b4da 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -9,6 +9,7 @@ ### Developer - Added `state` parameter to `TTTTeamVoiceChatTargets` hook +- Added `table.GetFirstItemWithPropertyValue` static method ## 2.3.4 (Beta) **Released: August 9th, 2025** diff --git a/docs/api/methods_table.html b/docs/api/methods_table.html index 6df5739c3..232ce88d0 100644 --- a/docs/api/methods_table.html +++ b/docs/api/methods_table.html @@ -29,9 +29,22 @@

  • excludes - Table of values to exclude
  • +

    table.GetFirstItemWithPropertyValue(tbl, key, val)

    +

    + Returns the first item in the given table that has an item whose property specified by the provided key has a value that matches the given val.
    + Realm: Client and Server
    + Added in: 2.3.5
    + Parameters: +

    +
      +
    • tbl - The table whose values are being inspected
    • +
    • key - The property name whose value is being compared
    • +
    • val - The value to find in the given property
    • +
    +

    table.HasItemWithPropertyValue(tbl, key, val)

    - Returns whether the given table has an item whose property specified by the provided key has a value that makes the given val.
    + Returns whether the given table has an item whose property specified by the provided key has a value that matches the given val.
    Realm: Client and Server
    Added in: 1.8.7
    Parameters: diff --git a/gamemodes/terrortown/gamemode/util.lua b/gamemodes/terrortown/gamemode/util.lua index 2a5cdcda7..f6759851d 100644 --- a/gamemodes/terrortown/gamemode/util.lua +++ b/gamemodes/terrortown/gamemode/util.lua @@ -223,12 +223,16 @@ function table.HasValue(tbl, val) end function table.HasItemWithPropertyValue(tbl, key, val) - if not tbl or not key then return end + return table.GetFirstItemWithPropertyValue(tbl, key, val) ~= nil +end + +function table.GetFirstItemWithPropertyValue(tbl, key, val) + if not tbl or not key then return nil end for _, v in pairs(tbl) do - if v[key] and v[key] == val then return true end + if v[key] == val then return v end end - return false + return nil end -- Value equality for tables From c75a50526789b8579a6c863524acf9ae929183bc Mon Sep 17 00:00:00 2001 From: Malivil Date: Sat, 16 Aug 2025 08:48:29 -0400 Subject: [PATCH 065/140] Added ability to control whether the mercenary gets body armor as part of their loadout (defaults to `true`) --- CONVARS.md | 1 + RELEASE.md | 3 +++ .../gamemode/roles/mercenary/shared.lua | 26 ++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/CONVARS.md b/CONVARS.md index 29e0db501..ddc8122f8 100644 --- a/CONVARS.md +++ b/CONVARS.md @@ -411,6 +411,7 @@ ttt_deputy_activation_credits 0 // The number of credits // Mercenary ttt_mercenary_credits_starting 1 // The number of credits a Mercenary should start with +ttt_mercenary_armor_loadout 1 // Whether the mercenary should get body armor as part of their loadout // Veteran ttt_veteran_damage_bonus 0.5 // Damage bonus that the Veteran has when they are the last innocent alive (e.g. 0.5 = 50% more damage) diff --git a/RELEASE.md b/RELEASE.md index 2eab2b4da..dd57aac5b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,6 +3,9 @@ ## 2.3.5 (Beta) **Released:** +### Additions +- Added ability to control whether the mercenary gets body armor as part of their loadout (defaults to `true`) + ### Fixes - Fixed illusionist not blocking traitor team voice chat - Fixed promoted impersonator and deputy who are searched by a detective not always showing their true role on the scoreboard diff --git a/gamemodes/terrortown/gamemode/roles/mercenary/shared.lua b/gamemodes/terrortown/gamemode/roles/mercenary/shared.lua index 7ed5aac33..32e7d7abe 100644 --- a/gamemodes/terrortown/gamemode/roles/mercenary/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/mercenary/shared.lua @@ -2,6 +2,20 @@ AddCSLuaFile() local hook = hook +------------- +-- CONVARS -- +------------- + +-- Create this here since it has a different default value +CreateConVar("ttt_mercenary_shop_mode", "2", FCVAR_REPLICATED) +local mercenary_armor_loadout = CreateConVar("ttt_mercenary_armor_loadout", "1", FCVAR_REPLICATED, "Whether the mercenary should get body armor as part of their loadout", 0, 1) + +ROLE_CONVARS[ROLE_MERCENARY] = {} +table.insert(ROLE_CONVARS[ROLE_MERCENARY], { + cvar = "ttt_mercenary_armor_loadout", + type = ROLE_CONVAR_TYPE_BOOL +}) + -- Initialize role features ROLE_STARTING_CREDITS[ROLE_MERCENARY] = 1 ROLE_HAS_SHOP_MODE[ROLE_MERCENARY] = true @@ -64,11 +78,9 @@ hook.Add("Initialize", "Mercenary_Shared_Initialize", function() end) hook.Add("TTTPrepareRound", "Mercenary_Shared_TTTPrepareRound", function() InitializeEquipment() -end) - -------------- --- CONVARS -- -------------- --- Create this here since it has a different default value -CreateConVar("ttt_mercenary_shop_mode", "2", FCVAR_REPLICATED) \ No newline at end of file + -- Update the loadout state here because "Initialize" is called before the convar value is loaded + -- so we can't do it in InitializeEquipment + local armor = table.GetFirstItemWithPropertyValue(EquipmentItems[ROLE_MERCENARY], "id", EQUIP_ARMOR) + armor.loadout = mercenary_armor_loadout:GetBool() +end) \ No newline at end of file From fad5dd6943ce2a0e529257ce13868b140ed95bd8 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sat, 16 Aug 2025 08:49:58 -0400 Subject: [PATCH 066/140] Added new method to luacheck --- .luacheckrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.luacheckrc b/.luacheckrc index 3b71d230b..ac4c4246e 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1250,6 +1250,7 @@ std = { EqualValues = {}, ExcludedKeys = {}, GetKeys = {}, + GetFirstItemWithPropertyValue = {}, HasItemWithPropertyValue = {}, HasTable = {}, HasValue = {}, From c1aa8b0af6367efcf892a1a92c4a37fbcc9500ea Mon Sep 17 00:00:00 2001 From: Malivil Date: Sat, 16 Aug 2025 09:06:09 -0400 Subject: [PATCH 067/140] Added HTML docs for new mercenary convar --- docs/teams/innocent.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/teams/innocent.html b/docs/teams/innocent.html index e8bfce346..af3d4d5ff 100644 --- a/docs/teams/innocent.html +++ b/docs/teams/innocent.html @@ -426,6 +426,12 @@

    Role Configuration:

    + + ttt_mercenary_armor_loadout + 1 + Boolean + Whether the mercenary should get body armor as part of their loadout. + ttt_mercenary_credits_starting 1 From 9ba6b0f67300b684da12d6ccfb7ccf124d4c1d2a Mon Sep 17 00:00:00 2001 From: Malivil Date: Sat, 16 Aug 2025 09:07:07 -0400 Subject: [PATCH 068/140] Added missing betaonly tag --- docs/teams/innocent.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/teams/innocent.html b/docs/teams/innocent.html index af3d4d5ff..7d07a1639 100644 --- a/docs/teams/innocent.html +++ b/docs/teams/innocent.html @@ -427,7 +427,7 @@

    Role Configuration:

    - ttt_mercenary_armor_loadout + ttt_mercenary_armor_loadout   1 Boolean Whether the mercenary should get body armor as part of their loadout. From 6b85ea7506cb1ddeb10418eb26ad67b6fa4b822c Mon Sep 17 00:00:00 2001 From: Malivil Date: Sat, 16 Aug 2025 10:24:55 -0400 Subject: [PATCH 069/140] - Added ability to change vampire's fang drain overheal to overheal (default), increase max health, or do both - Added `maxchange` parameter to `TTTVampireBodyEaten` hook --- API/HOOKS.md | 3 +- CONVARS.md | 1 + RELEASE.md | 2 ++ docs/api/hooks.html | 3 +- docs/teams/traitor.html | 12 ++++++++ .../entities/weapons/weapon_vam_fangs.lua | 30 ++++++++++++++++--- .../gamemode/roles/vampire/shared.lua | 11 +++++++ 7 files changed, 56 insertions(+), 6 deletions(-) diff --git a/API/HOOKS.md b/API/HOOKS.md index a551b5b50..3a857b6f3 100644 --- a/API/HOOKS.md +++ b/API/HOOKS.md @@ -1341,7 +1341,7 @@ Called after globals are synced but but before role colors and strings are set. *Realm:* Client and Server\ *Added in:* 1.2.7 -### TTTVampireBodyEaten(ply, ent, living, healed) +### TTTVampireBodyEaten(ply, ent, living, healed, maxchange) Called after a vampire eats a body.\ *Realm:* Server\ *Added in:* 1.6.16\ @@ -1350,6 +1350,7 @@ Called after a vampire eats a body.\ - *ent* - The target entity. Generally either a player or a ragdoll - *living* - Whether the target entity was living at the time they were eaten - *healed* - The amount of health the player gained from eating the body +- *maxchange* - The amount of maximum health the player gained from eating the body *(Added in 2.3.5)* ### TTTVampireInvisibilityChange(ply, invisible) Called when a vampire starts or ends their invisibility.\ diff --git a/CONVARS.md b/CONVARS.md index ddc8122f8..53c7b35b4 100644 --- a/CONVARS.md +++ b/CONVARS.md @@ -303,6 +303,7 @@ ttt_vampire_fang_dead_timer 0 // The amount of time fan ttt_vampire_fang_heal 50 // The amount of health a Vampire will heal by when they fully drain a target's blood ttt_vampire_fang_overheal 25 // The amount over the Vampire's normal maximum health (e.g. 100 + this ConVar) that the Vampire can heal to by drinking blood. ttt_vampire_fang_overheal_living -1 // The amount of overheal (see "ttt_vampire_fang_overheal") to give if the Vampire's target is living. Set to -1 to use the same amount as "ttt_vampire_fang_overheal" instead +ttt_vampire_fang_overheal_mode 0 // How to handle healing a vampire over their maximum health. 0 - Increase health. 1 - Increase max health. 2 - Increase both. ttt_vampire_fang_unfreeze_delay 2 // The number of seconds before players who were frozen in place by the fangs should be released if the Vampire stops using the fangs on them ttt_vampire_prime_death_mode 0 // What to do when the prime Vampire(s) (e.g. players who spawn as Vampires originally) are killed. 0 - Do nothing. 1 - Kill all Vampire thralls (non-prime Vampires). 2 - Revert all Vampire thralls (non-prime Vampires) to their original role ttt_vampire_prime_only_convert 1 // Whether only prime Vampires (e.g. players who spawn as Vampire originally) are allowed to convert other players diff --git a/RELEASE.md b/RELEASE.md index dd57aac5b..4805839b0 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -5,6 +5,7 @@ ### Additions - Added ability to control whether the mercenary gets body armor as part of their loadout (defaults to `true`) +- Added ability to change vampire's fang drain overheal to overheal (default), increase max health, or do both ### Fixes - Fixed illusionist not blocking traitor team voice chat @@ -13,6 +14,7 @@ ### Developer - Added `state` parameter to `TTTTeamVoiceChatTargets` hook - Added `table.GetFirstItemWithPropertyValue` static method +- Added `maxchange` parameter to `TTTVampireBodyEaten` hook ## 2.3.4 (Beta) **Released: August 9th, 2025** diff --git a/docs/api/hooks.html b/docs/api/hooks.html index d6e0997d6..81a2f7d1f 100644 --- a/docs/api/hooks.html +++ b/docs/api/hooks.html @@ -2002,7 +2002,7 @@

    TTTUpdateRoleState()Added in: 1.2.7

    -

    TTTVampireBodyEaten(ply, ent, living, healed)

    +

    TTTVampireBodyEaten(ply, ent, living, healed, maxchange)

    Called after a Vampire eats a body.
    Realm: Server
    @@ -2014,6 +2014,7 @@

     

    TTTVampireInvisibilityChange(ply, invisible)

    diff --git a/docs/teams/traitor.html b/docs/teams/traitor.html index bed696360..15fc85200 100644 --- a/docs/teams/traitor.html +++ b/docs/teams/traitor.html @@ -984,6 +984,18 @@

    Role Configuration:

    Integer The amount over the Vampire's normal maximum health that the Vampire can heal to by drinking blood from living players. (Set to -1 to use the same amount as ttt_vampire_fang_overheal instead.) + + ttt_vampire_fang_overheal_mode + 0 + Integer (0-1) + How to handle healing a vampire over their maximum health: +
      +
    1. Heal over their max health.
    2. +
    3. Increase their max health.
    4. +
    5. Increase their max health and heal them to full.
    6. +
    + + ttt_vampire_fang_timer 5 diff --git a/gamemodes/terrortown/entities/weapons/weapon_vam_fangs.lua b/gamemodes/terrortown/entities/weapons/weapon_vam_fangs.lua index 5be7e0387..2bc214a4e 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_vam_fangs.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_vam_fangs.lua @@ -73,6 +73,7 @@ if SERVER then CreateConVar("ttt_vampire_fang_dead_timer", "0", FCVAR_NONE, "The amount of time fangs must be used to fully drain a dead target's blood. Set to 0 to use the same time as \"ttt_vampire_fang_timer\"", 0, 30) CreateConVar("ttt_vampire_fang_timer", "5", FCVAR_NONE, "The amount of time fangs must be used to fully drain a target's blood", 0, 30) CreateConVar("ttt_vampire_fang_heal", "50", FCVAR_NONE, "The amount of health a vampire will heal by when they fully drain a target's blood", 0, 100) + CreateConVar("ttt_vampire_fang_overheal_mode", "0", FCVAR_NONE, "How to handle healing a vampire over their maximum health. 0 - Increase health. 1 - Increase max health. 2 - Do both", 0, 2) CreateConVar("ttt_vampire_fang_overheal", "25", FCVAR_NONE, "The amount over the vampire's normal maximum health (e.g. 100 + this ConVar) that the vampire can heal to by drinking blood", 0, 100) CreateConVar("ttt_vampire_fang_overheal_living", "-1", FCVAR_NONE, "The amount of overheal (see \"ttt_vampire_fang_overheal\") to give if the vampire's target is living. Set to -1 to use the same amount as \"ttt_vampire_fang_overheal\" instead", -1, 100) CreateConVar("ttt_vampire_fang_unfreeze_delay", "2", FCVAR_NONE, "The number of seconds before players who were frozen in place by the fangs should be released if the vampire stops using the fangs on them", 0, 15) @@ -364,16 +365,37 @@ function SWEP:DoKill() end function SWEP:DoHeal(living) + local mode = GetConVar("ttt_vampire_fang_overheal_mode"):GetInt() + local vamheal = GetConVar("ttt_vampire_fang_heal"):GetInt() local vamoverheal = GetConVar("ttt_vampire_fang_overheal_living"):GetInt() if not living or vamoverheal < 0 then vamoverheal = GetConVar("ttt_vampire_fang_overheal"):GetInt() end - local vamheal = GetConVar("ttt_vampire_fang_heal"):GetInt() local owner = self:GetOwner() - local health = math.min(owner:Health() + vamheal, owner:GetMaxHealth() + vamoverheal) - hook.Call("TTTVampireBodyEaten", nil, owner, self.TargetEntity, living, health - owner:Health()) - owner:SetHealth(health) + local health = owner:Health() + local maxhealth = owner:GetMaxHealth() + local newhealth = math.min(health + vamheal, maxhealth + vamoverheal) + local maxchange = 0 + + -- If we've exceeded the max health and we're set to not actually overheal, update max health instead + if newhealth > maxhealth and mode > VAMPIRE_OVERHEAL_MODE_HEALTH then + -- Count how much the max health is about to change + maxchange = newhealth - maxhealth + owner:SetMaxHealth(newhealth) + + -- If we're only changing the max health, reduce the amount we're healing to the previous max health + if mode == VAMPIRE_OVERHEAL_MODE_MAX_HEALTH then + newhealth = maxhealth + -- Otherwise heal them by the amount their max health changed + else + newhealth = health + maxchange + end + end + + local healed = newhealth - owner:Health() + owner:SetHealth(newhealth) + hook.Call("TTTVampireBodyEaten", nil, owner, self.TargetEntity, living, healed, maxchange) end function SWEP:UnfreezeTarget() diff --git a/gamemodes/terrortown/gamemode/roles/vampire/shared.lua b/gamemodes/terrortown/gamemode/roles/vampire/shared.lua index 9fd3727e3..558e6082a 100644 --- a/gamemodes/terrortown/gamemode/roles/vampire/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/vampire/shared.lua @@ -14,6 +14,11 @@ VAMPIRE_THRALL_FF_MODE_NONE = 0 VAMPIRE_THRALL_FF_MODE_REFLECT = 1 VAMPIRE_THRALL_FF_MODE_IMMUNE = 2 +-- Vampire overheal mode +VAMPIRE_OVERHEAL_MODE_HEALTH = 0 +VAMPIRE_OVERHEAL_MODE_MAX_HEALTH = 1 +VAMPIRE_OVERHEAL_MODE_BOTH = 2 + -- Initialize role features ROLE_CAN_SEE_JESTERS[ROLE_VAMPIRE] = true ROLE_CAN_SEE_MIA[ROLE_VAMPIRE] = true @@ -127,6 +132,12 @@ table.insert(ROLE_CONVARS[ROLE_VAMPIRE], { type = ROLE_CONVAR_TYPE_NUM, decimal = 0 }) +table.insert(ROLE_CONVARS[ROLE_VAMPIRE], { + cvar = "ttt_vampire_fang_overheal_mode", + type = ROLE_CONVAR_TYPE_DROPDOWN, + choices = {"Health", "Max health", "Both"}, + isNumeric = true +}) table.insert(ROLE_CONVARS[ROLE_VAMPIRE], { cvar = "ttt_vampire_fang_overheal_living", type = ROLE_CONVAR_TYPE_NUM, From 341d581aea93dcec04bc54d6086e1f34b0cf6fea Mon Sep 17 00:00:00 2001 From: Malivil Date: Sat, 16 Aug 2025 10:26:16 -0400 Subject: [PATCH 070/140] Added new globals --- .luacheckrc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.luacheckrc b/.luacheckrc index ac4c4246e..d2ee75fdc 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1089,6 +1089,9 @@ globals = { "VAMPIRE_DEATH_NONE", "VAMPIRE_DEATH_KILL_CONVERTED", "VAMPIRE_DEATH_REVERT_CONVERTED", + "VAMPIRE_OVERHEAL_MODE_HEALTH", + "VAMPIRE_OVERHEAL_MODE_MAX_HEALTH", + "VAMPIRE_OVERHEAL_MODE_BOTH", "VAMPIRE_THRALL_FF_MODE_NONE", "VAMPIRE_THRALL_FF_MODE_REFLECT", "VAMPIRE_THRALL_FF_MODE_IMMUNE", From 6bb06963dcc63e9aaee87d8da8049d14089d2e17 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sat, 16 Aug 2025 10:52:40 -0400 Subject: [PATCH 071/140] Added ability to disable automatically confirming a players death when searching their body (disabled by default) --- CONVARS.md | 3 ++- RELEASE.md | 1 + .../entities/weapons/weapon_ttt_binoculars.lua | 2 +- gamemodes/terrortown/gamemode/cl_search.lua | 3 ++- gamemodes/terrortown/gamemode/cl_targetid.lua | 10 ++++++---- gamemodes/terrortown/gamemode/corpse.lua | 3 ++- gamemodes/terrortown/gamemode/player.lua | 4 ++-- 7 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CONVARS.md b/CONVARS.md index 53c7b35b4..ddae76672 100644 --- a/CONVARS.md +++ b/CONVARS.md @@ -1242,7 +1242,8 @@ ttt_dna_scan_detectives_loadout 0 // Whether all detectives ttt_dna_scan_on_dialog 1 // Whether to show a button to open the DNA scanner on the body search dialog ttt_dna_scan_only_drop_on_death 0 // Whether the DNA scanner should only be droppable when the holder dies ttt_spectator_corpse_search 1 // Whether spectators can search bodies (not shared with other players) -ttt_corpse_search_not_shared 0 // Whether corpse searches are not shared with other players (only affects non-detective-like searchers) +ttt_corpse_search_auto_confirm 1 // Whether corpse searches are not shared with other players (only affects non-detective-like searchers) +ttt_corpse_search_not_shared 0 // Whether corpse searches automatically confirm the death of the player ttt_corpse_search_killer_team_text_traitor 0 // Whether corpse searches should include flavor text hinting at the team of their traitor team killer ttt_corpse_search_killer_team_text_innocent 0 // Whether corpse searches should include flavor text hinting at the team of their innocent team killer ttt_corpse_search_killer_team_text_monster 0 // Whether corpse searches should include flavor text hinting at the team of their monster team killer diff --git a/RELEASE.md b/RELEASE.md index 4805839b0..e7af609e7 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -6,6 +6,7 @@ ### Additions - Added ability to control whether the mercenary gets body armor as part of their loadout (defaults to `true`) - Added ability to change vampire's fang drain overheal to overheal (default), increase max health, or do both +- Added ability to disable automatically confirming a players death when searching their body (disabled by default) ### Fixes - Fixed illusionist not blocking traitor team voice chat diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_binoculars.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_binoculars.lua index f786a88f4..28c0c9b3b 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_binoculars.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_binoculars.lua @@ -165,7 +165,7 @@ local confirm = Sound("npc/turret_floor/click1.wav") function SWEP:IdentifyCorpse() if SERVER then local tr = self:GetOwner():GetEyeTrace(MASK_SHOT) - CORPSE.ShowSearch(self:GetOwner(), tr.Entity, false, true) + CORPSE.ShowSearch(self:GetOwner(), tr.Entity, not GetConVar("ttt_corpse_search_auto_confirm"):GetBool(), true) elseif IsFirstTimePredicted() then LocalPlayer():EmitSound(confirm) end diff --git a/gamemodes/terrortown/gamemode/cl_search.lua b/gamemodes/terrortown/gamemode/cl_search.lua index d510ac374..d140ddcfe 100644 --- a/gamemodes/terrortown/gamemode/cl_search.lua +++ b/gamemodes/terrortown/gamemode/cl_search.lua @@ -447,7 +447,8 @@ local function ShowSearchScreen(search_raw) btn:SetDisabled(true) end, disabled = function() - return client:IsSpec() or not client:KeyDownLast(IN_WALK) or CORPSE.GetFound(rag, false) + local covert = client:KeyDownLast(IN_WALK) or not GetConVar("ttt_corpse_search_auto_confirm"):GetBool() + return client:IsSpec() or not covert or CORPSE.GetFound(rag, false) end }) diff --git a/gamemodes/terrortown/gamemode/cl_targetid.lua b/gamemodes/terrortown/gamemode/cl_targetid.lua index 3ea8de6ca..d7b00a6da 100644 --- a/gamemodes/terrortown/gamemode/cl_targetid.lua +++ b/gamemodes/terrortown/gamemode/cl_targetid.lua @@ -48,10 +48,12 @@ local ClassHint = { end -- Only show covert search label if the body can be searched elseif CORPSE.CanBeSearched(ply, ent) then - local ownerEnt = CORPSE.GetPlayer(ent) - -- and has not already - if IsValid(ownerEnt) and not ownerEnt:GetNWBool("body_searched", false) then - txt = txt .. "_covert" + if GetConVar("ttt_corpse_search_auto_confirm"):GetBool() then + local ownerEnt = CORPSE.GetPlayer(ent) + -- and has not already + if IsValid(ownerEnt) and not ownerEnt:GetNWBool("body_searched", false) then + txt = txt .. "_covert" + end end -- If the body can't be searched, change the label to say "call a Detective" instead else diff --git a/gamemodes/terrortown/gamemode/corpse.lua b/gamemodes/terrortown/gamemode/corpse.lua index 3b58bc84a..e8c4593ed 100644 --- a/gamemodes/terrortown/gamemode/corpse.lua +++ b/gamemodes/terrortown/gamemode/corpse.lua @@ -1,6 +1,7 @@ ---- Corpse functions CreateConVar("ttt_corpse_search_not_shared", "0", FCVAR_NONE, "Whether corpse searches are not shared with other players (only affects non-detective-like searchers)", 0, 1) +CreateConVar("ttt_corpse_search_auto_confirm", "1", FCVAR_REPLICATED, "Whether corpse searches automatically confirm the death of the player", 0, 1) -- namespaced because we have no ragdoll metatable CORPSE = CORPSE or {} @@ -503,7 +504,7 @@ function CORPSE.ShowSearch(ply, rag, covert, long_range) else net.Broadcast() - -- Let detctives know that this body has already been searched + -- Let detectives know that this body has already been searched net.Start("TTT_RemoveCorpseCall") net.WriteString(rag.sid) net.Send(GetExtendedDetectiveFilter(true)) diff --git a/gamemodes/terrortown/gamemode/player.lua b/gamemodes/terrortown/gamemode/player.lua index e942ae14e..0e4bdf93e 100644 --- a/gamemodes/terrortown/gamemode/player.lua +++ b/gamemodes/terrortown/gamemode/player.lua @@ -483,7 +483,7 @@ function GM:KeyRelease(ply, key) return true end elseif tr.Entity.player_ragdoll then - CORPSE.ShowSearch(ply, tr.Entity, ply:KeyDown(IN_WALK) or ply:KeyDownLast(IN_WALK)) + CORPSE.ShowSearch(ply, tr.Entity, ply:KeyDown(IN_WALK) or ply:KeyDownLast(IN_WALK) or not GetConVar("ttt_corpse_search_auto_confirm"):GetBool()) return true end end @@ -504,7 +504,7 @@ local function SpecUseKey(ply, cmd, arg) ply:Spectate(OBS_MODE_IN_EYE) ply:SpectateEntity(tr.Entity) else - CORPSE.ShowSearch(ply, tr.Entity) + CORPSE.ShowSearch(ply, tr.Entity, not GetConVar("ttt_corpse_search_auto_confirm"):GetBool()) end elseif tr.Entity:IsPlayer() and tr.Entity:IsActive() then ply:Spectate(ply.spec_mode or OBS_MODE_CHASE) From bedc045ad40452273a8c1b810f04e915442dc04f Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 08:52:23 +0100 Subject: [PATCH 072/140] Added 'Kill Target' task --- .../{killairbourne.lua => killairborne.lua} | 24 ++-- .../roles/taskmaster/tasks/killtarget.lua | 114 ++++++++++++++++++ 2 files changed, 126 insertions(+), 12 deletions(-) rename gamemodes/terrortown/gamemode/roles/taskmaster/tasks/{killairbourne.lua => killairborne.lua} (58%) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairbourne.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua similarity index 58% rename from gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairbourne.lua rename to gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua index 4d87c05bc..f6af6821c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairbourne.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua @@ -3,11 +3,11 @@ local net = net local TASK = {} -TASK.id = "killairbourne" +TASK.id = "killairborne" TASK.isKillTask = true TASK.Name = function(ply) - return "Kill an Airbourne Player" + return "Kill an Airborne Player" end TASK.Description = function(ply) @@ -15,8 +15,8 @@ TASK.Description = function(ply) end if SERVER then - util.AddNetworkString("TTT_Taskmaster_KillAirbourne_Assigned") - util.AddNetworkString("TTT_Taskmaster_KillAirbourne_Cleanup") + util.AddNetworkString("TTT_Taskmaster_KillAirborne_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillAirborne_Cleanup") TASK.CanAssignTask = function(ply) return true @@ -27,7 +27,7 @@ if SERVER then } TASK.OnTaskAssigned = function(ply) - hook.Add("PlayerDeath", "Taskmaster_KillAirbourne_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + hook.Add("PlayerDeath", "Taskmaster_KillAirborne_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) if not IsPlayer(victim) then return end if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end @@ -36,13 +36,13 @@ if SERVER then end end) - net.Start("TTT_Taskmaster_KillAirbourne_Assigned") + net.Start("TTT_Taskmaster_KillAirborne_Assigned") net.Send(ply) end TASK.OnTaskRemoved = function(ply) - hook.Remove("PlayerDeath", "Taskmaster_KillAirbourne_PlayerDeath_" .. ply:SteamID64()) - net.Start("TTT_Taskmaster_KillAirbourne_Cleanup") + hook.Remove("PlayerDeath", "Taskmaster_KillAirborne_PlayerDeath_" .. ply:SteamID64()) + net.Start("TTT_Taskmaster_KillAirborne_Cleanup") net.Send(ply) end @@ -50,8 +50,8 @@ if SERVER then end if CLIENT then - net.Receive("TTT_Taskmaster_KillAirbourne_Assigned", function() - hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillAirbourne_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64(), function(ent, cli, text, col, secondaryText) + net.Receive("TTT_Taskmaster_KillAirborne_Assigned", function() + hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillAirborne_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64(), function(ent, cli, text, col, secondaryText) if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end if ent:OnGround() then @@ -62,8 +62,8 @@ if CLIENT then end) end) - net.Receive("TTT_Taskmaster_KillAirbourne_Cleanup", function() - hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillAirbourne_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64()) + net.Receive("TTT_Taskmaster_KillAirborne_Cleanup", function() + hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillAirborne_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64()) end) end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua new file mode 100644 index 000000000..75e072fc8 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua @@ -0,0 +1,114 @@ +local hook = hook +local net = net +local player = player +local util = util + +local TASK = {} + +TASK.id = "killtarget" +TASK.isKillTask = true + +TASK.Name = function(ply) + local name = "Target" + if IsPlayer(ply.Task_KillTargetPlayer) then + name = ply.Task_KillTargetPlayer:Nick() + end + return "Kill " .. name +end + +TASK.Description = function(ply) + local name = "target" + if IsPlayer(ply.Task_KillTargetPlayer) then + name = ply.Task_KillTargetPlayer:Nick() + end + return "Kill " .. name .. " by any means necessary" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_KillTarget_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillTarget_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON + } + + local function GetRandomTarget(ply) + -- Find the first random living player + for _, p in RandomPairs(player.GetAll()) do + if not p:Alive() or p:IsSpec() then continue end + if p == ply then continue end + return p + end + return nil + end + + TASK.OnTaskAssigned = function(ply) + local sid64 = ply:SteamID64() + + ply:SetProperty("Task_KillTargetPlayer", GetRandomTarget(ply), ply) + + hook.Add("PlayerDeath", "Taskmaster_KillTarget_PlayerDeath_" .. sid64, function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if victim == ply.Task_KillTargetPlayer then + ply:CompleteTask(TASK.id) + end + end) + + hook.Add("PostPlayerDeath", "Taskmaster_KillTarget_PostPlayerDeath_" .. sid64, function(victim) + if ply.Task_KillTargetPlayer ~= victim then return end + + local target = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. target:Nick()) + ply:SetProperty("Task_KillTargetPlayer", target, ply) + end) + + hook.Add("PlayerDisconnected", "Taskmaster_KillTarget_PlayerDisconnected_" .. sid64, function(leaver) + if ply.Task_KillTargetPlayer ~= leaver then return end + + local target = GetRandomTarget(ply) + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_KillTargetPlayer", target, ply) + end) + + net.Start("TTT_Taskmaster_KillTarget_Assigned") + net.Send(ply) + end + + TASK.OnTaskComplete = function(ply) + local sid64 = ply:SteamID64() + + hook.Remove("PlayerDeath", "Taskmaster_KillTarget_PlayerDeath_" .. sid64) + hook.Remove("PostPlayerDeath", "Taskmaster_KillTarget_PostPlayerDeath_" .. sid64) + hook.Remove("PlayerDisconnected", "Taskmaster_KillTarget_PlayerDisconnected_" .. sid64) + + net.Start("TTT_Taskmaster_KillTarget_Cleanup") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + TASK.OnTaskComplete(ply) + ply:ClearProperty("Task_KillTargetPlayer", ply) + end +end + +if CLIENT then + net.Receive("TTT_Taskmaster_KillTarget_Assigned", function() + hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_KillTarget_TTTTargetIDPlayerTargetIcon_" .. LocalPlayer():SteamID64(), function(ply, cli, showJester) + if cli:IsActiveTaskmaster() and ply == cli.Task_KillTargetPlayer then + return "task", true, ROLE_COLORS_SPRITE[ROLE_TASKMASTER], "down" + end + end) + end) + + net.Receive("TTT_Taskmaster_KillTarget_Cleanup", function() + hook.Remove("TTTTargetIDPlayerTargetIcon", "Taskmaster_KillTarget_TTTTargetIDPlayerTargetIcon_" .. LocalPlayer():SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 39c7e9a5257df0c45c1b0086671bfa243f5efbe5 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 09:04:34 +0100 Subject: [PATCH 073/140] Added 'Kill Kill a Player With a Pistol' task --- .../roles/taskmaster/tasks/killwithpistol.lua | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killwithpistol.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killwithpistol.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killwithpistol.lua new file mode 100644 index 000000000..d4f3cec4b --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killwithpistol.lua @@ -0,0 +1,41 @@ +local hook = hook + +local TASK = {} + +TASK.id = "killwithpistol" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Kill a Player With a Pistol" +end + +TASK.Description = function(ply) + return "Kill another player using a pistol" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillWithPistol_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if IsValid(inflictor) and inflictor:IsWeapon() and inflictor.Kind == WEAPON_PISTOL then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillWithPistol_PlayerDeath_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 1e0449cfc8d48bd02d089f582df14684259f0a22 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 09:12:50 +0100 Subject: [PATCH 074/140] Added 'Kill a Player While Aiming' task --- .../roles/taskmaster/tasks/killaiming.lua | 41 +++++++++++++++++++ .../{killwithpistol.lua => killpistol.lua} | 6 +-- 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killaiming.lua rename gamemodes/terrortown/gamemode/roles/taskmaster/tasks/{killwithpistol.lua => killpistol.lua} (76%) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killaiming.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killaiming.lua new file mode 100644 index 000000000..df6c34850 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killaiming.lua @@ -0,0 +1,41 @@ +local hook = hook + +local TASK = {} + +TASK.id = "killaiming" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Kill a Player While Aiming" +end + +TASK.Description = function(ply) + return "Kill another player while aiming down sights" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillAiming_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if IsValid(inflictor) and inflictor:IsWeapon() and inflictor.GetIronsights and inflictor:GetIronsights() then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillAiming_PlayerDeath_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killwithpistol.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killpistol.lua similarity index 76% rename from gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killwithpistol.lua rename to gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killpistol.lua index d4f3cec4b..e48b6a7a0 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killwithpistol.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killpistol.lua @@ -2,7 +2,7 @@ local hook = hook local TASK = {} -TASK.id = "killwithpistol" +TASK.id = "killpistol" TASK.isKillTask = true TASK.Name = function(ply) @@ -21,7 +21,7 @@ if SERVER then TASK.RequiredFeatures = {} TASK.OnTaskAssigned = function(ply) - hook.Add("PlayerDeath", "Taskmaster_KillWithPistol_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + hook.Add("PlayerDeath", "Taskmaster_KillPistol_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) if not IsPlayer(victim) then return end if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end @@ -32,7 +32,7 @@ if SERVER then end TASK.OnTaskRemoved = function(ply) - hook.Remove("PlayerDeath", "Taskmaster_KillWithPistol_PlayerDeath_" .. ply:SteamID64()) + hook.Remove("PlayerDeath", "Taskmaster_KillPistol_PlayerDeath_" .. ply:SteamID64()) end TASK.OnTaskComplete = TASK.OnTaskRemoved From 984f08f0aa35f1ecd543b0627a950e23cf8d9d3a Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 09:19:20 +0100 Subject: [PATCH 075/140] Added 'Kill a Player With Your Last Bullet' task --- .../roles/taskmaster/tasks/killlastbullet.lua | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua new file mode 100644 index 000000000..f8b5d6875 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua @@ -0,0 +1,43 @@ +local hook = hook + +local TASK = {} + +TASK.id = "killlastbullet" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Kill a Player With Your Last Bullet" +end + +TASK.Description = function(ply) + return "Kill another player with the last bullet in your gun's clip" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillLastBullet_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + timer.Simple(0, function() + if IsValid(inflictor) and inflictor:IsWeapon() and inflictor:GetMaxClip1() > 0 and inflictor:Clip1() == 0 then + ply:CompleteTask(TASK.id) + end + end) + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillLastBullet_PlayerDeath_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 360e391a5043aa853f3fad1b963df2c9acbbd401 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 09:27:41 +0100 Subject: [PATCH 076/140] Added 'Kill a Player With a Crowbar' task --- .../roles/taskmaster/tasks/killcrowbar.lua | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killcrowbar.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killcrowbar.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killcrowbar.lua new file mode 100644 index 000000000..9b6d4bc59 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killcrowbar.lua @@ -0,0 +1,41 @@ +local hook = hook + +local TASK = {} + +TASK.id = "killcrowbar" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Kill a Player With a Crowbar" +end + +TASK.Description = function(ply) + return "Kill another player using a crowbar" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillCrowbar_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if IsValid(inflictor) and inflictor:GetClass() == "weapon_zm_improvised" then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillCrowbar_PlayerDeath_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 2fe2830666fbe84c79c09c435163c45ed25e1b27 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 09:52:05 +0100 Subject: [PATCH 077/140] Added 'Kill a Player With a Headshot' task --- .../roles/taskmaster/tasks/killheadshot.lua | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killheadshot.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killheadshot.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killheadshot.lua new file mode 100644 index 000000000..e23785cec --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killheadshot.lua @@ -0,0 +1,59 @@ +local hook = hook +local player = player + +local PlayerIterator = player.Iterator + +local TASK = {} + +TASK.id = "killheadshot" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Kill a Player With a Headshot" +end + +TASK.Description = function(ply) + return "Kill another player by shooting them in the head" +end + +if SERVER then + TASK.CanAssignTask = function(ply) + local allHaveHeadbox = true + for _, p in PlayerIterator() do + local missingHeadbox = true + local set = p:GetHitboxSet() + for hitbox = 0, p:GetHitBoxCount(set) - 1 do + if p:GetHitBoxHitGroup(hitbox, set) == HITGROUP_HEAD then + missingHeadbox = false + break + end + end + if missingHeadbox then + allHaveHeadbox = false + break + end + end + return allHaveHeadbox + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillHeadshot_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if victim:GetNWBool("LastHitCrit") then + ply:CompleteTask(TASK.id) + end + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillHeadshot_PlayerDeath_" .. ply:SteamID64()) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 4168b20db4cf2bc565733faab3c68edddd5257ff Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 10:18:21 +0100 Subject: [PATCH 078/140] Added 'Kill a Player Who Attacked First' task --- .../roles/taskmaster/tasks/killretaliate.lua | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua new file mode 100644 index 000000000..40f1c7f27 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua @@ -0,0 +1,111 @@ +local hook = hook +local player = player + +local PlayerIterator = player.Iterator + +local TASK = {} + +TASK.id = "killretaliate" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Kill a Player Who Attacked First" +end + +TASK.Description = function(ply) + return "Kill another player who attacked you first" +end + +local TASK_KILLRETALIATE_NONE = 0 +local TASK_KILLRETALIATE_THEYATTACKED = 1 +local TASK_KILLRETALIATE_YOUATTACKED = 2 + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_KillRetaliate_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillRetaliate_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERTEXT + } + + TASK.OnTaskAssigned = function(ply) + + for _, p in PlayerIterator() do + if ply == p then continue end + p:SetProperty("Task_KillRetaliateAttacked", TASK_KILLRETALIATE_NONE, ply) + end + + local sid64 = ply:SteamID64() + + hook.Add("PlayerDeath", "Taskmaster_KillRetaliate_PlayerDeath_" .. sid64, function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if victim.Task_KillRetaliateAttacked == TASK_KILLRETALIATE_THEYATTACKED then + ply:CompleteTask(TASK.id) + end + end) + + hook.Add("PostEntityTakeDamage", "Taskmaster_KillRetaliate_PostEntityTakeDamage_" .. sid64, function(entity, dmginfo, wasDamageTaken) + if not wasDamageTaken then return end + if not IsPlayer(entity) then return end + + local attacker = dmginfo:GetAttacker() + if not IsPlayer(attacker) then return end + + if entity == ply and attacker.Task_KillRetaliateAttacked == TASK_KILLRETALIATE_NONE then + attacker:SetProperty("Task_KillRetaliateAttacked", TASK_KILLRETALIATE_THEYATTACKED, ply) + elseif attacker == ply and entity.Task_KillRetaliateAttacked == TASK_KILLRETALIATE_NONE then + entity:SetProperty("Task_KillRetaliateAttacked", TASK_KILLRETALIATE_YOUATTACKED, ply) + end + end) + + net.Start("TTT_Taskmaster_KillRetaliate_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + for _, p in PlayerIterator() do + if ply == p then continue end + p:ClearProperty("Task_KillRetaliateAttacked", ply) + end + + local sid64 = ply:SteamID64() + + hook.Remove("PlayerDeath", "Taskmaster_KillRetaliate_PlayerDeath_" .. sid64) + hook.Remove("PostEntityTakeDamage", "Taskmaster_KillRetaliate_PostEntityTakeDamage_" .. sid64) + + net.Start("TTT_Taskmaster_KillRetaliate_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_KillRetaliate_Assigned", function() + hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillRetaliate_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64(), function(ent, cli, text, col, secondaryText) + if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end + + local state = ent.Task_KillRetaliateAttacked + + if state == TASK_KILLRETALIATE_THEYATTACKED then + return "THEY ATTACKED FIRST", ROLE_COLORS_RADAR[ROLE_INNOCENT] + elseif state == TASK_KILLRETALIATE_YOUATTACKED then + return "YOU ATTACKED FIRST", ROLE_COLORS_RADAR[ROLE_TRAITOR] + else + return "HASN'T ATTACKED", ROLE_COLORS_RADAR[ROLE_TRAITOR] + end + end) + end) + + net.Receive("TTT_Taskmaster_KillRetaliate_Cleanup", function() + hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillRetaliate_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From 04e0130ccac20254aa123781e9128abef2ca4fb4 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 10:34:04 +0100 Subject: [PATCH 079/140] Added 'Kill a Player in One Hit' task --- .../roles/taskmaster/tasks/killonehit.lua | 95 +++++++++++++++++++ .../roles/taskmaster/tasks/killretaliate.lua | 6 +- 2 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua new file mode 100644 index 000000000..1d20a0249 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua @@ -0,0 +1,95 @@ +local hook = hook +local player = player + +local PlayerIterator = player.Iterator + +local TASK = {} + +TASK.id = "killonehit" +TASK.isKillTask = true + +TASK.Name = function(ply) + return "Kill a Player in One Hit" +end + +TASK.Description = function(ply) + return "Kill another player with a single attack" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_KillOneHit_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillOneHit_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERTEXT + } + + TASK.OnTaskAssigned = function(ply) + local sid64 = ply:SteamID64() + + hook.Add("PlayerDeath", "Taskmaster_KillOneHit_PlayerDeath_" .. sid64, function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if not victim.Task_KillOneHitAttacked or victim.Task_KillOneHitAttacked + 0.1 > CurTime() then -- Allow for a very small delay to account for things such as shotgun pellets + ply:CompleteTask(TASK.id) + end + end) + + hook.Add("PostEntityTakeDamage", "Taskmaster_KillOneHit_PostEntityTakeDamage_" .. sid64, function(entity, dmginfo, wasDamageTaken) + if not wasDamageTaken then return end + if not IsPlayer(entity) then return end + + local attacker = dmginfo:GetAttacker() + if not IsPlayer(attacker) or attacker ~= ply then return end + + if not entity.Task_KillOneHitAttacked then + entity:SetProperty("Task_KillOneHitAttacked", CurTime(), ply) + end + end) + + net.Start("TTT_Taskmaster_KillOneHit_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + for _, p in PlayerIterator() do + if ply == p then continue end + p:ClearProperty("Task_KillOneHitAttacked", ply) + end + + local sid64 = ply:SteamID64() + + hook.Remove("PlayerDeath", "Taskmaster_KillOneHit_PlayerDeath_" .. sid64) + hook.Remove("PostEntityTakeDamage", "Taskmaster_KillOneHit_PostEntityTakeDamage_" .. sid64) + + net.Start("TTT_Taskmaster_KillOneHit_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_KillOneHit_Assigned", function() + hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillOneHit_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64(), function(ent, cli, text, col, secondaryText) + if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end + + if ent.Task_KillOneHitAttacked then + return "DAMAGED", ROLE_COLORS_RADAR[ROLE_TRAITOR] + else + return "UNHARMED", ROLE_COLORS_RADAR[ROLE_INNOCENT] + end + end) + end) + + net.Receive("TTT_Taskmaster_KillOneHit_Cleanup", function() + hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillOneHit_TTTTargetIDPlayerText_" .. LocalPlayer():SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua index 40f1c7f27..a1a12ff89 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua @@ -94,11 +94,11 @@ if CLIENT then local state = ent.Task_KillRetaliateAttacked if state == TASK_KILLRETALIATE_THEYATTACKED then - return "THEY ATTACKED FIRST", ROLE_COLORS_RADAR[ROLE_INNOCENT] + return "AGGRESSIVE", ROLE_COLORS_RADAR[ROLE_INNOCENT] elseif state == TASK_KILLRETALIATE_YOUATTACKED then - return "YOU ATTACKED FIRST", ROLE_COLORS_RADAR[ROLE_TRAITOR] + return "DEFENSIVE", ROLE_COLORS_RADAR[ROLE_TRAITOR] else - return "HASN'T ATTACKED", ROLE_COLORS_RADAR[ROLE_TRAITOR] + return "PASSIVE", ROLE_COLORS_RADAR[ROLE_TRAITOR] end end) end) From 5b228652749b8d81fdbacccdb45939fb152d5d87 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 10:54:05 +0100 Subject: [PATCH 080/140] Added 'Get a Double Kill' task --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/killdouble.lua | 110 ++++++++++++++++++ .../roles/taskmaster/tasks/killonehit.lua | 2 +- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 2102738d2..5a0f148a2 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -21,6 +21,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") LANG.AddToLanguage("english", "taskmaster_holstered", "STAY HOLSTERED - {time}") + LANG.AddToLanguage("english", "taskmaster_killdouble", "KILL ANOTHER - {time}") LANG.AddToLanguage("english", "taskmaster_lookatplayer", "KEEP LOOKING - {time}") LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") LANG.AddToLanguage("english", "taskmaster_stayaway", "STAY AWAY - {time}") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua new file mode 100644 index 000000000..8fa6bb181 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua @@ -0,0 +1,110 @@ +local CurTime = CurTime +local hook = hook +local math = math +local net = net +local table = table +local util = util + +local MathMax = math.max + +local TASK = {} + +TASK.id = "killdouble" +TASK.isKillTask = true + +local taskmaster_killdouble_time = CreateConVar("ttt_taskmaster_killdouble_time", "5", FCVAR_REPLICATED, "The time (in seconds) the taskmaster has between kills to complete the 'Get a Double Kill' task", 0, 30) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_stayneartarget_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + return "Get a Double Kill" +end + +TASK.Description = function(ply) + local description = "Kill two players within " + + local time = taskmaster_killdouble_time:GetInt() + description = description .. time .. " second" + if time ~= 1 then + description = description .. "s" + end + return description .. " of each other" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_KillDouble_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillDouble_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + TASK.OnTaskAssigned = function(ply) + hook.Add("PlayerDeath", "Taskmaster_KillPistol_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if not ply.Task_KillDoubleLastKill or CurTime() > ply.Task_KillDoubleLastKill + taskmaster_killdouble_time:GetInt() then + ply:SetProperty("Task_KillDoubleLastKill", CurTime(), ply) + else + ply:CompleteTask(TASK.id) + end + end) + + net.Start("TTT_Taskmaster_KillDouble_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + ply:ClearProperty("Task_KillDoubleLastKill", ply) + + hook.Remove("PlayerDeath", "Taskmaster_KillPistol_PlayerDeath_" .. ply:SteamID64()) + + net.Start("TTT_Taskmaster_KillDouble_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_KillDouble_Assigned", function() + local client = LocalPlayer() + local time = taskmaster_killdouble_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_KillDouble_HUDPaint_" .. client:SteamID64(), function() + if not client:IsActiveTaskmaster() then return end + + local startTime = client.Task_KillDoubleLastKill + if not startTime or CurTime() > startTime + time then return end + + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + local message = PT("taskmaster_killdouble", { time = util.SimpleTime(remaining, "%02i:%02i") }) + local color = Color(25, 200, 25, 155) + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + local progress = elapsed / time + + CRHUD:PaintProgressBar(x, y, w, color, message, 1 - progress) + end) + end) + + net.Receive("TTT_Taskmaster_KillDouble_Cleanup", function() + hook.Remove("HUDPaint", "Taskmaster_KillDouble_HUDPaint_" .. LocalPlayer():SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua index 1d20a0249..e8c40ab81 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua @@ -35,7 +35,7 @@ if SERVER then if not IsPlayer(victim) then return end if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end - if not victim.Task_KillOneHitAttacked or victim.Task_KillOneHitAttacked + 0.1 > CurTime() then -- Allow for a very small delay to account for things such as shotgun pellets + if not victim.Task_KillOneHitAttacked or CurTime() < victim.Task_KillOneHitAttacked + 0.1 then -- Allow for a very small delay to account for things such as shotgun pellets ply:CompleteTask(TASK.id) end end) From 8b980be9a0dfd83d06e00e6ac3b06c8796458bdd Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 11:55:53 +0100 Subject: [PATCH 081/140] Added 'Kill a Player After a 360' task and fixed progress bar colour inconsistency --- .../gamemode/roles/shadow/cl_shadow.lua | 2 +- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/carrycorpse.lua | 2 +- .../roles/taskmaster/tasks/crouch.lua | 2 +- .../roles/taskmaster/tasks/crouchnearbody.lua | 2 +- .../roles/taskmaster/tasks/healthunder.lua | 2 +- .../roles/taskmaster/tasks/holstered.lua | 2 +- .../roles/taskmaster/tasks/kill360.lua | 174 ++++++++++++++++++ .../roles/taskmaster/tasks/killdouble.lua | 5 +- .../roles/taskmaster/tasks/lookatplayer.lua | 2 +- .../roles/taskmaster/tasks/standonplayer.lua | 2 +- .../roles/taskmaster/tasks/stayaway.lua | 2 +- .../roles/taskmaster/tasks/stayhidden.lua | 2 +- .../roles/taskmaster/tasks/stayhigher.lua | 2 +- .../roles/taskmaster/tasks/stayinarea.lua | 2 +- .../roles/taskmaster/tasks/staylower.lua | 2 +- .../roles/taskmaster/tasks/stayneartarget.lua | 2 +- .../roles/taskmaster/tasks/takedamage.lua | 2 +- 18 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua diff --git a/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua b/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua index ac7d4401e..697736341 100644 --- a/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua +++ b/gamemodes/terrortown/gamemode/roles/shadow/cl_shadow.lua @@ -492,7 +492,7 @@ AddHook("HUDPaint", "Shadow_HUDPaint", function() message = PT("shadow_buff_progress", { time = util.SimpleTime(remaining, "%02i:%02i") }) total = shadow_target_buff_delay:GetInt() - color = Color(25, 200, 25, 155) + color = Color(0, 255, 0, 155) end -- If we didn't get everything set, forget it diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 5a0f148a2..e877fb4ef 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -21,6 +21,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") LANG.AddToLanguage("english", "taskmaster_holstered", "STAY HOLSTERED - {time}") + LANG.AddToLanguage("english", "taskmaster_kill360", "KILL SOMEONE - {time}") LANG.AddToLanguage("english", "taskmaster_killdouble", "KILL ANOTHER - {time}") LANG.AddToLanguage("english", "taskmaster_lookatplayer", "KEEP LOOKING - {time}") LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua index a5a8ec5cd..9f15f7020 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -102,7 +102,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_carrycorpse", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 121f26b2b..de4a1fbdf 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -99,7 +99,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_crouch", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index 6c20ddf9d..a878e9f61 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -145,7 +145,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_crouchnearbody", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 56c9f92e1..8375da54c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -103,7 +103,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_healthunder", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index 446d90f1b..412672efe 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -105,7 +105,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_holstered", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua new file mode 100644 index 000000000..4d474f62b --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua @@ -0,0 +1,174 @@ +local hook = hook +local math = math + +local MathAbs = math.abs +local MathMax = math.max + +local TASK = {} + +TASK.id = "kill360" +TASK.isKillTask = true + +local taskmaster_kill360_time = CreateConVar("ttt_taskmaster_kill360_time", "3", FCVAR_REPLICATED, "The time (in seconds) a player has after completing a 360 to kill a player for the 'Kill a Player After a 360' task", 1, 10) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_kill360_time", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) + +TASK.Name = function(ply) + return "Kill a Player After a 360" +end + +TASK.Description = function(ply) + local description = "Kill another player within " + local time = taskmaster_kill360_time:GetInt() + description = description .. time .. " second" + if time ~= 1 then + description = description .. "s" + end + return description .. " of turning 360 degrees" +end + +if SERVER then + util.AddNetworkString("TTT_Taskmaster_Kill360_Assigned") + util.AddNetworkString("TTT_Taskmaster_Kill360_Cleanup") + + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_PROGRESSBAR + } + + TASK.OnTaskAssigned = function(ply) + local sid64 = ply:SteamID64() + + ply.Task_Kill360LastAngle = ply:GetAimVector():Angle().y + ply.Task_Kill360RotationDecay = 0 + ply.Task_Kill360RotationClockwise = 0 + ply.Task_Kill360RotationCounterClockwise = 0 + ply.Task_Kill360RotationDirection = 1 + + hook.Add("PlayerDeath", "Taskmaster_Kill360_PlayerDeath_" .. sid64, function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if ply.Task_Kill360Start and CurTime() < ply.Task_Kill360Start + taskmaster_kill360_time:GetInt() then + ply:CompleteTask(TASK.id) + end + end) + + hook.Add("Think", "Taskmaster_Kill360_Think_" ..sid64, function() + local angle = ply:GetAimVector():Angle().y + local angleDiff = angle - ply.Task_Kill360LastAngle + while angleDiff > 180 do + angleDiff = angleDiff - 360 + end + while angleDiff < -180 do + angleDiff = angleDiff + 360 + end + ply.Task_Kill360LastAngle = angle + + if ply.Task_Kill360Start and CurTime() < ply.Task_Kill360Start + taskmaster_kill360_time:GetInt() then return end + + -- If the player is turning fast enough quickly decrease the decay value, otherwise slowly increase it + if MathAbs(angleDiff) > 5 then + ply.Task_Kill360RotationDecay = MathMax(ply.Task_Kill360RotationDecay - 0.3, 0); + else + ply.Task_Kill360RotationDecay = MathMax(ply.Task_Kill360RotationDecay + 0.015, 1.5); + end + ply.Task_Kill360RotationClockwise = MathMax(ply.Task_Kill360RotationClockwise + angleDiff - ply.Task_Kill360RotationDecay, 0) + ply.Task_Kill360RotationCounterClockwise = MathMax(ply.Task_Kill360RotationCounterClockwise - angleDiff - ply.Task_Kill360RotationDecay, 0) + + -- Smoothly transition if the player changes rotation direction + if ply.Task_Kill360RotationClockwise > ply.Task_Kill360RotationCounterClockwise and ply.Task_Kill360RotationDirection == -1 then + ply.Task_Kill360RotationDirection = 1 + ply.Task_Kill360RotationCounterClockwise = 0 + elseif ply.Task_Kill360RotationCounterClockwise > ply.Task_Kill360RotationClockwise and ply.Task_Kill360RotationDirection == 1 then + ply.Task_Kill360RotationDirection = -1 + ply.Task_Kill360RotationClockwise = 0 + end + + if ply.Task_Kill360RotationDirection == 1 then + ply:SetProperty("Task_Kill360Progress", ply.Task_Kill360RotationClockwise, ply) + else + ply:SetProperty("Task_Kill360Progress", ply.Task_Kill360RotationCounterClockwise, ply) + end + + -- Give the player a little bit of leeway so they don't need to reach exactly 360 degrees + if ply.Task_Kill360RotationClockwise > 320 or ply.Task_Kill360RotationCounterClockwise > 320 then + ply.Task_Kill360RotationClockwise = 0 + ply.Task_Kill360RotationCounterClockwise = 0 + ply:SetProperty("Task_Kill360Start", CurTime(), ply) + end + end) + + net.Start("TTT_Taskmaster_Kill360_Assigned") + net.Send(ply) + end + + TASK.OnTaskRemoved = function(ply) + ply:ClearProperty("Task_Kill360Start", ply) + ply:ClearProperty("Task_Kill360Progress", ply) + ply.Task_Kill360LastAngle = nil + ply.Task_Kill360RotationDecay = nil + ply.Task_Kill360RotationClockwise = nil + ply.Task_Kill360RotationCounterClockwise = nil + ply.Task_Kill360RotationDirection = nil + + local sid64 = ply:SteamID64() + + hook.Remove("PlayerDeath", "Taskmaster_Kill360_PlayerDeath_" .. sid64) + hook.Remove("Think", "Taskmaster_Kill360_Think_" .. sid64) + + net.Start("TTT_Taskmaster_Kill360_Cleanup") + net.Send(ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +if CLIENT then + net.Receive("TTT_Taskmaster_Kill360_Assigned", function() + local client = LocalPlayer() + local time = taskmaster_kill360_time:GetInt() + + hook.Add("HUDPaint", "Taskmaster_Kill360_HUDPaint_" .. client:SteamID64(), function() + if not client:IsActiveTaskmaster() then return end + + local progress + local message + local color + + local startTime = client.Task_Kill360Start + if not startTime or CurTime() > startTime + time then + progress = client.Task_Kill360Progress / 320 + message = "DO A 360" + color = Color(255, 0, 0, 155) + else + local PT = LANG.GetParamTranslation + local elapsed = MathMax(0, CurTime() - startTime) + local remaining = time - elapsed + progress = 1 - (elapsed / time) + message = PT("taskmaster_kill360", { time = util.SimpleTime(remaining, "%02i:%02i") }) + color = Color(0, 255, 0, 155) + end + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + y = y + (y / 3) + + local w = 300 + + CRHUD:PaintProgressBar(x, y, w, color, message, progress) + end) + end) + + net.Receive("TTT_Taskmaster_Kill360_Cleanup", function() + hook.Remove("HUDPaint", "Taskmaster_Kill360_HUDPaint_" .. LocalPlayer():SteamID64()) + end) +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua index 8fa6bb181..27bf51538 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua @@ -14,7 +14,7 @@ TASK.isKillTask = true local taskmaster_killdouble_time = CreateConVar("ttt_taskmaster_killdouble_time", "5", FCVAR_REPLICATED, "The time (in seconds) the taskmaster has between kills to complete the 'Get a Double Kill' task", 0, 30) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { - cvar = "ttt_taskmaster_stayneartarget_time", + cvar = "ttt_taskmaster_killdouble_time", type = ROLE_CONVAR_TYPE_NUM, decimal = 0 }) @@ -25,7 +25,6 @@ end TASK.Description = function(ply) local description = "Kill two players within " - local time = taskmaster_killdouble_time:GetInt() description = description .. time .. " second" if time ~= 1 then @@ -89,7 +88,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_killdouble", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index 0c4d9b711..d04b04edf 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -152,7 +152,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_lookatplayer", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index 9c2b19cce..79a9c4595 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -100,7 +100,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_standonplayer", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua index 055b9a832..855825820 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua @@ -133,7 +133,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_stayaway", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua index b3c9ae4ee..e8e141d8e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua @@ -115,7 +115,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_stayhidden", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index c3c02966f..76b1d0c6b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -124,7 +124,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_stayhigher", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index e53fb1b18..3d51939ef 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -166,7 +166,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_stayinarea", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index febcd63dc..c55673fdc 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -124,7 +124,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_staylower", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 493959139..6e86ae50d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -246,7 +246,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_stayneartarget", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua index c48785d98..c95d59785 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -94,7 +94,7 @@ if CLIENT then local elapsed = MathMax(0, CurTime() - startTime) local remaining = time - elapsed local message = PT("taskmaster_takedamage", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(25, 200, 25, 155) + local color = Color(0, 255, 0, 155) local x = ScrW() / 2.0 local y = ScrH() / 2.0 From 4a3a7492673beadf877dc09e1f2c88d39f00e809 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 13:13:01 +0100 Subject: [PATCH 082/140] Added 'Kill a Player With a Specific Weapon' task (almost) --- .../roles/taskmaster/tasks/killweapon.lua | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua new file mode 100644 index 000000000..9c6af7650 --- /dev/null +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua @@ -0,0 +1,106 @@ +local hook = hook +local table = table +local math = math + +local TableInsert = table.insert +local MathRandom = math.random + +local TryTranslation = LANG.TryTranslation + +local TASK = {} + +TASK.id = "killweapon" +TASK.isKillTask = true + +TASK.Name = function(ply) + local weapon = "Specific Weapon" + if ply.Task_KillWeaponClass then + local wep = util.WeaponForClass(wep) + if IsValid(wep) then + weapon = TryTranslation(wep.GetPrintName and wep:GetPrintName() or wep.PrintName) + end + end + return "Kill a Player With a " .. weapon +end + +TASK.Description = function(ply) + local weapon = "specific weapon" + if ply.Task_KillWeaponClass then + local wep = util.WeaponForClass(wep) + if IsValid(wep) then + weapon = TryTranslation(wep.GetPrintName and wep:GetPrintName() or wep.PrintName) + end + end + return "Kill another player using a " .. weapon +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + local weps = {} + for _, wep in ipairs(weapons.GetList()) do + if wep.AutoSpawnable and wep.Kind == WEAPON_HEAVY and wep.Primary and wep.Primary.ClipMax and wep.Primary.Ammo and wep.Primary.Damage then + TableInsert(weps, wep) + end + end + ply:SetProperty("Task_KillWeaponClass", weps[MathRandom(#weps)].ClassName, ply) + + local equipped = false + if ply:GetActiveWeapon().Kind == WEAPON_HEAVY then + equipped = true + end + + for _, wep in pairs(ply:GetWeapons()) do + if wep.Kind == WEAPON_HEAVY then + ply:StripWeapon(wep:GetClass()) + end + end + + local weapon = ply:Give(ply.Task_KillWeaponClass) + local ammoMax = weapon.Primary.ClipMax + local ammoType = weapon.Primary.Ammo + weapon.AllowDrop = false + + if equipped then + ply:SetFOV(0, 0.2) + ply:SelectWeapon(ply.Task_KillWeaponClass) + end + + local sid64 = ply:SteamID64() + + hook.Add("PlayerDeath", "Taskmaster_KillWeapon_PlayerDeath_" .. sid64, function(victim, inflictor, attacker) + if not IsPlayer(victim) then return end + if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + + if IsValid(inflictor) and inflictor:IsWeapon() and inflictor:GetClass() == ply.Task_KillWeaponClass then + ply:CompleteTask(TASK.id) + end + end) + + hook.Add("Think", "Taskmaster_KillWeapon_Think_" ..sid64, function() + ply:SetAmmo(ammoMax, ammoType) + end) + end + + TASK.OnTaskRemoved = function(ply) + hook.Remove("PlayerDeath", "Taskmaster_KillWeapon_PlayerDeath_" .. ply:SteamID64()) + hook.Remove("Think", "Taskmaster_KillWeapon_Think_" .. ply:SteamID64()) + + for _, wep in pairs(ply:GetWeapons()) do + if wep:GetClass() == ply.Task_KillWeaponClass then + wep.AllowDrop = true + end + end + + ply:ClearProperty("Task_KillWeaponClass", ply) + end + + TASK.OnTaskComplete = TASK.OnTaskRemoved +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file From d2b04323bdb5e0eb4f0e4db6ab3679ab17582622 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 09:14:16 -0400 Subject: [PATCH 083/140] Updated weapon switch and pickup HUD elements to use the same logic for getting weapon names --- RELEASE.md | 4 ++++ gamemodes/terrortown/gamemode/cl_hudpickup.lua | 2 +- gamemodes/terrortown/gamemode/cl_wepswitch.lua | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index e7af609e7..e34c919f8 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,6 +3,10 @@ ## 2.3.5 (Beta) **Released:** +### Changes +- Updated weapon switch and pickup HUD elements to use the same logic for getting weapon names + - Fixes very rare cases where they would say different things + ### Additions - Added ability to control whether the mercenary gets body armor as part of their loadout (defaults to `true`) - Added ability to change vampire's fang drain overheal to overheal (default), increase max health, or do both diff --git a/gamemodes/terrortown/gamemode/cl_hudpickup.lua b/gamemodes/terrortown/gamemode/cl_hudpickup.lua index e97721133..bb81cfb9a 100644 --- a/gamemodes/terrortown/gamemode/cl_hudpickup.lua +++ b/gamemodes/terrortown/gamemode/cl_hudpickup.lua @@ -38,7 +38,7 @@ end function GM:HUDWeaponPickedUp(wep) if not (IsValid(wep) and IsValid(LocalPlayer())) or (not LocalPlayer():Alive()) then return end - local name = TryTranslation(wep.GetPrintName and wep:GetPrintName() or wep:GetClass() or "Unknown Weapon Name") + local name = TryTranslation(wep.GetPrintName and wep:GetPrintName() or wep.PrintName or wep:GetClass() or "Unknown Weapon Name") local pickup = {} pickup.time = CurTime() diff --git a/gamemodes/terrortown/gamemode/cl_wepswitch.lua b/gamemodes/terrortown/gamemode/cl_wepswitch.lua index 58440e592..6224fb42f 100644 --- a/gamemodes/terrortown/gamemode/cl_wepswitch.lua +++ b/gamemodes/terrortown/gamemode/cl_wepswitch.lua @@ -118,7 +118,7 @@ local TryTranslation = LANG.TryTranslation function WSWITCH:DrawWeapon(x, y, c, wep) if not IsValid(wep) then return false end - local name = TryTranslation(wep:GetPrintName() or wep.PrintName or "...") + local name = TryTranslation(wep.GetPrintName and wep:GetPrintName() or wep.PrintName or wep:GetClass() or "...") local cl1 = wep.Clip1 and IsValid(wep:GetOwner()) and wep:Clip1() or -1 local am1 = wep.Ammo1 and IsValid(wep:GetOwner()) and wep:Ammo1() or false local ammo = false From 40c5b16d246754c05f63f3bb71884286019dd908 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Sun, 17 Aug 2025 14:51:28 +0100 Subject: [PATCH 084/140] Misc changes --- .../roles/taskmaster/cl_taskmaster.lua | 1 + .../roles/taskmaster/tasks/calldetective.lua | 2 +- .../roles/taskmaster/tasks/carrycorpse.lua | 18 +- .../gamemode/roles/taskmaster/tasks/chat.lua | 18 +- .../roles/taskmaster/tasks/crouch.lua | 16 +- .../roles/taskmaster/tasks/crouchnearbody.lua | 19 ++- .../roles/taskmaster/tasks/crowbar.lua | 22 ++- .../roles/taskmaster/tasks/damageeveryone.lua | 24 ++- .../roles/taskmaster/tasks/dodamage.lua | 20 ++- .../roles/taskmaster/tasks/firebullets.lua | 18 +- .../taskmaster/tasks/getplayerkilled.lua | 50 ++++-- .../taskmaster/tasks/getplayertokill.lua | 52 +++--- .../roles/taskmaster/tasks/healthunder.lua | 17 +- .../roles/taskmaster/tasks/holstered.lua | 14 +- .../gamemode/roles/taskmaster/tasks/jump.lua | 20 ++- .../{killfaraway.lua => killdistant.lua} | 38 ++--- .../roles/taskmaster/tasks/killretaliate.lua | 4 +- .../roles/taskmaster/tasks/killtarget.lua | 47 +++--- .../roles/taskmaster/tasks/killweapon.lua | 16 +- .../roles/taskmaster/tasks/lookatplayer.lua | 59 +++++-- .../roles/taskmaster/tasks/pickupshopitem.lua | 4 +- .../roles/taskmaster/tasks/standonplayer.lua | 18 +- .../roles/taskmaster/tasks/stayaway.lua | 155 ------------------ .../roles/taskmaster/tasks/stayhidden.lua | 19 ++- .../roles/taskmaster/tasks/stayhigher.lua | 16 +- .../roles/taskmaster/tasks/stayinarea.lua | 16 +- .../roles/taskmaster/tasks/staylower.lua | 16 +- .../roles/taskmaster/tasks/stayneartarget.lua | 59 +++++-- .../roles/taskmaster/tasks/takedamage.lua | 20 ++- .../roles/taskmaster/tasks/weaponpickups.lua | 18 +- 30 files changed, 484 insertions(+), 332 deletions(-) rename gamemodes/terrortown/gamemode/roles/taskmaster/tasks/{killfaraway.lua => killdistant.lua} (78%) delete mode 100644 gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index e877fb4ef..b5ed18a9a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -16,6 +16,7 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_credit_bonus", "{role}, you have been awarded {num} credit(s) for completing a task.") -- Tasks + -- TODO: Create init method for tasks and move these into task files LANG.AddToLanguage("english", "taskmaster_carrycorpse", "CARRY CORPSE - {time}") LANG.AddToLanguage("english", "taskmaster_crouchnearbody", "CROUCH NEAR BODY - {time}") LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/calldetective.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/calldetective.lua index 40922233e..18dc36f31 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/calldetective.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/calldetective.lua @@ -3,7 +3,7 @@ local TASK = {} TASK.id = "calldetective" TASK.Name = function(ply) - return "Call Detective to Body" + return "Call a Detective to a Body" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua index 9f15f7020..39184cbc2 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -7,6 +7,7 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local TASK = {} @@ -21,16 +22,27 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_carrycorpse_time:GetInt() - local name = "Carry Corpse for " .. time .. " Second" + local name = "Carry a Corpse for " .. time .. " Second" if time ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_CarryCorpseStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) local time = taskmaster_carrycorpse_time:GetInt() - local desc = "Carry a player corpse for " .. time .. " second" + local desc = "Carry a dead player's corpse for " .. time .. " second" if time ~= 1 then desc = desc .. "s consecutively" end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua index 5385f4e6b..1526fac38 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua @@ -15,7 +15,15 @@ TASK.Name = function(ply) if times ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = times + else + progress = ply.Task_ChatCount + end + + return name .. " (" .. progress .. "/" .. times .. ")" end TASK.Description = function(ply) @@ -36,14 +44,14 @@ if SERVER then TASK.OnTaskAssigned = function(ply) local times = taskmaster_chat_times:GetInt() - local count = 0 + ply:SetProperty("Task_ChatCount", 0, ply) hook.Add("PlayerSay", "Taskmaster_Chat_PlayerSay_" .. ply:SteamID64(), function(sender, text, teamChat) if not IsPlayer(sender) then return end if sender ~= ply then return end if not ply:Alive() or ply:IsSpec() then return end - count = count + 1 - if count >= times then + ply:SetProperty("Task_ChatCount", ply.Task_ChatCount + 1, ply) + if ply.Task_ChatCount >= times then ply:CompleteTask(TASK.id) end end) @@ -51,6 +59,8 @@ if SERVER then TASK.OnTaskRemoved = function(ply) hook.Remove("PlayerSay", "Taskmaster_Chat_PlayerSay_" .. ply:SteamID64()) + + ply:ClearProperty("Task_ChatCount", ply) end TASK.OnTaskComplete = TASK.OnTaskRemoved diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index de4a1fbdf..85cc4f517 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -7,12 +7,13 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local TASK = {} TASK.id = "crouch" -local taskmaster_crouch_time = CreateConVar("ttt_taskmaster_crouch_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay crouched to complete the 'Crouch Near Body' task", 1, 240) +local taskmaster_crouch_time = CreateConVar("ttt_taskmaster_crouch_time", "40", FCVAR_REPLICATED, "The time (in seconds) a player must stay crouched to complete the 'Crouch Near Body' task", 1, 240) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_crouch_time", type = ROLE_CONVAR_TYPE_NUM, @@ -25,7 +26,18 @@ TASK.Name = function(ply) if time ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_CrouchStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index a878e9f61..958a2afaf 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -10,13 +10,14 @@ local util = util local EntsFindByClass = ents.FindByClass local MathMax = math.max +local MathFloor = math.floor local TASK = {} TASK.id = "crouchnearbody" local taskmaster_crouchnearbody_range = CreateConVar("ttt_taskmaster_crouchnearbody_range", "1", FCVAR_REPLICATED, "The distance (in meters) away a player must stay within to count for the 'Crouch Near Body' task", 1, 100) -local taskmaster_crouchnearbody_time = CreateConVar("ttt_taskmaster_crouchnearbody_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay near a body to count for the 'Crouch Near Body' task", 1, 240) +local taskmaster_crouchnearbody_time = CreateConVar("ttt_taskmaster_crouchnearbody_time", "20", FCVAR_REPLICATED, "The time (in seconds) a player must stay near a body to count for the 'Crouch Near Body' task", 1, 240) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_crouchnearbody_range", type = ROLE_CONVAR_TYPE_NUM, @@ -29,7 +30,19 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - return "Crouch Near Body" + local time = taskmaster_crouchnearbody_time:GetInt() + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_CrouchNearBodyStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return "Crouch Near a Body (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) @@ -62,7 +75,7 @@ TASK.Description = function(ply) end end local time = taskmaster_crouchnearbody_time:GetInt() - description = description .. " of a body for " .. time .. " second" + description = description .. " of a dead body for " .. time .. " second" if time == 1 then return description end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua index 6e9dce28e..b6e3b096c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua @@ -13,16 +13,24 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local times = taskmaster_crowbar_times:GetInt() - local name = "Swing Crowbar " .. times .. " Time" + local name = "Swing a Crowbar " .. times .. " Time" if times ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = times + else + progress = ply.Task_CrowbarCount + end + + return name .. " (" .. progress .. "/" .. times .. ")" end TASK.Description = function(ply) local times = taskmaster_crowbar_times:GetInt() - local desc = "Use the crowbar weapon " .. times .. " time" + local desc = "Attack with a crowbar " .. times .. " time" if times ~= 1 then desc = desc .. "s" end @@ -40,12 +48,12 @@ if SERVER then if wep.Task_CrowbarPrimaryAttack then return end local times = taskmaster_crowbar_times:GetInt() - local total = 0 + ply:SetProperty("Task_CrowbarCount", 0, ply) wep.Task_CrowbarPrimaryAttack = wep.PrimaryAttack wep.PrimaryAttack = function(this, worldsnd) wep.Task_CrowbarPrimaryAttack(wep, worldsnd) - total = total + 1 - if total >= times then + ply:SetProperty("Task_CrowbarCount", ply.Task_CrowbarCount + 1, ply) + if ply.Task_CrowbarCount >= times then ply:CompleteTask(TASK.id) end end @@ -83,6 +91,8 @@ if SERVER then local sid64 = ply:SteamID64() hook.Remove("WeaponEquip", "Taskmaster_Crowbar_WeaponEquip_" .. sid64) hook.Remove("PlayerDroppedWeapon", "Taskmaster_Crowbar_PlayerDroppedWeapon_" .. sid64) + + ply:ClearProperty("Task_CrowbarCount", ply) end TASK.OnTaskComplete = TASK.OnTaskRemoved diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua index 21cf87801..21b58f946 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua @@ -14,11 +14,27 @@ local TASK = {} TASK.id = "damageeveryone" TASK.Name = function(ply) - return "Damage All Players" + local damaged = ply.Task_DamageEveryoneDamaged or {} + local total = 0 + local progress = 0 + for _, p in PlayerIterator() do + if ply == p then continue end + if not p:Alive() or p:IsSpec() then continue end + total = total + 1 + if TableHasValue(damaged, p:SteamID64()) then + progress = progress + 1 + end + end + + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = total + end + + return "Damage Everyone Else (" .. progress .. "/" .. total .. ")" end TASK.Description = function(ply) - return "Damage all other living players" + return "Deal damage to all other living players" end if SERVER then @@ -86,8 +102,8 @@ if CLIENT then local client = LocalPlayer() hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_DamageEveryone_TTTTargetIDPlayerTargetIcon_" .. client:SteamID64(), function(ply, cli, showJester) if cli:IsActiveTaskmaster() and not TableHasValue(cli.Task_DamageEveryoneDamaged, ply:SteamID64()) then - local iconColor = ROLE_COLORS_SPRITE[ROLE_TRAITOR] - return "task", true, iconColor, "up" + local iconColor = ROLE_COLORS_SPRITE[ROLE_TASKMASTER] + return "task", true, iconColor, "down" end end) end) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua index 52006f54b..fdda1a945 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua @@ -11,12 +11,20 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local amount = taskmaster_dodamage_amount:GetInt() - return "Do " .. amount .. " Damage" + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = amount + else + progress = ply.Task_DoDamageTotal + end + + return "Deal " .. amount .. " Damage (" .. progress .. "/" .. amount .. ")" end TASK.Description = function(ply) local amount = taskmaster_dodamage_amount:GetInt() - return "Do " .. amount .. " damage to props and other players" + return "Deal " .. amount .. " damage to props and other players" end if SERVER then @@ -28,7 +36,7 @@ if SERVER then TASK.OnTaskAssigned = function(ply) local amount = taskmaster_dodamage_amount:GetInt() - local total = 0 + ply:SetProperty("Task_DoDamageTotal", 0, ply) hook.Add("PostEntityTakeDamage", "Taskmaster_DoDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken) if not wasDamageTaken then return end if entity == ply then return end @@ -36,8 +44,8 @@ if SERVER then local attacker = dmginfo:GetAttacker() if not IsPlayer(attacker) or attacker ~= ply then return end - total = total + dmginfo:GetDamage() - if total >= amount then + ply:SetProperty("Task_DoDamageTotal", ply.Task_DoDamageTotal + dmginfo:GetDamage(), ply) + if ply.Task_DoDamageTotal >= amount then ply:CompleteTask(TASK.id) end end) @@ -45,6 +53,8 @@ if SERVER then TASK.OnTaskRemoved = function(ply) hook.Remove("PostEntityTakeDamage", "Taskmaster_DoDamage_PostEntityTakeDamage_" .. ply:SteamID64()) + + ply:ClearProperty("Task_DoDamageTotal", ply) end TASK.OnTaskComplete = TASK.OnTaskRemoved diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua index c699ec543..b4be58a5b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua @@ -15,7 +15,15 @@ TASK.Name = function(ply) if times ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = times + else + progress = ply.Task_FireBulletsCount + end + + return name .. " (" .. progress .. "/" .. times .. ")" end TASK.Description = function(ply) @@ -36,12 +44,12 @@ if SERVER then TASK.OnTaskAssigned = function(ply) local times = taskmaster_firebullets_times:GetInt() - local count = 0 + ply:SetProperty("Task_FireBulletsCount", 0, ply) hook.Add("PostEntityFireBullets", "Taskmaster_FireBullets_PostEntityFireBullets_" .. ply:SteamID64(), function(entity, data) if not IsPlayer(entity) then return end if entity ~= ply then return end - count = count + 1 - if count >= times then + ply:SetProperty("Task_FireBulletsCount", ply.Task_FireBulletsCount + 1, ply) + if ply.Task_FireBulletsCount >= times then ply:CompleteTask(TASK.id) end end) @@ -49,6 +57,8 @@ if SERVER then TASK.OnTaskRemoved = function(ply) hook.Remove("PostEntityFireBullets", "Taskmaster_FireBullets_PostEntityFireBullets_" .. ply:SteamID64()) + + ply:ClearProperty("Task_FireBulletsCount", ply) end TASK.OnTaskComplete = TASK.OnTaskRemoved diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua index 5fdbad5f3..218779c27 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua @@ -21,21 +21,13 @@ TASK.Description = function(ply) name = ply.Task_GetPlayerKilledPlayer:Nick() end - return "Get " .. name .. " killed by another player" + return "Get " .. name .. " killed by another player (not you)" end if SERVER then util.AddNetworkString("TTT_Taskmaster_GetPlayerKilled_Assigned") util.AddNetworkString("TTT_Taskmaster_GetPlayerKilled_Cleanup") - TASK.CanAssignTask = function(ply) - return true - end - - TASK.RequiredFeatures = { - TASKMASTER_TF_TARGETID_PLAYERICON - } - local function GetRandomTarget(ply) -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do @@ -46,32 +38,51 @@ if SERVER then return nil end + TASK.CanAssignTask = function(ply) + return GetRandomTarget(ply) ~= nil + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON + } + TASK.OnTaskAssigned = function(ply) ply:SetProperty("Task_GetPlayerKilledPlayer", GetRandomTarget(ply), ply) hook.Add("PlayerDeath", "Taskmaster_GetPlayerKilled_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) if ply.Task_GetPlayerKilledPlayer ~= victim then return end - if attacker == ply then return end - ply:CompleteTask(TASK.id) + if attacker == ply then + local target = GetRandomTarget(ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "You killed your target for the '" .. TASK.Name(ply) .. "' task! Your new target is " .. target:Nick()) + ply:SetProperty("Task_GetPlayerKilledPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "You killed your target for the '" .. TASK.Name(ply) .. "' task! There are no new valid targets and you must reroll.") + end + else + ply:CompleteTask(TASK.id) + end end) hook.Add("PlayerDisconnected", "Taskmaster_GetPlayerKilled_PlayerDisconnected_" .. ply:SteamID64(), function(leaver) if ply.Task_GetPlayerKilledPlayer ~= leaver then return end local target = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) - ply:SetProperty("Task_GetPlayerKilledPlayer", target, ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_GetPlayerKilledPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! There are no new valid targets and you must reroll.") + end end) net.Start("TTT_Taskmaster_GetPlayerKilled_Assigned") net.Send(ply) end - TASK.OnTaskRemoved = function(ply) + TASK.OnTaskComplete = function(ply) timer.Remove("TTTTaskmasterGetPlayerKilledTimer") - ply:ClearProperty("Task_GetPlayerKilledPlayer", ply) - hook.Remove("PlayerDeath", "Taskmaster_GetPlayerKilled_PlayerDeath_" .. ply:SteamID64()) hook.Remove("PlayerDisconnected", "Taskmaster_GetPlayerKilled_PlayerDisconnected_" .. ply:SteamID64()) @@ -79,7 +90,10 @@ if SERVER then net.Send(ply) end - TASK.OnTaskComplete = TASK.OnTaskRemoved + TASK.OnTaskRemoved = function(ply) + TASK.OnTaskComplete(ply) + ply:ClearProperty("Task_GetPlayerKilledPlayer", ply) -- Don't clear the player name if the task was completed so it still shows up in the task name/description + end end if CLIENT then @@ -87,7 +101,7 @@ if CLIENT then local client = LocalPlayer() hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_GetPlayerKilled_TTTTargetIDPlayerTargetIcon_" .. client:SteamID64(), function(ply, cli, showJester) if cli:IsActiveTaskmaster() and ply == cli.Task_GetPlayerKilledPlayer then - local iconColor = ROLE_COLORS_SPRITE[ROLE_TRAITOR] + local iconColor = ROLE_COLORS_SPRITE[ROLE_TASKMASTER] return "task", true, iconColor, "up" end end) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua index 4c7442c96..42b242322 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua @@ -21,21 +21,13 @@ TASK.Description = function(ply) name = ply.Task_GetPlayerToKillPlayer:Nick() end - return "Get " .. name .. " to kill another player" + return "Get " .. name .. " to kill another player (not you)" end if SERVER then util.AddNetworkString("TTT_Taskmaster_GetPlayerToKill_Assigned") util.AddNetworkString("TTT_Taskmaster_GetPlayerToKill_Cleanup") - TASK.CanAssignTask = function(ply) - return true - end - - TASK.RequiredFeatures = { - TASKMASTER_TF_TARGETID_PLAYERICON - } - local function GetRandomTarget(ply) -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do @@ -46,32 +38,51 @@ if SERVER then return nil end + TASK.CanAssignTask = function(ply) + return GetRandomTarget(ply) ~= nil + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON + } + TASK.OnTaskAssigned = function(ply) ply:SetProperty("Task_GetPlayerToKillPlayer", GetRandomTarget(ply), ply) hook.Add("PlayerDeath", "Taskmaster_GetPlayerToKill_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) - if ply.Task_GetPlayerToKillPlayer ~= attacker then return end - if victim == ply then return end - ply:CompleteTask(TASK.id) + if ply.Task_GetPlayerToKillPlayer == attacker and attacker ~= victim then + if victim == ply then return end + ply:CompleteTask(TASK.id) + elseif ply.Task_GetPlayerToKillPlayer == victim then + local target = GetRandomTarget(ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. target:Nick()) + ply:SetProperty("Task_GetPlayerToKillPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! There are no new valid targets and you must reroll.") + end + end end) hook.Add("PlayerDisconnected", "Taskmaster_GetPlayerToKill_PlayerDisconnected_" .. ply:SteamID64(), function(leaver) if ply.Task_GetPlayerToKillPlayer ~= leaver then return end local target = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) - ply:SetProperty("Task_GetPlayerToKillPlayer", target, ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_GetPlayerToKillPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! There are no new valid targets and you must reroll.") + end end) net.Start("TTT_Taskmaster_GetPlayerToKill_Assigned") net.Send(ply) end - TASK.OnTaskRemoved = function(ply) + TASK.OnTaskComplete = function(ply) timer.Remove("TTTTaskmasterGetPlayerToKillTimer") - ply:ClearProperty("Task_GetPlayerToKillPlayer", ply) - hook.Remove("PlayerDeath", "Taskmaster_GetPlayerToKill_PlayerDeath_" .. ply:SteamID64()) hook.Remove("PlayerDisconnected", "Taskmaster_GetPlayerToKill_PlayerDisconnected_" .. ply:SteamID64()) @@ -79,7 +90,10 @@ if SERVER then net.Send(ply) end - TASK.OnTaskComplete = TASK.OnTaskRemoved + TASK.OnTaskRemoved = function(ply) + TASK.OnTaskComplete(ply) + ply:ClearProperty("Task_GetPlayerToKillPlayer", ply) -- Don't clear the player name if the task was completed so it still shows up in the task name/description + end end if CLIENT then @@ -87,7 +101,7 @@ if CLIENT then local client = LocalPlayer() hook.Add("TTTTargetIDPlayerTargetIcon", "Taskmaster_GetPlayerToKill_TTTTargetIDPlayerTargetIcon_" .. client:SteamID64(), function(ply, cli, showJester) if cli:IsActiveTaskmaster() and ply == cli.Task_GetPlayerToKillPlayer then - local iconColor = ROLE_COLORS_SPRITE[ROLE_TRAITOR] + local iconColor = ROLE_COLORS_SPRITE[ROLE_TASKMASTER] return "task", true, iconColor, "up" end end) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 8375da54c..877fcca71 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -7,6 +7,7 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local TASK = {} @@ -27,13 +28,25 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local amount = taskmaster_healthunder_amount:GetInt() - return "Stay Under " .. amount .. " HP" + local time = taskmaster_healthunder_time:GetInt() + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_HealthUnderStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return "Survive Under " .. amount .. " Health (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) local time = taskmaster_healthunder_time:GetInt() local amount = taskmaster_healthunder_amount:GetInt() - local desc = "Stay under " .. amount .. " health for " .. time .. " second" + local desc = "Survive under " .. amount .. " health for " .. time .. " second" if time ~= 1 then desc = desc .. "s" end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index 412672efe..f09e556de 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -7,6 +7,7 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local TASK = {} @@ -25,7 +26,18 @@ TASK.Name = function(ply) if time ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_HolsteredStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua index 59a8ea293..d769f1189 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -2,7 +2,7 @@ local TASK = {} TASK.id = "jump" -local taskmaster_jump_times = CreateConVar("ttt_taskmaster_jump_times", "25", FCVAR_REPLICATED, "The jump of times a player must jump to complete the 'Jump X Times' task", 1, 100) +local taskmaster_jump_times = CreateConVar("ttt_taskmaster_jump_times", "100", FCVAR_REPLICATED, "The jump of times a player must jump to complete the 'Jump X Times' task", 1, 500) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_jump_times", type = ROLE_CONVAR_TYPE_NUM, @@ -15,7 +15,15 @@ TASK.Name = function(ply) if times ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = times + else + progress = ply.Task_CrowbarCount + end + + return name .. " (" .. progress .. "/" .. times .. ")" end TASK.Description = function(ply) @@ -36,12 +44,12 @@ if SERVER then TASK.OnTaskAssigned = function(ply) local times = taskmaster_jump_times:GetInt() - local count = 0 + ply:SetProperty("Task_JumpCount", 0, ply) hook.Add("OnPlayerJump", "Taskmaster_Jump_OnPlayerJump_" .. ply:SteamID64(), function(caller) if not IsPlayer(caller) then return end if caller ~= ply then return end - count = count + 1 - if count >= times then + ply:SetProperty("Task_JumpCount", ply.Task_JumpCount + 1, ply) + if ply.Task_JumpCount >= times then ply:CompleteTask(TASK.id) end end) @@ -49,6 +57,8 @@ if SERVER then TASK.OnTaskRemoved = function(ply) hook.Remove("OnPlayerJump", "Taskmaster_Jump_OnPlayerJump_" .. ply:SteamID64()) + + ply:ClearProperty("Task_JumpCount", ply) end TASK.OnTaskComplete = TASK.OnTaskRemoved diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua similarity index 78% rename from gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua rename to gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua index 9ce441172..a49844331 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killfaraway.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua @@ -12,18 +12,18 @@ local MathSin = math.sin local TASK = {} -TASK.id = "killfaraway" +TASK.id = "killdistant" TASK.isKillTask = true -local taskmaster_killfaraway_range = CreateConVar("ttt_taskmaster_killfaraway_range", "25", FCVAR_REPLICATED, "The minimum distance (in meters) away a player can be to count for the 'Kill a Faraway Player' task", 0, 100) +local taskmaster_killdistant_range = CreateConVar("ttt_taskmaster_killdistant_range", "25", FCVAR_REPLICATED, "The minimum distance (in meters) away a player can be to count for the 'Kill a Distant Player' task", 0, 100) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { - cvar = "ttt_taskmaster_killfaraway_range", + cvar = "ttt_taskmaster_killdistant_range", type = ROLE_CONVAR_TYPE_NUM, decimal = 0 }) TASK.Name = function(ply) - return "Kill a Faraway Player" + return "Kill a Distant Player" end TASK.Description = function(ply) @@ -33,7 +33,7 @@ TASK.Description = function(ply) else unit = cvars.Number("ttt_distance_unit", 1) end - local range = taskmaster_killfaraway_range:GetInt() + local range = taskmaster_killdistant_range:GetInt() local description = "Kill another player from outside " if unit == 1 then description = description .. range .. " meter" @@ -59,8 +59,8 @@ TASK.Description = function(ply) end if SERVER then - util.AddNetworkString("TTT_Taskmaster_KillFaraway_Assigned") - util.AddNetworkString("TTT_Taskmaster_KillFaraway_Cleanup") + util.AddNetworkString("TTT_Taskmaster_KillDistant_Assigned") + util.AddNetworkString("TTT_Taskmaster_KillDistant_Cleanup") TASK.CanAssignTask = function(ply) return true @@ -72,12 +72,12 @@ if SERVER then } TASK.OnTaskAssigned = function(ply) - hook.Add("PlayerDeath", "Taskmaster_KillFaraway_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + hook.Add("PlayerDeath", "Taskmaster_KillDistant_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) if not IsPlayer(victim) then return end if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end local distanceSqr = victim:GetPos():DistToSqr(attacker:GetPos()) - local range = taskmaster_killfaraway_range:GetInt() * UNITS_PER_METER + local range = taskmaster_killdistant_range:GetInt() * UNITS_PER_METER local rangeSqr = range * range if distanceSqr >= rangeSqr then @@ -85,13 +85,13 @@ if SERVER then end end) - net.Start("TTT_Taskmaster_KillFaraway_Assigned") + net.Start("TTT_Taskmaster_KillDistant_Assigned") net.Send(ply) end TASK.OnTaskRemoved = function(ply) - hook.Remove("PlayerDeath", "Taskmaster_KillFaraway_PlayerDeath_" .. ply:SteamID64()) - net.Start("TTT_Taskmaster_KillFaraway_Cleanup") + hook.Remove("PlayerDeath", "Taskmaster_KillDistant_PlayerDeath_" .. ply:SteamID64()) + net.Start("TTT_Taskmaster_KillDistant_Cleanup") net.Send(ply) end @@ -99,12 +99,12 @@ if SERVER then end if CLIENT then - net.Receive("TTT_Taskmaster_KillFaraway_Assigned", function() + net.Receive("TTT_Taskmaster_KillDistant_Assigned", function() local sid64 = LocalPlayer():SteamID64() - local range = taskmaster_killfaraway_range:GetInt() * UNITS_PER_METER + local range = taskmaster_killdistant_range:GetInt() * UNITS_PER_METER local rangeSqr = range * range - hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillFaraway_TTTTargetIDPlayerText_" .. sid64, function(ent, cli, text, col, secondaryText) + hook.Add("TTTTargetIDPlayerText", "Taskmaster_KillDistant_TTTTargetIDPlayerText_" .. sid64, function(ent, cli, text, col, secondaryText) if not cli:IsActiveTaskmaster() or not IsPlayer(ent) then return end local distanceSqr = ent:GetPos():DistToSqr(cli:GetPos()) @@ -116,7 +116,7 @@ if CLIENT then end) local particleVelocity = Vector(0, 0, 80) - hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillFaraway_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) + hook.Add("TTTPlayerAliveClientThink", "Taskmaster_KillDistant_TTTPlayerAliveClientThink_" .. sid64, function(cli, ply) local shouldDraw = false if ply == cli and cli:IsActiveTaskmaster() then local pos = ply:GetPos() @@ -154,11 +154,11 @@ if CLIENT then end) end) - net.Receive("TTT_Taskmaster_KillFaraway_Cleanup", function() + net.Receive("TTT_Taskmaster_KillDistant_Cleanup", function() local client = LocalPlayer() local sid64 = client:SteamID64() - hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillFaraway_TTTTargetIDPlayerText_" .. sid64) - hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_KillFaraway_TTTPlayerAliveClientThink_" .. sid64) + hook.Remove("TTTTargetIDPlayerText", "Taskmaster_KillDistant_TTTTargetIDPlayerText_" .. sid64) + hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_KillDistant_TTTPlayerAliveClientThink_" .. sid64) if client.TaskmasterRadiusEmitter then client.TaskmasterRadiusEmitter:Finish() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua index a1a12ff89..43052091d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua @@ -9,11 +9,11 @@ TASK.id = "killretaliate" TASK.isKillTask = true TASK.Name = function(ply) - return "Kill a Player Who Attacked First" + return "Kill a Player That Attacked First" end TASK.Description = function(ply) - return "Kill another player who attacked you first" + return "Kill another player that attacked you first" end local TASK_KILLRETALIATE_NONE = 0 diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua index 75e072fc8..4c49238a9 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua @@ -28,24 +28,25 @@ if SERVER then util.AddNetworkString("TTT_Taskmaster_KillTarget_Assigned") util.AddNetworkString("TTT_Taskmaster_KillTarget_Cleanup") - TASK.CanAssignTask = function(ply) - return true - end - - TASK.RequiredFeatures = { - TASKMASTER_TF_TARGETID_PLAYERICON - } - local function GetRandomTarget(ply) -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do if not p:Alive() or p:IsSpec() then continue end + if p:ShouldActLikeJester() then continue end if p == ply then continue end return p end return nil end + TASK.CanAssignTask = function(ply) + return GetRandomTarget(ply) ~= nil + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON + } + TASK.OnTaskAssigned = function(ply) local sid64 = ply:SteamID64() @@ -53,27 +54,31 @@ if SERVER then hook.Add("PlayerDeath", "Taskmaster_KillTarget_PlayerDeath_" .. sid64, function(victim, inflictor, attacker) if not IsPlayer(victim) then return end - if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end + if victim ~= ply.Task_KillTargetPlayer then return end - if victim == ply.Task_KillTargetPlayer then + if IsPlayer(attacker) and attacker:IsActiveTaskmaster() and attacker == ply then ply:CompleteTask(TASK.id) + else + local target = GetRandomTarget(ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. target:Nick()) + ply:SetProperty("Task_KillTargetPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! There are no new valid targets and you must reroll.") + end end end) - hook.Add("PostPlayerDeath", "Taskmaster_KillTarget_PostPlayerDeath_" .. sid64, function(victim) - if ply.Task_KillTargetPlayer ~= victim then return end - - local target = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. target:Nick()) - ply:SetProperty("Task_KillTargetPlayer", target, ply) - end) - hook.Add("PlayerDisconnected", "Taskmaster_KillTarget_PlayerDisconnected_" .. sid64, function(leaver) if ply.Task_KillTargetPlayer ~= leaver then return end local target = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) - ply:SetProperty("Task_KillTargetPlayer", target, ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_KillTargetPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! There are no new valid targets and you must reroll.") + end end) net.Start("TTT_Taskmaster_KillTarget_Assigned") @@ -93,7 +98,7 @@ if SERVER then TASK.OnTaskRemoved = function(ply) TASK.OnTaskComplete(ply) - ply:ClearProperty("Task_KillTargetPlayer", ply) + ply:ClearProperty("Task_KillTargetPlayer", ply) -- Don't clear the player name if the task was completed so it still shows up in the task name/description end end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua index 9c6af7650..c2d14e392 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua @@ -15,9 +15,11 @@ TASK.isKillTask = true TASK.Name = function(ply) local weapon = "Specific Weapon" if ply.Task_KillWeaponClass then - local wep = util.WeaponForClass(wep) - if IsValid(wep) then - weapon = TryTranslation(wep.GetPrintName and wep:GetPrintName() or wep.PrintName) + for _, wep in ipairs(weapons.GetList()) do + if ply.Task_KillWeaponClass == WEPS.GetClass(wep) then + weapon = TryTranslation(wep.PrintName) + break + end end end return "Kill a Player With a " .. weapon @@ -26,9 +28,11 @@ end TASK.Description = function(ply) local weapon = "specific weapon" if ply.Task_KillWeaponClass then - local wep = util.WeaponForClass(wep) - if IsValid(wep) then - weapon = TryTranslation(wep.GetPrintName and wep:GetPrintName() or wep.PrintName) + for _, wep in ipairs(weapons.GetList()) do + if ply.Task_KillWeaponClass == WEPS.GetClass(wep) then + weapon = TryTranslation(wep.PrintName) + break + end end end return "Kill another player using a " .. weapon diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index d04b04edf..6087c8ef5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -7,6 +7,7 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local TASK = {} @@ -24,7 +25,19 @@ TASK.Name = function(ply) if IsPlayer(ply.Task_LookAtPlayerPlayer) then name = ply.Task_LookAtPlayerPlayer:Nick() end - return "Look at " .. name + + local time = taskmaster_lookatplayer_time:GetInt() + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_LookAtPlayerStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return "Look at " .. name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) @@ -44,15 +57,6 @@ if SERVER then util.AddNetworkString("TTT_Taskmaster_LookAtPlayer_Assigned") util.AddNetworkString("TTT_Taskmaster_LookAtPlayer_Cleanup") - TASK.CanAssignTask = function(ply) - return true - end - - TASK.RequiredFeatures = { - TASKMASTER_TF_TARGETID_PLAYERICON, - TASKMASTER_TF_PROGRESSBAR - } - local function GetRandomTarget(ply) -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do @@ -63,6 +67,15 @@ if SERVER then return nil end + TASK.CanAssignTask = function(ply) + return GetRandomTarget(ply) ~= nil + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON, + TASKMASTER_TF_PROGRESSBAR + } + local function IsTargetInView(ply) if not IsValid(ply) then return false end @@ -96,23 +109,33 @@ if SERVER then hook.Add("PlayerDeath", "Taskmaster_LookAtPlayer_PlayerDeath_" .. sid64, function(victim, inflictor, attacker) if ply.Task_LookAtPlayerPlayer ~= victim then return end - if attacker == ply then return end - ply:CompleteTask(TASK.id) + + local target = GetRandomTarget(ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. target:Nick()) + ply:SetProperty("Task_LookAtPlayerPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! There are no new valid targets and you must reroll.") + end end) hook.Add("PlayerDisconnected", "Taskmaster_LookAtPlayer_PlayerDisconnected_" .. sid64, function(leaver) if ply.Task_LookAtPlayerPlayer ~= leaver then return end local target = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) - ply:SetProperty("Task_LookAtPlayerPlayer", target, ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_LookAtPlayerPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! There are no new valid targets and you must reroll.") + end end) net.Start("TTT_Taskmaster_LookAtPlayer_Assigned") net.Send(ply) end - TASK.OnTaskRemoved = function(ply) + TASK.OnTaskComplete = function(ply) timer.Remove("TTTTaskmasterLookAtPlayerTimer") local sid64 = ply:SteamID64() @@ -120,13 +143,15 @@ if SERVER then hook.Remove("PlayerDisconnected", "Taskmaster_LookAtPlayer_PlayerDisconnected_" .. sid64) ply:ClearProperty("Task_LookAtPlayerStart", ply) - ply:ClearProperty("Task_LookAtPlayerPlayer", ply) net.Start("TTT_Taskmaster_LookAtPlayer_Cleanup") net.Send(ply) end - TASK.OnTaskComplete = TASK.OnTaskRemoved + TASK.OnTaskRemoved = function(ply) + TASK.OnTaskComplete(ply) + ply:ClearProperty("Task_LookAtPlayerPlayer", ply) -- Don't clear the player name if the task was completed so it still shows up in the task name/description + end end if CLIENT then diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua index 16bb0298f..00c6aca33 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/pickupshopitem.lua @@ -3,11 +3,11 @@ local TASK = {} TASK.id = "pickupshopitem" TASK.Name = function(ply) - return "Pick up Shop Item" + return "Pick up a Shop Item" end TASK.Description = function(ply) - return "Pick up a shop item dropped on the ground" + return "Pick up a shop item that another player dropped on the ground" end if SERVER then diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index 79a9c4595..d740e09db 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -7,12 +7,13 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local TASK = {} TASK.id = "standonplayer" -local taskmaster_standonplayer_time = CreateConVar("ttt_taskmaster_standonplayer_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay stand on top of another player to complete the 'Stand On Player' task", 1, 240) +local taskmaster_standonplayer_time = CreateConVar("ttt_taskmaster_standonplayer_time", "15", FCVAR_REPLICATED, "The time (in seconds) a player must stay stand on top of another player to complete the 'Stand On Player' task", 1, 240) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_standonplayer_time", type = ROLE_CONVAR_TYPE_NUM, @@ -21,11 +22,22 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_standonplayer_time:GetInt() - local name = "Stand on Player for " .. time .. " Second" + local name = "Stand on a Player for " .. time .. " Second" if time ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_StandOnPlayerStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua deleted file mode 100644 index 855825820..000000000 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayaway.lua +++ /dev/null @@ -1,155 +0,0 @@ -local CurTime = CurTime -local hook = hook -local math = math -local net = net -local player = player -local table = table -local timer = timer -local util = util - -local MathMax = math.max -local PlayerIterator = player.Iterator - -local TASK = {} - -TASK.id = "stayaway" - -local taskmaster_stayaway_range = CreateConVar("ttt_taskmaster_stayaway_range", "25", FCVAR_REPLICATED, "The distance (in meters) a player must stay away from other people to count for the 'Stay Away' task", 1, 100) -local taskmaster_stayaway_time = CreateConVar("ttt_taskmaster_stayaway_time", "30", FCVAR_REPLICATED, "The time (in seconds) a player must stay away from other people to count for the 'Stay Away' task", 1, 240) -table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { - cvar = "ttt_taskmaster_stayaway_range", - type = ROLE_CONVAR_TYPE_NUM, - decimal = 0 -}) -table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { - cvar = "ttt_taskmaster_stayaway_time", - type = ROLE_CONVAR_TYPE_NUM, - decimal = 0 -}) - -TASK.Name = function(ply) - local time = taskmaster_stayaway_time:GetInt() - local name = "Stay Away for " .. time .. " Second" - if time ~= 1 then - name = name .. "s" - end - return name -end - -TASK.Description = function(ply) - local time = taskmaster_stayaway_time:GetInt() - local desc = "Stay away from other players for " .. time .. " second" - if time ~= 1 then - desc = desc .. "s" - end - return desc -end - -if SERVER then - util.AddNetworkString("TTT_Taskmaster_StayAway_Assigned") - util.AddNetworkString("TTT_Taskmaster_StayAway_Cleanup") - - TASK.CanAssignTask = function(ply) - return true - end - - TASK.RequiredFeatures = { - TASKMASTER_TF_PROGRESSBAR - } - - local function GetNearestPlayer(ply) - local pos = ply:GetPos() - local nearest = nil - for _, p in PlayerIterator() do - if p == ply then continue end - if not p:Alive() or p:IsSpec() then continue end - - local dist = pos:DistToSqr(p:GetPos()) - if not nearest or dist < nearest.dist then - nearest = { - ply = p, - dist = dist - } - end - end - - return nearest - end - - TASK.OnTaskAssigned = function(ply) - local range = taskmaster_stayaway_range:GetInt() * UNITS_PER_METER - local rangeSqr = range * range - local time = taskmaster_stayaway_time:GetInt() - timer.Create("TTTTaskmasterStayAwayTimer", 0.1, 0, function() - if not IsPlayer(ply) then return end - - -- Within range - local nearest = GetNearestPlayer(ply) - if ply:Alive() and not ply:IsSpec() and nearest.dist >= rangeSqr then - -- Just starting - if not ply.Task_StayAwayStart then - ply:SetProperty("Task_StayAwayStart", CurTime(), ply) - -- Long enough - elseif CurTime() > ply.Task_StayAwayStart + time then - ply:CompleteTask(TASK.id) - end - -- Not within range - else - ply:ClearProperty("Task_StayAwayStart", ply) - end - end) - - net.Start("TTT_Taskmaster_StayAway_Assigned") - net.Send(ply) - end - - TASK.OnTaskRemoved = function(ply) - timer.Remove("TTTTaskmasterStayAwayTimer") - - ply:ClearProperty("Task_StayAwayLocation", ply) - ply:ClearProperty("Task_StayAwayStart", ply) - - hook.Remove("PostPlayerDeath", "Taskmaster_StayAway_PostPlayerDeath_" .. ply:SteamID64()) - - net.Start("TTT_Taskmaster_StayAway_Cleanup") - net.Send(ply) - end - - TASK.OnTaskComplete = TASK.OnTaskRemoved -end - -if CLIENT then - net.Receive("TTT_Taskmaster_StayAway_Assigned", function() - local client = LocalPlayer() - local time = taskmaster_stayaway_time:GetInt() - - hook.Add("HUDPaint", "Taskmaster_StayAway_HUDPaint_" .. client:SteamID64(), function() - if not client:IsActiveTaskmaster() then return end - - local startTime = client.Task_StayAwayStart - if not startTime then return end - - local PT = LANG.GetParamTranslation - local elapsed = MathMax(0, CurTime() - startTime) - local remaining = time - elapsed - local message = PT("taskmaster_stayaway", { time = util.SimpleTime(remaining, "%02i:%02i") }) - local color = Color(0, 255, 0, 155) - - local x = ScrW() / 2.0 - local y = ScrH() / 2.0 - y = y + (y / 3) - - local w = 300 - local progress = elapsed / time - - CRHUD:PaintProgressBar(x, y, w, color, message, progress) - end) - end) - - net.Receive("TTT_Taskmaster_StayAway_Cleanup", function() - local client = LocalPlayer() - hook.Remove("HUDPaint", "Taskmaster_StayAway_HUDPaint_" .. client:SteamID64()) - end) -end - -TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua index e8e141d8e..4c465513f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua @@ -8,6 +8,7 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local PlayerIterator = player.Iterator local TASK = {} @@ -22,7 +23,23 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - return "Stay Hidden" + local time = taskmaster_stayhidden_time:GetInt() + local name = "Stay Hidden for " .. time .. " Second" + if time ~= 1 then + name = name .. "s" + end + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_StayHiddenStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index 76b1d0c6b..66959e616 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -8,6 +8,7 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local PlayerIterator = player.Iterator local TASK = {} @@ -23,11 +24,22 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_stayhigher_time:GetInt() - local name = "Highest for " .. time .. " Second" + local name = "Be Highest for " .. time .. " Second" if time ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_StayHigherStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index 3d51939ef..d6b9f3cd8 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -11,6 +11,7 @@ local MathMax = math.max local MathPi = math.pi local MathRand = math.Rand local MathSin = math.sin +local MathFloor = math.floor local TASK = {} @@ -35,7 +36,18 @@ TASK.Name = function(ply) if time ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_StayInAreaStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) @@ -156,6 +168,8 @@ if CLIENT then end end) + -- TODO: Needs a visual to help locate the area if you can't immediately see it. Maybe copy the same thing we have for the shadow? + hook.Add("HUDPaint", "Taskmaster_StayInArea_HUDPaint_" .. sid64, function() if not client:IsActiveTaskmaster() then return end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index c55673fdc..91dfcacd2 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -8,6 +8,7 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local PlayerIterator = player.Iterator local TASK = {} @@ -23,11 +24,22 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_staylower_time:GetInt() - local name = "Lowest for " .. time .. " Second" + local name = "Be Lowest for " .. time .. " Second" if time ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_StayLowerStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 6e86ae50d..744df5c41 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -13,6 +13,7 @@ local MathMax = math.max local MathPi = math.pi local MathRand = math.Rand local MathSin = math.sin +local MathFloor = math.floor local TASK = {} @@ -36,7 +37,19 @@ TASK.Name = function(ply) if IsPlayer(ply.Task_StayNearTargetPlayer) then name = ply.Task_StayNearTargetPlayer:Nick() end - return "Stay Near " .. name + + local time = taskmaster_stayneartarget_time:GetInt() + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_StayNearTargetStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return "Stay Near " .. name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) @@ -86,16 +99,6 @@ if SERVER then util.AddNetworkString("TTT_Taskmaster_StayNearTarget_Assigned") util.AddNetworkString("TTT_Taskmaster_StayNearTarget_Cleanup") - TASK.CanAssignTask = function(ply) - return true - end - - TASK.RequiredFeatures = { - TASKMASTER_TF_TARGETID_PLAYERICON, - TASKMASTER_TF_PROGRESSBAR, - TASKMASTER_TF_PARTICLERADIUS - } - local function GetRandomTarget(ply) -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do @@ -106,6 +109,16 @@ if SERVER then return nil end + TASK.CanAssignTask = function(ply) + return GetRandomTarget(ply) ~= nil + end + + TASK.RequiredFeatures = { + TASKMASTER_TF_TARGETID_PLAYERICON, + TASKMASTER_TF_PROGRESSBAR, + TASKMASTER_TF_PARTICLERADIUS + } + TASK.OnTaskAssigned = function(ply) local sid64 = ply:SteamID64() local range = taskmaster_stayneartarget_range:GetInt() * UNITS_PER_METER @@ -137,26 +150,33 @@ if SERVER then if ply.Task_StayNearTargetPlayer ~= victim then return end local target = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. target:Nick()) - ply:SetProperty("Task_StayNearTargetPlayer", target, ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! Your new target is " .. target:Nick()) + ply:SetProperty("Task_StayNearTargetPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has died! There are no new valid targets and you must reroll.") + end end) hook.Add("PlayerDisconnected", "Taskmaster_StayNearTarget_PlayerDisconnected_" .. sid64, function(leaver) if ply.Task_StayNearTargetPlayer ~= leaver then return end local target = GetRandomTarget(ply) - ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) - ply:SetProperty("Task_StayNearTargetPlayer", target, ply) + if target then + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! Your new target is " .. target:Nick()) + ply:SetProperty("Task_StayNearTargetPlayer", target, ply) + else + ply:QueueMessage(MSG_PRINTBOTH, "Your target for the '" .. TASK.Name(ply) .. "' task has disappeared! There are no new valid targets and you must reroll.") + end end) net.Start("TTT_Taskmaster_StayNearTarget_Assigned") net.Send(ply) end - TASK.OnTaskRemoved = function(ply) + TASK.OnTaskComplete = function(ply) timer.Remove("TTTTaskmasterStayNearTargetTimer") - ply:ClearProperty("Task_StayNearTargetPlayer", ply) ply:ClearProperty("Task_StayNearTargetStart", ply) hook.Remove("PostPlayerDeath", "Taskmaster_StayNearTarget_PostPlayerDeath_" .. ply:SteamID64()) @@ -166,7 +186,10 @@ if SERVER then net.Send(ply) end - TASK.OnTaskComplete = TASK.OnTaskRemoved + TASK.OnTaskRemoved = function(ply) + TASK.OnTaskComplete(ply) + ply:ClearProperty("Task_StayNearTargetPlayer", ply) -- Don't clear the player name if the task was completed so it still shows up in the task name/description + end end if CLIENT then diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua index c95d59785..f574f174a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -7,6 +7,7 @@ local timer = timer local util = util local MathMax = math.max +local MathFloor = math.floor local TASK = {} @@ -20,12 +21,23 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - return "Take Damage" + local time = taskmaster_takedamage_time:GetInt() + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = time + else + local startTime = ply.Task_TakeDamageStart + if startTime then + progress = MathFloor(MathMax(0, CurTime() - startTime)) + end + end + + return "Take Damage and Survive (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) local time = taskmaster_takedamage_time:GetInt() - local desc = "Take damage and survive for " .. time .. " second" + local desc = "Get another player to deal damage to you and survive for " .. time .. " second" if time ~= 1 then desc = desc .. "s" end @@ -58,6 +70,10 @@ if SERVER then hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken) if not wasDamageTaken then return end if entity ~= ply then return end + + local attacker = dmginfo:GetAttacker() + if not IsPlayer(attacker) or attacker == entity then return end + ply:SetProperty("Task_TakeDamageStart", CurTime(), ply) end) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua index 07f255b46..1b2c5e28d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua @@ -17,7 +17,15 @@ TASK.Name = function(ply) if count ~= 1 then name = name .. "s" end - return name + + local progress = 0 + if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + progress = count + else + progress = ply.Task_WeaponPickupsCount + end + + return name .. " (" .. progress .. "/" .. count .. ")" end TASK.Description = function(ply) @@ -38,14 +46,14 @@ if SERVER then TASK.OnTaskAssigned = function(ply) local count = taskmaster_weaponpickups_count:GetInt() - local total = 0 + ply:SetProperty("Task_WeaponPickupsCount", 0, ply) hook.Add("WeaponEquip", "Taskmaster_WeaponPickup_WeaponEquip_" .. ply:SteamID64(), function(wep, owner) if owner ~= ply then return end if not IsValid(wep) then return end if wep.Task_WeaponPickupsUsed then return end wep.Task_WeaponPickupsUsed = true - total = total + 1 - if total >= count then + ply:SetProperty("Task_WeaponPickupsCount", ply.Task_WeaponPickupsCount + 1, ply) + if ply.Task_WeaponPickupsCount >= count then ply:CompleteTask(TASK.id) end end) @@ -53,6 +61,8 @@ if SERVER then TASK.OnTaskRemoved = function(ply) hook.Remove("WeaponEquip", "Taskmaster_WeaponPickup_WeaponEquip_" .. ply:SteamID64()) + + ply:ClearProperty("Task_WeaponPickupsCount", ply) end TASK.OnTaskComplete = TASK.OnTaskRemoved From 6f7be995dade80fa754aad4f9bf6415fe97d7b23 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 10:32:46 -0400 Subject: [PATCH 085/140] Cleanup --- .../gamemode/roles/taskmaster/tasks/carrycorpse.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/chat.lua | 7 ++++++- .../gamemode/roles/taskmaster/tasks/crouch.lua | 3 ++- .../roles/taskmaster/tasks/crouchnearbody.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/crowbar.lua | 5 ++++- .../roles/taskmaster/tasks/damageeveryone.lua | 2 +- .../gamemode/roles/taskmaster/tasks/dodamage.lua | 7 ++++++- .../gamemode/roles/taskmaster/tasks/firebullets.lua | 7 ++++++- .../roles/taskmaster/tasks/getplayerkilled.lua | 13 +++++++++++-- .../roles/taskmaster/tasks/getplayertokill.lua | 13 +++++++++++-- .../gamemode/roles/taskmaster/tasks/healthunder.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/holstered.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/jump.lua | 7 ++++++- .../gamemode/roles/taskmaster/tasks/kill360.lua | 2 ++ .../roles/taskmaster/tasks/killlastbullet.lua | 1 + .../roles/taskmaster/tasks/killretaliate.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/killtarget.lua | 2 +- .../gamemode/roles/taskmaster/tasks/killweapon.lua | 6 ++++-- .../roles/taskmaster/tasks/lookatplayer.lua | 3 ++- .../roles/taskmaster/tasks/standonplayer.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/stayhidden.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/stayhigher.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/stayinarea.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/staylower.lua | 3 ++- .../roles/taskmaster/tasks/stayneartarget.lua | 3 ++- .../gamemode/roles/taskmaster/tasks/takedamage.lua | 3 ++- .../roles/taskmaster/tasks/weaponpickups.lua | 7 +++++-- 27 files changed, 92 insertions(+), 29 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua index 39184cbc2..2905c6346 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -8,6 +8,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -28,7 +29,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_CarryCorpseStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua index 1526fac38..2a31ec6c8 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua @@ -1,3 +1,8 @@ +local hook = hook +local table = table + +local TableHasValue = table.HasValue + local TASK = {} TASK.id = "chat" @@ -17,7 +22,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times else progress = ply.Task_ChatCount diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 85cc4f517..27a73c0c5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -8,6 +8,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -28,7 +29,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_CrouchStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index 958a2afaf..6fecedc4a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -11,6 +11,7 @@ local util = util local EntsFindByClass = ents.FindByClass local MathMax = math.max local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -33,7 +34,7 @@ TASK.Name = function(ply) local time = taskmaster_crouchnearbody_time:GetInt() local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_CrouchNearBodyStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua index b6e3b096c..414766b3b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua @@ -1,5 +1,8 @@ +local hook = hook local table = table +local TableHasValue = table.HasValue + local TASK = {} TASK.id = "crowbar" @@ -19,7 +22,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times else progress = ply.Task_CrowbarCount diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua index 21b58f946..841990b1a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua @@ -26,7 +26,7 @@ TASK.Name = function(ply) end end - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = total end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua index fdda1a945..e7024846b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua @@ -1,3 +1,8 @@ +local hook = hook +local table = table + +local TableHasValue = table.HasValue + local TASK = {} TASK.id = "dodamage" @@ -13,7 +18,7 @@ TASK.Name = function(ply) local amount = taskmaster_dodamage_amount:GetInt() local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = amount else progress = ply.Task_DoDamageTotal diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua index b4be58a5b..ef6671c1d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua @@ -1,3 +1,8 @@ +local hook = hook +local table = table + +local TableHasValue = table.HasValue + local TASK = {} TASK.id = "firebullets" @@ -17,7 +22,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times else progress = ply.Task_FireBulletsCount diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua index 218779c27..e487f109e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua @@ -29,13 +29,22 @@ if SERVER then util.AddNetworkString("TTT_Taskmaster_GetPlayerKilled_Cleanup") local function GetRandomTarget(ply) + local passive = nil -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do - if not p:Alive() or p:IsSpec() then continue end if p == ply then continue end + if not p:Alive() or p:IsSpec() then continue end + if p:ShouldActLikeJester() then continue end + -- They just want live, leave them alone + if ROLE_HAS_PASSIVE_WIN[p:GetRole()] then + passive = p + continue + end return p end - return nil + + -- Return the passive winner (if we have one) as a last resort + return passive end TASK.CanAssignTask = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua index 42b242322..48997cc7a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua @@ -29,13 +29,22 @@ if SERVER then util.AddNetworkString("TTT_Taskmaster_GetPlayerToKill_Cleanup") local function GetRandomTarget(ply) + local passive = nil -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do - if not p:Alive() or p:IsSpec() then continue end if p == ply then continue end + if not p:Alive() or p:IsSpec() then continue end + if p:ShouldActLikeJester() then continue end + -- They just want live, they aren't going to kill anyone + if ROLE_HAS_PASSIVE_WIN[p:GetRole()] then + passive = p + continue + end return p end - return nil + + -- Return the passive winner (if we have one) as a last resort + return passive end TASK.CanAssignTask = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 877fcca71..926536737 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -8,6 +8,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -31,7 +32,7 @@ TASK.Name = function(ply) local time = taskmaster_healthunder_time:GetInt() local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_HealthUnderStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index f09e556de..e12b7e3c9 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -8,6 +8,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -28,7 +29,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_HolsteredStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua index d769f1189..ac909a7a5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -1,3 +1,8 @@ +local hook = hook +local table = table + +local TableHasValue = table.HasValue + local TASK = {} TASK.id = "jump" @@ -17,7 +22,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times else progress = ply.Task_CrowbarCount diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua index 4d474f62b..fa3b17adf 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua @@ -1,5 +1,7 @@ local hook = hook local math = math +local table = table +local util = util local MathAbs = math.abs local MathMax = math.max diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua index f8b5d6875..820417906 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua @@ -1,4 +1,5 @@ local hook = hook +local timer = timer local TASK = {} diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua index 43052091d..80ee20c65 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua @@ -1,5 +1,7 @@ local hook = hook +local net = net local player = player +local util = util local PlayerIterator = player.Iterator @@ -33,7 +35,6 @@ if SERVER then } TASK.OnTaskAssigned = function(ply) - for _, p in PlayerIterator() do if ply == p then continue end p:SetProperty("Task_KillRetaliateAttacked", TASK_KILLRETALIATE_NONE, ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua index 4c49238a9..61efc03dc 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua @@ -31,9 +31,9 @@ if SERVER then local function GetRandomTarget(ply) -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do + if p == ply then continue end if not p:Alive() or p:IsSpec() then continue end if p:ShouldActLikeJester() then continue end - if p == ply then continue end return p end return nil diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua index c2d14e392..ad8cd1730 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua @@ -1,9 +1,10 @@ local hook = hook +local ipairs = ipairs local table = table local math = math -local TableInsert = table.insert local MathRandom = math.random +local TableInsert = table.insert local TryTranslation = LANG.TryTranslation @@ -55,7 +56,8 @@ if SERVER then ply:SetProperty("Task_KillWeaponClass", weps[MathRandom(#weps)].ClassName, ply) local equipped = false - if ply:GetActiveWeapon().Kind == WEAPON_HEAVY then + local active = ply:GetActiveWeapon() + if active and active.Kind == WEAPON_HEAVY then equipped = true end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index 6087c8ef5..cc81d3d4f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -8,6 +8,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -28,7 +29,7 @@ TASK.Name = function(ply) local time = taskmaster_lookatplayer_time:GetInt() local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_LookAtPlayerStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index d740e09db..a7f862893 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -8,6 +8,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -28,7 +29,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StandOnPlayerStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua index 4c465513f..36a2c8b08 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua @@ -10,6 +10,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor local PlayerIterator = player.Iterator +local TableHasValue = table.HasValue local TASK = {} @@ -30,7 +31,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayHiddenStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index 66959e616..fd6e77e14 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -10,6 +10,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor local PlayerIterator = player.Iterator +local TableHasValue = table.HasValue local TASK = {} @@ -30,7 +31,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayHigherStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index d6b9f3cd8..3b4eaad85 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -12,6 +12,7 @@ local MathPi = math.pi local MathRand = math.Rand local MathSin = math.sin local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -38,7 +39,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayInAreaStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index 91dfcacd2..986ce5136 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -10,6 +10,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor local PlayerIterator = player.Iterator +local TableHasValue = table.HasValue local TASK = {} @@ -30,7 +31,7 @@ TASK.Name = function(ply) end local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayLowerStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 744df5c41..5d55b9199 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -14,6 +14,7 @@ local MathPi = math.pi local MathRand = math.Rand local MathSin = math.sin local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -40,7 +41,7 @@ TASK.Name = function(ply) local time = taskmaster_stayneartarget_time:GetInt() local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayNearTargetStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua index f574f174a..76577e1e1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -8,6 +8,7 @@ local util = util local MathMax = math.max local MathFloor = math.floor +local TableHasValue = table.HasValue local TASK = {} @@ -23,7 +24,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_takedamage_time:GetInt() local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_TakeDamageStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua index 1b2c5e28d..7a04f78c8 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua @@ -1,5 +1,8 @@ +local hook = hook local table = table +local TableHasValue = table.HasValue + local TASK = {} TASK.id = "weaponpickups" @@ -18,8 +21,8 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 - if (table.HasValue(ply.taskmasterCompletedTasks, TASK.id)) then + local progress + if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = count else progress = ply.Task_WeaponPickupsCount From 0d2ecc050bc6b2224951ff639f9cf4d0619aa247 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 10:34:23 -0400 Subject: [PATCH 086/140] Luacheck fixes --- .../terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua | 2 +- gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua | 2 +- gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua | 2 +- .../gamemode/roles/taskmaster/tasks/crouchnearbody.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua | 2 +- .../gamemode/roles/taskmaster/tasks/damageeveryone.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua | 2 +- .../gamemode/roles/taskmaster/tasks/getplayerkilled.lua | 2 +- .../gamemode/roles/taskmaster/tasks/getplayertokill.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/holstered.lua | 2 +- gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua | 2 +- .../gamemode/roles/taskmaster/tasks/standonplayer.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/staylower.lua | 2 +- .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua index 2905c6346..e7e9e2b05 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua index 2a31ec6c8..d95c756ce 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua @@ -21,7 +21,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 27a73c0c5..2295891d5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index 6fecedc4a..f71820921 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -33,7 +33,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_crouchnearbody_time:GetInt() - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua index 414766b3b..0f90cd1b3 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua @@ -21,7 +21,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua index 841990b1a..a94f27e87 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua @@ -16,7 +16,7 @@ TASK.id = "damageeveryone" TASK.Name = function(ply) local damaged = ply.Task_DamageEveryoneDamaged or {} local total = 0 - local progress = 0 + local progress for _, p in PlayerIterator() do if ply == p then continue end if not p:Alive() or p:IsSpec() then continue end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua index e7024846b..a85c327a0 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua @@ -17,7 +17,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local amount = taskmaster_dodamage_amount:GetInt() - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = amount else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua index ef6671c1d..4e097f009 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua @@ -21,7 +21,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua index e487f109e..4c17fa07e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua @@ -29,7 +29,7 @@ if SERVER then util.AddNetworkString("TTT_Taskmaster_GetPlayerKilled_Cleanup") local function GetRandomTarget(ply) - local passive = nil + local passive -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do if p == ply then continue end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua index 48997cc7a..683ca836e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua @@ -29,7 +29,7 @@ if SERVER then util.AddNetworkString("TTT_Taskmaster_GetPlayerToKill_Cleanup") local function GetRandomTarget(ply) - local passive = nil + local passive -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do if p == ply then continue end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 926536737..02e77de2e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -31,7 +31,7 @@ TASK.Name = function(ply) local amount = taskmaster_healthunder_amount:GetInt() local time = taskmaster_healthunder_time:GetInt() - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index e12b7e3c9..42489e9c3 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua index ac909a7a5..fb36541bb 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -21,7 +21,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index cc81d3d4f..41efe9ccc 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) end local time = taskmaster_lookatplayer_time:GetInt() - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index a7f862893..33c8070cd 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua index 36a2c8b08..1ea5e5f7b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua @@ -30,7 +30,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index fd6e77e14..98149eb65 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -30,7 +30,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index 3b4eaad85..36c39a040 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -38,7 +38,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index 986ce5136..aa78977c8 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -30,7 +30,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 5d55b9199..dc1cc8d66 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -40,7 +40,7 @@ TASK.Name = function(ply) end local time = taskmaster_stayneartarget_time:GetInt() - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua index 76577e1e1..532edbc1e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -23,7 +23,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_takedamage_time:GetInt() - local progress = 0 + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else From 128d863f3d85b44952acf58d6e930a5f24ac32d9 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 10:36:25 -0400 Subject: [PATCH 087/140] Revert wrong luacheck fix --- .../gamemode/roles/taskmaster/tasks/getplayerkilled.lua | 2 +- .../gamemode/roles/taskmaster/tasks/getplayertokill.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua index 4c17fa07e..e487f109e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua @@ -29,7 +29,7 @@ if SERVER then util.AddNetworkString("TTT_Taskmaster_GetPlayerKilled_Cleanup") local function GetRandomTarget(ply) - local passive + local passive = nil -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do if p == ply then continue end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua index 683ca836e..48997cc7a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua @@ -29,7 +29,7 @@ if SERVER then util.AddNetworkString("TTT_Taskmaster_GetPlayerToKill_Cleanup") local function GetRandomTarget(ply) - local passive + local passive = nil -- Find the first random living player for _, p in RandomPairs(player.GetAll()) do if p == ply then continue end From 89052eceb44df126e22d5a15c815a8e9ac985d69 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 10:39:58 -0400 Subject: [PATCH 088/140] Try a different fork of gluacheck --- .github/workflows/luacheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml index 82944c75a..7bbd5e50c 100644 --- a/.github/workflows/luacheck.yml +++ b/.github/workflows/luacheck.yml @@ -17,7 +17,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - repository: 'Malivil/gluacheck' + repository: 'impulsh/gluacheck' path: 'gluacheck' - name: Setup ‘lua’ From ae6aa98575414c451599572bb69d4ef437b1fc71 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 10:42:26 -0400 Subject: [PATCH 089/140] Try the right rockspec --- .github/workflows/luacheck.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml index 7bbd5e50c..c6d63daa1 100644 --- a/.github/workflows/luacheck.yml +++ b/.github/workflows/luacheck.yml @@ -30,10 +30,10 @@ jobs: uses: leafo/gh-actions-luarocks@v4 - name: Setup dependencies - run: luarocks install --deps-only gluacheck-dev-1.rockspec + run: luarocks install --deps-only luacheck-dev-1.rockspec - name: Build ‘luacheck’ (bootstrap) - run: luarocks make gluacheck-dev-1.rockspec + run: luarocks make luacheck-dev-1.rockspec - name: Run ‘luacheck’ (dogfood) run: lua -e 'package.path="./gluacheck/src/?.lua;./gluacheck/src/?/init.lua;"..package.path' gluacheck/bin/luacheck.lua gamemodes scripts From 4a69303fbc24cceb38e3911af14b1ee71e09f37e Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 10:44:02 -0400 Subject: [PATCH 090/140] Change to the actual right rockspec --- .github/workflows/luacheck.yml | 4 +- gluacheck-dev-1.rockspec | 147 ++++++++++++++------------------- 2 files changed, 64 insertions(+), 87 deletions(-) diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml index c6d63daa1..7bbd5e50c 100644 --- a/.github/workflows/luacheck.yml +++ b/.github/workflows/luacheck.yml @@ -30,10 +30,10 @@ jobs: uses: leafo/gh-actions-luarocks@v4 - name: Setup dependencies - run: luarocks install --deps-only luacheck-dev-1.rockspec + run: luarocks install --deps-only gluacheck-dev-1.rockspec - name: Build ‘luacheck’ (bootstrap) - run: luarocks make luacheck-dev-1.rockspec + run: luarocks make gluacheck-dev-1.rockspec - name: Run ‘luacheck’ (dogfood) run: lua -e 'package.path="./gluacheck/src/?.lua;./gluacheck/src/?/init.lua;"..package.path' gluacheck/bin/luacheck.lua gamemodes scripts diff --git a/gluacheck-dev-1.rockspec b/gluacheck-dev-1.rockspec index 3ae109c3b..d002f242a 100644 --- a/gluacheck-dev-1.rockspec +++ b/gluacheck-dev-1.rockspec @@ -1,105 +1,82 @@ -local package_name = "gluacheck" -local package_version = "dev" -local rockspec_revision = "1" -local github_account_name = "Malivil" -local github_repo_name = package_name - -rockspec_format = "3.0" -package = package_name -version = package_version .. "-" .. rockspec_revision - +package = "luacheck" +version = "dev-1" source = { - url = "git+https://github.com/" .. github_account_name .. "/" .. github_repo_name .. ".git" + url = "git+https://github.com/mpeterv/luacheck.git" } - -if package_version == "dev" then source.branch = "master" else source.tag = "v" .. package_version end - description = { summary = "A static analyzer and a linter for Lua", detailed = [[ - Luacheck is a command-line tool for linting and static analysis of Lua - code. It is able to spot usage of undefined global variables, unused - local variables and a few other typical problems within Lua programs. - ]], - homepage = "https://github.com/lunarmodules/luacheck", +Luacheck is a command-line tool for linting and static analysis of Lua code. +It is able to spot usage of undefined global variables, unused local variables and +a few other typical problems within Lua programs. +]], + homepage = "https://github.com/mpeterv/luacheck", license = "MIT" } - dependencies = { - "lua >= 5.1", + "lua >= 5.1, < 5.4", "argparse >= 0.6.0", "luafilesystem >= 1.6.3" } - -test_dependencies = { - "busted", -} - -test = { - type = "busted", -} - build = { type = "builtin", modules = { - luacheck = "gluacheck/src/luacheck/init.lua", - ["luacheck.builtin_standards"] = "gluacheck/src/luacheck/builtin_standards/init.lua", - ["luacheck.builtin_standards.love"] = "gluacheck/src/luacheck/builtin_standards/love.lua", - ["luacheck.builtin_standards.playdate"] = "gluacheck/src/luacheck/builtin_standards/playdate.lua", - ["luacheck.builtin_standards.ngx"] = "gluacheck/src/luacheck/builtin_standards/ngx.lua", - ["luacheck.cache"] = "gluacheck/src/luacheck/cache.lua", - ["luacheck.check"] = "gluacheck/src/luacheck/check.lua", - ["luacheck.check_state"] = "gluacheck/src/luacheck/check_state.lua", - ["luacheck.config"] = "gluacheck/src/luacheck/config.lua", - ["luacheck.core_utils"] = "gluacheck/src/luacheck/core_utils.lua", - ["luacheck.decoder"] = "gluacheck/src/luacheck/decoder.lua", - ["luacheck.expand_rockspec"] = "gluacheck/src/luacheck/expand_rockspec.lua", - ["luacheck.filter"] = "gluacheck/src/luacheck/filter.lua", - ["luacheck.format"] = "gluacheck/src/luacheck/format.lua", - ["luacheck.fs"] = "gluacheck/src/luacheck/fs.lua", - ["luacheck.globbing"] = "gluacheck/src/luacheck/globbing.lua", - ["luacheck.lexer"] = "gluacheck/src/luacheck/lexer.lua", - ["luacheck.main"] = "gluacheck/src/luacheck/main.lua", - ["luacheck.multithreading"] = "gluacheck/src/luacheck/multithreading.lua", - ["luacheck.options"] = "gluacheck/src/luacheck/options.lua", - ["luacheck.parser"] = "gluacheck/src/luacheck/parser.lua", - ["luacheck.profiler"] = "gluacheck/src/luacheck/profiler.lua", - ["luacheck.runner"] = "gluacheck/src/luacheck/runner.lua", - ["luacheck.serializer"] = "gluacheck/src/luacheck/serializer.lua", - ["luacheck.stages"] = "gluacheck/src/luacheck/stages/init.lua", - ["luacheck.stages.detect_bad_whitespace"] = "gluacheck/src/luacheck/stages/detect_bad_whitespace.lua", - ["luacheck.stages.detect_compound_operators"] = "gluacheck/src/luacheck/stages/detect_compound_operators.lua", - ["luacheck.stages.detect_cyclomatic_complexity"] = "gluacheck/src/luacheck/stages/detect_cyclomatic_complexity.lua", - ["luacheck.stages.detect_empty_blocks"] = "gluacheck/src/luacheck/stages/detect_empty_blocks.lua", - ["luacheck.stages.detect_empty_statements"] = "gluacheck/src/luacheck/stages/detect_empty_statements.lua", - ["luacheck.stages.detect_globals"] = "gluacheck/src/luacheck/stages/detect_globals.lua", - ["luacheck.stages.detect_reversed_fornum_loops"] = "gluacheck/src/luacheck/stages/detect_reversed_fornum_loops.lua", - ["luacheck.stages.detect_unbalanced_assignments"] = "gluacheck/src/luacheck/stages/detect_unbalanced_assignments.lua", - ["luacheck.stages.detect_uninit_accesses"] = "gluacheck/src/luacheck/stages/detect_uninit_accesses.lua", - ["luacheck.stages.detect_unreachable_code"] = "gluacheck/src/luacheck/stages/detect_unreachable_code.lua", - ["luacheck.stages.detect_unused_fields"] = "gluacheck/src/luacheck/stages/detect_unused_fields.lua", - ["luacheck.stages.detect_unused_locals"] = "gluacheck/src/luacheck/stages/detect_unused_locals.lua", - ["luacheck.stages.linearize"] = "gluacheck/src/luacheck/stages/linearize.lua", - ["luacheck.stages.name_functions"] = "gluacheck/src/luacheck/stages/name_functions.lua", - ["luacheck.stages.parse"] = "gluacheck/src/luacheck/stages/parse.lua", - ["luacheck.stages.parse_inline_options"] = "gluacheck/src/luacheck/stages/parse_inline_options.lua", - ["luacheck.stages.resolve_locals"] = "gluacheck/src/luacheck/stages/resolve_locals.lua", - ["luacheck.stages.unwrap_parens"] = "gluacheck/src/luacheck/stages/unwrap_parens.lua", - ["luacheck.standards"] = "gluacheck/src/luacheck/standards.lua", - ["luacheck.unicode"] = "gluacheck/src/luacheck/unicode.lua", - ["luacheck.unicode_printability_boundaries"] = "gluacheck/src/luacheck/unicode_printability_boundaries.lua", - ["luacheck.utils"] = "gluacheck/src/luacheck/utils.lua", - ["luacheck.vendor.sha1"] = "gluacheck/src/luacheck/vendor/sha1/init.lua", - ["luacheck.vendor.sha1.bit32_ops"] = "gluacheck/src/luacheck/vendor/sha1/bit32_ops.lua", - ["luacheck.vendor.sha1.bit_ops"] = "gluacheck/src/luacheck/vendor/sha1/bit_ops.lua", - ["luacheck.vendor.sha1.common"] = "gluacheck/src/luacheck/vendor/sha1/common.lua", - ["luacheck.vendor.sha1.lua53_ops"] = "gluacheck/src/luacheck/vendor/sha1/lua53_ops.lua", - ["luacheck.vendor.sha1.pure_lua_ops"] = "gluacheck/src/luacheck/vendor/sha1/pure_lua_ops.lua", - ["luacheck.version"] = "gluacheck/src/luacheck/version.lua" + luacheck = "src/luacheck/init.lua", + ["luacheck.builtin_standards"] = "src/luacheck/builtin_standards/init.lua", + ["luacheck.builtin_standards.love"] = "src/luacheck/builtin_standards/love.lua", + ["luacheck.builtin_standards.ngx"] = "src/luacheck/builtin_standards/ngx.lua", + ["luacheck.cache"] = "src/luacheck/cache.lua", + ["luacheck.check"] = "src/luacheck/check.lua", + ["luacheck.check_state"] = "src/luacheck/check_state.lua", + ["luacheck.config"] = "src/luacheck/config.lua", + ["luacheck.core_utils"] = "src/luacheck/core_utils.lua", + ["luacheck.decoder"] = "src/luacheck/decoder.lua", + ["luacheck.expand_rockspec"] = "src/luacheck/expand_rockspec.lua", + ["luacheck.filter"] = "src/luacheck/filter.lua", + ["luacheck.format"] = "src/luacheck/format.lua", + ["luacheck.fs"] = "src/luacheck/fs.lua", + ["luacheck.globbing"] = "src/luacheck/globbing.lua", + ["luacheck.lexer"] = "src/luacheck/lexer.lua", + ["luacheck.main"] = "src/luacheck/main.lua", + ["luacheck.multithreading"] = "src/luacheck/multithreading.lua", + ["luacheck.options"] = "src/luacheck/options.lua", + ["luacheck.parser"] = "src/luacheck/parser.lua", + ["luacheck.profiler"] = "src/luacheck/profiler.lua", + ["luacheck.runner"] = "src/luacheck/runner.lua", + ["luacheck.serializer"] = "src/luacheck/serializer.lua", + ["luacheck.stages"] = "src/luacheck/stages/init.lua", + ["luacheck.stages.detect_bad_whitespace"] = "src/luacheck/stages/detect_bad_whitespace.lua", + ["luacheck.stages.detect_cyclomatic_complexity"] = "src/luacheck/stages/detect_cyclomatic_complexity.lua", + ["luacheck.stages.detect_empty_blocks"] = "src/luacheck/stages/detect_empty_blocks.lua", + ["luacheck.stages.detect_empty_statements"] = "src/luacheck/stages/detect_empty_statements.lua", + ["luacheck.stages.detect_globals"] = "src/luacheck/stages/detect_globals.lua", + ["luacheck.stages.detect_reversed_fornum_loops"] = "src/luacheck/stages/detect_reversed_fornum_loops.lua", + ["luacheck.stages.detect_unbalanced_assignments"] = "src/luacheck/stages/detect_unbalanced_assignments.lua", + ["luacheck.stages.detect_uninit_accesses"] = "src/luacheck/stages/detect_uninit_accesses.lua", + ["luacheck.stages.detect_unreachable_code"] = "src/luacheck/stages/detect_unreachable_code.lua", + ["luacheck.stages.detect_unused_fields"] = "src/luacheck/stages/detect_unused_fields.lua", + ["luacheck.stages.detect_unused_locals"] = "src/luacheck/stages/detect_unused_locals.lua", + ["luacheck.stages.linearize"] = "src/luacheck/stages/linearize.lua", + ["luacheck.stages.name_functions"] = "src/luacheck/stages/name_functions.lua", + ["luacheck.stages.parse"] = "src/luacheck/stages/parse.lua", + ["luacheck.stages.parse_inline_options"] = "src/luacheck/stages/parse_inline_options.lua", + ["luacheck.stages.resolve_locals"] = "src/luacheck/stages/resolve_locals.lua", + ["luacheck.stages.unwrap_parens"] = "src/luacheck/stages/unwrap_parens.lua", + ["luacheck.standards"] = "src/luacheck/standards.lua", + ["luacheck.unicode"] = "src/luacheck/unicode.lua", + ["luacheck.unicode_printability_boundaries"] = "src/luacheck/unicode_printability_boundaries.lua", + ["luacheck.utils"] = "src/luacheck/utils.lua", + ["luacheck.vendor.sha1"] = "src/luacheck/vendor/sha1/init.lua", + ["luacheck.vendor.sha1.bit_ops"] = "src/luacheck/vendor/sha1/bit_ops.lua", + ["luacheck.vendor.sha1.bit32_ops"] = "src/luacheck/vendor/sha1/bit32_ops.lua", + ["luacheck.vendor.sha1.common"] = "src/luacheck/vendor/sha1/common.lua", + ["luacheck.vendor.sha1.lua53_ops"] = "src/luacheck/vendor/sha1/lua53_ops.lua", + ["luacheck.vendor.sha1.pure_lua_ops"] = "src/luacheck/vendor/sha1/pure_lua_ops.lua", + ["luacheck.version"] = "src/luacheck/version.lua" }, install = { bin = { - luacheck = "gluacheck/bin/luacheck.lua" + luacheck = "bin/luacheck.lua" } } } From bbebdca2042d99d734d78330b03de08dbcb93d66 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 10:45:05 -0400 Subject: [PATCH 091/140] I'll get this right eventually --- .github/workflows/luacheck.yml | 4 ++-- gluacheck-dev-1.rockspec => luacheck-dev-1.rockspec | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename gluacheck-dev-1.rockspec => luacheck-dev-1.rockspec (100%) diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml index 7bbd5e50c..c6d63daa1 100644 --- a/.github/workflows/luacheck.yml +++ b/.github/workflows/luacheck.yml @@ -30,10 +30,10 @@ jobs: uses: leafo/gh-actions-luarocks@v4 - name: Setup dependencies - run: luarocks install --deps-only gluacheck-dev-1.rockspec + run: luarocks install --deps-only luacheck-dev-1.rockspec - name: Build ‘luacheck’ (bootstrap) - run: luarocks make gluacheck-dev-1.rockspec + run: luarocks make luacheck-dev-1.rockspec - name: Run ‘luacheck’ (dogfood) run: lua -e 'package.path="./gluacheck/src/?.lua;./gluacheck/src/?/init.lua;"..package.path' gluacheck/bin/luacheck.lua gamemodes scripts diff --git a/gluacheck-dev-1.rockspec b/luacheck-dev-1.rockspec similarity index 100% rename from gluacheck-dev-1.rockspec rename to luacheck-dev-1.rockspec From 6b7842731a938bb59721dbfb19270385b04bcfc4 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 10:47:41 -0400 Subject: [PATCH 092/140] Fixed paths --- luacheck-dev-1.rockspec | 104 ++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/luacheck-dev-1.rockspec b/luacheck-dev-1.rockspec index d002f242a..db6ed3001 100644 --- a/luacheck-dev-1.rockspec +++ b/luacheck-dev-1.rockspec @@ -21,58 +21,58 @@ dependencies = { build = { type = "builtin", modules = { - luacheck = "src/luacheck/init.lua", - ["luacheck.builtin_standards"] = "src/luacheck/builtin_standards/init.lua", - ["luacheck.builtin_standards.love"] = "src/luacheck/builtin_standards/love.lua", - ["luacheck.builtin_standards.ngx"] = "src/luacheck/builtin_standards/ngx.lua", - ["luacheck.cache"] = "src/luacheck/cache.lua", - ["luacheck.check"] = "src/luacheck/check.lua", - ["luacheck.check_state"] = "src/luacheck/check_state.lua", - ["luacheck.config"] = "src/luacheck/config.lua", - ["luacheck.core_utils"] = "src/luacheck/core_utils.lua", - ["luacheck.decoder"] = "src/luacheck/decoder.lua", - ["luacheck.expand_rockspec"] = "src/luacheck/expand_rockspec.lua", - ["luacheck.filter"] = "src/luacheck/filter.lua", - ["luacheck.format"] = "src/luacheck/format.lua", - ["luacheck.fs"] = "src/luacheck/fs.lua", - ["luacheck.globbing"] = "src/luacheck/globbing.lua", - ["luacheck.lexer"] = "src/luacheck/lexer.lua", - ["luacheck.main"] = "src/luacheck/main.lua", - ["luacheck.multithreading"] = "src/luacheck/multithreading.lua", - ["luacheck.options"] = "src/luacheck/options.lua", - ["luacheck.parser"] = "src/luacheck/parser.lua", - ["luacheck.profiler"] = "src/luacheck/profiler.lua", - ["luacheck.runner"] = "src/luacheck/runner.lua", - ["luacheck.serializer"] = "src/luacheck/serializer.lua", - ["luacheck.stages"] = "src/luacheck/stages/init.lua", - ["luacheck.stages.detect_bad_whitespace"] = "src/luacheck/stages/detect_bad_whitespace.lua", - ["luacheck.stages.detect_cyclomatic_complexity"] = "src/luacheck/stages/detect_cyclomatic_complexity.lua", - ["luacheck.stages.detect_empty_blocks"] = "src/luacheck/stages/detect_empty_blocks.lua", - ["luacheck.stages.detect_empty_statements"] = "src/luacheck/stages/detect_empty_statements.lua", - ["luacheck.stages.detect_globals"] = "src/luacheck/stages/detect_globals.lua", - ["luacheck.stages.detect_reversed_fornum_loops"] = "src/luacheck/stages/detect_reversed_fornum_loops.lua", - ["luacheck.stages.detect_unbalanced_assignments"] = "src/luacheck/stages/detect_unbalanced_assignments.lua", - ["luacheck.stages.detect_uninit_accesses"] = "src/luacheck/stages/detect_uninit_accesses.lua", - ["luacheck.stages.detect_unreachable_code"] = "src/luacheck/stages/detect_unreachable_code.lua", - ["luacheck.stages.detect_unused_fields"] = "src/luacheck/stages/detect_unused_fields.lua", - ["luacheck.stages.detect_unused_locals"] = "src/luacheck/stages/detect_unused_locals.lua", - ["luacheck.stages.linearize"] = "src/luacheck/stages/linearize.lua", - ["luacheck.stages.name_functions"] = "src/luacheck/stages/name_functions.lua", - ["luacheck.stages.parse"] = "src/luacheck/stages/parse.lua", - ["luacheck.stages.parse_inline_options"] = "src/luacheck/stages/parse_inline_options.lua", - ["luacheck.stages.resolve_locals"] = "src/luacheck/stages/resolve_locals.lua", - ["luacheck.stages.unwrap_parens"] = "src/luacheck/stages/unwrap_parens.lua", - ["luacheck.standards"] = "src/luacheck/standards.lua", - ["luacheck.unicode"] = "src/luacheck/unicode.lua", - ["luacheck.unicode_printability_boundaries"] = "src/luacheck/unicode_printability_boundaries.lua", - ["luacheck.utils"] = "src/luacheck/utils.lua", - ["luacheck.vendor.sha1"] = "src/luacheck/vendor/sha1/init.lua", - ["luacheck.vendor.sha1.bit_ops"] = "src/luacheck/vendor/sha1/bit_ops.lua", - ["luacheck.vendor.sha1.bit32_ops"] = "src/luacheck/vendor/sha1/bit32_ops.lua", - ["luacheck.vendor.sha1.common"] = "src/luacheck/vendor/sha1/common.lua", - ["luacheck.vendor.sha1.lua53_ops"] = "src/luacheck/vendor/sha1/lua53_ops.lua", - ["luacheck.vendor.sha1.pure_lua_ops"] = "src/luacheck/vendor/sha1/pure_lua_ops.lua", - ["luacheck.version"] = "src/luacheck/version.lua" + luacheck = "gluacheck/src/luacheck/init.lua", + ["luacheck.builtin_standards"] = "gluacheck/src/luacheck/builtin_standards/init.lua", + ["luacheck.builtin_standards.love"] = "gluacheck/src/luacheck/builtin_standards/love.lua", + ["luacheck.builtin_standards.ngx"] = "gluacheck/src/luacheck/builtin_standards/ngx.lua", + ["luacheck.cache"] = "gluacheck/src/luacheck/cache.lua", + ["luacheck.check"] = "gluacheck/src/luacheck/check.lua", + ["luacheck.check_state"] = "gluacheck/src/luacheck/check_state.lua", + ["luacheck.config"] = "gluacheck/src/luacheck/config.lua", + ["luacheck.core_utils"] = "gluacheck/src/luacheck/core_utils.lua", + ["luacheck.decoder"] = "gluacheck/src/luacheck/decoder.lua", + ["luacheck.expand_rockspec"] = "gluacheck/src/luacheck/expand_rockspec.lua", + ["luacheck.filter"] = "gluacheck/src/luacheck/filter.lua", + ["luacheck.format"] = "gluacheck/src/luacheck/format.lua", + ["luacheck.fs"] = "gluacheck/src/luacheck/fs.lua", + ["luacheck.globbing"] = "gluacheck/src/luacheck/globbing.lua", + ["luacheck.lexer"] = "gluacheck/src/luacheck/lexer.lua", + ["luacheck.main"] = "gluacheck/src/luacheck/main.lua", + ["luacheck.multithreading"] = "gluacheck/src/luacheck/multithreading.lua", + ["luacheck.options"] = "gluacheck/src/luacheck/options.lua", + ["luacheck.parser"] = "gluacheck/src/luacheck/parser.lua", + ["luacheck.profiler"] = "gluacheck/src/luacheck/profiler.lua", + ["luacheck.runner"] = "gluacheck/src/luacheck/runner.lua", + ["luacheck.serializer"] = "gluacheck/src/luacheck/serializer.lua", + ["luacheck.stages"] = "gluacheck/src/luacheck/stages/init.lua", + ["luacheck.stages.detect_bad_whitespace"] = "gluacheck/src/luacheck/stages/detect_bad_whitespace.lua", + ["luacheck.stages.detect_cyclomatic_complexity"] = "gluacheck/src/luacheck/stages/detect_cyclomatic_complexity.lua", + ["luacheck.stages.detect_empty_blocks"] = "gluacheck/src/luacheck/stages/detect_empty_blocks.lua", + ["luacheck.stages.detect_empty_statements"] = "gluacheck/src/luacheck/stages/detect_empty_statements.lua", + ["luacheck.stages.detect_globals"] = "gluacheck/src/luacheck/stages/detect_globals.lua", + ["luacheck.stages.detect_reversed_fornum_loops"] = "gluacheck/src/luacheck/stages/detect_reversed_fornum_loops.lua", + ["luacheck.stages.detect_unbalanced_assignments"] = "gluacheck/src/luacheck/stages/detect_unbalanced_assignments.lua", + ["luacheck.stages.detect_uninit_accesses"] = "gluacheck/src/luacheck/stages/detect_uninit_accesses.lua", + ["luacheck.stages.detect_unreachable_code"] = "gluacheck/src/luacheck/stages/detect_unreachable_code.lua", + ["luacheck.stages.detect_unused_fields"] = "gluacheck/src/luacheck/stages/detect_unused_fields.lua", + ["luacheck.stages.detect_unused_locals"] = "gluacheck/src/luacheck/stages/detect_unused_locals.lua", + ["luacheck.stages.linearize"] = "gluacheck/src/luacheck/stages/linearize.lua", + ["luacheck.stages.name_functions"] = "gluacheck/src/luacheck/stages/name_functions.lua", + ["luacheck.stages.parse"] = "gluacheck/src/luacheck/stages/parse.lua", + ["luacheck.stages.parse_inline_options"] = "gluacheck/src/luacheck/stages/parse_inline_options.lua", + ["luacheck.stages.resolve_locals"] = "gluacheck/src/luacheck/stages/resolve_locals.lua", + ["luacheck.stages.unwrap_parens"] = "gluacheck/src/luacheck/stages/unwrap_parens.lua", + ["luacheck.standards"] = "gluacheck/src/luacheck/standards.lua", + ["luacheck.unicode"] = "gluacheck/src/luacheck/unicode.lua", + ["luacheck.unicode_printability_boundaries"] = "gluacheck/src/luacheck/unicode_printability_boundaries.lua", + ["luacheck.utils"] = "gluacheck/src/luacheck/utils.lua", + ["luacheck.vendor.sha1"] = "gluacheck/src/luacheck/vendor/sha1/init.lua", + ["luacheck.vendor.sha1.bit_ops"] = "gluacheck/src/luacheck/vendor/sha1/bit_ops.lua", + ["luacheck.vendor.sha1.bit32_ops"] = "gluacheck/src/luacheck/vendor/sha1/bit32_ops.lua", + ["luacheck.vendor.sha1.common"] = "gluacheck/src/luacheck/vendor/sha1/common.lua", + ["luacheck.vendor.sha1.lua53_ops"] = "gluacheck/src/luacheck/vendor/sha1/lua53_ops.lua", + ["luacheck.vendor.sha1.pure_lua_ops"] = "gluacheck/src/luacheck/vendor/sha1/pure_lua_ops.lua", + ["luacheck.version"] = "gluacheck/src/luacheck/version.lua" }, install = { bin = { From 2539bf97dd27b54ef018aab826192f514208252f Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 10:48:48 -0400 Subject: [PATCH 093/140] Fixed another path --- luacheck-dev-1.rockspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/luacheck-dev-1.rockspec b/luacheck-dev-1.rockspec index db6ed3001..22b7d4f48 100644 --- a/luacheck-dev-1.rockspec +++ b/luacheck-dev-1.rockspec @@ -76,7 +76,7 @@ build = { }, install = { bin = { - luacheck = "bin/luacheck.lua" + luacheck = "gluacheck/bin/luacheck.lua" } } } From c25e078e31f325aa315ecd9e53b5248bcad88553 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 11:13:01 -0400 Subject: [PATCH 094/140] Fixed load error --- .../gamemode/roles/taskmaster/tasks/killweapon.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua index ad8cd1730..9b211af9c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua @@ -6,8 +6,6 @@ local math = math local MathRandom = math.random local TableInsert = table.insert -local TryTranslation = LANG.TryTranslation - local TASK = {} TASK.id = "killweapon" @@ -15,10 +13,10 @@ TASK.isKillTask = true TASK.Name = function(ply) local weapon = "Specific Weapon" - if ply.Task_KillWeaponClass then + if CLIENT and ply.Task_KillWeaponClass then for _, wep in ipairs(weapons.GetList()) do if ply.Task_KillWeaponClass == WEPS.GetClass(wep) then - weapon = TryTranslation(wep.PrintName) + weapon = LANG.TryTranslation(wep.PrintName) break end end @@ -28,10 +26,10 @@ end TASK.Description = function(ply) local weapon = "specific weapon" - if ply.Task_KillWeaponClass then + if CLIENT and ply.Task_KillWeaponClass then for _, wep in ipairs(weapons.GetList()) do if ply.Task_KillWeaponClass == WEPS.GetClass(wep) then - weapon = TryTranslation(wep.PrintName) + weapon = LANG.TryTranslation(wep.PrintName) break end end From 3f42e69893550d4a74f5baddcded970edbda96ba Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 14:30:17 -0400 Subject: [PATCH 095/140] Change to my updated luacheck fork for better continue support --- .github/workflows/luacheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml index c6d63daa1..1c4800a6f 100644 --- a/.github/workflows/luacheck.yml +++ b/.github/workflows/luacheck.yml @@ -17,7 +17,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - repository: 'impulsh/gluacheck' + repository: 'Malivil/gluacheck' path: 'gluacheck' - name: Setup ‘lua’ From d57b23f7972b693aabf9c0e2f220abcec2e3b40f Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 17:27:18 -0400 Subject: [PATCH 096/140] Revert overzealous luacheck changes --- .../terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua | 2 +- gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua | 2 +- .../gamemode/roles/taskmaster/tasks/crouchnearbody.lua | 2 +- .../gamemode/roles/taskmaster/tasks/damageeveryone.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/holstered.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua | 2 +- .../gamemode/roles/taskmaster/tasks/standonplayer.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/staylower.lua | 2 +- .../gamemode/roles/taskmaster/tasks/stayneartarget.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua index e7e9e2b05..2905c6346 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 2295891d5..27a73c0c5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index f71820921..6fecedc4a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -33,7 +33,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_crouchnearbody_time:GetInt() - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua index a94f27e87..841990b1a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua @@ -16,7 +16,7 @@ TASK.id = "damageeveryone" TASK.Name = function(ply) local damaged = ply.Task_DamageEveryoneDamaged or {} local total = 0 - local progress + local progress = 0 for _, p in PlayerIterator() do if ply == p then continue end if not p:Alive() or p:IsSpec() then continue end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 02e77de2e..926536737 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -31,7 +31,7 @@ TASK.Name = function(ply) local amount = taskmaster_healthunder_amount:GetInt() local time = taskmaster_healthunder_time:GetInt() - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index 42489e9c3..e12b7e3c9 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index 41efe9ccc..cc81d3d4f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) end local time = taskmaster_lookatplayer_time:GetInt() - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index 33c8070cd..a7f862893 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua index 1ea5e5f7b..36a2c8b08 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua @@ -30,7 +30,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index 98149eb65..fd6e77e14 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -30,7 +30,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index aa78977c8..986ce5136 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -30,7 +30,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index dc1cc8d66..5d55b9199 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -40,7 +40,7 @@ TASK.Name = function(ply) end local time = taskmaster_stayneartarget_time:GetInt() - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua index 532edbc1e..76577e1e1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -23,7 +23,7 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_takedamage_time:GetInt() - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else From 47730379dd095440d98fc2f3adf5f1ffde540d12 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 17:27:57 -0400 Subject: [PATCH 097/140] Fixed wrong variable usage --- gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua index fb36541bb..95c662960 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -25,7 +25,7 @@ TASK.Name = function(ply) if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times else - progress = ply.Task_CrowbarCount + progress = ply.Task_JumpCount end return name .. " (" .. progress .. "/" .. times .. ")" From 3b55777b129e2db6fa2a5e9a509ff2a8d9a72b45 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 17:29:44 -0400 Subject: [PATCH 098/140] Fixed error --- .../terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index 36c39a040..3b4eaad85 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -38,7 +38,7 @@ TASK.Name = function(ply) name = name .. "s" end - local progress + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time else From 042a94e37a2bcff3a51a3c325a8a3b7a191a608a Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 17:31:53 -0400 Subject: [PATCH 099/140] Added line pointing to the area in the Stay in Area task --- .../roles/taskmaster/tasks/stayinarea.lua | 78 +++++++++++++++---- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index 3b4eaad85..88e552de3 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -117,6 +117,60 @@ if SERVER then end if CLIENT then + + local function DrawLink(ply, targetPos) + if not ply.TaskmasterStayInAreaLinkEmitter then ply.TaskmasterStayInAreaLinkEmitter = ParticleEmitter(targetPos) end + if not ply.TaskmasterStayInAreaLinkNextPart then ply.TaskmasterStayInAreaLinkNextPart = CurTime() end + if not ply.TaskmasterStayInAreaLinkOffset then ply.TaskmasterStayInAreaLinkOffset = 0 end + local startPos = ply:GetPos() + Vector(0, 0, 30) + local endPos = targetPos + Vector(0, 0, 30) + local dir = endPos - startPos + dir = dir:GetNormalized() * 50 + if ply.TaskmasterStayInAreaLinkNextPart < CurTime() then + local pos = startPos + (dir * ply.TaskmasterStayInAreaLinkOffset) + -- Use DistToSqr as it's more efficient and this is called very frequently + -- 9000000 = 3000^2 + while startPos:DistToSqr(pos) <= 9000000 and startPos:DistToSqr(pos) <= startPos:DistToSqr(endPos) do + ply.TaskmasterStayInAreaLinkEmitter:SetPos(pos) + ply.TaskmasterStayInAreaLinkNextPart = CurTime() + 0.02 + local particle = ply.TaskmasterStayInAreaLinkEmitter:Add("particle/wisp.vmt", pos) + particle:SetVelocity(vector_origin) + particle:SetDieTime(0.25) + particle:SetStartAlpha(200) + particle:SetEndAlpha(0) + particle:SetStartSize(3) + particle:SetEndSize(2) + particle:SetRoll(MathRand(0, MathPi)) + particle:SetRollDelta(0) + local color = ROLE_COLORS[ROLE_TRAITOR] + particle:SetColor(color.r, color.g, color.b) + pos:Add(dir) + end + ply.TaskmasterStayInAreaLinkOffset = ply.TaskmasterStayInAreaLinkOffset + 0.04 + if ply.TaskmasterStayInAreaLinkOffset > 1 then + ply.TaskmasterStayInAreaLinkOffset = 0 + end + end + end + + local function RemoveLink(ply) + if ply.TaskmasterStayInAreaLinkEmitter then + ply.TaskmasterStayInAreaLinkEmitter:Finish() + ply.TaskmasterStayInAreaLinkEmitter = nil + ply.TaskmasterStayInAreaLinkNextPart = nil + ply.TaskmasterStayInAreaLinkOffset = nil + end + end + + local function RemoveRadius(ply) + if ply.TaskmasterRadiusEmitter then + ply.TaskmasterRadiusEmitter:Finish() + ply.TaskmasterRadiusEmitter = nil + ply.TaskmasterRadiusNextPart = nil + ply.TaskmasterRadiusDir = nil + end + end + net.Receive("TTT_Taskmaster_StayInArea_Assigned", function() local client = LocalPlayer() local sid64 = client:SteamID64() @@ -158,19 +212,21 @@ if CLIENT then end ply.TaskmasterRadiusDir = ply.TaskmasterRadiusDir + 0.02 end + + if distance > rangeSqr then + DrawLink(ply, pos) + else + RemoveLink(ply) + end shouldDraw = true end - if not shouldDraw and ply.TaskmasterRadiusEmitter then - ply.TaskmasterRadiusEmitter:Finish() - ply.TaskmasterRadiusEmitter = nil - ply.TaskmasterRadiusDir = nil - ply.TaskmasterRadiusNextPart = nil + if not shouldDraw then + RemoveRadius(ply) + RemoveLink(ply) end end) - -- TODO: Needs a visual to help locate the area if you can't immediately see it. Maybe copy the same thing we have for the shadow? - hook.Add("HUDPaint", "Taskmaster_StayInArea_HUDPaint_" .. sid64, function() if not client:IsActiveTaskmaster() then return end @@ -200,12 +256,8 @@ if CLIENT then hook.Remove("TTTPlayerAliveClientThink", "Taskmaster_StayInArea_TTTPlayerAliveClientThink_" .. sid64) hook.Remove("HUDPaint", "Taskmaster_StayInArea_HUDPaint_" .. sid64) - if client.TaskmasterRadiusEmitter then - client.TaskmasterRadiusEmitter:Finish() - client.TaskmasterRadiusEmitter = nil - client.TaskmasterRadiusDir = nil - client.TaskmasterRadiusNextPart = nil - end + RemoveRadius(client) + RemoveLink(client) end) end From 51fee1fb710eb5623f74d9142d67db772b6e7713 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 18:38:18 -0400 Subject: [PATCH 100/140] Fixed descriptions being cut off in the reroll list if they were too long --- .../terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index b5ed18a9a..77cda3e78 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -297,7 +297,11 @@ local function CreateTaskReroll(task, dscrollpanel) ddesc:SetPos(margin, (margin * 1.5) + nameHeight) ddesc:SetWidth(width - (margin * 2) - buttonWidth - scrollBarWidth) ddesc:SetWrap(true) - ddesc:SetAutoStretchVertical(true) + -- This text has wrapped to another line, increase the height so it doesn't get cut off + -- SizeToContents and SetAutoStretchVertical both weren't working, so here we are... + if #desc > 55 then + ddesc:SetHeight(ddesc:GetTall() + 20) + end local _, descHeight = ddesc:GetSize() local height = (margin * 2.5) + nameHeight + descHeight From 3634495c6962c6a9cd0b26f685d718bf8d3dc16a Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 21:15:54 -0400 Subject: [PATCH 101/140] Update to conform to updated gluacheck fork --- .github/workflows/luacheck.yml | 4 +- ...-dev-1.rockspec => luacheck-dev-1.rockspec | 43 +++++-------------- 2 files changed, 12 insertions(+), 35 deletions(-) rename gluacheck-dev-1.rockspec => luacheck-dev-1.rockspec (81%) diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml index 82944c75a..1c4800a6f 100644 --- a/.github/workflows/luacheck.yml +++ b/.github/workflows/luacheck.yml @@ -30,10 +30,10 @@ jobs: uses: leafo/gh-actions-luarocks@v4 - name: Setup dependencies - run: luarocks install --deps-only gluacheck-dev-1.rockspec + run: luarocks install --deps-only luacheck-dev-1.rockspec - name: Build ‘luacheck’ (bootstrap) - run: luarocks make gluacheck-dev-1.rockspec + run: luarocks make luacheck-dev-1.rockspec - name: Run ‘luacheck’ (dogfood) run: lua -e 'package.path="./gluacheck/src/?.lua;./gluacheck/src/?/init.lua;"..package.path' gluacheck/bin/luacheck.lua gamemodes scripts diff --git a/gluacheck-dev-1.rockspec b/luacheck-dev-1.rockspec similarity index 81% rename from gluacheck-dev-1.rockspec rename to luacheck-dev-1.rockspec index 3ae109c3b..22b7d4f48 100644 --- a/gluacheck-dev-1.rockspec +++ b/luacheck-dev-1.rockspec @@ -1,51 +1,29 @@ -local package_name = "gluacheck" -local package_version = "dev" -local rockspec_revision = "1" -local github_account_name = "Malivil" -local github_repo_name = package_name - -rockspec_format = "3.0" -package = package_name -version = package_version .. "-" .. rockspec_revision - +package = "luacheck" +version = "dev-1" source = { - url = "git+https://github.com/" .. github_account_name .. "/" .. github_repo_name .. ".git" + url = "git+https://github.com/mpeterv/luacheck.git" } - -if package_version == "dev" then source.branch = "master" else source.tag = "v" .. package_version end - description = { summary = "A static analyzer and a linter for Lua", detailed = [[ - Luacheck is a command-line tool for linting and static analysis of Lua - code. It is able to spot usage of undefined global variables, unused - local variables and a few other typical problems within Lua programs. - ]], - homepage = "https://github.com/lunarmodules/luacheck", +Luacheck is a command-line tool for linting and static analysis of Lua code. +It is able to spot usage of undefined global variables, unused local variables and +a few other typical problems within Lua programs. +]], + homepage = "https://github.com/mpeterv/luacheck", license = "MIT" } - dependencies = { - "lua >= 5.1", + "lua >= 5.1, < 5.4", "argparse >= 0.6.0", "luafilesystem >= 1.6.3" } - -test_dependencies = { - "busted", -} - -test = { - type = "busted", -} - build = { type = "builtin", modules = { luacheck = "gluacheck/src/luacheck/init.lua", ["luacheck.builtin_standards"] = "gluacheck/src/luacheck/builtin_standards/init.lua", ["luacheck.builtin_standards.love"] = "gluacheck/src/luacheck/builtin_standards/love.lua", - ["luacheck.builtin_standards.playdate"] = "gluacheck/src/luacheck/builtin_standards/playdate.lua", ["luacheck.builtin_standards.ngx"] = "gluacheck/src/luacheck/builtin_standards/ngx.lua", ["luacheck.cache"] = "gluacheck/src/luacheck/cache.lua", ["luacheck.check"] = "gluacheck/src/luacheck/check.lua", @@ -68,7 +46,6 @@ build = { ["luacheck.serializer"] = "gluacheck/src/luacheck/serializer.lua", ["luacheck.stages"] = "gluacheck/src/luacheck/stages/init.lua", ["luacheck.stages.detect_bad_whitespace"] = "gluacheck/src/luacheck/stages/detect_bad_whitespace.lua", - ["luacheck.stages.detect_compound_operators"] = "gluacheck/src/luacheck/stages/detect_compound_operators.lua", ["luacheck.stages.detect_cyclomatic_complexity"] = "gluacheck/src/luacheck/stages/detect_cyclomatic_complexity.lua", ["luacheck.stages.detect_empty_blocks"] = "gluacheck/src/luacheck/stages/detect_empty_blocks.lua", ["luacheck.stages.detect_empty_statements"] = "gluacheck/src/luacheck/stages/detect_empty_statements.lua", @@ -90,8 +67,8 @@ build = { ["luacheck.unicode_printability_boundaries"] = "gluacheck/src/luacheck/unicode_printability_boundaries.lua", ["luacheck.utils"] = "gluacheck/src/luacheck/utils.lua", ["luacheck.vendor.sha1"] = "gluacheck/src/luacheck/vendor/sha1/init.lua", - ["luacheck.vendor.sha1.bit32_ops"] = "gluacheck/src/luacheck/vendor/sha1/bit32_ops.lua", ["luacheck.vendor.sha1.bit_ops"] = "gluacheck/src/luacheck/vendor/sha1/bit_ops.lua", + ["luacheck.vendor.sha1.bit32_ops"] = "gluacheck/src/luacheck/vendor/sha1/bit32_ops.lua", ["luacheck.vendor.sha1.common"] = "gluacheck/src/luacheck/vendor/sha1/common.lua", ["luacheck.vendor.sha1.lua53_ops"] = "gluacheck/src/luacheck/vendor/sha1/lua53_ops.lua", ["luacheck.vendor.sha1.pure_lua_ops"] = "gluacheck/src/luacheck/vendor/sha1/pure_lua_ops.lua", From 77a33d12e786516cba8f944b7796c849b8551554 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 21:25:11 -0400 Subject: [PATCH 102/140] Cleanup --- .../gamemode/roles/taskmaster/tasks/getplayerkilled.lua | 1 - .../gamemode/roles/taskmaster/tasks/getplayertokill.lua | 1 - .../gamemode/roles/taskmaster/tasks/killdistant.lua | 5 +++-- .../gamemode/roles/taskmaster/tasks/killnearby.lua | 5 +++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua index e487f109e..08aab0207 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua @@ -20,7 +20,6 @@ TASK.Description = function(ply) if IsPlayer(ply.Task_GetPlayerKilledPlayer) then name = ply.Task_GetPlayerKilledPlayer:Nick() end - return "Get " .. name .. " killed by another player (not you)" end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua index 48997cc7a..73493e4b1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua @@ -20,7 +20,6 @@ TASK.Description = function(ply) if IsPlayer(ply.Task_GetPlayerToKillPlayer) then name = ply.Task_GetPlayerToKillPlayer:Nick() end - return "Get " .. name .. " to kill another player (not you)" end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua index a49844331..7da029861 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua @@ -5,6 +5,7 @@ local math = math local net = net local table = table +local MathCeil = math.ceil local MathCos = math.cos local MathPi = math.pi local MathRand = math.Rand @@ -41,7 +42,7 @@ TASK.Description = function(ply) description = description .. "s" end elseif unit == 2 then - local convertedRange = math.ceil(range * FEET_PER_METER) + local convertedRange = MathCeil(range * FEET_PER_METER) description = description .. convertedRange if convertedRange == 1 then description = description .. " foot" @@ -49,7 +50,7 @@ TASK.Description = function(ply) description = description .. " feet" end else - local convertedRange = math.ceil(range * UNITS_PER_METER) + local convertedRange = MathCeil(range * UNITS_PER_METER) description = description .. convertedRange .. " unit" if convertedRange ~= 1 then description = description .. "s" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua index ffd313b8c..9cd640f68 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua @@ -5,6 +5,7 @@ local math = math local net = net local table = table +local MathCeil = math.ceil local MathCos = math.cos local MathPi = math.pi local MathRand = math.Rand @@ -41,7 +42,7 @@ TASK.Description = function(ply) description = description .. "s" end elseif unit == 2 then - local convertedRange = math.ceil(range * FEET_PER_METER) + local convertedRange = MathCeil(range * FEET_PER_METER) description = description .. convertedRange if convertedRange == 1 then description = description .. " foot" @@ -49,7 +50,7 @@ TASK.Description = function(ply) description = description .. " feet" end else - local convertedRange = math.ceil(range * UNITS_PER_METER) + local convertedRange = MathCeil(range * UNITS_PER_METER) description = description .. convertedRange .. " unit" if convertedRange ~= 1 then description = description .. "s" From e77d4abeff93a2732c00f96fdd6a592f1d431d0b Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 17 Aug 2025 21:28:32 -0400 Subject: [PATCH 103/140] Fixed error rerolling when there are no available tasks remaining --- gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index e1796f433..cce794786 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -112,7 +112,7 @@ function plymeta:RerollTask(taskId, free) -- they only had tasks left that don't block a win condition. -- Rerolling one of those tasks no longer guarantees that they are winning, -- unless the newly rolled task ALSO does not block a win condition. - if not newTask.allowRoundEnd and self.taskmasterShouldWin then + if newTask and not newTask.allowRoundEnd and self.taskmasterShouldWin then self:SetProperty("taskmasterShouldWin", false) end From d1c86e9d972b406d2483bd3ed9c723b452211e3c Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 14:05:58 -0400 Subject: [PATCH 104/140] Added Task.Initialize optional method Moved translations into individual tasks Added placeholders for missing translations and features --- .../roles/taskmaster/cl_taskmaster.lua | 48 +++++++++++-------- .../gamemode/roles/taskmaster/shared.lua | 19 +++++++- .../gamemode/roles/taskmaster/taskmaster.lua | 10 ++++ .../roles/taskmaster/tasks/carrycorpse.lua | 4 ++ .../roles/taskmaster/tasks/crouch.lua | 4 ++ .../roles/taskmaster/tasks/crouchnearbody.lua | 4 ++ .../roles/taskmaster/tasks/healthunder.lua | 4 ++ .../roles/taskmaster/tasks/holstered.lua | 4 ++ .../roles/taskmaster/tasks/kill360.lua | 4 ++ .../roles/taskmaster/tasks/killdouble.lua | 4 ++ .../roles/taskmaster/tasks/lookatplayer.lua | 4 ++ .../roles/taskmaster/tasks/standonplayer.lua | 4 ++ .../roles/taskmaster/tasks/stayhidden.lua | 4 ++ .../roles/taskmaster/tasks/stayhigher.lua | 4 ++ .../roles/taskmaster/tasks/stayinarea.lua | 3 ++ .../roles/taskmaster/tasks/staylower.lua | 4 ++ .../roles/taskmaster/tasks/stayneartarget.lua | 4 ++ .../roles/taskmaster/tasks/takedamage.lua | 4 ++ 18 files changed, 116 insertions(+), 20 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 77cda3e78..90c85afde 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -1,3 +1,16 @@ +local CurTime = CurTime +local draw = draw +local hook = hook +local ipairs = ipairs +local math = math +local net = net +local player = player +local string = string +local surface = surface +local table = table +local util = util +local vgui = vgui + ------------------ -- TRANSLATIONS -- ------------------ @@ -15,31 +28,18 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() -- Bonus credit popup LANG.AddToLanguage("english", "taskmaster_credit_bonus", "{role}, you have been awarded {num} credit(s) for completing a task.") - -- Tasks - -- TODO: Create init method for tasks and move these into task files - LANG.AddToLanguage("english", "taskmaster_carrycorpse", "CARRY CORPSE - {time}") - LANG.AddToLanguage("english", "taskmaster_crouchnearbody", "CROUCH NEAR BODY - {time}") - LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") - LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") - LANG.AddToLanguage("english", "taskmaster_holstered", "STAY HOLSTERED - {time}") - LANG.AddToLanguage("english", "taskmaster_kill360", "KILL SOMEONE - {time}") - LANG.AddToLanguage("english", "taskmaster_killdouble", "KILL ANOTHER - {time}") - LANG.AddToLanguage("english", "taskmaster_lookatplayer", "KEEP LOOKING - {time}") - LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") - LANG.AddToLanguage("english", "taskmaster_stayaway", "STAY AWAY - {time}") - LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") - LANG.AddToLanguage("english", "taskmaster_stayhidden", "STAY HIDDEN - {time}") - LANG.AddToLanguage("english", "taskmaster_stayhigher", "STAY HIGHEST - {time}") - LANG.AddToLanguage("english", "taskmaster_stayinarea", "STAY IN AREA - {time}") - LANG.AddToLanguage("english", "taskmaster_staylower", "STAY LOWEST - {time}") - LANG.AddToLanguage("english", "taskmaster_takedamage", "AVOID DAMAGE - {time}") - -- Win conditions LANG.AddToLanguage("english", "win_taskmaster", "The {role} finished off their list!") LANG.AddToLanguage("english", "ev_win_taskmaster", "The methodical {role} has won the round!") -- HUD LANG.AddToLanguage("english", "taskmaster_hud", "You will lose in: {time}") + + -- Cheat Sheet + LANG.AddToLanguage("english", "cheatsheet_desc_taskmaster", "TODO") + + -- Popup + LANG.AddToLanguage("english", "info_popup_taskmaster", [[You are {role}! TODO]]) end) ------------- @@ -452,4 +452,14 @@ hook.Add("TTTEquipmentTabs", "Taskmaster_TTTEquipmentTabs", function(dsheet, dfr dsheet:AddSheet(LANG.GetTranslation("taskmaster_reroll_name"), dscrollpanel, "icon16/table_edit.png", false, false, LANG.GetTranslation("equip_tooltip_taskmaster_reroll")) return true end +end) + +-------------- +-- TUTORIAL -- +-------------- + +hook.Add("TTTTutorialRoleText", "Taskmaster_TTTTutorialRoleText", function(role, titleLabel) + --if role == ROLE_TASKMASTER then + -- -- TODO + --end end) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua index f7a5fd7ea..7d13bb683 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -1,5 +1,9 @@ AddCSLuaFile() +local hook = hook +local ipairs = ipairs +local table = table + -- Task Features TASKMASTER_TF_TARGETID_PLAYERICON = 0 TASKMASTER_TF_TARGETID_PLAYERTEXT = 1 @@ -80,4 +84,17 @@ local function AddTaskFiles(root) end AddTaskFiles("terrortown/gamemode/roles/taskmaster/tasks/") -- Internal tasks -AddTaskFiles("taskmastertasks/") -- External tasks \ No newline at end of file +AddTaskFiles("taskmastertasks/") -- External tasks + +hook.Add("Initialize", "Taskmaster_Task_Initialize", function() + for _, t in pairs(TASKMASTER.killTasks) do + if t.Initialize then + t.Initialize() + end + end + for _, t in pairs(TASKMASTER.miscTasks) do + if t.Initialize then + t.Initialize() + end + end +end) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index cce794786..f9abe4a5d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -1,5 +1,15 @@ AddCSLuaFile() +local CurTime = CurTime +local hook = hook +local ipairs = ipairs +local math = math +local net = net +local pairs = pairs +local player = player +local table = table +local util = util + local plymeta = FindMetaTable("Player") util.AddNetworkString("TTT_TaskmasterRerollTask") diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua index 2905c6346..ca2446b52 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -100,6 +100,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_carrycorpse", "CARRY CORPSE - {time}") + end + net.Receive("TTT_Taskmaster_CarryCorpse_Assigned", function() local client = LocalPlayer() local sid64 = client:SteamID64() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 27a73c0c5..4b01626b2 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -98,6 +98,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_crouch", "STAY CROUCHING - {time}") + end + net.Receive("TTT_Taskmaster_Crouch_Assigned", function() local client = LocalPlayer() local time = taskmaster_crouch_time:GetInt() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index 6fecedc4a..ee1479863 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -144,6 +144,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_crouchnearbody", "CROUCH NEAR BODY - {time}") + end + net.Receive("TTT_Taskmaster_CrouchNearBody_Assigned", function() local client = LocalPlayer() local sid64 = client:SteamID64() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 926536737..feabfd965 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -103,6 +103,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_healthunder", "DON'T HEAL - {time}") + end + net.Receive("TTT_Taskmaster_HealthUnder_Assigned", function() local client = LocalPlayer() local time = taskmaster_healthunder_time:GetInt() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index e12b7e3c9..bddc89da9 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -103,6 +103,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_holstered", "STAY HOLSTERED - {time}") + end + net.Receive("TTT_Taskmaster_Holstered_Assigned", function() local client = LocalPlayer() local sid64 = client:SteamID64() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua index fa3b17adf..4a2f1916d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua @@ -133,6 +133,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_kill360", "KILL SOMEONE - {time}") + end + net.Receive("TTT_Taskmaster_Kill360_Assigned", function() local client = LocalPlayer() local time = taskmaster_kill360_time:GetInt() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua index 27bf51538..bd049cd63 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua @@ -74,6 +74,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_killdouble", "KILL ANOTHER - {time}") + end + net.Receive("TTT_Taskmaster_KillDouble_Assigned", function() local client = LocalPlayer() local time = taskmaster_killdouble_time:GetInt() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index cc81d3d4f..67113bc87 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -156,6 +156,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_lookatplayer", "KEEP LOOKING - {time}") + end + net.Receive("TTT_Taskmaster_LookAtPlayer_Assigned", function() local client = LocalPlayer() local sid64 = client:SteamID64() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index a7f862893..e60116a64 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -98,6 +98,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_standonplayer", "STAY ON PLAYER - {time}") + end + net.Receive("TTT_Taskmaster_StandOnPlayer_Assigned", function() local client = LocalPlayer() local sid64 = client:SteamID64() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua index 36a2c8b08..d0c49baa0 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua @@ -119,6 +119,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_stayhidden", "STAY HIDDEN - {time}") + end + net.Receive("TTT_Taskmaster_StayHidden_Assigned", function() local client = LocalPlayer() local time = taskmaster_stayhidden_time:GetInt() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index fd6e77e14..08ef21d00 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -122,6 +122,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_stayhigher", "STAY HIGHEST - {time}") + end + net.Receive("TTT_Taskmaster_StayHigher_Assigned", function() local client = LocalPlayer() local sid64 = client:SteamID64() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index 88e552de3..c637c7322 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -117,6 +117,9 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_stayinarea", "STAY IN AREA - {time}") + end local function DrawLink(ply, targetPos) if not ply.TaskmasterStayInAreaLinkEmitter then ply.TaskmasterStayInAreaLinkEmitter = ParticleEmitter(targetPos) end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index 986ce5136..90bb888e0 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -122,6 +122,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_staylower", "STAY LOWEST - {time}") + end + net.Receive("TTT_Taskmaster_StayLower_Assigned", function() local client = LocalPlayer() local sid64 = client:SteamID64() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 5d55b9199..2bc86ac3a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -194,6 +194,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_stayneartarget", "STAY NEAR TARGET - {time}") + end + net.Receive("TTT_Taskmaster_StayNearTarget_Assigned", function() local client = LocalPlayer() local sid64 = client:SteamID64() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua index 76577e1e1..ce4833fad 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -97,6 +97,10 @@ if SERVER then end if CLIENT then + TASK.Initialize = function() + LANG.AddToLanguage("english", "taskmaster_takedamage", "AVOID DAMAGE - {time}") + end + net.Receive("TTT_Taskmaster_TakeDamage_Assigned", function() local client = LocalPlayer() local time = taskmaster_takedamage_time:GetInt() From c519767329f6e19b452b3b2fd1e85320b8ccb117 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 14:07:57 -0400 Subject: [PATCH 105/140] Cleanup --- gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua index 7d13bb683..bb7271272 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -29,7 +29,6 @@ CreateConVar("ttt_taskmaster_wins_with_others", "1", FCVAR_REPLICATED, "If the T CreateConVar("ttt_taskmaster_is_passive", "0", FCVAR_REPLICATED, "Whether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if 'ttt_taskmaster_wins_with_others' is enabled)") ROLE_CONVARS[ROLE_TASKMASTER] = {} - table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_kill_tasks", type = ROLE_CONVAR_TYPE_NUM, From 62fa5dbe4b19ae7351f0ee23efc8c5b913783be5 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 14:48:17 -0400 Subject: [PATCH 106/140] Added tutorial, popup, and cheatsheet text --- .../roles/taskmaster/cl_taskmaster.lua | 38 ++++++++++++++++--- .../roles/taskmaster/tasks/carrycorpse.lua | 2 + .../gamemode/roles/taskmaster/tasks/chat.lua | 2 + .../roles/taskmaster/tasks/crouch.lua | 2 + .../roles/taskmaster/tasks/crouchnearbody.lua | 5 ++- .../roles/taskmaster/tasks/crowbar.lua | 2 + .../roles/taskmaster/tasks/damageeveryone.lua | 6 ++- .../roles/taskmaster/tasks/dodamage.lua | 5 ++- .../roles/taskmaster/tasks/firebullets.lua | 2 + .../taskmaster/tasks/getplayerkilled.lua | 4 +- .../taskmaster/tasks/getplayertokill.lua | 4 +- .../roles/taskmaster/tasks/healthunder.lua | 5 ++- .../roles/taskmaster/tasks/holstered.lua | 2 + .../gamemode/roles/taskmaster/tasks/jump.lua | 2 + .../roles/taskmaster/tasks/killtarget.lua | 4 +- .../roles/taskmaster/tasks/killweapon.lua | 4 +- .../roles/taskmaster/tasks/lookatplayer.lua | 14 ++++--- .../roles/taskmaster/tasks/standonplayer.lua | 2 + .../roles/taskmaster/tasks/stayhidden.lua | 2 + .../roles/taskmaster/tasks/stayhigher.lua | 2 + .../roles/taskmaster/tasks/stayinarea.lua | 2 + .../roles/taskmaster/tasks/staylower.lua | 2 + .../roles/taskmaster/tasks/stayneartarget.lua | 14 ++++--- .../roles/taskmaster/tasks/takedamage.lua | 6 ++- .../roles/taskmaster/tasks/weaponpickups.lua | 2 + 25 files changed, 107 insertions(+), 28 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 90c85afde..3d45829b1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -36,10 +36,13 @@ hook.Add("Initialize", "Taskmaster_Translations_Initialize", function() LANG.AddToLanguage("english", "taskmaster_hud", "You will lose in: {time}") -- Cheat Sheet - LANG.AddToLanguage("english", "cheatsheet_desc_taskmaster", "TODO") + LANG.AddToLanguage("english", "cheatsheet_desc_taskmaster", "Has a list of tasks that they must complete before the end of the round.") -- Popup - LANG.AddToLanguage("english", "info_popup_taskmaster", [[You are {role}! TODO]]) + LANG.AddToLanguage("english", "info_popup_taskmaster", [[You are {role}! Complete your tasks +before the end of the round! + +Press {menukey} to pay a credit to replace a task you don't like!]]) end) ------------- @@ -459,7 +462,32 @@ end) -------------- hook.Add("TTTTutorialRoleText", "Taskmaster_TTTTutorialRoleText", function(role, titleLabel) - --if role == ROLE_TASKMASTER then - -- -- TODO - --end + if role == ROLE_TASKMASTER then + local roleColor = GetRoleTeamColor(ROLE_TEAM_INDEPENDENT) + local html = "The " .. ROLE_STRINGS[ROLE_TASKMASTER] .. " is an independent role whose goal is to complete a series of tasks before the round ends." + + -- Reroll + html = html .. "Undesired or uncompletable tasks can be rerolled by spending a credit in the equipment menu (press '" .. Key("+menu_context", "C") .. "')" + + -- Show all enabled tasks + local tasks = {} + for _, t in pairs(TASKMASTER.killTasks) do + if not t.Enabled() then continue end + table.insert(tasks, { name = t.Name(), desc = t.Description() }) + end + for _, t in pairs(TASKMASTER.miscTasks) do + if not t.Enabled() then continue end + table.insert(tasks, { name = t.Name(), desc = t.Description() }) + end + table.SortByMember(tasks, "name", true) + + html = html .. "Available tasks:" + html = html .. "
      " + for _, t in ipairs(tasks) do + html = html .. "
    • " .. t.name .. " - " .. t.desc .. "
    • " + end + html = html .. "
    " + + return html + end end) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua index ca2446b52..87032b2af 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -28,6 +28,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua index d95c756ce..4262b2474 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua @@ -21,6 +21,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 4b01626b2..e0a9d9fe3 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -28,6 +28,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index ee1479863..6829fde8c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -32,6 +32,9 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_crouchnearbody_time:GetInt() + local name = "Crouch Near a Body" + + if not ply then return name end local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then @@ -43,7 +46,7 @@ TASK.Name = function(ply) end end - return "Crouch Near a Body (" .. progress .. "/" .. time .. ")" + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua index 0f90cd1b3..5fb2a0e38 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua @@ -21,6 +21,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua index 841990b1a..723ea256d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua @@ -14,6 +14,10 @@ local TASK = {} TASK.id = "damageeveryone" TASK.Name = function(ply) + local name = "Damage Everyone Else" + + if not ply then return name end + local damaged = ply.Task_DamageEveryoneDamaged or {} local total = 0 local progress = 0 @@ -30,7 +34,7 @@ TASK.Name = function(ply) progress = total end - return "Damage Everyone Else (" .. progress .. "/" .. total .. ")" + return name .. " (" .. progress .. "/" .. total .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua index a85c327a0..b597c263c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua @@ -16,6 +16,9 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local amount = taskmaster_dodamage_amount:GetInt() + local name = "Deal " .. amount .. " Damage" + + if not ply then return name end local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then @@ -24,7 +27,7 @@ TASK.Name = function(ply) progress = ply.Task_DoDamageTotal end - return "Deal " .. amount .. " Damage (" .. progress .. "/" .. amount .. ")" + return name .. " (" .. progress .. "/" .. amount .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua index 4e097f009..7adc4b804 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua @@ -21,6 +21,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua index 08aab0207..90af4fffb 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayerkilled.lua @@ -9,7 +9,7 @@ TASK.id = "getplayerkilled" TASK.Name = function(ply) local name = "Target" - if IsPlayer(ply.Task_GetPlayerKilledPlayer) then + if ply and IsPlayer(ply.Task_GetPlayerKilledPlayer) then name = ply.Task_GetPlayerKilledPlayer:Nick() end return "Get " .. name .. " Killed" @@ -17,7 +17,7 @@ end TASK.Description = function(ply) local name = "target" - if IsPlayer(ply.Task_GetPlayerKilledPlayer) then + if ply and IsPlayer(ply.Task_GetPlayerKilledPlayer) then name = ply.Task_GetPlayerKilledPlayer:Nick() end return "Get " .. name .. " killed by another player (not you)" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua index 73493e4b1..21f22dae9 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/getplayertokill.lua @@ -9,7 +9,7 @@ TASK.id = "getplayertokill" TASK.Name = function(ply) local name = "Target" - if IsPlayer(ply.Task_GetPlayerToKillPlayer) then + if ply and IsPlayer(ply.Task_GetPlayerToKillPlayer) then name = ply.Task_GetPlayerToKillPlayer:Nick() end return "Get " .. name .. " to Kill" @@ -17,7 +17,7 @@ end TASK.Description = function(ply) local name = "target" - if IsPlayer(ply.Task_GetPlayerToKillPlayer) then + if ply and IsPlayer(ply.Task_GetPlayerToKillPlayer) then name = ply.Task_GetPlayerToKillPlayer:Nick() end return "Get " .. name .. " to kill another player (not you)" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index feabfd965..da83974fd 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -30,6 +30,9 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local amount = taskmaster_healthunder_amount:GetInt() local time = taskmaster_healthunder_time:GetInt() + local name = "Survive Under " .. amount .. " Health" + + if not ply then return name end local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then @@ -41,7 +44,7 @@ TASK.Name = function(ply) end end - return "Survive Under " .. amount .. " Health (" .. progress .. "/" .. time .. ")" + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index bddc89da9..9c7c085d5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -28,6 +28,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua index 95c662960..f38dd4abb 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -21,6 +21,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = times diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua index 61efc03dc..25a14f20c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua @@ -10,7 +10,7 @@ TASK.isKillTask = true TASK.Name = function(ply) local name = "Target" - if IsPlayer(ply.Task_KillTargetPlayer) then + if ply and IsPlayer(ply.Task_KillTargetPlayer) then name = ply.Task_KillTargetPlayer:Nick() end return "Kill " .. name @@ -18,7 +18,7 @@ end TASK.Description = function(ply) local name = "target" - if IsPlayer(ply.Task_KillTargetPlayer) then + if ply and IsPlayer(ply.Task_KillTargetPlayer) then name = ply.Task_KillTargetPlayer:Nick() end return "Kill " .. name .. " by any means necessary" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua index 9b211af9c..7ed1549e7 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua @@ -13,7 +13,7 @@ TASK.isKillTask = true TASK.Name = function(ply) local weapon = "Specific Weapon" - if CLIENT and ply.Task_KillWeaponClass then + if CLIENT and ply and ply.Task_KillWeaponClass then for _, wep in ipairs(weapons.GetList()) do if ply.Task_KillWeaponClass == WEPS.GetClass(wep) then weapon = LANG.TryTranslation(wep.PrintName) @@ -26,7 +26,7 @@ end TASK.Description = function(ply) local weapon = "specific weapon" - if CLIENT and ply.Task_KillWeaponClass then + if CLIENT and ply and ply.Task_KillWeaponClass then for _, wep in ipairs(weapons.GetList()) do if ply.Task_KillWeaponClass == WEPS.GetClass(wep) then weapon = LANG.TryTranslation(wep.PrintName) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index 67113bc87..3f76291d3 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -22,11 +22,15 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - local name = "Target" - if IsPlayer(ply.Task_LookAtPlayerPlayer) then - name = ply.Task_LookAtPlayerPlayer:Nick() + local name = "Look at " + if ply and IsPlayer(ply.Task_LookAtPlayerPlayer) then + name = name .. ply.Task_LookAtPlayerPlayer:Nick() + else + name = name .. "Target" end + if not ply then return name end + local time = taskmaster_lookatplayer_time:GetInt() local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then @@ -38,12 +42,12 @@ TASK.Name = function(ply) end end - return "Look at " .. name .. " (" .. progress .. "/" .. time .. ")" + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) local name = "target" - if IsPlayer(ply.Task_LookAtPlayerPlayer) then + if ply and IsPlayer(ply.Task_LookAtPlayerPlayer) then name = ply.Task_LookAtPlayerPlayer:Nick() end local time = taskmaster_lookatplayer_time:GetInt() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index e60116a64..37c3f2c44 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -28,6 +28,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua index d0c49baa0..b81666efa 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua @@ -30,6 +30,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index 08ef21d00..48cd7b8a9 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -30,6 +30,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index c637c7322..96f767a62 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -38,6 +38,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index 90bb888e0..8f55bd655 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -30,6 +30,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 2bc86ac3a..51d7b1117 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -34,11 +34,15 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - local name = "Target" - if IsPlayer(ply.Task_StayNearTargetPlayer) then - name = ply.Task_StayNearTargetPlayer:Nick() + local name = "Stay Near " + if ply and IsPlayer(ply.Task_StayNearTargetPlayer) then + name = name .. ply.Task_StayNearTargetPlayer:Nick() + else + name = name .. "Target" end + if not ply then return name end + local time = taskmaster_stayneartarget_time:GetInt() local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then @@ -50,7 +54,7 @@ TASK.Name = function(ply) end end - return "Stay Near " .. name .. " (" .. progress .. "/" .. time .. ")" + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) @@ -84,7 +88,7 @@ TASK.Description = function(ply) end local name = "target" - if IsPlayer(ply.Task_StayNearTargetPlayer) then + if ply and IsPlayer(ply.Task_StayNearTargetPlayer) then name = ply.Task_StayNearTargetPlayer:Nick() end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua index ce4833fad..d99f16231 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -23,6 +23,10 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local time = taskmaster_takedamage_time:GetInt() + local name = "Take Damage and Survive" + + if not ply then return name end + local progress = 0 if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = time @@ -33,7 +37,7 @@ TASK.Name = function(ply) end end - return "Take Damage and Survive (" .. progress .. "/" .. time .. ")" + return name .. " (" .. progress .. "/" .. time .. ")" end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua index 7a04f78c8..cc72c9aad 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua @@ -21,6 +21,8 @@ TASK.Name = function(ply) name = name .. "s" end + if not ply then return name end + local progress if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then progress = count From ce2320f3ef4baa5819d34db16e98319ca5eda55f Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 15:00:44 -0400 Subject: [PATCH 107/140] Changed Taskmaster to see jesters and MIA in case they need to kill everyone to win --- gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua index bb7271272..1bdef54d5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -10,6 +10,9 @@ TASKMASTER_TF_TARGETID_PLAYERTEXT = 1 TASKMASTER_TF_PROGRESSBAR = 2 TASKMASTER_TF_PARTICLERADIUS = 3 +-- Initialize role features +ROLE_CAN_SEE_JESTERS[ROLE_TASKMASTER] = true +ROLE_CAN_SEE_MIA[ROLE_TASKMASTER] = true ROLE_STARTING_CREDITS[ROLE_TASKMASTER] = 1 ROLE_HAS_PASSIVE_WIN[ROLE_TASKMASTER] = false cvars.AddChangeCallback("ttt_taskmaster_is_passive", function(_, _, newValue) From b6860f741fdae90ab4ba7260d562efb2b5a35c05 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 15:06:56 -0400 Subject: [PATCH 108/140] Expanded Taskmaster tutorial --- .../roles/taskmaster/cl_taskmaster.lua | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 3d45829b1..b16d915f7 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -50,6 +50,9 @@ end) ------------- local taskmaster_wins_with_others = GetConVar("ttt_taskmaster_wins_with_others") +local taskmaster_kill_tasks = GetConVar("ttt_taskmaster_kill_tasks") +local taskmaster_misc_tasks = GetConVar("ttt_taskmaster_misc_tasks") +local taskmaster_win_block_length = GetConVar("ttt_taskmaster_win_block_length") local xOffset = CreateClientConVar("ttt_taskmaster_list_x_pos", "10", true, false, "The X (horizontal) position of the Taskmaster's task list HUD", 0, ScrW()) local yOffset = CreateClientConVar("ttt_taskmaster_list_y_pos", "10", true, false, "The Y (vertical) position of the Taskmaster's task list HUD", 0, ScrH()) @@ -466,6 +469,36 @@ hook.Add("TTTTutorialRoleText", "Taskmaster_TTTTutorialRoleText", function(role, local roleColor = GetRoleTeamColor(ROLE_TEAM_INDEPENDENT) local html = "The " .. ROLE_STRINGS[ROLE_TASKMASTER] .. " is an independent role whose goal is to complete a series of tasks before the round ends." + -- Task counts + local kill_count = taskmaster_kill_tasks:GetInt() + local kill_plural = "" + if kill_count ~= 1 then + kill_plural = "s" + end + local misc_count = taskmaster_misc_tasks:GetInt() + local misc_plural = "" + if misc_count ~= 1 then + misc_plural = "s" + end + html = html .. "The " .. ROLE_STRINGS[ROLE_TASKMASTER] .. " must complete " .. kill_count .. " task" .. kill_plural .. " that require killing a player (or multiple) and " .. misc_count .. " other task" .. misc_plural .. "." + + -- Win condition + if taskmaster_wins_with_others:GetBool() then + html = html .. "If another team wins after the " .. ROLE_STRINGS[ROLE_TASKMASTER] .. " finishes their tasks, they will share the win." + else + html = html .. "In addition to completing all tasks, the " .. ROLE_STRINGS[ROLE_TASKMASTER] .. " must also eliminate all other players." + end + + -- Win block + local block_length = taskmaster_win_block_length:GetInt() + if block_length > 0 then + local plural = "" + if block_length ~= 1 then + plural = "s" + end + html = html .. "If the " .. ROLE_STRINGS[ROLE_TASKMASTER] .. " has not completed all tasks by the time another team wins, the round will be extended by " .. block_length .. " second" .. plural .. ", giving them extra time to finish." + end + -- Reroll html = html .. "Undesired or uncompletable tasks can be rerolled by spending a credit in the equipment menu (press '" .. Key("+menu_context", "C") .. "')" From ef75c38ae93fa27a65b4bed80fff6f0e08f75c73 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 15:17:03 -0400 Subject: [PATCH 109/140] Standardize naming on PascalCase --- .../roles/taskmaster/cl_taskmaster.lua | 30 +++--- .../gamemode/roles/taskmaster/shared.lua | 12 +-- .../gamemode/roles/taskmaster/taskmaster.lua | 91 ++++++++++--------- .../roles/taskmaster/tasks/carrycorpse.lua | 2 +- .../gamemode/roles/taskmaster/tasks/chat.lua | 2 +- .../roles/taskmaster/tasks/crouch.lua | 2 +- .../roles/taskmaster/tasks/crouchnearbody.lua | 2 +- .../roles/taskmaster/tasks/crowbar.lua | 2 +- .../roles/taskmaster/tasks/damageeveryone.lua | 2 +- .../roles/taskmaster/tasks/dodamage.lua | 2 +- .../roles/taskmaster/tasks/firebullets.lua | 2 +- .../roles/taskmaster/tasks/healthunder.lua | 2 +- .../roles/taskmaster/tasks/holstered.lua | 2 +- .../gamemode/roles/taskmaster/tasks/jump.lua | 2 +- .../roles/taskmaster/tasks/kill360.lua | 2 +- .../roles/taskmaster/tasks/killaiming.lua | 2 +- .../roles/taskmaster/tasks/killairborne.lua | 2 +- .../roles/taskmaster/tasks/killbehind.lua | 2 +- .../roles/taskmaster/tasks/killcrowbar.lua | 2 +- .../roles/taskmaster/tasks/killdistant.lua | 2 +- .../roles/taskmaster/tasks/killdouble.lua | 2 +- .../roles/taskmaster/tasks/killheadshot.lua | 2 +- .../roles/taskmaster/tasks/killlastbullet.lua | 2 +- .../roles/taskmaster/tasks/killmidair.lua | 2 +- .../roles/taskmaster/tasks/killnearby.lua | 2 +- .../roles/taskmaster/tasks/killonehit.lua | 2 +- .../roles/taskmaster/tasks/killpistol.lua | 2 +- .../roles/taskmaster/tasks/killretaliate.lua | 2 +- .../roles/taskmaster/tasks/killtarget.lua | 2 +- .../roles/taskmaster/tasks/killweapon.lua | 2 +- .../roles/taskmaster/tasks/lookatplayer.lua | 2 +- .../roles/taskmaster/tasks/standonplayer.lua | 2 +- .../roles/taskmaster/tasks/stayhidden.lua | 2 +- .../roles/taskmaster/tasks/stayhigher.lua | 2 +- .../roles/taskmaster/tasks/stayinarea.lua | 2 +- .../roles/taskmaster/tasks/staylower.lua | 2 +- .../roles/taskmaster/tasks/stayneartarget.lua | 2 +- .../roles/taskmaster/tasks/survive.lua | 2 +- .../roles/taskmaster/tasks/takedamage.lua | 2 +- .../roles/taskmaster/tasks/weaponpickups.lua | 2 +- 40 files changed, 104 insertions(+), 103 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index b16d915f7..c6e36732a 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -82,7 +82,7 @@ hook.Add("TTTScoringSecondaryWins", "Taskmaster_TTTScoringSecondaryWins", functi for _, ply in player.Iterator() do if not ply:IsTaskmaster() then continue end - if ply.taskmasterShouldWin then + if ply.TaskmasterShouldWin then table.insert(secondary_wins, ROLE_TASKMASTER) break end @@ -135,7 +135,7 @@ local function DrawTask(task, height, isShadow) progress = task.Progress(client) desc = desc .. " " .. progress end - local completed = table.HasValue(client.taskmasterCompletedTasks, task.id) + local completed = table.HasValue(client.TaskmasterCompletedTasks, task.id) -- The checkboxes don't naturally align with the text, thus the '+2's everywhere to make it line up surface.DrawRect(xPos + margin + 2 + offset, height + 2 + offset, 2, checkboxSize) @@ -228,14 +228,14 @@ hook.Add("HUDPaint", "Taskmaster_HUDPaint", function() local height = yOffset:GetInt() + margin - for _, id in ipairs(client.taskmasterKillTasks) do - DrawTask(TASKMASTER.killTasks[id], height, true) - height = DrawTask(TASKMASTER.killTasks[id], height) + for _, id in ipairs(client.TaskmasterKillTasks) do + DrawTask(TASKMASTER.KillTasks[id], height, true) + height = DrawTask(TASKMASTER.KillTasks[id], height) end - for _, id in ipairs(client.taskmasterMiscTasks) do - DrawTask(TASKMASTER.miscTasks[id], height, true) - height = DrawTask(TASKMASTER.miscTasks[id], height) + for _, id in ipairs(client.TaskmasterMiscTasks) do + DrawTask(TASKMASTER.MiscTasks[id], height, true) + height = DrawTask(TASKMASTER.MiscTasks[id], height) end maxHeight = height - yOffset:GetInt() @@ -275,7 +275,7 @@ local scrollBarWidth = 15 local creditsIconSize = 32 local function CreateTaskReroll(task, dscrollpanel) - if table.HasValue(client.taskmasterCompletedTasks, task.id) then return false end + if table.HasValue(client.TaskmasterCompletedTasks, task.id) then return false end local name = task.Name(client) local desc = task.Description(client) @@ -353,16 +353,16 @@ local function CreateTaskList(dscrollpanel) dtasks = {} dtasksHeight = 0 - for _, id in ipairs(client.taskmasterKillTasks) do - local dtask, dline = CreateTaskReroll(TASKMASTER.killTasks[id], dscrollpanel) + for _, id in ipairs(client.TaskmasterKillTasks) do + local dtask, dline = CreateTaskReroll(TASKMASTER.KillTasks[id], dscrollpanel) if dtask then table.insert(dtasks, dtask) table.insert(dtasks, dline) end end - for _, id in ipairs(client.taskmasterMiscTasks) do - local dtask, dline = CreateTaskReroll(TASKMASTER.miscTasks[id], dscrollpanel) + for _, id in ipairs(client.TaskmasterMiscTasks) do + local dtask, dline = CreateTaskReroll(TASKMASTER.MiscTasks[id], dscrollpanel) if dtask then table.insert(dtasks, dtask) table.insert(dtasks, dline) @@ -504,11 +504,11 @@ hook.Add("TTTTutorialRoleText", "Taskmaster_TTTTutorialRoleText", function(role, -- Show all enabled tasks local tasks = {} - for _, t in pairs(TASKMASTER.killTasks) do + for _, t in pairs(TASKMASTER.KillTasks) do if not t.Enabled() then continue end table.insert(tasks, { name = t.Name(), desc = t.Description() }) end - for _, t in pairs(TASKMASTER.miscTasks) do + for _, t in pairs(TASKMASTER.MiscTasks) do if not t.Enabled() then continue end table.insert(tasks, { name = t.Name(), desc = t.Description() }) end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua index 1bdef54d5..55b376a7c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -55,7 +55,7 @@ TASKMASTER = { function TASKMASTER.RegisterTask(task) task.id = task.id or task.Id or task.ID - if TASKMASTER.killTasks[task.id] or TASKMASTER.miscTasks[task.id] then + if TASKMASTER.KillTasks[task.id] or TASKMASTER.MiscTasks[task.id] then ErrorNoHalt("ERROR: Attempted to register Taskmaster task '" .. task.id .. "' with duplicate task ID.\n") return end @@ -70,10 +70,10 @@ function TASKMASTER.RegisterTask(task) type = ROLE_CONVAR_TYPE_BOOL }) - if task.isKillTask then - TASKMASTER.killTasks[task.id] = task + if task.IsKillTask then + TASKMASTER.KillTasks[task.id] = task else - TASKMASTER.miscTasks[task.id] = task + TASKMASTER.MiscTasks[task.id] = task end end @@ -89,12 +89,12 @@ AddTaskFiles("terrortown/gamemode/roles/taskmaster/tasks/") -- Internal tasks AddTaskFiles("taskmastertasks/") -- External tasks hook.Add("Initialize", "Taskmaster_Task_Initialize", function() - for _, t in pairs(TASKMASTER.killTasks) do + for _, t in pairs(TASKMASTER.KillTasks) do if t.Initialize then t.Initialize() end end - for _, t in pairs(TASKMASTER.miscTasks) do + for _, t in pairs(TASKMASTER.MiscTasks) do if t.Initialize then t.Initialize() end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index f9abe4a5d..fb4905b06 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -33,14 +33,14 @@ local taskmaster_wins_with_others = GetConVar("ttt_taskmaster_wins_with_others") function plymeta:AssignTask(isKillTask, index) if not self:IsTaskmaster() then return end - local taskList = isKillTask and TASKMASTER.killTasks or TASKMASTER.miscTasks + local taskList = isKillTask and TASKMASTER.KillTasks or TASKMASTER.MiscTasks local taskIds = table.GetKeys(taskList) - local activeTasksName = isKillTask and "taskmasterKillTasks" or "taskmasterMiscTasks" + local activeTasksName = isKillTask and "TaskmasterKillTasks" or "TaskmasterMiscTasks" for _, activeId in ipairs(self[activeTasksName]) do table.RemoveByValue(taskIds, activeId) end - for _, rerolledId in ipairs(self.taskmasterRerolledTasks) do + for _, rerolledId in ipairs(self.TaskmasterRerolledTasks) do table.RemoveByValue(taskIds, rerolledId) end table.Shuffle(taskIds) @@ -49,9 +49,9 @@ function plymeta:AssignTask(isKillTask, index) if taskList[id].CanAssignTask(self) then local blockedByFeature = false for _, feature in pairs(taskList[id].RequiredFeatures) do - for _, killTaskId in pairs(self.taskmasterKillTasks) do - if not table.HasValue(self.taskmasterCompletedTasks, killTaskId) and not table.HasValue(self.taskmasterRerolledTasks, killTaskId) then - if table.HasValue(TASKMASTER.killTasks[killTaskId].RequiredFeatures, feature) then + for _, killTaskId in pairs(self.TaskmasterKillTasks) do + if not table.HasValue(self.TaskmasterCompletedTasks, killTaskId) and not table.HasValue(self.TaskmasterRerolledTasks, killTaskId) then + if table.HasValue(TASKMASTER.KillTasks[killTaskId].RequiredFeatures, feature) then blockedByFeature = true break end @@ -59,9 +59,9 @@ function plymeta:AssignTask(isKillTask, index) end if blockedByFeature then break end - for _, miscTaskId in pairs(self.taskmasterMiscTasks) do - if not table.HasValue(self.taskmasterCompletedTasks, miscTaskId) and not table.HasValue(self.taskmasterRerolledTasks, miscTaskId) then - if table.HasValue(TASKMASTER.miscTasks[miscTaskId].RequiredFeatures, feature) then + for _, miscTaskId in pairs(self.TaskmasterMiscTasks) do + if not table.HasValue(self.TaskmasterCompletedTasks, miscTaskId) and not table.HasValue(self.TaskmasterRerolledTasks, miscTaskId) then + if table.HasValue(TASKMASTER.MiscTasks[miscTaskId].RequiredFeatures, feature) then blockedByFeature = true break end @@ -88,9 +88,9 @@ function plymeta:AssignTask(isKillTask, index) end function plymeta:RemoveTask(taskId) - local isKillTask = TASKMASTER.killTasks[taskId] and true or false - local taskList = isKillTask and TASKMASTER.killTasks or TASKMASTER.miscTasks - local activeTasksName = isKillTask and "taskmasterKillTasks" or "taskmasterMiscTasks" + local isKillTask = TASKMASTER.KillTasks[taskId] and true or false + local taskList = isKillTask and TASKMASTER.KillTasks or TASKMASTER.MiscTasks + local activeTasksName = isKillTask and "TaskmasterKillTasks" or "TaskmasterMiscTasks" taskList[taskId].OnTaskRemoved(self) @@ -102,13 +102,13 @@ function plymeta:RerollTask(taskId, free) if not self:IsTaskmaster() then return end if not free and self:GetCredits() == 0 then return end - local isKillTask = TASKMASTER.killTasks[taskId] and true or false - local activeTasksName = isKillTask and "taskmasterKillTasks" or "taskmasterMiscTasks" + local isKillTask = TASKMASTER.KillTasks[taskId] and true or false + local activeTasksName = isKillTask and "TaskmasterKillTasks" or "TaskmasterMiscTasks" local index = table.KeyFromValue(self[activeTasksName], taskId) if not index then return end - table.insert(self.taskmasterRerolledTasks, taskId) - self:SetProperty("taskmasterRerolledTasks", self.taskmasterRerolledTasks, self) + table.insert(self.TaskmasterRerolledTasks, taskId) + self:SetProperty("TaskmasterRerolledTasks", self.TaskmasterRerolledTasks, self) local newTask = self:AssignTask(isKillTask, index) self:RemoveTask(taskId) @@ -122,8 +122,9 @@ function plymeta:RerollTask(taskId, free) -- they only had tasks left that don't block a win condition. -- Rerolling one of those tasks no longer guarantees that they are winning, -- unless the newly rolled task ALSO does not block a win condition. - if newTask and not newTask.allowRoundEnd and self.taskmasterShouldWin then - self:SetProperty("taskmasterShouldWin", false) + if newTask and not newTask.AllowRoundEnd and self.TaskmasterShouldWin then + self:SetProperty("TaskmasterShouldWin", false) + -- TODO: Alert the player that they have more tasks to do again end net.Start("TTT_TaskmasterUpdateTaskList") @@ -137,17 +138,17 @@ end) function plymeta:CompleteTask(taskId) if not self:IsActiveTaskmaster() then return end - local isKillTask = TASKMASTER.killTasks[taskId] and true or false - local taskList = isKillTask and TASKMASTER.killTasks or TASKMASTER.miscTasks - local activeTasksName = isKillTask and "taskmasterKillTasks" or "taskmasterMiscTasks" + local isKillTask = TASKMASTER.KillTasks[taskId] and true or false + local taskList = isKillTask and TASKMASTER.KillTasks or TASKMASTER.MiscTasks + local activeTasksName = isKillTask and "TaskmasterKillTasks" or "TaskmasterMiscTasks" if table.HasValue(self[activeTasksName], taskId) then taskList[taskId].OnTaskComplete(self) - table.insert(self.taskmasterCompletedTasks, taskId) - self:SetProperty("taskmasterCompletedTasks", self.taskmasterCompletedTasks, self) + table.insert(self.TaskmasterCompletedTasks, taskId) + self:SetProperty("TaskmasterCompletedTasks", self.TaskmasterCompletedTasks, self) - local activeTasksList = table.Copy(self.taskmasterKillTasks) - table.Add(activeTasksList, self.taskmasterMiscTasks) - for _, id in ipairs(self.taskmasterCompletedTasks) do + local activeTasksList = table.Copy(self.TaskmasterKillTasks) + table.Add(activeTasksList, self.TaskmasterMiscTasks) + for _, id in ipairs(self.TaskmasterCompletedTasks) do if table.HasValue(activeTasksList, id) then table.RemoveByValue(activeTasksList, id) else @@ -157,22 +158,22 @@ function plymeta:CompleteTask(taskId) local tasksRemaining = #activeTasksList for _, id in pairs(activeTasksList) do - local task = TASKMASTER.killTasks[id] + local task = TASKMASTER.KillTasks[id] if not task then - task = TASKMASTER.miscTasks[id] + task = TASKMASTER.MiscTasks[id] end if not task then continue end -- Tasks that allow the round to end should not count for this check -- since the Taskmaster wins even if they are still active - if task.allowRoundEnd then + if task.AllowRoundEnd then tasksRemaining = tasksRemaining - 1 end end if tasksRemaining <= 0 then - self:SetProperty("taskmasterShouldWin", true) + self:SetProperty("TaskmasterShouldWin", true) -- TODO: Alert the player that they have finished all their tasks end @@ -196,10 +197,10 @@ function plymeta:CompleteTask(taskId) end ROLE_ON_ROLE_ASSIGNED[ROLE_TASKMASTER] = function(ply) - ply:SetProperty("taskmasterKillTasks", {}, ply) - ply:SetProperty("taskmasterMiscTasks", {}, ply) - ply:SetProperty("taskmasterCompletedTasks", {}, ply) - ply:SetProperty("taskmasterRerolledTasks", {}, ply) + ply:SetProperty("TaskmasterKillTasks", {}, ply) + ply:SetProperty("TaskmasterMiscTasks", {}, ply) + ply:SetProperty("TaskmasterCompletedTasks", {}, ply) + ply:SetProperty("TaskmasterRerolledTasks", {}, ply) for _ = 1, taskmaster_kill_tasks:GetInt() do ply:AssignTask(true) end @@ -219,7 +220,7 @@ hook.Add("TTTWinCheckBlocks", "Taskmaster_TTTWinCheckBlocks", function(win_block local taskmaster = player.GetLivingRole(ROLE_TASKMASTER) if not IsPlayer(taskmaster) then return win_type end - if taskmaster.taskmasterShouldWin then return win_type end + if taskmaster.TaskmasterShouldWin then return win_type end if not taskmaster_blocks_team_wins:GetBool() then return win_type end @@ -259,7 +260,7 @@ hook.Add("TTTCheckForWin", "Taskmaster_TTTCheckForWin", function() local other_alive = false for _, v in player.Iterator() do if v:IsActive() then - if v:IsTaskmaster() and v.taskmasterShouldWin then + if v:IsTaskmaster() and v.TaskmasterShouldWin then winning_taskmaster_alive = true elseif not v:ShouldActLikeJester() and not ROLE_HAS_PASSIVE_WIN[v:GetRole()] then other_alive = true @@ -285,23 +286,23 @@ end) ------------- local function CleanupTasks(ply) - if ply.taskmasterKillTasks then - for _, id in pairs(ply.taskmasterKillTasks) do + if ply.TaskmasterKillTasks then + for _, id in pairs(ply.TaskmasterKillTasks) do ply:RemoveTask(id) end end - if ply.taskmasterMiscTasks then - for _, id in pairs(ply.taskmasterMiscTasks) do + if ply.TaskmasterMiscTasks then + for _, id in pairs(ply.TaskmasterMiscTasks) do ply:RemoveTask(id) end end - ply:ClearProperty("taskmasterKillTasks", ply) - ply:ClearProperty("taskmasterMiscTasks", ply) - ply:ClearProperty("taskmasterCompletedTasks", ply) - ply:ClearProperty("taskmasterRerolledTasks", ply) - ply:ClearProperty("taskmasterShouldWin") + ply:ClearProperty("TaskmasterKillTasks", ply) + ply:ClearProperty("TaskmasterMiscTasks", ply) + ply:ClearProperty("TaskmasterCompletedTasks", ply) + ply:ClearProperty("TaskmasterRerolledTasks", ply) + ply:ClearProperty("TaskmasterShouldWin") end hook.Add("TTTPrepareRound", "Taskmaster_TTTPrepareRound", function() diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua index 87032b2af..088946e12 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/carrycorpse.lua @@ -31,7 +31,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_CarryCorpseStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua index 4262b2474..208fc1129 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua @@ -24,7 +24,7 @@ TASK.Name = function(ply) if not ply then return name end local progress - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = times else progress = ply.Task_ChatCount diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index e0a9d9fe3..752dd0969 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -31,7 +31,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_CrouchStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index 6829fde8c..1a62e4fe1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -37,7 +37,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_CrouchNearBodyStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua index 5fb2a0e38..5435b8326 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua @@ -24,7 +24,7 @@ TASK.Name = function(ply) if not ply then return name end local progress - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = times else progress = ply.Task_CrowbarCount diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua index 723ea256d..735f7511c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua @@ -30,7 +30,7 @@ TASK.Name = function(ply) end end - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = total end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua index b597c263c..542918e7b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua @@ -21,7 +21,7 @@ TASK.Name = function(ply) if not ply then return name end local progress - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = amount else progress = ply.Task_DoDamageTotal diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua index 7adc4b804..ecd081152 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua @@ -24,7 +24,7 @@ TASK.Name = function(ply) if not ply then return name end local progress - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = times else progress = ply.Task_FireBulletsCount diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index da83974fd..69a06280c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -35,7 +35,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_HealthUnderStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua index 9c7c085d5..8637a5899 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/holstered.lua @@ -31,7 +31,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_HolsteredStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua index f38dd4abb..ef27470db 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -24,7 +24,7 @@ TASK.Name = function(ply) if not ply then return name end local progress - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = times else progress = ply.Task_JumpCount diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua index 4a2f1916d..ea89f21c3 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua @@ -9,7 +9,7 @@ local MathMax = math.max local TASK = {} TASK.id = "kill360" -TASK.isKillTask = true +TASK.IsKillTask = true local taskmaster_kill360_time = CreateConVar("ttt_taskmaster_kill360_time", "3", FCVAR_REPLICATED, "The time (in seconds) a player has after completing a 360 to kill a player for the 'Kill a Player After a 360' task", 1, 10) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killaiming.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killaiming.lua index df6c34850..6cc0ad58f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killaiming.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killaiming.lua @@ -3,7 +3,7 @@ local hook = hook local TASK = {} TASK.id = "killaiming" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) return "Kill a Player While Aiming" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua index f6af6821c..bdfe3b4c3 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killairborne.lua @@ -4,7 +4,7 @@ local net = net local TASK = {} TASK.id = "killairborne" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) return "Kill an Airborne Player" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua index ccfef07f2..d988de2b6 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua @@ -9,7 +9,7 @@ local MathRad = math.rad local TASK = {} TASK.id = "killbehind" -TASK.isKillTask = true +TASK.IsKillTask = true local taskmaster_killbehind_angle = CreateConVar("ttt_taskmaster_killbehind_view_angle", "75", FCVAR_REPLICATED, "The angle (in degrees) from a player's eye angle within which the Taskmaster is 'spotted' for the 'Kill a Player From Behind' task", 0, 180) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killcrowbar.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killcrowbar.lua index 9b6d4bc59..2d3b32cef 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killcrowbar.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killcrowbar.lua @@ -3,7 +3,7 @@ local hook = hook local TASK = {} TASK.id = "killcrowbar" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) return "Kill a Player With a Crowbar" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua index 7da029861..4284b7ab9 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdistant.lua @@ -14,7 +14,7 @@ local MathSin = math.sin local TASK = {} TASK.id = "killdistant" -TASK.isKillTask = true +TASK.IsKillTask = true local taskmaster_killdistant_range = CreateConVar("ttt_taskmaster_killdistant_range", "25", FCVAR_REPLICATED, "The minimum distance (in meters) away a player can be to count for the 'Kill a Distant Player' task", 0, 100) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua index bd049cd63..7b67a4ec0 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua @@ -10,7 +10,7 @@ local MathMax = math.max local TASK = {} TASK.id = "killdouble" -TASK.isKillTask = true +TASK.IsKillTask = true local taskmaster_killdouble_time = CreateConVar("ttt_taskmaster_killdouble_time", "5", FCVAR_REPLICATED, "The time (in seconds) the taskmaster has between kills to complete the 'Get a Double Kill' task", 0, 30) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killheadshot.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killheadshot.lua index e23785cec..1c77b72ae 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killheadshot.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killheadshot.lua @@ -6,7 +6,7 @@ local PlayerIterator = player.Iterator local TASK = {} TASK.id = "killheadshot" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) return "Kill a Player With a Headshot" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua index 820417906..c48c67443 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killlastbullet.lua @@ -4,7 +4,7 @@ local timer = timer local TASK = {} TASK.id = "killlastbullet" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) return "Kill a Player With Your Last Bullet" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killmidair.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killmidair.lua index a3dab87ce..e46a7f48b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killmidair.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killmidair.lua @@ -3,7 +3,7 @@ local hook = hook local TASK = {} TASK.id = "killmidair" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) return "Kill a Player While Midair" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua index 9cd640f68..e5da6619f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killnearby.lua @@ -14,7 +14,7 @@ local MathSin = math.sin local TASK = {} TASK.id = "killnearby" -TASK.isKillTask = true +TASK.IsKillTask = true local taskmaster_killnearby_range = CreateConVar("ttt_taskmaster_killnearby_range", "5", FCVAR_REPLICATED, "The maximum distance (in meters) away a player can be to count for the 'Kill a Nearby Player' task", 0, 10) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua index e8c40ab81..9c40ca2b7 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killonehit.lua @@ -6,7 +6,7 @@ local PlayerIterator = player.Iterator local TASK = {} TASK.id = "killonehit" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) return "Kill a Player in One Hit" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killpistol.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killpistol.lua index e48b6a7a0..1e04d8d2d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killpistol.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killpistol.lua @@ -3,7 +3,7 @@ local hook = hook local TASK = {} TASK.id = "killpistol" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) return "Kill a Player With a Pistol" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua index 80ee20c65..daffcafa9 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killretaliate.lua @@ -8,7 +8,7 @@ local PlayerIterator = player.Iterator local TASK = {} TASK.id = "killretaliate" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) return "Kill a Player That Attacked First" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua index 25a14f20c..4c26f2eb8 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killtarget.lua @@ -6,7 +6,7 @@ local util = util local TASK = {} TASK.id = "killtarget" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) local name = "Target" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua index 7ed1549e7..25a3d01b2 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua @@ -9,7 +9,7 @@ local TableInsert = table.insert local TASK = {} TASK.id = "killweapon" -TASK.isKillTask = true +TASK.IsKillTask = true TASK.Name = function(ply) local weapon = "Specific Weapon" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua index 3f76291d3..4e496c388 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/lookatplayer.lua @@ -33,7 +33,7 @@ TASK.Name = function(ply) local time = taskmaster_lookatplayer_time:GetInt() local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_LookAtPlayerStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua index 37c3f2c44..bfd877b5d 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/standonplayer.lua @@ -31,7 +31,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StandOnPlayerStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua index b81666efa..c763579c6 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhidden.lua @@ -33,7 +33,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayHiddenStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua index 48cd7b8a9..49b015147 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayhigher.lua @@ -33,7 +33,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayHigherStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua index 96f767a62..efa641f76 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayinarea.lua @@ -41,7 +41,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayInAreaStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua index 8f55bd655..c3683c9d1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/staylower.lua @@ -33,7 +33,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayLowerStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua index 51d7b1117..dbb631cfa 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/stayneartarget.lua @@ -45,7 +45,7 @@ TASK.Name = function(ply) local time = taskmaster_stayneartarget_time:GetInt() local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_StayNearTargetStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua index 7626f3cdb..2f7ba69da 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua @@ -3,7 +3,7 @@ local hook = hook local TASK = {} TASK.id = "survive" -TASK.allowRoundEnd = true +TASK.AllowRoundEnd = true TASK.Name = function(ply) return "Survive" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua index d99f16231..add9732fd 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -28,7 +28,7 @@ TASK.Name = function(ply) if not ply then return name end local progress = 0 - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time else local startTime = ply.Task_TakeDamageStart diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua index cc72c9aad..c5b30c643 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua @@ -24,7 +24,7 @@ TASK.Name = function(ply) if not ply then return name end local progress - if TableHasValue(ply.taskmasterCompletedTasks, TASK.id) then + if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = count else progress = ply.Task_WeaponPickupsCount From 3c3876f2f44ac07da8fe051d0fdc06379051f28e Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 15:17:55 -0400 Subject: [PATCH 110/140] Fixed missing changes --- gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua index 55b376a7c..42ba53ad3 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -48,8 +48,8 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { ----------------------- TASKMASTER = { - killTasks = {}, - miscTasks = {} + KillTasks = {}, + MiscTasks = {} } function TASKMASTER.RegisterTask(task) From b932682c745131b26294d61953aa52f138d11b02 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 16:55:17 -0400 Subject: [PATCH 111/140] Rework logic for events that complete on round end to fix winning if they die before the round ends but after they finish their other tasks --- .../gamemode/roles/taskmaster/taskmaster.lua | 73 +++++++++---------- .../roles/taskmaster/tasks/survive.lua | 2 +- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index fb4905b06..c72be464b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -110,23 +110,13 @@ function plymeta:RerollTask(taskId, free) table.insert(self.TaskmasterRerolledTasks, taskId) self:SetProperty("TaskmasterRerolledTasks", self.TaskmasterRerolledTasks, self) - local newTask = self:AssignTask(isKillTask, index) + self:AssignTask(isKillTask, index) self:RemoveTask(taskId) if not free then self:SubtractCredits(1) end - -- Reset their win state if they rerolled. - -- If they have already "won" and they are re-rolling, that means - -- they only had tasks left that don't block a win condition. - -- Rerolling one of those tasks no longer guarantees that they are winning, - -- unless the newly rolled task ALSO does not block a win condition. - if newTask and not newTask.AllowRoundEnd and self.TaskmasterShouldWin then - self:SetProperty("TaskmasterShouldWin", false) - -- TODO: Alert the player that they have more tasks to do again - end - net.Start("TTT_TaskmasterUpdateTaskList") net.Send(self) end @@ -135,6 +125,33 @@ net.Receive("TTT_TaskmasterRerollTask", function(len, ply) ply:RerollTask(net.ReadString()) end) +function plymeta:GetActiveTasks(roundEnd) + local activeTasksList = table.Copy(self.TaskmasterKillTasks) + table.Add(activeTasksList, self.TaskmasterMiscTasks) + for _, id in ipairs(self.TaskmasterCompletedTasks) do + if table.HasValue(activeTasksList, id) then + table.RemoveByValue(activeTasksList, id) + end + end + + if roundEnd then + -- Loop backwards so removing something from this list doesn't mess us up + for i = #activeTasksList, 1, -1 do + local id = activeTasksList[i] + local task = TASKMASTER.KillTasks[id] + if not task then + task = TASKMASTER.MiscTasks[id] + end + + if task and task.CompleteOnRoundEnd then + table.RemoveByValue(activeTasksList, id) + end + end + end + + return activeTasksList +end + function plymeta:CompleteTask(taskId) if not self:IsActiveTaskmaster() then return end @@ -146,33 +163,8 @@ function plymeta:CompleteTask(taskId) table.insert(self.TaskmasterCompletedTasks, taskId) self:SetProperty("TaskmasterCompletedTasks", self.TaskmasterCompletedTasks, self) - local activeTasksList = table.Copy(self.TaskmasterKillTasks) - table.Add(activeTasksList, self.TaskmasterMiscTasks) - for _, id in ipairs(self.TaskmasterCompletedTasks) do - if table.HasValue(activeTasksList, id) then - table.RemoveByValue(activeTasksList, id) - else - break - end - end - - local tasksRemaining = #activeTasksList - for _, id in pairs(activeTasksList) do - local task = TASKMASTER.KillTasks[id] - if not task then - task = TASKMASTER.MiscTasks[id] - end - - if not task then continue end - - -- Tasks that allow the round to end should not count for this check - -- since the Taskmaster wins even if they are still active - if task.AllowRoundEnd then - tasksRemaining = tasksRemaining - 1 - end - end - - if tasksRemaining <= 0 then + local activeTasksList = self:GetActiveTasks() + if #activeTasksList == 0 then self:SetProperty("TaskmasterShouldWin", true) -- TODO: Alert the player that they have finished all their tasks end @@ -221,6 +213,8 @@ hook.Add("TTTWinCheckBlocks", "Taskmaster_TTTWinCheckBlocks", function(win_block if not IsPlayer(taskmaster) then return win_type end if taskmaster.TaskmasterShouldWin then return win_type end + -- If we only have tasks remaining that complete on round end, let the round end as normal + if #taskmaster:GetActiveTasks(true) == 0 then return win_type end if not taskmaster_blocks_team_wins:GetBool() then return win_type end @@ -260,7 +254,8 @@ hook.Add("TTTCheckForWin", "Taskmaster_TTTCheckForWin", function() local other_alive = false for _, v in player.Iterator() do if v:IsActive() then - if v:IsTaskmaster() and v.TaskmasterShouldWin then + -- If they have won or the only active tasks they have remaining complete on round end, they win + if v:IsTaskmaster() and (v.TaskmasterShouldWin or #v:GetActiveTasks(true) == 0) then winning_taskmaster_alive = true elseif not v:ShouldActLikeJester() and not ROLE_HAS_PASSIVE_WIN[v:GetRole()] then other_alive = true diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua index 2f7ba69da..e1463ae32 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/survive.lua @@ -3,7 +3,7 @@ local hook = hook local TASK = {} TASK.id = "survive" -TASK.AllowRoundEnd = true +TASK.CompleteOnRoundEnd = true TASK.Name = function(ply) return "Survive" From 8647eb53b3a291d1b9b3e079e7885dfade407446 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 17:20:19 -0400 Subject: [PATCH 112/140] Changed taskmaster to allow rerolled tasks to show back up in future rerolls (can be disabled by convar) Fixed tutorial section on win block length showing when win blocking was disabled --- .../roles/taskmaster/cl_taskmaster.lua | 23 ++++++++++++------- .../gamemode/roles/taskmaster/shared.lua | 5 ++-- .../gamemode/roles/taskmaster/taskmaster.lua | 8 +++++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index c6e36732a..977913d69 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -49,9 +49,11 @@ end) -- CONVARS -- ------------- -local taskmaster_wins_with_others = GetConVar("ttt_taskmaster_wins_with_others") local taskmaster_kill_tasks = GetConVar("ttt_taskmaster_kill_tasks") local taskmaster_misc_tasks = GetConVar("ttt_taskmaster_misc_tasks") +local taskmaster_repeat_rerolls = GetConVar("ttt_taskmaster_repeat_rerolls") +local taskmaster_wins_with_others = GetConVar("ttt_taskmaster_wins_with_others") +local taskmaster_blocks_team_wins = GetConVar("ttt_taskmaster_blocks_team_wins") local taskmaster_win_block_length = GetConVar("ttt_taskmaster_win_block_length") local xOffset = CreateClientConVar("ttt_taskmaster_list_x_pos", "10", true, false, "The X (horizontal) position of the Taskmaster's task list HUD", 0, ScrW()) @@ -490,17 +492,22 @@ hook.Add("TTTTutorialRoleText", "Taskmaster_TTTTutorialRoleText", function(role, end -- Win block - local block_length = taskmaster_win_block_length:GetInt() - if block_length > 0 then - local plural = "" - if block_length ~= 1 then - plural = "s" + if taskmaster_blocks_team_wins:GetBool() then + local block_length = taskmaster_win_block_length:GetInt() + if block_length > 0 then + local plural = "" + if block_length ~= 1 then + plural = "s" + end + html = html .. "If the " .. ROLE_STRINGS[ROLE_TASKMASTER] .. " has not completed all tasks by the time another team wins, the round will be extended by " .. block_length .. " second" .. plural .. ", giving them extra time to finish." end - html = html .. "If the " .. ROLE_STRINGS[ROLE_TASKMASTER] .. " has not completed all tasks by the time another team wins, the round will be extended by " .. block_length .. " second" .. plural .. ", giving them extra time to finish." end -- Reroll - html = html .. "Undesired or uncompletable tasks can be rerolled by spending a credit in the equipment menu (press '" .. Key("+menu_context", "C") .. "')" + html = html .. "Undesired or uncompletable tasks can be rerolled by spending a credit in the equipment menu (press '" .. Key("+menu_context", "C") .. "')." + if taskmaster_repeat_rerolls:GetBool() then + html = html .. "Note, however, that tasks that have previously been rerolled can appear again when rerolling other tasks in the future." + end -- Show all enabled tasks local tasks = {} diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua index 42ba53ad3..253eb2973 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -25,11 +25,12 @@ end) CreateConVar("ttt_taskmaster_kill_tasks", "1", FCVAR_REPLICATED, "The number of kill tasks assigned to the Taskmaster", 0, 10) CreateConVar("ttt_taskmaster_misc_tasks", "2", FCVAR_REPLICATED, "The number of miscellaneous tasks assigned to the Taskmaster", 0, 10) -CreateConVar("ttt_taskmaster_completion_bonus", "1", FCVAR_REPLICATED, "How many credits the Taskmaster should get whenever they complete a task", 0, 10) +CreateConVar("ttt_taskmaster_completion_bonus", "1", FCVAR_NONE, "How many credits the Taskmaster should get whenever they complete a task", 0, 10) +CreateConVar("ttt_taskmaster_repeat_rerolls", "1", FCVAR_REPLICATED, "Whether the Taskmaster can be assigned tasks they previously rerolled away from", 0, 1) CreateConVar("ttt_taskmaster_blocks_team_wins", "1", FCVAR_REPLICATED, "Whether the Taskmaster should block teams (innocent, traitor, monster) from winning if they are alive and haven't finished their tasks.") CreateConVar("ttt_taskmaster_win_block_length", "60", FCVAR_REPLICATED, "How long (in seconds) the Taskmaster should block teams (innocent, traitor, monster) from winning for (if 'ttt_taskmaster_blocks_team_wins' is enabled). Set to 0 to block until time runs out", 0, 300) CreateConVar("ttt_taskmaster_wins_with_others", "1", FCVAR_REPLICATED, "If the Taskmaster should be allowed to win alongside other teams/players") -CreateConVar("ttt_taskmaster_is_passive", "0", FCVAR_REPLICATED, "Whether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if 'ttt_taskmaster_wins_with_others' is enabled)") +CreateConVar("ttt_taskmaster_is_passive", "0", FCVAR_NONE, "Whether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if 'ttt_taskmaster_wins_with_others' is enabled)") ROLE_CONVARS[ROLE_TASKMASTER] = {} table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index c72be464b..c5d85ff05 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -21,6 +21,7 @@ util.AddNetworkString("TTT_TaskmasterUpdateTaskList") local taskmaster_kill_tasks = GetConVar("ttt_taskmaster_kill_tasks") local taskmaster_misc_tasks = GetConVar("ttt_taskmaster_misc_tasks") +local taskmaster_repeat_rerolls = GetConVar("ttt_taskmaster_repeat_rerolls") local taskmaster_completion_bonus = GetConVar("ttt_taskmaster_completion_bonus") local taskmaster_blocks_team_wins = GetConVar("ttt_taskmaster_blocks_team_wins") local taskmaster_win_block_length = GetConVar("ttt_taskmaster_win_block_length") @@ -107,8 +108,11 @@ function plymeta:RerollTask(taskId, free) local index = table.KeyFromValue(self[activeTasksName], taskId) if not index then return end - table.insert(self.TaskmasterRerolledTasks, taskId) - self:SetProperty("TaskmasterRerolledTasks", self.TaskmasterRerolledTasks, self) + -- Don't track what we rerolled if we allow the player to reroll back to the same tasks + if not taskmaster_repeat_rerolls:GetBool() then + table.insert(self.TaskmasterRerolledTasks, taskId) + self:SetProperty("TaskmasterRerolledTasks", self.TaskmasterRerolledTasks, self) + end self:AssignTask(isKillTask, index) self:RemoveTask(taskId) From 71699f073634b2220590802296e989453b77ff66 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 17:32:25 -0400 Subject: [PATCH 113/140] Fixed error in Damage Everyone task when a non-player is shot --- .../gamemode/roles/taskmaster/tasks/damageeveryone.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua index 735f7511c..e52628ea4 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/damageeveryone.lua @@ -57,6 +57,7 @@ if SERVER then hook.Add("PostEntityTakeDamage", "Taskmaster_DamageEveryone_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken) if not wasDamageTaken then return end if entity == ply then return end + if not IsPlayer(entity) then return end local attacker = dmginfo:GetAttacker() if not IsPlayer(attacker) or attacker ~= ply then return end From cc417f031e751b006cd1f6f99b206c81368520bf Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 18 Aug 2025 17:32:36 -0400 Subject: [PATCH 114/140] Implemented IsRoleAbilityDisabled logic --- .../gamemode/roles/taskmaster/taskmaster.lua | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index c5d85ff05..1c29efacc 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -163,6 +163,18 @@ function plymeta:CompleteTask(taskId) local taskList = isKillTask and TASKMASTER.KillTasks or TASKMASTER.MiscTasks local activeTasksName = isKillTask and "TaskmasterKillTasks" or "TaskmasterMiscTasks" if table.HasValue(self[activeTasksName], taskId) then + -- If our role ability was disabled, keep track of which tasks were completed + -- so we can instantly complete them if/when we become un-disabled + if self:IsRoleAbilityDisabled() then + if not self.TaskmasterCompletedButBlocked then + self.TaskmasterCompletedButBlocked = {} + end + if not table.HasValue(self.TaskmasterCompletedButBlocked, taskId) then + table.insert(self.TaskmasterCompletedButBlocked, taskId) + end + return false + end + taskList[taskId].OnTaskComplete(self) table.insert(self.TaskmasterCompletedTasks, taskId) self:SetProperty("TaskmasterCompletedTasks", self.TaskmasterCompletedTasks, self) @@ -192,6 +204,16 @@ function plymeta:CompleteTask(taskId) return false end +hook.Add("TTTOnRoleAbilityEnabled", "Taskmaster_TTTOnRoleAbilityEnabled", function(ply) + if not ply:IsTaskmaster() then return end + if not ply.TaskmasterCompletedButBlocked then return end + + for _, taskId in ipairs(ply.TaskmasterCompletedButBlocked) do + ply:CompleteTask(taskId) + end + ply.TaskmasterCompletedButBlocked = nil +end) + ROLE_ON_ROLE_ASSIGNED[ROLE_TASKMASTER] = function(ply) ply:SetProperty("TaskmasterKillTasks", {}, ply) ply:SetProperty("TaskmasterMiscTasks", {}, ply) @@ -302,6 +324,7 @@ local function CleanupTasks(ply) ply:ClearProperty("TaskmasterCompletedTasks", ply) ply:ClearProperty("TaskmasterRerolledTasks", ply) ply:ClearProperty("TaskmasterShouldWin") + ply.TaskmasterCompletedButBlocked = nil end hook.Add("TTTPrepareRound", "Taskmaster_TTTPrepareRound", function() From f54712566dac4c0932a5caf0b1ea4caf12b4213c Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 20 Aug 2025 10:13:31 -0400 Subject: [PATCH 115/140] Added missing convars to ULX Added missing convar description --- .../gamemode/roles/taskmaster/shared.lua | 35 ++++++++++++++++--- .../gamemode/roles/taskmaster/taskmaster.lua | 3 +- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua index 253eb2973..2169c58a8 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/shared.lua @@ -25,12 +25,11 @@ end) CreateConVar("ttt_taskmaster_kill_tasks", "1", FCVAR_REPLICATED, "The number of kill tasks assigned to the Taskmaster", 0, 10) CreateConVar("ttt_taskmaster_misc_tasks", "2", FCVAR_REPLICATED, "The number of miscellaneous tasks assigned to the Taskmaster", 0, 10) -CreateConVar("ttt_taskmaster_completion_bonus", "1", FCVAR_NONE, "How many credits the Taskmaster should get whenever they complete a task", 0, 10) CreateConVar("ttt_taskmaster_repeat_rerolls", "1", FCVAR_REPLICATED, "Whether the Taskmaster can be assigned tasks they previously rerolled away from", 0, 1) -CreateConVar("ttt_taskmaster_blocks_team_wins", "1", FCVAR_REPLICATED, "Whether the Taskmaster should block teams (innocent, traitor, monster) from winning if they are alive and haven't finished their tasks.") +CreateConVar("ttt_taskmaster_blocks_team_wins", "1", FCVAR_REPLICATED, "Whether the Taskmaster should block teams (innocent, traitor, monster) from winning if they are alive and haven't finished their tasks.", 0, 1) CreateConVar("ttt_taskmaster_win_block_length", "60", FCVAR_REPLICATED, "How long (in seconds) the Taskmaster should block teams (innocent, traitor, monster) from winning for (if 'ttt_taskmaster_blocks_team_wins' is enabled). Set to 0 to block until time runs out", 0, 300) -CreateConVar("ttt_taskmaster_wins_with_others", "1", FCVAR_REPLICATED, "If the Taskmaster should be allowed to win alongside other teams/players") -CreateConVar("ttt_taskmaster_is_passive", "0", FCVAR_NONE, "Whether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if 'ttt_taskmaster_wins_with_others' is enabled)") +CreateConVar("ttt_taskmaster_wins_with_others", "1", FCVAR_REPLICATED, "If the Taskmaster should be allowed to win alongside other teams/players", 0, 1) +CreateConVar("ttt_taskmaster_is_passive", "0", FCVAR_REPLICATED, "Whether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if 'ttt_taskmaster_wins_with_others' is enabled)", 0, 1) ROLE_CONVARS[ROLE_TASKMASTER] = {} table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { @@ -43,6 +42,32 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { type = ROLE_CONVAR_TYPE_NUM, decimal = 0 }) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_completion_bonus", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_repeat_rerolls", + type = ROLE_CONVAR_TYPE_BOOL +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_blocks_team_wins", + type = ROLE_CONVAR_TYPE_BOOL +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_win_block_length", + type = ROLE_CONVAR_TYPE_NUM, + decimal = 0 +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_wins_with_others", + type = ROLE_CONVAR_TYPE_BOOL +}) +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { + cvar = "ttt_taskmaster_is_passive", + type = ROLE_CONVAR_TYPE_BOOL +}) ----------------------- -- TASK REGISTRATION -- @@ -62,7 +87,7 @@ function TASKMASTER.RegisterTask(task) end local cvarName = "ttt_taskmaster_" .. task.id .. "_enabled" - local enabled = CreateConVar(cvarName, 1, FCVAR_REPLICATED) + local enabled = CreateConVar(cvarName, 1, FCVAR_REPLICATED, "Whether the '" .. task.Name() .. "' task should be enabled", 0, 1) task.Enabled = function() return enabled:GetBool() end diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index 1c29efacc..db9f11aa7 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -19,10 +19,11 @@ util.AddNetworkString("TTT_TaskmasterUpdateTaskList") -- CONVARS -- ------------- +local taskmaster_completion_bonus = CreateConVar("ttt_taskmaster_completion_bonus", "1", FCVAR_NONE, "How many credits the Taskmaster should get whenever they complete a task", 0, 10) + local taskmaster_kill_tasks = GetConVar("ttt_taskmaster_kill_tasks") local taskmaster_misc_tasks = GetConVar("ttt_taskmaster_misc_tasks") local taskmaster_repeat_rerolls = GetConVar("ttt_taskmaster_repeat_rerolls") -local taskmaster_completion_bonus = GetConVar("ttt_taskmaster_completion_bonus") local taskmaster_blocks_team_wins = GetConVar("ttt_taskmaster_blocks_team_wins") local taskmaster_win_block_length = GetConVar("ttt_taskmaster_win_block_length") local taskmaster_wins_with_others = GetConVar("ttt_taskmaster_wins_with_others") From 307ae7879f5e1c1ea279f4a9cd7d1845eb734bac Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 20 Aug 2025 10:18:11 -0400 Subject: [PATCH 116/140] Added description and ranges to some auto-generated convars --- gamemodes/terrortown/gamemode/init_shd.lua | 4 ++-- gamemodes/terrortown/gamemode/shared.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gamemodes/terrortown/gamemode/init_shd.lua b/gamemodes/terrortown/gamemode/init_shd.lua index d41286a23..490248117 100644 --- a/gamemodes/terrortown/gamemode/init_shd.lua +++ b/gamemodes/terrortown/gamemode/init_shd.lua @@ -11,9 +11,9 @@ for role = 0, ROLE_MAX do if INDEPENDENT_ROLES[role] then local jesterVisible = ROLE_CAN_SEE_JESTERS[role] and "1" or "0" - CreateConVar("ttt_" .. rolestring .. "_can_see_jesters", jesterVisible, FCVAR_REPLICATED) + CreateConVar("ttt_" .. rolestring .. "_can_see_jesters", jesterVisible, FCVAR_REPLICATED, "Whether jesters are revealed (via head icons, color/icon on the scoreboard, etc.) to this role", 0, 1) local miaVisible = ROLE_CAN_SEE_MIA[role] and "1" or "0" - CreateConVar("ttt_" .. rolestring .. "_update_scoreboard", miaVisible, FCVAR_REPLICATED) + CreateConVar("ttt_" .. rolestring .. "_update_scoreboard", miaVisible, FCVAR_REPLICATED, "Whether this role shows dead players as missing in action", 0, 1) end end diff --git a/gamemodes/terrortown/gamemode/shared.lua b/gamemodes/terrortown/gamemode/shared.lua index db572cb50..def8ff8e3 100644 --- a/gamemodes/terrortown/gamemode/shared.lua +++ b/gamemodes/terrortown/gamemode/shared.lua @@ -488,7 +488,7 @@ function CreateShopConVars(role) CreateCreditConVar(role) CreateConVar("ttt_" .. rolestring .. "_shop_random_percent", "0", FCVAR_REPLICATED, "The percent chance that a weapon in the shop will not be shown for the " .. rolestring, 0, 100) - CreateConVar("ttt_" .. rolestring .. "_shop_random_enabled", "0", FCVAR_REPLICATED, "Whether shop randomization should run for the " .. rolestring) + CreateConVar("ttt_" .. rolestring .. "_shop_random_enabled", "0", FCVAR_REPLICATED, "Whether shop randomization should run for the " .. rolestring, 0, 1) local hassync = (TRAITOR_ROLES[role] and role ~= ROLE_TRAITOR) or (DETECTIVE_ROLES[role] and role ~= ROLE_DETECTIVE) or ROLE_HAS_SHOP_SYNC[role] if hassync then From ab362a057402e9044a159422a338b02df3ec59e4 Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 20 Aug 2025 10:37:20 -0400 Subject: [PATCH 117/140] Added markdown docs --- CONVARS.md | 88 ++++++++++++++++++- .../roles/taskmaster/tasks/crouch.lua | 2 +- .../roles/taskmaster/tasks/dodamage.lua | 2 +- .../roles/taskmaster/tasks/kill360.lua | 2 +- .../roles/taskmaster/tasks/killbehind.lua | 2 +- 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/CONVARS.md b/CONVARS.md index 528fa3cd6..d65b4ee69 100644 --- a/CONVARS.md +++ b/CONVARS.md @@ -116,6 +116,7 @@ ttt_shadow_enabled 0 // Whether or not the Sha ttt_arsonist_enabled 0 // Whether or not the Arsonist should spawn ttt_hivemind_enabled 0 // Whether or not the Hive Mind should spawn ttt_plaguemaster_enabled 0 // Whether or not the Plaguemaster should spawn +ttt_taskmaster_enabled 0 // Whether or not the Taskmaster should spawn // Individual Role Spawn Weights ttt_hypnotist_spawn_weight 1 // The weight assigned to spawning the Hypnotist @@ -165,6 +166,7 @@ ttt_shadow_spawn_weight 1 // The weight assigned to ttt_arsonist_spawn_weight 1 // The weight assigned to spawning the Arsonist ttt_hivemind_spawn_weight 1 // The weight assigned to spawning the Hive Mind ttt_plaguemaster_spawn_weight 1 // The weight assigned to spawning the Plaguemaster +ttt_taskmaster_spawn_weight 1 // The weight assigned to spawning the Taskmaster // (Note: Each role is limited to one player per round.) @@ -216,6 +218,7 @@ ttt_shadow_min_players 0 // The minimum number of ttt_arsonist_min_players 0 // The minimum number of players required to spawn the Arsonist ttt_hivemind_min_players 0 // The minimum number of players required to spawn the Hive Mind ttt_plaguemaster_min_players 0 // The minimum number of players required to spawn the Plaguemaster +ttt_taskmaster_min_players 0 // The minimum number of players required to spawn the Taskmaster // Grouped Role Spawn Settings ttt_twins_enabled 0 // Whether or not the Twins should spawn @@ -965,7 +968,88 @@ ttt_plaguemaster_dart_replace_timer 0 // How long (in seconds) ttt_plaguemaster_body_search_mode 1 // Whether dead bodies reveal if they had the plague when searched. 0 - Don't show. 1 - Show if died from plague. 2 - Show if Infected with plague. ttt_plaguemaster_can_see_jesters 0 // Whether jesters are revealed (via head icons, color/icon on the scoreboard, etc.) to the Plaguemaster ttt_plaguemaster_update_scoreboard 1 // Whether the Plaguemaster shows dead players as missing in action - + +// Taskmaster +ttt_taskmaster_blocks_team_wins 1 // Whether the Taskmaster should block teams (innocent, traitor, monster) from winning if they are alive and haven't finished their tasks. +ttt_taskmaster_is_passive 0 // Whether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if 'ttt_taskmaster_wins_with_others' is enabled) +ttt_taskmaster_kill_tasks 1 // The number of kill tasks assigned to the Taskmaster +ttt_taskmaster_misc_tasks 2 // The number of miscellaneous tasks assigned to the Taskmaster +ttt_taskmaster_repeat_rerolls 1 // Whether the Taskmaster can be assigned tasks they previously rerolled away from +ttt_taskmaster_win_block_length 60 // How long (in seconds) the Taskmaster should block teams (innocent, traitor, monster) from winning for (if 'ttt_taskmaster_blocks_team_wins' is enabled). Set to 0 to block until time runs out +ttt_taskmaster_wins_with_others 1 // If the Taskmaster should be allowed to win alongside other teams/players + +// Taskmaster tasks +ttt_taskmaster_calldetective_enabled 1 // Whether the 'Call a Detective to a Body' task should be enabled +ttt_taskmaster_carrycorpse_enabled 1 // Whether the 'Carry a Corpse for X Seconds' task should be enabled +ttt_taskmaster_carrycorpse_time 60 // The time (in seconds) a player must carry a player corpse to complete the 'Carry Corpse' task +ttt_taskmaster_chat_enabled 1 // Whether the 'Send X Messages' task should be enabled +ttt_taskmaster_chat_times 25 // The number of text messages a player must sent to complete the 'Send X Messages' task +ttt_taskmaster_completion_bonus 1 // How many credits the Taskmaster should get whenever they complete a task +ttt_taskmaster_crouch_enabled 1 // Whether the 'Crouch for X Seconds' task should be enabled +ttt_taskmaster_crouch_time 40 // The time (in seconds) a player must stay crouched to complete the 'Crouch' task +ttt_taskmaster_crouchnearbody_enabled 1 // Whether the 'Crouch Near a Body' task should be enabled +ttt_taskmaster_crouchnearbody_range 1 // The distance (in meters) away a player must stay within to count for the 'Crouch Near Body' task +ttt_taskmaster_crouchnearbody_time 20 // The time (in seconds) a player must stay near a body to count for the 'Crouch Near Body' task +ttt_taskmaster_crowbar_enabled 1 // Whether the 'Swing a Crowbar 50 Times' task should be enabled +ttt_taskmaster_crowbar_times 50 // The number of times a player must use their crowbar to complete the 'Swing Crowbar' task +ttt_taskmaster_damageeveryone_enabled 1 // Whether the 'Damage Everyone Else' task should be enabled +ttt_taskmaster_dodamage_amount 200 // The amount of damage a player must do to complete the 'Deal X Damage' task +ttt_taskmaster_dodamage_enabled 1 // Whether the 'Deal X Damage' task should be enabled +ttt_taskmaster_firebullets_enabled 1 // Whether the 'Fire X Bullets' task should be enabled +ttt_taskmaster_firebullets_times 100 // The jump of times a player must fire a bullet to complete the 'Fire X Bullets' task +ttt_taskmaster_getplayerkilled_enabled 1 // Whether the 'Get Target Killed' task should be enabled +ttt_taskmaster_getplayertokill_enabled 1 // Whether the 'Get Target to Kill' task should be enabled +ttt_taskmaster_healthunder_amount 15 // The amount of health a player must stay under to complete the 'Health Under' task +ttt_taskmaster_healthunder_enabled 1 // Whether the 'Survive Under X Health' task should be enabled +ttt_taskmaster_healthunder_time 60 // The time (in seconds) a player must stay under the target health to complete the 'Health Under' task +ttt_taskmaster_holstered_enabled 1 // Whether the 'Stay Holstered for X Seconds' task should be enabled +ttt_taskmaster_holstered_time 60 // The time (in seconds) a player must stay holstered to complete the 'Holstered' task +ttt_taskmaster_jump_enabled 1 // Whether the 'Jump x Times' task should be enabled +ttt_taskmaster_jump_times 100 // The jump of times a player must jump to complete the 'Jump X Times' task +ttt_taskmaster_kill360_enabled 1 // Whether the 'Kill a Player After a 360' task should be enabled +ttt_taskmaster_kill360_time 3 // The time (in seconds) a player has after completing a 360 to kill a player for the 'Kill a Player After a 360' task +ttt_taskmaster_killaiming_enabled 1 // Whether the 'Kill a Player While Aiming' task should be enabled +ttt_taskmaster_killairborne_enabled 1 // Whether the 'Kill an Airborne Player' task should be enabled +ttt_taskmaster_killbehind_enabled 1 // Whether the 'Kill a Player From Behind' task should be enabled +ttt_taskmaster_killbehind_view_angle 75 // The angle (in degrees) from a player's eye angle within which the Taskmaster is 'spotted' for the 'Kill a Player From Behind' task +ttt_taskmaster_killcrowbar_enabled 1 // Whether the 'Kill a Player With a Crowbar' task should be enabled +ttt_taskmaster_killdistant_enabled 1 // Whether the 'Kill a Distant Player' task should be enabled +ttt_taskmaster_killdistant_range 25 // The minimum distance (in meters) away a player can be to count for the 'Kill a Distant Player' task +ttt_taskmaster_killdouble_enabled 1 // Whether the 'Get a Double Kill' task should be enabled +ttt_taskmaster_killdouble_time 5 // The time (in seconds) the taskmaster has between kills to complete the 'Get a Double Kill' task +ttt_taskmaster_killheadshot_enabled 1 // Whether the 'Kill a Player With a Headshot' task should be enabled +ttt_taskmaster_killlastbullet_enabled 1 // Whether the 'Kill a Player With Your Last Bullet' task should be enabled +ttt_taskmaster_killmidair_enabled 1 // Whether the 'Kill a Player While Midair' task should be enabled +ttt_taskmaster_killnearby_enabled 1 // Whether the 'Kill a Nearby Player' task should be enabled +ttt_taskmaster_killnearby_range 5 // The maximum distance (in meters) away a player can be to count for the 'Kill a Nearby Player' task +ttt_taskmaster_killonehit_enabled 1 // Whether the 'Kill a Player in One Hit' task should be enabled +ttt_taskmaster_killpistol_enabled 1 // Whether the 'Kill a Player With a Pistol' task should be enabled +ttt_taskmaster_killretaliate_enabled 1 // Whether the 'Kill a Player That Attacked First' task should be enabled +ttt_taskmaster_killtarget_enabled 1 // Whether the 'Kill Target' task should be enabled +ttt_taskmaster_killweapon_enabled 1 // Whether the 'Kill a Player With a Specific Weapon' task should be enabled +ttt_taskmaster_lookatplayer_enabled 1 // Whether the 'Look at Target' task should be enabled +ttt_taskmaster_lookatplayer_time 30 // The time (in seconds) a player must look at their target to complete for the 'Look at Player' task +ttt_taskmaster_pickupshopitem_enabled 1 // Whether the 'Pick up a Shop Item' task should be enabled +ttt_taskmaster_standonplayer_enabled 1 // Whether the 'Stand on a Player for X Seconds' task should be enabled +ttt_taskmaster_standonplayer_time 15 // The time (in seconds) a player must stay stand on top of another player to complete the 'Stand On Player' task +ttt_taskmaster_stayhidden_enabled 1 // Whether the 'Stay Hidden for X Seconds' task should be enabled +ttt_taskmaster_stayhidden_time 30 // The time (in seconds) a player must hide to complete for the 'Stay Hidden' task +ttt_taskmaster_stayhigher_enabled 1 // Whether the 'Be Highest for X Seconds' task should be enabled +ttt_taskmaster_stayhigher_time 30 // The time (in seconds) a player must stay higher up to complete the 'Highest Player' task +ttt_taskmaster_stayinarea_enabled 1 // Whether the 'Stay in Area for X Seconds' task should be enabled +ttt_taskmaster_stayinarea_range 5 // The distance (in meters) away from the location that a player must stay within to count for the 'Stay in Area' task +ttt_taskmaster_stayinarea_time 30 // The time (in seconds) a player must stay inside the target area to count for the 'Stay in Area' task +ttt_taskmaster_staylower_enabled 1 // Whether the 'Be Lowest for X Seconds' task should be enabled +ttt_taskmaster_staylower_time 30 // The time (in seconds) a player must stay lower down to complete the 'Lowest Player' task +ttt_taskmaster_stayneartarget_enabled 1 // Whether the 'Stay Near Target' task should be enabled +ttt_taskmaster_stayneartarget_range 5 // The distance (in meters) away a player must stay within to count for the 'Stay Near Target' task +ttt_taskmaster_stayneartarget_time 30 // The time (in seconds) a player must stay near their target to count for the 'Stay Near Target' task +ttt_taskmaster_survive_enabled 1 // Whether the 'Survive' task should be enabled +ttt_taskmaster_takedamage_enabled 1 // Whether the 'Take Damage and Survive' task should be enabled +ttt_taskmaster_takedamage_time 60 // The time (in seconds) a player must survive without taking further damage to complete the 'Take Damage' task +ttt_taskmaster_weaponpickups_count 20 // The number of unique weapons a player must pick up to complete the 'Pick Up Weapons' task +ttt_taskmaster_weaponpickups_enabled 1 // Whether the 'Pick up X Weapons' task should be enabled + // ---------------------------------------- // GROUPED ROLE SETTINGS @@ -1143,6 +1227,7 @@ ttt_shadow_starting_health 100 // The amount of health t ttt_arsonist_starting_health 100 // The amount of health the Arsonist starts with ttt_hivemind_starting_health 100 // The amount of health the Hive Mind starts with ttt_plaguemaster_starting_health 100 // The amount of health the Plaguemaster starts with +ttt_taskmaster_starting_health 100 // The amount of health the Taskmaster starts with // Individual Role Max Health. Set to 0 or -1 to use the game's default maximum health. ttt_traitor_max_health 100 // The maximum amount of health a Traitor can have @@ -1197,6 +1282,7 @@ ttt_shadow_max_health 100 // The maximum amount of ttt_arsonist_max_health 100 // The maximum amount of health the Arsonist can have ttt_hivemind_max_health 100 // The maximum amount of health the Hive Mind can have ttt_plaguemaster_max_health 100 // The maximum amount of health the Plaguemaster can have +ttt_taskmaster_max_health 100 // The maximum amount of health the Taskmaster can have // Round Time ttt_roundtime_win_draw 0 // Whether a round that ends because the round time limit has passed counts as a draw. If it is not a draw, the traitor team loses diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua index 752dd0969..b9930368b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouch.lua @@ -14,7 +14,7 @@ local TASK = {} TASK.id = "crouch" -local taskmaster_crouch_time = CreateConVar("ttt_taskmaster_crouch_time", "40", FCVAR_REPLICATED, "The time (in seconds) a player must stay crouched to complete the 'Crouch Near Body' task", 1, 240) +local taskmaster_crouch_time = CreateConVar("ttt_taskmaster_crouch_time", "40", FCVAR_REPLICATED, "The time (in seconds) a player must stay crouched to complete the 'Crouch' task", 1, 240) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_crouch_time", type = ROLE_CONVAR_TYPE_NUM, diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua index 542918e7b..d495884dc 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua @@ -7,7 +7,7 @@ local TASK = {} TASK.id = "dodamage" -local taskmaster_dodamage_amount = CreateConVar("ttt_taskmaster_dodamage_amount", "200", FCVAR_REPLICATED, "The amount of damage a player must do to complete the 'Do X Damage' task", 1, 1000) +local taskmaster_dodamage_amount = CreateConVar("ttt_taskmaster_dodamage_amount", "200", FCVAR_REPLICATED, "The amount of damage a player must do to complete the 'Deal X Damage' task", 1, 1000) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_dodamage_amount", type = ROLE_CONVAR_TYPE_NUM, diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua index ea89f21c3..95bb8f037 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/kill360.lua @@ -11,7 +11,7 @@ local TASK = {} TASK.id = "kill360" TASK.IsKillTask = true -local taskmaster_kill360_time = CreateConVar("ttt_taskmaster_kill360_time", "3", FCVAR_REPLICATED, "The time (in seconds) a player has after completing a 360 to kill a player for the 'Kill a Player After a 360' task", 1, 10) +local taskmaster_kill360_time = CreateConVar("ttt_taskmaster_kill360_time", "3", FCVAR_REPLICATED, "The time (in seconds) a player has after completing a 360 to kill a player for the 'Kill a Player After a 360' task", 1, 10) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_kill360_time", type = ROLE_CONVAR_TYPE_NUM, diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua index d988de2b6..228cba27c 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killbehind.lua @@ -11,7 +11,7 @@ local TASK = {} TASK.id = "killbehind" TASK.IsKillTask = true -local taskmaster_killbehind_angle = CreateConVar("ttt_taskmaster_killbehind_view_angle", "75", FCVAR_REPLICATED, "The angle (in degrees) from a player's eye angle within which the Taskmaster is 'spotted' for the 'Kill a Player From Behind' task", 0, 180) +local taskmaster_killbehind_angle = CreateConVar("ttt_taskmaster_killbehind_view_angle", "75", FCVAR_REPLICATED, "The angle (in degrees) from a player's eye angle within which the Taskmaster is 'spotted' for the 'Kill a Player From Behind' task", 0, 180) table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { cvar = "ttt_taskmaster_killbehind_view_angle", type = ROLE_CONVAR_TYPE_NUM, From 84a45979b003bc92d72e0897af556c5c953417d3 Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 20 Aug 2025 10:56:15 -0400 Subject: [PATCH 118/140] Added cr4ttt_role_cvars script --- scripts/server/cr4ttt_functions.lua | 143 ++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 scripts/server/cr4ttt_functions.lua diff --git a/scripts/server/cr4ttt_functions.lua b/scripts/server/cr4ttt_functions.lua new file mode 100644 index 000000000..297290f99 --- /dev/null +++ b/scripts/server/cr4ttt_functions.lua @@ -0,0 +1,143 @@ +local function PrintRoleCvars(ply, cmd, args) + if #args == 0 then + ErrorNoHalt("Missing role name argument. Usage: cr4ttt_role_cvars role_name [format] [min_name_length_for_md_fmt] [min_default_length_for_md_fmt]\n") + return + end + + local roleName = args[1] + local roleId + if table.HasValue(ROLE_STRINGS_RAW, roleName) then + roleId = table.KeyFromValue(ROLE_STRINGS_RAW, roleName) + elseif table.HasValue(ROLE_STRINGS_SHORT, roleName) then + roleId = table.KeyFromValue(ROLE_STRINGS_SHORT, roleName) + else + ErrorNoHalt("Unknown role: " .. roleName .. "\n") + return + end + + if roleId <= ROLE_NONE or roleId > ROLE_MAX then + ErrorNoHalt("Invalid role: " .. roleName .. "\n") + return + end + + local fmt + if #args > 1 then + fmt = args[2] + end + + local nameLengthMin = 46 + if #args > 2 then + nameLengthMin = tonumber(args[3]) or nameLengthMin + end + + local defaultLengthMin = 8 + if #args > 3 then + defaultLengthMin = tonumber(args[4]) or defaultLengthMin + end + + local formatFn + if fmt == "md" or fmt == "markdown" then + formatFn = function(cvar) + local convar = GetConVar(cvar.cvar) + + local namePadded = convar:GetName() + while #namePadded < nameLengthMin do + namePadded = namePadded .. " " + end + + local defaultPadded = convar:GetDefault() + while #defaultPadded < defaultLengthMin do + defaultPadded = defaultPadded .. " " + end + + print(namePadded .. " " .. defaultPadded .. "// " .. convar:GetHelpText()) + end + elseif fmt == "html" or fmt == "web" then + local function GetCvarType(convar, cvar) + if cvar.type == ROLE_CONVAR_TYPE_NUM then + local typeName + if not cvar.decimal or cvar.decimal == 0 then + typeName = "Integer" + else + typeName = "Float" + end + + if convar:GetMin() and convar:GetMax() then + return typeName .. " (" .. convar:GetMin() .. "-" .. convar:GetMax() .. ")" + end + return typeName + elseif cvar.type == ROLE_CONVAR_TYPE_BOOL then + return "Boolean" + elseif cvar.type == ROLE_CONVAR_TYPE_TEXT then + return "String" + elseif cvar.type == ROLE_CONVAR_TYPE_DROPDOWN then + if cvar.isNumeric then + if convar:GetMin() and convar:GetMax() then + return "Integer (" .. convar:GetMin() .. "-" .. convar:GetMax() .. ")" + end + return "Integer" + end + return "String" + end + end + + local function GetCvarDesc(convar, cvar) + local desc = convar:GetHelpText() + if cvar.type == ROLE_CONVAR_TYPE_DROPDOWN then + desc = desc .. ":" + desc = desc .. "\r\n
      " + for _, choice in ipairs(cvar.choices) do + desc = desc .. "\r\n
    1. " .. choice .. ".
    2. " + end + desc = desc .. "\r\n
    \r\n " + end + + return desc + end + + formatFn = function(cvar) + local convar = GetConVar(cvar.cvar) + print("") + print(" " .. cvar.cvar .. "") + print(" " .. convar:GetDefault() .. "") + print(" " .. GetCvarType(convar, cvar) .. "") + print(" " .. GetCvarDesc(convar, cvar) .. "") + print("") + end + else + formatFn = function(cvar) + local convar = GetConVar(cvar.cvar) + print(convar:GetName() .. " (def. " .. convar:GetDefault() .. ") - " .. convar:GetHelpText()) + end + end + + local roleCvars = table.Copy(ROLE_CONVARS[roleId]) + table.SortByMember(roleCvars, "cvar", true) + + for _, cvar in ipairs(roleCvars) do + formatFn(cvar) + end +end + +function PrintRoleCvarsComplete(cmd, argStr, args) + if #args == 1 then + local filtered = {} + for _, r in ipairs(ROLE_STRINGS_RAW) do + if string.StartsWith(r, args[1]) then + table.insert(filtered, cmd .. " \"" .. r .. "\" ") + end + end + return filtered + elseif #args == 2 then + local options = {"html", "markdown", "ulx"} + local filtered = {} + for _, f in ipairs(options) do + if string.StartsWith(f, args[2]) then + table.insert(filtered, cmd .. " \"" .. args[1] .. "\" \"" .. f .. "\"") + end + end + return filtered + end +end + +concommand.Add("cr4ttt_role_cvars", PrintRoleCvars, PrintRoleCvarsComplete, "Prints all role custom convars in the desired format. Usage: cr4ttt_role_cvars role_name [format] [min_name_length_for_md_fmt] [min_default_length_for_md_fmt]") \ No newline at end of file From 37bedfdb591242ae02d7639772976a6833e8ab95 Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 20 Aug 2025 10:57:57 -0400 Subject: [PATCH 119/140] Fixed global bleed --- scripts/server/cr4ttt_functions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/server/cr4ttt_functions.lua b/scripts/server/cr4ttt_functions.lua index 297290f99..a6c2cb333 100644 --- a/scripts/server/cr4ttt_functions.lua +++ b/scripts/server/cr4ttt_functions.lua @@ -119,7 +119,7 @@ local function PrintRoleCvars(ply, cmd, args) end end -function PrintRoleCvarsComplete(cmd, argStr, args) +local function PrintRoleCvarsComplete(cmd, argStr, args) if #args == 1 then local filtered = {} for _, r in ipairs(ROLE_STRINGS_RAW) do From d103ec71348d7c276f6cda21267df08a2d5a3889 Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 20 Aug 2025 11:15:20 -0400 Subject: [PATCH 120/140] Added support for non-numeric dropdowns to HTML docs generator --- scripts/server/cr4ttt_functions.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/server/cr4ttt_functions.lua b/scripts/server/cr4ttt_functions.lua index a6c2cb333..e9d400176 100644 --- a/scripts/server/cr4ttt_functions.lua +++ b/scripts/server/cr4ttt_functions.lua @@ -85,11 +85,19 @@ local function PrintRoleCvars(ply, cmd, args) local desc = convar:GetHelpText() if cvar.type == ROLE_CONVAR_TYPE_DROPDOWN then desc = desc .. ":" - desc = desc .. "\r\n
      " - for _, choice in ipairs(cvar.choices) do - desc = desc .. "\r\n
    1. " .. choice .. ".
    2. " + if cvar.isNumeric then + desc = desc .. "\r\n
        " + for _, choice in ipairs(cvar.choices) do + desc = desc .. "\r\n
      1. " .. choice .. ".
      2. " + end + desc = desc .. "\r\n
      \r\n " + else + desc = desc .. "\r\n
        " + for _, choice in ipairs(cvar.choices) do + desc = desc .. "\r\n
      • " .. choice .. ".
      • " + end + desc = desc .. "\r\n
      \r\n " end - desc = desc .. "\r\n
    \r\n " end return desc From 1d7000ed91ccb36f1deb41d36e507da84e2c4edf Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 20 Aug 2025 11:29:49 -0400 Subject: [PATCH 121/140] Added taskmaster HTML docs --- docs/teams/independent.html | 513 +++++++++++++++++++++++++++++++++++- 1 file changed, 503 insertions(+), 10 deletions(-) diff --git a/docs/teams/independent.html b/docs/teams/independent.html index 705007d60..9598326c6 100644 --- a/docs/teams/independent.html +++ b/docs/teams/independent.html @@ -791,16 +791,18 @@

    Role Configuration:

    - ttt_plaguemaster_body_search_mode - 0 - Integer (0-2) - Whether dead bodies reveal if they had the plague when searched: -
      -
    1. Don't show.
    2. -
    3. Show if died from plague.
    4. -
    5. Show if infected with plague.
    6. -
    - + + ttt_plaguemaster_body_search_mode + 0 + Integer (0-2) + Whether dead bodies reveal if they had the plague when searched: +
      +
    1. Don't show.
    2. +
    3. Show if died from plague.
    4. +
    5. Show if infected with plague.
    6. +
    + + ttt_plaguemaster_can_see_jesters 0 @@ -1111,6 +1113,497 @@

    Role Configuration:

    +
    + +
    +
    [more]
    +

    The Taskmaster has a list of tasks that they must complete before the end of the round.

    +
    +

    The Taskmaster's tasks are divided into two types: Kill and Miscellaneous.

    +

    A Kill-type task requires that the Taskmaster themselves kill another player, and the Miscellaneous tasks are everything else.

    +

    The number of Kill tasks that a Taskmaster must complete is controlled by the ttt_taskmaster_kill_tasks convar, and the ttt_taskmaster_misc_tasks convar for Miscellaneous tasks.

    +

    If a task cannot be completed (or the Taskmaster deems it too difficult), they can spend a credit to reroll that task to get something different.

    +

    If the ttt_taskmaster_wins_with_others convar is disabled, the Taskmaster must also kill all other (non-passive) players to win.

    +

    The ttt_taskmaster_blocks_team_wins convar controls whether the Taskmaster blocks another team for winning for a short period of time if their tasks have not been completed yet, giving them extra time to finish.

    +

    Role Configuration:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ConVarDefaultTypeDescription
    ttt_taskmaster_is_passive0BooleanWhether the Taskmaster should count as a 'passive' role for roles that need to kill other players, allowing them to win while the Taskmaster is still alive (if ttt_taskmaster_wins_with_others is enabled)
    ttt_taskmaster_kill_tasks1Integer (0-10)The number of kill tasks assigned to the Taskmaster
    ttt_taskmaster_misc_tasks2Integer (0-10)The number of miscellaneous tasks assigned to the Taskmaster
    ttt_taskmaster_repeat_rerolls1BooleanWhether the Taskmaster can be assigned tasks they previously rerolled away from
    ttt_taskmaster_win_block_length60Integer (0-300)How long (in seconds) the Taskmaster should block teams (innocent, traitor, monster) from winning for (if ttt_taskmaster_blocks_team_wins is enabled). Set to 0 to block until time runs out
    ttt_taskmaster_wins_with_others1BooleanIf the Taskmaster should be allowed to win alongside other teams/players
    ttt_taskmaster_blocks_team_wins1BooleanWhether the Taskmaster should block teams (innocent, traitor, monster) from winning if they are alive and haven't finished their tasks.
    ttt_taskmaster_calldetective_enabled1BooleanWhether the 'Call a Detective to a Body' task should be enabled
    ttt_taskmaster_carrycorpse_enabled1BooleanWhether the 'Carry a Corpse for X Seconds' task should be enabled
    ttt_taskmaster_carrycorpse_time60Integer (1-240)The time (in seconds) a player must carry a player corpse to complete the 'Carry Corpse' task
    ttt_taskmaster_chat_enabled1BooleanWhether the 'Send X Messages' task should be enabled
    ttt_taskmaster_chat_times25Integer (1-100)The number of text messages a player must sent to complete the 'Send X Messages' task
    ttt_taskmaster_completion_bonus1Integer (0-10)How many credits the Taskmaster should get whenever they complete a task
    ttt_taskmaster_crouch_enabled1BooleanWhether the 'Crouch for X Seconds' task should be enabled
    ttt_taskmaster_crouch_time40Integer (1-240)The time (in seconds) a player must stay crouched to complete the 'Crouch' task
    ttt_taskmaster_crouchnearbody_enabled1BooleanWhether the 'Crouch Near a Body' task should be enabled
    ttt_taskmaster_crouchnearbody_range1Integer (1-100)The distance (in meters) away a player must stay within to count for the 'Crouch Near Body' task
    ttt_taskmaster_crouchnearbody_time20Integer (1-240)The time (in seconds) a player must stay near a body to count for the 'Crouch Near Body' task
    ttt_taskmaster_crowbar_enabled1BooleanWhether the 'Swing a Crowbar X Times' task should be enabled
    ttt_taskmaster_crowbar_times50Integer (1-240)The number of times a player must use their crowbar to complete the 'Swing Crowbar' task
    ttt_taskmaster_damageeveryone_enabled1BooleanWhether the 'Damage Everyone Else' task should be enabled
    ttt_taskmaster_dodamage_amount200Integer (1-1000)The amount of damage a player must do to complete the 'Deal X Damage' task
    ttt_taskmaster_dodamage_enabled1BooleanWhether the 'Deal X Damage' task should be enabled
    ttt_taskmaster_firebullets_enabled1BooleanWhether the 'Fire X Bullets' task should be enabled
    ttt_taskmaster_firebullets_times100Integer (1-1000)The jump of times a player must fire a bullet to complete the 'Fire X Bullets' task
    ttt_taskmaster_getplayerkilled_enabled1BooleanWhether the 'Get Target Killed' task should be enabled
    ttt_taskmaster_getplayertokill_enabled1BooleanWhether the 'Get Target to Kill' task should be enabled
    ttt_taskmaster_healthunder_amount15Integer (1-240)The amount of health a player must stay under to complete the 'Health Under' task
    ttt_taskmaster_healthunder_enabled1BooleanWhether the 'Survive Under X Health' task should be enabled
    ttt_taskmaster_healthunder_time60Integer (1-240)The time (in seconds) a player must stay under the target health to complete the 'Health Under' task
    ttt_taskmaster_holstered_enabled1BooleanWhether the 'Stay Holstered for X Seconds' task should be enabled
    ttt_taskmaster_holstered_time60Integer (1-240)The time (in seconds) a player must stay holstered to complete the 'Holstered' task
    ttt_taskmaster_jump_enabled1BooleanWhether the 'Jump X Times' task should be enabled
    ttt_taskmaster_jump_times100Integer (1-500)The jump of times a player must jump to complete the 'Jump X Times' task
    ttt_taskmaster_kill360_enabled1BooleanWhether the 'Kill a Player After a 360' task should be enabled
    ttt_taskmaster_kill360_time3Integer (1-10)The time (in seconds) a player has after completing a 360 to kill a player for the 'Kill a Player After a 360' task
    ttt_taskmaster_killaiming_enabled1BooleanWhether the 'Kill a Player While Aiming' task should be enabled
    ttt_taskmaster_killairborne_enabled1BooleanWhether the 'Kill an Airborne Player' task should be enabled
    ttt_taskmaster_killbehind_enabled1BooleanWhether the 'Kill a Player From Behind' task should be enabled
    ttt_taskmaster_killbehind_view_angle75Integer (0-180)The angle (in degrees) from a player's eye angle within which the Taskmaster is 'spotted' for the 'Kill a Player From Behind' task
    ttt_taskmaster_killcrowbar_enabled1BooleanWhether the 'Kill a Player With a Crowbar' task should be enabled
    ttt_taskmaster_killdistant_enabled1BooleanWhether the 'Kill a Distant Player' task should be enabled
    ttt_taskmaster_killdistant_range25Integer (0-100)The minimum distance (in meters) away a player can be to count for the 'Kill a Distant Player' task
    ttt_taskmaster_killdouble_enabled1BooleanWhether the 'Get a Double Kill' task should be enabled
    ttt_taskmaster_killdouble_time5Integer (0-30)The time (in seconds) the taskmaster has between kills to complete the 'Get a Double Kill' task
    ttt_taskmaster_killheadshot_enabled1BooleanWhether the 'Kill a Player With a Headshot' task should be enabled
    ttt_taskmaster_killlastbullet_enabled1BooleanWhether the 'Kill a Player With Your Last Bullet' task should be enabled
    ttt_taskmaster_killmidair_enabled1BooleanWhether the 'Kill a Player While Midair' task should be enabled
    ttt_taskmaster_killnearby_enabled1BooleanWhether the 'Kill a Nearby Player' task should be enabled
    ttt_taskmaster_killnearby_range5Integer (0-10)The maximum distance (in meters) away a player can be to count for the 'Kill a Nearby Player' task
    ttt_taskmaster_killonehit_enabled1BooleanWhether the 'Kill a Player in One Hit' task should be enabled
    ttt_taskmaster_killpistol_enabled1BooleanWhether the 'Kill a Player With a Pistol' task should be enabled
    ttt_taskmaster_killretaliate_enabled1BooleanWhether the 'Kill a Player That Attacked First' task should be enabled
    ttt_taskmaster_killtarget_enabled1BooleanWhether the 'Kill Target' task should be enabled
    ttt_taskmaster_killweapon_enabled1BooleanWhether the 'Kill a Player With a Specific Weapon' task should be enabled
    ttt_taskmaster_lookatplayer_enabled1BooleanWhether the 'Look at Target' task should be enabled
    ttt_taskmaster_lookatplayer_time30Integer (1-240)The time (in seconds) a player must look at their target to complete for the 'Look at Player' task
    ttt_taskmaster_pickupshopitem_enabled1BooleanWhether the 'Pick up a Shop Item' task should be enabled
    ttt_taskmaster_standonplayer_enabled1BooleanWhether the 'Stand on a Player for X Seconds' task should be enabled
    ttt_taskmaster_standonplayer_time15Integer (1-240)The time (in seconds) a player must stay stand on top of another player to complete the 'Stand On Player' task
    ttt_taskmaster_stayhidden_enabled1BooleanWhether the 'Stay Hidden for X Seconds' task should be enabled
    ttt_taskmaster_stayhidden_time30Integer (1-240)The time (in seconds) a player must hide to complete for the 'Stay Hidden' task
    ttt_taskmaster_stayhigher_enabled1BooleanWhether the 'Be Highest for X Seconds' task should be enabled
    ttt_taskmaster_stayhigher_time30Integer (1-240)The time (in seconds) a player must stay higher up to complete the 'Highest Player' task
    ttt_taskmaster_stayinarea_enabled1BooleanWhether the 'Stay in Area for X Seconds' task should be enabled
    ttt_taskmaster_stayinarea_range5Integer (1-100)The distance (in meters) away from the location that a player must stay within to count for the 'Stay in Area' task
    ttt_taskmaster_stayinarea_time30Integer (1-240)The time (in seconds) a player must stay inside the target area to count for the 'Stay in Area' task
    ttt_taskmaster_staylower_enabled1BooleanWhether the 'Be Lowest for X Seconds' task should be enabled
    ttt_taskmaster_staylower_time30Integer (1-240)The time (in seconds) a player must stay lower down to complete the 'Lowest Player' task
    ttt_taskmaster_stayneartarget_enabled1BooleanWhether the 'Stay Near Target' task should be enabled
    ttt_taskmaster_stayneartarget_range5Integer (1-100)The distance (in meters) away a player must stay within to count for the 'Stay Near Target' task
    ttt_taskmaster_stayneartarget_time30Integer (1-240)The time (in seconds) a player must stay near their target to count for the 'Stay Near Target' task
    ttt_taskmaster_survive_enabled1BooleanWhether the 'Survive' task should be enabled
    ttt_taskmaster_takedamage_enabled1BooleanWhether the 'Take Damage and Survive' task should be enabled
    ttt_taskmaster_takedamage_time60Integer (1-240)The time (in seconds) a player must survive without taking further damage to complete the 'Take Damage' task
    ttt_taskmaster_weaponpickups_count20Integer (1-240)The number of unique weapons a player must pick up to complete the 'Pick Up Weapons' task
    ttt_taskmaster_weaponpickups_enabled1BooleanWhether the 'Pick up X Weapons' task should be enabled
    +

    A list of ConVars shared by every role and instructions for using ConVars can be found on the Configuring ConVars tutorial page.

    +
    +
    +
    From 2cc6e143bf63c2985d9b096d9823bbf6d73a7c87 Mon Sep 17 00:00:00 2001 From: Malivil Date: Wed, 20 Aug 2025 11:40:11 -0400 Subject: [PATCH 122/140] Error prevention and cleanup --- .../terrortown/gamemode/roles/taskmaster/tasks/chat.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua | 2 +- .../gamemode/roles/taskmaster/tasks/dodamage.lua | 2 +- .../gamemode/roles/taskmaster/tasks/firebullets.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/jump.lua | 2 +- .../gamemode/roles/taskmaster/tasks/killweapon.lua | 7 ++++++- .../gamemode/roles/taskmaster/tasks/weaponpickups.lua | 2 +- 7 files changed, 12 insertions(+), 7 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua index 208fc1129..572c54858 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/chat.lua @@ -27,7 +27,7 @@ TASK.Name = function(ply) if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = times else - progress = ply.Task_ChatCount + progress = ply.Task_ChatCount or 0 end return name .. " (" .. progress .. "/" .. times .. ")" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua index 5435b8326..ae7554f39 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crowbar.lua @@ -27,7 +27,7 @@ TASK.Name = function(ply) if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = times else - progress = ply.Task_CrowbarCount + progress = ply.Task_CrowbarCount or 0 end return name .. " (" .. progress .. "/" .. times .. ")" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua index d495884dc..b04489ac9 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/dodamage.lua @@ -24,7 +24,7 @@ TASK.Name = function(ply) if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = amount else - progress = ply.Task_DoDamageTotal + progress = ply.Task_DoDamageTotal or 0 end return name .. " (" .. progress .. "/" .. amount .. ")" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua index ecd081152..d39bd2dd8 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/firebullets.lua @@ -27,7 +27,7 @@ TASK.Name = function(ply) if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = times else - progress = ply.Task_FireBulletsCount + progress = ply.Task_FireBulletsCount or 0 end return name .. " (" .. progress .. "/" .. times .. ")" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua index ef27470db..48e6add24 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/jump.lua @@ -27,7 +27,7 @@ TASK.Name = function(ply) if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = times else - progress = ply.Task_JumpCount + progress = ply.Task_JumpCount or 0 end return name .. " (" .. progress .. "/" .. times .. ")" diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua index 25a3d01b2..0def1c842 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killweapon.lua @@ -21,7 +21,12 @@ TASK.Name = function(ply) end end end - return "Kill a Player With a " .. weapon + + local article = "a" + if StartsWithVowel(weapon) then + article = article .. "n" + end + return "Kill a Player With " .. article .. " " .. weapon end TASK.Description = function(ply) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua index c5b30c643..07bb38437 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/weaponpickups.lua @@ -27,7 +27,7 @@ TASK.Name = function(ply) if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = count else - progress = ply.Task_WeaponPickupsCount + progress = ply.Task_WeaponPickupsCount or 0 end return name .. " (" .. progress .. "/" .. count .. ")" From c4848e55e37dab197c24b66ba14bee8880683e70 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 24 Aug 2025 17:25:06 -0400 Subject: [PATCH 123/140] Change create HTML tutorial to link to HTML convars page --- docs/tutorials/create_your_own_role.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/tutorials/create_your_own_role.html b/docs/tutorials/create_your_own_role.html index 78c019705..e2552601e 100644 --- a/docs/tutorials/create_your_own_role.html +++ b/docs/tutorials/create_your_own_role.html @@ -669,8 +669,7 @@

    ConVars

    ttt_%NAMERAW%_shop_mode (Whether the role should have access to traitor and/or detective shop items) [INDEPENDENT ONLY]
    - -

    For more information on the specifics of these ConVars you can read the full list of ConVars here.

    +

    For more information on the specifics of these ConVars you can read the full list of ConVars here.

    If you would like to add your own ConVars that aren't automatically created you can do so here. First create the ConVars as you would normally with CreateConVar. (Note: Please try to keep your ConVars as consistently named as possible. The recommended naming scheme for role specific convars is ttt_%NAMERAW%_....)

    Once you have defined your ConVar you can add it to the convars table to get it to show up inside ULX menus. Each entry in the convars table needs two properties. cvar is the name of the ConVar you want to add and type is one of three values depending on if you want your ConVar to show up as a slider, checkbox or textbox.

    From 2c0d786ee2f8459119c0a0d4beb6d7b12bf32751 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 24 Aug 2025 17:41:22 -0400 Subject: [PATCH 124/140] Added task complete messages --- .../gamemode/roles/taskmaster/taskmaster.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index db9f11aa7..f50da047f 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -176,14 +176,19 @@ function plymeta:CompleteTask(taskId) return false end - taskList[taskId].OnTaskComplete(self) + local task = taskList[taskId] + task.OnTaskComplete(self) table.insert(self.TaskmasterCompletedTasks, taskId) self:SetProperty("TaskmasterCompletedTasks", self.TaskmasterCompletedTasks, self) local activeTasksList = self:GetActiveTasks() if #activeTasksList == 0 then self:SetProperty("TaskmasterShouldWin", true) - -- TODO: Alert the player that they have finished all their tasks + -- TODO: Play a sound + self:QueueMessage(MSG_PRINTBOTH, "All tasks complete!", nil, "tskTaskComplete") + else + -- TODO: Play a sound + self:QueueMessage(MSG_PRINTBOTH, "'" .. task.Name(self) .. "' complete!", nil, "tskTaskComplete") end net.Start("TTT_TaskmasterUpdateTaskList") @@ -198,8 +203,6 @@ function plymeta:CompleteTask(taskId) }) end - -- TODO: Play a sound and/or send a message when a task is completed - return true end return false From cb8e2f9dabe2d5fd3d465d922a854e20f0b5bd08 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 25 Aug 2025 10:55:24 -0400 Subject: [PATCH 125/140] Fixed missing param in docs --- API/HOOKS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/HOOKS.md b/API/HOOKS.md index 3a857b6f3..a29ed46dd 100644 --- a/API/HOOKS.md +++ b/API/HOOKS.md @@ -1263,7 +1263,7 @@ Called before a team chat message is sent. Used to modify the targets of the tea *Return:* Whether or not this team chat message should be sent (Defaults to `true`) -### TTTTeamVoiceChatTargets(speaker, targets) +### TTTTeamVoiceChatTargets(speaker, targets, state) Called before a team voice state message is sent. Used to modify the targets of the team voice state message.\ *Realm:* Server\ *Added in:* 2.0.7\ From 44bf3b6c4dcb33a2ae0221875452cf04e1123bc7 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Mon, 25 Aug 2025 16:09:24 +0100 Subject: [PATCH 126/140] Mixed duplicate hook name --- .../terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua index 7b67a4ec0..4b1d1ceb8 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/killdouble.lua @@ -46,7 +46,7 @@ if SERVER then } TASK.OnTaskAssigned = function(ply) - hook.Add("PlayerDeath", "Taskmaster_KillPistol_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) + hook.Add("PlayerDeath", "Taskmaster_KillDouble_PlayerDeath_" .. ply:SteamID64(), function(victim, inflictor, attacker) if not IsPlayer(victim) then return end if not IsPlayer(attacker) or not attacker:IsActiveTaskmaster() or attacker ~= ply then return end @@ -64,7 +64,7 @@ if SERVER then TASK.OnTaskRemoved = function(ply) ply:ClearProperty("Task_KillDoubleLastKill", ply) - hook.Remove("PlayerDeath", "Taskmaster_KillPistol_PlayerDeath_" .. ply:SteamID64()) + hook.Remove("PlayerDeath", "Taskmaster_KillDouble_PlayerDeath_" .. ply:SteamID64()) net.Start("TTT_Taskmaster_KillDouble_Cleanup") net.Send(ply) From 685b186adb7761717879bb0a45b77a03062ab762 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Mon, 25 Aug 2025 17:27:17 +0100 Subject: [PATCH 127/140] Added task complete sounds and reworked reroll logic to account for edge cases where no valid tasks exist --- .../roles/taskmaster/cl_taskmaster.lua | 8 ++++ .../gamemode/roles/taskmaster/taskmaster.lua | 37 +++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua index 977913d69..6a0813c6e 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/cl_taskmaster.lua @@ -530,4 +530,12 @@ hook.Add("TTTTutorialRoleText", "Taskmaster_TTTTutorialRoleText", function(role, return html end +end) + +------------ +-- SOUNDS -- +------------ + +net.Receive("TTT_TaskmasterTaskComplete", function(len, ply) + surface.PlaySound("buttons/bell1.wav") end) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index f50da047f..f871f22c5 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -14,6 +14,7 @@ local plymeta = FindMetaTable("Player") util.AddNetworkString("TTT_TaskmasterRerollTask") util.AddNetworkString("TTT_TaskmasterUpdateTaskList") +util.AddNetworkString("TTT_TaskmasterTaskComplete") ------------- -- CONVARS -- @@ -32,6 +33,20 @@ local taskmaster_wins_with_others = GetConVar("ttt_taskmaster_wins_with_others") -- TASK ASSIGNMENT -- --------------------- +local function CheckTaskmasterWin(ply) + local activeTasksList = ply:GetActiveTasks() + if #activeTasksList == 0 then + ply:SetProperty("TaskmasterShouldWin", true) + local message = "All tasks complete!" + if taskmaster_wins_with_others then + message = message .. " You will win at the end of the round." + end + ply:QueueMessage(MSG_PRINTBOTH, message, nil, "tskTaskComplete") + return true + end + return false +end + function plymeta:AssignTask(isKillTask, index) if not self:IsTaskmaster() then return end @@ -85,7 +100,6 @@ function plymeta:AssignTask(isKillTask, index) end end end - -- TODO: Handle edge case where there are no valid tasks. Maybe we need to change their role? Or just have a free fallback task? return false end @@ -115,7 +129,7 @@ function plymeta:RerollTask(taskId, free) self:SetProperty("TaskmasterRerolledTasks", self.TaskmasterRerolledTasks, self) end - self:AssignTask(isKillTask, index) + local rerolled = self:AssignTask(isKillTask, index) self:RemoveTask(taskId) if not free then @@ -124,6 +138,11 @@ function plymeta:RerollTask(taskId, free) net.Start("TTT_TaskmasterUpdateTaskList") net.Send(self) + + if not rerolled then + self:QueueMessage(MSG_PRINTTALK, "Ran out of valid tasks to assign! You can have this one for free.", nil, "tskTaskRerollFailed") + CheckTaskmasterWin(self) + end end net.Receive("TTT_TaskmasterRerollTask", function(len, ply) @@ -181,15 +200,11 @@ function plymeta:CompleteTask(taskId) table.insert(self.TaskmasterCompletedTasks, taskId) self:SetProperty("TaskmasterCompletedTasks", self.TaskmasterCompletedTasks, self) - local activeTasksList = self:GetActiveTasks() - if #activeTasksList == 0 then - self:SetProperty("TaskmasterShouldWin", true) - -- TODO: Play a sound - self:QueueMessage(MSG_PRINTBOTH, "All tasks complete!", nil, "tskTaskComplete") - else - -- TODO: Play a sound - self:QueueMessage(MSG_PRINTBOTH, "'" .. task.Name(self) .. "' complete!", nil, "tskTaskComplete") - end + net.Start("TTT_TaskmasterTaskComplete") + net.Send(self) + self:QueueMessage(MSG_PRINTTALK, "'" .. task.Name(self) .. "' complete!", nil, "tskTaskComplete") + + CheckTaskmasterWin(self) net.Start("TTT_TaskmasterUpdateTaskList") net.Send(self) From 95588031537afaf322652d08e922c54d4b1a9714 Mon Sep 17 00:00:00 2001 From: donaldsonnick Date: Mon, 25 Aug 2025 17:37:27 +0100 Subject: [PATCH 128/140] Small fix --- gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua index f871f22c5..6ad2507e0 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/taskmaster.lua @@ -38,7 +38,7 @@ local function CheckTaskmasterWin(ply) if #activeTasksList == 0 then ply:SetProperty("TaskmasterShouldWin", true) local message = "All tasks complete!" - if taskmaster_wins_with_others then + if taskmaster_wins_with_others:GetBool() then message = message .. " You will win at the end of the round." end ply:QueueMessage(MSG_PRINTBOTH, message, nil, "tskTaskComplete") From fb35f6ec6313a1908acb63785dc469ed6fba5db0 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 25 Aug 2025 12:55:08 -0400 Subject: [PATCH 129/140] Fixed broken doc header --- docs/api/hooks.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/hooks.html b/docs/api/hooks.html index f4027a8bc..181f4ed97 100644 --- a/docs/api/hooks.html +++ b/docs/api/hooks.html @@ -308,7 +308,7 @@

    TTTDetectiveCalledToBody(ply, owner, rag) +

    TTTDetectiveCalledToBody(ply, owner, rag)  

    Called when a player calls a detective to a player ragdoll.
    Realm: Server
    From 09a281bbf8edb51db694de0dc979518eaceca237 Mon Sep 17 00:00:00 2001 From: Malivil Date: Mon, 25 Aug 2025 13:01:43 -0400 Subject: [PATCH 130/140] Added Taskmaster to release notes --- RELEASE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index 660d507f0..83e9d9892 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,6 +3,9 @@ ## 2.3.5 (Beta) **Released:** +### Additions +- Added new independent role: Taskmaster + ### Developer - Added `TTTDetectiveCalledToBody` hook which is called when a player calls a detective to a body - Changed `SYNC` methods to not clear values that are already `nil` From 24a68306deb28b923fc9bfda79d3d56f6b9712dd Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 28 Aug 2025 12:44:19 -0400 Subject: [PATCH 131/140] Moved role template into its own folder --- CREATE_YOUR_OWN_ROLE.md | 2 +- docs/tutorials/create_your_own_role.html | 2 +- templates/{ => role}/Icon Template.psd | Bin templates/{ => role}/Role Addon Template/addon.json | 0 .../lua/customroles/%NAMERAW%.lua | 0 .../vgui/ttt/roles/%NAMESHORT%/icon_%NAMESHORT%.vmt | 0 .../ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%.vmt | 0 .../roles/%NAMESHORT%/sprite_%NAMESHORT%_noz.vmt | 0 templates/{ => role}/Score Template.psd | Bin templates/{ => role}/Sprite Template.psd | Bin templates/{ => role}/Workshop Icon Template.psd | Bin 11 files changed, 2 insertions(+), 2 deletions(-) rename templates/{ => role}/Icon Template.psd (100%) rename templates/{ => role}/Role Addon Template/addon.json (100%) rename templates/{ => role}/Role Addon Template/lua/customroles/%NAMERAW%.lua (100%) rename templates/{ => role}/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/icon_%NAMESHORT%.vmt (100%) rename templates/{ => role}/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%.vmt (100%) rename templates/{ => role}/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%_noz.vmt (100%) rename templates/{ => role}/Score Template.psd (100%) rename templates/{ => role}/Sprite Template.psd (100%) rename templates/{ => role}/Workshop Icon Template.psd (100%) diff --git a/CREATE_YOUR_OWN_ROLE.md b/CREATE_YOUR_OWN_ROLE.md index 98b46546c..8e0a59ab3 100644 --- a/CREATE_YOUR_OWN_ROLE.md +++ b/CREATE_YOUR_OWN_ROLE.md @@ -56,7 +56,7 @@ In order to create your own role you will need to make sure you have downloaded - **.lua** - This can be done in Notepad in a pinch but at the very least we would recommend [Notepad++](https://notepad-plus-plus.org/). - **.vmt and .vtf** - [VTFEdit Reloaded](https://github.com/Sky-rym/VTFEdit-Reloaded/releases) is the best way to edit these files but if you know what you are doing there are plugins for other apps. -In this guide we will be walking through how we made the Summoner role and you can download all the templates we are using [here](/templates). +In this guide we will be walking through how we made the Summoner role and you can download all the templates we are using [here](/templates/role). Last thing to do before you are ready to get started is to unzip that file which should give you 4 .psd files and a folder like this: diff --git a/docs/tutorials/create_your_own_role.html b/docs/tutorials/create_your_own_role.html index e2552601e..94da29f04 100644 --- a/docs/tutorials/create_your_own_role.html +++ b/docs/tutorials/create_your_own_role.html @@ -94,7 +94,7 @@

    Before You Start

  • .lua - This can be done in Notepad in a pinch but at the very least we would recommend Notepad++.
  • .vmt and .vtf - VTFEdit Reloaded is the best way to edit these files but if you know what you are doing there are plugins for other apps.
  • -

    In this guide we will be walking through how we made the Summoner role and you can download all the templates we are using here.

    +

    In this guide we will be walking through how we made the Summoner role and you can download all the templates we are using here.

    Last thing to do before you are ready to get started is to unzip that file which should give you 4 .psd files and a folder like this:

    diff --git a/templates/Icon Template.psd b/templates/role/Icon Template.psd similarity index 100% rename from templates/Icon Template.psd rename to templates/role/Icon Template.psd diff --git a/templates/Role Addon Template/addon.json b/templates/role/Role Addon Template/addon.json similarity index 100% rename from templates/Role Addon Template/addon.json rename to templates/role/Role Addon Template/addon.json diff --git a/templates/Role Addon Template/lua/customroles/%NAMERAW%.lua b/templates/role/Role Addon Template/lua/customroles/%NAMERAW%.lua similarity index 100% rename from templates/Role Addon Template/lua/customroles/%NAMERAW%.lua rename to templates/role/Role Addon Template/lua/customroles/%NAMERAW%.lua diff --git a/templates/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/icon_%NAMESHORT%.vmt b/templates/role/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/icon_%NAMESHORT%.vmt similarity index 100% rename from templates/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/icon_%NAMESHORT%.vmt rename to templates/role/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/icon_%NAMESHORT%.vmt diff --git a/templates/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%.vmt b/templates/role/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%.vmt similarity index 100% rename from templates/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%.vmt rename to templates/role/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%.vmt diff --git a/templates/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%_noz.vmt b/templates/role/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%_noz.vmt similarity index 100% rename from templates/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%_noz.vmt rename to templates/role/Role Addon Template/materials/vgui/ttt/roles/%NAMESHORT%/sprite_%NAMESHORT%_noz.vmt diff --git a/templates/Score Template.psd b/templates/role/Score Template.psd similarity index 100% rename from templates/Score Template.psd rename to templates/role/Score Template.psd diff --git a/templates/Sprite Template.psd b/templates/role/Sprite Template.psd similarity index 100% rename from templates/Sprite Template.psd rename to templates/role/Sprite Template.psd diff --git a/templates/Workshop Icon Template.psd b/templates/role/Workshop Icon Template.psd similarity index 100% rename from templates/Workshop Icon Template.psd rename to templates/role/Workshop Icon Template.psd From 6bbb5e42e77a7605d6c85440a87c0aa697cc67d2 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 28 Aug 2025 14:32:51 -0400 Subject: [PATCH 132/140] Cleanup --- docs/tutorials/create_your_own_role.html | 1 + .../gamemode/roles/taskmaster/tasks/crouchnearbody.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua | 2 +- .../terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/create_your_own_role.html b/docs/tutorials/create_your_own_role.html index 94da29f04..8349d87de 100644 --- a/docs/tutorials/create_your_own_role.html +++ b/docs/tutorials/create_your_own_role.html @@ -265,6 +265,7 @@

    Short DescriptionAlong with the full role description, you need a short description that is no longer than two sentences that will be displayed in the cheat sheet. This is just to convey a general idea of what you role does and does not need to convey much detail. Keep it short and sweet.

    The short description for the Summoner will look like this:

    ROLE.shortdesc = "Can buy items that summon minions."
    +

    Team

    Next we have the team. You can set which team your role is a member of by using the the following values

    diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua index 1a62e4fe1..d2321c506 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/crouchnearbody.lua @@ -31,11 +31,11 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - local time = taskmaster_crouchnearbody_time:GetInt() local name = "Crouch Near a Body" if not ply then return name end + local time = taskmaster_crouchnearbody_time:GetInt() local progress = 0 if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua index 69a06280c..ecab4872b 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/healthunder.lua @@ -29,11 +29,11 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { TASK.Name = function(ply) local amount = taskmaster_healthunder_amount:GetInt() - local time = taskmaster_healthunder_time:GetInt() local name = "Survive Under " .. amount .. " Health" if not ply then return name end + local time = taskmaster_healthunder_time:GetInt() local progress = 0 if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time diff --git a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua index add9732fd..bfdb592c1 100644 --- a/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua +++ b/gamemodes/terrortown/gamemode/roles/taskmaster/tasks/takedamage.lua @@ -22,11 +22,11 @@ table.insert(ROLE_CONVARS[ROLE_TASKMASTER], { }) TASK.Name = function(ply) - local time = taskmaster_takedamage_time:GetInt() local name = "Take Damage and Survive" if not ply then return name end + local time = taskmaster_takedamage_time:GetInt() local progress = 0 if TableHasValue(ply.TaskmasterCompletedTasks, TASK.id) then progress = time From a586fb7eb4a39c24d7495bbd2fa637a9a666847a Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 28 Aug 2025 15:12:43 -0400 Subject: [PATCH 133/140] Added HTML tutorial for creating Taskmaster tasks --- CREATE_TASKMASTER_TASKS.md | 0 CREATE_YOUR_OWN_ROLE.md | 4 +- docs/tutorials.html | 1 + docs/tutorials/create_taskmaster_tasks.html | 612 ++++++++++++++++++ docs/tutorials/create_your_own_role.html | 4 +- .../taskmaster/Task Addon Template/addon.json | 5 + .../lua/taskmastertasks/%TASKNAME%.lua | 35 + .../taskmaster/Workshop Icon Template.psd | Bin 0 -> 205763 bytes 8 files changed, 657 insertions(+), 4 deletions(-) create mode 100644 CREATE_TASKMASTER_TASKS.md create mode 100644 docs/tutorials/create_taskmaster_tasks.html create mode 100644 templates/taskmaster/Task Addon Template/addon.json create mode 100644 templates/taskmaster/Task Addon Template/lua/taskmastertasks/%TASKNAME%.lua create mode 100644 templates/taskmaster/Workshop Icon Template.psd diff --git a/CREATE_TASKMASTER_TASKS.md b/CREATE_TASKMASTER_TASKS.md new file mode 100644 index 000000000..e69de29bb diff --git a/CREATE_YOUR_OWN_ROLE.md b/CREATE_YOUR_OWN_ROLE.md index 8e0a59ab3..ffabffb90 100644 --- a/CREATE_YOUR_OWN_ROLE.md +++ b/CREATE_YOUR_OWN_ROLE.md @@ -998,7 +998,7 @@ For example, here is what 'sprite_sum_noz.vmt' looks like: ## Uploading Your Addon -Your role is almost ready to go! The last thing you need to do is upload your addon to the steam workshop. Before you can do that there are 2 more files you need to make 'addon.json' and your workshop icon. +Your role is almost ready to go! The last thing you need to do is upload your addon to the steam workshop. Before you can do that there are 2 more files you need to make: 'addon.json' and your workshop icon. ### addon.json @@ -1017,7 +1017,7 @@ For example 'addon.json' for the Summoner role looks like this: Now is the time to open the last template. This step is completely optional, you can use whatever workshop icon you want! However, if you want to keep it consistent with other CR for TTT roles open up 'Workshop Icon Template.psd'. -Right about now you should be getting a feeling of déjà vu because out friend the white dashed outline is back. Copy your role icon onto the 'Icon' layer and this time instead of a shadow you should see a thick coloured outline. Resize your icon so the white all fits within the guide, it doesn't matter if the coloured outline spills outside the guide. Hide the 'Icon Guide' layer and you are almost good to go. +Right about now you should be getting a feeling of déjà vu because our friend the white dashed outline is back. Copy your role icon onto the 'Icon' layer and this time instead of a shadow you should see a thick coloured outline. Resize your icon so the white all fits within the guide, it doesn't matter if the coloured outline spills outside the guide. Hide the 'Icon Guide' layer and you are almost good to go. Each team in CR for TTT has it's own colour it is identified by and this template works best if you switch out the background to match that colour. In the layers window you should see four different background colours. Hide all the ones that don't match your roles team. You should also see four different stroke outline effects on the 'Role Icon' layer. Hide all the ones that don't match the colour of the background. diff --git a/docs/tutorials.html b/docs/tutorials.html index 46c026684..d5247d46d 100644 --- a/docs/tutorials.html +++ b/docs/tutorials.html @@ -44,6 +44,7 @@

    Table of Contents

    1. Create Your Own Role
    2. Create Custom Weapons
    3. +
    4. Create Custom Taskmaster Tasks
    diff --git a/docs/tutorials/create_taskmaster_tasks.html b/docs/tutorials/create_taskmaster_tasks.html new file mode 100644 index 000000000..d417c3284 --- /dev/null +++ b/docs/tutorials/create_taskmaster_tasks.html @@ -0,0 +1,612 @@ + + + + Custom Roles for TTT - Tutorials - Creating Custom Taskmaster Tasks + + + + + + + + + + + + +
    +

    Creating Custom Taskmaster Tasks

    +

    This tutorial aims to provide the step-by-step instructions that a developer would need to create custom tasks for the Taskmaster task. It covers creation of the task's basic features, what options are available for preset advanced features to choose from, all the way through creating publishing to the workshop.

    +

    This tutorial assumes you have some programming experience, even if it's not directly in Lua itself. Lua is a pretty straight-forward language, so knowledge of its specifics are not necessary as long as you are good at googling. Of course, if you have any questions about how to do more advanced things, feel free to join our Discord server to ask.

    +
    +
    +

    Table of Contents

    +
      +
    1. Before You Start
    2. +
    3. Code +
        +
      1. Task Table
      2. +
      3. Task Properties
      4. +
      5. Name
      6. +
      7. Description
      8. +
      9. Initialize
      10. +
      11. Server-Side
      12. +
          +
        1. CanAssignTask
        2. +
        3. RequiredFeatures
        4. +
        5. OnTaskAssigned
        6. +
        7. OnTaskRemoved
        8. +
        9. OnTaskComplete
        10. +
        +
      13. Task Registration
      14. +
      15. Example File
      16. +
      +
    4. +
    5. Uploading Your Addon +
        +
      1. addon.json
      2. +
      3. Workshop Icon
      4. +
      5. Folder Name
      6. +
      7. Final Checks
      8. +
      9. Uploading
      10. +
      +
    6. +
    7. Wrapping Up
    8. +
    + +

    Before You Start

    +

    In order to create your own tasks you will need to make sure you have downloaded tools to edit the following file types:

    +
      +
    • .psd - For this guide we will be using Photoshop but GIMP is a great free alternative.
    • +
    • .lua - This can be done in Notepad in a pinch but at the very least we would recommend Notepad++.
    • +
    +

    In this guide we will be walking through how to make a basic task and you can download all the templates we are using here.

    +

    Last thing to do before you are ready to get started is to unzip that file which should give you 1 .psd file and a folder like this:

    +

    + +

    Code

    +

    Open up 'Task Addon Template' > 'lua' > 'taskmastertasks' and rename '%TASKNAME%.lua' to whatever you want the name of your task to be. In this case we will be creating a task to have a player take damage and survive, so we'll rename it to 'takedamage.lua'.

    +

    Open up that file and you should see something like this:

    +
    local TASK = {}
    +
    +TASK.id = ""
    +TASK.IsKillTask = false
    +TASK.CompleteOnRoundEnd = false
    +
    +TASK.Name = function(ply)
    +    return ""
    +end
    +
    +TASK.Description = function(ply)
    +    return ""
    +end
    +
    +TASK.Initialize = function()
    +end
    +
    +if SERVER then
    +    TASK.CanAssignTask = function(ply)
    +        return true
    +    end
    +
    +    TASK.RequiredFeatures = {}
    +
    +    TASK.OnTaskAssigned = function(ply)
    +    end
    +
    +    TASK.OnTaskRemoved = function(ply)
    +    end
    +
    +    TASK.OnTaskComplete = function(ply)
    +    end
    +end
    +
    +TASKMASTER.RegisterTask(TASK)
    +        
    + +

    Lets break that down piece by piece.

    + +

    Task Table

    +

    First we have this line here:

    +
    local TASK = {}
    +

    You don't need to touch this line. The TASK table will store everything CR for TTT needs to understand your task.

    + +

    Task Properties

    +

    The next chunk here is all about the properties of your task:

    +
    TASK.id = ""  
    +TASK.IsKillTask = false
    +TASK.CompleteOnRoundEnd = false
    + +

    id is the unique identifier for the task and is used to create the the necessary ConVars for the task. It should only contain lowercase characters a-z, without spaces or punctuation.

    +

    IsKillTask marks this task as to whether this task requires the Taskmaster to kill another player themselves.

    +

    CompleteOnRoundEnd determines whether this task automatically completes when the round ends. Tasks with this set will not stop the round from ending to give the Taskmaster additional time to complete their tasks (if it is configured to do so).

    +

    For our task that block of code will now look like this.

    + +
    TASK.id = "takedamage"
    +TASK.IsKillTask = false
    +TASK.CompleteOnRoundEnd = false
    + +

    Name

    +

    The first function in the task file is defining the task's name. This is shown in multiple places in the Taskmaster's UI and is generally used as a quick way of tracking task progress.

    +

    For the task we are creating, we'll start with a basic name and update it with progress later. For now, it should look something like this:

    +
    TASK.Name = function(ply)
    +    return "Take Damage and Survive"
    +end
    + +

    Description

    +

    The next function cares about the description of your task that shows up on the Taskmaster's UI. The description should give information on how the complete the task. Try to keep the length of the description under 75 characters so it doesn't take up too much space.

    +

    The description, much like the task, will start out with a basic sentence and will be updated with progress and more info later. Let's start with just this:

    +
    TASK.Description = function(ply)
    +    return "Get another player to deal damage to you and survive"
    +end
    + +

    Initialize

    +

    This function is used to set up code for the task that needs to run before it has been assigned. The most common usage for this function is creating translation strings on the client side for use in UI displays.

    +

    At this point we're not using an UIs so we'll just leave this empty for now:

    +
    TASK.Initialize = function()
    +end
    + +

    Server-Side

    +

    The next group of lines only run in server-side code, so they are defined within a if SERVER then block:

    +
    if SERVER then
    +    TASK.CanAssignTask = function(ply)
    +        return true
    +    end
    +
    +    TASK.RequiredFeatures = {}
    +
    +    TASK.OnTaskAssigned = function(ply)
    +    end
    +
    +    TASK.OnTaskRemoved = function(ply)
    +    end
    +
    +    TASK.OnTaskComplete = function(ply)
    +    end
    +end
    + +

    CanAssignTask

    +

    The first function is used to determine whether our task can be be assigned to the target player. Some examples of things that could be checked for is the presence of a particular role in the round, the number of living players, or whether the current map has the correct features (props, traitor traps, etc.).

    +

    This task does not have any restrictions, so we'll just leave it as-is:

    +
    TASK.CanAssignTask = function(ply)
    +    return true
    +end
    + +

    RequiredFeatures

    +

    The TASK.RequiredFeatures property represents a table of values indicating which shared task features are being used by this task. Only one task with a specific feature can be active at any given time so this table is used by the task assignment logic to accomplish that.

    +

    See below for the current supported features:

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    OptionDescriptionAdded in
    TASKMASTER_TF_TARGETID_PLAYERICONThis task adds an icon over the head of a targeted player2.3.5
    TASKMASTER_TF_TARGETID_PLAYERTEXTThis task adds text to the mouseover text of a targeted player2.3.5
    TASKMASTER_TF_PROGRESSBARThis task displays a progress bar on the player's screen2.3.5
    TASKMASTER_TF_PARTICLERADIUSThis task draws a radius around some entity or location using particles2.3.5
    +

    For now we are not using any features so we'll leave this empty:

    +
    TASK.RequiredFeatures = {}
    + +

    OnTaskAssigned

    +

    The majority of the logic for a task is set up in this function, called when this task is assigned to a particular player. This method is often used for setting up hooks and timers which track progress and handle task completion.

    +

    As mentioned earlier, this task is for a player to take damage and then survive some number of seconds. With that in mind, our logic can be split into this parts:

    +
      +
    1. Keep track of when this player takes damage
    2. +
    3. If the player is still alive after some number of seconds, mark the task as complete
    4. +
    +

    To accomplish this, we will be adding a hook to track damage to this player and a timer to track survival time. Be sure to name the hook with the player's Steam ID so it is unique across different players (just in case):

    +
    TASK.OnTaskAssigned = function(ply)
    +    timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +        if not IsPlayer(ply) then return end
    +        if not ply:Alive() or ply:IsSpec() then return end
    +    end)
    +
    +    hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken)
    +        if not wasDamageTaken then return end
    +        if entity ~= ply then return end
    +
    +        local attacker = dmginfo:GetAttacker()
    +        if not IsPlayer(attacker) or attacker == entity then return end
    +    end)
    +end
    + +

    We need to know how long they have survived so we will also be tracking when that damage occurred using the SYNC system.

    +
    TASK.OnTaskAssigned = function(ply)
    +    local time = 30
    +    timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +        if not IsPlayer(ply) then return end
    +        if not ply:Alive() or ply:IsSpec() then return end
    +        if not ply.Task_TakeDamageStart then return end
    +        if CurTime() <= ply.Task_TakeDamageStart + time then return end
    +
    +        ply:CompleteTask(TASK.id)
    +    end)
    +
    +    hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken)
    +        if not wasDamageTaken then return end
    +        if entity ~= ply then return end
    +
    +        local attacker = dmginfo:GetAttacker()
    +        if not IsPlayer(attacker) or attacker == entity then return end
    +
    +        ply:SetProperty("Task_TakeDamageStart", CurTime(), ply)
    +    end)
    +end
    + +

    Now that we have the basics of the logic in place, let's make the survival time configurable instead of a hardcoded 30 seconds. To do that we'll create a ConVar and then we can use it in the timer as well as our TASK.Name and TASK.Description functions from before.

    +

    First things first, we'll create the ConVar and add it to the Taskmaster's ConVars table to show on ULX. Back toward the top of the file, we'll add these lines:

    +
    local taskmaster_takedamage_time = CreateConVar("ttt_taskmaster_takedamage_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must survive without taking further damage to complete the 'Take Damage' task", 1, 240)
    +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], {
    +    cvar = "ttt_taskmaster_takedamage_time",
    +    type = ROLE_CONVAR_TYPE_NUM,
    +    decimal = 0
    +})
    +

    Note how the ConVar has the ID of the task in it, to differentiate between the possible tasks.

    +

    Now that the ConVar exists, let's update the TASK.Name and TASK.Description functions to use it. The TASK.Name function will be a little bit more complicated because we're also going to add progress tracking:

    +
    TASK.Name = function(ply)
    +    local name = "Take Damage and Survive"
    +
    +    -- If this function is being used without a player (like in the tutorial screen, for example) don't show progress
    +    if not ply then return name end
    +
    +    local time = taskmaster_takedamage_time:GetInt()
    +    local progress = 0
    +    -- If this task is already completed, just show the total time instead of "0/30" in case the tracking variable is cleared
    +    if table.HasValue(ply.TaskmasterCompletedTasks, TASK.id) then
    +        progress = time
    +    else
    +        -- If we have a start time saved, use that to determine the total progress
    +        local startTime = ply.Task_TakeDamageStart
    +        if startTime then
    +            progress = math.floor(math.max(0, CurTime() - startTime))
    +        end
    +    end
    +
    +    return name .. " (" .. progress .. "/" .. time .. ")"
    +end
    +
    +TASK.Description = function(ply)
    +    local time = taskmaster_takedamage_time:GetInt()
    +    local desc = "Get another player to deal damage to you and survive for " .. time .. " second"
    +    if time ~= 1 then
    +        desc = desc .. "s"
    +    end
    +    return desc
    +end
    +

    And of course, we should update the timer to use the ConVar for tracking survival length as well:

    +
    local time = taskmaster_takedamage_time:GetInt()
    +timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +    if not IsPlayer(ply) then return end
    +    if not ply:Alive() or ply:IsSpec() then return end
    +    if not ply.Task_TakeDamageStart then return end
    +    if CurTime() <= ply.Task_TakeDamageStart + time then return end
    +
    +    ply:CompleteTask(TASK.id)
    +end)
    + +

    A nice quality-of-life feature would be to show the player a progress bar of how much longer they need to survive, so we'll add that too. Since the progress bar is a shared feature, we first need to update TASK.RequiredFeatures:

    +
    TASK.RequiredFeatures = {
    +    TASKMASTER_TF_PROGRESSBAR
    +}
    +
    +

    The progress bar itself has to be handled in the client logic, so we'll want to set up a net message to tell the client to start doing that and call it inside TASK.OnTaskAssigned

    +
    if SERVER then
    +    util.AddNetworkString("TTT_Taskmaster_TakeDamage_Assigned")
    +
    +    TASK.OnTaskAssigned = function(ply)
    +        local time = taskmaster_takedamage_time:GetInt()
    +        timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +            if not IsPlayer(ply) then return end
    +            if not ply:Alive() or ply:IsSpec() then return end
    +            if not ply.Task_TakeDamageStart then return end
    +            if CurTime() <= ply.Task_TakeDamageStart + time then return end
    +
    +            ply:CompleteTask(TASK.id)
    +        end)
    +
    +        hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken)
    +            if not wasDamageTaken then return end
    +            if entity ~= ply then return end
    +
    +            local attacker = dmginfo:GetAttacker()
    +            if not IsPlayer(attacker) or attacker == entity then return end
    +
    +            ply:SetProperty("Task_TakeDamageStart", CurTime(), ply)
    +        end)
    +
    +        net.Start("TTT_Taskmaster_TakeDamage_Assigned")
    +        net.Send(ply)
    +    end
    +end
    +

    In the client code, we need to receive the net message and add a hook to show the UI elements. We also need to modify the TASK.Initialize function to add a translation to show on the UI inside client code. Something like this:

    +
    TASK.Initialize = function()
    +    if CLIENT then
    +        LANG.AddToLanguage("english", "taskmaster_takedamage", "AVOID DAMAGE - {time}")
    +    end
    +end
    +
    +if CLIENT then
    +    net.Receive("TTT_Taskmaster_TakeDamage_Assigned", function()
    +        local client = LocalPlayer()
    +        local time = taskmaster_takedamage_time:GetInt()
    +
    +        hook.Add("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64(), function()
    +            if not client:IsActiveTaskmaster() then return end
    +
    +            local startTime = client.Task_TakeDamageStart
    +            if not startTime then return end
    +
    +            local PT = LANG.GetParamTranslation
    +            local elapsed = math.max(0, CurTime() - startTime)
    +            local remaining = time - elapsed
    +            local message = PT("taskmaster_takedamage", { time = util.SimpleTime(remaining, "%02i:%02i") })
    +            local color = Color(0, 255, 0, 155)
    +
    +            local x = ScrW() / 2.0
    +            local y = ScrH() / 2.0
    +            y = y + (y / 3)
    +
    +            local w = 300
    +            local progress = elapsed / time
    +
    +            CRHUD:PaintProgressBar(x, y, w, color, message, progress)
    +        end)
    +    end)
    +end
    + +

    With that, our main logic for the task is done! We just have to clean things up, and that's where the next two functions come into play.

    + +

    OnTaskRemoved

    +

    This function is called when a task is removed, whether it's from the round ending, a player's role changing, or that player rerolling a task. It is used to stop any timers, remove any hooks, and cleanup and tracking variables that were used.

    +

    In the case of this task, we have a timer, a hook, a tracking variable, and some client code that also needs to be cleaned up so we'll be sending another net message. All told, that looks something like this:

    +
    if SERVER then
    +    util.AddNetworkString("TTT_Taskmaster_TakeDamage_Cleanup")
    +
    +    TASK.OnTaskRemoved = function(ply)
    +        timer.Remove("TTTTaskmasterTakeDamageTimer")
    +
    +        hook.Remove("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64())
    +
    +        ply:ClearProperty("Task_TakeDamageStart", ply)
    +
    +        net.Start("TTT_Taskmaster_TakeDamage_Cleanup")
    +        net.Send(ply)
    +    end
    +end
    +
    +if CLIENT then
    +    net.Receive("TTT_Taskmaster_TakeDamage_Cleanup", function()
    +        local client = LocalPlayer()
    +        hook.Remove("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64())
    +    end)
    +end
    + +

    OnTaskComplete

    +

    As compared to TASK.OnTaskRemoved, this function is called when a task has been completed successfully. In a majority of cases the logic when a task is complete will be the same as when it is removed so you can just piggy-back on TASK.OnTaskRemoved like this:

    +
    if SERVER then
    +    TASK.OnTaskComplete = TASK.OnTaskRemoved
    +end
    + +

    Task Registration

    +

    The last line simply tells CR for TTT to register your task and passes through all the relevant information. You do not need to edit this line. CR for TTT automatically creates an "enabled" convar for your task in the form of ttt_taskmaster_%TASKNAME%_enabled.

    +
    TASKMASTER.RegisterTask(TASK)
    + +

    Example File

    +

    Once you have done that you are finished with coding. You can close your file and move on to creating your workshop icon. One last time before moving on to that, here is the full takedamage.lua file for reference after some consolidation and cleanup:

    +
    local TASK = {}
    +
    +TASK.id = "takedamage"
    +TASK.IsKillTask = false
    +TASK.CompleteOnRoundEnd = false
    +
    +local taskmaster_takedamage_time = CreateConVar("ttt_taskmaster_takedamage_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must survive without taking further damage to complete the 'Take Damage' task", 1, 240)
    +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], {
    +    cvar = "ttt_taskmaster_takedamage_time",
    +    type = ROLE_CONVAR_TYPE_NUM,
    +    decimal = 0
    +})
    +
    +TASK.Name = function(ply)
    +    local name = "Take Damage and Survive"
    +
    +    -- If this function is being used without a player (like in the tutorial screen, for example) don't show progress
    +    if not ply then return name end
    +
    +    local time = taskmaster_takedamage_time:GetInt()
    +    local progress = 0
    +    -- If this task is already completed, just show the total time instead of "0/30" in case the tracking variable is cleared
    +    if table.HasValue(ply.TaskmasterCompletedTasks, TASK.id) then
    +        progress = time
    +    else
    +        -- If we have a start time saved, use that to determine the total progress
    +        local startTime = ply.Task_TakeDamageStart
    +        if startTime then
    +            progress = math.floor(math.max(0, CurTime() - startTime))
    +        end
    +    end
    +
    +    return name .. " (" .. progress .. "/" .. time .. ")"
    +end
    +
    +TASK.Description = function(ply)
    +    local time = taskmaster_takedamage_time:GetInt()
    +    local desc = "Get another player to deal damage to you and survive for " .. time .. " second"
    +    if time ~= 1 then
    +        desc = desc .. "s"
    +    end
    +    return desc
    +end
    +
    +TASK.Initialize = function()
    +    if CLIENT then
    +        LANG.AddToLanguage("english", "taskmaster_takedamage", "AVOID DAMAGE - {time}")
    +    end
    +end
    +
    +if SERVER then
    +    util.AddNetworkString("TTT_Taskmaster_TakeDamage_Assigned")
    +    util.AddNetworkString("TTT_Taskmaster_TakeDamage_Cleanup")
    +
    +    TASK.CanAssignTask = function(ply)
    +        return true
    +    end
    +
    +    TASK.RequiredFeatures = {
    +        TASKMASTER_TF_PROGRESSBAR
    +    }
    +
    +    TASK.OnTaskAssigned = function(ply)
    +        local time = taskmaster_takedamage_time:GetInt()
    +        timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +            if not IsPlayer(ply) then return end
    +            if not ply:Alive() or ply:IsSpec() then return end
    +            if not ply.Task_TakeDamageStart then return end
    +            if CurTime() <= ply.Task_TakeDamageStart + time then return end
    +
    +            ply:CompleteTask(TASK.id)
    +        end)
    +
    +        hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken)
    +            if not wasDamageTaken then return end
    +            if entity ~= ply then return end
    +
    +            local attacker = dmginfo:GetAttacker()
    +            if not IsPlayer(attacker) or attacker == entity then return end
    +
    +            ply:SetProperty("Task_TakeDamageStart", CurTime(), ply)
    +        end)
    +
    +        net.Start("TTT_Taskmaster_TakeDamage_Assigned")
    +        net.Send(ply)
    +    end
    +
    +    TASK.OnTaskRemoved = function(ply)
    +        timer.Remove("TTTTaskmasterTakeDamageTimer")
    +
    +        hook.Remove("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64())
    +
    +        ply:ClearProperty("Task_TakeDamageStart", ply)
    +
    +        net.Start("TTT_Taskmaster_TakeDamage_Cleanup")
    +        net.Send(ply)
    +    end
    +
    +    TASK.OnTaskComplete = TASK.OnTaskRemoved
    +end
    +
    +if CLIENT then
    +    net.Receive("TTT_Taskmaster_TakeDamage_Assigned", function()
    +        local client = LocalPlayer()
    +        local time = taskmaster_takedamage_time:GetInt()
    +
    +        hook.Add("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64(), function()
    +            if not client:IsActiveTaskmaster() then return end
    +
    +            local startTime = client.Task_TakeDamageStart
    +            if not startTime then return end
    +
    +            local PT = LANG.GetParamTranslation
    +            local elapsed = math.max(0, CurTime() - startTime)
    +            local remaining = time - elapsed
    +            local message = PT("taskmaster_takedamage", { time = util.SimpleTime(remaining, "%02i:%02i") })
    +            local color = Color(0, 255, 0, 155)
    +
    +            local x = ScrW() / 2.0
    +            local y = ScrH() / 2.0
    +            y = y + (y / 3)
    +
    +            local w = 300
    +            local progress = elapsed / time
    +
    +            CRHUD:PaintProgressBar(x, y, w, color, message, progress)
    +        end)
    +    end)
    +
    +    net.Receive("TTT_Taskmaster_TakeDamage_Cleanup", function()
    +        local client = LocalPlayer()
    +        hook.Remove("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64())
    +    end)
    +end
    +
    +TASKMASTER.RegisterTask(TASK)
    + +

    Uploading Your Addon

    +

    Your task is almost ready to go! The last thing you need to do is upload your addon to the steam workshop. Before you can do that there are 2 more files you need to make: 'addon.json' and your workshop icon.

    + +

    addon.json

    +

    'addon.json' is found inside the 'Task Addon Template' folder. Open it up and set %TASKNAME% to whatever the name of your role is.

    +

    For example 'addon.json' for the Summoner role looks like this:

    +
    {
    +  "title": "'Take Damage and Survive' Task for Taskmaster (CR for TTT)",
    +  "type": "ServerContent",
    +  "ignore": []
    +}
    +        
    + +

    Workshop Icon

    +

    Now is the time to open the last template. This step is completely optional, you can use whatever workshop icon you want! However, if you want to keep it consistent with CR for TTT roles and related addons open up 'Workshop Icon Template.psd'.

    +

    The icon is already set up to have the Taskmaster icon and team color set, so all you have to do is add text with your task name.

    + +

    In the case of the task we're working on it will look something like this:

    +

    +

    Once you have done that you are ready to hit save. You want to put your icon in the same folder as 'Task Addon Template', not inside it! Steam workshop needs the icon file to be a .jpg so make sure you save it as the right format.

    + +

    Here is my finalised 'TakeDamageTask.jpg':

    +

    + +

    Folder Name

    +

    While we are at it now is a good time to rename 'Task Addon Template' because it's not a template anymore, it's yours! Name it the same thing as whatever you named your workshop icon.

    +

    Before you get to uploading now is a great time to test your addon. In Steam right click on Garry's Mod and click 'Manage' > 'Browse local files'. A folder should open up which contains your GMod files. Open 'garrysmod' > 'addons' and paste the folder you just renamed in here. When you next boot up GMod your addon should load and you can test it out before uploading to the workshop.

    + +

    Final Checks

    +

    Before you upload your addon, now is a great time to check that your file structure is all correct! For reference here is the file structure of my completed task addon:

    +
    ├─ TakeDamageTask.jpg
    +└─ TakeDamageTask
    +   ├─ addon.json
    +   └─ lua
    +      └─ taskmastertasks
    +         └─ takedamage.lua
    +        
    + +

    Uploading

    +

    Now you are ready to upload your addon. In Steam right click on Garry's Mod and click 'Manage' > 'Browse local files'. A folder should open up which contains your GMod files. Open 'bin' and you should see a file called 'gmad.exe'. Drag and drop your addon folder onto 'gmad.exe'. You should see a new file appear next to your addon folder. It should have the same name as your folder but with the file extension '.gma'. In my case we now have a new file called 'TakeDamageTask.gma'.

    +

    Go back to the same folder where you found 'gmad.exe'. Click on the address bar up the top and type 'cmd'. This should open up the command prompt where you need to type this command to upload your addon.

    +
    gmpublish.exe create -addon %PATHTOGMA% -icon %PATHTOJPG%
    + +

    Replace %PATHTOGMA% and %PATHTOJPG% with the file paths to your .gma addon file and .jpg workshop icon respectively.

    +

    For my Summoner addon it looks like this:

    +
    gmpublish.exe create -addon "C:\Development\GMod\TakeDamageTask.gma" -icon "C:\Development\GMod\TakeDamageTask.jpg"
    + +

    Hit enter and you should see your addon upload to Steam! Once it is done you can view your addon by opening steam, hovering over your name up the top and clicking 'Content'. Click the 'Workshop Items' tab and your addon should be there! Here you can give your addon a description and then change its visibility to public. (Note: If you ever need to update a pre-existing addon you can read how to do that here.)

    +

    Once you have made your addon public, we would love to hear about it! Jump into our discord server and show us what you have made!

    + +

    Wrapping Up

    +

    If you have any problems or questions please jump into the Custom Roles for TTT Discord Server! We love to see what everyone has making and there are almost always people online willing to help out if you need a hand.

    +
    +
    + + \ No newline at end of file diff --git a/docs/tutorials/create_your_own_role.html b/docs/tutorials/create_your_own_role.html index 8349d87de..19e544fc6 100644 --- a/docs/tutorials/create_your_own_role.html +++ b/docs/tutorials/create_your_own_role.html @@ -1078,7 +1078,7 @@

    .vmt Files

    Uploading Your Addon

    -

    Your role is almost ready to go! The last thing you need to do is upload your addon to the steam workshop. Before you can do that there are 2 more files you need to make 'addon.json' and your workshop icon.

    +

    Your role is almost ready to go! The last thing you need to do is upload your addon to the steam workshop. Before you can do that there are 2 more files you need to make: 'addon.json' and your workshop icon.

    addon.json

    'addon.json' is found inside the 'Role Addon Template' folder. Open it up and set %NAME% to whatever the name of your role is.

    @@ -1092,7 +1092,7 @@

    addon.json

    Workshop Icon

    Now is the time to open the last template. This step is completely optional, you can use whatever workshop icon you want! However, if you want to keep it consistent with other CR for TTT roles open up 'Workshop Icon Template.psd'.

    -

    Right about now you should be getting a feeling of déjà vu because out friend the white dashed outline is back. Copy your role icon onto the 'Icon' layer and this time instead of a shadow you should see a thick coloured outline. Resize your icon so the white all fits within the guide, it doesn't matter if the coloured outline spills outside the guide. Hide the 'Icon Guide' layer and you are almost good to go.

    +

    Right about now you should be getting a feeling of déjà vu because our friend the white dashed outline is back. Copy your role icon onto the 'Icon' layer and this time instead of a shadow you should see a thick coloured outline. Resize your icon so the white all fits within the guide, it doesn't matter if the coloured outline spills outside the guide. Hide the 'Icon Guide' layer and you are almost good to go.

    Each team in CR for TTT has it's own colour it is identified by and this template works best if you switch out the background to match that colour. In the layers window you should see four different background colours. Hide all the ones that don't match your roles team. You should also see four different stroke outline effects on the 'Role Icon' layer. Hide all the ones that don't match the colour of the background.

    In the case of the Summoner or another traitor role it should look like this:

    diff --git a/templates/taskmaster/Task Addon Template/addon.json b/templates/taskmaster/Task Addon Template/addon.json new file mode 100644 index 000000000..308a7d967 --- /dev/null +++ b/templates/taskmaster/Task Addon Template/addon.json @@ -0,0 +1,5 @@ +{ + "title": "'%TASKNAME%' Task for Taskmaster (CR for TTT)", + "type": "ServerContent", + "ignore": [] +} \ No newline at end of file diff --git a/templates/taskmaster/Task Addon Template/lua/taskmastertasks/%TASKNAME%.lua b/templates/taskmaster/Task Addon Template/lua/taskmastertasks/%TASKNAME%.lua new file mode 100644 index 000000000..a7355bf1f --- /dev/null +++ b/templates/taskmaster/Task Addon Template/lua/taskmastertasks/%TASKNAME%.lua @@ -0,0 +1,35 @@ +local TASK = {} + +TASK.id = "" +TASK.IsKillTask = false +TASK.CompleteOnRoundEnd = false + +TASK.Name = function(ply) + return "" +end + +TASK.Description = function(ply) + return "" +end + +TASK.Initialize = function() +end + +if SERVER then + TASK.CanAssignTask = function(ply) + return true + end + + TASK.RequiredFeatures = {} + + TASK.OnTaskAssigned = function(ply) + end + + TASK.OnTaskRemoved = function(ply) + end + + TASK.OnTaskComplete = function(ply) + end +end + +TASKMASTER.RegisterTask(TASK) \ No newline at end of file diff --git a/templates/taskmaster/Workshop Icon Template.psd b/templates/taskmaster/Workshop Icon Template.psd new file mode 100644 index 0000000000000000000000000000000000000000..682d78958bcb16e2fcb9a0a3f21d64355f754002 GIT binary patch literal 205763 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 R_BQaJ*amF-`gVK${|C}a;!ywq literal 0 HcmV?d00001 From 64a9d3ea56ae4fed5b4034d18282f83bdc9376c6 Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 28 Aug 2025 15:44:25 -0400 Subject: [PATCH 134/140] Cleanup --- docs/api/methods_global.html | 2 +- docs/tutorials/create_taskmaster_tasks.html | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/api/methods_global.html b/docs/api/methods_global.html index a7208a2c2..7d75de4a7 100644 --- a/docs/api/methods_global.html +++ b/docs/api/methods_global.html @@ -273,7 +273,7 @@

    RegisterRole(roleTable)

    - Registers a role with Custom Roles for TTT. See here for instructions on how to create a role and role table structure.
    + Registers a role with Custom Roles for TTT. See here for instructions on how to create a role and role table structure.
    Realm: Client and Server
    Added in: 1.0.9

    diff --git a/docs/tutorials/create_taskmaster_tasks.html b/docs/tutorials/create_taskmaster_tasks.html index d417c3284..5efbfde85 100644 --- a/docs/tutorials/create_taskmaster_tasks.html +++ b/docs/tutorials/create_taskmaster_tasks.html @@ -187,22 +187,22 @@

    RequiredFeatures

    - TASKMASTER_TF_TARGETID_PLAYERICON + TASKMASTER_TF_TARGETID_PLAYERICON This task adds an icon over the head of a targeted player 2.3.5 - TASKMASTER_TF_TARGETID_PLAYERTEXT + TASKMASTER_TF_TARGETID_PLAYERTEXT This task adds text to the mouseover text of a targeted player 2.3.5 - TASKMASTER_TF_PROGRESSBAR + TASKMASTER_TF_PROGRESSBAR This task displays a progress bar on the player's screen 2.3.5 - TASKMASTER_TF_PARTICLERADIUS + TASKMASTER_TF_PARTICLERADIUS This task draws a radius around some entity or location using particles 2.3.5 @@ -313,7 +313,7 @@

    OnTaskAssigned

    TASKMASTER_TF_PROGRESSBAR } -

    The progress bar itself has to be handled in the client logic, so we'll want to set up a net message to tell the client to start doing that and call it inside TASK.OnTaskAssigned

    +

    The progress bar itself has to be handled in the client logic, so we'll want to set up a net message to tell the client to start doing that and call it inside TASK.OnTaskAssigned.

    if SERVER then
         util.AddNetworkString("TTT_Taskmaster_TakeDamage_Assigned")
     
    @@ -416,7 +416,7 @@ 

    Task Registration
    TASKMASTER.RegisterTask(TASK)

    Example File

    -

    Once you have done that you are finished with coding. You can close your file and move on to creating your workshop icon. One last time before moving on to that, here is the full takedamage.lua file for reference after some consolidation and cleanup:

    +

    Once you have done that you are finished with coding. You can close your file and move on to creating your workshop icon. One last time before moving on to that, here is the full takedamage.lua file for reference after some consolidation and cleanup:

    local TASK = {}
     
     TASK.id = "takedamage"
    
    From 57d1be65e87d238188efa7926e8e1dd10647d717 Mon Sep 17 00:00:00 2001
    From: Malivil 
    Date: Thu, 28 Aug 2025 15:44:35 -0400
    Subject: [PATCH 135/140] Added Markdown version of Taskmaster task tutorial
    
    ---
     CREATE_TASKMASTER_TASKS.md | 645 +++++++++++++++++++++++++++++++++++++
     1 file changed, 645 insertions(+)
    
    diff --git a/CREATE_TASKMASTER_TASKS.md b/CREATE_TASKMASTER_TASKS.md
    index e69de29bb..bd6139b35 100644
    --- a/CREATE_TASKMASTER_TASKS.md
    +++ b/CREATE_TASKMASTER_TASKS.md
    @@ -0,0 +1,645 @@
    +# Creating Your Own Custom Task for the Taskmaster
    +
    +## Table of Contents
    +1. [Before You Start](#Before-You-Start)
    +1. [Code](#Code)
    +   1. [Task Table](#Task-Table)
    +   1. [Task Properties](#Task-Properties)
    +   1. [Name](#Name)
    +   1. [Description](#Description)
    +   1. [Initialize](#Initialize)
    +   1. [Server-Side](#Server-Side)
    +        1. [CanAssignTask](#CanAssignTask)
    +        1. [RequiredFeatures](#RequiredFeatures)
    +        1. [OnTaskAssigned](#OnTaskAssigned)
    +        1. [OnTaskRemoved](#OnTaskRemoved)
    +        1. [OnTaskComplete](#OnTaskComplete)
    +   1. [Task Registration](#Task-Registration)
    +   1. [Example File](#Example-File)
    +1. [Uploading Your Addon](#Uploading-Your-Addon)
    +   1. [addon.json](#addonjson)
    +   1. [Workshop Icon](#Workshop-Icon)
    +   1. [Folder Name](#Folder-Name)
    +   1. [Final Checks](#Final-Checks)
    +   1. [Uploading](#Uploading)
    +1. [Wrapping Up](#Wrapping-Up)
    +
    +## Before You Start
    +In order to create your own tasks you will need to make sure you have downloaded tools to edit the following file types:
    +
    +- **.psd** - For this guide we will be using Photoshop but [GIMP](https://www.gimp.org/) is a great free alternative.
    +- **.lua** - This can be done in Notepad in a pinch but at the very least we would recommend [Notepad++](https://notepad-plus-plus.org/).
    +
    +In this guide we will be walking through how to make a basic task and you can download all the templates we are using [here](/templates/taskmaster).
    +
    +Last thing to do before you are ready to get started is to unzip that file which should give you 1 .psd file and a folder like this:
    +
    +![TemplateContents.png](https://i.imgur.com/W9Z00s1.png)
    +
    +## Code
    +Open up 'Task Addon Template' > 'lua' > 'taskmastertasks' and rename '%TASKNAME%.lua' to whatever you want the name of your task to be. In this case we will be creating a task to have a player take damage and survive, so we'll rename it to 'takedamage.lua'.
    +
    +Open up that file and you should see something like this:
    +
    +```lua
    +local TASK = {}
    +
    +TASK.id = ""
    +TASK.IsKillTask = false
    +TASK.CompleteOnRoundEnd = false
    +
    +TASK.Name = function(ply)
    +    return ""
    +end
    +
    +TASK.Description = function(ply)
    +    return ""
    +end
    +
    +TASK.Initialize = function()
    +end
    +
    +if SERVER then
    +    TASK.CanAssignTask = function(ply)
    +        return true
    +    end
    +
    +    TASK.RequiredFeatures = {}
    +
    +    TASK.OnTaskAssigned = function(ply)
    +    end
    +
    +    TASK.OnTaskRemoved = function(ply)
    +    end
    +
    +    TASK.OnTaskComplete = function(ply)
    +    end
    +end
    +
    +TASKMASTER.RegisterTask(TASK)
    +```
    +
    +Lets break that down piece by piece.
    +
    +### Task Table
    +First we have this line here:
    +
    +```lua
    +local TASK = {}
    +```
    +
    +You don't need to touch this line. The TASK table will store everything CR for TTT needs to understand your task.
    +
    +### Task Properties
    +The next chunk here is all about the properties of your task:
    +
    +```lua
    +TASK.id = ""  
    +TASK.IsKillTask = false
    +TASK.CompleteOnRoundEnd = false
    +```
    +
    +`id` is the unique identifier for the task and is used to create the the necessary ConVars for the task. It should only contain lowercase characters a-z, without spaces or punctuation.
    +
    +`IsKillTask` marks this task as to whether this task requires the Taskmaster to kill another player themselves.
    +
    +`CompleteOnRoundEnd` determines whether this task automatically completes when the round ends. Tasks with this set will not stop the round from ending to give the Taskmaster additional time to complete their tasks (if it is configured to do so).
    +
    +For our task that block of code will now look like this.
    +
    +```lua
    +TASK.id = "takedamage"
    +TASK.IsKillTask = false
    +TASK.CompleteOnRoundEnd = false
    +```
    +
    +### Name
    +The first function in the task file is defining the task's name. This is shown in multiple places in the Taskmaster's UI and is generally used as a quick way of tracking task progress.
    +
    +For the task we are creating, we'll start with a basic name and update it with progress later. For now, it should look something like this:
    +
    +```lua
    +TASK.Name = function(ply)
    +    return "Take Damage and Survive"
    +end
    +```
    +
    +### Description
    +The next function cares about the description of your task that shows up on the Taskmaster's UI. The description should give information on how the complete the task. Try to keep the length of the description under 75 characters so it doesn't take up too much space.
    +
    +The description, much like the task, will start out with a basic sentence and will be updated with progress and more info later. Let's start with just this:
    +
    +```lua
    +TASK.Description = function(ply)
    +    return "Get another player to deal damage to you and survive"
    +end
    +```
    +
    +### Initialize
    +This function is used to set up code for the task that needs to run before it has been assigned. The most common usage for this function is creating translation strings on the client side for use in UI displays.
    +
    +At this point we're not using an UIs so we'll just leave this empty for now:
    +
    +```lua
    +TASK.Initialize = function()
    +end
    +```
    +
    +### Server-Side
    +The next group of lines only run in server-side code, so they are defined within a `if SERVER then` block:
    +
    +```lua
    +if SERVER then
    +    TASK.CanAssignTask = function(ply)
    +        return true
    +    end
    +
    +    TASK.RequiredFeatures = {}
    +
    +    TASK.OnTaskAssigned = function(ply)
    +    end
    +
    +    TASK.OnTaskRemoved = function(ply)
    +    end
    +
    +    TASK.OnTaskComplete = function(ply)
    +    end
    +end
    +```
    +
    +#### CanAssignTask
    +The first function is used to determine whether our task can be be assigned to the target player. Some examples of things that could be checked for is the presence of a particular role in the round, the number of living players, or whether the current map has the correct features (props, traitor traps, etc.).
    +
    +This task does not have any restrictions, so we'll just leave it as-is:
    +
    +```lua
    +TASK.CanAssignTask = function(ply)
    +    return true
    +end
    +```
    +
    +#### RequiredFeatures
    +The `TASK.RequiredFeatures` property represents a table of values indicating which shared task features are being used by this task. Only one task with a specific feature can be active at any given time so this table is used by the task assignment logic to accomplish that.
    +
    +See below for the current supported features:
    +| Option                             | Description                                                             | Added in |
    +|------------------------------------|-------------------------------------------------------------------------|----------|
    +| `TASKMASTER_TF_TARGETID_PLAYERICON`| This task adds an icon over the head of a targeted player               | 2.3.5    |
    +| `TASKMASTER_TF_TARGETID_PLAYERTEXT`| This task adds text to the mouseover text of a targeted player          | 2.3.5    |
    +| `TASKMASTER_TF_PROGRESSBAR`        | This task displays a progress bar on the player's screen                | 2.3.5    |
    +| `TASKMASTER_TF_PARTICLERADIUS`     | This task draws a radius around some entity or location using particles | 2.3.5    |
    +
    +For now we are not using any features so we'll leave this empty:
    +
    +```lua
    +TASK.RequiredFeatures = {}
    +```
    +
    +#### OnTaskAssigned
    +The majority of the logic for a task is set up in this function, called when this task is assigned to a particular player. This method is often used for setting up hooks and timers which track progress and handle task completion.
    +
    +As mentioned earlier, this task is for a player to take damage and then survive some number of seconds. With that in mind, our logic can be split into this parts:
    +1. Keep track of when this player takes damage
    +1. If the player is still alive after some number of seconds, mark the task as complete
    +
    +To accomplish this, we will be adding a hook to track damage to this player and a timer to track survival time. Be sure to name the hook with the player's Steam ID so it is unique across different players (just in case):
    +
    +```lua
    +TASK.OnTaskAssigned = function(ply)
    +    timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +        if not IsPlayer(ply) then return end
    +        if not ply:Alive() or ply:IsSpec() then return end
    +    end)
    +
    +    hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken)
    +        if not wasDamageTaken then return end
    +        if entity ~= ply then return end
    +
    +        local attacker = dmginfo:GetAttacker()
    +        if not IsPlayer(attacker) or attacker == entity then return end
    +    end)
    +end
    +```
    +
    +We need to know how long they have survived so we will also be tracking when that damage occurred using the [SYNC](./API/METHODS_SYNC.md) system.
    +
    +```lua
    +TASK.OnTaskAssigned = function(ply)
    +    local time = 30
    +    timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +        if not IsPlayer(ply) then return end
    +        if not ply:Alive() or ply:IsSpec() then return end
    +        if not ply.Task_TakeDamageStart then return end
    +        if CurTime() <= ply.Task_TakeDamageStart + time then return end
    +
    +        ply:CompleteTask(TASK.id)
    +    end)
    +
    +    hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken)
    +        if not wasDamageTaken then return end
    +        if entity ~= ply then return end
    +
    +        local attacker = dmginfo:GetAttacker()
    +        if not IsPlayer(attacker) or attacker == entity then return end
    +
    +        ply:SetProperty("Task_TakeDamageStart", CurTime(), ply)
    +    end)
    +end
    +```
    +
    +Now that we have the basics of the logic in place, let's make the survival time configurable instead of a hardcoded 30 seconds. To do that we'll create a ConVar and then we can use it in the timer as well as our `TASK.Name` and `TASK.Description` functions from before.
    +
    +First things first, we'll create the ConVar and add it to the Taskmaster's ConVars table to show on ULX. Back toward the top of the file, we'll add these lines:
    +
    +```lua
    +local taskmaster_takedamage_time = CreateConVar("ttt_taskmaster_takedamage_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must survive without taking further damage to complete the 'Take Damage' task", 1, 240)
    +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], {
    +    cvar = "ttt_taskmaster_takedamage_time",
    +    type = ROLE_CONVAR_TYPE_NUM,
    +    decimal = 0
    +})
    +```
    +
    +Note how the ConVar has the ID of the task in it, to differentiate between the possible tasks.
    +
    +Now that the ConVar exists, let's update the `TASK.Name` and `TASK.Description` functions to use it. The `TASK.Name` function will be a little bit more complicated because we're also going to add progress tracking:
    +
    +```lua
    +TASK.Name = function(ply)
    +    local name = "Take Damage and Survive"
    +
    +    -- If this function is being used without a player (like in the tutorial screen, for example) don't show progress
    +    if not ply then return name end
    +
    +    local time = taskmaster_takedamage_time:GetInt()
    +    local progress = 0
    +    -- If this task is already completed, just show the total time instead of "0/30" in case the tracking variable is cleared
    +    if table.HasValue(ply.TaskmasterCompletedTasks, TASK.id) then
    +        progress = time
    +    else
    +        -- If we have a start time saved, use that to determine the total progress
    +        local startTime = ply.Task_TakeDamageStart
    +        if startTime then
    +            progress = math.floor(math.max(0, CurTime() - startTime))
    +        end
    +    end
    +
    +    return name .. " (" .. progress .. "/" .. time .. ")"
    +end
    +
    +TASK.Description = function(ply)
    +    local time = taskmaster_takedamage_time:GetInt()
    +    local desc = "Get another player to deal damage to you and survive for " .. time .. " second"
    +    if time ~= 1 then
    +        desc = desc .. "s"
    +    end
    +    return desc
    +end
    +```
    +
    +And of course, we should update the timer to use the ConVar for tracking survival length as well:
    +
    +```lua
    +local time = taskmaster_takedamage_time:GetInt()
    +timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +    if not IsPlayer(ply) then return end
    +    if not ply:Alive() or ply:IsSpec() then return end
    +    if not ply.Task_TakeDamageStart then return end
    +    if CurTime() <= ply.Task_TakeDamageStart + time then return end
    +
    +    ply:CompleteTask(TASK.id)
    +end)
    +```
    +A nice quality-of-life feature would be to show the player a progress bar of how much longer they need to survive, so we'll add that too. Since the progress bar is a shared feature, we first need to update `TASK.RequiredFeatures`:
    +
    +```lua
    +TASK.RequiredFeatures = {
    +    TASKMASTER_TF_PROGRESSBAR
    +}
    +```
    +
    +The progress bar itself has to be handled in the client logic, so we'll want to set up a `net` message to tell the client to start doing that and call it inside `TASK.OnTaskAssigned`.
    +```lua
    +if SERVER then
    +    util.AddNetworkString("TTT_Taskmaster_TakeDamage_Assigned")
    +
    +    TASK.OnTaskAssigned = function(ply)
    +        local time = taskmaster_takedamage_time:GetInt()
    +        timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +            if not IsPlayer(ply) then return end
    +            if not ply:Alive() or ply:IsSpec() then return end
    +            if not ply.Task_TakeDamageStart then return end
    +            if CurTime() <= ply.Task_TakeDamageStart + time then return end
    +
    +            ply:CompleteTask(TASK.id)
    +        end)
    +
    +        hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken)
    +            if not wasDamageTaken then return end
    +            if entity ~= ply then return end
    +
    +            local attacker = dmginfo:GetAttacker()
    +            if not IsPlayer(attacker) or attacker == entity then return end
    +
    +            ply:SetProperty("Task_TakeDamageStart", CurTime(), ply)
    +        end)
    +
    +        net.Start("TTT_Taskmaster_TakeDamage_Assigned")
    +        net.Send(ply)
    +    end
    +end
    +```
    +
    +In the client code, we need to receive the `net` message and add a hook to show the UI elements. We also need to modify the `TASK.Initialize` function to add a translation to show on the UI inside client code. Something like this:
    +
    +```lua
    +TASK.Initialize = function()
    +    if CLIENT then
    +        LANG.AddToLanguage("english", "taskmaster_takedamage", "AVOID DAMAGE - {time}")
    +    end
    +end
    +
    +if CLIENT then
    +    net.Receive("TTT_Taskmaster_TakeDamage_Assigned", function()
    +        local client = LocalPlayer()
    +        local time = taskmaster_takedamage_time:GetInt()
    +
    +        hook.Add("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64(), function()
    +            if not client:IsActiveTaskmaster() then return end
    +
    +            local startTime = client.Task_TakeDamageStart
    +            if not startTime then return end
    +
    +            local PT = LANG.GetParamTranslation
    +            local elapsed = math.max(0, CurTime() - startTime)
    +            local remaining = time - elapsed
    +            local message = PT("taskmaster_takedamage", { time = util.SimpleTime(remaining, "%02i:%02i") })
    +            local color = Color(0, 255, 0, 155)
    +
    +            local x = ScrW() / 2.0
    +            local y = ScrH() / 2.0
    +            y = y + (y / 3)
    +
    +            local w = 300
    +            local progress = elapsed / time
    +
    +            CRHUD:PaintProgressBar(x, y, w, color, message, progress)
    +        end)
    +    end)
    +end
    +```
    +
    +With that, our main logic for the task is done! We just have to clean things up, and that's where the next two functions come into play.
    +
    +#### OnTaskRemoved
    +This function is called when a task is removed, whether it's from the round ending, a player's role changing, or that player rerolling a task. It is used to stop any timers, remove any hooks, and cleanup and tracking variables that were used.
    +
    +In the case of this task, we have a timer, a hook, a tracking variable, and some client code that also needs to be cleaned up so we'll be sending another `net` message. All told, that looks something like this:
    +
    +```lua
    +if SERVER then
    +    util.AddNetworkString("TTT_Taskmaster_TakeDamage_Cleanup")
    +
    +    TASK.OnTaskRemoved = function(ply)
    +        timer.Remove("TTTTaskmasterTakeDamageTimer")
    +
    +        hook.Remove("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64())
    +
    +        ply:ClearProperty("Task_TakeDamageStart", ply)
    +
    +        net.Start("TTT_Taskmaster_TakeDamage_Cleanup")
    +        net.Send(ply)
    +    end
    +end
    +
    +if CLIENT then
    +    net.Receive("TTT_Taskmaster_TakeDamage_Cleanup", function()
    +        local client = LocalPlayer()
    +        hook.Remove("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64())
    +    end)
    +end
    +```
    +
    +#### OnTaskComplete
    +As compared to `TASK.OnTaskRemoved`, this function is called when a task has been completed successfully. In a majority of cases the logic when a task is complete will be the same as when it is removed so you can just piggy-back on `TASK.OnTaskRemoved` like this:
    +
    +```lua
    +if SERVER then
    +    TASK.OnTaskComplete = TASK.OnTaskRemoved
    +end
    +```
    +
    +### Task Registration
    +The last line simply tells CR for TTT to register your task and passes through all the relevant information. You do not need to edit this line. CR for TTT automatically creates an "enabled" convar for your task in the form of `ttt_taskmaster_%TASKNAME%_enabled`.
    +
    +```lua
    +TASKMASTER.RegisterTask(TASK)
    +```
    +
    +### Example File
    +Once you have done that you are finished with coding. You can close your file and move on to creating your workshop icon. One last time before moving on to that, here is the full `takedamage.lua` file for reference after some consolidation and cleanup:
    +
    +```lua
    +local TASK = {}
    +
    +TASK.id = "takedamage"
    +TASK.IsKillTask = false
    +TASK.CompleteOnRoundEnd = false
    +
    +local taskmaster_takedamage_time = CreateConVar("ttt_taskmaster_takedamage_time", "60", FCVAR_REPLICATED, "The time (in seconds) a player must survive without taking further damage to complete the 'Take Damage' task", 1, 240)
    +table.insert(ROLE_CONVARS[ROLE_TASKMASTER], {
    +    cvar = "ttt_taskmaster_takedamage_time",
    +    type = ROLE_CONVAR_TYPE_NUM,
    +    decimal = 0
    +})
    +
    +TASK.Name = function(ply)
    +    local name = "Take Damage and Survive"
    +
    +    -- If this function is being used without a player (like in the tutorial screen, for example) don't show progress
    +    if not ply then return name end
    +
    +    local time = taskmaster_takedamage_time:GetInt()
    +    local progress = 0
    +    -- If this task is already completed, just show the total time instead of "0/30" in case the tracking variable is cleared
    +    if table.HasValue(ply.TaskmasterCompletedTasks, TASK.id) then
    +        progress = time
    +    else
    +        -- If we have a start time saved, use that to determine the total progress
    +        local startTime = ply.Task_TakeDamageStart
    +        if startTime then
    +            progress = math.floor(math.max(0, CurTime() - startTime))
    +        end
    +    end
    +
    +    return name .. " (" .. progress .. "/" .. time .. ")"
    +end
    +
    +TASK.Description = function(ply)
    +    local time = taskmaster_takedamage_time:GetInt()
    +    local desc = "Get another player to deal damage to you and survive for " .. time .. " second"
    +    if time ~= 1 then
    +        desc = desc .. "s"
    +    end
    +    return desc
    +end
    +
    +TASK.Initialize = function()
    +    if CLIENT then
    +        LANG.AddToLanguage("english", "taskmaster_takedamage", "AVOID DAMAGE - {time}")
    +    end
    +end
    +
    +if SERVER then
    +    util.AddNetworkString("TTT_Taskmaster_TakeDamage_Assigned")
    +    util.AddNetworkString("TTT_Taskmaster_TakeDamage_Cleanup")
    +
    +    TASK.CanAssignTask = function(ply)
    +        return true
    +    end
    +
    +    TASK.RequiredFeatures = {
    +        TASKMASTER_TF_PROGRESSBAR
    +    }
    +
    +    TASK.OnTaskAssigned = function(ply)
    +        local time = taskmaster_takedamage_time:GetInt()
    +        timer.Create("TTTTaskmasterTakeDamageTimer", 0.1, 0, function()
    +            if not IsPlayer(ply) then return end
    +            if not ply:Alive() or ply:IsSpec() then return end
    +            if not ply.Task_TakeDamageStart then return end
    +            if CurTime() <= ply.Task_TakeDamageStart + time then return end
    +
    +            ply:CompleteTask(TASK.id)
    +        end)
    +
    +        hook.Add("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64(), function(entity, dmginfo, wasDamageTaken)
    +            if not wasDamageTaken then return end
    +            if entity ~= ply then return end
    +
    +            local attacker = dmginfo:GetAttacker()
    +            if not IsPlayer(attacker) or attacker == entity then return end
    +
    +            ply:SetProperty("Task_TakeDamageStart", CurTime(), ply)
    +        end)
    +
    +        net.Start("TTT_Taskmaster_TakeDamage_Assigned")
    +        net.Send(ply)
    +    end
    +
    +    TASK.OnTaskRemoved = function(ply)
    +        timer.Remove("TTTTaskmasterTakeDamageTimer")
    +
    +        hook.Remove("PostEntityTakeDamage", "Taskmaster_TakeDamage_PostEntityTakeDamage_" .. ply:SteamID64())
    +
    +        ply:ClearProperty("Task_TakeDamageStart", ply)
    +
    +        net.Start("TTT_Taskmaster_TakeDamage_Cleanup")
    +        net.Send(ply)
    +    end
    +
    +    TASK.OnTaskComplete = TASK.OnTaskRemoved
    +end
    +
    +if CLIENT then
    +    net.Receive("TTT_Taskmaster_TakeDamage_Assigned", function()
    +        local client = LocalPlayer()
    +        local time = taskmaster_takedamage_time:GetInt()
    +
    +        hook.Add("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64(), function()
    +            if not client:IsActiveTaskmaster() then return end
    +
    +            local startTime = client.Task_TakeDamageStart
    +            if not startTime then return end
    +
    +            local PT = LANG.GetParamTranslation
    +            local elapsed = math.max(0, CurTime() - startTime)
    +            local remaining = time - elapsed
    +            local message = PT("taskmaster_takedamage", { time = util.SimpleTime(remaining, "%02i:%02i") })
    +            local color = Color(0, 255, 0, 155)
    +
    +            local x = ScrW() / 2.0
    +            local y = ScrH() / 2.0
    +            y = y + (y / 3)
    +
    +            local w = 300
    +            local progress = elapsed / time
    +
    +            CRHUD:PaintProgressBar(x, y, w, color, message, progress)
    +        end)
    +    end)
    +
    +    net.Receive("TTT_Taskmaster_TakeDamage_Cleanup", function()
    +        local client = LocalPlayer()
    +        hook.Remove("HUDPaint", "Taskmaster_TakeDamage_HUDPaint_" .. client:SteamID64())
    +    end)
    +end
    +
    +TASKMASTER.RegisterTask(TASK)
    +```
    +
    +## Uploading Your Addon
    +Your task is almost ready to go! The last thing you need to do is upload your addon to the steam workshop. Before you can do that there are 2 more files you need to make: 'addon.json' and your workshop icon.
    +addon.json
    +
    +'addon.json' is found inside the 'Task Addon Template' folder. Open it up and set `%TASKNAME%` to whatever the name of your role is.
    +
    +For example 'addon.json' for the Summoner role looks like this:
    +```
    +{
    +  "title": "'Take Damage and Survive' Task for Taskmaster (CR for TTT)",
    +  "type": "ServerContent",
    +  "ignore": []
    +}
    +```       
    +
    +### Workshop Icon
    +Now is the time to open the last template. This step is completely optional, you can use whatever workshop icon you want! However, if you want to keep it consistent with CR for TTT roles and related addons open up 'Workshop Icon Template.psd'.
    +
    +The icon is already set up to have the Taskmaster icon and team color set, so all you have to do is add text with your task name.
    +
    +In the case of the task we're working on it will look something like this:
    +![TakeDamageTaskLayers.png](https://i.imgur.com/WQHTBGZ.png)
    +
    +Once you have done that you are ready to hit save. You want to put your icon in the same folder as 'Task Addon Template', not inside it! Steam workshop needs the icon file to be a .jpg so make sure you save it as the right format.
    +
    +Here is my finalised 'TakeDamageTask.jpg':
    +![TakeDamageTask.png](https://i.imgur.com/97JWCED.png)
    +
    +### Folder Name
    +While we are at it now is a good time to rename 'Task Addon Template' because it's not a template anymore, it's yours! Name it the same thing as whatever you named your workshop icon.
    +
    +Before you get to uploading now is a great time to test your addon. In Steam right click on Garry's Mod and click 'Manage' > 'Browse local files'. A folder should open up which contains your GMod files. Open 'garrysmod' > 'addons' and paste the folder you just renamed in here. When you next boot up GMod your addon should load and you can test it out before uploading to the workshop.
    +Final Checks
    +
    +Before you upload your addon, now is a great time to check that your file structure is all correct! For reference here is the file structure of my completed task addon:
    +```
    +├─ TakeDamageTask.jpg
    +└─ TakeDamageTask
    +   ├─ addon.json
    +   └─ lua
    +      └─ taskmastertasks
    +         └─ takedamage.lua
    +```
    +
    +### Uploading
    +Now you are ready to upload your addon. In Steam right click on Garry's Mod and click 'Manage' > 'Browse local files'. A folder should open up which contains your GMod files. Open 'bin' and you should see a file called 'gmad.exe'. Drag and drop your addon folder onto 'gmad.exe'. You should see a new file appear next to your addon folder. It should have the same name as your folder but with the file extension '.gma'. In my case we now have a new file called 'TakeDamageTask.gma'.
    +
    +Go back to the same folder where you found 'gmad.exe'. Click on the address bar up the top and type 'cmd'. This should open up the command prompt where you need to type this command to upload your addon.
    +```
    +gmpublish.exe create -addon %PATHTOGMA% -icon %PATHTOJPG%
    +```
    +
    +Replace %PATHTOGMA% and %PATHTOJPG% with the file paths to your .gma addon file and .jpg workshop icon respectively.
    +
    +For my Summoner addon it looks like this:
    +```
    +gmpublish.exe create -addon "C:\Development\GMod\TakeDamageTask.gma" -icon "C:\Development\GMod\TakeDamageTask.jpg"
    +```
    +
    +Hit enter and you should see your addon upload to Steam! Once it is done you can view your addon by opening steam, hovering over your name up the top and clicking 'Content'. Click the 'Workshop Items' tab and your addon should be there! Here you can give your addon a description and then change its visibility to public. (Note: If you ever need to update a pre-existing addon you can read how to do that here.)
    +
    +Once you have made your addon public, we would love to hear about it! Jump into [our discord server](https://discord.gg/BAPZrykC3F) and show us what you have made!
    +
    +## Wrapping Up
    +If you have any problems or questions please jump into the [Custom Roles for TTT Discord Server](https://discord.gg/BAPZrykC3F)! We love to see what everyone has making and there are almost always people online willing to help out if you need a hand.
    \ No newline at end of file
    
    From cdf9eef28dd7cccdcc1b5ddf53f4bb09ad77cc93 Mon Sep 17 00:00:00 2001
    From: Malivil 
    Date: Fri, 29 Aug 2025 11:27:26 -0400
    Subject: [PATCH 136/140] Apply suggestions from code review
    
    Co-authored-by: Noxx <10150659+NoxxFlame@users.noreply.github.com>
    ---
     CREATE_TASKMASTER_TASKS.md | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/CREATE_TASKMASTER_TASKS.md b/CREATE_TASKMASTER_TASKS.md
    index bd6139b35..bd8ef67e8 100644
    --- a/CREATE_TASKMASTER_TASKS.md
    +++ b/CREATE_TASKMASTER_TASKS.md
    @@ -598,12 +598,12 @@ Now is the time to open the last template. This step is completely optional, you
     
     The icon is already set up to have the Taskmaster icon and team color set, so all you have to do is add text with your task name.
     
    -In the case of the task we're working on it will look something like this:
    +In the case of the task we're working on it will look something like this:  
     ![TakeDamageTaskLayers.png](https://i.imgur.com/WQHTBGZ.png)
     
     Once you have done that you are ready to hit save. You want to put your icon in the same folder as 'Task Addon Template', not inside it! Steam workshop needs the icon file to be a .jpg so make sure you save it as the right format.
     
    -Here is my finalised 'TakeDamageTask.jpg':
    +Here is my finalised 'TakeDamageTask.jpg':  
     ![TakeDamageTask.png](https://i.imgur.com/97JWCED.png)
     
     ### Folder Name
    
    From e53a6407184b17828d423a567598645264d85169 Mon Sep 17 00:00:00 2001
    From: Malivil 
    Date: Sun, 31 Aug 2025 10:13:44 -0400
    Subject: [PATCH 137/140] Added ability for the Tracker to be a special
     innocent instead of a special detective (disabled by default)
    
    ---
     CONVARS.md                                    |  1 +
     RELEASE.md                                    |  1 +
     docs/teams/detective.html                     |  8 ++-
     docs/teams/innocent.html                      |  2 +-
     .../gamemode/roles/tracker/shared.lua         | 50 ++++++++++++-------
     5 files changed, 42 insertions(+), 20 deletions(-)
    
    diff --git a/CONVARS.md b/CONVARS.md
    index e0edaed10..1d203ce42 100644
    --- a/CONVARS.md
    +++ b/CONVARS.md
    @@ -525,6 +525,7 @@ ttt_paladin_heal_self                          1       // Whether the Paladin's
     ttt_paladin_credits_starting                   1       // The number of credits a Paladin should start with
     
     // Tracker
    +ttt_tracker_is_innocent                        0       // Whether the Tracker should be treated as a special innocent
     ttt_tracker_footstep_time                      15      // The amount of time players' footsteps should show to the Tracker before fading. Set to 0 to disable
     ttt_tracker_footstep_color                     1       // Whether players' footsteps should have different colors
     ttt_tracker_credits_starting                   1       // The number of credits a Tracker should start with
    diff --git a/RELEASE.md b/RELEASE.md
    index 9f9377be0..c83762093 100644
    --- a/RELEASE.md
    +++ b/RELEASE.md
    @@ -8,6 +8,7 @@
     - Added ability to control whether the mercenary gets body armor as part of their loadout (defaults to `true`)
     - Added ability to change vampire's fang drain overheal to overheal (default), increase max health, or do both
     - Added ability to disable automatically confirming a players death when searching their body (disabled by default)
    +- Added ability for the Tracker to be a special innocent instead of a special detective (disabled by default)
     
     ### Changes
     - Updated weapon switch and pickup HUD elements to use the same logic for getting weapon names
    diff --git a/docs/teams/detective.html b/docs/teams/detective.html
    index b5659bbe3..f1a7a87b8 100644
    --- a/docs/teams/detective.html
    +++ b/docs/teams/detective.html
    @@ -668,7 +668,7 @@ 

    Role Configuration:

    - +
    [more]

    The Tracker can see a trail of footsteps left by other players.

    @@ -706,6 +706,12 @@

    Role Configuration:

    Integer The time in seconds a player's footsteps should show to the Tracker before fading. (Set to 0 to disable.) + + ttt_tracker_is_innocent   + 0 + Boolean + Whether the Tracker should be treated as a special innocent. + ttt_tracker_radar_loadout 0 diff --git a/docs/teams/innocent.html b/docs/teams/innocent.html index 7d07a1639..aef9f12fa 100644 --- a/docs/teams/innocent.html +++ b/docs/teams/innocent.html @@ -1055,7 +1055,7 @@

    Role Configuration:

    -

    The following roles can be configured to be a special innocent instead of their default team: Sapper.

    +

    The following roles can be configured to be a special innocent instead of their default team: Sapper and Tracker.

    diff --git a/gamemodes/terrortown/gamemode/roles/tracker/shared.lua b/gamemodes/terrortown/gamemode/roles/tracker/shared.lua index 523f8d07b..57e0a5078 100644 --- a/gamemodes/terrortown/gamemode/roles/tracker/shared.lua +++ b/gamemodes/terrortown/gamemode/roles/tracker/shared.lua @@ -2,29 +2,13 @@ AddCSLuaFile() local hook = hook -local function InitializeEquipment() - if DefaultEquipment then - DefaultEquipment[ROLE_TRACKER] = { - EQUIP_ARMOR, - EQUIP_RADAR - } - end -end -InitializeEquipment() - -hook.Add("Initialize", "Tracker_Shared_Initialize", function() - InitializeEquipment() -end) -hook.Add("TTTPrepareRound", "Tracker_Shared_TTTPrepareRound", function() - InitializeEquipment() -end) - ------------------ -- ROLE CONVARS -- ------------------ CreateConVar("ttt_tracker_footstep_time", "15", FCVAR_REPLICATED, "The amount of time players' footsteps should show to the tracker before fading. Set to 0 to disable", 0, 60) CreateConVar("ttt_tracker_footstep_color", "1", FCVAR_REPLICATED) +local tracker_is_innocent = CreateConVar("ttt_tracker_is_innocent", "0", FCVAR_REPLICATED, "Whether the tracker should be treated as a special innocent", 0, 1) ROLE_CONVARS[ROLE_TRACKER] = {} table.insert(ROLE_CONVARS[ROLE_TRACKER], { @@ -39,4 +23,34 @@ table.insert(ROLE_CONVARS[ROLE_TRACKER], { table.insert(ROLE_CONVARS[ROLE_TRACKER], { cvar = "ttt_tracker_radar_loadout", type = ROLE_CONVAR_TYPE_BOOL -}) \ No newline at end of file +}) +table.insert(ROLE_CONVARS[ROLE_TRACKER], { + cvar = "ttt_tracker_is_innocent", + type = ROLE_CONVAR_TYPE_BOOL +}) + +------------------- +-- ROLE FEATURES -- +------------------- + +local function InitializeEquipment() + if DefaultEquipment then + DefaultEquipment[ROLE_TRACKER] = { + EQUIP_ARMOR, + EQUIP_RADAR + } + end +end +InitializeEquipment() + +hook.Add("Initialize", "Tracker_Shared_Initialize", function() + InitializeEquipment() +end) +hook.Add("TTTPrepareRound", "Tracker_Shared_TTTPrepareRound", function() + InitializeEquipment() +end) + +hook.Add("TTTUpdateRoleState", "Tracker_TTTUpdateRoleState", function() + local is_innocent = tracker_is_innocent:GetBool() + DETECTIVE_ROLES[ROLE_TRACKER] = not is_innocent +end) \ No newline at end of file From bf9fb6ffa7d64b6771bda8b67bd056a2da5df09a Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 31 Aug 2025 11:39:37 -0400 Subject: [PATCH 138/140] Added "signed" parameter to "util.BitsRequired" from base TTT --- API/METHODS_UTIL.md | 3 ++- docs/api/methods_util.html | 3 ++- gamemodes/terrortown/gamemode/util.lua | 27 +++++++++++++++----------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/API/METHODS_UTIL.md b/API/METHODS_UTIL.md index 51bcc699f..030ac0b62 100644 --- a/API/METHODS_UTIL.md +++ b/API/METHODS_UTIL.md @@ -1,12 +1,13 @@ # Utility Methods Utility methods created to help with various common scenarios -### util.BitsRequired(num) +### util.BitsRequired(nu, signed) Returns how many bits are required to transmit the given number over the network.\ *Realm:* Client and Server\ *Added in:* 2.3.3\ *Parameters:* - *num* - The number being transmitted +- *signed* - Whether the number being transmitted is signed *(Added in 2.3.5)* ### util.BurnRagdoll(rag, burn_time, scorch) Burns a player ragdoll, shows scorch marks, and automatically destroys the ragdoll unless it's been extinguished by water.\ diff --git a/docs/api/methods_util.html b/docs/api/methods_util.html index 1886559d1..5f387224d 100644 --- a/docs/api/methods_util.html +++ b/docs/api/methods_util.html @@ -17,7 +17,7 @@

    Utility Methods

    Utility methods created to help with various common scenarios

    -

    util.BitsRequired(num)  

    +

    util.BitsRequired(num, signed)  

    Returns how many bits are required to transmit the given number over the network.
    Realm: Client and Server
    @@ -26,6 +26,7 @@

    util.BitsRequired(nu

    • num - The number being transmitted
    • +
    • signed - Whether the number being transmitted is signed (Added in 2.3.5)

    util.BurnRagdoll(rag, burn_time, scorch)

    diff --git a/gamemodes/terrortown/gamemode/util.lua b/gamemodes/terrortown/gamemode/util.lua index f6759851d..4a888c32f 100644 --- a/gamemodes/terrortown/gamemode/util.lua +++ b/gamemodes/terrortown/gamemode/util.lua @@ -472,6 +472,21 @@ function util.SimpleTime(seconds, fmt) return StringFormat(fmt, m, s, ms) end +-- Returns the number of bits required to network an integer +function util.BitsRequired(num, signed) + local bits, max = 0, 1 + while max <= num do + bits = bits + 1 + max = max + max + end + + if signed then + bits = bits + 1 + end + + return bits +end + if SERVER then function util.ExecFile(filePath, errorIfMissing) if not FileExists(filePath, "GAME") then @@ -554,20 +569,10 @@ function util.FormattedList(tbl, formatting) return result end -function util.BitsRequired(num) - local bits, max = 0, 1 - while max <= num do - bits = bits + 1 - max = max + max - end - return bits -end - local roleBits = nil function util.RoleBits() if not roleBits then - -- Add a bit to the required for this since we're sending it as signed to support ROLE_NONE (-1) - roleBits = math.max(8, util.BitsRequired(ROLE_MAX) + 1) + roleBits = math.max(8, util.BitsRequired(ROLE_MAX, true)) end return roleBits From 13ad0b42aaa0be9a706a42c8ca0b9025f65f02d5 Mon Sep 17 00:00:00 2001 From: Malivil Date: Sun, 31 Aug 2025 11:47:28 -0400 Subject: [PATCH 139/140] Fixed typo --- API/METHODS_UTIL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/METHODS_UTIL.md b/API/METHODS_UTIL.md index 030ac0b62..00c5989e2 100644 --- a/API/METHODS_UTIL.md +++ b/API/METHODS_UTIL.md @@ -1,7 +1,7 @@ # Utility Methods Utility methods created to help with various common scenarios -### util.BitsRequired(nu, signed) +### util.BitsRequired(num, signed) Returns how many bits are required to transmit the given number over the network.\ *Realm:* Client and Server\ *Added in:* 2.3.3\ From 13982d1f1e1f690399358bfdca05186814208bad Mon Sep 17 00:00:00 2001 From: Malivil Date: Thu, 4 Sep 2025 07:34:36 -0400 Subject: [PATCH 140/140] Ported "TTT: Add custom T Radio sound support and extra default sounds" from base TTT --- RELEASE.md | 2 + .../entities/entities/ttt_radio.lua | 215 +++++------------- gamemodes/terrortown/gamemode/cl_radio.lua | 83 +++++++ gamemodes/terrortown/gamemode/init.lua | 1 + .../terrortown/gamemode/lang/english.lua | 5 +- gamemodes/terrortown/gamemode/radio_shd.lua | 189 +++++++++++++++ gamemodes/terrortown/gamemode/shared.lua | 1 + 7 files changed, 339 insertions(+), 157 deletions(-) create mode 100644 gamemodes/terrortown/gamemode/cl_radio.lua create mode 100644 gamemodes/terrortown/gamemode/radio_shd.lua diff --git a/RELEASE.md b/RELEASE.md index c83762093..0631c548e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -13,12 +13,14 @@ ### Changes - Updated weapon switch and pickup HUD elements to use the same logic for getting weapon names - Fixes very rare cases where they would say different things +- Ported "TTT: Add custom T Radio sound support and extra default sounds" from base TTT ### Fixes - Fixed illusionist not blocking traitor team voice chat - Fixed promoted impersonator and deputy who are searched by a detective not always showing their true role on the scoreboard ### Developer +- Ported "signed" parameter added to `util.BitsRequired` from base TTT - Added `state` parameter to `TTTTeamVoiceChatTargets` hook - Added `table.GetFirstItemWithPropertyValue` static method - Added `maxchange` parameter to `TTTVampireBodyEaten` hook diff --git a/gamemodes/terrortown/entities/entities/ttt_radio.lua b/gamemodes/terrortown/entities/entities/ttt_radio.lua index 909e178a1..ae181fec8 100644 --- a/gamemodes/terrortown/entities/entities/ttt_radio.lua +++ b/gamemodes/terrortown/entities/entities/ttt_radio.lua @@ -14,16 +14,15 @@ local MathRand = math.Rand local MathRandom = math.random local SoundPlay = sound.Play local TableAdd = table.Add -local TableHasValue = table.HasValue local TableInsert = table.insert local TableRandom = table.Random local TableRemove = table.remove local TimerSimple = timer.Simple if CLIENT then - -- this entity can be DNA-sampled so we need some display info - ENT.Icon = "vgui/ttt/icon_radio" - ENT.PrintName = "radio_name" + -- this entity can be DNA-sampled so we need some display info + ENT.Icon = "vgui/ttt/icon_radio" + ENT.PrintName = "radio_name" end ENT.Type = "anim" @@ -116,162 +115,73 @@ function ENT:AddSound(snd) end end -local simplesounds = { - scream = { - Sound("vo/npc/male01/pain07.wav"), - Sound("vo/npc/male01/pain08.wav"), - Sound("vo/npc/male01/pain09.wav"), - Sound("vo/npc/male01/no02.wav") - }, - explosion = { - Sound("BaseExplosionEffect.Sound") - } -}; - -local serialsounds = { - footsteps = { - sound = { - {Sound("player/footsteps/concrete1.wav"), Sound("player/footsteps/concrete2.wav")}, - {Sound("player/footsteps/concrete3.wav"), Sound("player/footsteps/concrete4.wav")} - }, - times = {8, 16}, - delay = 0.35, - ampl = 80 - }, - - burning = { - sound = { - Sound("General.BurningObject"), - Sound("General.StopBurning") - }, - times = {2, 2}, - delay = 4, - }, - - - beeps = { - sound = { Sound("weapons/c4/c4_beep1.wav") }, - delay = 0.75, - times = {8, 12}, - ampl = 70 - } -}; - -local gunsounds = { - shotgun = { - sound = Sound( "Weapon_XM1014.Single" ), - delay = 0.8, - times = {1, 3}, - burst = false - }, - - pistol = { - sound = Sound( "Weapon_FiveSeven.Single" ), - delay = 0.4, - times = {2, 4}, - burst = false - }, - - mac10 = { - sound = Sound( "Weapon_mac10.Single" ), - delay = 0.065, - times = {5, 10}, - burst = true - }, - - deagle = { - sound = Sound( "Weapon_Deagle.Single" ), - delay = 0.6, - times = {1, 3}, - burst = false - }, - - m16 = { - sound = Sound( "Weapon_M4A1.Single" ), - delay = 0.2, - times = {1, 5}, - burst = true - }, - - rifle = { - sound = Sound( "weapons/scout/scout_fire-1.wav" ), - delay = 1.5, - times = {1, 1}, - burst = false, - ampl = 80 - }, - - huge = { - sound = Sound( "Weapon_m249.Single" ), - delay = 0.055, - times = {6, 12}, - burst = true - } -}; - function ENT:PlayDelayedSound(snd, ampl, last) -- maybe we can get destroyed while a timer is still up - if IsValid(self) then - if istable(snd) then - snd = TableRandom(snd) - end + if not IsValid(self) then return end - if self.BroadcastSound then - self:BroadcastSound(snd, ampl) - else - SoundPlay(snd, self:GetPos(), ampl) - end - self.Playing = not last + if istable(snd) then + snd = TableRandom(snd) + end + + if self.BroadcastSound then + self:BroadcastSound(snd, ampl) + else + SoundPlay(snd, self:GetPos(), ampl) end + + self.Playing = not last end function ENT:PlaySound(snd) - local pos = self:GetPos() - local this = self - if simplesounds[snd] then - if self.BroadcastSound then - self:BroadcastSound(TableRandom(simplesounds[snd])) + local soundData = TRADIO.Sounds[snd] + if not soundData then return end + + if hook.Run("TTTRadioPlaySound", self, snd, soundData) == true then return end + + local sndlist = soundData.sound + local ampl = soundData.ampl + + local serial = soundData.serial + local times = soundData.times + if serial or times then + local num = istable(sndlist) and #sndlist or 1 + + if times then + times = istable(times) and MathRandom(times[1], times[2]) or times else - SoundPlay(TableRandom(simplesounds[snd]), pos) + times = num end - elseif gunsounds[snd] then - local gunsound = gunsounds[snd] - local times = MathRandom(gunsound.times[1], gunsound.times[2]) - local t = 0 - for i=1, times do - TimerSimple(t, - function() - if IsValid(this) then - this:PlayDelayedSound(gunsound.sound, gunsound.ampl or 90, i == times) - end - end) - - if gunsound.burst then - t = t + gunsound.delay - else - t = t + MathRand(gunsound.delay, gunsound.delay * 2) + + if times > 1 then + local delay = soundData.delay + local idx = 1 + + local t = 0 + for i = 1, times do + local sndpath = serial and sndlist[idx] or sndlist + + TimerSimple(t, + function() + -- maybe we can get destroyed while a timer is still up + if not IsValid(self) then return end + + self:PlayDelayedSound(sndpath, ampl, i == times) + end) + + local d = istable(delay) and MathRand(delay[1], delay[2]) or delay + t = t + d + + if serial then + idx = idx + 1 + if idx > num then idx = 1 end + end end - end - elseif serialsounds[snd] then - local serialsound = serialsounds[snd] - local num = #serialsound.sound - local times = MathRandom(serialsound.times[1], serialsound.times[2]) - local t = 0 - local idx = 1 - for i=1, times do - local chosen = serialsound.sound[idx] - TimerSimple(t, - function() - if IsValid(this) then - this:PlayDelayedSound(chosen, serialsound.ampl or 75, i == times) - end - end) - - t = t + serialsound.delay - idx = idx + 1 - if idx > num then idx = 1 end + + return end end + + self:PlayDelayedSound(sndlist, ampl, true) end local nextplay = 0 @@ -288,13 +198,6 @@ function ENT:Think() end if SERVER then - local soundtypes = { - "scream", "shotgun", "explosion", - "pistol", "mac10", "deagle", - "m16", "rifle", "huge", - "burning", "beeps", "footsteps" - }; - local function RadioCmd(ply, cmd, args) if not IsValid(ply) then return end if #args ~= 2 then return end @@ -308,7 +211,7 @@ if SERVER then if radio:GetOwner() ~= ply then return end if radio:GetClass() ~= "ttt_radio" then return end - if not TableHasValue(soundtypes, snd) then + if not TRADIO.Sounds[snd] then print("Received radio sound not in table from", ply) return end diff --git a/gamemodes/terrortown/gamemode/cl_radio.lua b/gamemodes/terrortown/gamemode/cl_radio.lua new file mode 100644 index 000000000..9117236dd --- /dev/null +++ b/gamemodes/terrortown/gamemode/cl_radio.lua @@ -0,0 +1,83 @@ +--- Traitor radio controls + +local math = math +local vgui = vgui + +local GetTranslation = LANG.GetTranslation +local TryTranslation = LANG.TryTranslation + +TRADIO.SoundOrder = { + "scream", "burning", "explosion", "footsteps", + "pistol", "shotgun", "mac10", "deagle", + "m16", "rifle", "huge", "glock", + "beeps", "sipistol", "teleport", "hstation" +}; + +local function PlayRadioSound(snd) + local r = LocalPlayer().radio + if IsValid(r) then + RunConsoleCommand("ttt_radio_play", tostring(r:EntIndex()), snd) + end +end + +local function ButtonClickPlay(s) PlayRadioSound(s.snd) end + +local columns = 4 +local rows = 5 + +local bh, bw = 50, 100 +local m = 5 + +local function CreateSoundBoard(parent) + local b = vgui.Create("DScrollPanel", parent) + + local sorder = TRADIO.SoundOrder + local ver = math.min(math.ceil(#sorder / columns), rows) + local sbar = ver == rows and b:GetVBar():GetWide() or 0 + + b:SetSize(bw * columns + m * (columns - 1) + sbar, bh * ver + m * (ver - 1)) + b:SetPos(m, 25) + b:CenterHorizontal() + + local grid = vgui.Create("DIconLayout", b) + grid:Dock(FILL) + grid:SetSpaceX(m) + grid:SetSpaceY(m) + + for ri, snd in ipairs(sorder) do + local but = grid:Add("DButton") + but:SetSize(bw, bh) + but:SetText(TryTranslation(TRADIO.Sounds[snd].name)) + but.snd = snd + but.DoClick = ButtonClickPlay + end + + return b +end + +function TRADIO.CreateMenu(parent) + local w, h = parent:GetSize() + + local client = LocalPlayer() + + local wrap = vgui.Create("DPanel", parent) + wrap:SetSize(w, h) + wrap:SetPaintBackground(false) + + local dhelp = vgui.Create("DLabel", wrap) + dhelp:SetFont("TabLarge") + dhelp:SetText(GetTranslation("radio_help")) + dhelp:SetTextColor(COLOR_WHITE) + + if IsValid(client.radio) then + CreateSoundBoard(wrap) + elseif client:HasWeapon("weapon_ttt_radio") then + dhelp:SetText(GetTranslation("radio_notplaced")) + end + + dhelp:SizeToContents() + dhelp:SetPos(10, 5) + dhelp:CenterHorizontal() + + return wrap +end \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/init.lua b/gamemodes/terrortown/gamemode/init.lua index c51ecf2da..213bcd561 100644 --- a/gamemodes/terrortown/gamemode/init.lua +++ b/gamemodes/terrortown/gamemode/init.lua @@ -24,6 +24,7 @@ AddCSLuaFile("lang_shd.lua") AddCSLuaFile("corpse_shd.lua") AddCSLuaFile("player_ext_shd.lua") AddCSLuaFile("weaponry_shd.lua") +AddCSLuaFile("radio_shd.lua") AddCSLuaFile("cl_radio.lua") AddCSLuaFile("cl_radar.lua") AddCSLuaFile("cl_tbuttons.lua") diff --git a/gamemodes/terrortown/gamemode/lang/english.lua b/gamemodes/terrortown/gamemode/lang/english.lua index 0da23eb01..bf13f54b3 100644 --- a/gamemodes/terrortown/gamemode/lang/english.lua +++ b/gamemodes/terrortown/gamemode/lang/english.lua @@ -150,7 +150,10 @@ L.radio_button_huge = "H.U.G.E burst" L.radio_button_c4 = "C4 beeping" L.radio_button_burn = "Burning" L.radio_button_steps = "Footsteps" - +L.radio_button_glock = "Glock shots" +L.radio_button_sipist = "Silenced shots" +L.radio_button_tele = "Teleport" +L.radio_button_heal = "Healing" -- Intro screen shown after joining L.intro_help = "If you're new to the game, press F1 for instructions!" diff --git a/gamemodes/terrortown/gamemode/radio_shd.lua b/gamemodes/terrortown/gamemode/radio_shd.lua new file mode 100644 index 000000000..26fe2e85a --- /dev/null +++ b/gamemodes/terrortown/gamemode/radio_shd.lua @@ -0,0 +1,189 @@ +--- Traitor radio sounds + +local table = table + +TRADIO = TRADIO or {} + +-- This table contains every sound that can be played by the Radio traitor item. +-- +-- It also contains settings that change how the sound is displayed on the client, +-- or how the sound is played on the server: +-- +-- name: The name of the sound shown in the T menu. Can be localized. +-- sound: The sound to be played, or a table of sounds to pick from randomly. +-- serial: If true, play through the sound table in sequential order instead of randomly. +-- times: The number of times to repeat the sound. If set to a table, randomizes the number of times within the range {min, max}. +-- delay: The delay between sound repetitions. If set to a table, randomizes the delay within the range {min, max}. +-- ampl: The sound level. See https://wiki.facepunch.com/gmod/Enums/SNDLVL +-- +-- If serial = true, you can nest multiple sound tables within the sound table. +-- This will cause the radio to choose a random sound from the first table, +-- then on the next repetition, choose a random sound from the next table, +-- and so on. See footsteps (lines 70-80) for an example of this. +-- +-- +-- If you want to add custom sounds to the radio, use TRADIO.AddNewSound in a shared lua file. +-- For example: +-- TRADIO.AddNewSound("glock", { +-- name = "Glock shots", +-- sound = Sound("Weapon_Glock.single"), +-- delay = 0.1, +-- times = {4, 8}, +-- ampl = 90 +-- }) +-- +-- If you want to overwrite an existing sound, you can simply modify TRADIO.Sounds directly. +-- For example: +-- TRADIO.Sounds.explosion = { +-- name = "Balloon pop!", +-- sound = Sound("garrysmod/balloon_pop_cute.wav") +-- } + +TRADIO.Sounds = { + -- Simple Sounds + scream = { + name = "radio_button_scream", + sound = { + Sound("vo/npc/male01/pain07.wav"), + Sound("vo/npc/male01/pain08.wav"), + Sound("vo/npc/male01/pain09.wav"), + Sound("vo/npc/male01/no02.wav") + } + }, + explosion = { + name = "radio_button_expl", + sound = Sound("BaseExplosionEffect.Sound") + }, + beeps = { + name = "radio_button_c4", + sound = Sound("weapons/c4/c4_beep1.wav"), + delay = 0.75, + times = {8, 12}, + ampl = 70 + }, + hstation = { + name = "radio_button_heal", + sound = Sound("items/medshot4.wav"), + delay = 2, + times = {4, 8} + }, + + -- Serial Sounds + footsteps = { + name = "radio_button_steps", + sound = { + {Sound("player/footsteps/concrete1.wav"), Sound("player/footsteps/concrete2.wav")}, + {Sound("player/footsteps/concrete3.wav"), Sound("player/footsteps/concrete4.wav")} + }, + serial = true, + times = {8, 16}, + delay = 0.35, + ampl = 80 + }, + + burning = { + name = "radio_button_burn", + sound = { + Sound("General.BurningObject"), + Sound("General.StopBurning") + }, + serial = true, + delay = 4, + }, + + teleport = { + name = "radio_button_tele", + sound = { + Sound("ambient/levels/labs/teleport_mechanism_windup1.wav"), + Sound("ambient/levels/labs/electric_explosion4.wav") + }, + serial = true, + delay = 1 + }, + + -- Gun Sounds + shotgun = { + name = "radio_button_shotgun", + sound = Sound( "Weapon_XM1014.Single" ), + delay = {0.8, 1.6}, + times = {1, 3}, + ampl = 90 + }, + + pistol = { + name = "radio_button_pistol", + sound = Sound( "Weapon_FiveSeven.Single" ), + delay = {0.4, 0.8}, + times = {2, 4}, + ampl = 90 + }, + + mac10 = { + name = "radio_button_mac10", + sound = Sound( "Weapon_mac10.Single" ), + delay = 0.065, + times = {5, 10}, + ampl = 90 + }, + + deagle = { + name = "radio_button_deagle", + sound = Sound( "Weapon_Deagle.Single" ), + delay = {0.6, 1.2}, + times = {1, 3}, + ampl = 90 + }, + + m16 = { + name = "radio_button_m16", + sound = Sound( "Weapon_M4A1.Single" ), + delay = 0.2, + times = {1, 5}, + ampl = 90 + }, + + rifle = { + name = "radio_button_rifle", + sound = Sound( "weapons/scout/scout_fire-1.wav" ), + ampl = 80 + }, + + huge = { + name = "radio_button_huge", + sound = Sound( "Weapon_m249.Single" ), + delay = 0.055, + times = {6, 12}, + ampl = 90 + }, + + glock = { + name = "radio_button_glock", + sound = Sound("Weapon_Glock.single"), + delay = 0.1, + times = {4, 8}, + ampl = 90 + }, + + sipistol = { + name = "radio_button_sipist", + sound = Sound("Weapon_USP.SilencedShot"), + delay = {0.38, 0.76}, + times = {2, 4}, + ampl = 50 + } +} + +function TRADIO.AddNewSound(key, soundData) + local newkey = key + local i = 1 + while TRADIO.Sounds[newkey] do + newkey = key .. tostring(i) + i = i + 1 + end + + TRADIO.Sounds[newkey] = soundData + + if CLIENT then + table.insert(TRADIO.SoundOrder, newkey) + end +end diff --git a/gamemodes/terrortown/gamemode/shared.lua b/gamemodes/terrortown/gamemode/shared.lua index def8ff8e3..7dd64e2fe 100644 --- a/gamemodes/terrortown/gamemode/shared.lua +++ b/gamemodes/terrortown/gamemode/shared.lua @@ -1553,6 +1553,7 @@ COLOR_CYAN = Color(0, 255, 255, 255) include("lang_shd.lua") -- uses some of util include("equip_items_shd.lua") +include("radio_shd.lua") function DetectiveMode() return GetGlobalBool("ttt_detective", false) end function HasteMode() return GetGlobalBool("ttt_haste", false) end