From 67cf0e576bd4de3139b58456253f1b538cd8144f Mon Sep 17 00:00:00 2001 From: troytt <47798984@qq.com> Date: Tue, 22 Sep 2026 10:14:11 +0000 Subject: [PATCH] feat(ui): dynamic attack icon (fist/sword) and add throw & unsummon default skills (Fixes #376) --- public/skills/icon_0.png | Bin 2809 -> 2914 bytes public/skills/icon_0_fist.png | Bin 0 -> 2687 bytes public/skills/icon_0_sword.png | Bin 0 -> 2914 bytes public/skills/icon_2.png | Bin 0 -> 2918 bytes public/skills/icon_3.png | Bin 0 -> 2887 bytes public/ui/icon_0.png | Bin 2809 -> 2914 bytes public/ui/icon_0_fist.png | Bin 0 -> 2687 bytes public/ui/icon_0_sword.png | Bin 0 -> 2914 bytes public/ui/icon_2.png | Bin 0 -> 2918 bytes public/ui/icon_3.png | Bin 0 -> 2887 bytes src/ui/hotkeys.ts | 110 +++++++++++++++++++++++++++++---- src/ui/hud-manager.ts | 7 ++- tests/hotkeys.test.ts | 110 +++++++++++++++++++++++++++++++++ 13 files changed, 214 insertions(+), 13 deletions(-) create mode 100644 public/skills/icon_0_fist.png create mode 100644 public/skills/icon_0_sword.png create mode 100644 public/skills/icon_2.png create mode 100644 public/skills/icon_3.png create mode 100644 public/ui/icon_0_fist.png create mode 100644 public/ui/icon_0_sword.png create mode 100644 public/ui/icon_2.png create mode 100644 public/ui/icon_3.png diff --git a/public/skills/icon_0.png b/public/skills/icon_0.png index ca65c842d65db01a51de18854bd091f3a38ceddb..a141a92bb03e96f7c424d455d205aa4f3de6f3ed 100644 GIT binary patch delta 2110 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 delta 2004 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 literal 0 HcmV?d00001 diff --git a/public/skills/icon_0_sword.png b/public/skills/icon_0_sword.png new file mode 100644 index 0000000000000000000000000000000000000000..a141a92bb03e96f7c424d455d205aa4f3de6f3ed GIT binary patch literal 2914 zcmWkweK?a1AH6Y|TA3Q%YI<(Hx4LOGSsG1EtxQcV7JD)pMvGO8m6ef?DV~Ojw1k#K zzOqzWsHRkkN{A^+Mf4?lC@;h7J=b;4xy~QI>-zn3uIr>Qceu}=yJRi^0P{WQG_P4F z{-0=rS><*c-vR&wfXQHOnT;?2;6XG2#!^Ti2LR|)h($-_03f4d>j0od3XL`hWD;h1 zE)Ro2u6dsQ!5ecPIX|5uxp`l@PbQFz3 zVNn2xMuAu~9*2c7C8wx8VO+`cn%)Nfzd27$Rfi65X&cEIW$-ZgM11sAz*k6f{>1mLqHJ|NhZPqCRIkl z2qC_hO_NcnQVubf1Q#M;Asr{;5^Oc2CO@pGP{c5wWd302xjBC(b?QO`?4T>6cDqUZpPS=1! zq0neF27|$3u{azK1VI=NLJ$PQFoGaN1R@fNWFkVE1(`x4lPDAlgF>WIsWd8uMx!z5 z6gG{_U@*803W?2OGMOwEi^JjYIZQ5>OB0GDJSLyd7YGDm0aqv#ibNubM3NQ9lS@VA z$wIkYo|`3&%Tg#a{yu4hkRyQ{{Yqi?uW?f%jpALw_;?Oue8V8|47y}V75D6rb z@c={v0X`ay!r`zWh{NM?cnAwaAdKJ;1R@|XL?99gFo}qeNdzK=N~MtKG%}MxI|_^K5RkE|Y{ z1?T`_#i;L<>ibJdDn7v#-rV`1_R06=?&#UH9VB0nLz3asg^O8WfF{YHZ22y*?7-ik+dH4exkgE<1C{$b;7 zT+BI!kyTN^98z#&waBm2@ZE`5H%%>VwS(Df%>KI2K5ZK#3Cnf+W9?nqXtY;lktkbp z%I{=uOJ(&J&lW%Dj!Sypn7Ms>TmhRTQ}vq{RL8X~%FZH3qnv|YI`caVP|W~#SeHpj|}ASvjfJ2Hx09Yfa& zipXG8t#zO>^a1l)1@pT9$@l;)v4CPo94e^Ym%E>k*5R`}JK^i-{c$a*WOOPIc?Qfx zT4^M13%OAQtw(feRFEb8{Wpo^ay+uYd#8WYD6HZ**rb*>vUl7I-+)3m%+Sa2tceLq zTs$h;wm9SCE>9s@RMe0|+9GlL`Xao)r0l-#)lMlY_Uf4c>zxlh6X|se)BGgXmOh_OOG+d3 z%r>cqhcX-F`JFrGNpDB@ zZVqQX`75a4uP%@4HJ-6yp#dL5GW}w)X#J%%NgZ=Pdm>hMZylj6-YzLaL0-!oBri)I>Z6te!#ud`bX37Sy7JXIjGS+PW5uwW6tG-aWl zOOeZE%CEflS2rzLkeWDIJyvA=at3v3WjWtb0dIE1vwf}E$DNc5tydW?GV7Q8KAm`K z%Vulh@r;jd6pZ*c%$hKJeW?0!+rizSzfs9QpTUGwYrr)k@@(8DaZ?}WG&a)avj*ZV~pp}&euQ06+5WHzQ&PGuX`>{rCovO%V%D#D7*67Y1rcZ z^7ky)+lxH!WpsZJUwi3xNB@c7j=oBF*#WahH4>OF%&WMV50)5oOqJxliJc zmnj6*^0~44dE&av*EcM-MA_XQMulkUBZ>d4>6$Dw2sTLg@z_sj6+iKJs>{Ai z;Be&Sxjx0|>1F)88D(76?PLFqu_1NCHlrRbj5#N@ufOBoz00krG&4vD@|v)ZtqfCd z`cRdK(pL~&j2Gs<)sMaDpY(WD>%X{j46pw3vl%Fy+sLNh>l18Gh>D7grhMjc-9uWl znD(qN_Pmw_6MHYVLAFDGXTPYuc!9VvJ*}qI=mv-Fyx+U-!M`q9w)~vyk;jx6+NHKr zZ3#yft6pku=FAMcmonr)FZ}J^QSE%)VaM9$H-Ao6j2Dvoh(4GwH!k zUjkZWv07iu-RK%P^}tzDb36`5Q@U zhA3|Fd6UAMpPXZFxs=m4?NvnPBv*7!-mVoG21P5*pQNqZV>N6(M1A0fFRjnpT-J1^ z((YQf$;<8R>JahKkN;)_WgK6o(mt!vTiL#HI$#j%V5<#_=`(iqd%9IM{(ZQuc+%58 zKVtfO$IF3=uBpjG-@?v&-}~cOvN6_8y~uNS$oza0(|shpTMtup7Vht-U*c^@@dQPe@h7G+Q{v^J+cGYI#dtu>M&`LP2Ad)-`<|#CTpL zn*Ml>1_hN={8*k{M_ZQhH0=j)rQN`Mr=4raM%zU!7O=c&%C#vjj=KLJrwU#Dys0be z35Kgq&M?q~h@;>h^Fy6-6(Q?%k8E*lP#Al^!O0?5T}XZD2ERwqV>YpGQc{r>28X-R=fkt5$%=45)fE9c8;-#0@8bM9kp><=F0GdjY$zMgu!y~ilFvhuWS zoC@R5yvikF0v}GUx)saLmIaAjD_cJq1tsu&e-qM=uHNo^Eninq9j)NBllDH!{t?N4S-^ zM{n2SyNW-GG2D#$afW+)|CkfMzjo{M)(YF4;uBM2Ij+Hlk7P0bp-txNPCHE=zc*OZ zE>Ik@w}|@6U>wyvTW49}uxqs$$=kU#eqBXbYKFYpSZpIzt5LgipcegtHEd%|eWt6T-01N;Ylf7%+5&?jKN0W#kje_R_0D}&(8E`xR$QfV-0JO@W$$Ft& z%2Fx#SS%U`f&>BqCK5;_5{*ixQmG6EgTrF*ITQ|uBj(fie7;mHlF4L>w50m_`pL;j zGztZx06ZFn2hju^NW`KbJQl*^NFbU>0AUDAMWe_BkPPGKI4l(g8CVn*CNLo^Lc-BW z5C=pvC=fz{*)V}iz;TIaHWkmN5`}nBKmxf4QAETGXhbOq%V&~A40I%n7qj44GEvB) z%PCk9B#>|rIh`)!k`)wUHjK|^;N*O|Ttt-%SOo~BmrYHy^8I_@=aN?Or1uRQr@Jg6? z9U$)J%3GyMT`U$0&*uSvL&YH=Q2^8UFf3t$kyNIPOip6(D8$m`-u zTKVyPk;?9DNGFqhRVsDyB|45gUZBjD)zqi8w%=Fl)RU7n%EO zTc@i>p-^Zv8jHn(Ac(`^@OV6tfQKN6NF>5AOokycnM@_alzC8T2$e#k(U>$colZyS zGz39d3>pWaGMP*slSbiiSu7Tt&E|5s0xpZk;~^rkl+O|f1VW)uBIJogBC%L3l}eK$ z`SCKbDpnL9AFoJ~MJAOfmC6EDno6ZADJfB_)m>d(YPD`)U_b{%f;cpefW|>+JP`|# zun+}LrV;=M!2<#`8im7wcs!0kz!4yj2;qq^4u&BTOoT{eGKokb!&C~1Orz6j6b3?N zF%d3@$>XvGe9o_5zf$K~TIPR1PrTpb06^Dv`2heL=Y}}5CI9X@7{Ow9F5fZpE+S}! z={KLR4+>9)YJ^^L@RsPH-&N-MPkakKZRsqQ9)ckW051Dbzc-@7TTcEmw~p z)I|$M528&$_lITyc?afpKj&<+)!g3*CjAJT=?M?t+9nR`tq6a$`oD_smtW@TY(iOY zhV9Z9ogcn^5_Kt5(LB9%mlk(+DV_E6%y7*9 zb0D|IQeTTwRx9O5a)5r0@%jI4mB?@mp_nLdSMVzgV%XJ+N=$@T=2;?ex=L zP7kdIb1!^h_mD#CtsB*9J_7-3a_Jrpk&-)PC}fIo9!b z(OiWEws`HcFNtY%y9hA#&o%30_E%kbr>nQ+m%eGbw~pD^wK$z>Z#Bt_>$gkHU#|aT zf~594F}Mxq>(i2uX!j!d&)A$I$7O@9)z8@!z@zP`5B$O+ydxvb!?97KZp}$BZUT8` zCA(YNNcOl$R?eER6e|X8^RzhF(D5ng>Pl_65XBd+VH$Z0+cVo+6tp`_!%Coqf9&q4gvq%cy2KZl?aP z8szd?U0>YtE2+94+QQ<)=MtyiZcV+u*TciQ)FCNkF3`v4UG;!>p+u9c(R6p8>g4gh zjq5ViJZ;CSEBOx@<;S1Cod`6#5c=g^<>Agk*JbL^z(H==_sWLOU+jXge)m9YQ|SJ! z1NJ4NayQ45b7!Qw@vc+;YyS>;=tU^Hiq7o)Q70Kbu`!}!{N0X=%qo;Sa2*-S7#ka4 zcbUF-ffjtzUC+YjJrz}*52$OZ$&YbX{t@iGvK|a`9~Ls?XR991?vxCsgIUL`+2>Ug z3I0`4Q&UU-c)fKZG!HOWW-J?ep*L6vmM$p>BsxtQ?NB5<9^^O>EQg9xIx}siwfR~T ze{>HnDIImY4?b>4+|l6YnLLF# zLJ7%yuxi(AJ!WXpg031TQK7}i$wNnq+loh~KMxmJShsMZBnu6b1!Uir$g;-}7((2W~C-#;CTixe*mTQrglxb~J8% z2zIsGp1njnm*c5>i^lI?$$R{!WZ$RA``1Em3V+S!*EjxPtTB>VAH=-P%xn97VgImG zU*x&U{vba6-G-s{gE?k#hb~`UO9_;@taMOl16)_k*p8#lSVT2u+{))TP8FBy(w^V( z9Wuy&`pL)A1dLWN?f<3++?YP9irQ(#wp%NM;%-wnwZ>hJLn=R;xa8&-Un`GabM0P5 z7Cph+(y*&4w`i*U=wb!UY49`Zo`3nvdV8L{F~MwNyD5g(Q}OUY&&h!JyN07n-rS7U z|MH6PKBWKb)^g*ob`9KAow?H;1BZaMaml>BO{yh5=A0_(O;^VomiKU&M1Z=A=9IuK9YE zp22Wy`^@USqiJ@8KH&Q2GojuLTHhA4vnj>hbAOHUmlb^36lCW9@Z;{X$x}1F(1UB* zid%|Kqe0{vcD%H_2a)(MUiZK}t}2%Fw$0oj*LANZ0a@|nxrLL$&Hc;6toMd`+PN8X z?YPA9uyKjFZ07O1qkE!;DoY-}@!T|bH|~;E*v-bXS>;=Bt+x!$5$G=GH|I_}A2Ki4 znX%NcKeP#6N#Ae0QIPyOV9f%#>ziFoJsBS*<^(k4n%_z}7rU{A{Ct2Q$$A9zEji{?p)WZTImo-v+Dsu>iR3 L@<1NB@S^_*NDv-; literal 0 HcmV?d00001 diff --git a/public/skills/icon_3.png b/public/skills/icon_3.png new file mode 100644 index 0000000000000000000000000000000000000000..a9118d034c683a0606e2e0f7e376ad62c3d73fea GIT binary patch literal 2887 zcmV-N3%K-&P)wkN^OTND#aL0MLXW^u%n8d{C^MWDE=l6c7*`92_Vl z94ss>JUB2oI5&fI6kH?-R5%z^I3#Qs5NIqATtp;rBp7HsBz!CkWK1k@NCL^QNaAeeAi zpm;#MU>ux;NW@fB$ZR02d|0rENXUFt;BX+goK*ZQ0KklJ%$QK*gfQT6aL|}|_=r60 zpg7pLWZiroE&h3I7py0SddIayeNd2NMN{382kWq++2*%e4zY@3=Ak37)SsBSU40! z5F}_QJY*;+cuWw0I81~vFqBAOj5u7NP*AK)Y^+pxyd+@QWO&eIkl=uz+@v7kc4!sh;WdQkerl+fRwnPprEj*v!oQ$E~7K?_2 zF(>;?bMR|=D(|Gg*3A}OJOSHkd<+^HbI`vl86meXR)VU6#y}9lFgf&{>j8ZM>an9t zAZP^&McKB}(Zho^NyC6|7dE&mYX`Oz{G7&vegn3!S&a?{ zRqnwn?Xq%xRCZmJ+a;W(CJM{pD+9C#hN!oqde~*h>+T&KT?z+iB+TsCS{m;u4u;=< zs&Z3x2&Gc+8VoxtY(#gYnpU#zczg>dU11?}a}yqd&^G=t9?jP_UsO(g)ofWqO(A1j z$R=)tgdlf%@7b?Pu$vK(a=wjE6>I9Uy!RvW5{$(z_>nMbuG z_iU>W3!CZkevKI+j-Tbg>o}KhZH^Uag^RU?4&#nBObc`;V;k+ktZrzS-%J}pT1zdL z(bI>A|-TrwhPy0%}E_b2{>TyEy|SAYmNw4G zb?6pp%%hSMTalyGI>6E15mmsPujYva9Fl*#%IG}rjUF~yw;@!s2@*{z2M}1C1$>Zt zp5vMdg$=u=2q~qQoAz0|*W$OcNiH^UVI?_L*0|SK>(q@5H-YZK=NVT8#Dz1i7`*n? zeM>FgI^%<9S%;jfm04tdt`p?UdZZXZ*RJTp{{7E9Vxu1BTvmyQxzV-CaT@p*d{ewKNAe3y}#GuUElf4V9a z`AEwKnWP6bhwn*WN;l**dEx$p@tDy%^JMZYbmT-YdkN`+uzuYX8wM?Crs(Zc1h0tS zl2^Lrw12V3zj(Ct#`IRSy}>#=r#rgw!!?HtqfU(lz-7mu1>Pffi z;79FqOic$ddu+29z4DR^m?BONYI4yzii0K8;5QAq#Ov*x*?C}J(9gMPQbwl3T%PvZ zx?P81D6V#>79@DHva>Qlga)CZz1`c&k_&e3VdAai={Y%FB$;Z>)tt9Y_jtf(HYEo8!OSPQ*l!AVqR!i1A3QxwVj{rm-eUox6zunk>e+Ca zDH!bSt1qewF(-dHMK+mZa^hcz;UUbUA~B7$Iz=__1Y7No3lq#`ocFE1MB$0Q_N9li z{3SD)@%MhQAnh|v*HlLj4Xsg&NsEw)HzIm+xCVV_y#$Y^?fa2=?CNMfj?BI!s?YpO zJ8`Fl<9HX9lC=1Eo#7Bz^rN^uJXRswesx8?r991zx#!vAdoLxvS||zijgkQ**owL& zqlDkew2!KN2kkT4XYdc79+G!{s(bgWgZBOlsxocy^@M6m8h%BMTC6|`NBZ3nd`IrU zZLx2$STIjI^tSb~V_?xMLaO?n!2Pb%UvLkSlQ9T#73*x_JK_Ad7OLx*^7}aLSWnNmQdS`Gd>M(=}Bd+Y@zDwqo1~ z2Oo=XnjZAtuSdNr)nL2tACUd`wKZkB?$n*ihyAJtlOk6951T;FZlLL-ZrqK?<@$&I zfnytuP^{FteH1*voUpYRH+YQq$R_01Q@x-um4b(Y#Mq zk)&-nVwerq0@ato<6=EMC2ozC!}3LV_08+tx2^Klmd!rXT6@ntvCabL)Nod8T&Ip5 z202?;A7D@H9mTzo;1jTW5hB$sKuZwzgVzP^VXzt>S5yW|-KmpTcx46KY?oO)h5hp& zC572xLW;6#P!u7{vZ0O%-^qwF;7=R?c5e-nSA&U-dStgoaPzXrG9?~{BuYVv5iCDa l`tOJ=f;x2+MwUOv{s&W2!AP%a9QObK002ovPDHLkV1iLM5HbJ& literal 0 HcmV?d00001 diff --git a/public/ui/icon_0.png b/public/ui/icon_0.png index ca65c842d65db01a51de18854bd091f3a38ceddb..a141a92bb03e96f7c424d455d205aa4f3de6f3ed 100644 GIT binary patch delta 2110 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 delta 2004 zcmV;_2P^pE7WoyhJOh6Rr%6OXRCwAYmS1cW$sNbPJLB11+aZJ`5LIa*RP?b@g%pXG zdpX@p)hfLcXa4w1;2v73LOMbc$Iix1HgUZ1+BgZ9hwCG_CdVbml?INZLZ^GF)Q3v< z)T&a;2^t-=IS3>HJL{dD*-oxrd1q#|^XK>de&#o;;mfud0{njx`0VZ51Ao+@y>shV zSOcK@fJRDbRSn;{7g5DDrUI>7E_YXQ&DYEr0M^uB-Bxf)6%^E$3{8TQSfM9Bub^R0 zLHk@pjQz&+2VrTP8_dHQxx6=+Nq@0se@#An` zipiCn4XOJAY~g=0z!IoqenP_LHf$6PPsX&)L*nX;?_6mQo^3@cR>$t^N^}=UFJd@0 zd*#J+@@J%FkLd_xKr0N!_Z_CD<^BWi_REG5+5b!mSEP=5Oy7-_GHe`kL9}hhYboT60A^)3U+r#Q8yb)r7gSf|U$uB%JWy^dXp$ID2PqY$nhWFf#2@8(at zS}jW5AYy-#<-SbVaZt9pme?@wL5ri$rsQY|6#kGfDTnHm=TBOy2$Vxa+PsM$uIl^p z`C+lW1C#46sW+q}(!^XeJX9FP$UajXjEl2A21sf|#A2PsxhgHVPlSXs@B1W0&(3Hf zOEWJ_1GFrp7x| zd7ISG-PnFQVMv@I%zY97bHX|b&n@YU#$s$Xp7X>AKiJpa0@DYe-5m|0s%BFNIvg)# zC{;R|kw6vTZEoo2i&+nTZ|{E^9iuEfLqegcQH901WofRVtA4sEf~vhIXe$w(ojY4H zq4R%+S;->AZ50DFa6xk!h%hrEN)+WQx@Z|y7oaP=p17v4Wm}J%l1EKN!JR?em=gMd z*INwS6bcFK{d~?GOD)gIPiD-L%nN4*c2ey`?e1ml)9u(Ib~a!Nhmy4J({f-bpDJfp zJWIE2;ph^9$eTrSQn7@P`?9&!Fguvo8=ZgplqMoh*>&}mghY`&TF;CIV;SShKSBkc zc4s(@GCQ)=dgWv|Lp4AKZY1$>iY$+;A8VMj$im&Ua$CvhkmQ}+kdh=G3W|rPs0sY#|mIH<1H+;qqkk=LPaa8QGHa=r2v9n7&b9YsIG+>-w8vO*|ULq;&&fX z&1d?U!ds4Reqy1N=nZtL`!0lSpTDV+ju3l!!KTqvqZRG&hzIHA%47n+C&u6>#r z43pb_V}{TReXG%AUp;@ZvU5`@lQSxe+rdMJO^p9PVj@~36H>Z(Z8>`}t7bUl*`;eu zdzt#)Ect{|>aa#;^*tdb+T!sJQ{^=#pA2y-IWHwdmOzwj{iBV|)31XRQYiRo%Qb1M zW<`S6=Kh{_w@4p@I2UKnXzj^W%_ovxSO7h-bm;xqkr|xb^8$bBT(oF7%P$N1MvD2p z^w@9an>X~`Wu8!Zsodv1efl!mQ6J-tAJl~N=wBjTn0-7wnfCdm4S#irL-=(vkxvh) z^HSIDrRq8QDwrR6RwkN^OTND#aL0MLXW^u%n8d{C^MWDE=l6c7*`92_Vl z94ss>JUB2oI5&fI6kH?-R5%z^I3#Qs5NIqATtp;rBp7HsBz!CkWK1k@NCL^QNaAeeAi zpm;#MU>ux;NW@fB$ZR02d|0rENXUFt;BX+goK*ZQ0KklJ%$QK*gfQT6aL|}|_=r60 zpg7pLWZiroE&h3I7py0SddIayeNd2NMN{382kWq++2*%e4zY@3=Ak37)SsBSU40! z5F}_QJY*;+cuWw0I81~vFqBAOj5u7NP*AK)Y^+pxyd+@QWO&eIkl=uz+@v7kc4!sh;WdQkerl+fRwnPprEj*v!oQ|nkGRYjved-ZW0pwk&py7RpQUU3+bAS3X&AE0Vb3`ttegBY1+hAb?rt)NCOFp z6Z_tK&e?I&q5(aY?>(}f^Pcy4&hx&<1Q+w%u<-)$&gRoqX9&oaQ!il{fNlbWMjO>s zZaM2vuT~teNs$;RN;J>UY0fHOSbf!F5Zly?ftfADR_+g%Q709a~z@3CXmpwyt|onua^hY2`6YW&g{(Y zR=d1WU$JWC{-(|-!cy>rHAgbpZH70W9xPnuP1}2CJ#HbZs;DApz`+~rJ583>pox;1Cd zdB6YonNLegORrxm`QXnl|8!mX51zg}cK%Yy2gHm1w)7Wlpk`&?(XTRv3rZ4Q*t*y~ zSwN5*Da(TKxhI=tEM_}gbf7dO3s;Hk%A?v|s{~og3W5?*H+gjlkMd`9_I~FlS#a3u zgs3pAc|1C#FK=nt%V+Ka87}<#FW$fQ6MQoykUzv$dH5?Ps;Iw#yWL)AZ8vL=-QWVT zsaubJ_=cmh;vOE)I8(ZYDR;NplTt9HylM4k4d6SL+7~DCeYk&=Z*nc~A=7OGaGj<} z6s2drj5r0hQ!RGG_Dh0?2h&#+321s1a_7J@KvrWXnbr1oSFtSfJ-ooCA9r?Z78%@3 zB_}q(!O`S5~q+<`gkW*Ha7 z#8+J|O)v7~e&;;Y@Ga=^alv2Y2H8^O0vX+d<+|bS#oY^td{+ZQcVt>q`ex9zW6^cFOd!i~p<+x4&o>T4$ebqzMH(k!V2sxr z9`w8kf`GiRNK;6;QT*wgHTEpiSjF!JvWUrT8pZJd)%j-$D;w|K*HOsCtt*;BFD8D@ z;tHMGMD8Z>>LF-?4=WC@<;kd0(T=C3GdZ_|$*4+iwTK+%;K?k`mMy z%!s$5<)+Eg(W{s4N2e~y4ZmfQ2RV2KTXixj#cQQt(R7eY(pRQcT$GD6DXBn8QKHtr zEq^IuVP%D0INfSNZL*yk4N3_N&>Iqbg^7aDd*;$68B=K_~3|h<+#rlAhC9C zDg!GU3$7KLh%1#wL}03a_pSUZi^0c#^arPF(`8?3Vg_Lb&N2!AV`@RKa2~=kPZILccS02f2q+FD_0Dplp=jwf@@uO;`J6> zLQ-*%=?&Ixd*E2wL)p&KA#KO&?DAy|^1^vv))bXDx4!~sWS@Ylb5^zrm3>t=qq;cv6Ml0#aSNAf(LLKkPcCT`SQOjuTQD{mracXxYw z_Ef8mdc1M+k+`xwy5XP*JK-pR>qCX>c2*hG>@^%0-}#Dn17hP?omXjDDU2Fvf!$U1)T z0NZazEWVt5dYlq}Nt}?n;solFq_za=Z^Ct%Q~`oH|Ms=T$$BGqQ7_)sF!t9bYt{63 tP#V!3=)pWLyU{SQSFgW=^U!oL6j002ovPDHLkV1mNGy7mA7 literal 0 HcmV?d00001 diff --git a/public/ui/icon_0_sword.png b/public/ui/icon_0_sword.png new file mode 100644 index 0000000000000000000000000000000000000000..a141a92bb03e96f7c424d455d205aa4f3de6f3ed GIT binary patch literal 2914 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 L@<1NB@S^_*NDv-; literal 0 HcmV?d00001 diff --git a/public/ui/icon_3.png b/public/ui/icon_3.png new file mode 100644 index 0000000000000000000000000000000000000000..a9118d034c683a0606e2e0f7e376ad62c3d73fea GIT binary patch literal 2887 zcmV-N3%K-&P)wkN^OTND#aL0MLXW^u%n8d{C^MWDE=l6c7*`92_Vl z94ss>JUB2oI5&fI6kH?-R5%z^I3#Qs5NIqATtp;rBp7HsBz!CkWK1k@NCL^QNaAeeAi zpm;#MU>ux;NW@fB$ZR02d|0rENXUFt;BX+goK*ZQ0KklJ%$QK*gfQT6aL|}|_=r60 zpg7pLWZiroE&h3I7py0SddIayeNd2NMN{382kWq++2*%e4zY@3=Ak37)SsBSU40! z5F}_QJY*;+cuWw0I81~vFqBAOj5u7NP*AK)Y^+pxyd+@QWO&eIkl=uz+@v7kc4!sh;WdQkerl+fRwnPprEj*v!oQ$E~7K?_2 zF(>;?bMR|=D(|Gg*3A}OJOSHkd<+^HbI`vl86meXR)VU6#y}9lFgf&{>j8ZM>an9t zAZP^&McKB}(Zho^NyC6|7dE&mYX`Oz{G7&vegn3!S&a?{ zRqnwn?Xq%xRCZmJ+a;W(CJM{pD+9C#hN!oqde~*h>+T&KT?z+iB+TsCS{m;u4u;=< zs&Z3x2&Gc+8VoxtY(#gYnpU#zczg>dU11?}a}yqd&^G=t9?jP_UsO(g)ofWqO(A1j z$R=)tgdlf%@7b?Pu$vK(a=wjE6>I9Uy!RvW5{$(z_>nMbuG z_iU>W3!CZkevKI+j-Tbg>o}KhZH^Uag^RU?4&#nBObc`;V;k+ktZrzS-%J}pT1zdL z(bI>A|-TrwhPy0%}E_b2{>TyEy|SAYmNw4G zb?6pp%%hSMTalyGI>6E15mmsPujYva9Fl*#%IG}rjUF~yw;@!s2@*{z2M}1C1$>Zt zp5vMdg$=u=2q~qQoAz0|*W$OcNiH^UVI?_L*0|SK>(q@5H-YZK=NVT8#Dz1i7`*n? zeM>FgI^%<9S%;jfm04tdt`p?UdZZXZ*RJTp{{7E9Vxu1BTvmyQxzV-CaT@p*d{ewKNAe3y}#GuUElf4V9a z`AEwKnWP6bhwn*WN;l**dEx$p@tDy%^JMZYbmT-YdkN`+uzuYX8wM?Crs(Zc1h0tS zl2^Lrw12V3zj(Ct#`IRSy}>#=r#rgw!!?HtqfU(lz-7mu1>Pffi z;79FqOic$ddu+29z4DR^m?BONYI4yzii0K8;5QAq#Ov*x*?C}J(9gMPQbwl3T%PvZ zx?P81D6V#>79@DHva>Qlga)CZz1`c&k_&e3VdAai={Y%FB$;Z>)tt9Y_jtf(HYEo8!OSPQ*l!AVqR!i1A3QxwVj{rm-eUox6zunk>e+Ca zDH!bSt1qewF(-dHMK+mZa^hcz;UUbUA~B7$Iz=__1Y7No3lq#`ocFE1MB$0Q_N9li z{3SD)@%MhQAnh|v*HlLj4Xsg&NsEw)HzIm+xCVV_y#$Y^?fa2=?CNMfj?BI!s?YpO zJ8`Fl<9HX9lC=1Eo#7Bz^rN^uJXRswesx8?r991zx#!vAdoLxvS||zijgkQ**owL& zqlDkew2!KN2kkT4XYdc79+G!{s(bgWgZBOlsxocy^@M6m8h%BMTC6|`NBZ3nd`IrU zZLx2$STIjI^tSb~V_?xMLaO?n!2Pb%UvLkSlQ9T#73*x_JK_Ad7OLx*^7}aLSWnNmQdS`Gd>M(=}Bd+Y@zDwqo1~ z2Oo=XnjZAtuSdNr)nL2tACUd`wKZkB?$n*ihyAJtlOk6951T;FZlLL-ZrqK?<@$&I zfnytuP^{FteH1*voUpYRH+YQq$R_01Q@x-um4b(Y#Mq zk)&-nVwerq0@ato<6=EMC2ozC!}3LV_08+tx2^Klmd!rXT6@ntvCabL)Nod8T&Ip5 z202?;A7D@H9mTzo;1jTW5hB$sKuZwzgVzP^VXzt>S5yW|-KmpTcx46KY?oO)h5hp& zC572xLW;6#P!u7{vZ0O%-^qwF;7=R?c5e-nSA&U-dStgoaPzXrG9?~{BuYVv5iCDa l`tOJ=f;x2+MwUOv{s&W2!AP%a9QObK002ovPDHLkV1iLM5HbJ& literal 0 HcmV?d00001 diff --git a/src/ui/hotkeys.ts b/src/ui/hotkeys.ts index 1778dd9..01edaab 100644 --- a/src/ui/hotkeys.ts +++ b/src/ui/hotkeys.ts @@ -33,19 +33,43 @@ export interface SkillIconMapping { } /** - * Resolves authentic icon mapping for skillId (Parity with 1.13c Skillicon.DC6 frame 0). + * Resolves authentic icon mapping for skillId (Parity with 1.13c Skillicon.DC6 frame 2 sword / frame 0 fist, frame 6 throw, frame 4 unsummon). */ -export function resolveSkillIcon(skillId: number): SkillIconMapping { +export function resolveSkillIcon(skillId: number, hasWeapon = true): SkillIconMapping { if (skillId === 0) { return { skillId: 0, name: 'Attack', - iconPath: '/skills/icon_0.png', + iconPath: hasWeapon ? '/skills/icon_0.png' : '/skills/icon_0_fist.png', isGeneric: true, dc6Path: 'data/global/ui/SPELLS/Skillicon.DC6', dc6Source: 'data/global/ui/SPELLS/Skillicon.DC6', - frameIndex: 0, - atlasRect: { x: 0, y: 0, width: 48, height: 48, w: 48, h: 48 }, + frameIndex: hasWeapon ? 2 : 0, + atlasRect: { x: hasWeapon ? 96 : 0, y: 0, width: 48, height: 48, w: 48, h: 48 }, + } + } + if (skillId === 2) { + return { + skillId: 2, + name: 'Throw', + iconPath: '/skills/icon_2.png', + isGeneric: true, + dc6Path: 'data/global/ui/SPELLS/Skillicon.DC6', + dc6Source: 'data/global/ui/SPELLS/Skillicon.DC6', + frameIndex: 6, + atlasRect: { x: 288, y: 0, width: 48, height: 48, w: 48, h: 48 }, + } + } + if (skillId === 3) { + return { + skillId: 3, + name: 'Unsummon', + iconPath: '/skills/icon_3.png', + isGeneric: true, + dc6Path: 'data/global/ui/SPELLS/Skillicon.DC6', + dc6Source: 'data/global/ui/SPELLS/Skillicon.DC6', + frameIndex: 4, + atlasRect: { x: 192, y: 0, width: 48, height: 48, w: 48, h: 48 }, } } return { @@ -72,6 +96,8 @@ export const RIGHT_SKILL_BOUNDS = { x: 635, y: 551, width: 48, height: 48 } as c export const DEFAULT_SORCERESS_SKILLS: readonly HotkeySkillEntry[] = [ { skillId: 0, name: 'Attack', nameZh: '普通攻击', level: 1, manaCost: 0, leftUsable: true, rightUsable: true }, + { skillId: 2, name: 'Throw', nameZh: '投掷', level: 1, manaCost: 0, leftUsable: true, rightUsable: true }, + { skillId: 3, name: 'Unsummon', nameZh: '取消召唤', level: 1, manaCost: 0, leftUsable: false, rightUsable: true }, { skillId: 36, name: 'Fire Bolt', nameZh: '火弹', level: 5, manaCost: 2.5, leftUsable: true, rightUsable: true }, { skillId: 47, name: 'Fire Ball', nameZh: '火球', level: 8, manaCost: 8.5, leftUsable: true, rightUsable: true }, { skillId: 56, name: 'Meteor', nameZh: '陨石', level: 6, manaCost: 22, leftUsable: true, rightUsable: true }, @@ -117,6 +143,9 @@ export class SkillHotkeysHud { ]) availableSkills: HotkeySkillEntry[] = [...DEFAULT_SORCERESS_SKILLS] + hasEquippedWeapon = true + fistImage: HTMLImageElement | null = null + swordImage: HTMLImageElement | null = null private iconImages = new Map() preloadSkillIcons(): void { @@ -128,6 +157,16 @@ export class SkillHotkeysHud { this.iconImages.set(s.skillId, img) } } + if (!this.fistImage) { + const fImg = new Image() + fImg.src = '/skills/icon_0_fist.png' + this.fistImage = fImg + } + if (!this.swordImage) { + const sImg = new Image() + sImg.src = '/skills/icon_0_sword.png' + this.swordImage = sImg + } } getHotkeyLabelForSkill(side: 'left' | 'right', skillId: number): string | null { @@ -304,7 +343,9 @@ export class SkillHotkeysHud { ctx: CanvasRenderingContext2D, genericSkillsImg: HTMLImageElement | null, font: D2FontRenderer, + hasEquippedWeapon = this.hasEquippedWeapon, ): void { + this.hasEquippedWeapon = hasEquippedWeapon this.drawSkillButton(ctx, LEFT_SKILL_BOUNDS.x, LEFT_SKILL_BOUNDS.y, 'left', this.leftSkillId, genericSkillsImg, font) this.drawSkillButton(ctx, RIGHT_SKILL_BOUNDS.x, RIGHT_SKILL_BOUNDS.y, 'right', this.rightSkillId, genericSkillsImg, font) @@ -356,14 +397,59 @@ export class SkillHotkeysHud { ctx.fillStyle = '#14110d' ctx.fillRect(x, y, 48, 48) - const icon = this.iconImages.get(skillId) - if (icon && icon.complete && icon.naturalWidth > 0) { - ctx.drawImage(icon, x, y, 48, 48) - } else if (skillId === 0 && genericSkillsImg && genericSkillsImg.complete && genericSkillsImg.naturalWidth > 0) { - ctx.drawImage(genericSkillsImg, 0, 0, 48, 48, x, y, 48, 48) + if (skillId === 0) { + if (this.hasEquippedWeapon) { + if (this.swordImage && this.swordImage.complete && this.swordImage.naturalWidth > 0) { + ctx.drawImage(this.swordImage, x, y, 48, 48) + } else if (genericSkillsImg && genericSkillsImg.complete && genericSkillsImg.naturalWidth > 0) { + ctx.drawImage(genericSkillsImg, 2 * 48, 0, 48, 48, x, y, 48, 48) + } else { + const icon = this.iconImages.get(0) + if (icon && icon.complete && icon.naturalWidth > 0) { + ctx.drawImage(icon, x, y, 48, 48) + } else { + ctx.fillStyle = side === 'left' ? '#5c2818' : '#182c5c' + ctx.fillRect(x + 2, y + 2, 44, 44) + } + } + } else { + if (this.fistImage && this.fistImage.complete && this.fistImage.naturalWidth > 0) { + ctx.drawImage(this.fistImage, x, y, 48, 48) + } else if (genericSkillsImg && genericSkillsImg.complete && genericSkillsImg.naturalWidth > 0) { + ctx.drawImage(genericSkillsImg, 0, 0, 48, 48, x, y, 48, 48) + } else { + ctx.fillStyle = side === 'left' ? '#5c2818' : '#182c5c' + ctx.fillRect(x + 2, y + 2, 44, 44) + } + } + } else if (skillId === 2) { + const icon = this.iconImages.get(2) + if (icon && icon.complete && icon.naturalWidth > 0) { + ctx.drawImage(icon, x, y, 48, 48) + } else if (genericSkillsImg && genericSkillsImg.complete && genericSkillsImg.naturalWidth > 0) { + ctx.drawImage(genericSkillsImg, 6 * 48, 0, 48, 48, x, y, 48, 48) + } else { + ctx.fillStyle = side === 'left' ? '#5c2818' : '#182c5c' + ctx.fillRect(x + 2, y + 2, 44, 44) + } + } else if (skillId === 3) { + const icon = this.iconImages.get(3) + if (icon && icon.complete && icon.naturalWidth > 0) { + ctx.drawImage(icon, x, y, 48, 48) + } else if (genericSkillsImg && genericSkillsImg.complete && genericSkillsImg.naturalWidth > 0) { + ctx.drawImage(genericSkillsImg, 4 * 48, 0, 48, 48, x, y, 48, 48) + } else { + ctx.fillStyle = side === 'left' ? '#5c2818' : '#182c5c' + ctx.fillRect(x + 2, y + 2, 44, 44) + } } else { - ctx.fillStyle = side === 'left' ? '#5c2818' : '#182c5c' - ctx.fillRect(x + 2, y + 2, 44, 44) + const icon = this.iconImages.get(skillId) + if (icon && icon.complete && icon.naturalWidth > 0) { + ctx.drawImage(icon, x, y, 48, 48) + } else { + ctx.fillStyle = side === 'left' ? '#5c2818' : '#182c5c' + ctx.fillRect(x + 2, y + 2, 44, 44) + } } ctx.strokeStyle = highlighted ? '#e8c26b' : '#584a34' diff --git a/src/ui/hud-manager.ts b/src/ui/hud-manager.ts index 013df93..8c9637f 100644 --- a/src/ui/hud-manager.ts +++ b/src/ui/hud-manager.ts @@ -940,7 +940,12 @@ export class HudManager { ) // 5. Left & Right Active Skill Slots & Speedbar Popup (`spellsel.cpp`) - this.hotkeys.draw(ctx, this.images.get('genericSkills') ?? null, this.font) + this.hotkeys.draw( + ctx, + this.images.get('genericSkills') ?? null, + this.font, + Boolean(this.inventory.equipped.weapon1), + ) // 6. 4x4 Belt & Pop-up Belt (`ctrlpnl_popbelt.dc6`) this.belt.draw( diff --git a/tests/hotkeys.test.ts b/tests/hotkeys.test.ts index 35c08a6..487cb55 100644 --- a/tests/hotkeys.test.ts +++ b/tests/hotkeys.test.ts @@ -144,4 +144,114 @@ describe('SkillHotkeysHud & Dual Slot Mechanics', () => { hud.handleMouseMove(attackEntry!.x + 10, attackEntry!.y + 10) expect(hud.hoverSpeedbarSkillId).toBe(0) }) + + it('includes Throw and Unsummon as default universal skills with correct usability (Issue #376)', () => { + const throwSkill = hud.availableSkills.find((s) => s.skillId === 2) + const unsummonSkill = hud.availableSkills.find((s) => s.skillId === 3) + + expect(throwSkill).toBeDefined() + expect(throwSkill!.name).toBe('Throw') + expect(throwSkill!.nameZh).toBe('投掷') + expect(throwSkill!.leftUsable).toBe(true) + expect(throwSkill!.rightUsable).toBe(true) + + expect(unsummonSkill).toBeDefined() + expect(unsummonSkill!.name).toBe('Unsummon') + expect(unsummonSkill!.nameZh).toBe('取消召唤') + expect(unsummonSkill!.leftUsable).toBe(false) + expect(unsummonSkill!.rightUsable).toBe(true) + }) + + it('resolves authentic D2 icons: sword with weapon, fist unarmed, throw and unsummon (Issue #376)', () => { + // Attack with weapon: sword (frame 2) + const swordIcon = resolveSkillIcon(0, true) + expect(swordIcon.iconPath).toBe('/skills/icon_0.png') + expect(swordIcon.frameIndex).toBe(2) + expect(swordIcon.atlasRect.x).toBe(96) + + // Attack without weapon: fist (frame 0) + const fistIcon = resolveSkillIcon(0, false) + expect(fistIcon.iconPath).toBe('/skills/icon_0_fist.png') + expect(fistIcon.frameIndex).toBe(0) + expect(fistIcon.atlasRect.x).toBe(0) + + // Throw (skill 2): frame 6 + const throwIcon = resolveSkillIcon(2) + expect(throwIcon.iconPath).toBe('/skills/icon_2.png') + expect(throwIcon.frameIndex).toBe(6) + expect(throwIcon.atlasRect.x).toBe(288) + + // Unsummon (skill 3): frame 4 + const unsummonIcon = resolveSkillIcon(3) + expect(unsummonIcon.iconPath).toBe('/skills/icon_3.png') + expect(unsummonIcon.frameIndex).toBe(4) + expect(unsummonIcon.atlasRect.x).toBe(192) + }) + + it('renders sword when weapon equipped and fist when unarmed (Issue #376)', () => { + const drawImageCalls: any[] = [] + const ctx = { + fillRect: () => {}, + strokeRect: () => {}, + drawImage: (...args: any[]) => { + drawImageCalls.push(args) + }, + } as unknown as CanvasRenderingContext2D + + const mockAtlas = { + complete: true, + naturalWidth: 1152, + naturalHeight: 48, + } as unknown as HTMLImageElement + + const mockFont = { drawText: () => {} } as any + + hud.leftSkillId = 0 + + // 1. Armed attack: should draw sword (sx = 96, frame 2) + drawImageCalls.length = 0 + hud.draw(ctx, mockAtlas, mockFont, true) + const armedDraw = drawImageCalls.find((c) => c[0] === mockAtlas && c[1] === 96) + expect(armedDraw).toBeDefined() + + // 2. Unarmed attack: should draw fist (sx = 0, frame 0) + drawImageCalls.length = 0 + hud.draw(ctx, mockAtlas, mockFont, false) + const unarmedDraw = drawImageCalls.find((c) => c[0] === mockAtlas && c[1] === 0) + expect(unarmedDraw).toBeDefined() + + // 3. Throw (skill 2): should draw throw (sx = 288, frame 6) + hud.leftSkillId = 2 + drawImageCalls.length = 0 + hud.draw(ctx, mockAtlas, mockFont, true) + const throwDraw = drawImageCalls.find((c) => c[0] === mockAtlas && c[1] === 288) + expect(throwDraw).toBeDefined() + + // 4. Unsummon (skill 3): should draw unsummon (sx = 192, frame 4) + hud.rightSkillId = 3 + drawImageCalls.length = 0 + hud.draw(ctx, mockAtlas, mockFont, true) + const unsummonDraw = drawImageCalls.find((c) => c[0] === mockAtlas && c[1] === 192) + expect(unsummonDraw).toBeDefined() + }) + + it('allows binding Throw to left/right slot and Unsummon to right slot only (Issue #376)', () => { + // Left speedbar should include Throw but NOT Unsummon + const leftSkills = hud.getSpeedbarSkills('left') + expect(leftSkills.some((e) => e.skill.skillId === 2)).toBe(true) + expect(leftSkills.some((e) => e.skill.skillId === 3)).toBe(false) + + // Right speedbar should include both Throw and Unsummon + const rightSkills = hud.getSpeedbarSkills('right') + expect(rightSkills.some((e) => e.skill.skillId === 2)).toBe(true) + expect(rightSkills.some((e) => e.skill.skillId === 3)).toBe(true) + + // Assign Throw to left + hud.assignSkill('left', 2) + expect(hud.leftSkillId).toBe(2) + + // Assign Unsummon to right + hud.assignSkill('right', 3) + expect(hud.rightSkillId).toBe(3) + }) })