From 59237650454dd9127967afb6b48c45bc2b145206 Mon Sep 17 00:00:00 2001 From: SonjaMcNeilly Date: Wed, 11 Oct 2023 15:18:40 +1000 Subject: [PATCH 01/27] Changed turret selection so when turrets are selected, it's green instead of red --- .../turret-select/droid-tower-selected.png | Bin 0 -> 8372 bytes .../turret-select/fire-tower-clicked.png | Bin 7337 -> 0 bytes .../turret-select/fire-tower-selected.png | Bin 0 -> 6485 bytes .../turret-select/mine-tower-selected.png | Bin 0 -> 7634 bytes .../turret-select/stun-tower-selected.png | Bin 0 -> 6935 bytes .../turret-select/tnt-tower-selected.png | Bin 0 -> 6337 bytes .../turret-select/wall-tower-default.png | Bin 6163 -> 10190 bytes .../turret-select/wall-tower-selected.png | Bin 0 -> 6765 bytes .../turret-select/weapon-tower-selected.png | Bin 0 -> 9914 bytes .../com/csse3200/game/screens/TowerType.java | 14 +++++++------- 10 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 source/core/assets/images/turret-select/droid-tower-selected.png delete mode 100644 source/core/assets/images/turret-select/fire-tower-clicked.png create mode 100644 source/core/assets/images/turret-select/fire-tower-selected.png create mode 100644 source/core/assets/images/turret-select/mine-tower-selected.png create mode 100644 source/core/assets/images/turret-select/stun-tower-selected.png create mode 100644 source/core/assets/images/turret-select/tnt-tower-selected.png create mode 100644 source/core/assets/images/turret-select/wall-tower-selected.png create mode 100644 source/core/assets/images/turret-select/weapon-tower-selected.png diff --git a/source/core/assets/images/turret-select/droid-tower-selected.png b/source/core/assets/images/turret-select/droid-tower-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..f08c574bc373a81ac6bcc1c1d0532e4f9c3b964b GIT binary patch literal 8372 zcmV;lAWPqgP)+U&$!F>=QNbnR7P&|c_CCl1KimW(WaoLs9)^1sG)t3L-EyY$gwJvSN zt2hRh<39|2WzsrK;FYC304@B*&I)9Tabhq(}lhzyl-)z|3IIKHhs<-<$5i z02qJ-HQ-<{{i|wn&^_Jrru#SF^}hFg1nz)`UfHKhF24r=|KU#W^>Oduwt!$~0Cw%z z1s#D-u!TMET8uDoqytB8ABMzEFHIm1@WtKAdEm)Ul(XZ1Bn-}0)!M0(IOxAh5BDLsPU7R zc76NiLRYOwtcPCNN9>Ff(ZcM@gp$9|qFOf#GpN$|eB9w_5W2V`kwVTq3o`%x>iAt; zXDRSLv$?k!Hbytz3bN3D)`<4wJ*|7PzrXI;dCbrjxVAwJ`gOv{SG0)MsmF9s#PYHH zkTb+s2#PQsXXd=WbWF}BS+$VMV!*cMZSf!r{g*Hc zzq%u~!}8Q(Nfn|^h3bz5=(8-ul=^c?h0Oegzt1IQD%2$drhM-XGP-(`Zw%Alp(VsjN1m?OJ=FWdw8(l#I1gXVjXg7EO>Ib8NkFxz$8@nhwmlxz zn!a7&`5$T+{M15hKMS$$GWBP>>hC=y6_PEt)D)<@kyy6$C=1;;e1L07O_ zV}yMM03Jw06FSdxI?6`7I|3cg^#psjYbv5nK!p=sja0{m_+25zWA2}4I|3blGGR`<0suG#20GRM&p>$H zEBABED;X@I<&n&VcgAv8p0CFBD4~w~e^tl1nou)h-7Jg`k7h>RM%!go-q{96%~aw2L|>@? z#{lr-Mt|7%6DyurDABm&!yy*>D=vATE$n~i9=g#H=y-m@ocJ>U*jC}xbDnoMVa9jn zc5B$NDU$-OB@uw5!1TK8(t(AA}pMauRWXR`TwX9?WgM_gQa*?*)M;|SV+}QS(6(R zR^sK+%*ZnlE%JkEUDq4yoa=5~37#r0LDQ*TcQ%$&PAUX?dR+2>cBB2}W<6HQyH=jt zBI^h|&#oGXo45C5oXl4ZYT(kS|La|n5hf~5@4K*t74lx|)W}z1D42|ARqJ~#1vpQL znKje)G@2gu5#U2xCCGv=SF%lPVec1G?8))m+S-f`a4p{!F8N+dSbLUo-gsgBYh$^w zU*iAXJ|RUZJ;@7`p!B_q%bnX4-Tas<Be!oR4HKyt|N!W`un(7}@ZIyKg-{GA{YEqnVLsz4zh|%~N@(7ME+xONIonr)^w6 z;SP5m9iTa16~sVUK`Dfyjp(Ea+T%n8;orD zrspjJKzblN@c3A6?1whkO>IWoSKEyC(It%aY($Iv&!voYxqyZ{8t|<~EA)rgt~}!% z1A7(3JpLXQ^WvNe-j|r`L3(iaFLb?6V`t4fx6*uKnYSohZ&3a1?qAASt2OSb0gIMk zbsKWvdwV4fQ1I3gM#E8v>9NAP$oevoM{X_En*e>Oyh}!jV3)d{iG8Zt#ia zV_D!6%g3_7Czg+8fln+S%YudsHwejT+DRMJ_Oy|4GKN77#w8~PHH1!eJ3s?gOpjSQ zHLBX^uaVGj#Il6R&P<;fNMBCxE8I5s*%|u*y-Dw;Ox2s7Ws}!se4O5L9zWE+BBC(E-AK#Kbm;SxNr1`KlWwjAZu%EOjVMG*e79wWC z+C~`JKqz_YB3!n-Tyn{!#25`Wm?VtMth^z>S&&WWP1fli%4is*iL~==ZfqS*e@t=8}IF!V~IWaEPqRfqd zFle$6bH;yuQ_Eh>weuWrtHpO(iD)=tQJqczz%^ls&E5=`m9UgV!Uo+axx9u%3tU3A zSf&Q$TX$_W8DY-U(S(z}m-(4XRE1G*o{~JQFIG&_#u-HabsJG~s9{QvG-`f=3JhB3YTbF3q zd@Lz(T_u@S7|k=>2_*@iRG$tFODw7pV1`m}UXMp47QUcVBeUut*ur8b6DJ?cUdn#a z$vRkE%_NjqG(c|{t;Uh2uBLZ)#)hud=&P(qGlh#P9Qwt9o`H4l>Ic8vHCc4>g}Dep%u}h zrPlC?RR%QTF%mBw*=$+k5Wu;)(JJJl3Ez42X}rE-={a} zKpDdz>!6*oza&ht@m%=a`Uiz~q^^n+ql!*<&TFCvsPFzkM=wa-9U(P)1k7N-QVOn^CQK(g~$f{#F6Z zDQoV$prT)>Hwb#Ou2dnaM{v(1gn%q&-l({k7G}z0bbqv6w|;f|w{FSXZ2B|PZ$7sF zbLa2#*Q^;Dl2|D_*(JH$E^L9(_mcgCDiKp><^ok@uqyyWBdW}-u!JI&bYwBr!L`$< zHeUf)DZAfJ*`GB&nRYI1yJ&BWZn`YyF?aQ$p@_w$)BOIa_xqElQ+o@y3XiDoU-pQV z=Ql^~p@hQycG^)yLK{nKa&MdaR?j|DAql5wr=h3lf(cs;K3o&KjY+LeJTzZo7L zz7S&J?0x&DEu80IKsRHfAc7l3DQ>4Rv=47d&IOU7{LTRm8fUzt%c-ZR{~?%6iJqI72KzgI>nxtzX^- zD`^Q9Vk)(m8BAinIuy$c%!nyD`+qf`NM=08xe^WcMb}3#Orgs6#Pk?soh)3RoM}W> z%A0p&cVWUjaQof3b9UhDS9EHuN^-3VVxgTgp1;v@@!ZAFI%(%$3tMa_1Q<7q z9zJD6qAf)D1xu z;rF|Gx-NAFIE?7vO_sk6%CaYfC3>#EG5$sA$j?s= zC&*aum?0hM&;Y#{>`7MVF5Q3BHL*rBBh6QiT={HnJpa2|NE-wKic}~uRD?XD6{l;Y zO6*ev2(Jr+(QaVM3ra8bsI{t&y(Di+`kM)HcWr2n^YszHL)gBldainx!-a!4tAptcv8Zf`zsmX zp`yyeX_sJo0BBDLtZBPsjAt%om2r-7sD+~JcLU)F6_!A5yNT!dcD1@43E}1Ixs$_o#x6>c(9A=|IFoR=k}Pd| zTGhVvNCtSG1BsA(mt1bZ)VAEuT!>h5rs|#7u*)NJ8L1gH=z5;xtt7KQGxLoLzC|NyYRLN$sL5+L zz*-ucCOd{5b6mkDTgX;Ie^zi z0^{d-_8W5jR({*A;^r1Xq!Tg97H zsGvL>VjfY&{CUOFIB@RK;uW0=n7tg$msqt1&vS_ZM^s;ns+a{1d2@bFuJ|4;$cl?5 zDxBk8KX)~!{>KSu=BiikI@q^Q5h%|~(#l=Y15S=Fs!ox(yueM6P`9Nj>zY_pr(6nY zVyFSVE48qpSy#DGOU|Vu#RrP2QsrE^u`fl6@~nn3$%k`XLSr2h`w(cvV+#)q zsG{wazLbIZZzf8_L2{wwS`0|W=;jEt?{C9gdlgrk^^uemrDev!*ZJ7C=7VbbFk-*9L+)VXB9wmTTRk=jDYdKxIf!70?7% z0`)%Gv#33X$8P=VwhCX|DI)K}p#~j`tdG32A+q5X0Y5-m6NFfp@9#Zusp)~H*IAHd za7ov!hv%ghe+qZL6gY($)vCf3T&fj0dQ?;YSK9Cxl%>_6+GLmuFJ~_`^R7#EdOO$^ ze1B{A*2_9I>UxoD-Ft-u2;b7aZ2{GgTS>8EYBZ8+TnlTzZr$B-a9`K{RZ;M5RS=6y zPO*k4dQ{vL(wefAlu?qaEVy~M84IQC0w|qFR6#aFt)8YBl2FNn?h>y`4uVp1jFi-- zSHc@f?=C4*9xT=AO}$xvw`p_q&HY^mE>!ED)EcXTSQ#gS`Ah0GW(wr4aaC6V)exou zQ-fkx2_o*Cg;K1=&85Y2JmXeDHq{~wjc~n5m)Kgs$v8of&<^pde?s5f+|ZC~r_2YMblw|sZ#{-f@Rg$vO%cl%0$m{ov@ooxYHM z1{X%?SasQ$3Cf~$vQxIe&(2)Rayw(AQ-Q0lb&P?j#TS$~SSBgZ8Te?mkqYXaSX?^f za`tjZ{8;=+$z{y#&R?v{E~PtjTuJ~e7guBs$?$JMb(&ydb~@4*{^ho9+kV|1=$Nil zK&@Gi<=TeY{@F^|TH$u#NdQ>mdDz~xjhlGrCE*~!Rimd;lK}!qHY8tRx$aLbQP;#e zkv!3S^{3Y!&tAffiY^(yldag1EGYoFe~(h z`!XGY&iq23ixO;0)0Ugg!_Mo|XQuO+vFy`U!dj0FpHTG@Mkb?!(RZU8B0udIZqMq? zdj9H9u6;3mG4t;UBR0i_XvOHeWCmN6t?TNVSec8N*4&N!XPk`F=kB7egrG0uh9T`6SMrI^4%JaOP8Z;B?3ylo*4qcw}c1_I}&JN(pO1b*00rhQT zSppYRG!<=lNnFbbI}6f4$Vj#AX&o7QVCejEkjevo=>EE#+@El~er*foza0E%%UJ*Lw@yNbStd5(8q%3f+;0i#s*l;p~x zKxqm_n?a3ML!$QI&BS>J90ERs-WmUBVKd z%U;iB>}KmnwVr!ZV>P*@g*4iY3X`| z9??t|VB-KYol@X(#|+U*aGW-eh%wx)cWgb5iQLOHmOt~m(rXvW&U3`ynUrqu_GoO*=q@_=EMqh2gmi8 z{xJq%l?fte)HpS$E-bMkRCcwXZ6DeW0M4(qK9D zSyZk&*kc{obKpYPXFCrXZN~dJq3cGusfrV6B&9{POYvm9*Aiw;#q#>dVD8~ZA3oK( ztL5ifR69;6nRZwCaLjHX8p)oRII*slW2`1v({S`9%vZwfNK}c2Za|O)ofe~IB&tO( zW}WP9F1bS%T%cZC=v3z|;g&>_B&MW*6i7(%0-qL^n2-X-q=0kz8wICrUE5e#i`3MB z*QstpM5eNZY{AKKhv#`lI3g}gaotQ<$^4B1OhqOq`8&h_2 zlPOHivMmsWncRPEV8;MV6{b`gZ@n7v5iM#zvg6ScsV&Ki$B!QGoW785cQQ`1HD!g+ zyon4%x<+n{bOhH1Z$`DInyQ+C@VaSjkM{nlw?Dor9ob^0%w{L;bg&R>4)urD z$Z=e;vY1*#i}G+^C{8H3CZy<~gjy`X@*1h{jHXpxyJ1($uC)0(=6eP;KJ55c9h{Nc z6RQ&T5A8>%BW_TG;txIcg_vm4nw*t4(z^$y#nsDLmkg8+IyLYCIR*`^*l~tf7*&QM z5@Q+!S*Z4d`jntTHdMDWANL8`@#HbEvev@YQGEEg2WXQ=CyVi`R%2DxY4TAUP{Cy# z*NyYBQlVj0I;>LXE}-<`qbi{Ly7oh7XXoP8@O=1KA}9vpux+0YAJqiqlCO%$yN{KH zhV5eSV|lTvi1oKGy!s(-D_|%Mk5w~Qh=khl&28%STW`Ph;q{-DTIa$ay!_m74Tsi{ zSQW%7Cl#V)D5kdZ*^4i#}zqhn*nYDRX_xo#o za@{Aa8XGHls#vf;aXbNAp5HR-SY|Xc>u+B*ERc%-k)FY6$O&*>qNjL9YB&JWU>Z9(BcO(cB ztNiau$ID||r~ms|MJ{mFsBgqqA9YEr7vA~$aJh|z(B5rop#EmNtVRcXG2`_$lCCLqfre4 zEO^=Fq))8IgmmkV4miSj_;%seQ&ZN|hNP9mW}m7*qmyys+!C)cgZ@yFdEok z(@87w;?2U%EvdAIyUojt94vZqt0zuO+@xE0YNe|{Fu zc3Mdbk}QdbAILbF&5eFneE^_MRi^&Xu8Ip=yuu8MDzMZrV4ei}vr~V%CherZFlkOa zA2;JWJ+&AKMEu+81JF<}Q-2=|OS}RAKRf(K$8MD0ormNRe3R{YfFeBP{Xb za`jh^MOlL0q9v>^UC&>8F6(56t5IPYJCaW1CLnhur;5E9?FMwL>A=`osp?HU8He0h zPLU_r8&vhXyi!Y89VdP}4uzWqUux9?WS;Kd0001QNkl33`7EjbPDy zte19u`{oNr{_uxA!QSlvP?d~JBnXltolk?4=Md-$z(9Ban)GJ1A7=qlc1j(`GM3SZ z1{mCgN0000< KMNUMnLSTYva#_6q literal 0 HcmV?d00001 diff --git a/source/core/assets/images/turret-select/fire-tower-clicked.png b/source/core/assets/images/turret-select/fire-tower-clicked.png deleted file mode 100644 index ce1969010c7a7ba188309595367df16e2bec6e09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7337 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
rNklt>aBBiuY4uXNR~Y_ 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 literal 0 HcmV?d00001 diff --git a/source/core/assets/images/turret-select/mine-tower-selected.png b/source/core/assets/images/turret-select/mine-tower-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..072186dc79ff3086cf3abcbc6bf90d7c2b9410f8 GIT binary patch literal 7634 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 literal 0 HcmV?d00001 diff --git a/source/core/assets/images/turret-select/tnt-tower-selected.png b/source/core/assets/images/turret-select/tnt-tower-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..bc6b588739cb91b64593add4d50c9c5d618b673a GIT binary patch literal 6337 zcmV;y7(VBTP)Nkl zYj7l2b;r-Wx4Y*l&1kfGTGDEz_3m1`i@nBXV-ix~RfOUjkT_KdF7kmdAs7syc*PVk zg%ipFjDh5XKO}JyQdBUNsuV?tlT@l8fQ|9mtoLPWAC`9Y)@YvHGu?gfO`TTHXe7-@ zEA5PSW^{j5BaOPJyYF=Wbt^B1bY)# z@7ThAW)i^q98w zM?1j3W_&OPqmi-s5DR@qr~0#Z$4}*dZ{!a)2}7%KW`i2^6NHfuX<^o*$8^vmwLYq- zC!S!o`~M|tBHZ+Oe_z=M)Y%r8ITJ4N3q6OTW1sH}_77jkoO=QQidBlZp8LOX&maK2 zj~eu07N94LgaLsr1HxLkz9m$&B>7?2+9Zl@gx#0@yoacnWfLSKkzk!SAe zJZ_iVV%fi8Ooi@`1=)jIXx07sqJo}4&&7n%Jy~b3t!O=D(q29c04Gz?6>v=#UW1E0 z!{L#KgDlwhO6ts$0FWyS3u}K=r|-d-iUsJA3QJ`tjKuYX(fzZ5(BR8L3ZlfdZ;_Nx zv_=bD@(2J7rC`MT^5DR^iOFm!= z`;X{qkfBe#T^zcI)%Wx%k`3VW~xBWjabQ|4|F5Amb0RT32fkw1{J{TE&YOn4| ztJ2IC3x%XfkOnO``>%n)qXZef$#YRhI1JCV!!l4VxuGok&S@%(t%y*Qcl4KBARPd_K&=pJar+bU8QqzZhRNL75ajtt>3GW^T!W>faqHuh&_Lx;LJB*?Lf^)?uA*i;T`l%2;nzOBnk|=nsK$A{n(Z}U@zw;-y3G(@6_wOB`dYzyhoe4riw$*u=j%!)(%)G%%94+xepuEz#&lI zEge}=gRP1!>`-aDK@F=8MZp@b-MDYM3&`pn$hlcqDlRHv?F3ucN4Vto+QP<%`rVs7 zbk>^rh%4PctjyX=kmVFfDR$GT{%@m^Lto$KbeHbzII^k5;C55WVJEMxKvJ$KVbz;b zk{5&X*R!wv1cUm@OO%?O7-7E{i;ln3-!_WRhD-k7<=kXBZGx48lFLK4*zDgOyIwh2 z7spSukFcr)&+S0-xx8)A!VFk|w!W~?wWREoe=k+PahUmDQ=^vVW%;)d{H9g_jcDIL z7#V$H&%Zz&Kx>8WFg_N8zF>cQSS(7YQ%}3n?MN7j4|fL+TrPbU&nu+({?+`If2cff1sB3B{I`2@Gk1$z{nv`= zakwKi)E;@PPCYg^9zFcQ01bQ{AMzeta#P!3wf4ly_D;Vx=>6?)Ux(iwdNcnVP2FyZ zla}D@Hst;%?~z)NL1`>uG#teTRTv2${Exn1|8w(&xwE#gElQ|LrYg`n<3CBLFW7%_ zzA*RhDf5lr%{b{`Hk$<}??V4zKU!;*c~{gIjBznZs)VJeE9eSwayXU8Qq%`O^Ny3k zt>lwRSc>{knv)A>8ssKdu24~5a8x}`Q>iSLqArvYq&O$49@dqx6m`N+km5wUD&3%{ zJD6@z{jn7F!><)Cn~C7|J)m4+ZDM*1hrlO<6bUYQ zw*)zCP$P~@;VQOAS*+R+gBk%G7=S~&D4}sKc{Jl>sIpkwql8t1yqg!GLC_IS#2?M9 z6Ch!9i??I6b*rI-RfDvX5-(nVp~K0#y?)4GhWdlSvFJF78(mFV=4L_(s|HCs$!7m& z;eeBN?(o(VVkF&hI2!5y?Ew-8C9LgG!m5FgJRn^eCzOPwkW_Lhr7MF?S=c5-39ANK zC#$bc07HOCTmTK2d(wjp?p|v5U=^rcBa#YLf-6Tp_HQ)vA0;xT(sA6lBuxgO# z>@Ikdqpe9xN+G4hN%j=mqJ&iiM>xb0b`&I3rr9N7#KHBlo2H;m%)ScVv8O`HUeOne z3n4RS2ArH55RT9#m&~_J_>)FP!ja;NJuZceOJ256R1`(mr=%M`)uT3u`$?S#yQAUAC@7t|zWvhz><&4n{`PoqDV-U1M7dYrZfSnL9s!GJ7@u zTW-!hA#Bkvxs2S7vItwm@>AyhL?femr+&`PI6q~_r_Y>>CsuApc^XYy3oC7=I|>WM zlTOO{4dIABLdkY(M>i6@c0|}BXs_5IEyR+A(ZcHxhqe?}TgO;V&h0F&*ki&GgI*e7 zkKA7^{Oe;nqR-AaBX-7#-PQs%khT>TBP_^Gj;^l@C#h&Ausm2UZt`9sMHn;vO;x}; z(00P|Yg*xHa|$!(M+hVFQl6?=!154E;=&e3&ZW_Qa3`iR(ponln}~kTB-c zcc$k%^v+DrFdXNRpy}XC@rlo~taf>b&m!eC>#UXRSx?9*{4Ev=+#f0{|#S5?v(zwpZn>A~2 zTZ}Eev3yUiH^=)s2Bx|L2XY}6YDu@)lET6o)U-Jjy7sF##&TEl6UC%`L^xuMn|#>G zxw_xm6DT7DdoNJ^<}1G?OQl6M=)SiL0^PzR2rgX zgoW;}$O|-SC-t10y@Ol)Zmj)DE@Qrx5}v456+lZ?=T<;*p|x^Feb+I^xgWNu7znRx zGnbv*;)l4!Po|ypvdPWsF+FDM)Zne)7F$MG7c=LhOXrgPh1tR(x4@5M@>5E0tUD_& zz`ZIe;=lJ|8tk0s#WwzP!~v+=#$5if=?fXK<_hI)<({FX-lhJwt@f3glW<#QjD@k4 zYw@khqH#x{yFdNP{Mo1$Waq;}p)36peY3|pCvtVUsSTuMgoUC?olU>hyl$PgGtL+Q zU|)58yKEE39-^gB5CB|Q9k%G==Qp>2bS13(I=xoAtTcXdEfBxyeKodY<)Q{sav97_ z=8h3c?$E;Qh&5k$0VMq2u?Hqz+s!@POIk))#l_-)HCs60ey?Yj z9&w!os`Z&&?DOI`RyCDD%2kim-&niFXVl@Ee6+Ao%$hUS6#!7esuy<3N$>(6^a6DZ zQ&vaSkZnb^ksXL^Cuxd$;Kzid!V2RMGH>(`es6B^JDi-`5>wr@jIe|wf|ARqA5(3t zsNK9=DqK?8GVC%tf2))&nt$nXxk`4c^mBEnKlUe!w91S(t&?t$o}gIyueEi6cBBaI z2oP0V6vISn?Rcl|V5I^_2nj!}QKbo~(#Z?|9d;6IiLOp9BdjVpsa=^EuXe`e`jDRh zX6bLOfIBY8TE(KASgjUwZ9ME2a&vo0D|Pg%s9&x7z5BE6JVL80W%o(HK9pcv#o1Ru`E0i1T zN?jG;1=F`<=;-#}qS9%5b9C)Vxo(k`5f)AfBqfykCT$eit+UsUYexx^)$i)f`o>60 z^v`M~VHGJOmT-JQN&kkE-9{`MFHo0a))VPx>q@1Qlo1o}L(`SC3M~`7YY{C)19X;9 zlJ~6(tx{8L7uH%gjy`52haBg`xaw*yoie! zH2XqPEhDUm7LgGxQs@cv-ngb;h1}JAj4(1vm^Xo1sY_+Gn&Pe29xHltZQ6D#RBON~ z<<_zGS0oyl)1unBVV{ZojvDl$(PbP0uz?pv zpEv5cx`1xghBe}xTU|>KMoebVdA(CVuZ7t~jc5z4Bq~HR;yN_jY%JT+9XL?zxHCHC zq@ACdGuFJ5a|eVia6m=L0<(&WiS!l%ShF&f`-Hy_O1%dvL{qtsdL8aoT@>+VL|$`Q zINE~xU6FAHW!IlEnRxNvS~Cm^rd$r3ow=f8jPU!q<9nCtG#f}5H$wfv*Q2A6>j4(5 zZ>Re`Ks)}X<1lN@Kz2UYR1;@x1r8=SI(T&D`n#sjWM;FkyE!*lngSai7`FxU2UHNE z5y5oIy#(v(KD5%Fu)KW+^@M=Ob4M_5->NIZ<6P_s!R^(#;0YrHGxWHxbPV=n#q)Zi z0N^x09WBIMJP#fx9N}Gy>J+c#;IVFjGk@NAN)3u4^{$1@HQW)D8I)^Ltq|!C=f-y33NXJp=4jowEg-|)7jg>1d6stTRM{jEyO^_ zrX7{XQq&2GO%|=x2(4%hw5~VBz%{uy+*>6`74?GN+j}4M^z^6%siLk>o$9M|Vk#7M zgmTGM=3P;DROVgL5ZX#ufA`2U&*Q5Jin8x%n?;&XP$xfo%zOXbe?9m7^&ifb_x8y{rFODa_?x180*^irq}&_Byn`z ztHhz-A6DYpBibg$!j-g;ZmbF+Y3KR*Fy_)x4L+=|X??-Wsf9T+CEaCp9B`wxpzwMizsA3bHo|sAk zOP{~FA%+-}q3gusl+|hsE$s?RLPCL|NYH8}?umOZTo9lnpwesUm&vkqD;S%ImN#qt<}rmhvF5fXQ0z;pYNK)_ zJ*FGJ6K3K2;)Ai%X3m6sF%LR5SkhiT3;+vJEt(C<&{WDvnXtMFhZ={!4=H)AxE>EP z!bWDTnR`$p7Ppw`n&9MJaPuyXs>i(Ii<&L$M~1?~AL-O%qnG%F9y{&OG)v*6{3p5O zksu4Y8qpv?1J`!HHmITv@GKOH%9ZW|i^YX^C+*}|+D<`n#Rezs2q)(*3R^rw4f?bv zlE+?sv_J3W-;;JypPw(x9Z4;xL62$>4|UlIBXPYa(EHp#Xz*nrg%Z~O#dCjXseDll zrR|h~qOz0TA`Y)fE*}?;`0uwEiSBQ)xOmdDR7uZLfot(sd4YdV*y72TK6f?;l}$Q#$4})iWX?U2FuLbM zEc}(%;9^fPWkZ^!aQCsCo4a$r-&F-Jn{BZQ_lL37qIBg`)S&1B%L@iZn?XvuI;f`9}RHHKRsWV z`_R?gmBZZP^-ZLbwW?`3%^|ngzcJxKKur>No50#12I{WKCwYPYBQ@v~FMRCGrsagI z_HMYV^SJ#=>dccdJwAQ3^RBO6V^{i8GwIcbS&!!_*92AdoIBXB*gNo9U`hdNe<0y; zVT)%g-CreJ$^>hRwy@uS-JE{NS>RE&F4A@x+(s zK6U2drw2lV697<`9+xN(6i3R+0Jk`hz90;RN1#LN@U~+sK*mX9O#^TmOD)XcP-F~n z9xtT83y=&h1IJkiS)DAXVkf+ad8!vacE+iB5CHr?Q3Vpo@JH_#00000NkvXXu0mjf Doefd) literal 0 HcmV?d00001 diff --git a/source/core/assets/images/turret-select/wall-tower-default.png b/source/core/assets/images/turret-select/wall-tower-default.png index bc7ba42f86a4181a4ed574edb88a3b164fac1685..059e516028df73ae34396915429296047ef57c9e 100644 GIT binary patch literal 10190 zcmV;PyA07*naRCr$Poe6lHRk`@z^X;?GWZ#>nNt=|k6e!d#KtU*!3J8K+EtJX?y^lNk z_kX?m7XcNJr6`D^hznj(Rs|`QR@PFWP`Z=7Nt;a8NoL=^_57dnO(vaeP1+`HCi9)= z$`|=n_X;{ulpeY`07M?jqguf0*azSUcLh^TC)Ul@*JQl z+Ni=P3V|%Eu=AaIII#CHP&9(2%c|h4bE^O$1myd0I0kQS-3ts$!v&X|31$-ysye!? z6R#;e>bKIjeHkJ7SY@DNeYyIM%>^{CUMrZm4K$6GokV4fM<rc3LPlv`Gs3l&uby(XUjZ^p?!e?341Av6k&G25{A z;a7M$EPt2d*_$kOq0*jZrA%hwWa?!+J4p!;?J|*qA({yd_T0$)Nzn8%NLiD7?_{$f zz##CfHm|a%rLfTXXP$%muRur~rCmpUaOoW{7GZ%}$MN(x1QTED%&~zh%LZnPp|K__ z{Mnk1KpPB3AsUWBQDGM38_fd_7F9m3?%PL#Uyhc4>5+6l9*T}e}Q`QuAE0?|u7{zysxK!^(mqfk9WX0PHRJf-Sn=*%x;eZ-QFw8QGGO=&{{^yFbQab?sY!T zngvkH0#W(=o?vW6moHr1KM-OSNd~9g0=aqiorOi&y9ij=WZ}S>Z3DZDIO;ZVInD9v zY}=L!muYLGs2m7OIL;!VGfb97bH|W?j6t<>NLau#i&}lLH4OuywLX7T80Zf|C=h|_ zni8n2EP^4h9N9K-5EGbXgDj^xz98HBuemPs)|QBJAf(}#0hYlTkpKb9Mj=zBg;~_* zi>_%L2w(2?M+I+x5W=A-R4*ujd6k7@gO$)&I#}D9BFcf7hGR4$R?tY9E&&yTW}=X3 zegdpZ8wSFc6OBc{iiD!DzyK@L5jnB;8(^K7+>ECtkU(02b+|XO%pa2PW)Rw4JJ0dm z@!EFqP~d(A(G`(X{h5dVDm66N? zfH*RXk>xP`ParH_r>fdTiIFjCTwLYYFwy&G|_5OGe4+9H9 zn1CFL$Tz4etTez%BOhzpHP&&0m8xkD_e56ugYu7Q2KFwhaDIC>YN6w4lcBLrLSqdB zYm#RAcseJY^BQ0oU`-3Z23Q7I23T=PHTC$#g`u#tUR9vlVx}KaG36BF(gvQTpApQ= z{yL7;8opo*>2($6TfdQSXF5*Qc|VpZZ0ZPqCK{K&34r+F z@5Yi@;C?y3)bcd>e1CJ`Neyrz8Bvzy@w<*S`s6wKS(>6c$0=7d3+ylEms+16S#B^C zzac3D3PF8x61d=+LQQ;(=_fD@guiqQpgfI$_(1xzDjsvWkIrfLgYSYL+jzLYhfaIlV*m0^y zsuO9fnwnC5r_q>`(5Gvz@OZ&$?-Q4L1F?Gmz&^55QZ?OFg4N-Po)rj68(E&+xuC@I zRPw!D0cEbo6MhI`6dVgIMM1QpDV(A)tX06l)L<>qSSJP+^~QKX-sv)jlk=(?}_7;BkGd$xLfaHDt#Ei>xLc==DQ55TAWujWwd1N>g|mz#2U&oS_-0|D&*;p$g~PWqmPpFd*f-W(*@Q@=RKjZ5}^s2M!Rel_UVxt zi|i*&hDMUWaxC-G!gAaF<7t}73<{=NOQn`Kk&jhW;DW-!teFKCX+W}r03fuW%yw&I zd+2ct;LLRh|F<|-s2dBM@qDbLLp%uKvhm_UNMPBrPlL6tACTK zz!ImO{b4k&ER$sIn$!?NGB0uPKR)dH6UI!VXVSCaS zNfr1+qrxOugPL%~FtA$uf7CD(A1i=)Ot2Eq*Bq_S@BnFQJyVza#Nz5QQBto@4W_2% z-9M__#J9u1njr9cIy9ECf^<5io&jY~6vr}_CT9Qy)6v9pGsSesepZ(` z5m;4K#ZWP?VCFgRv$|JiLF2|B6^0<>4+GCLVCH!PtXa_4r?d$Z1xS6KAVfo=0oEz) zm09qziNFdBgdiF=z?ubJeQKL<%mArB0#>U4cB{$ISf{pI(*JD}IqyceA^rRKw6tI% zu&QfHp{i~T~0FDO@Q;4DgI|~YWDi$y4zHrT2uXS!&Ood|~cW=cr3ig6>8+ZgaGG+Fk=NOaaY?Y4pfYQ(qwQ+ zJ}}8q&@>JD6#=}MKt<~pRFdZ~q6Z2K@(=#!l~=vJ^pZ9EI%{h>(g`Y3*Ng-!($m9t zJ@MEDM|%5Lbx7(G8EXz2V;TdHYep#yiCd%EE0hFVqYKAPtSaAA!aS5D1&> z)zE1!0~(VH&_;u$P%QA82sQo#kT3RuLbI@2%!7}mEJW*X!=xBZ#ei)B*S)m7@ZIVq zXTDMJ#sAu4fRzU4ymueiZ~4uC-SXL@IrCPX`>FpZWjRjJWC>}QP=m?Ur8*TVqCuuO zIA`E}IKS_8jeM-2k-ioJR+Ds%^=AI1)ZVP6z-lt#V-yP_Er3~xz-3)8;hJzGj?n@< z6RM<+pu7IvdV4|E=G&gz{Hg&~8h~}=y&d-ZZr%8mg5vU( zSKo4*#bmZ}vKU7In9}VL2CN|p7)=3(7>08OKEO){$l=Qv)L~#@-Rwy;3j-z>ypwl6 zYH%z9Mw184VFCxD{)j3P6d4L5J+QiW8=ezw1BK$@kK#h~e7sTzi^dou1RBF~w7tE&-K5e4P`PN?l~ zgvw9@QjW7Q_ z=iaY;`7TFR;W?|Xx<%r6D=o`$a^?2W4c>8vhtgmVT-p2@6oq?1zbG#?k<1tar%Lc~ z&V1N1??MQ04qY=1TGFB1#4l431fLr%!J-vwZj?B)m6F9ct+O&X zG&CzQnD0FdmmPW=oMH%~jA>ZkQdrYr33C?Mo4*iVpYsV&Xc~A$8g`*x(xejYx5B*} zKGC%|VNM6%^OWsOc*$A~98PE~VmTR>*Mim%T)!Gel&Sa%!G0u=n-xZ1mMpNO8{wApPG()-H4T2)-qr=q1Y6?VH6YR~a zf%=?EaN7$&K{U{KxMm~%S9Z(?XZL&z3%qSm5ccSfegYx{PuUj3i{_DF30lzGK{f5$ zjGP7823YBG`&ZrbFXY|(&0FtrI16jfU$-&D2^LP4M2@)G#1}HlG2LV>?rQ{2C62fT zNlli7i+n~L_HA=lLaVb32#{l&K?>#sI^mMTJD@P^84N`rVDym0xL_#>uwJH}1v$?? zvaj(0Lt~|Z#;SYq?>YB=^@~4qI16f5UHJvSz*~4p6a@_-WLDx(80v!yJ3fH<-ZqHS z{4l79UqxbEVa^PP9mVkaoaNAM%>$DXAJ+H81QPh{Vi>9iTH&Jh_rWHHf%tADvBv<5 z<4ls@+d((&{U>99l}7g!?tgVl)~5fw@qU}GvAz)CO%_fRML}X1G}rHj^SbuKTz`k| zTM-kO8tSAqRR^Xnw+eO_EQEG@v2GHH`AkhR#pqgzn7~DSjZhrw(Y0UFq#g?_mNUtL zfeyN9-#=+be$I2h+}}9vIao7QlVxPMVgJ_6&P}&$e8BF^UqrxS%~oEC#dtDyBM@BB z@u99=$b@o2KP8eL-~t2^&B6P{XTpbti**e|v{Pz$A^D753d8F5ov_f;tb>-rH>D=xm`51_x@`IIVc{aE z%c;_}7x_sh0u}McY*HA?g59v(y&K8{oj~GZsgNEUEMd?TzRVb4r5{)i+MW4}&;QiM zpk~YAO#VK8mHR_f?QMmes9%q=6HSzwh#%Eu&4=x!OQFqK3Vy+^Lp3}YN`Xjox_?IE zR%O91uu9QUkn&j3pZ#Ck68O924B)Uj6mH(3DlK2SdGrV^nA%IVvP3^X#63aPi^y z^-u+|rXx)%g%F4o3(r{2fGt8LuqkmY1FST?Y<&M9SXNiT;`7(t94x^SQ{LUcpWC&M z&W;CwINl>opiI%wW66X1oO!UTpcZ1f!>$a=M<%TWH4Y`AURc!Epoc5SN(b`!u<|q& zB7zl;*o$BrRRSO4Y_hC^tZvW)q^V#TrZ5dnp*g!`@yc~K$5#4V(c;4&(fN^nhSwxS z0-dReS0&xNy;Hgr4&*KXugNiHFf_&pkQMjC>LWX$%6Ax?@z5xfO0=0t7NNsg1aFot zgS~MxxI-%FVgJNS4X{qvl_w|bZzMIACA)a>g_qqFxu$CuoY%U85h;e#C^BU@Jh7?_ zLEZ+>S6`+(>?S#uGFCjAm%vvN>V_NYUV)-;A4FMU%&L<4#k#x&@O<^<(C(8U-s{m9 zRU{Vr4F}7JW2KqKdf=w(AIcVNwYRLfy8a?hgR`K!F;_!0O@gFD;RTF!KdRsCf)-a9 zd{A_z9t_pz15;Ac$tTk_#Q8tBYd<)}pswXcnm~nSz-=ply1e=DQ9&*A3`8N)H(-F} z$hLvQWd&!J4boX-J#gdozbK@sil3~$@STN$?m3LNy8@G~3@M4MwL*iz&<~0hLysj_ z4>ydQ4PY8nj0ew!| z&{(qqtm_^w0j2nNtCs$&G!m$e1_HHYNqS<{`O#n~apd1CJ{#VebDq8yE3xeOXiALs zus*+d>ERu4PS3}>|21-MF}akc-{gYLl^4OGta3dP@9p!0e;{OlHG4GHbq`lydF~Tu zFW8ZuT6()j-LcW zSs;u1l8I71ckM-)7tOstn z@qubAo$*+0d3_#WXGo0aBAghU5sgKb#q?Nm;O(;W^vUKhYte^osliYJjzTdo&vyh? zxyP&dFF+ZyKei^ zk882G@X?x5cMbw`Se9)FV}?w~B<2HIJ-Yty@|C*to~o(zp&LRv7)0yTdYbfQHWoQP z%C90Il8I+BQB9oxokdGPp(veTVspm@%jc#W_x=-dvYjve@%{aq{B}$1WJklN^7)L5 zybB0%{eSp#^;drIt?w*SBGtc|U+m3hDK;#t0>T{CU9R~wQgF8S!9V`LBzXs*=8@SxrF_#pot*Z{9+PaAsY@B+S{-L{>OB#Gt-_k1Sy}$#LCGj&#~d zaJAEx4_nJuf!kg<6oN=b%f``IIOug#b$efeMMZf#p1$YTPjy_lqTXxfBgfglr!9L% zfThM_h&GvYGV-^+QPuFyTjxLc#@03EYP95kN^|0NisIs$%3=VDRTbU&-d?g)pS~Fh zRI;atsdR`TxcJC>K;{>T1|v#n2!{czEmuhW#3l`^P9r)qrZrW`5y=4assGL~ol{3m-ao_NTA@!>8}OZ`cK&Ga0gs&{#@5L^DDHtRG&s`r7_O zt?U2eTesV)&pMA2!yyin_|>rwikZ4T#8^KJq~nkqc5mMYVoU^v9eqR+LKH?6gL>O{ zOI__dk9_i~tA2I!V}E*g>MAn5C1nIysiyFI7cKw1r?u^-4gcp(re^6%Iu;5FMEL;L z&jFd%L{Suwqm29mJ{_!UYIkim#9Ed*_4fJ^S{qmKE)y`HRoBIkR#tiWIkELGYR)HK`l(JGM+1Dg`3W%a`D9}qq1D)}OPPT4gRoNS7-1fbf%=5~JZ(^KILQb&k zi~*}|$9C&GPd&L{&YbF{#TC`HR)@n(Q?vuCS{6o#Cy|E1^VUvKloLNjOd*L~?Rs-J zbRF&jii}6cPdZ#03E0MiO1$r2pP855+W64pPZTa&N|vV1=#(*Fz5di6^IrS?6BnL) z!P@HOm#iz$6or>1(FqvxFves>-t?Kl!DDPxRRw>)ANIVn4|?4m;P|m)Un#_p14582 z#c|`l%`%|SyLWDS?4hEouI!o7RAovWi*&+EkNmv!wLd<7%_lDURQbi%-e}cSiC1LV zj#ZpXP7a#Rj!G^&4*CMneWV*&K5hele-IdM@YESHU6eXylvwpge0XxYA1G1`*X@3h zqLtu*U*G?WhwPtU-x$Ss_)d=LY`_VWoiQ5gg}Z-Tx%J6E+`RnKtBWtb_H(qRs=Ok~ zcEH%FAO13pDG8RRs}DL3bwE#t2O@z8=$rQmPy`}|0idYF@2Z;k0l+kG!m=ts^TDkY zuMD(p`u6vKH+TIPcDu7|-f67zNf(_lVEz5}AD;E@tFL}>(Rml0J5_HL=jM96F`TXJcOaV4DkF)5|p6gzOIPB`@`0v>Z+%|{rt<@PP+G}v4B|* zmiJJL@W2;0d|DM@#W}04$rUVivm(WL4A?>fqr*49Pm`nv_Y4w;-P;$?O&{v*19ad3 zfD#1|9DvVU43L`x5RWH->uOI7R7=YNS1-Uc%R|7^rnJ><4HlnK{rjJ8+y2}%)pkmX zo%LY#H#V5=z4>!DYBE}}{L=M;V78h>F>b<|W&sTB^y#UT{u$ibMZog)6R_9>So`(^ zXb}M333$nwN5T5I4xl>;)?C0f)tKQ-*w@ocHSYbVT6V_5U;pgwoqwJZ0;Z{uSr1lk z-N)v;Z@S?YP^gtFE?bW*HYYENaWhu2WxB%*$v#Vr3ITisEKdqp`wsx91T4o0u<8N2 z$1g!36iX3=bR5kCn54^YMiD_bDN{Y=4u-0FHD^}@MD=)uMHQ8Le6pxuf zn*m@Qm&P)y-tI=GdH-f8s+{+`2j1WF_>>SZO@(A0SXLLWNO1vcnsqSZK24%MTC6@7 zh(HgD4r#2uK0x~q0w|G$kF{hmKo0S-L_l?QDL&R*z}53HBbXI$S0mfJ{}m{xocF}0 z_xJpE+ADq%FUSnAQcR)Q>f&TECSVO)iStelf1K`=43PQ<0t76i`&b?ihPrx;Vlj+u zHUzb2Ajrx>AW8s@O&W5$$&@&PvN-_rD}mumT0&#J0>u^Of4F~!-&Qf}!5XOh*!@vK zKzYfiki1P+0EHrn$UC_J({9m6i+bce5&;wyNl+DIK|o-!0L8LYpub&itABN%dO_uj z_r0|BC9l~sW4D9Ndayd{_L+b8-S1o?MU+o0S#ec4$C+)4Dv<+>$;6!Q{t&Q0OD;{G z%o1gN7yhV}I80#K2E)j#&?u$KApGW7E&7 zm4QfAMa{B8hUP7rBGWoZ)Zo!UDVPlXmvnS$8Uimtj~)t7?2aadAZp5BLL3?d4S!trvZE zz1vz`5~r;;MW1V&VQJD}UHp@OV6#mCSFSy+&c~8v1!7%?1u78X1Q9cu7-5izX{5;r zXc}0K(>sv#Aw_eTbddroxu+R}I=JY8R>|dGIyR3ueH ziYOD!gAkHY;;@>OXJm5k7Y&*wqZqj|91=^_$!8QOlq$t(IU1+sSeRBca_k!o3~$1i z;Z>TYRmN&lXpU1=npRnpS)t6lf_Xu~HixL?<%=o<+Dz0|q>YmX>zYTmVTPqaunK7f z)~G*ZSpiBR5FAY!LpDJVriuRglh2c-REmPg4-3g9=);$Fk?-_U6&ds_4L@vLt`C}fHO7kq`@*wVWuK- zV(p$ZSl@fS9tXoQ2#4cfb6Si5>50iZW1gBcSeM-M5;j|Tm<=~mWK8d##)eKBtc!p0 zA~xIjw3~!Jjk3+?S5F$OZ@#<_M@11tQ30FN3a;$*z5Oerd;AnNe$rq)<(F{lp&n>% z?lj_9r>Jw%`8AUU>)D8kTbjF}p}~l}r*ns&s=7}aEF;`-s=8!$yvG1*cJ$+nYehza z^^cWhJ4sJ`dPn=R8P_ATq(T`77J=$Jx2@N^uY1>Scxrq7P*)nDO&!%41y(9lV~Oy= zhyJ2}YyfuhfMpC=6G4?KN&=Vx*U7<^QDD`SxZsNGuNacp+?twU(rc?vn8mnzH$3`m zy#d!ru$6IOoi(>W7uTNcE%4Rf{%Dx!>bE6KAxQ+RWHPkZ@e`KOU=eX8;Yw<&q{e#v zy`CZMW|&3;tWgpmz7;Wn^={wLcb`AMM%P%$bj59-9rheCg$7v14^!$BvszxB=Fw@5Gt-Mgo?VL&5@{ zS=8!_t!Wqtt@Zh%g3lX*pf3#m6tI5T-8Hb((-&Ok4@Q9DSjfq?$we+}LxJ7&uEWf3 zj|1J_E6P#QP;!L&Ol+`Gr|Fe|rXPI|kk7Mb0o1ZUR6f5a7+cZh4_Etr5!UYuL0}*Z zv8c3Dip#suZ!l)FqS~#&Q1te?gYCJ&P>izM&HB;Yc}`1fO}1qV%`xu{V0;)Ug1o{dnsDk;nB2O?|x{Lu;ml;0PIkT0x-gE5aJsdxFaiXPXs&t{AZ z0)F{2e<1Sx-X7n4cjo|D?Pj96q+F-PUFtAzo97hv4nk#UteM&O(_Je};5y`wFX;$G zFZK8%B|cw-^ZP^K_kiLh`ykPv{%{o!Su1=0mTz1kj%?(ZU=g$UEPzGv%b$sf;?G)}d-B^mJmh}9DZxu;Y4EzW!F&IP z)r)+WL}{Y$XH~Dw@W$vGD;$SNDAubg_`juC^!Yb`bbTag{wCB|$qZz)7>#PzcDnmF zM55xdslbY88&9tf+79)ABq@-SV~2|QMZoba=$Z)wxH~-HKH>pQ!%$d~1#`*^fTqdi zfB=f3!r|5)=6*IYBlK~5xDkz*bKXNHiM>OShS=ZmaVD*mQCCjC`vNC z_jVoBAM5~%TyHvI3#OvhMj0s*c+~4EyzQeeFHPRLH>Pm@KOy? zDWVhsgCJetrn;x z^!v2{`kLI=1k{gmZUF#nBM-DR%z|up3&Vq$r@KBz$i8bLHc8EOX7Fm--e8bpkI}6) zGydyVXVvlov+LW?*1%RgzTZ{L8xKH<3$PlZmwIoXK4S4tbhN<9akj{l8%j~3jE{NVqG`du!k_qi!x zPqwc4$Mbd8bSJWXYEGYlsND2?A^@IBBbr=sJ6(|Mms)lF#d#`8U0HEOE2r~g6FO!% zu$zaH66ZCfHZH4tGM~j3HQ-4Kzhz^wE+6X>uJG+KtQ6u_B~w*`vh5BtEg7v^bd}-A z-t;rur4+r_=#JoJ)IdHNF|sh`yx_QsSczTjO%L<$B$>AkE#=KB$e|Fz1Ou40RvGUx z%5kXy<@EbA6CIE@Re9y!V)c|sfuPy}#N86Xz?K2co^UR>f4QiL5`xk==gPTo8riy7 zK51%*u=I@_aE_WZ?b6H3UYzs2S(Y81`NQJ((^YI(~CZ;tvTfC zQ$uPb@ZTz#srlSjc@#LxiyLF?H}6YemBvEw*^!e z?}Z^Y4#;m7rBMW?($-XD2Uw28Q6RD{yBO5X;s?}ll6IHb`rgu)eMo1NytV!$r;>$F z$Igo195QOCqvwUZJfFH%|5Z?vh?`*)8Kl?<$|ee3rPVtQQcUk-oW*KG3SUPQmkYYo zIii=BHI0GO$0RD}vai2Php2}4d~(P7tm9Yw8dsiK4yaVNv`pF0DG#{Kvn8A-zMr>r z18R{J3v`bUXD_XoKUV85U+BLDy5!B&^SP%{vW=Ux4x#Q{&1z) znonxk_L14nQcAC;b1kp{#kwQ8Q`NNc%u3T76>#8~6Rdj=vBbk*{GvoX$Ej6tMBf2O z73m;rRPnIv`I&J8ZY4e5dh?J>{|yS})1_i&o}+GQdFOBBPoJh0!aXD%wUU8u)r8mi z_NnAhwy^Ee7(88_z+Yfbjl?1HWO6JIC)0wXso7N`Jwn1XOby$3KsF3DqPob3md4L; z4v8#e9^3Y|OHx|t#btXCoqEqUbQG^c#cf|W4Yrmp81xl6(1-kE!yrn9m`l}h-W5SQ zIp2My+YfpI#;%E+TJeaeR#aAXO;#xWelXoiazEtJAm!s9j=AR|EQsx9h|v%RXyu%# z6oH=PL9Kez#=S4v_Uqu96j>nZTEyj%qh(gwZYt3oL>!a%EgNSJvfy*?OiPz7;%)lb$r*}Ks{e`O{A7gF`R3`yK zW`Cz~*0uP`QDN&Bd7Quz&mpYd;EIX%f${j89G7>svn8;AnO>RAqFTkY3tt`pbDp%B zt5#vSJ6g!fBy+*L;vwPT#km|O&Lr#AtfgrBUmfRIH)Wzo$ot#hRC}|SOGBQz3e+I_ z0kvn>Df($%ZZAimJ|;`C&VT|sdZcHri?6BGEwf`63a1LMUt*zS!>*6|=o>cdK>;}W zolGqZv=G_iN9J;PHozmTT~Xio{;c9`vDNnUUwu8nl)3cZC&DYIha&eKqdC9GDlqMf zkbB@AoXA-(jB9v_x$JbNkv|VlA`d9+9Ue$#X0E4t;ii_I)sawZBnFIgkIeEzTqasG zGyg0Sri&$yTaxyF^#n8jraTc-65|3EwLyvE!c;j=x*|6NQ*;uNALAIO_5EK{)DUuS zf+xwu@REw3y_>eKqAs+sZ`x01;&C0Tay2l;6?qd72LzLrHR@3}NjqnRT%9g|lc7fW zH|_%u;*+yqr7uK<0!jOZCDr8m{ZmCUKuTpg2#F(qGuxw`W3|6=|FeQOlpR$Ok4!zjOhO6g%b5N$G$yVqpW z^--^&aNyTe#Nb2h#${P|Rw9jTVZeGXQm38;Gf11jM=CE7uy^IZcz!>oIE9x-|XqA)Jx6US7)|v*?Vw;dIZ(rfCVmh+A?tQ14zU(Xg=Y)907+d+6ReQt z=2@DSN~f>C>&-s5$*Cr@2tFl!t%C9St3+Juc=SPI&14L$v=RoCWqc)2x?sDZ-`A`ora9 zb3q^@>ZUdib-|;`m>-*xP~{BnT+2s(f&HGH&4ij7$=LmU<5q4DxqwW;Pq_WY2JbSo z%-R{BdfzuFteex@w=m`FI1wD|Gv}4Q*7&l-h&naYd1P+y2bmBYtJW$47P-HYbLH-3 z_vf^7dFC^eo9LlLEXavBN>?Q2g-6(pbjtVK9+Pr8#P9YhT1LjRLpgYsyDkDohS#Jc zSrOeR6){eyZM^mq*rK(p_efcW?|I}Q;mhw2B9w@W);oP-I)6gMIZ+HjFKJ?}q?aSU zEXu<=zxO>$dYK?h8z&UBTPDTMv+yn&V$pYrAQhK*MZRbku(~5J54#LddXiE&%=d?H z_TpdMhxndZIDsEYn^6(-W5{{bR`wj$ZJFBUmlDN7(_5dj>=72k0~C9DA6AO!cT7f4 zFC(o+p?~mk0Gf~WQ;O4#)En40;UE38E||A5`n2>Bf86}mTHZJJI|k?pU&H*Ymng(g zvh&Y|>WIW$$Sa38ZGVLe`P7&bdl@_lQa9NH^LgmtH=Yz8WP}?i9AEUXn?Xlv;^%;aB<~JvQ zuDUJ*;?g)x=O_`k{JwI)dP3_N_OMt4B1zD;_+|!BE#%MRAn7siQOw}^#fUAWqnW%w zl1VSuIO^B(GCbGo_q(1p_n)aW+|Y_f2As@hJxr?0JHXp;doqh!icCoyNba#f&0)Xw zBMf)T0XTsc?7!l4jqRIf_i5Mjxt!uxJh_zg`H{KUzM1PF5^BOCwN-WIMy< zYk%`X129^dnVfX?U?Ozo4KyDO2bFIbXM?e~+9UwcBaDGdWG6d6kNI-Y@FkXtcouaT z+~uDhB7xSoGIAh!j1j<~$=}(2=sVc6V^WIH!cW{2yM^HCK9N-UE*DjpAd&xP2)sDS zivF%EC5*g*lcAs%n`wD83~CZ;&GFKq>_5{lx-r1+bH*0@NRjkObr_Z!pEk|L%ToM1 zOaJ{xek7*SLUB61sZSBXC`CZYh;jPa@)=|8MQ83ydB)i3`;mCO2v_M%kWrZ**Z6+z zU%LXq)AjZSHw4+seI@!)>h|8sQ$x9Z15kOT0YWHIUT>g}2$r((6S~P1hJD=dTs=&@ z6d_ARGQ?`XYPeUj`P%3!LyPnMm%;I7AqmzKVU`&nNmu z;S@YUySMh45%&Rx-+)s+?>WB7JlY+jNPHh`nAT`Edj71f-FuEk1isR3!IRLLp=c3e zgkcA_!HyNCmQMVoKOCb7MG@hh>%??uP7KGO$dOxPR+xW3VK8*(oo1t)rQ!8g`z*_0 zPA`!N{*)I8n$aP!|GL(6L}F1oNfP^yOImYv-fAppN1LP*!7VA;go#$;JmgC!*@QU$ z|E?5=4qydkx2C*sa6$@zEXcKWrBa0emKqOP&uFQot~Kp19NZQlBPG(^3pA(o=~&bt zy4p3{F%jM^w&Sf|inukN#f)XU+jkKgfLvQBF_B}@^_C%2Hgr?S_KDy0#}7j3(q}$#``CrudHZZwy1Ikm*QYHz z;l{FM2Ev8gR(Ml%mzUuq2`m}kO%Rwj1h9Uzv7GP*npPzHRtBsMe@nkuZj5W^|EKf8PwNuy!HlQhP+KXn2ISIpX1Uj!}PLHa~$`=WOB#5i}Br`joes6 z+lVK9ZvvOY9Y1&8VtakSRb}rj%d(=C9&(Q%kvb=VTv}0|_o+RlKlw}JGzdfIRo+$E z0#D~oD_rEm)NGN4)8reLC7e6;#I3(b;d}N|!|#@?a?ZCnyz8rMjbslU(_Sd2`I&a! zM{kd4{=L-hrD?UH-b@a8&$C_Xir-kdOT7G~DqDYL5lDU^x87JzJ3~DBz+77!56PY% zx>-Lq(|wpJSOQ+^zo(PEvV8EYtj+GaJeKW8FL?r#1i8B}AHmwitc&}04LPtqqyklV zJT2XH8#=2+-2ioThDg}Q?_yxtehS>T{aD*Cu7bQq+;d*0C|fN_$9Es~FXZ?Xa)&-Q zcly{#ltOX&Hp)hUD`{t}47Bg(&6kvvWTCVAR-hI=mh5y?Unr};I9jYrw%0k4hRaqo zU89NLamv!?YkO-TnY?_9Mn7LEqO1*i-*9AtJc%Z@ta9>$rv3!^b~pX@OBk;@F9T@C zZR8hV3$Z!t`}kvXZ^wQhkw0mKlJ0K)VT0eOe`QMS#`S6cmQh-)Gdi}KX|c9kWd3>_ zSm^QU-dlr>vva?X5tdHH>!cS%>bI;dLmm(co-v^OH~y1%kT>i6G58}F@wgCnU~(a zgCwng*_&mKG|zdvGN0eD+cbMRJ$-=<#HuNX$5|csHEsW*vJpUvZ4>hRpW9E_6nT3` z6?L5$!6v7@Q)pvP`jFLZ#to}X+0#gS-)f&~cZ}#vJQR91l|qw3iq&8xbhB`SwynFu zk5T!LxXi^5JC5qD&wlpb;{$q}NDHms{F>NO&fD`laww_T`vf|3gN!Dcybh?3^8pnc z9|Wn%2v*QrTc9x@#>2Yr>Bwo5tD+#-9oox4<~~KF;yQfgA$>g-=uv!DMG(xRsx>tp zfrrCP)~wzq7}8f@Z-JlQGw0FulH&$1qY}O3V;VSy^ulY(`zrh31xKqSnE(7|xw7tf zW1a}J$YE_AH+4Tce42EmMdaqc_%Zw*M2gkJYVW&Rq0tyO{#U$A)YlaVp8Zpq8xWLT zk)RW$Cob)YqmyrrlITt`PQ8YlWG~b^OhV4F?+rm|=J!Ws!jCMh)isN8a!!&-T;JXm z_cFb$_{hfD$#wVgHOL$Mtg4#@5zn$1e*OfXFY<2Kn!RuT68%Dx6pLN2ZuoOkJU>## zoc)>|2#F<&S6S3-kaFEW@>w}1Csh8{=*Z4^)Y2C0-l2Taq6VW6=}c?S&q{p!zZA#| z-AY3*J$>;)IO_|oyZjW8|6xJcs`i6}KWzLg{>lX4gUlrGx!pYkY3dnBKZ`G-mV^cs zD~*j&HGe}T{9a2ZHgnenSl`{IIViW*vkLcBSVOGHC2WrMdXguOgk5L*sG-*XC&Nf%6dAbzv zCkZp1Sn+cJon2_?(RT z?Va!00fC1G9v(zs(g@8gk>ALP%kY9dc@@0RN03#wyGy~x7p6`lHfB^ZSW!3B;H|4y z;OBY{;Cq6I37uaEUxrRhS+Ih2<&=d`0+r~0ajwfbKlNO3|ZfTfUosCq8pN6gm->SnnN>K?jm&CjRF zUQ$MtzT#B$HsRa!!<+6pT1KwjhZZ;WFI0vI++qC$RiTD}YpAKdy9;4MImRwce$FuS za&9~$&#c^IGw-j}+@ESrjq9hdMt=NAf!=^aY9TSf2QKIl=hUc})w6#^DY$p_H~7I( zTe2b=yFKP4{qL6`v2dz@%QfC99#>#q#i#TjWo4%zCzXXY(GHKXH23n7#tRq?w23c8 T(EaH(%MW+}H_)orL`MA|0X?Vg diff --git a/source/core/assets/images/turret-select/wall-tower-selected.png b/source/core/assets/images/turret-select/wall-tower-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6564c3a915611e4f64da55569b6dd3cd7ad3dd GIT binary patch literal 6765 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 P00000NkvXXu0mjfxlJh@ literal 0 HcmV?d00001 diff --git a/source/core/assets/images/turret-select/weapon-tower-selected.png b/source/core/assets/images/turret-select/weapon-tower-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..f9e319656720a6bd306e3491e3cee99218268136 GIT binary patch literal 9914 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 Date: Mon, 16 Oct 2023 13:43:10 +1000 Subject: [PATCH 02/27] working through facotry tests --- .../game/entities/factories/WaveFactory.java | 7 +- .../entities/factories/WaveFactoryTest.java | 450 ++++++++++-------- 2 files changed, 262 insertions(+), 195 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java index a064173e0..d5ed54d20 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java @@ -67,6 +67,7 @@ public class WaveFactory { )), new ArrayList<>(Arrays.asList("DodgingDragon", "Coat", "Coat" )), new ArrayList<>(Arrays.asList("FireWorm", "Coat", "DodgingDragon" )), new ArrayList<>(Arrays.asList("FireWorm", "Coat", "Coat" + )), new ArrayList<>(Arrays.asList("Coat", "Coat", "FireWorm" )), new ArrayList<>(Arrays.asList("Coat", "Coat", "Coat", "DodgingDragon", "FireWorm" )) )); @@ -153,7 +154,7 @@ public static LevelWaves createLevel(int chosenLevel) { minMobs = 8; break; default: - boss = BOSS_2; + boss = BOSS_1; bossHealth = LVL1_BOSS_BASE_HEALTH; possibleMobs = lvl1Structure; minMobs = 5; @@ -176,8 +177,10 @@ public static LevelWaves createLevel(int chosenLevel) { // Calculate the number of mobs for the wave if (leftToSort == 0) { num = minMobs - currentMobs; + System.out.println(num + " for " + mob + " at wave " + atWave); } else { - num = rand.nextInt(minMobs - currentMobs - (2 * leftToSort)) + 2; + num = rand.nextInt(minMobs - currentMobs - (2 * leftToSort) - 2) + 2; + System.out.println(num + " for " + mob + " at wave " + atWave); currentMobs += num; } diff --git a/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java index 6abe8ec1a..a18ea5940 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java @@ -1,140 +1,194 @@ -//package com.csse3200.game.entities.factories; -// -//import com.csse3200.game.components.tasks.waves.LevelWaves; -//import com.badlogic.gdx.assets.AssetManager; -//import com.csse3200.game.components.tasks.waves.LevelWaves; -//import com.csse3200.game.components.tasks.waves.WaveClass; -//import com.csse3200.game.extensions.GameExtension; -//import com.csse3200.game.physics.PhysicsService; -//import com.csse3200.game.rendering.DebugRenderer; -//import com.csse3200.game.rendering.RenderService; -//import com.csse3200.game.screens.GameLevelData; -//import com.csse3200.game.services.GameTime; -//import com.csse3200.game.services.ResourceService; -//import com.csse3200.game.services.ServiceLocator; -//import com.csse3200.game.services.WaveService; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.Disabled; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.junit.jupiter.MockitoExtension; -// -//import static org.junit.jupiter.api.Assertions.*; -//import static org.mockito.Mockito.*; -// -//import com.csse3200.game.entities.Entity; -// -//import java.security.Provider; -//import java.util.ArrayList; -//import java.util.Arrays; -//import java.util.List; -//import java.util.Map; -// -//@ExtendWith(GameExtension.class) -//@ExtendWith(MockitoExtension.class) -//class WaveFactoryTest { -// -// private LevelWaves lvl1; -// private LevelWaves lvl2; -// private LevelWaves lvl3; -// -// private final int MIN_HEALTH = 60; -// private final int MIN_BOSS_HEALTH = 80; -// -// // level stats for level 1 - water planet -// private final int LVL1_DIFF = 2; -// private final int LVL1_WAVES = 5; -// private final int LVL1_CHOSEN_LVL = 1; -// private final ArrayList LVL1_MOBS = new ArrayList<>(Arrays.asList("Coat", "SplittingWaterSlime", "WaterQueen")); -// private final String LVL1_BOSS = "IceBoss"; -// -// // level stats for level 2 - magic planet -// private final int LVL2_DIFF = 3; -// private final int LVL2_WAVES = 10; -// private final int LVL2_CHOSEN_LVL = 0; -// private final ArrayList LVL2_MOBS = new ArrayList<>(Arrays.asList("ArcaneArcher", "SplittingNightBorne", "Skeleton", "DeflectWizard")); -// private final String LVL2_BOSS = "PatrickBoss"; -// -// // level stats for level 3 - fire planet -// private final int LVL3_DIFF = 5; -// private final int LVL3_WAVES = 15; -// private final int LVL3_CHOSEN_LVL = 2; -// private final ArrayList LVL3_MOBS = new ArrayList<>(Arrays.asList("Xeno", "DodgingDragon", "FireWorm")); -// private final String LVL3_BOSS = "FireBoss"; -//// private final String LVL3_BOSS = "FireBoss"; -// //TODO: make this a fire boss in sprint 4 -// -// private static final String[] waveSounds = { -// "sounds/waves/wave-start/Wave_Start_Alarm.ogg", -// "sounds/waves/wave-end/Wave_Over_01.ogg" -// }; -// -// @BeforeEach -// void setUp() { -// GameTime gameTime = mock(GameTime.class); -// ServiceLocator.registerTimeSource(gameTime); -// ServiceLocator.registerPhysicsService(new PhysicsService()); -// RenderService render = new RenderService(); -// render.setDebug(mock(DebugRenderer.class)); -// ServiceLocator.registerRenderService(render); -// ResourceService resourceService = mock(ResourceService.class); -// ServiceLocator.registerResourceService(resourceService); -// WaveService waveService = new WaveService(); -// ServiceLocator.registerWaveService(waveService); -// ServiceLocator.getResourceService().loadSounds(waveSounds); -// -// lvl1 = WaveFactory.createLevel(LVL1_DIFF, LVL1_WAVES, LVL1_CHOSEN_LVL); -// lvl2 = WaveFactory.createLevel(LVL2_DIFF, LVL2_WAVES, LVL2_CHOSEN_LVL); -// lvl3 = WaveFactory.createLevel(LVL3_DIFF, LVL3_WAVES, LVL3_CHOSEN_LVL); -// } -// -// @Test -// void createBaseWaves() { -// GameLevelData.setSelectedLevel(0); -// Entity level1 = WaveFactory.createWaves(); -// assertNotNull(level1); -// -// GameLevelData.setSelectedLevel(1); -// Entity level2 = WaveFactory.createWaves(); -// assertNotNull(level2); -// -// GameLevelData.setSelectedLevel(2); -// Entity level3 = WaveFactory.createWaves(); -// assertNotNull(level3); -// } -// -// @Test -// void testCreateLevel() { -// assertNotNull(lvl1); -// assertNotNull(lvl2); -// assertNotNull(lvl3); -// } -// -// /** -// * The three following tests ensure that every wave in the level is created correctly -// * Since the waves are stored in a hashmap, by definition the mobs are unique and this -// * quality does not have to be checked. -// * */ -// @Test -// void testLevel1Creation() { -// List lvl1Mobs = lvl1.getWaves(); -// -// int waveNum = 1; -// for (WaveClass wave : lvl1Mobs) { -// -// // check the number of mobs in a wave -// if (waveNum % 5 != 0) { -// assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); -// } else { -// assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs: 2 general and 1 boss."); -// } -// -// // check if the boss is in the wave if it is a boss wave -// if (waveNum % 5 == 0) { -// assertTrue(wave.getEntities().containsKey(LVL1_BOSS), "This wave should contain a boss."); -// } -// -// // check the health of the mobs and ensure the mobs are the correct type +package com.csse3200.game.entities.factories; + +import com.csse3200.game.components.tasks.waves.LevelWaves; +import com.badlogic.gdx.assets.AssetManager; +import com.csse3200.game.components.tasks.waves.LevelWaves; +import com.csse3200.game.components.tasks.waves.WaveClass; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.rendering.DebugRenderer; +import com.csse3200.game.rendering.RenderService; +import com.csse3200.game.screens.GameLevelData; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ResourceService; +import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.services.WaveService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.csse3200.game.entities.Entity; + +import java.security.Provider; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@ExtendWith(GameExtension.class) +@ExtendWith(MockitoExtension.class) +class WaveFactoryTest { + + private LevelWaves lvl1; + private LevelWaves lvl2; + private LevelWaves lvl3; + + private final int MIN_HEALTH = 60; + private final int MIN_BOSS_HEALTH = 80; + + // level stats for level 1 - water planet + private final int LVL1_WAVES = 5; + private final int LVL1_CHOSEN_LVL = 1; + private final int LVL1_LVL = 1; + private final ArrayList LVL1_MOBS = new ArrayList<>(Arrays.asList("Coat", "SplittingWaterSlime", "WaterQueen")); + + // level stats for level 2 - magic planet + private final int LVL2_WAVES = 10; + private final int LVL2_CHOSEN_LVL = 0; + private final int LVL2_LVL = 2; + + private final ArrayList LVL2_MOBS = new ArrayList<>(Arrays.asList("ArcaneArcher", "SplittingNightBorne", "Skeleton", "DeflectWizard")); + + // level stats for level 3 - fire planet + private final int LVL3_WAVES = 15; + private final int LVL3_CHOSEN_LVL = 2; + private final int LVL3_LVL = 3; + + private final ArrayList LVL3_MOBS = new ArrayList<>(Arrays.asList("Xeno", "DodgingDragon", "FireWorm")); + // private final String LVL3_BOSS = "FireBoss"; + //TODO: make this a fire boss in sprint 4 + + private static final ArrayList MELEE_MOBS = new ArrayList<>(Arrays.asList( + "Skeleton", "Coat", "DragonKnight" + )); + + private static final ArrayList> LVL1_WAVES_STRUC = new ArrayList<>(Arrays.asList( + new ArrayList<>(Arrays.asList("Coat" + )), new ArrayList<>(Arrays.asList("Coat", "WaterQueen" + )), new ArrayList<>(Arrays.asList("WaterQueen", "SplittingWaterSlime" + )), new ArrayList<>(Arrays.asList("Coat", "WaterQueen", "SplittingWaterSlime" + )) + )); + + private static final ArrayList> LVL2_WAVES_STRUC = new ArrayList<>(Arrays.asList( + new ArrayList<>(Arrays.asList("Skeleton" + )), new ArrayList<>(Arrays.asList("Skeleton", "ArcaneArcher" + )), new ArrayList<>(Arrays.asList("Skeleton", "DeflectWizard" + )), new ArrayList<>(Arrays.asList("Skeleton", "NightBorne" + )), new ArrayList<>(Arrays.asList("DeflectWizard", "NightBorne" + )), new ArrayList<>(Arrays.asList("NightBorne", "Skeleton" + )), new ArrayList<>(Arrays.asList("DeflectWizard", "NightBorne" + )), new ArrayList<>(Arrays.asList("ArcaneArcher", "NightBorne", "DeflectWizard" + )), new ArrayList<>(Arrays.asList("Skeleton", "ArcaneArcher", "DeflectWizard", "NightBorne" + )) + )); + + private static final ArrayList> LVL3_WAVES_STRUC = new ArrayList<>(Arrays.asList( + new ArrayList<>(Arrays.asList("Coat" + )), new ArrayList<>(Arrays.asList("Coat", "DodgingDragon" + )), new ArrayList<>(Arrays.asList("Coat", "FireWorm" + )), new ArrayList<>(Arrays.asList("Coat", "Coat" + )), new ArrayList<>(Arrays.asList("Coat", "FireWorm" + )), new ArrayList<>(Arrays.asList("DodgingDragon", "FireWorm" + )), new ArrayList<>(Arrays.asList("DodgingDragon", "Coat" + )), new ArrayList<>(Arrays.asList("FireWorm", "Coat" + )), new ArrayList<>(Arrays.asList("Coat", "Coat" + )), new ArrayList<>(Arrays.asList("DodgingDragon", "Coat", "Coat" + )), new ArrayList<>(Arrays.asList("FireWorm", "Coat", "DodgingDragon" + )), new ArrayList<>(Arrays.asList("FireWorm", "Coat", "Coat" + )), new ArrayList<>(Arrays.asList("Coat", "Coat", "Coat", "DodgingDragon", "FireWorm" + )) + )); + + private static final String[] waveSounds = { + "sounds/waves/wave-start/Wave_Start_Alarm.ogg", + "sounds/waves/wave-end/Wave_Over_01.ogg" + }; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + RenderService render = new RenderService(); + render.setDebug(mock(DebugRenderer.class)); + ServiceLocator.registerRenderService(render); + ResourceService resourceService = mock(ResourceService.class); + ServiceLocator.registerResourceService(resourceService); + WaveService waveService = new WaveService(); + ServiceLocator.registerWaveService(waveService); + ServiceLocator.getResourceService().loadSounds(waveSounds); + + lvl1 = WaveFactory.createLevel(LVL1_CHOSEN_LVL); + lvl2 = WaveFactory.createLevel(LVL2_CHOSEN_LVL); + lvl3 = WaveFactory.createLevel(LVL3_CHOSEN_LVL); + } + + @Test + void createBaseWaves() { + GameLevelData.setSelectedLevel(0); + Entity level1 = WaveFactory.createWaves(); + assertNotNull(level1); + + GameLevelData.setSelectedLevel(1); + Entity level2 = WaveFactory.createWaves(); + assertNotNull(level2); + + GameLevelData.setSelectedLevel(2); + Entity level3 = WaveFactory.createWaves(); + assertNotNull(level3); + } + + @Test + void testCreateLevel() { + assertNotNull(lvl1); + assertNotNull(lvl2); + assertNotNull(lvl3); + } + + /** + * The three following tests ensure that every wave in the level is created correctly + * Since the waves are stored in a hashmap, by definition the mobs are unique and this + * quality does not have to be checked. + * */ + @Test + void testLevel1Creation() { + List lvl1Mobs = lvl1.getWaves(); + + int waveNum = 1; + for (WaveClass wave : lvl1Mobs) { + + // check the number of mobs in a wave + if (waveNum == 5 || waveNum == 1) { + assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); + } else if (waveNum == 2 || waveNum == 3) { + assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); + } else { + assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); + } + + // check if the boss is in the wave if it is a boss wave + String LVL1_BOSS = "IceBoss"; + if (waveNum % 5 == 0) { + assertTrue(wave.getEntities().containsKey(LVL1_BOSS), "This wave should contain a boss."); + } + + System.out.println("wave:"+wave); + System.out.println("wave entities:"+wave.getEntities()); + System.out.println("wave entities:"+wave.getEntities()); + System.out.println("wave keyset:"+wave.getEntities().keySet()); + System.out.println("wave values:"+wave.getEntities().values()); + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); +// assertTrue(values[0] > 1 && values[0] < (minMObwave.getSize())); + System.out.println(key + " : " + values[0]); + } + // check the health of the mobs and ensure the mobs are the correct type // for (Map.Entry entry : wave.getEntities().entrySet()) { // String mob = entry.getKey(); // int[] spawn = entry.getValue(); @@ -148,31 +202,36 @@ // } // } // } -// -// waveNum++; -// } -// assertEquals(6, waveNum, "The should be 5 waves making numWave 6."); -// } -// @Test -// void testLevel2Creation() { -// -// List lvl1Mobs = lvl2.getWaves(); -// -// int waveNum = 1; -// for (WaveClass wave : lvl1Mobs) { -// -// // check the number of mobs in a wave -// if (waveNum % 5 != 0) { -// assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); -// } else { -// assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs: 2 general and 1 boss."); -// } -// -// // check if the boss is in the wave if it is a boss wave -// if (waveNum % 5 == 0) { -// assertTrue(wave.getEntities().containsKey(LVL2_BOSS), "This wave should contain a boss."); -// } -// + + waveNum++; + } + assertEquals(6, waveNum, "The should be 5 waves making numWave 6."); + } + @Test + void testLevel2Creation() { + + List lvl1Mobs = lvl2.getWaves(); + + int waveNum = 1; + for (WaveClass wave : lvl1Mobs) { + + // check the number of mobs in a wave + if (waveNum == 1 || waveNum == 10) { + assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); + } else if (1 < waveNum && waveNum < 8) { + assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); + } else if (waveNum == 8){ + assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); + } else { + assertEquals(4, wave.getEntities().size(), "Wave should contain 4 mobs."); + } + + // check if the boss is in the wave if it is a boss wave + String LVL2_BOSS = "PatrickBoss"; + if (waveNum % 5 == 0) { + assertTrue(wave.getEntities().containsKey(LVL2_BOSS), "This wave should contain a boss."); + } + // for (Map.Entry entry : wave.getEntities().entrySet()) { // String mob = entry.getKey(); // int[] spawn = entry.getValue(); @@ -186,31 +245,36 @@ // } // } // } -// -// waveNum++; -// } -// assertEquals(11, waveNum, "There should be 10 waves making numWave 11."); -// } -// @Test -// void testLevel3Creation() { -// -// List lvl1Mobs = lvl3.getWaves(); -// -// int waveNum = 1; -// for (WaveClass wave : lvl1Mobs) { -// // check the number of mobs in a wave -// if (waveNum % 5 != 0) { -// assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); -// } else { -// assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs: 2 general and 1 boss."); -// } -// -// // check if the boss is in the wave if it is a boss wave -// if (waveNum % 5 == 0) { -// assertTrue(wave.getEntities().containsKey(LVL3_BOSS), "This wave should contain a boss."); -// } -// -// // check the health of the mobs and ensure the mobs are the correct type + + waveNum++; + } + assertEquals(11, waveNum, "There should be 10 waves making numWave 11."); + } + @Test + void testLevel3Creation() { + + List lvl1Mobs = lvl3.getWaves(); + + int waveNum = 1; + for (WaveClass wave : lvl1Mobs) { + // check the number of mobs in a wave + if (waveNum == 1 || waveNum == 15) { + assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); + } else if (1 < waveNum && waveNum < 10) { + assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); + } else if (9 < waveNum && waveNum < 14){ + assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); + } else if (waveNum == 14){ + assertEquals(5, wave.getEntities().size(), "Wave should contain 4 mobs."); + } + + // check if the boss is in the wave if it is a boss wave + String LVL3_BOSS = "FireBoss"; + if (waveNum == 15) { + assertTrue(wave.getEntities().containsKey(LVL3_BOSS), "This wave should contain a boss."); + } + + // check the health of the mobs and ensure the mobs are the correct type // for (Map.Entry entry : wave.getEntities().entrySet()) { // String mob = entry.getKey(); // int[] spawn = entry.getValue(); @@ -224,9 +288,9 @@ // } // } // } -// waveNum++; -// } -// assertEquals(16, waveNum, "There should be 15 waves making numWave 16."); -// } -// -//} + waveNum++; + } + assertEquals(16, waveNum, "There should be 15 waves making numWave 16."); + } + +} From 5f65f89f37fefae8120ac4f427cec8e525419c72 Mon Sep 17 00:00:00 2001 From: Samantha Sullivan Date: Mon, 16 Oct 2023 15:05:38 +1000 Subject: [PATCH 03/27] finished waveFactory tests --- .../entities/factories/WaveFactoryTest.java | 186 ++++++++++-------- 1 file changed, 102 insertions(+), 84 deletions(-) diff --git a/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java index a18ea5940..5fedf14d0 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java @@ -38,7 +38,8 @@ class WaveFactoryTest { private LevelWaves lvl2; private LevelWaves lvl3; - private final int MIN_HEALTH = 60; + private final int MIN_MELEE_HEALTH = 80; + private final int MIN_RANGE_HEALTH = 60; private final int MIN_BOSS_HEALTH = 80; // level stats for level 1 - water planet @@ -101,6 +102,7 @@ class WaveFactoryTest { )), new ArrayList<>(Arrays.asList("DodgingDragon", "Coat", "Coat" )), new ArrayList<>(Arrays.asList("FireWorm", "Coat", "DodgingDragon" )), new ArrayList<>(Arrays.asList("FireWorm", "Coat", "Coat" + )), new ArrayList<>(Arrays.asList("Coat", "Coat", "FireWorm" )), new ArrayList<>(Arrays.asList("Coat", "Coat", "Coat", "DodgingDragon", "FireWorm" )) )); @@ -124,9 +126,9 @@ void setUp() { ServiceLocator.registerWaveService(waveService); ServiceLocator.getResourceService().loadSounds(waveSounds); - lvl1 = WaveFactory.createLevel(LVL1_CHOSEN_LVL); - lvl2 = WaveFactory.createLevel(LVL2_CHOSEN_LVL); - lvl3 = WaveFactory.createLevel(LVL3_CHOSEN_LVL); + lvl1 = WaveFactory.createLevel(LVL1_LVL); + lvl2 = WaveFactory.createLevel(LVL2_LVL); + lvl3 = WaveFactory.createLevel(LVL3_LVL); } @Test @@ -159,10 +161,13 @@ void testCreateLevel() { @Test void testLevel1Creation() { List lvl1Mobs = lvl1.getWaves(); + int bossHealth = 500; + int mobCount = 5; int waveNum = 1; for (WaveClass wave : lvl1Mobs) { - + int mobsRemaining = wave.getEntities().size() - 1; + int totalMobsSpawned = 0; // check the number of mobs in a wave if (waveNum == 5 || waveNum == 1) { assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); @@ -172,37 +177,28 @@ void testLevel1Creation() { assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); } - // check if the boss is in the wave if it is a boss wave - String LVL1_BOSS = "IceBoss"; - if (waveNum % 5 == 0) { - assertTrue(wave.getEntities().containsKey(LVL1_BOSS), "This wave should contain a boss."); - } - System.out.println("wave:"+wave); - System.out.println("wave entities:"+wave.getEntities()); - System.out.println("wave entities:"+wave.getEntities()); - System.out.println("wave keyset:"+wave.getEntities().keySet()); - System.out.println("wave values:"+wave.getEntities().values()); - for (String key: wave.getEntities().keySet()) { - int[] values = wave.getEntities().get(key); -// assertTrue(values[0] > 1 && values[0] < (minMObwave.getSize())); - System.out.println(key + " : " + values[0]); - } - // check the health of the mobs and ensure the mobs are the correct type -// for (Map.Entry entry : wave.getEntities().entrySet()) { -// String mob = entry.getKey(); -// int[] spawn = entry.getValue(); -// -// if (waveNum % 5 != 0) { -// assertTrue(LVL1_MOBS.contains(mob), "This mob is not assigned to this level."); -// assertEquals(MIN_HEALTH + waveNum, spawn[1], "The health of the mob should be " + MIN_HEALTH + waveNum + " ."); -// } else { -// if (mob == LVL1_BOSS) { -// assertEquals(MIN_BOSS_HEALTH + waveNum, spawn[1], "The health of the boss should be " + MIN_BOSS_HEALTH + waveNum + " ."); -// } -// } -// } + if (waveNum != 5) { + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + assertTrue(values[0] > 1 && values[0] <= (mobCount - totalMobsSpawned - (2 * mobsRemaining))); + totalMobsSpawned += values[0]; + mobsRemaining --; + if (MELEE_MOBS.contains(key)) { + assertTrue(values[1] == MIN_MELEE_HEALTH + waveNum, "The health of the mob should be " + MIN_MELEE_HEALTH + waveNum + " ."); + } else { + assertTrue(values[1] == MIN_RANGE_HEALTH + waveNum, "The health of the mob should be " + MIN_RANGE_HEALTH + waveNum + " ."); + } + } + } else { + assertTrue(wave.getEntities().keySet().size() == 1); + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + assertTrue(values[1] == bossHealth, "The health of the boss should be " + MIN_BOSS_HEALTH); + } + } + mobCount ++; waveNum++; } assertEquals(6, waveNum, "The should be 5 waves making numWave 6."); @@ -210,10 +206,14 @@ void testLevel1Creation() { @Test void testLevel2Creation() { - List lvl1Mobs = lvl2.getWaves(); + List lvl2Mobs = lvl2.getWaves(); + int bossHealth = 1000; + int mobCount = 6; int waveNum = 1; - for (WaveClass wave : lvl1Mobs) { + for (WaveClass wave : lvl2Mobs) { + int mobsRemaining = wave.getEntities().size() - 1; + int totalMobsSpawned = 0; // check the number of mobs in a wave if (waveNum == 1 || waveNum == 10) { @@ -226,26 +226,33 @@ void testLevel2Creation() { assertEquals(4, wave.getEntities().size(), "Wave should contain 4 mobs."); } - // check if the boss is in the wave if it is a boss wave - String LVL2_BOSS = "PatrickBoss"; - if (waveNum % 5 == 0) { - assertTrue(wave.getEntities().containsKey(LVL2_BOSS), "This wave should contain a boss."); - } + if (waveNum != 10) { + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); -// for (Map.Entry entry : wave.getEntities().entrySet()) { -// String mob = entry.getKey(); -// int[] spawn = entry.getValue(); -// -// if (waveNum % 5 != 0) { -// assertTrue(LVL2_MOBS.contains(mob)); -// assertEquals(MIN_HEALTH + (waveNum * 2), spawn[1], "The health of the mob should be " + MIN_HEALTH + (waveNum * 2) + " ."); -// } else { -// if (mob == LVL2_BOSS) { -// assertEquals(MIN_BOSS_HEALTH + (waveNum * 2), spawn[1], "The health of the boss should be " + MIN_BOSS_HEALTH + (waveNum * 2) + " ."); -// } -// } -// } + assertTrue(values[0] > 1 && values[0] <= (mobCount - totalMobsSpawned - (2 * mobsRemaining))); + totalMobsSpawned += values[0]; + mobsRemaining --; + System.out.println("wave is: "+ wave); + + if (MELEE_MOBS.contains(key)) { + System.out.println("the health is: " + values[1]); + System.out.println("I want it to be: " + (MIN_MELEE_HEALTH + (waveNum * 2))); + + assertEquals(values[1], (MIN_MELEE_HEALTH + (waveNum * 2)), "The health of the mob should be " + (MIN_MELEE_HEALTH + (waveNum * 2)) + " ."); + } else { + assertEquals(values[1], (MIN_RANGE_HEALTH + (waveNum * 2)), "The health of the mob should be " + (MIN_RANGE_HEALTH + (waveNum * 2)) + " ."); + } + } + } else { + assertTrue(wave.getEntities().keySet().size() == 1); + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + assertTrue(values[1] == bossHealth, "The health of the boss should be " + MIN_BOSS_HEALTH); + } + } + mobCount ++; waveNum++; } assertEquals(11, waveNum, "There should be 10 waves making numWave 11."); @@ -253,41 +260,52 @@ void testLevel2Creation() { @Test void testLevel3Creation() { - List lvl1Mobs = lvl3.getWaves(); + List lvl3Mobs = lvl3.getWaves(); + + int bossHealth = 2000; + int mobCount = 8; int waveNum = 1; - for (WaveClass wave : lvl1Mobs) { + for (WaveClass wave : lvl3Mobs) { + int mobsRemaining = wave.getEntities().size() - 1; + int totalMobsSpawned = 0; + + //TODO unhash when fire mobs integrated. + System.out.println("wave is: "+ wave); // check the number of mobs in a wave - if (waveNum == 1 || waveNum == 15) { - assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); - } else if (1 < waveNum && waveNum < 10) { - assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); - } else if (9 < waveNum && waveNum < 14){ - assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); - } else if (waveNum == 14){ - assertEquals(5, wave.getEntities().size(), "Wave should contain 4 mobs."); - } +// if (waveNum == 1 || waveNum == 15) { +// assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); +// } else if (1 < waveNum && waveNum < 10) { +// assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); +// } else if (9 < waveNum && waveNum < 14){ +// assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); +// } else if (waveNum == 14){ +// assertEquals(5, wave.getEntities().size(), "Wave should contain 4 mobs."); +// } - // check if the boss is in the wave if it is a boss wave - String LVL3_BOSS = "FireBoss"; - if (waveNum == 15) { - assertTrue(wave.getEntities().containsKey(LVL3_BOSS), "This wave should contain a boss."); - } + if (waveNum != 15) { + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); - // check the health of the mobs and ensure the mobs are the correct type -// for (Map.Entry entry : wave.getEntities().entrySet()) { -// String mob = entry.getKey(); -// int[] spawn = entry.getValue(); -// -// if (waveNum % 5 != 0) { -// assertTrue(LVL3_MOBS.contains(mob)); -// assertEquals(MIN_HEALTH + (waveNum * 3), spawn[1], "The health of the mob should be " + MIN_HEALTH + (waveNum * 3) + " ."); -// } else { -// if (mob == LVL3_BOSS) { -// assertEquals(MIN_BOSS_HEALTH + (waveNum * 3), spawn[1], "The health of the boss should be " + MIN_BOSS_HEALTH + (waveNum * 3) + " ."); -// } -// } -// } + assertTrue(values[0] > 1 && values[0] <= (mobCount - totalMobsSpawned - (2 * mobsRemaining))); + totalMobsSpawned += values[0]; + mobsRemaining --; + + if (MELEE_MOBS.contains(key)) { + assertEquals(values[1], (MIN_MELEE_HEALTH + (waveNum * 3)), "The health of the mob should be " + (MIN_MELEE_HEALTH + (waveNum * 2)) + " ."); + } else { + assertEquals(values[1], (MIN_RANGE_HEALTH + (waveNum * 3)), "The health of the mob should be " + (MIN_RANGE_HEALTH + (waveNum * 2)) + " ."); + } + } + + } else { + assertTrue(wave.getEntities().keySet().size() == 1); + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + assertTrue(values[1] == bossHealth, "The health of the boss should be " + MIN_BOSS_HEALTH); + } + } + mobCount ++; waveNum++; } assertEquals(16, waveNum, "There should be 15 waves making numWave 16."); From cfcd12afbebffdd2216d65544d86bacf7edad926 Mon Sep 17 00:00:00 2001 From: Nhat Minh Le Date: Mon, 16 Oct 2023 15:27:31 +1000 Subject: [PATCH 04/27] Fix duplicated string in GameAreaDisplay --- .../game/components/gamearea/GameAreaDisplay.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/gamearea/GameAreaDisplay.java b/source/core/src/main/com/csse3200/game/components/gamearea/GameAreaDisplay.java index 3db923bca..9a8149869 100644 --- a/source/core/src/main/com/csse3200/game/components/gamearea/GameAreaDisplay.java +++ b/source/core/src/main/com/csse3200/game/components/gamearea/GameAreaDisplay.java @@ -15,7 +15,7 @@ */ public class GameAreaDisplay extends UIComponent { private static final Logger logger = LoggerFactory.getLogger(GameAreaDisplay.class); - + private static final String DEFAULT_STYLE = "default"; private String gameAreaName = ""; private Label title; @@ -45,10 +45,10 @@ private Dialog createTowerDetailsDialog() { Label.LabelStyle labelStyle = new Label.LabelStyle(); labelStyle.font = new BitmapFont(); labelStyle.fontColor = Color.WHITE; - skin.add("default", labelStyle); + skin.add(DEFAULT_STYLE, labelStyle); // Create the dialog using the registered label style - Dialog dialog = new Dialog("Tower Details", skin,"default"); + Dialog dialog = new Dialog("Tower Details", skin, DEFAULT_STYLE); dialog.text("Health: 100"); // Set tower health here dialog.getContentTable().row(); dialog.text("Attack: 50"); // Set tower attack here @@ -56,8 +56,9 @@ private Dialog createTowerDetailsDialog() { dialog.setVisible(false); // Hide the dialog initially return dialog; } + private void addActors() { - title = new Label(this.gameAreaName, skin, "default"); + title = new Label(this.gameAreaName, skin, DEFAULT_STYLE); stage.addActor(title); } @@ -68,7 +69,6 @@ public void draw(SpriteBatch batch) { float offsetY = 30f; title.setPosition(offsetX, screenHeight - offsetY); - } @Override From 208ad9d62492a377f8297426f4f448672e749461 Mon Sep 17 00:00:00 2001 From: Samantha Sullivan Date: Mon, 16 Oct 2023 15:39:05 +1000 Subject: [PATCH 05/27] fixed import statements spelling for MobTask directory --- .../main/com/csse3200/game/components/npc/SplitMoblings.java | 3 ++- .../main/com/csse3200/game/components/tasks/MobDodgeTask.java | 3 +-- .../com/csse3200/game/components/tasks/MobTask/MobTask.java | 3 ++- .../com/csse3200/game/components/tasks/MobTask/MobType.java | 2 +- .../main/com/csse3200/game/entities/factories/NPCFactory.java | 4 ++-- .../com/csse3200/game/components/DodgingComponentTest.java | 3 +-- .../test/com/csse3200/game/components/SplitMoblingsTest.java | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/npc/SplitMoblings.java b/source/core/src/main/com/csse3200/game/components/npc/SplitMoblings.java index 5896c2f10..865dd2345 100644 --- a/source/core/src/main/com/csse3200/game/components/npc/SplitMoblings.java +++ b/source/core/src/main/com/csse3200/game/components/npc/SplitMoblings.java @@ -1,7 +1,8 @@ package com.csse3200.game.components.npc; import com.csse3200.game.components.Component; -import com.csse3200.game.components.tasks.mobtask.MobType; +//import com.csse3200.game.components.tasks.mobtask.MobType; +import com.csse3200.game.components.tasks.MobTask.*; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.factories.NPCFactory; import com.csse3200.game.services.ServiceLocator; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/MobDodgeTask.java b/source/core/src/main/com/csse3200/game/components/tasks/MobDodgeTask.java index f50ca2ebd..fed334ba6 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/MobDodgeTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/MobDodgeTask.java @@ -1,7 +1,6 @@ package com.csse3200.game.components.tasks; -import com.csse3200.game.components.tasks.mobtask.MobTask; -import com.csse3200.game.components.tasks.mobtask.MobType; +import com.csse3200.game.components.tasks.MobTask.*; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobTask.java b/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobTask.java index 5ad20684e..145ed404d 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobTask.java @@ -1,4 +1,4 @@ -package com.csse3200.game.components.tasks.mobtask; +package com.csse3200.game.components.tasks.MobTask; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Timer; @@ -15,6 +15,7 @@ import com.csse3200.game.rendering.AnimationRenderComponent; import com.csse3200.game.services.GameTime; import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.components.tasks.MobTask.MobType; /** * The AI Task for all general mobs. This task handles the sequencing for melee diff --git a/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobType.java b/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobType.java index 8bf4e3b7e..335b2a7e7 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobType.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobType.java @@ -1,4 +1,4 @@ -package com.csse3200.game.components.tasks.mobtask; +package com.csse3200.game.components.tasks.MobTask; public enum MobType { SKELETON(true), diff --git a/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java index 1ceb66b31..4b9541a52 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java @@ -11,9 +11,9 @@ import com.csse3200.game.components.tasks.MobDodgeTask; import com.csse3200.game.components.tasks.MobMeleeAttackTask; import com.csse3200.game.components.tasks.MobRangedAttackTask; +import com.csse3200.game.components.tasks.MobTask.*; +import com.csse3200.game.components.tasks.MobTask.MobType; import com.csse3200.game.components.tasks.MobWanderTask; -import com.csse3200.game.components.tasks.mobtask.MobTask; -import com.csse3200.game.components.tasks.mobtask.MobType; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.Melee; import com.csse3200.game.entities.PredefinedWeapons; diff --git a/source/core/src/test/com/csse3200/game/components/DodgingComponentTest.java b/source/core/src/test/com/csse3200/game/components/DodgingComponentTest.java index 05369c3a5..7b7b9be4c 100644 --- a/source/core/src/test/com/csse3200/game/components/DodgingComponentTest.java +++ b/source/core/src/test/com/csse3200/game/components/DodgingComponentTest.java @@ -5,6 +5,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.csse3200.game.components.tasks.MobTask.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -14,8 +15,6 @@ import com.csse3200.game.components.npc.DodgingComponent; import com.csse3200.game.components.tasks.MobDodgeTask; import com.csse3200.game.components.tasks.MobWanderTask; -import com.csse3200.game.components.tasks.mobtask.MobTask; -import com.csse3200.game.components.tasks.mobtask.MobType; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.EntityService; import com.csse3200.game.entities.factories.NPCFactory; diff --git a/source/core/src/test/com/csse3200/game/components/SplitMoblingsTest.java b/source/core/src/test/com/csse3200/game/components/SplitMoblingsTest.java index a28f836a1..d21af300c 100644 --- a/source/core/src/test/com/csse3200/game/components/SplitMoblingsTest.java +++ b/source/core/src/test/com/csse3200/game/components/SplitMoblingsTest.java @@ -19,7 +19,7 @@ import com.badlogic.gdx.math.Vector2; import com.csse3200.game.components.npc.SplitMoblings; -import com.csse3200.game.components.tasks.mobtask.MobType; +import com.csse3200.game.components.tasks.MobTask.MobType; import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.EntityService; import com.csse3200.game.entities.factories.NPCFactory; From 36e9a9df691b1cdfdb76cdefdecaa8916095b52e Mon Sep 17 00:00:00 2001 From: Nhat Minh Le Date: Mon, 16 Oct 2023 16:08:03 +1000 Subject: [PATCH 06/27] Fix code smells in DroidCombatTask --- .../components/tasks/DroidCombatTask.java | 168 ++++++++++++------ .../components/tasks/DroidCombatTaskTest.java | 5 +- 2 files changed, 114 insertions(+), 59 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java index 1c597fc2e..a3c8d5822 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/DroidCombatTask.java @@ -48,7 +48,7 @@ public class DroidCombatTask extends DefaultTask implements PriorityTask { public enum STATE { IDLE, UP, DOWN, SHOOT_UP, SHOOT_DOWN, WALK, DIE } - public STATE towerState = STATE.WALK; + private STATE towerState = STATE.WALK; /** * @param priority Task priority when targets are detected (0 when nothing detected). Must be a positive integer. @@ -90,7 +90,7 @@ public void update() { } else { endTime = timeSource.getTime() + (INTERVAL * 1000); } - } + } } /** @@ -104,75 +104,31 @@ public void updateTowerState() { towerState = STATE.DIE; return; } + switch (towerState) { case WALK -> { - owner.getEntity().getEvents().trigger(WALK); - towerState = STATE.IDLE; + handleWalkState(); } case IDLE -> { - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(ATTACK_UP); - owner.getEntity().getEvents().trigger(SHOOT_UP); - towerState = STATE.DOWN; - } else { - owner.getEntity().getEvents().trigger(IDLE); - } + handleIdleState(); } case SHOOT_DOWN -> { - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(ATTACK_DOWN); - owner.getEntity().getEvents().trigger(SHOOT_DOWN); - towerState = STATE.UP; - } else { - owner.getEntity().getEvents().trigger(GO_UP); - towerState = STATE.UP; - } + handleShootDownState(); } case SHOOT_UP -> { - if (isTargetVisible()) { - - owner.getEntity().getEvents().trigger(ATTACK_UP); - owner.getEntity().getEvents().trigger(SHOOT_UP); - towerState = STATE.DOWN; - } else { - owner.getEntity().getEvents().trigger(IDLE); - towerState = STATE.IDLE; - } + handleShootUpState(); } case DOWN -> { - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(GO_DOWN); - towerState = STATE.SHOOT_DOWN; - } else { - owner.getEntity().getEvents().trigger(IDLE); - towerState = STATE.IDLE; - } + handleDownState(); } case UP -> { - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(GO_UP); - towerState = STATE.SHOOT_UP; - } else { - owner.getEntity().getEvents().trigger(GO_UP); - towerState = STATE.IDLE; - - - } + handleUpState(); } - case DIE -> { - if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { - owner.getEntity().setFlagForDelete(true); - } + default -> { // DIE + handleDieState(); } } } - /** - * For stopping the running task - */ - @Override - public void stop() { - super.stop(); - } /** * Returns the current state of the tower. @@ -214,4 +170,104 @@ public float getFireRateInterval() { return fireRateInterval; } -} + /** + * Function for setting the tower state + * @param newState New state of the tower + */ + public void setTowerState(STATE newState) { + this.towerState = newState; + } + + /** + * Function for getting the tower state + * + * @return The state of this tower + */ + public STATE getTowerState() { + return this.towerState; + } + + /** + * Function triggers walk and changes state from WALK to IDLE + */ + private void handleWalkState() { + owner.getEntity().getEvents().trigger(WALK); + towerState = STATE.IDLE; + } + + /** + * Function triggers actions at IDLE state + */ + private void handleIdleState() { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK_UP); + owner.getEntity().getEvents().trigger(SHOOT_UP); + towerState = STATE.DOWN; + } else { + owner.getEntity().getEvents().trigger(IDLE); + } + } + + /** + * Function triggers actions at SHOOT_DOWN state, then switch to UP + */ + private void handleShootDownState() { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK_DOWN); + owner.getEntity().getEvents().trigger(SHOOT_DOWN); + } else { + owner.getEntity().getEvents().trigger(GO_UP); + } + + towerState = STATE.UP; + } + + /** + * Function triggers actions at SHOOT_UP state, then switch to DOWN or IDLE + */ + private void handleShootUpState() { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK_UP); + owner.getEntity().getEvents().trigger(SHOOT_UP); + towerState = STATE.DOWN; + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } + } + + /** + * Function triggers actions at DOWN state, then switch to SHOOT_DOWN or IDLE + */ + private void handleDownState() { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(GO_DOWN); + towerState = STATE.SHOOT_DOWN; + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } + } + + /** + * Function triggers actions at UP state, then switch to SHOOT_UP or IDLE + */ + private void handleUpState() { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(GO_UP); + towerState = STATE.SHOOT_UP; + } else { + owner.getEntity().getEvents().trigger(GO_UP); + towerState = STATE.IDLE; + } + } + + /** + * Function handles DIE state + */ + private void handleDieState() { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } +} \ No newline at end of file diff --git a/source/core/src/test/com/csse3200/game/components/tasks/DroidCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/DroidCombatTaskTest.java index 5878ea061..6e888c1c3 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/DroidCombatTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/DroidCombatTaskTest.java @@ -64,7 +64,7 @@ public void testUpdateTowerStateWithTargetInRange() { entity.getEvents().addListener(DroidCombatTask.SHOOT_DOWN,shootDown); //Jump to IDLE state droidCombatTask.start(); - droidCombatTask.towerState = DroidCombatTask.STATE.IDLE; + droidCombatTask.setTowerState(DroidCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); @@ -110,7 +110,7 @@ public void testUpdateTowerStateWithTargetNotInRange() { entity.getEvents().addListener(DroidCombatTask.IDLE, idle); entity.getEvents().addListener(DroidCombatTask.ATTACK_UP,attackUp); //Jump to IDLE state - droidCombatTask.towerState = DroidCombatTask.STATE.IDLE; + droidCombatTask.setTowerState(DroidCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); @@ -122,7 +122,6 @@ public void testUpdateTowerStateWithTargetNotInRange() { verify(idle).handle(); verifyNoInteractions(attackUp); assertEquals(DroidCombatTask.STATE.IDLE, droidCombatTask.getState()); - } From 858fa9bf2ec4e8d73125b69b6eb1465565052416 Mon Sep 17 00:00:00 2001 From: max9753 Date: Mon, 16 Oct 2023 16:26:55 +1000 Subject: [PATCH 07/27] Fixed some code smells from random files that I saw. --- .../main/com/csse3200/game/areas/ForestGameArea.java | 6 +++--- .../game/components/gamearea/CurrencyDisplay.java | 4 ++-- .../components/gamearea/EngineerCountDisplay.java | 5 ++--- .../pausemenu/PauseMenuButtonComponent.java | 12 ++++++------ .../pausemenu/PauseMenuTimeStopComponent.java | 1 + 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java index db2890793..a46125349 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -206,9 +206,9 @@ public class ForestGameArea extends GameArea { "sounds/mobs/archerArrow.mp3" }; - private static final String backgroundMusic = "sounds/background/Sci-Fi1.ogg"; + private static final String BACKGROUND_MUSIC = "sounds/background/Sci-Fi1.ogg"; - private static final String[] forestMusic = {backgroundMusic}; + private static final String[] forestMusic = {BACKGROUND_MUSIC}; private Entity player; private Entity waves; @@ -893,7 +893,7 @@ private void spawnDroidTower() { } private void playMusic() { - Music music = ServiceLocator.getResourceService().getAsset(backgroundMusic, Music.class); + Music music = ServiceLocator.getResourceService().getAsset(BACKGROUND_MUSIC, Music.class); music.setLooping(true); music.setVolume(0.3f); music.play(); diff --git a/source/core/src/main/com/csse3200/game/components/gamearea/CurrencyDisplay.java b/source/core/src/main/com/csse3200/game/components/gamearea/CurrencyDisplay.java index c4388e0cf..bfd4c0b31 100644 --- a/source/core/src/main/com/csse3200/game/components/gamearea/CurrencyDisplay.java +++ b/source/core/src/main/com/csse3200/game/components/gamearea/CurrencyDisplay.java @@ -28,7 +28,7 @@ public class CurrencyDisplay extends UIComponent { private TextButton scrapsTb; private TextButton crystalsTb; private Sound clickSound; - private static final String defaultFont = "determination_mono_18"; + private static final String DEFAULT_FONT = "determination_mono_18"; /** * Adds actors to stage @@ -63,7 +63,7 @@ private void addActors() { private TextButton createButton(String imageFilePath, int value) { Drawable drawable = new TextureRegionDrawable(new TextureRegion(new Texture(imageFilePath))); TextButton.TextButtonStyle style = new TextButton.TextButtonStyle( - drawable, drawable, drawable, getSkin().getFont(defaultFont)); + drawable, drawable, drawable, getSkin().getFont(DEFAULT_FONT)); // create button TextButton tb = new TextButton(String.format("%d", value), style); diff --git a/source/core/src/main/com/csse3200/game/components/gamearea/EngineerCountDisplay.java b/source/core/src/main/com/csse3200/game/components/gamearea/EngineerCountDisplay.java index 3f569d147..80f419d47 100644 --- a/source/core/src/main/com/csse3200/game/components/gamearea/EngineerCountDisplay.java +++ b/source/core/src/main/com/csse3200/game/components/gamearea/EngineerCountDisplay.java @@ -7,7 +7,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.ui.TextTooltip; -import com.badlogic.gdx.scenes.scene2d.ui.TooltipManager; import com.badlogic.gdx.scenes.scene2d.utils.Drawable; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.utils.Align; @@ -16,7 +15,7 @@ public class EngineerCountDisplay extends UIComponent { private TextButton engineerTb; - private static final String defaultFont = "determination_mono_18"; + private static final String DEFAULT_FONT = "determination_mono_18"; @Override public void create() { @@ -37,7 +36,7 @@ private void addActors() { Drawable drawable = new TextureRegionDrawable(new TextureRegion( new Texture("images/engineers/engineerBanner.png"))); TextButton.TextButtonStyle style = new TextButton.TextButtonStyle( - drawable, drawable, drawable, getSkin().getFont(defaultFont)); + drawable, drawable, drawable, getSkin().getFont(DEFAULT_FONT)); String text = String.format("%d", ServiceLocator.getGameEndService().getEngineerCount()); engineerTb = new TextButton(text, style); diff --git a/source/core/src/main/com/csse3200/game/components/pausemenu/PauseMenuButtonComponent.java b/source/core/src/main/com/csse3200/game/components/pausemenu/PauseMenuButtonComponent.java index e0241e660..77591d440 100644 --- a/source/core/src/main/com/csse3200/game/components/pausemenu/PauseMenuButtonComponent.java +++ b/source/core/src/main/com/csse3200/game/components/pausemenu/PauseMenuButtonComponent.java @@ -21,8 +21,8 @@ public class PauseMenuButtonComponent extends UIComponent { private static final float Z_INDEX = 2f; private Window window; private final GdxGame game; - private static final float windowSizeX = 300; - private static final float windowSizeY = 400; + private static final float WINDOW_SIZE_X = 300; + private static final float WINDOW_SIZE_Y = 400; private final String[] sounds = { "sounds/ui/click/click_01.ogg", "sounds/ui/open_close/close_01.ogg", @@ -110,10 +110,10 @@ public void changed(ChangeEvent changeEvent, Actor actor) { window.add(planetSelectBtn).center(); window.row(); window.add(mainMenuBtn).center(); - window.setWidth(windowSizeX); - window.setHeight(windowSizeY); - window.setX((ServiceLocator.getRenderService().getStage().getWidth() / 2) - (windowSizeX / 2)); - window.setY((ServiceLocator.getRenderService().getStage().getHeight() / 2) - (windowSizeY / 2)); + window.setWidth(WINDOW_SIZE_X); + window.setHeight(WINDOW_SIZE_Y); + window.setX((ServiceLocator.getRenderService().getStage().getWidth() / 2) - (WINDOW_SIZE_X / 2)); + window.setY((ServiceLocator.getRenderService().getStage().getHeight() / 2) - (WINDOW_SIZE_Y / 2)); stage.addActor(window); } diff --git a/source/core/src/main/com/csse3200/game/components/pausemenu/PauseMenuTimeStopComponent.java b/source/core/src/main/com/csse3200/game/components/pausemenu/PauseMenuTimeStopComponent.java index e2c08c105..371c64690 100644 --- a/source/core/src/main/com/csse3200/game/components/pausemenu/PauseMenuTimeStopComponent.java +++ b/source/core/src/main/com/csse3200/game/components/pausemenu/PauseMenuTimeStopComponent.java @@ -13,6 +13,7 @@ public class PauseMenuTimeStopComponent extends Component { private Array freezeList; public PauseMenuTimeStopComponent() { + // Not implemented } /** From 47b95b08b6a9b466f855f16f7a1e0878190b775d Mon Sep 17 00:00:00 2001 From: Samantha Sullivan Date: Mon, 16 Oct 2023 16:29:05 +1000 Subject: [PATCH 08/27] added set check to wave factory tests --- .../game/entities/factories/WaveFactory.java | 2 +- .../entities/factories/WaveFactoryTest.java | 106 ++++++++---------- 2 files changed, 46 insertions(+), 62 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java index aa8730e0d..1f7f10121 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java @@ -30,7 +30,7 @@ public class WaveFactory { // TODO: include necromancer private static final ArrayList MELEE_MOBS = new ArrayList<>(Arrays.asList( - "Skeleton", "Coat", "DragonKnight" + "Skeleton", "Coat", "DragonKnight", "Necromancer" )); private static final ArrayList> lvl1Structure = new ArrayList<>(Arrays.asList( diff --git a/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java index 5fedf14d0..eca488c8f 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java @@ -1,7 +1,5 @@ package com.csse3200.game.entities.factories; -import com.csse3200.game.components.tasks.waves.LevelWaves; -import com.badlogic.gdx.assets.AssetManager; import com.csse3200.game.components.tasks.waves.LevelWaves; import com.csse3200.game.components.tasks.waves.WaveClass; import com.csse3200.game.extensions.GameExtension; @@ -14,21 +12,16 @@ import com.csse3200.game.services.ServiceLocator; import com.csse3200.game.services.WaveService; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +import com.csse3200.game.entities.Entity; +import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -import com.csse3200.game.entities.Entity; -import java.security.Provider; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; @ExtendWith(GameExtension.class) @ExtendWith(MockitoExtension.class) @@ -41,31 +34,11 @@ class WaveFactoryTest { private final int MIN_MELEE_HEALTH = 80; private final int MIN_RANGE_HEALTH = 60; private final int MIN_BOSS_HEALTH = 80; - - // level stats for level 1 - water planet - private final int LVL1_WAVES = 5; - private final int LVL1_CHOSEN_LVL = 1; private final int LVL1_LVL = 1; - private final ArrayList LVL1_MOBS = new ArrayList<>(Arrays.asList("Coat", "SplittingWaterSlime", "WaterQueen")); - - // level stats for level 2 - magic planet - private final int LVL2_WAVES = 10; - private final int LVL2_CHOSEN_LVL = 0; private final int LVL2_LVL = 2; - - private final ArrayList LVL2_MOBS = new ArrayList<>(Arrays.asList("ArcaneArcher", "SplittingNightBorne", "Skeleton", "DeflectWizard")); - - // level stats for level 3 - fire planet - private final int LVL3_WAVES = 15; - private final int LVL3_CHOSEN_LVL = 2; private final int LVL3_LVL = 3; - - private final ArrayList LVL3_MOBS = new ArrayList<>(Arrays.asList("Xeno", "DodgingDragon", "FireWorm")); - // private final String LVL3_BOSS = "FireBoss"; - //TODO: make this a fire boss in sprint 4 - private static final ArrayList MELEE_MOBS = new ArrayList<>(Arrays.asList( - "Skeleton", "Coat", "DragonKnight" + "Skeleton", "Coat", "DragonKnight", "Necromancer" )); private static final ArrayList> LVL1_WAVES_STRUC = new ArrayList<>(Arrays.asList( @@ -79,31 +52,31 @@ class WaveFactoryTest { private static final ArrayList> LVL2_WAVES_STRUC = new ArrayList<>(Arrays.asList( new ArrayList<>(Arrays.asList("Skeleton" )), new ArrayList<>(Arrays.asList("Skeleton", "ArcaneArcher" - )), new ArrayList<>(Arrays.asList("Skeleton", "DeflectWizard" - )), new ArrayList<>(Arrays.asList("Skeleton", "NightBorne" - )), new ArrayList<>(Arrays.asList("DeflectWizard", "NightBorne" - )), new ArrayList<>(Arrays.asList("NightBorne", "Skeleton" - )), new ArrayList<>(Arrays.asList("DeflectWizard", "NightBorne" - )), new ArrayList<>(Arrays.asList("ArcaneArcher", "NightBorne", "DeflectWizard" - )), new ArrayList<>(Arrays.asList("Skeleton", "ArcaneArcher", "DeflectWizard", "NightBorne" + )), new ArrayList<>(Arrays.asList("Skeleton", "Wizard" + )), new ArrayList<>(Arrays.asList("Skeleton", "SplittingNightBorne" + )), new ArrayList<>(Arrays.asList("Wizard", "SplittingNightBorne" + )), new ArrayList<>(Arrays.asList("SplittingNightBorne", "Skeleton" + )), new ArrayList<>(Arrays.asList("Wizard", "SplittingNightBorne" + )), new ArrayList<>(Arrays.asList("ArcaneArcher", "SplittingNightBorne", "Wizard" + )), new ArrayList<>(Arrays.asList("Skeleton", "ArcaneArcher", "Wizard", "SplittingNightBorne" )) )); private static final ArrayList> LVL3_WAVES_STRUC = new ArrayList<>(Arrays.asList( - new ArrayList<>(Arrays.asList("Coat" - )), new ArrayList<>(Arrays.asList("Coat", "DodgingDragon" - )), new ArrayList<>(Arrays.asList("Coat", "FireWorm" - )), new ArrayList<>(Arrays.asList("Coat", "Coat" - )), new ArrayList<>(Arrays.asList("Coat", "FireWorm" + new ArrayList<>(Arrays.asList("Necromancer" + )), new ArrayList<>(Arrays.asList("Necromancer", "DodgingDragon" + )), new ArrayList<>(Arrays.asList("Necromancer", "FireWorm" + )), new ArrayList<>(Arrays.asList("Necromancer", "DeflectFireWizard" + )), new ArrayList<>(Arrays.asList("DeflectFireWizard", "FireWorm" )), new ArrayList<>(Arrays.asList("DodgingDragon", "FireWorm" - )), new ArrayList<>(Arrays.asList("DodgingDragon", "Coat" - )), new ArrayList<>(Arrays.asList("FireWorm", "Coat" - )), new ArrayList<>(Arrays.asList("Coat", "Coat" - )), new ArrayList<>(Arrays.asList("DodgingDragon", "Coat", "Coat" - )), new ArrayList<>(Arrays.asList("FireWorm", "Coat", "DodgingDragon" - )), new ArrayList<>(Arrays.asList("FireWorm", "Coat", "Coat" - )), new ArrayList<>(Arrays.asList("Coat", "Coat", "FireWorm" - )), new ArrayList<>(Arrays.asList("Coat", "Coat", "Coat", "DodgingDragon", "FireWorm" + )), new ArrayList<>(Arrays.asList("DodgingDragon", "Necromancer" + )), new ArrayList<>(Arrays.asList("FireWorm", "Necromancer" + )), new ArrayList<>(Arrays.asList("DeflectFireWizard", "Necromancer" + )), new ArrayList<>(Arrays.asList("DodgingDragon", "DeflectFireWizard", "Necromancer" + )), new ArrayList<>(Arrays.asList("FireWorm", "Necromancer", "DodgingDragon" + )), new ArrayList<>(Arrays.asList("FireWorm", "SplittingRocky", "Necromancer" + )), new ArrayList<>(Arrays.asList("SplittingRocky", "DeflectFireWizard", "FireWorm" + )), new ArrayList<>(Arrays.asList("DeflectFireWizard", "SplittingRocky", "Necromancer", "DodgingDragon", "FireWorm" )) )); @@ -168,6 +141,7 @@ void testLevel1Creation() { for (WaveClass wave : lvl1Mobs) { int mobsRemaining = wave.getEntities().size() - 1; int totalMobsSpawned = 0; + // check the number of mobs in a wave if (waveNum == 5 || waveNum == 1) { assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); @@ -179,6 +153,10 @@ void testLevel1Creation() { if (waveNum != 5) { + Set mobNames = new HashSet<>(wave.getEntities().keySet()); + Set expectedMobNames = new HashSet<>(LVL1_WAVES_STRUC.get(waveNum - 1)); + assertTrue(mobNames.equals(expectedMobNames), "The mobs in the wave should be " + expectedMobNames + " ."); + for (String key: wave.getEntities().keySet()) { int[] values = wave.getEntities().get(key); assertTrue(values[0] > 1 && values[0] <= (mobCount - totalMobsSpawned - (2 * mobsRemaining))); @@ -227,6 +205,10 @@ void testLevel2Creation() { } if (waveNum != 10) { + Set mobNames = new HashSet<>(wave.getEntities().keySet()); + Set expectedMobNames = new HashSet<>(LVL2_WAVES_STRUC.get(waveNum - 1)); + assertTrue(mobNames.equals(expectedMobNames), "The mobs in the wave should be " + expectedMobNames + " ."); + for (String key: wave.getEntities().keySet()) { int[] values = wave.getEntities().get(key); @@ -270,20 +252,22 @@ void testLevel3Creation() { int mobsRemaining = wave.getEntities().size() - 1; int totalMobsSpawned = 0; - //TODO unhash when fire mobs integrated. - System.out.println("wave is: "+ wave); // check the number of mobs in a wave -// if (waveNum == 1 || waveNum == 15) { -// assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); -// } else if (1 < waveNum && waveNum < 10) { -// assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); -// } else if (9 < waveNum && waveNum < 14){ -// assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); -// } else if (waveNum == 14){ -// assertEquals(5, wave.getEntities().size(), "Wave should contain 4 mobs."); -// } + if (waveNum == 1 || waveNum == 15) { + assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); + } else if (1 < waveNum && waveNum < 10) { + assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); + } else if (9 < waveNum && waveNum < 14){ + assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); + } else if (waveNum == 14){ + assertEquals(5, wave.getEntities().size(), "Wave should contain 4 mobs."); + } if (waveNum != 15) { + Set mobNames = new HashSet<>(wave.getEntities().keySet()); + Set expectedMobNames = new HashSet<>(LVL3_WAVES_STRUC.get(waveNum - 1)); + assertTrue(mobNames.equals(expectedMobNames), "The mobs in the wave should be " + expectedMobNames + " ."); + for (String key: wave.getEntities().keySet()) { int[] values = wave.getEntities().get(key); From 6703cb472a3d105f1d80fbf9aad45805581e56c9 Mon Sep 17 00:00:00 2001 From: Nhat Minh Le Date: Mon, 16 Oct 2023 16:35:34 +1000 Subject: [PATCH 09/27] Fix 6 code smells in FireTowerCombatTask (1 high, 3 medium, 2 low) --- .../components/tasks/FireTowerCombatTask.java | 113 +++++++++++++----- .../tasks/FireTowerCombatTaskTest.java | 4 +- 2 files changed, 82 insertions(+), 35 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java index 13aa11a18..5bac6e0c6 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java @@ -47,7 +47,7 @@ public class FireTowerCombatTask extends DefaultTask implements PriorityTask { public enum STATE { IDLE, PREP_ATTACK, ATTACK, DEATH } - public STATE towerState = STATE.IDLE; + private STATE towerState = STATE.IDLE; /** * Starts the task running, triggers the initial 'IDLE' event @@ -99,43 +99,19 @@ public void updateTowerState() { towerState = STATE.DEATH; return; } + switch (towerState) { case IDLE -> { - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(PREP_ATTACK); - towerState = STATE.PREP_ATTACK; - } + handleIdleState(); } case PREP_ATTACK -> { - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(ATTACK); - towerState = STATE.ATTACK; - } else { - owner.getEntity().getEvents().trigger(IDLE); - towerState = STATE.IDLE; - } + handlePrepAttackState(); } case ATTACK -> { - if (shoot) { - if (!isTargetVisible()) { - owner.getEntity().getEvents().trigger(IDLE); - towerState = STATE.IDLE; - } else { - owner.getEntity().getEvents().trigger(ATTACK); - Entity newProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, - new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), ProjectileEffects.BURN, false); - newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), - (float) (owner.getEntity().getPosition().y)); - ServiceLocator.getEntityService().register(newProjectile); - } - } - shoot = !shoot; - + handleAttackState(); } - case DEATH -> { - if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { - owner.getEntity().setFlagForDelete(true); - } + default -> { // DEATH + handleDeathState(); } } } @@ -143,6 +119,7 @@ public void updateTowerState() { /** * stops the current animation. */ + @Override public void stop() { super.stop(); owner.getEntity().getEvents().trigger(IDLE); @@ -167,7 +144,7 @@ public int getPriority() { * not currently used. * @return the priority for this task */ - private int getActivePriority() { + public int getActivePriority() { return !isTargetVisible() ? 0 : priority; } @@ -175,7 +152,7 @@ private int getActivePriority() { * not currently used. * @return */ - private int getInactivePriority() { + public int getInactivePriority() { return isTargetVisible() ? priority : 0; } @@ -200,4 +177,74 @@ public float getFireRateInterval() { return fireRateInterval; } + /** + * Function for getting the tower's state. + * + * @return The tower's state + */ + public STATE getTowerState() { + return this.towerState; + } + + /** + * Function for setting the tower's state + * + * @param newState The new state of this tower + */ + public void setTowerState(STATE newState) { + this.towerState = newState; + } + + /** + * Function triggers actions at IDLE state, then switch to PREP_ATTACK + */ + private void handleIdleState() { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(PREP_ATTACK); + towerState = STATE.PREP_ATTACK; + } + } + + /** + * Functions triggers actions at PREP_ATTACH state, then switch to ATTACK or IDLE + */ + private void handlePrepAttackState() { + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(ATTACK); + towerState = STATE.ATTACK; + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } + } + + /** + * Functions trigger actions at ATTACK state + */ + private void handleAttackState() { + if (shoot) { + if (!isTargetVisible()) { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } else { + owner.getEntity().getEvents().trigger(ATTACK); + Entity newProjectile = ProjectileFactory.createEffectProjectile(PhysicsLayer.NPC, + new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), ProjectileEffects.BURN, false); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), + (owner.getEntity().getPosition().y)); + ServiceLocator.getEntityService().register(newProjectile); + } + } + + shoot = !shoot; + } + + /** + * Functions triggers actions at DEATH state + */ + private void handleDeathState() { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } } diff --git a/source/core/src/test/com/csse3200/game/components/tasks/FireTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/FireTowerCombatTaskTest.java index a2b92d857..ec0cbe0ef 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/FireTowerCombatTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/FireTowerCombatTaskTest.java @@ -66,7 +66,7 @@ public void testUpdateTowerStateWithTargetInRange() { entity.getEvents().addListener(FireTowerCombatTask.ATTACK, attack); //Jump to IDLE state fireTowerCombatTask.start(); - fireTowerCombatTask.towerState = FireTowerCombatTask.STATE.IDLE; + fireTowerCombatTask.setTowerState(FireTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); @@ -103,7 +103,7 @@ public void testUpdateTowerStateWithTargetNotInRange() { entity.getEvents().addListener(FireTowerCombatTask.IDLE, idle); entity.getEvents().addListener(FireTowerCombatTask.PREP_ATTACK, prepAttack); - fireTowerCombatTask.towerState = FireTowerCombatTask.STATE.IDLE; + fireTowerCombatTask.setTowerState(FireTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); From 86fadf78a4528e8f9087cf3a71a63463181c8726 Mon Sep 17 00:00:00 2001 From: Samantha Sullivan Date: Mon, 16 Oct 2023 16:39:18 +1000 Subject: [PATCH 10/27] fixing code smells in wave factory --- .../game/entities/factories/WaveFactory.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java index 1f7f10121..3dc6f8b33 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/WaveFactory.java @@ -27,8 +27,6 @@ public class WaveFactory { private static final Logger logger = LoggerFactory.getLogger(WaveFactory.class); private static Random rand = new Random(); - - // TODO: include necromancer private static final ArrayList MELEE_MOBS = new ArrayList<>(Arrays.asList( "Skeleton", "Coat", "DragonKnight", "Necromancer" )); @@ -72,15 +70,6 @@ public class WaveFactory { )) )); - // The base health for the different mobs - private static int MELEE_BASE_HEALTH = 80; - private static int RANGE_BASE_HEALTH = 60; - - // Base health of the bosses - private static int LVL1_BOSS_BASE_HEALTH = 500; - private static int LVL2_BOSS_BASE_HEALTH = 1000; - private static int LVL3_BOSS_BASE_HEALTH = 2000; - private static final String BOSS_1 = "IceBoss"; private static final String BOSS_2 = "PatrickBoss"; private static final String BOSS_3 = "FireBoss"; @@ -140,6 +129,11 @@ public static LevelWaves createLevel(int chosenLevel) { String boss = ""; int bossHealth; int minMobs; + // Base health of the bosses + int LVL1_BOSS_BASE_HEALTH = 500; + int LVL2_BOSS_BASE_HEALTH = 1000; + int LVL3_BOSS_BASE_HEALTH = 2000; + switch (chosenLevel) { case 2: boss = BOSS_2; @@ -185,8 +179,11 @@ public static LevelWaves createLevel(int chosenLevel) { } // Calculate the health + int RANGE_BASE_HEALTH = 60; int health = RANGE_BASE_HEALTH; if (MELEE_MOBS.contains(mob)) { + // The base health for the different mobs + int MELEE_BASE_HEALTH = 80; health = MELEE_BASE_HEALTH; } int[] mobStats = {num, health + (atWave * chosenLevel)}; From ef9a8388273749d227bc1ba8db33297fb16483fb Mon Sep 17 00:00:00 2001 From: Nhat Minh Le Date: Mon, 16 Oct 2023 16:46:23 +1000 Subject: [PATCH 11/27] Fix 4 code smells in FireworksTowerCombatTask (2 medium, 2 low) --- .../tasks/FireworksTowerCombatTask.java | 23 +++++++++++++++++-- .../tasks/FireworksTowerCombatTaskTest.java | 4 ++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java index 8d10036ae..b40cfa0db 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireworksTowerCombatTask.java @@ -45,7 +45,7 @@ public class FireworksTowerCombatTask extends DefaultTask implements PriorityTas public enum STATE { IDLE, ATTACK, DEATH } - public STATE towerState = STATE.IDLE; + private STATE towerState = STATE.IDLE; /** * @param priority Task priority when targets are detected (0 when nothing is present) @@ -77,6 +77,7 @@ public void start() { * updates the current state of the tower based on the current state of the game. If enemies are detected, attack * state is activated and otherwise idle state remains. */ + @Override public void update() { if (timeSource.getTime() >= endTime) { updateTowerState(); @@ -111,7 +112,7 @@ public void updateTowerState() { Entity newProjectile = ProjectileFactory.createSplitFireWorksFireball(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), 3); newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), - (float) (owner.getEntity().getPosition().y)); + (owner.getEntity().getPosition().y)); ServiceLocator.getEntityService().register(newProjectile); } else { owner.getEntity().getEvents().trigger(IDLE); @@ -139,6 +140,7 @@ public STATE getState() { /** * stops the current animation and switches back the state of the tower to IDLE. */ + @Override public void stop() { super.stop(); owner.getEntity().getEvents().trigger(IDLE); @@ -159,4 +161,21 @@ public int getPriority() { public boolean isTargetVisible() { return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } + + /** + * Function for getting the tower's state + * + * @return The state of this tower + */ + public STATE getTowerState() { + return this.towerState; + } + + /** + * Function for setting the tower's state + * @param newState The new state of this tower + */ + public void setTowerState(STATE newState) { + this.towerState = newState; + } } diff --git a/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java index 9310e8229..e7addd508 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/FireworksTowerCombatTaskTest.java @@ -64,7 +64,7 @@ public void testUpdateTowerStateWithTargetInRange() { entity.getEvents().addListener(FireworksTowerCombatTask.ATTACK, attack); //Jump to IDLE state fireworksTowerCombatTask.start(); - fireworksTowerCombatTask.towerState = FireworksTowerCombatTask.STATE.IDLE; + fireworksTowerCombatTask.setTowerState(FireworksTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); @@ -92,7 +92,7 @@ public void testUpdateTowerStateWithTargetNotInRange() { entity.getEvents().addListener(FireworksTowerCombatTask.IDLE, idle); entity.getEvents().addListener(FireworksTowerCombatTask.ATTACK, attack); - fireworksTowerCombatTask.towerState = FireworksTowerCombatTask.STATE.IDLE; + fireworksTowerCombatTask.setTowerState(FireworksTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); From 5b4e7919853fa791dddb771396c08916110bdaa9 Mon Sep 17 00:00:00 2001 From: max9753 Date: Mon, 16 Oct 2023 16:46:44 +1000 Subject: [PATCH 12/27] Cleaned up MobAttackTask.java --- .../game/components/tasks/MobAttackTask.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/MobAttackTask.java b/source/core/src/main/com/csse3200/game/components/tasks/MobAttackTask.java index c5fc7f0bf..39215cb9e 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/MobAttackTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/MobAttackTask.java @@ -36,7 +36,7 @@ public class MobAttackTask extends DefaultTask implements PriorityTask { private final Vector2 maxRangePosition = new Vector2(); private final PhysicsEngine physics; private GameTime timeSource; - private long endTime; + private final RaycastHit hit = new RaycastHit(); private static final long DELAY = 1000; // delay between shots @@ -50,7 +50,6 @@ private enum STATE { /** * @param priority Task priority when targets are detected (0 when nothing detected). Must be a positive integer. - * @param maxRange Maximum effective range of the weapon mob. This determines the detection distance of targets */ public MobAttackTask(int priority) { this.priority = priority; @@ -69,9 +68,7 @@ public void start() { startTime = timeSource.getTime(); Vector2 mobPosition = owner.getEntity().getCenterPosition(); this.maxRangePosition.set(0, mobPosition.y); - //owner.getEntity().getEvents().trigger(IDLE); - endTime = timeSource.getTime() + (INTERVAL * 500); -// owner.getEntity().getEvents().trigger("shootStart"); + long endTime = timeSource.getTime() + (INTERVAL * 500); } /** @@ -130,7 +127,6 @@ public void updateMobState() { newProjectile.setScale(-1f, 1f); ServiceLocator.getEntityService().register(newProjectile); -// System.out.printf("ANIMATION: " + owner.getEntity().getComponent(AnimationRenderComponent.class).getCurrentAnimation() + "\n"); this.owner.getEntity().getEvents().trigger(FIRING); mobState = STATE.STOW; } @@ -193,11 +189,10 @@ private boolean isTargetVisible() { * the function will return null. If it returns null when the mob is in state FIRING or DEPLOY, it will not fire * and will STOW. * - * returns the Weapon (Melee or Projectile) the mob will use to attack the target. null if immune target or no target + * @return the Weapon (Melee or Projectile) the mob will use to attack the target. null if immune target or no target * */ private Weapon meleeOrProjectile() { -// Vector2 newVector = new Vector2(owner.getEntity().getPosition().x - 10f, owner.getEntity().getPosition().y - 2f); -// Fixture hitraycast = physics.raycastGetHit(owner.getEntity().getPosition(), newVector, TARGET); + setTarget(); TouchAttackComponent comp = owner.getEntity().getComponent(TouchAttackComponent.class); Weapon chosenWeapon = null; From 93be4f9f81f606ee340b0c5f48209f5f30cf0cab Mon Sep 17 00:00:00 2001 From: max9753 Date: Mon, 16 Oct 2023 16:49:19 +1000 Subject: [PATCH 13/27] Cleaned up MobDeathTask.java although this task I dont think is used currently. --- .../main/com/csse3200/game/components/tasks/MobDeathTask.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/MobDeathTask.java b/source/core/src/main/com/csse3200/game/components/tasks/MobDeathTask.java index 478623bcf..5782ca8f9 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/MobDeathTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/MobDeathTask.java @@ -7,10 +7,8 @@ import com.csse3200.game.entities.Entity; import com.csse3200.game.entities.factories.DropFactory; import com.csse3200.game.physics.PhysicsEngine; -import com.csse3200.game.physics.raycast.RaycastHit; import com.csse3200.game.services.ServiceLocator; import com.csse3200.game.services.GameTime; -//import com.csse3200.game.rendering.DebugRenderer; /** @@ -60,7 +58,6 @@ public void update() { public void updateBossState() { mobHealth = owner.getEntity().getComponent(CombatStatsComponent.class).getHealth(); - // TODO: inset a bit that picks from a list of drop options and drops this if (mobIsDead(mobHealth)) { killMob(); From b61f6581f43f0b434a98afb4e43e5aaa5d0ea74a Mon Sep 17 00:00:00 2001 From: Nhat Minh Le Date: Mon, 16 Oct 2023 16:55:22 +1000 Subject: [PATCH 14/27] Fix 4 code smells in PierceTowerCombatTask (2 medium, 2 low) --- .../tasks/PierceTowerCombatTask.java | 25 ++++++++++++++++--- .../tasks/PierceTowerCombatTaskTest.java | 4 +-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java index cdefde434..7b5dd7069 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java @@ -43,7 +43,7 @@ public class PierceTowerCombatTask extends DefaultTask implements PriorityTask { public enum STATE { IDLE, ATTACK, DEATH } - public STATE towerState = STATE.IDLE; + private STATE towerState = STATE.IDLE; /** * @param priority Task priority when targets are detected (0 when nothing is present) @@ -75,6 +75,7 @@ public void start() { * updates the current state of the tower based on the current state of the game. If enemies are detected, attack * state is activated and otherwise idle state remains. */ + @Override public void update() { if (timeSource.getTime() >= endTime) { updateTowerState(); @@ -113,14 +114,14 @@ public void updateTowerState() { Entity newProjectile = ProjectileFactory.createPierceFireBall(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), - (float) (owner.getEntity().getPosition().y)); + (owner.getEntity().getPosition().y)); ServiceLocator.getEntityService().register(newProjectile); } } shoot = !shoot; } - case DEATH -> { + default -> { // DEATH if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { owner.getEntity().setFlagForDelete(true); } @@ -139,6 +140,7 @@ public STATE getState() { /** * stops the current animation and switches back the state of the tower to IDLE. */ + @Override public void stop() { super.stop(); owner.getEntity().getEvents().trigger(IDLE); @@ -159,4 +161,21 @@ public int getPriority() { public boolean isTargetVisible() { return physics.raycast(towerPosition, maxRangePosition, TARGET, hit); } + + /** + * Function for getting the tower's state + * + * @return The state of this tower + */ + public STATE getTowerState() { + return this.towerState; + } + + /** + * Function for setting the tower's state + * @param newState The new state of this tower + */ + public void setTowerState(STATE newState) { + this.towerState = newState; + } } diff --git a/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java index a1a6ff11c..f8d060992 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/PierceTowerCombatTaskTest.java @@ -64,7 +64,7 @@ public void testUpdateTowerStateWithTargetInRange() { entity.getEvents().addListener(PierceTowerCombatTask.ATTACK, attack); //Jump to IDLE state pierceTowerCombatTask.start(); - pierceTowerCombatTask.towerState = PierceTowerCombatTask.STATE.IDLE; + pierceTowerCombatTask.setTowerState(PierceTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); @@ -93,7 +93,7 @@ public void testUpdateTowerStateWithTargetNotInRange() { entity.getEvents().addListener(PierceTowerCombatTask.IDLE, idle); entity.getEvents().addListener(PierceTowerCombatTask.ATTACK, attack); - pierceTowerCombatTask.towerState = PierceTowerCombatTask.STATE.IDLE; + pierceTowerCombatTask.setTowerState(PierceTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); From caa458a0d9d6ca7fa44a760135b7294d955168b8 Mon Sep 17 00:00:00 2001 From: max9753 Date: Mon, 16 Oct 2023 16:55:59 +1000 Subject: [PATCH 15/27] Cleaned up some code smells in MobTask.java, which I now realise might fall under another team & hopefully this is not still being worked on. --- .../tasks/{MobTask => mobtask}/MobTask.java | 62 ------------------- .../tasks/{MobTask => mobtask}/MobType.java | 0 2 files changed, 62 deletions(-) rename source/core/src/main/com/csse3200/game/components/tasks/{MobTask => mobtask}/MobTask.java (71%) rename source/core/src/main/com/csse3200/game/components/tasks/{MobTask => mobtask}/MobType.java (100%) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobTask.java b/source/core/src/main/com/csse3200/game/components/tasks/mobtask/MobTask.java similarity index 71% rename from source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobTask.java rename to source/core/src/main/com/csse3200/game/components/tasks/mobtask/MobTask.java index 5ad20684e..f0721757d 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/mobtask/MobTask.java @@ -163,7 +163,6 @@ public void update() { runFlag = true; } } - case DEATH, DEFAULT -> {} } } @@ -180,67 +179,6 @@ private void animate() { } case DEFAULT -> owner.getEntity().getEvents().trigger("mob_default"); } - // switch (mobType) { - // case SKELETON -> { - // switch (state) { - // case RUN -> owner.getEntity().getEvents().trigger("skeleton_walk"); - // case ATTACK -> owner.getEntity().getEvents().trigger("skeleton_attack"); - // case DEATH -> owner.getEntity().getEvents().trigger("skeleton_death"); - // case DEFAULT -> owner.getEntity().getEvents().trigger("skeleton_default"); - // } - // } - // case WIZARD -> { - // switch (state) { - // case RUN -> owner.getEntity().getEvents().trigger("wizard_run"); - // case ATTACK -> owner.getEntity().getEvents().trigger("wizard_attack"); - // case DEATH -> owner.getEntity().getEvents().trigger("wizard_death"); - // case DEFAULT -> owner.getEntity().getEvents().trigger("default"); - // } - // } - // case WATER_QUEEN -> { - // switch (state) { - // case RUN -> owner.getEntity().getEvents().trigger("water_queen_walk"); - // case ATTACK -> owner.getEntity().getEvents().trigger("water_queen_attack"); - // case DEATH -> owner.getEntity().getEvents().trigger("water_queen_death"); - // case DEFAULT -> owner.getEntity().getEvents().trigger("default"); - // } - // } - // case WATER_SLIME -> { - // switch (state) { - // case RUN -> owner.getEntity().getEvents().trigger("water_slime_walk"); - // case ATTACK -> owner.getEntity().getEvents().trigger("water_slime_attack"); - // case DEATH -> { - // owner.getEntity().getEvents().trigger("water_slime_death"); - // owner.getEntity().getEvents().trigger("splitDeath"); - // } - // case DEFAULT -> owner.getEntity().getEvents().trigger("default"); - // } - // } - // case FIRE_WORM -> { - // switch (state) { - // case RUN -> owner.getEntity().getEvents().trigger("fire_worm_walk"); - // case ATTACK -> owner.getEntity().getEvents().trigger("fire_worm_attack"); - // case DEATH -> owner.getEntity().getEvents().trigger("fire_worm_death"); - // case DEFAULT -> owner.getEntity().getEvents().trigger("default"); - // } - // } - // case DRAGON_KNIGHT -> { - // switch (state) { - // case RUN -> owner.getEntity().getEvents().trigger("dragon_knight_run"); - // case ATTACK -> owner.getEntity().getEvents().trigger("dragon_knight_attack"); - // case DEATH -> owner.getEntity().getEvents().trigger("dragon_knight_death"); - // case DEFAULT -> owner.getEntity().getEvents().trigger("default"); - // } - // } - // case COAT -> { - // switch (state) { - // case RUN -> owner.getEntity().getEvents().trigger("coat_run"); - // case ATTACK -> owner.getEntity().getEvents().trigger("coat_attack"); - // case DEATH -> owner.getEntity().getEvents().trigger("coat_death"); - // case DEFAULT -> owner.getEntity().getEvents().trigger("default"); - // } - // } - // } } /** diff --git a/source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobType.java b/source/core/src/main/com/csse3200/game/components/tasks/mobtask/MobType.java similarity index 100% rename from source/core/src/main/com/csse3200/game/components/tasks/MobTask/MobType.java rename to source/core/src/main/com/csse3200/game/components/tasks/mobtask/MobType.java From ecfab7b5588fdf6e6cc09c04b4db4e2412f17d49 Mon Sep 17 00:00:00 2001 From: Nhat Minh Le Date: Mon, 16 Oct 2023 17:02:12 +1000 Subject: [PATCH 16/27] Fix 4 code smells in RicochetTowerCombatTask (2 medium, 2 low) --- .../tasks/RicochetTowerCombatTask.java | 22 +++++++++++++++---- .../tasks/RicochetTowerCombatTaskTest.java | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java index 7fc3e5728..0fdcdbd26 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java @@ -45,7 +45,7 @@ public class RicochetTowerCombatTask extends DefaultTask implements PriorityTask public enum STATE { IDLE, ATTACK, DEATH } - public STATE towerState = STATE.IDLE; + private STATE towerState = STATE.IDLE; /** * @param priority Task priority when targets are detected (0 when nothing is present) @@ -77,6 +77,7 @@ public void start() { * updates the current state of the tower based on the current state of the game. If enemies are detected, attack * state is activated and otherwise idle state remains. */ + @Override public void update() { if (timeSource.getTime() >= endTime) { updateTowerState(); @@ -89,7 +90,6 @@ public void update() { * of the game. If enemies are detected, state of the tower is changed to attack state. */ public void updateTowerState() { - if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DEATH) { owner.getEntity().getEvents().trigger(DEATH); towerState = STATE.DEATH; @@ -114,13 +114,13 @@ public void updateTowerState() { // NEED TO DO USER TESTING TO FIGURE OUT THE BOUNCE COUNT new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), 3); newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), - (float) (owner.getEntity().getPosition().y)); + (owner.getEntity().getPosition().y)); ServiceLocator.getEntityService().register(newProjectile); } } shoot = !shoot; } - case DEATH -> { + default -> { // DEATH if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { owner.getEntity().setFlagForDelete(true); } @@ -128,13 +128,27 @@ public void updateTowerState() { } } + /** + * Function for getting the tower's state + * + * @return The state of this tower + */ public STATE getState() { return this.towerState; } + /** + * Function for setting the tower's state + * @param newState The new state of this tower + */ + public void setState(STATE newState) { + this.towerState = newState; + } + /** * stops the current animation and switches back the state of the tower to IDLE. */ + @Override public void stop() { super.stop(); owner.getEntity().getEvents().trigger(IDLE); diff --git a/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java index 7f78761c8..e64d534cf 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/RicochetTowerCombatTaskTest.java @@ -64,7 +64,7 @@ public void testUpdateTowerStateWithTargetInRange() { entity.getEvents().addListener(RicochetTowerCombatTask.ATTACK, attack); //Jump to IDLE state ricochetTowerCombatTask.start(); - ricochetTowerCombatTask.towerState = RicochetTowerCombatTask.STATE.IDLE; + ricochetTowerCombatTask.setState(RicochetTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); @@ -92,7 +92,7 @@ public void testUpdateTowerStateWithTargetNotInRange() { entity.getEvents().addListener(RicochetTowerCombatTask.IDLE, idle); entity.getEvents().addListener(RicochetTowerCombatTask.ATTACK, attack); - ricochetTowerCombatTask.towerState = RicochetTowerCombatTask.STATE.IDLE; + ricochetTowerCombatTask.setState(RicochetTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); From da0102b2e4a4735a1e5fa764506b3f9d979214fe Mon Sep 17 00:00:00 2001 From: Nhat Minh Le Date: Mon, 16 Oct 2023 17:09:54 +1000 Subject: [PATCH 17/27] Fix 4 code smells in StunTowerCombatTask (2 medium, 2 low) --- .../components/tasks/StunTowerCombatTask.java | 23 +++++++++++++++---- .../tasks/StunTowerCombatTaskTest.java | 4 ++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java index ec469b269..000240e6a 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java @@ -46,7 +46,7 @@ public class StunTowerCombatTask extends DefaultTask implements PriorityTask { public enum STATE { IDLE, ATTACK, DIE } - public STATE towerState = STATE.IDLE; + private STATE towerState = STATE.IDLE; /** * @param priority Task priority when targets are detected (0 when nothing is present) @@ -80,6 +80,7 @@ public void start() { * updates the current state of the tower based on the current state of the game. If enemies are detected, attack * state is activated and otherwise idle state remains. */ + @Override public void update() { if (timeSource.getTime() >= endTime) { updateTowerState(); @@ -96,7 +97,6 @@ public void update() { * of the game. If enemies are detected, state of the tower is changed to attack state. */ public void updateTowerState() { - if (owner.getEntity().getComponent(CombatStatsComponent.class).getHealth() <= 0 && towerState != STATE.DIE) { owner.getEntity().getEvents().trigger(DEATH); @@ -124,15 +124,16 @@ public void updateTowerState() { new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f), ProjectileEffects.STUN, false); newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.25), - (float) (owner.getEntity().getPosition().y)); + (owner.getEntity().getPosition().y)); ServiceLocator.getEntityService().register(newProjectile); owner.getEntity().getEvents().trigger(IDLE); towerState = STATE.IDLE; } } + shoot = !shoot; } - case DIE -> { + default -> { // DIE if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { owner.getEntity().setFlagForDelete(true); } @@ -140,13 +141,27 @@ public void updateTowerState() { } } + /** + * Function for getting the tower's state + * + * @return The state of this tower + */ public STATE getState() { return this.towerState; } + /** + * Function for setting the tower's state + * @param newState The new state of this tower + */ + public void setState(STATE newState) { + this.towerState = newState; + } + /** * stops the current animation and switches back the state of the tower to IDLE. */ + @Override public void stop() { super.stop(); owner.getEntity().getEvents().trigger(IDLE); diff --git a/source/core/src/test/com/csse3200/game/components/tasks/StunTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/StunTowerCombatTaskTest.java index dcd668fc0..30f490340 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/StunTowerCombatTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/StunTowerCombatTaskTest.java @@ -64,7 +64,7 @@ public void testUpdateTowerStateWithTargetInRange() { entity.getEvents().addListener(StunTowerCombatTask.ATTACK, attack); //Jump to IDLE state stunTowerCombatTask.start(); - stunTowerCombatTask.towerState = StunTowerCombatTask.STATE.IDLE; + stunTowerCombatTask.setState(StunTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); @@ -93,7 +93,7 @@ public void testUpdateTowerStateWithTargetNotInRange() { entity.getEvents().addListener(StunTowerCombatTask.IDLE, idle); entity.getEvents().addListener(StunTowerCombatTask.ATTACK, attack); - stunTowerCombatTask.towerState = StunTowerCombatTask.STATE.IDLE; + stunTowerCombatTask.setState(StunTowerCombatTask.STATE.IDLE); ServiceLocator.getPhysicsService().getPhysics().update(); entity.update(); From a97f0bb4d55d617486e3e53e6d926f6ff7105287 Mon Sep 17 00:00:00 2001 From: max9753 Date: Mon, 16 Oct 2023 17:10:07 +1000 Subject: [PATCH 18/27] Cleaned up code smells in SpawnWaveTask.java and WaveClass.Java --- .../com/csse3200/game/components/tasks/SpawnWaveTask.java | 2 +- .../csse3200/game/components/tasks/waves/WaveClass.java | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/SpawnWaveTask.java b/source/core/src/main/com/csse3200/game/components/tasks/SpawnWaveTask.java index d6c4a3d85..912a2451b 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/SpawnWaveTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/SpawnWaveTask.java @@ -8,7 +8,7 @@ import org.slf4j.LoggerFactory; public class SpawnWaveTask extends DefaultTask implements PriorityTask { - private static final Logger logger = LoggerFactory.getLogger(SpawnWaveTask.class); + private final GameTime globalTime; private long endTime = 0; private final int SPAWNING_INTERVAL = 10; diff --git a/source/core/src/main/com/csse3200/game/components/tasks/waves/WaveClass.java b/source/core/src/main/com/csse3200/game/components/tasks/waves/WaveClass.java index 41f031858..3e197601e 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/waves/WaveClass.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/waves/WaveClass.java @@ -1,18 +1,14 @@ package com.csse3200.game.components.tasks.waves; -import com.badlogic.gdx.math.GridPoint2; -import com.csse3200.game.entities.Entity; + import com.csse3200.game.services.GameTime; -import com.csse3200.game.services.ServiceLocator; + import java.util.*; public class WaveClass { private HashMap entities; - private GameTime gameTime; - private long startTime; private List wave; - private Random rand = new Random(); private int mobIndex; /** From 196dbb1915e7dbdf63db125515efe9a203cf8520 Mon Sep 17 00:00:00 2001 From: Nhat Minh Le Date: Mon, 16 Oct 2023 17:21:29 +1000 Subject: [PATCH 19/27] Fix 2 code smells in TNTTowerCombatTask (2 low) --- .../components/tasks/TNTTowerCombatTask.java | 20 ++----------------- .../tasks/TNTTowerCombatTaskTest.java | 10 ---------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java index bcb210846..5c449019e 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/TNTTowerCombatTask.java @@ -25,7 +25,6 @@ public class TNTTowerCombatTask extends DefaultTask implements PriorityTask { public static final String DEFAULT = "defaultStart"; public static final String DAMAGE = "TNTDamageStart"; - // class attributes private final int priority; // The active priority this task will have private final float maxRange; @@ -35,7 +34,7 @@ public class TNTTowerCombatTask extends DefaultTask implements PriorityTask { private final GameTime timeSource; private long endTime; private final RaycastHit hit = new RaycastHit(); - public boolean readToDelete = false; + private boolean readToDelete = false; public enum STATE { IDLE, EXPLODE, REMOVE @@ -51,7 +50,6 @@ public TNTTowerCombatTask(int priority, float maxRange) { this.maxRange = maxRange; physics = ServiceLocator.getPhysicsService().getPhysics(); timeSource = ServiceLocator.getTimeSource(); - } /** @@ -87,7 +85,6 @@ public void update() { */ public void updateTowerState() { // configure tower state depending on target visibility - switch (towerState) { case IDLE -> { // targets detected in idle mode - start deployment @@ -103,19 +100,10 @@ public void updateTowerState() { owner.getEntity().getEvents().trigger(DAMAGE); towerState = STATE.REMOVE; } - case REMOVE -> { + default -> { // REMOVE readToDelete = true; } } - - - } - /** - * For stopping the running task - */ - @Override - public void stop() { - super.stop(); } /** @@ -124,7 +112,6 @@ public void stop() { */ @Override public int getPriority() { - if (isReadyToDelete()) { owner.getEntity().setFlagForDelete(true); return -1; @@ -152,11 +139,8 @@ public boolean isTargetVisible() { } public boolean isReadyToDelete() { - return readToDelete; } - - } diff --git a/source/core/src/test/com/csse3200/game/components/tasks/TNTTowerCombatTaskTest.java b/source/core/src/test/com/csse3200/game/components/tasks/TNTTowerCombatTaskTest.java index 65a0e4724..74ff509f7 100644 --- a/source/core/src/test/com/csse3200/game/components/tasks/TNTTowerCombatTaskTest.java +++ b/source/core/src/test/com/csse3200/game/components/tasks/TNTTowerCombatTaskTest.java @@ -20,8 +20,6 @@ @ExtendWith(MockitoExtension.class) public class TNTTowerCombatTaskTest { - - TNTTowerCombatTask tntTowerCombatTask; @BeforeEach @@ -37,7 +35,6 @@ void setUp() { @Test public void testStartTriggersDefaultEvent() { - Entity entity = createTNT(); EventListener0 defaultStartListener = mock(EventListener0.class); @@ -50,7 +47,6 @@ public void testStartTriggersDefaultEvent() { @Test public void testUpdateTowerStateWithTargetInRange() { - Entity entity = createTNT(); entity.setPosition(10,10); @@ -90,12 +86,10 @@ public void testUpdateTowerStateWithTargetInRange() { tntTowerCombatTask.updateTowerState(); // Set flag to dispose assertTrue(tntTowerCombatTask.isReadyToDelete()); - } @Test public void testStayAtIdleWhenNoTargetInRange() { - Entity entity = createTNT(); entity.setPosition(10,10); @@ -117,7 +111,6 @@ public void testStayAtIdleWhenNoTargetInRange() { verifyNoInteractions(defaultStartListener); // still in idle assertEquals(TNTTowerCombatTask.STATE.IDLE, tntTowerCombatTask.getState()); - } Entity createTNT() { @@ -128,7 +121,6 @@ Entity createTNT() { .addComponent(new ColliderComponent()); entity.create(); return entity; - } Entity createNPC() { @@ -139,6 +131,4 @@ Entity createNPC() { Target.create(); return Target; } - - } From d333a3db85fc0bd3b2abccda45c803ad219ac4c2 Mon Sep 17 00:00:00 2001 From: max9753 Date: Mon, 16 Oct 2023 17:23:00 +1000 Subject: [PATCH 20/27] Fixed an error with MobTask directory. --- .../entities/factories/WaveFactoryTest.java | 530 ++++++++++-------- 1 file changed, 298 insertions(+), 232 deletions(-) diff --git a/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java b/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java index 6abe8ec1a..eca488c8f 100644 --- a/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java +++ b/source/core/src/test/com/csse3200/game/entities/factories/WaveFactoryTest.java @@ -1,232 +1,298 @@ -//package com.csse3200.game.entities.factories; -// -//import com.csse3200.game.components.tasks.waves.LevelWaves; -//import com.badlogic.gdx.assets.AssetManager; -//import com.csse3200.game.components.tasks.waves.LevelWaves; -//import com.csse3200.game.components.tasks.waves.WaveClass; -//import com.csse3200.game.extensions.GameExtension; -//import com.csse3200.game.physics.PhysicsService; -//import com.csse3200.game.rendering.DebugRenderer; -//import com.csse3200.game.rendering.RenderService; -//import com.csse3200.game.screens.GameLevelData; -//import com.csse3200.game.services.GameTime; -//import com.csse3200.game.services.ResourceService; -//import com.csse3200.game.services.ServiceLocator; -//import com.csse3200.game.services.WaveService; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.Disabled; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.junit.jupiter.MockitoExtension; -// -//import static org.junit.jupiter.api.Assertions.*; -//import static org.mockito.Mockito.*; -// -//import com.csse3200.game.entities.Entity; -// -//import java.security.Provider; -//import java.util.ArrayList; -//import java.util.Arrays; -//import java.util.List; -//import java.util.Map; -// -//@ExtendWith(GameExtension.class) -//@ExtendWith(MockitoExtension.class) -//class WaveFactoryTest { -// -// private LevelWaves lvl1; -// private LevelWaves lvl2; -// private LevelWaves lvl3; -// -// private final int MIN_HEALTH = 60; -// private final int MIN_BOSS_HEALTH = 80; -// -// // level stats for level 1 - water planet -// private final int LVL1_DIFF = 2; -// private final int LVL1_WAVES = 5; -// private final int LVL1_CHOSEN_LVL = 1; -// private final ArrayList LVL1_MOBS = new ArrayList<>(Arrays.asList("Coat", "SplittingWaterSlime", "WaterQueen")); -// private final String LVL1_BOSS = "IceBoss"; -// -// // level stats for level 2 - magic planet -// private final int LVL2_DIFF = 3; -// private final int LVL2_WAVES = 10; -// private final int LVL2_CHOSEN_LVL = 0; -// private final ArrayList LVL2_MOBS = new ArrayList<>(Arrays.asList("ArcaneArcher", "SplittingNightBorne", "Skeleton", "DeflectWizard")); -// private final String LVL2_BOSS = "PatrickBoss"; -// -// // level stats for level 3 - fire planet -// private final int LVL3_DIFF = 5; -// private final int LVL3_WAVES = 15; -// private final int LVL3_CHOSEN_LVL = 2; -// private final ArrayList LVL3_MOBS = new ArrayList<>(Arrays.asList("Xeno", "DodgingDragon", "FireWorm")); -// private final String LVL3_BOSS = "FireBoss"; -//// private final String LVL3_BOSS = "FireBoss"; -// //TODO: make this a fire boss in sprint 4 -// -// private static final String[] waveSounds = { -// "sounds/waves/wave-start/Wave_Start_Alarm.ogg", -// "sounds/waves/wave-end/Wave_Over_01.ogg" -// }; -// -// @BeforeEach -// void setUp() { -// GameTime gameTime = mock(GameTime.class); -// ServiceLocator.registerTimeSource(gameTime); -// ServiceLocator.registerPhysicsService(new PhysicsService()); -// RenderService render = new RenderService(); -// render.setDebug(mock(DebugRenderer.class)); -// ServiceLocator.registerRenderService(render); -// ResourceService resourceService = mock(ResourceService.class); -// ServiceLocator.registerResourceService(resourceService); -// WaveService waveService = new WaveService(); -// ServiceLocator.registerWaveService(waveService); -// ServiceLocator.getResourceService().loadSounds(waveSounds); -// -// lvl1 = WaveFactory.createLevel(LVL1_DIFF, LVL1_WAVES, LVL1_CHOSEN_LVL); -// lvl2 = WaveFactory.createLevel(LVL2_DIFF, LVL2_WAVES, LVL2_CHOSEN_LVL); -// lvl3 = WaveFactory.createLevel(LVL3_DIFF, LVL3_WAVES, LVL3_CHOSEN_LVL); -// } -// -// @Test -// void createBaseWaves() { -// GameLevelData.setSelectedLevel(0); -// Entity level1 = WaveFactory.createWaves(); -// assertNotNull(level1); -// -// GameLevelData.setSelectedLevel(1); -// Entity level2 = WaveFactory.createWaves(); -// assertNotNull(level2); -// -// GameLevelData.setSelectedLevel(2); -// Entity level3 = WaveFactory.createWaves(); -// assertNotNull(level3); -// } -// -// @Test -// void testCreateLevel() { -// assertNotNull(lvl1); -// assertNotNull(lvl2); -// assertNotNull(lvl3); -// } -// -// /** -// * The three following tests ensure that every wave in the level is created correctly -// * Since the waves are stored in a hashmap, by definition the mobs are unique and this -// * quality does not have to be checked. -// * */ -// @Test -// void testLevel1Creation() { -// List lvl1Mobs = lvl1.getWaves(); -// -// int waveNum = 1; -// for (WaveClass wave : lvl1Mobs) { -// -// // check the number of mobs in a wave -// if (waveNum % 5 != 0) { -// assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); -// } else { -// assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs: 2 general and 1 boss."); -// } -// -// // check if the boss is in the wave if it is a boss wave -// if (waveNum % 5 == 0) { -// assertTrue(wave.getEntities().containsKey(LVL1_BOSS), "This wave should contain a boss."); -// } -// -// // check the health of the mobs and ensure the mobs are the correct type -// for (Map.Entry entry : wave.getEntities().entrySet()) { -// String mob = entry.getKey(); -// int[] spawn = entry.getValue(); -// -// if (waveNum % 5 != 0) { -// assertTrue(LVL1_MOBS.contains(mob), "This mob is not assigned to this level."); -// assertEquals(MIN_HEALTH + waveNum, spawn[1], "The health of the mob should be " + MIN_HEALTH + waveNum + " ."); -// } else { -// if (mob == LVL1_BOSS) { -// assertEquals(MIN_BOSS_HEALTH + waveNum, spawn[1], "The health of the boss should be " + MIN_BOSS_HEALTH + waveNum + " ."); -// } -// } -// } -// -// waveNum++; -// } -// assertEquals(6, waveNum, "The should be 5 waves making numWave 6."); -// } -// @Test -// void testLevel2Creation() { -// -// List lvl1Mobs = lvl2.getWaves(); -// -// int waveNum = 1; -// for (WaveClass wave : lvl1Mobs) { -// -// // check the number of mobs in a wave -// if (waveNum % 5 != 0) { -// assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); -// } else { -// assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs: 2 general and 1 boss."); -// } -// -// // check if the boss is in the wave if it is a boss wave -// if (waveNum % 5 == 0) { -// assertTrue(wave.getEntities().containsKey(LVL2_BOSS), "This wave should contain a boss."); -// } -// -// for (Map.Entry entry : wave.getEntities().entrySet()) { -// String mob = entry.getKey(); -// int[] spawn = entry.getValue(); -// -// if (waveNum % 5 != 0) { -// assertTrue(LVL2_MOBS.contains(mob)); -// assertEquals(MIN_HEALTH + (waveNum * 2), spawn[1], "The health of the mob should be " + MIN_HEALTH + (waveNum * 2) + " ."); -// } else { -// if (mob == LVL2_BOSS) { -// assertEquals(MIN_BOSS_HEALTH + (waveNum * 2), spawn[1], "The health of the boss should be " + MIN_BOSS_HEALTH + (waveNum * 2) + " ."); -// } -// } -// } -// -// waveNum++; -// } -// assertEquals(11, waveNum, "There should be 10 waves making numWave 11."); -// } -// @Test -// void testLevel3Creation() { -// -// List lvl1Mobs = lvl3.getWaves(); -// -// int waveNum = 1; -// for (WaveClass wave : lvl1Mobs) { -// // check the number of mobs in a wave -// if (waveNum % 5 != 0) { -// assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); -// } else { -// assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs: 2 general and 1 boss."); -// } -// -// // check if the boss is in the wave if it is a boss wave -// if (waveNum % 5 == 0) { -// assertTrue(wave.getEntities().containsKey(LVL3_BOSS), "This wave should contain a boss."); -// } -// -// // check the health of the mobs and ensure the mobs are the correct type -// for (Map.Entry entry : wave.getEntities().entrySet()) { -// String mob = entry.getKey(); -// int[] spawn = entry.getValue(); -// -// if (waveNum % 5 != 0) { -// assertTrue(LVL3_MOBS.contains(mob)); -// assertEquals(MIN_HEALTH + (waveNum * 3), spawn[1], "The health of the mob should be " + MIN_HEALTH + (waveNum * 3) + " ."); -// } else { -// if (mob == LVL3_BOSS) { -// assertEquals(MIN_BOSS_HEALTH + (waveNum * 3), spawn[1], "The health of the boss should be " + MIN_BOSS_HEALTH + (waveNum * 3) + " ."); -// } -// } -// } -// waveNum++; -// } -// assertEquals(16, waveNum, "There should be 15 waves making numWave 16."); -// } -// -//} +package com.csse3200.game.entities.factories; + +import com.csse3200.game.components.tasks.waves.LevelWaves; +import com.csse3200.game.components.tasks.waves.WaveClass; +import com.csse3200.game.extensions.GameExtension; +import com.csse3200.game.physics.PhysicsService; +import com.csse3200.game.rendering.DebugRenderer; +import com.csse3200.game.rendering.RenderService; +import com.csse3200.game.screens.GameLevelData; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ResourceService; +import com.csse3200.game.services.ServiceLocator; +import com.csse3200.game.services.WaveService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import com.csse3200.game.entities.Entity; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + + + +@ExtendWith(GameExtension.class) +@ExtendWith(MockitoExtension.class) +class WaveFactoryTest { + + private LevelWaves lvl1; + private LevelWaves lvl2; + private LevelWaves lvl3; + + private final int MIN_MELEE_HEALTH = 80; + private final int MIN_RANGE_HEALTH = 60; + private final int MIN_BOSS_HEALTH = 80; + private final int LVL1_LVL = 1; + private final int LVL2_LVL = 2; + private final int LVL3_LVL = 3; + private static final ArrayList MELEE_MOBS = new ArrayList<>(Arrays.asList( + "Skeleton", "Coat", "DragonKnight", "Necromancer" + )); + + private static final ArrayList> LVL1_WAVES_STRUC = new ArrayList<>(Arrays.asList( + new ArrayList<>(Arrays.asList("Coat" + )), new ArrayList<>(Arrays.asList("Coat", "WaterQueen" + )), new ArrayList<>(Arrays.asList("WaterQueen", "SplittingWaterSlime" + )), new ArrayList<>(Arrays.asList("Coat", "WaterQueen", "SplittingWaterSlime" + )) + )); + + private static final ArrayList> LVL2_WAVES_STRUC = new ArrayList<>(Arrays.asList( + new ArrayList<>(Arrays.asList("Skeleton" + )), new ArrayList<>(Arrays.asList("Skeleton", "ArcaneArcher" + )), new ArrayList<>(Arrays.asList("Skeleton", "Wizard" + )), new ArrayList<>(Arrays.asList("Skeleton", "SplittingNightBorne" + )), new ArrayList<>(Arrays.asList("Wizard", "SplittingNightBorne" + )), new ArrayList<>(Arrays.asList("SplittingNightBorne", "Skeleton" + )), new ArrayList<>(Arrays.asList("Wizard", "SplittingNightBorne" + )), new ArrayList<>(Arrays.asList("ArcaneArcher", "SplittingNightBorne", "Wizard" + )), new ArrayList<>(Arrays.asList("Skeleton", "ArcaneArcher", "Wizard", "SplittingNightBorne" + )) + )); + + private static final ArrayList> LVL3_WAVES_STRUC = new ArrayList<>(Arrays.asList( + new ArrayList<>(Arrays.asList("Necromancer" + )), new ArrayList<>(Arrays.asList("Necromancer", "DodgingDragon" + )), new ArrayList<>(Arrays.asList("Necromancer", "FireWorm" + )), new ArrayList<>(Arrays.asList("Necromancer", "DeflectFireWizard" + )), new ArrayList<>(Arrays.asList("DeflectFireWizard", "FireWorm" + )), new ArrayList<>(Arrays.asList("DodgingDragon", "FireWorm" + )), new ArrayList<>(Arrays.asList("DodgingDragon", "Necromancer" + )), new ArrayList<>(Arrays.asList("FireWorm", "Necromancer" + )), new ArrayList<>(Arrays.asList("DeflectFireWizard", "Necromancer" + )), new ArrayList<>(Arrays.asList("DodgingDragon", "DeflectFireWizard", "Necromancer" + )), new ArrayList<>(Arrays.asList("FireWorm", "Necromancer", "DodgingDragon" + )), new ArrayList<>(Arrays.asList("FireWorm", "SplittingRocky", "Necromancer" + )), new ArrayList<>(Arrays.asList("SplittingRocky", "DeflectFireWizard", "FireWorm" + )), new ArrayList<>(Arrays.asList("DeflectFireWizard", "SplittingRocky", "Necromancer", "DodgingDragon", "FireWorm" + )) + )); + + private static final String[] waveSounds = { + "sounds/waves/wave-start/Wave_Start_Alarm.ogg", + "sounds/waves/wave-end/Wave_Over_01.ogg" + }; + + @BeforeEach + void setUp() { + GameTime gameTime = mock(GameTime.class); + ServiceLocator.registerTimeSource(gameTime); + ServiceLocator.registerPhysicsService(new PhysicsService()); + RenderService render = new RenderService(); + render.setDebug(mock(DebugRenderer.class)); + ServiceLocator.registerRenderService(render); + ResourceService resourceService = mock(ResourceService.class); + ServiceLocator.registerResourceService(resourceService); + WaveService waveService = new WaveService(); + ServiceLocator.registerWaveService(waveService); + ServiceLocator.getResourceService().loadSounds(waveSounds); + + lvl1 = WaveFactory.createLevel(LVL1_LVL); + lvl2 = WaveFactory.createLevel(LVL2_LVL); + lvl3 = WaveFactory.createLevel(LVL3_LVL); + } + + @Test + void createBaseWaves() { + GameLevelData.setSelectedLevel(0); + Entity level1 = WaveFactory.createWaves(); + assertNotNull(level1); + + GameLevelData.setSelectedLevel(1); + Entity level2 = WaveFactory.createWaves(); + assertNotNull(level2); + + GameLevelData.setSelectedLevel(2); + Entity level3 = WaveFactory.createWaves(); + assertNotNull(level3); + } + + @Test + void testCreateLevel() { + assertNotNull(lvl1); + assertNotNull(lvl2); + assertNotNull(lvl3); + } + + /** + * The three following tests ensure that every wave in the level is created correctly + * Since the waves are stored in a hashmap, by definition the mobs are unique and this + * quality does not have to be checked. + * */ + @Test + void testLevel1Creation() { + List lvl1Mobs = lvl1.getWaves(); + int bossHealth = 500; + int mobCount = 5; + + int waveNum = 1; + for (WaveClass wave : lvl1Mobs) { + int mobsRemaining = wave.getEntities().size() - 1; + int totalMobsSpawned = 0; + + // check the number of mobs in a wave + if (waveNum == 5 || waveNum == 1) { + assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); + } else if (waveNum == 2 || waveNum == 3) { + assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); + } else { + assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); + } + + + if (waveNum != 5) { + Set mobNames = new HashSet<>(wave.getEntities().keySet()); + Set expectedMobNames = new HashSet<>(LVL1_WAVES_STRUC.get(waveNum - 1)); + assertTrue(mobNames.equals(expectedMobNames), "The mobs in the wave should be " + expectedMobNames + " ."); + + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + assertTrue(values[0] > 1 && values[0] <= (mobCount - totalMobsSpawned - (2 * mobsRemaining))); + totalMobsSpawned += values[0]; + mobsRemaining --; + + if (MELEE_MOBS.contains(key)) { + assertTrue(values[1] == MIN_MELEE_HEALTH + waveNum, "The health of the mob should be " + MIN_MELEE_HEALTH + waveNum + " ."); + } else { + assertTrue(values[1] == MIN_RANGE_HEALTH + waveNum, "The health of the mob should be " + MIN_RANGE_HEALTH + waveNum + " ."); + } + } + } else { + assertTrue(wave.getEntities().keySet().size() == 1); + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + assertTrue(values[1] == bossHealth, "The health of the boss should be " + MIN_BOSS_HEALTH); + } + } + mobCount ++; + waveNum++; + } + assertEquals(6, waveNum, "The should be 5 waves making numWave 6."); + } + @Test + void testLevel2Creation() { + + List lvl2Mobs = lvl2.getWaves(); + int bossHealth = 1000; + int mobCount = 6; + + int waveNum = 1; + for (WaveClass wave : lvl2Mobs) { + int mobsRemaining = wave.getEntities().size() - 1; + int totalMobsSpawned = 0; + + // check the number of mobs in a wave + if (waveNum == 1 || waveNum == 10) { + assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); + } else if (1 < waveNum && waveNum < 8) { + assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); + } else if (waveNum == 8){ + assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); + } else { + assertEquals(4, wave.getEntities().size(), "Wave should contain 4 mobs."); + } + + if (waveNum != 10) { + Set mobNames = new HashSet<>(wave.getEntities().keySet()); + Set expectedMobNames = new HashSet<>(LVL2_WAVES_STRUC.get(waveNum - 1)); + assertTrue(mobNames.equals(expectedMobNames), "The mobs in the wave should be " + expectedMobNames + " ."); + + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + + assertTrue(values[0] > 1 && values[0] <= (mobCount - totalMobsSpawned - (2 * mobsRemaining))); + totalMobsSpawned += values[0]; + mobsRemaining --; + System.out.println("wave is: "+ wave); + + if (MELEE_MOBS.contains(key)) { + System.out.println("the health is: " + values[1]); + System.out.println("I want it to be: " + (MIN_MELEE_HEALTH + (waveNum * 2))); + + assertEquals(values[1], (MIN_MELEE_HEALTH + (waveNum * 2)), "The health of the mob should be " + (MIN_MELEE_HEALTH + (waveNum * 2)) + " ."); + } else { + assertEquals(values[1], (MIN_RANGE_HEALTH + (waveNum * 2)), "The health of the mob should be " + (MIN_RANGE_HEALTH + (waveNum * 2)) + " ."); + } + } + + } else { + assertTrue(wave.getEntities().keySet().size() == 1); + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + assertTrue(values[1] == bossHealth, "The health of the boss should be " + MIN_BOSS_HEALTH); + } + } + mobCount ++; + waveNum++; + } + assertEquals(11, waveNum, "There should be 10 waves making numWave 11."); + } + @Test + void testLevel3Creation() { + + List lvl3Mobs = lvl3.getWaves(); + + int bossHealth = 2000; + int mobCount = 8; + + int waveNum = 1; + for (WaveClass wave : lvl3Mobs) { + int mobsRemaining = wave.getEntities().size() - 1; + int totalMobsSpawned = 0; + + // check the number of mobs in a wave + if (waveNum == 1 || waveNum == 15) { + assertEquals(1, wave.getEntities().size(), "Wave should contain 1 mob."); + } else if (1 < waveNum && waveNum < 10) { + assertEquals(2, wave.getEntities().size(), "Wave should contain 2 mobs."); + } else if (9 < waveNum && waveNum < 14){ + assertEquals(3, wave.getEntities().size(), "Wave should contain 3 mobs."); + } else if (waveNum == 14){ + assertEquals(5, wave.getEntities().size(), "Wave should contain 4 mobs."); + } + + if (waveNum != 15) { + Set mobNames = new HashSet<>(wave.getEntities().keySet()); + Set expectedMobNames = new HashSet<>(LVL3_WAVES_STRUC.get(waveNum - 1)); + assertTrue(mobNames.equals(expectedMobNames), "The mobs in the wave should be " + expectedMobNames + " ."); + + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + + assertTrue(values[0] > 1 && values[0] <= (mobCount - totalMobsSpawned - (2 * mobsRemaining))); + totalMobsSpawned += values[0]; + mobsRemaining --; + + if (MELEE_MOBS.contains(key)) { + assertEquals(values[1], (MIN_MELEE_HEALTH + (waveNum * 3)), "The health of the mob should be " + (MIN_MELEE_HEALTH + (waveNum * 2)) + " ."); + } else { + assertEquals(values[1], (MIN_RANGE_HEALTH + (waveNum * 3)), "The health of the mob should be " + (MIN_RANGE_HEALTH + (waveNum * 2)) + " ."); + } + } + + } else { + assertTrue(wave.getEntities().keySet().size() == 1); + for (String key: wave.getEntities().keySet()) { + int[] values = wave.getEntities().get(key); + assertTrue(values[1] == bossHealth, "The health of the boss should be " + MIN_BOSS_HEALTH); + } + } + mobCount ++; + waveNum++; + } + assertEquals(16, waveNum, "There should be 15 waves making numWave 16."); + } + +} From 3889b03bd6827d2f85f487bddcd64abaa8ee2bfd Mon Sep 17 00:00:00 2001 From: Nhat Minh Le Date: Mon, 16 Oct 2023 18:01:59 +1000 Subject: [PATCH 21/27] Fix 9 code smells in TowerCombatTask (1 high, 7 medium, 1 low) --- .../components/tasks/TowerCombatTask.java | 150 +++++++++--------- 1 file changed, 78 insertions(+), 72 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java index c9f5c2b33..7f838bc14 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/TowerCombatTask.java @@ -123,77 +123,13 @@ public void updateTowerState() { towerState = STATE.DEATH; return; } - switch (towerState) { - case IDLE -> { - // targets detected in idle mode - start deployment - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(DEPLOY); - towerState = STATE.DEPLOY; - } - } - case DEPLOY -> { - // currently deploying, - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(FIRING); - towerState = STATE.FIRING; - } else { - owner.getEntity().getEvents().trigger(STOW); - towerState = STATE.STOW; - } - } - case FIRING -> { - if (shoot) { - // targets gone - stop firing - if (!isTargetVisible()) { - - owner.getEntity().getEvents().trigger(STOW); - towerState = STATE.STOW; - } else { - owner.getEntity().getEvents().trigger(FIRING); - // this might be changed to an event which gets triggered everytime the tower enters the firing state - - Entity newProjectile = ProjectileFactory.createFireBall(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); - newProjectile.setScale(1.1f, 0.8f); - newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.5), (float) (owner.getEntity().getPosition().y)); - ServiceLocator.getEntityService().register(newProjectile); - - // * TEMPRORARYYYYYYYY PLS DON'T DELETE THIS - // PIERCE FIREBALL - // Entity pierceFireball = ProjectileFactory.createPierceFireBall(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f,2f)); - // pierceFireball.setPosition((float) (owner.getEntity().getPosition().x + 0), (float) (owner.getEntity().getPosition().y + 0.4)); - // ServiceLocator.getEntityService().register(pierceFireball); - - // RICOCHET FIREBALL - // Entity ricochetProjectile = ProjectileFactory.createRicochetFireball(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f,2f), 0); - - // ricochetProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0), (float) (owner.getEntity().getPosition().y + 0.4)); - // ServiceLocator.getEntityService().register(ricochetProjectile); - - // SPLIT FIREWORKS FIREBALLL - // Entity splitFireWorksProjectile = ProjectileFactory.createSplitFireWorksFireball(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f,2f), 16); - - // splitFireWorksProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.75), (float) (owner.getEntity().getPosition().y + 0.4)); - // ServiceLocator.getEntityService().register(splitFireWorksProjectile); - } - } - shoot = !shoot; - } - case STOW -> { - // currently stowing - if (isTargetVisible()) { - owner.getEntity().getEvents().trigger(DEPLOY); - towerState = STATE.DEPLOY; - } else { - owner.getEntity().getEvents().trigger(IDLE); - towerState = STATE.IDLE; - } - } - case DEATH -> { - if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { - owner.getEntity().setFlagForDelete(true); - } - } + switch (towerState) { + case IDLE -> handleIdleState(); + case DEPLOY -> handleDeployState(); + case FIRING -> handleFiringState(); + case STOW -> handleStowState(); + default -> handleDeathState(); // DEATH } } /** @@ -245,7 +181,7 @@ private boolean isTargetVisible() { * @param newInterval The rate at which the tower should fire projectiles in shots per second. */ private void changeFireRateInterval(int newInterval) { - logger.info("Changing fire rate to: " + newInterval); + logger.info("Changing fire rate to: %d", newInterval); fireRateInterval = 1 / ((float) newInterval / 5); } @@ -258,4 +194,74 @@ public float getFireRateInterval() { return fireRateInterval; } -} + /** + * Function triggers actions at IDLE state, then switch to DEPLOY + */ + private void handleIdleState() { + // targets detected in idle mode - start deployment + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(DEPLOY); + towerState = STATE.DEPLOY; + } + } + + /** + * Function triggers actions at DEPLOY state, then switch to FIRING or STOW + */ + private void handleDeployState() { + // currently deploying, + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(FIRING); + towerState = STATE.FIRING; + } else { + owner.getEntity().getEvents().trigger(STOW); + towerState = STATE.STOW; + } + } + + /** + * Function triggers actions at FIRING state + */ + private void handleFiringState() { + if (shoot) { + // targets gone - stop firing + if (!isTargetVisible()) { + owner.getEntity().getEvents().trigger(STOW); + towerState = STATE.STOW; + } else { + owner.getEntity().getEvents().trigger(FIRING); + // this might be changed to an event which gets triggered everytime the tower enters the firing state + + Entity newProjectile = ProjectileFactory.createFireBall(PhysicsLayer.NPC, new Vector2(100, owner.getEntity().getPosition().y), new Vector2(2f, 2f)); + newProjectile.setScale(1.1f, 0.8f); + newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.5), (owner.getEntity().getPosition().y)); + ServiceLocator.getEntityService().register(newProjectile); + } + } + + shoot = !shoot; + } + + /** + * Function triggers actions at STOW state, then switch to DEPLOY or IDLE + */ + private void handleStowState() { + // currently stowing + if (isTargetVisible()) { + owner.getEntity().getEvents().trigger(DEPLOY); + towerState = STATE.DEPLOY; + } else { + owner.getEntity().getEvents().trigger(IDLE); + towerState = STATE.IDLE; + } + } + + /** + * Function handle DEATH state + */ + private void handleDeathState() { + if (owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + } + } +} \ No newline at end of file From b3edf44080ae2af1da61e43d696ead0cfe5e9dc2 Mon Sep 17 00:00:00 2001 From: BlairCannon97 Date: Mon, 16 Oct 2023 18:52:06 +1000 Subject: [PATCH 22/27] Fixed sound trigger for waves, where start/end sounds were incorrectly played. Fixed issue where game was lost during boss fights. --- .../com/csse3200/game/components/tasks/waves/WaveTask.java | 4 ++-- .../src/main/com/csse3200/game/screens/MainGameScreen.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/waves/WaveTask.java b/source/core/src/main/com/csse3200/game/components/tasks/waves/WaveTask.java index 2b04e4bc0..d49816692 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/waves/WaveTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/waves/WaveTask.java @@ -83,7 +83,6 @@ public void start() { this.currentWave = level.getWave(currentWaveIndex); ServiceLocator.getWaveService().setEnemyCount(currentWave.getSize()); logger.info("Wave {} starting with {} enemies", currentWaveIndex, ServiceLocator.getWaveService().getEnemyCount()); - this.waveStart.play(); // endTime = globalTime.getTime() + (SPAWNING_INTERVAL * 1000); } @@ -105,15 +104,16 @@ public void update() { // logger.info("No enemies remaining, begin next wave"); if (nextWaveAt == 0) { logger.info("Next wave in 10 seconds"); + this.waveEnd.play(); nextWaveAt = globalTime.getTime() + 10000; ServiceLocator.getWaveService().setNextWaveTime(nextWaveAt); } else { if (globalTime.getTime() >= nextWaveAt || ServiceLocator.getWaveService().shouldSkip()) { + this.waveStart.play(); ServiceLocator.getWaveService().toggleDelay(); currentWaveIndex++; ServiceLocator.getWaveService().setNextWaveTime(0); nextWaveAt = 0; - this.waveEnd.play(); this.waveInProgress = true; this.level.setWaveIndex(currentWaveIndex); // Set the service wave count to the current wave index. diff --git a/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java b/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java index e21cec99c..93471edd3 100644 --- a/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java +++ b/source/core/src/main/com/csse3200/game/screens/MainGameScreen.java @@ -220,7 +220,7 @@ public void render(float delta) { } else if (ServiceLocator.getWaveService().isLevelCompleted()) { // Check if all waves are completed and the level has been completed logger.info("Main game level completed detected, go to win screen"); - ui.getEvents().trigger("lose"); // needs to change to: ui.getEvents().trigger("win"); + ui.getEvents().trigger("win"); // needs to change to: ui.getEvents().trigger("win"); // Add something in to unlock the next planet/level? } } From 4b348390ee6734d3c16eb00b4d69d7bcf180a9fd Mon Sep 17 00:00:00 2001 From: SonjaMcNeilly Date: Mon, 16 Oct 2023 18:56:06 +1000 Subject: [PATCH 23/27] Added new towers to turret select screen and selectable towers --- .../turret-select/firework-tower-default.png | Bin 0 -> 10919 bytes .../turret-select/firework-tower-selected.png | Bin 0 -> 8446 bytes .../turret-select/pierce-tower-default.png | Bin 0 -> 10623 bytes .../turret-select/pierce-tower-selected.png | Bin 0 -> 8341 bytes .../turret-select/ricochet-tower-default.png | Bin 0 -> 12116 bytes .../turret-select/ricochet-tower-selected.png | Bin 0 -> 9456 bytes .../game/input/BuildInputComponent.java | 3 +++ .../com/csse3200/game/screens/TowerType.java | 8 +++++++- .../game/screens/TurretSelectionScreen.java | 4 ---- 9 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 source/core/assets/images/turret-select/firework-tower-default.png create mode 100644 source/core/assets/images/turret-select/firework-tower-selected.png create mode 100644 source/core/assets/images/turret-select/pierce-tower-default.png create mode 100644 source/core/assets/images/turret-select/pierce-tower-selected.png create mode 100644 source/core/assets/images/turret-select/ricochet-tower-default.png create mode 100644 source/core/assets/images/turret-select/ricochet-tower-selected.png diff --git a/source/core/assets/images/turret-select/firework-tower-default.png b/source/core/assets/images/turret-select/firework-tower-default.png new file mode 100644 index 0000000000000000000000000000000000000000..666995a7a797d23432308fb60d77644fb69b6e5f GIT binary patch literal 10919 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 JPDHLkV1ibBk17BF literal 0 HcmV?d00001 diff --git a/source/core/assets/images/turret-select/firework-tower-selected.png b/source/core/assets/images/turret-select/firework-tower-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..a047f5688ad4138330d53f5272c202ac78b958af GIT binary patch literal 8446 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 z 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 Z{y!&8Bzr{{b}0Y=002ovPDHLkV1jLHSeF0* literal 0 HcmV?d00001 diff --git a/source/core/assets/images/turret-select/pierce-tower-selected.png b/source/core/assets/images/turret-select/pierce-tower-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..5d376fce5b054f561eb5c27e4b8faeda2fd958d2 GIT binary patch literal 8341 zcmV;GAZp)a$wJ=Ocwq^K1c)jQxmKqFdSuA2ZsqVAmPtb0ae#f=Zn`(gJ`$L1u~ z@?S1yl$he;UHtXHz)uA?U&Xt4AQ~C%`&-mtSZ0`~VgiBev!MSo_;}+nji`^#1NwV< zYdtXC)p*9iIle8h!t)Egi{pP+|MzDRgCF9|A}g}rU^wP0yp!_@9svZ`P={K`Cn#Y? z`u;NgBs}YQ{k5zR$g?a+#1hb|w)h&{jo+=ZSJ$;fK6(TI(m5`1AeQ}NQ7r(hW<_=_ zXJhAy4X z)M8e@p-qMAkF#@Yct=M4>7)Xm&3Cw5tZ2)#*0wPDKmt-<69CK)xr3l26gUEheRaQMIEE8Fg17Sr2Orpk-Xeo>DoxUEWkvQu!Of$jVhu@EE|z!H zIqP5N8UEdv5<9Mw$|6>A0!c;OynDJrluJ7kC;loR1scOr2v9AG9zmN~I=i=2DE&cF zO+LwE@RM_JOL+%ZWm12#uKwOZQmI5ek=D{FpsZpnVih3>5NbO)h~os$@Zhsm@*Klm z1^}}{?x3J1RrWa7QB^Kh+*0GHor9|I4h*zmoC-q<=>4Igjy4slzthRC<47uw;kaVI zxfTFIawuVAZO3aIwIu*h8B<~x%Qi`fN>K=Pg~49uz(D#6*0@(Wf}f0KQ&|`SIk|!H zGe+OVj&SGbKU?b0f&rZixdo1C&q&0wVBs&qsOM??>E5MbIs9V)C^hfbh(<(Ke0G{^ z`V*7w(Vb3qtw^YehXKH?5iRZD9FMC+t&ue8W;T;+Ib9)E-0c<1j!l5e9T$w7#L^!? zAxKR896SLcve_)Mg;AGSQ*6H7Q*6F#2k#F_!G=Dm?-%W{wogyCZ+DHOb|(N_jlcJ$ z{WC&x=urTOHnW8B8>#y#wzm)-g_!+Q`#@+6Jkxc60ajb7Ar)lJfHuI z$l-e3P6Sh2k2h4j$#y$1!`*bx2&aoVIp=rvc-z>3$pl?uN))ek{8S+toxlwF4Myqa zN~noP+hc9!{oP){dlM2MkMS*oHWDxI16a6lVsu!kzJf(j~?+N?s9T-T}5V@zOxd9Tquu%r~fI z!!qD)^gxxpdg7*Z1XjTA+H;X|BMl}Pad3g|J>`3Tz**Nml!t2UXMoXg8{yboo*-MFx)WK^;i}P8M;^KkM%-YOH;K%qyIjASwY()+}NTg}0vFazo-&@}FWI@%y)I-Ezsqyvm6Q z3^!QB%7Md?Lv~HluBrG$+59Oh%9pB1^{a0m+Xct zOaVu=eQ~Ncy>!A|=mdsEtQ^oJobSQVsfQfH0}lO_`lPE~{@p8|Y>l+oC)@>3Kv=}e z0aj$SbWkFRlS%0F_kk3Y2+_!*_=&{*hc>i+?&J7z8)Uk-C)%Y?7+A#0fq66Mh2@Z( z4!1@j+!BFQPnuykhB&J2kW@@mDWgJ|QoD0>FyA4*}65MAJbjWh7TA2+-i4CTWByWU7B>;A`)# zdiT&J4VMNd+y@wESQ2XpbpNqui4s+&%3&GgkPK2lf^6D(roz@ETvSKkN0FDXfNsw!zjQ!}+jEYHwIyjdHY*+kZ^#Ff>DpJ@B9 zt-ss8Xu^GvafC&z960poksFCZ8uj%T7?#VB3D+CKjk9w|s_e!&xQf93&|SOF?D9tC zNYUR@u!wawHvZ~`D-)-Ze+>+jasmsuQS#V1BoHMSm0ChZYVi(^ZbT)s_kaJ;@Bg%6 z>%xilnZ^Sau}paT!1i+guE4E0DglW@38YIVw5g1Ryp5IPQIavH4Hzgtv$gjpum9oo z*>NRS)HatzEEB>lkqV*`mey}`Ouv*8*=!*jzJ{eC)I(G=ztg{stiH&4PrcE-{?&DF ztd1-3qP@Qsu}q+IVhx}G(v5X0m5kL)%%ek)TneEZ{*F>kx4&G`w%ENd{q&Wa#_{8f zD=cD}!0?O;q<=aM^#p}}H8|q`W6~P!v5bThqiwOTz4-J?3&!N)Cm1YZnc%8(#(;qV z)tuCqkN1$!i0fEEEANJ%27J#+@Pc){Xa(n(T~;C0SDVY z`dRD4G;EMUR)G9 z6!|j4GF67bZq5-73&XBQVkwzsf~*Ir(e>YpZXH8Q6#~HTf3#tB&vySqYD!(s+E@WS zJ4&u|kXTud5s8GPl4z0|)Rap9)=9~1dTmot)y!MO0>FB!WdPtaQZ-qLbq+A%TE?bV zKlIyXKm)0^W6z!Vw`cz8*|peKHm)Cbj9?KfqryCB3-#}SfvRk}HHZ$Y6)=#7PVKnH~4|;gZn;wK#s_BjYKo2OBu6An6;hM{yd@Co}%^@$wc0%`V-i* zQbD-S$U&xh($_?l=r1dwN*>o6Fp4NzV*UE|ryo{gN}U{2E@n7}!G;@r_`I9XB%Xzg zFh|m${RBw>lGd_z6p|_%UN9x0vmPZk&;c+fuZc>$M>Zd=t@}cKN1=B56s0Qs_3cmp zOb*Le6O}Y0sSGKDYrr6>1T5%tX4Dt%qY{W^`aM4*q>vt{{>;UH*-{%LBCqxv^+>Xl zubsX(t)>e0{ud>&(gEobqk2Io;jwXob~~fALNeigLsBUT6}xgWWRm-(6;?E4!wE*o zvYxOW9JM#{huPx=7SFfj#qYmzYip#%Hcq!;R8f?~A}W#bVWU(L%S4>b0Ct~^5{%Yf z5dbBr(&mX&Z<_v;A~I;YDx{=R`$=VZen_rt6n;>CQPb6+;Voi`bIPAyv+Kuu?m7_K z?;7G#CoPKA#-fiLC5Pb7%x0jHipvZSL-OF7ds0XSIU;8w=l}zX{SZdkGOMf0v)|$D zzYUKQp~;#n9gtSVLy4!s3-M9^)MJREBG!vfzqByY8ePddI6JG`Q%Dw6ZK@t-K*@rt z4~eD36)L%i-laorF^A|Wm`4A9kiC^Nzo^&8 zBUB_4z9*75nOXVm^qXfEw=Zn#p5bmvkIfw!eG~<;f>MC*eYfuphGpt70&c4iskA~u zrTl5ykV-2Oau5RvVc16)r$R|5mI{K3TB`%SbeOhoak}T1RHJWbE<>Bmh4JGY-fM(&pT&?TOzZR zgo?ET;f5RQ-&1$3H z=Uutv%AS==;JN45Z|v~z4E%!}SLQKnrmP14PfjZt+Y84|i+UOeC6%t`3@5-KBYj^z z$O;~TlB;xvI}@E}ATuUOqf%{}6qKCfbwBfoNlUC9EpJzLZ|Pa3O4=owq%B3lMB7tv z@R8Q%PR;M$mh4XXq@d)BpGe$H2vp#MMrKeCPLH13%Ec{<|7G4!mR2p#8AH1MjxWCZ zTGeZz6EB?lwkm0}jlM9d+K@yU`v^ExabhNDN{MEaDI1Lyrld8!4on|m8I3(7@lcg! zP7H$^F(xf0EwTD`2Nx2J+*!WJOC#p-&IAPZh9J}u`EsN+`d0u@P7P`uG&EqSCyoKT z*S2NNlh;3h)BA>c4-8XWcf)lp|I_vSQ)k}xe~YN3GHakSx+IDzl&Cb%XwS@c;6wB)gHV%d`GA^pqaIza zNB{=Hpa0c$-y4op2&_Cna^0}@`mL3hR{Wf3B$Q3d=r$X51dQ2PsZ97lFe)TO)9uwu zY79aexksby#&ldkh^$FTtd3Z_nCedX@S&uG5`+`stZ__XRCzKmaLXWEHqY~fzzXN| z01a~e-Q73*xnfD#PZeYTi<(PsX z)}&O0r9!F7IXHjpcsxi{V&fc~6Qu^u3&n?IcxigtCCu_nkYSk=KDW|YB`@6cz~8@g zKJRDLOwGEzY1`fh56(?=C2ycROnJkz)ZHL{WYwOoinHZ40t+Vfnr20ISSdPLFe!;O z#pYA5{o2*9zq9$>BRy~XS4kmxCDDkA5{w2wxr|C=MKNEEj4peUgqw%^iKe)@9b8xLglFSw`l_{PpJXtMU1qZhRu;_Tc1CnHS=zL$?|e&xi9!TJ`p^amEDl6tT~}>cPf<6}K`+n2U@B2Fl@Lg#&_Nn% z%MdH3Bs?=qelvSbbIXO_nTdf(ORTOf-MDDdozhc~*H=H?_d)PZ0H6zFvV4Dgm!S?7 zB@^N>C!5W6Tu3%r?u;RduuO?Xiw!+OLD*GhOxidWN`-W98bXI6&~^AUL|UUXo`k`5 zhG!I3WMzh9WMDuw6M&{sOkse&4}uTv`t9!JL(OSF8a-nF&D!67qxZeOf1#u@;!!$i zd@QYp9Q4TgP%zrVjBXyI;o{_Lr?34ee4rpwIR+-J#8Q%~9PNmM992|&Kqv*$0VySV zg?*lxC2s=*7-F!cdefJu{OPOF>EiUt1Jx+Z(>ZWzQ}@qaKJnVRCHF0OX|88}R(kw# z#~*8s{zu2Xa!k402n!gJ2Xs;?dQFwSpg?A9Bn%|q;8YFPyW=%$X)a@vMU^YU-H{i; z0|g_wCM~hP@P9tP#O z*hplpf=V8vAyvzgg!S(VeCOr+UT@MQ&70~;U!lobS#}IJGcX8h^a`3iKSNCpS3#Ps zl{YFNI0niWdf%K?IXjxaJ2U>6^o4#%CSw~8GfQWu7CboTW=>#}tlnK?B&m&lL81^x ztRw#A#Hr+JqLMOmpP;4}{h3E_Yepw@TFcgAQJC_A1_npwO+RCmpPbThE}rRRLQ(Y< zUfOh7py8(a+gOf~SY5T5%Z(vJ05sw`BgbeQH~83W3zw-onwVxg3`Vv#OVb-(bwl4# zV}HR{>nkW}1;yB~x@z^RBc8dXk6>()(ObD_EUKh`W`ntt*&q^nakK}qq<_PpC)!Kq zIavn5jB$}!n9@BMj=+r**geg+HvaD7l4dL1Pzbp3yK7$JoZLZN`&q0I!By`B=S2=I z{G)YMMz0~!D;OvNOL*GdgI&{Dr|zh-)8o;@%c{yjV*|U8O?G z^b8K@OEj;+qu1KwF*Tp5dUvX`t{_JlvGm(>oEIL#>FGupqhtApu9ea2h|bD4~0l*^cm`O;Y>kIi2`ue(t1FN@z&yotq# zrB%yA&9^n)rX4Yi|gP;l-&~R$hup8uJ^afY+2+3@f zgKn=mFLFFP_x71DSK14A@ESXJ!CWY7^k!?Ziw&RfE%VR$$L1e09P_f8P>E4mOjV?= z`sfrITmKXqY;eUTeKFxot44VyyOtqq4o~Q6QPbyW;kiYx@J91(jn6LdE{YZMt!5)~ z1SDnH{AOgvkNY?kPCd>oH_`m0<_?)7*(Me z(_GWj({7x;dy{9=_hKFKX^B(GB`JS;74PD0tZ8DOF(3pCQt9CInGmYE`XQ}J-ua#% zUvk&7Ez?~M1(gpR4t#UTOlV_{LKo9@PAgm$16EzP3XARzZQsA`1OLvzwsb(6n)atZ zElbJ*qe8-{Rv=(XFmw7u8X;5k$GiDWrA_X)uD|1p+luWT@(NKvBL$VF${Vg&xUvTp zLJywr%}sCa4SpD2C`aUaP10&WA(ccU4j{m5vgTtrhJ%cc7Z5$den*Xc&qbfD-u}sX zSDY?{dvU(7R{uIjd}h^W4#O%qysPs)PqZUmk@lxOs-)S7LO3}hdz7SF%5sb>mWh7v z+|rIKmtEC4DL&XJ0Zr3_{9vPn<#m^Yp$@`@`RVcpmRN?Bg;4;%FSrNfgq$4}ZNWl5 z!29`)Lk z*6|GkD^!}xXAam5G<>PfBG(w9U~H`5flMV!U|$HDzdm!oYq5@jE1FoUar9A;;fB{A ze>400kw7cB8(nlmlr*?3)i?&2v=R$Rfh0jhkHzUWb#+blbtKn}uS^@qD=cnIN{RKa zS3FKh<(X4T$z%{Ld?<2G|Bp5qs!serOKOb{CbcRwh=c?~lAzSW)xdDWc6Wn7#^!%N zkdnqGQ7Lo@E zf{O>>hNxItc9x1X2Td(9jV>mw#4?D1_ExUf%_IYzXoIDjh0(*LSA{6O&ikLP8I?=1 zV1iXPV_{Ta5zE4;z#^7~QGrD)3!{R5V&(48A(_=Zh}FMifkNj38L6j>)ep|qQ%LmX za4k|<7>u0UFn7{pxM97Mb1wDPdR8Q235ciTAh04AkouYcpvTR-qYmOY5t2i6#Nogh zb{2{ShSu^5UMI(J^{10vi;yEpHOVEE1junEGwfF*7+2iQOPX}|RA=3-rGjUAtJ>m| z!ZI7?Lbwe4e3hs!J7-sTh6fvKJFbrnv4~Zipb`p6r4Z%P-gM8k0V&WJmO_vYN;pMY zlVeJ+CTY*GBKssI$qx=bP#ss|tHN^VyJu3}&7r<99ZBtVlu6}c`AMIxa&wKNcArKx zi&(`As{T+?>7<$(mO>(u3TNc2Y3&G6$-|ngZQA|K{oPrT{PTz3c5@8(jjrUWHJynQ z^OS@_M@xGhW#F^<4wQ=(Z5FAD9t722Z@PCrl~e&KGw(jB9#m8622Ii)+w+|Rbbfs{ zrL)kxINlQZ=#g@<;*5iH{^$rC_N7A-gt-vDJS(QeE-v=FY5@RkDwFy{+p3o+WF0HA zr~#n_L&9Bc{-7vd!uCzb2_c4#)?VMRU`=`gbj;c^sIP);`@jOXCp>m${ zJYKgjcED$WAqDjQfWX6=q&;s|e`X9z6Z94(P5R==MCUCDHL)Te6P8(qg}#IzV#nij zEgJh28~AFcVC*bA^`2-y2T#VLebo+PM+^TBS6Q|-EI f@6b!)0l@zPt+BL@kRimS00000NkvXXu0mjfOfnYo literal 0 HcmV?d00001 diff --git a/source/core/assets/images/turret-select/ricochet-tower-default.png b/source/core/assets/images/turret-select/ricochet-tower-default.png new file mode 100644 index 0000000000000000000000000000000000000000..882a58ce076ec0ed4719e06a9401f3f16e0a560c GIT binary patch literal 12116 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 TowerFactory.createWallTower(); case FIRE -> TowerFactory.createFireTower(); case STUN -> TowerFactory.createStunTower(); + case PIERCE -> TowerFactory.createPierceTower(); + case FIREWORK -> TowerFactory.createFireworksTower(); + case RICOCHET -> TowerFactory.createRicochetTower(); }; // build the selected tower newTower.setPosition(x, y); diff --git a/source/core/src/main/com/csse3200/game/screens/TowerType.java b/source/core/src/main/com/csse3200/game/screens/TowerType.java index 9c4adba91..1c534c8ac 100644 --- a/source/core/src/main/com/csse3200/game/screens/TowerType.java +++ b/source/core/src/main/com/csse3200/game/screens/TowerType.java @@ -14,7 +14,13 @@ public enum TowerType { STUN("Stun Tower", "The Stun Tower releases electric shocks that temporarily immobilize and damage enemies.", 5, "1000", "images/turret-select/stun-tower-default.png", "images/turret-select/stun-tower-selected.png"), INCOME("Income Tower", "The Income Tower generates additional in-game currency over time.", - 5, "0", "images/turret-select/mine-tower-default.png", "images/turret-select/mine-tower-selected.png"); + 5, "0", "images/turret-select/mine-tower-default.png", "images/turret-select/mine-tower-selected.png"), + PIERCE("Pierce Tower", "The Pierce Tower fires a projectile that pierces through targets and does not dissipate upon contact.", + 6, "0", "images/turret-select/pierce-tower-default.png", "images/turret-select/pierce-tower-selected.png"), + RICOCHET("Ricochet Tower", "The Ricochet Tower fires a projectile that upon contact does damage and changes direction", + 7, "0", "images/turret-select/ricochet-tower-default.png", "images/turret-select/ricochet-tower-selected.png"), + FIREWORK("Firework Tower", "The Firework Tower fires a projectile that splits on contact with its target", + 8, "0", "images/turret-select/firework-tower-default.png", "images/turret-select/firework-tower-selected.png"); private final String towerName; private final String description; diff --git a/source/core/src/main/com/csse3200/game/screens/TurretSelectionScreen.java b/source/core/src/main/com/csse3200/game/screens/TurretSelectionScreen.java index e27598388..8d6278666 100644 --- a/source/core/src/main/com/csse3200/game/screens/TurretSelectionScreen.java +++ b/source/core/src/main/com/csse3200/game/screens/TurretSelectionScreen.java @@ -153,10 +153,6 @@ public void clicked(InputEvent event, float x, float y) { descriptionLabel = createButton("images/turret-select/imageedit_15_5627113584.png", "images/turret-select/imageedit_15_5627113584.png", "Description: ", turretName, ""); - //turretDescriptionText = createButton("images/turret-select/imageedit_20_9050213399.png", - // "images/turret-select/imageedit_20_9050213399.png", ) - - TextButton button = createButton(turret.getDefaultImage(), turret.getClickedImage(), turret.getPrice(), turret.getTowerName(), turret.getDescription()); From c66f4d3e8bce1233645d8c4cf2fb11ee733df5d6 Mon Sep 17 00:00:00 2001 From: FattyHope Date: Mon, 16 Oct 2023 19:00:13 +1000 Subject: [PATCH 24/27] Changed hammer icon to a more suitable, spanner icon --- source/core/assets/images/spanner.png | Bin 0 -> 516 bytes .../csse3200/game/input/UpgradeUIComponent.java | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 source/core/assets/images/spanner.png diff --git a/source/core/assets/images/spanner.png b/source/core/assets/images/spanner.png new file mode 100644 index 0000000000000000000000000000000000000000..d5b7acbf1e333a7d9b49964656623cb1633b229d GIT binary patch literal 516 zcmV+f0{i`mP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGbVgLXKVgWdM zncDyW02y>eSaefwW^{L9a%BK_cXuvnZfkR6VQ^(GZ*pgw?mQX*00Cr4L_t(oNA1-y zF9bmt#_=T*rMT7?P|@L2_yD5UP$>Bx8jT8}lc?nAH7cE!N`X+25dP2QCRvkr=XU4j z?e0l_x3`zgK6|sfGgZONV;9$W#t+_chkdwZ3OjhkZ-Xz)qIhO-0;@Fm#s*w+2&*)3 zEnkOK8n~A0zTd+tmNw;MJrirNii0zxqKngmxi{F$h+ZqJICz9c$%@J~LOM>bXjYC8 zm&TS5r(Q0aC$e&idYR=I%LpS8my0H=tQ??zq7>bZbHwGM88a(eh)GbPKlfN2$Nol@mmJG6lP%cx;c`4#2{ zd+EGk6ARER*uy6taD!XuLRzQGKi6@qD3(dNRTi6+7t11EV3oy+mn#>r%3?0%$_K2% zI=6CV)CIVfE2&%HUamAfg!;|BJVY0vk Date: Mon, 16 Oct 2023 19:26:02 +1000 Subject: [PATCH 25/27] Adding new parameter to new turrets 'skinName' --- .../core/src/main/com/csse3200/game/screens/TowerType.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/screens/TowerType.java b/source/core/src/main/com/csse3200/game/screens/TowerType.java index 1c534c8ac..4c910c97c 100644 --- a/source/core/src/main/com/csse3200/game/screens/TowerType.java +++ b/source/core/src/main/com/csse3200/game/screens/TowerType.java @@ -15,11 +15,11 @@ public enum TowerType { 5, "1000", "images/turret-select/stun-tower-default.png", "images/turret-select/stun-tower-selected.png"), INCOME("Income Tower", "The Income Tower generates additional in-game currency over time.", 5, "0", "images/turret-select/mine-tower-default.png", "images/turret-select/mine-tower-selected.png"), - PIERCE("Pierce Tower", "The Pierce Tower fires a projectile that pierces through targets and does not dissipate upon contact.", + PIERCE("Pierce Tower", "", "The Pierce Tower fires a projectile that pierces through targets and does not dissipate upon contact.", 6, "0", "images/turret-select/pierce-tower-default.png", "images/turret-select/pierce-tower-selected.png"), - RICOCHET("Ricochet Tower", "The Ricochet Tower fires a projectile that upon contact does damage and changes direction", + RICOCHET("Ricochet Tower", "", "The Ricochet Tower fires a projectile that upon contact does damage and changes direction", 7, "0", "images/turret-select/ricochet-tower-default.png", "images/turret-select/ricochet-tower-selected.png"), - FIREWORK("Firework Tower", "The Firework Tower fires a projectile that splits on contact with its target", + FIREWORK("Firework Tower", "", "The Firework Tower fires a projectile that splits on contact with its target", 8, "0", "images/turret-select/firework-tower-default.png", "images/turret-select/firework-tower-selected.png"); private final String towerName; From f2e7d04d6644fe39f009c2c167ed64c8692441ba Mon Sep 17 00:00:00 2001 From: Thivan W Date: Mon, 16 Oct 2023 19:26:39 +1000 Subject: [PATCH 26/27] Fixed tower scanner to match the enemies hitboxes --- .../com/csse3200/game/components/tasks/FireTowerCombatTask.java | 2 +- .../csse3200/game/components/tasks/PierceTowerCombatTask.java | 2 +- .../csse3200/game/components/tasks/RicochetTowerCombatTask.java | 2 +- .../com/csse3200/game/components/tasks/StunTowerCombatTask.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java index 13aa11a18..58a76a43b 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/FireTowerCombatTask.java @@ -67,7 +67,7 @@ public FireTowerCombatTask(int priority, float maxRange) { public void start() { super.start(); // get the tower coordinates - this.towerPosition = owner.getEntity().getCenterPosition(); + this.towerPosition = owner.getEntity().getCenterPosition().sub(0.125f,0.125f); this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); owner.getEntity().getEvents().addListener("addFireRate",this::changeFireRateInterval); //default to idle state diff --git a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java index cdefde434..dffe53878 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/PierceTowerCombatTask.java @@ -63,7 +63,7 @@ public PierceTowerCombatTask(int priority, float maxRange) { public void start() { super.start(); // Get the tower coordinates - this.towerPosition = owner.getEntity().getCenterPosition(); + this.towerPosition = owner.getEntity().getCenterPosition().sub(0.25f, 0.25f); this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); // Set the default state to IDLE state owner.getEntity().getEvents().trigger(IDLE); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java index 7fc3e5728..9e9aabc3e 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/RicochetTowerCombatTask.java @@ -65,7 +65,7 @@ public RicochetTowerCombatTask(int priority, float maxRange) { public void start() { super.start(); // Get the tower coordinates - this.towerPosition = owner.getEntity().getCenterPosition(); + this.towerPosition = owner.getEntity().getCenterPosition().sub(0.25f, 0.25f); this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); // Set the default state to IDLE state owner.getEntity().getEvents().trigger(IDLE); diff --git a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java index ec469b269..014903ffa 100644 --- a/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java +++ b/source/core/src/main/com/csse3200/game/components/tasks/StunTowerCombatTask.java @@ -67,7 +67,7 @@ public StunTowerCombatTask(int priority, float maxRange) { public void start() { super.start(); //get the tower coordinates - this.towerPosition = owner.getEntity().getCenterPosition(); + this.towerPosition = owner.getEntity().getCenterPosition().sub(0.25f, 0.25f); this.maxRangePosition.set(towerPosition.x + maxRange, towerPosition.y); owner.getEntity().getEvents().addListener("addFireRate",this::changeFireRateInterval); //set the default state to IDLE state From 1474230b1caccb8552790d922fd0d1259cbf1533 Mon Sep 17 00:00:00 2001 From: SonjaMcNeilly Date: Mon, 16 Oct 2023 21:33:10 +1000 Subject: [PATCH 27/27] Update TowerType --- .../main/com/csse3200/game/screens/TowerType.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/core/src/main/com/csse3200/game/screens/TowerType.java b/source/core/src/main/com/csse3200/game/screens/TowerType.java index 7770cea31..11bbf590a 100644 --- a/source/core/src/main/com/csse3200/game/screens/TowerType.java +++ b/source/core/src/main/com/csse3200/game/screens/TowerType.java @@ -2,19 +2,19 @@ public enum TowerType { WEAPON("Weapon Tower", "weapon_tower", "The Weapon Tower is a simple and basic turret that fires rapid shots at enemies dealing damage over time.", - 0, "15", "images/turret-select/Weapon-Tower-Default.png", "images/turret-select/Weapon-Tower-Clicked.png"), + 0, "15", "images/turret-select/Weapon-Tower-Default.png", "images/turret-select/weapon-tower-selected.png"), TNT("TNT Tower", "tnt_tower", "The TNT Tower launches explosive projectiles, dealing area damage to groups of enemies.", - 1, "30", "images/turret-select/tnt-tower-default.png", "images/turret-select/tnt-tower-clicked.png"), + 1, "30", "images/turret-select/tnt-tower-default.png", "images/turret-select/tnt-tower-selected.png"), DROID("Droid Tower", "droid_tower", "Droid Towers deploy robotic helpers that assist in combat and provide support to nearby turrets.", - 2, "45", "images/turret-select/droid-tower-default.png", "images/turret-select/droid-tower-clicked.png"), + 2, "45", "images/turret-select/droid-tower-default.png", "images/turret-select/droid-tower-selected.png"), WALL("Wall Tower", "wall", "The Wall Tower creates barriers to block enemy paths, slowing down their progress.", - 3, "45", "images/turret-select/wall-tower-default.png", "images/turret-select/wall-tower-clicked.png"), + 3, "45", "images/turret-select/wall-tower-default.png", "images/turret-select/wall-tower-selected.png"), FIRE("Fire Tower", "fire_tower", "The Fire Tower emits flames, causing damage over time to enemies caught in its fiery radius.", - 4, "45", "images/turret-select/fire-tower-default.png", "images/turret-select/fire-tower-clicked.png"), + 4, "45", "images/turret-select/fire-tower-default.png", "images/turret-select/fire-tower-selected.png"), STUN("Stun Tower", "stun_tower", "The Stun Tower releases electric shocks that temporarily immobilize and damage enemies.", - 5, "45", "images/turret-select/stun-tower-default.png", "images/turret-select/stun-tower-clicked.png"), + 5, "45", "images/turret-select/stun-tower-default.png", "images/turret-select/stun-tower-selected.png"), INCOME("Income Tower", "income_tower", "The Income Tower generates additional in-game currency over time.", - 6, "10", "images/turret-select/mine-tower-default.png", "images/turret-select/mine-tower-clicked.png"), + 6, "10", "images/turret-select/mine-tower-default.png", "images/turret-select/mine-tower-selected.png"), PIERCE("Pierce Tower", "", "The Pierce Tower fires a projectile that pierces through targets and does not dissipate upon contact.", 6, "0", "images/turret-select/pierce-tower-default.png", "images/turret-select/pierce-tower-selected.png"), RICOCHET("Ricochet Tower", "", "The Ricochet Tower fires a projectile that upon contact does damage and changes direction",