From be0c401aa3e01d4781a72788753d3574833f52f7 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Mon, 14 Feb 2022 03:25:05 +0100 Subject: [PATCH] feat: wip on adding support for mana system --- assets/icons/official/combat-values/mana.png | Bin 0 -> 17660 bytes lang/de.json | 12 ++++- lang/en.json | 10 ++++ package.json | 2 +- scss/components/actor/_combat_value.scss | 5 +- .../shared/_form_field_icon_button.scss | 11 ++++ .../shared/_form_field_input_extra_slim.scss | 13 +++++ scss/ds4.scss | 2 + spec/item/spell/calculate-spell-price.spec.ts | 5 ++ src/actor/actor-data-properties-base.ts | 2 +- src/actor/actor.ts | 12 +++-- src/actor/character/character.ts | 7 +++ src/actor/creature/creature.ts | 7 +++ src/common/common-data.ts | 2 +- src/config.ts | 4 +- src/global.d.ts | 1 + src/handlebars/handlebars-partials.ts | 1 + src/hooks/init.ts | 21 +++++++- src/item/item-sheet.ts | 3 ++ src/item/spell/calculate-mana-const.ts | 49 ++++++++++++++++++ src/item/spell/spell-data-source.ts | 5 ++ src/item/spell/spell-sheet.ts | 35 +++++++++++++ src/item/talent/talent.ts | 2 +- src/settings.ts | 12 +++++ template.json | 9 ++++ .../actor/components/actor-progression.hbs | 12 +++++ .../sheets/actor/components/combat-value.hbs | 2 +- .../sheets/actor/components/combat-values.hbs | 2 + .../item/components/properties/spell.hbs | 36 ++++++++++--- .../components/form-field-icon-button.hbs | 16 ++++++ yarn.lock | 10 ++-- 31 files changed, 286 insertions(+), 24 deletions(-) create mode 100644 assets/icons/official/combat-values/mana.png create mode 100644 scss/components/shared/_form_field_icon_button.scss create mode 100644 scss/components/shared/_form_field_input_extra_slim.scss create mode 100644 src/item/spell/calculate-mana-const.ts create mode 100644 src/item/spell/spell-sheet.ts create mode 100644 templates/sheets/shared/components/form-field-icon-button.hbs diff --git a/assets/icons/official/combat-values/mana.png b/assets/icons/official/combat-values/mana.png new file mode 100644 index 0000000000000000000000000000000000000000..4758ebc73d54f4757239a406defd923a8a3f7c7e GIT binary patch literal 17660 zcmajHc|6qJ|37SZAqk=I?mo_(04EFo_@p%}|3OZF6|8YGv> zGDMbXT(VV6!j!>%M%U-_{r-OU{dnAec#Jvg>%6ve&g=C&lX$`0h?iTGn~8~u7cw?L zGcmE0GyeYK042qd4#nVu%is8N5EIkh*Ni{r9>2<~pzvU@p>430pGR=0YoI$*XlSUS z=MC>5H&=gmMZdsn1>aAJGBF)uf(*`BhduZW;xvgBA+il2pYQ@Vl9yn9+AIBNtPQJ0`M9lg(iA)U|ojG$F=S(GP6 zCf35T@Q(2yYMn!vNgs^pCBS3liP_#879A`Ai+(mHi<57GdCOPO9mAcI*r47uj3M-F z(s<4PA2UFi895O@+{wp$_dN12-cr5dcWd`(sbG3OLC^}XAIT!}IZX>%Z4vz_(kidX zC)>_Gz-7!!6_7WP=JinS=sLHUj4VgTDB`rK_i<+&xW0-O-p(up%b2*?RboBp|2^3` zA>+$cSGoRg_vhIVO|L$K{$rPOO@bm_f>~g@m&cU`#pO|D&%%G1p(Ua?>s2`c^*2mQ z1n&=Xiy0vgxnBc)ecc&tT?2wy&^Lv_*f&zlPos}d94w5@ECjr1N#1R4WB70LZ(nz( zk^-gmt9=%O6Y8utUUG^ccy9=Nlx)?~=9fKb0j8YfrORn^K`HCL@+M_kfTpcqQD7d? zZ-szHGr(NDeqXF9wi<9_ShJILZ>srOFBZ^odxH4|lF>2v3LwZT?26o)-%+wW`Qf|) zLam!bm?9)Gw9-7Cni= z+rtKcF=PzK1fS*!ZGOlzikY?vbhrG6q?M;;0c$6d+&RK z7}4LOOz@&;^o&s=s)o$*PJd|a3l}SlL_qMJMlF9uYQd}z)uPKziL|6Ze4jH$ z3AGLa{TkOKUk!1plE?#gEq82O!2MA$aLcioWYGg1Nl*r>)&MZd1+4d9~Z%BFA`Ik3MXkOlyIzW>@Nn4O`y24~8TL;K{XJL$f*Mq! zami{j`zVC=esPz^d$Wz^-+SJ4b;Xi0_d1xG3a4ZvWM2tXai*4Nj7_z&>RVNXnfCs2 zO*&4Mje^jo?Xk2J&}Rv>`@Nz;d>^+!c_uBU0HJYQfqD}G5n!xn%{W^42QmH9{011& zG;)|AO3|47p3Al_H+J1qcvC>1Gs2D6pE2wBhgH)@c2Q9orSBx-l-{1BLl&>qpMNGEAXRA+Mh&u0-3G?J zkTeDrk#>!~1Hz_DzI;!l8Fh!jyq=fzvXr`hE3EX*BiiKIB*hAEcX!Y9Ht}seSgbw> z-@0nI4;QeuJizr+Q(K!kCB?`P`C+CMm@mBF_b6GLqU37c)#YArd~3{|n6`m$W&YTD z-CMf9SI|m!N-nUy$V17cWgamc5t-rrAezPYVBu}m@4y?0Zy(pjN~Ya2KV7}?W}YqL zYC+tfZT*R?z}8p?KF3q?+RRN;uS*5z13COHfs+M2PsCKHYV&?w@sk6z^@C{rcIL_t2Xlu#;^Kw3?UwerfRQBT zJ{KIrsede7zZJ)0`${tA-=GaDbwN!sHVZhz zHVrUV1y70K{F~shi{Jc<=h?LDqukBVV7amt#{pYE0$Db_QwIxikSvVF@q(pmISLh? z?#X7%-3+LkSp**5_suKox4o~deY4zApa~bF*`z?>X`}2Rg(joyS3ke}r_n+)m_r_Q zvBe#XIq}~y-DOc4NkGOK?kM*>H@MA~F@PYLr(~XOSo?DK@FdWr@%!=k%%OZRfbe<1 z_+FKA8L;lg!wh2OixG}&C;!GUa9!&s` z%$LA|DcXNEELj|GGM&*WQ2P5*#gX$*)E5G!qW~=loLlTUR zpv(bjor7`%OEoz*bmXvSJF38=+)7c+zFZeMl%@|!><;EWuIk8Wryr=YMavleeCOzF zrsvS#)~L!cm2X;k7&^50rqS5gPHCuofkDUb^o)g`V%%O0aoGySfLG4;XM4Wy{W)Xt zA7a$aM!87RL$+`Kk1`9?ML>+~?!>$F{eL(6x2r1P$yGZ9qB>`Rs5&%j1BiZ_>6r>G z)-f0roImr2C3#UbU2y&T>z` z-}p$r;My(=vH=VD^dZN3c>!2Gv+P7j;RVNf`WqkyIPi7)kGJ*V9Mpgc* zzgH$7WQyhkO=`hnvw?VW+~wW#?BNKYr{V5nryBMPdUY`9oq&g515@bhxAJv!mGitL zTn>MM%W^L;hy?VZ88lv2e=py4J71~K_N___VhwQrU*6!_uKuqv6;KyNdSKjTU`ydZ zJAMC$3TUtr%$uJD=(j3Q0rWrm(x(EnR0(pFM@jcf|62g~h68^ORD2&?PU2E4c7q0= zFuu#9Naypq(dAk)cm}PqL*;q+-2030e^sH;(=NA4@qabxHM8=~#GlB@1O|wEW{au> zm9YLc?)IPI^Yg&)|67>?hpffiM~q{dwzj1qt#PzjFryxT+L{7 zDmOc6rToYPnCZlSrsEz$MDUJe-d|8A9E@P`LOp+HFSs8f@x2o2Dc#)4&HdmfSO9GD zh!k|Tl#cKl^jU^?yR6fGf8@c z=dogV#giUfctt^C+T8b6A)2PY3pi}S!~1!#NY-g~nZ)Tzz}Z0-O3!MrGNi^?%r39U zI0VDv2g6wX*{KEf@%Z!7-mc>kqiDxxAFD4&fstRakhut>v1&v=!q%OQlE;c>$50hl zcvmixsbRHE2)v;>M=IyLqnHK>#6;%BoOm$z8A=-Aajehv*#Yd+wwf%5VyL9gkaT5cEZWnp~Pu~X;;a3gy~F;Z@gzHoker+a&I<* zJ@;Y6&irMfUqabyK`WBGGIi!zFuiEe)o3mSfp5j%`l&(mi!J45c-{c8Ac}+h1!*W= zo~h_u)0lx@Sm`||iUzG+K0T%MAS-E_`8_cl0ad+Z>0Z9wsFTe%9Y5GUVMcFF{mTM$ zV=d4=?7L`6T}tY^^VJAH3XXhBZ)mzJ@=?Yn@Nk9Ofwa5%zZ3m<$8ylMeuDZnCy56M zrFXuX;zM4{8QLwMChsRdC&b+f>ZRB)&KcrXS?93JR@EAOd)^tsnQv+ckhM}@s-9u5 z@Lj&C>cPgnNQ}d^WHka3>v(702138~Wq*-OjXvO3)5^9SXSF30o17>$eb7ysG zyfsuBN@-AD5hZ(2G)n%aj-S!7&40a=AT^ygbDn&Wa6ay49Rh!_h}pq}-kqUa!>TS- zmsa-7$6T$uq$hlxCb-hbylD$(e$*pqBu-z?7f=#^Qc%_*VL{jQGl?o*{EPw=C@@c% zg)CqC`DHLuGvr}W_Lt?y;M`kI@RBGb{C^T2--} zNvdNd9k^G-X_kDE5Q!~YT@HlMP!IFyzD`S3g-gtSJ)?uirp^;ODSUlrC~{=k-3F64 zAO5~~{b%=w#$2|JJrjQ0j*3Dd`&NXiltpzd@`Z~zn~P&|OyNcUQyqwyjsR)WXwCY; zhn;1X<2j2dqIv|)&Q^Zs4H@WY)5#7G54`{l68=X9){ymQ%YIL0f3}qX@?OfEl@+Cc zPy@;kcnd*7UqH|za)d>H>E|eXM%&)xx?q)!bRowR8BCFT+AKk=GAR;5r>t{ePSej8 z>%L<(XSyj?nN#-VW8Pxqk81+@%k@3)W4Ab$JRo!nJHZV{vC z{3~itZTa8Ap{y|$A+x|FVkNMC{GAW4rL0z(-+N)LbTJz}+3`b+GIwo+CRx|_P(mJ> zSs6s{}UT14v_xn+&kFXtW(=-}Iy zA%Aq$LBlGSjn{^^>?k5*37VWdX#7H|YZT8gR8_Iw{>>OYnl~GC`VKJ?~`wBnO}s_zGvWm2%{&H_qY3}s(fE;48P2~g~W%0sF`I1Cv|Dy z*i2m5G#*FHW^q1>c`APK35VX^uhvF$kk^ZEl}Sw`Vy#7Kk{`4E$Ur!3T@j7tNpMCO z2{|*bKQ5a?H1%T%F@l}uDRS>#TjZIT{G)vDeM`9#o1~3J3*_~z6dRQ2+;W~@!>awU z9elQ3+}4m@VypeVBtqmdpE{JE)&;!%r$px-lOC_CNMC8se}pY3oe#F}%T9&JLxgv0 z!38+w5GisHvDULRA&qj*5dKTjC9a*Wh%mt+3Yp`l* zHMUr=b3SX2(kD|nPkz$u)~5vLWs~j&Yd^XBxFhQ&Th7|Q1TcXXgV(?Ad0;|svr$oG zTCaTk8sYi&5g~-A6j(;luCmx;GFYjbw`f&7u;hYo6%M=L+b-?-RpQ+9;$yn(AIqDk zVB9Yd*uf4$lu@EU0Mg%w(ZEN-BGQ>NM0Vxl?=}$A|-iaUuSGq0#EE~udhf*ZkC@QmFTQt zO4MO|RZu?@l7FE6dR9UPR(dTY3nA1ZyK;sy`d)x)S*2?Pmp3ud_$VTe+h#b(vTExY zh?Maee9?_GCyS+2LHxQT)Rq%ZfQU-yke<+$pb~i8HFEqHsr~(h=-|^ZlkJ|UbXphw zUF+S&J~<~DcvuEFBJ3=&wjrcjq;mZT5+@_vz#h~q2!lI4mPl}bz1KzKW-h2?c;VUaWo3_?kLz8luv5PcDnGP7M);^ASQoF>QabAfI!D>XRj=-s#D;1lsF2x&ASrRh_y?vyQfDdmEpIMlKq{*t77DikkEUG zl&6JQXTi=6KEaGIAv3ScQmhbBN%A#uj@l;$TfdUAUXD$5n!B5rMNkIKala*4CeF}H zl&_;${GPPiAfQQ^SdF2t$Gux9k6FuxZ!*^)Fi#JtUYapomG;a`<-Ek-xy>hP!}^>w zbsWCa%gr6qfytNgSz?pA$98aa|3jz(|P)l6W1Sb_*`Pt-f3i`2~#(T52+=fcF8kRIA!Q@^%oS<>t6PpoJY}8_OcV_3v&vU>nj#=I`@bF-&^wfk9g>~* zW}d%dMMhDOTZaOUOyw#H_Zm|dN=_o6M^gs$ZI$U(R?SL-YsNDkvi{7}jED~E7lFdVkjZD~ zgz0Nv#6+0;OoH#In&;CbpKS;&a6xF9C~(}dy| ztBDq$`+&&B71;Dl>3XWgt>Pcc0ZG0mnWM63kWMj81><22caNPV@26~({vvvYWm)7~ z4NdWHn7IU`7u`pw^>!0N6Q^^&2-YeL3ZhNLIi4KBl&p(gV=`q>hEL2!N0;hF#4KuD zVl~>oz=i+Z%v^H>1FwsTuznI@tSaLho5Oj!9?8!ETJyP!Q*=Ir30xP`VA;%C;DFFP zUwJ1^(=jB!sAnh*rrvYF(=6L>b@TKE#|_IzGPMZmAKpw< zBMiL_jzi$%i%?CW2FuP{L|?)U_qH=$ci0JO3B~uex{oLSyoJE`JDs6#llMHMZ=h>QN%ba5uQ+&0K13Ds(6t8Q)JkO3kP)4l{hDcOS8kMO+Tr zBF0;{OheVfnH2*1)>W(sXpwe+y6LF@+1_*5gpcKtp#IA*Nz2U1+-q0_WF^e1kHAb9 z9KXcOcML2d!H|$gw5WMTF>!g$SRuOh8}@PH^v;(g=b7XU(vh9hbX@-NOEYF*iyF{L z@^G+>GQ;n^f8JAye1n1KG${qp3Y}&c05icS1^v!x(&JHNNA)eqF2wI8D2jX`hC1 z;StAvId{aIl$@``njv1&sXDeT}`ICHcaxX$IAhOYN3XO5{6k`s&N8@dqwKKO1^ z*gB&J{q6F=<`*9-{YrI?S6wE(b4BmMZiZ#)C~>)t*XthHS#V~#GM^(x!H z=x`X4hU6B_h|8r7T{|EvqZ(sz7=!*>Sf4|9zjr&x(J0^|xb-6R3l*a3Y-Q!y-brK5 z{=GkxIZ~KWTl!SPUxT{2hzi&%$s&u*5zs&AkvzJa+&Q|&LD?XUQD}i?IbWK&zC^H~6v^BO3 zvNW9Z-~e4xdo7GeiL1tnB~HgwB$Egov-ye_^~~rl#j;Lzb(1i{{U*w53+&H! zQL1u2J-`~>rYYE|2^_CJPvz7niFQ5<5%1V&kOQ0foE?BbWo~Ahh6Drit_P zj#G|NjV{mC?%ZU)v`@dTBH2dBrv(9-{jj5K%r0DEC+U1Bvx~~5ozZoqOWIm9XjSVy z|IgNuVNS+`SBM{%K!9^5J=fro}z{Ho_`#jdIE>oud(4`~XgkKe={iyz|sSct+9^lji z>eL95(qh#&!}O_fO&Z_XniX~j^$`Qt*p1bxFh4Nguk7^I)}2B|ns4wxm=bj}+;4(g zUUuxd+NT{E%>DIlN5cA_%8%Fw7W17?u9lK~uJP-ij-1)q@B0)1&CJ9RmzUZDA_jyX zKb1t$$zM|Bh!X7>H46^n!e#z^}*iY~=w*{LMgS3(-VuB0vKW zYNl*}>sXHTKaumE(im{Z#kGB7T%e1WQ|+<+ChBdnrN-VWB=5R<)7vU8{)Q~)jqBLfDIkg}* z#Z+DUd4Rrhv zW@kC|y@>DA!SP#*eI^5}bvkrms$C*m&%X7Od2Kt<_(kf@a(r{KERLM-1Wn?LM<2yf>RU}v>D z9&2HQ95`xUU2=9EF>UsaQ1WrIGY+;04prP=QrXVIcj!q&Fhzm0L$uH!hl>o9;@+U9aCu&oB11mG zN@MF^e43KI>TfRwtNgab$knh5|Dm(i5ZnVtxvZYD*aCSc-Wy6>cxtWmp`Wr!7-uYS zu>A>h#{n>C3bw}psVp~Ro$H~){r)EYG>o`d|7~E*>j1CkVbV4I(zKZh%9TLf+klXf z0w%@79ZtDP1(i3HueS$iDR1$(6oBPNrFuOC>KQd+IT@JKrA}}KbiQicUv80V{?MrW zr6L5Y|1%pU6eLOQS+SySzVgiiA6%)Us56W!2f;2$u*8wh>z&`?7nli(Am7>>T$=>sXkXs<>p4rL5z-R>@6&i>r z%>b{0%2S-vPr5nkSG{(E;(SL($Q$d+$gv}Y>RCx~7Y5dd*5Y=}&jQ9B0v4klUJ51Q#W(=-vEa< z(Q0>s9cAfW2q8ZHQ`KPwR+&VpV@t6OJS`VtM3-g`P1{Z4B6gzcTwSx{+Zd+sDb@Q6 zmCgW=g6xy*WNLK|76|cjbImu*K64y`7qdLm-dk_(srHRu*aG=N*zS$_XfdkVwL>`9Vq*kU@Y3;;R`6dg6aqC=jCQd`-K!6O%tQSBsqDPpofv@Qczw#e7 z;ZIBo+5&Uz(PAJTa3vUE;XT5H8w1dAnOVGNd8B;(o=XsHeZIV1&x%(yQ(dS$PcrIv zG(&*+N|cR}eNeGy0rwDt9$P5Hsu2Ns)vjVM0vUY;Lpg%kzXSd6`Ne?2At1En<2S3& z2YIy*ALLO>ISbdpTC+0T-%kUuL|bFoiAj!+a@vV|mzA;*o~N#kt+q&8NT_Bu?=Qb> zZhCmsDgsRUKpt8C0k#?fg?ZZt=5`I~JY@oC+f{9xm@P;xM1wTB?Oumn*DadpUAex! zM;;~E8+au`8IxoU;{u-UZAI*I=fj0@kc<75P-B_Ss13Dw`cLb_Ei`ke*Z8N>|j~5#QlM!^Q(;JeE`8Q0#cmNVC!4! z+#CFbgcJErxr$(?nYrX*$k;JtiMj+|Ap5IYjbg?_JVWw@F+mL0%35dJ5d?=$U*;bA zw7Y#gsvl+%F2~jfW3p!=A$b2$u!PwUHx7ZNy-_&;jP0KMUF*wUrC8hCk)wSsfD+|; zakM)=g=3IGUO-6p%VQ9HwauK$nlR1P1j6&GX-xmzCR(GQ7`zxqpW?Q$lF~ zo1Gb9h!9;V|GvOS;NKMh$LB+hHSU*-lwa1? z|CgX)mj8>w{}#BJ{kv*Ft8Xgn(-YN3|0TG7`27Da2;Zk|>Z`hsXNZxLhgGKlW1Qad zfjlt;t1#B{XUV~q!7LJbbf9}-4JWG40M_;bVdPJx0j1OAVHls-F3lI*weWuj1_Ez! z%Kg|E{TAU#u;7HTEd9Gn+fLl*$V_j9-PXHleQ{_8hAhLV^zk~sNDzFL#Q-%9w>Y@o?>Uz2f z=&1~NT9nK2cJS9_4A6D3WG}3I)4*xqg3@Qf#s(#&m!0SjR14o-bMW@1KSJ+Ntm0rW zA$WWJPl3*Pg}{ecI4eLcc8;$)pRa7Cy%G~m2>_BFFh%q@mU<$FNSmSPJS zz$cYsU_-m^uJxb|Tcz&x1XSFgd!ya=$2iM4-k=3}pYQ@VwTaSIW_V3^JTvfiZ+;kk z)Y_fw3UFE4qFV-fz>+*$b{U*72}IDU2pA09PJf_m%*Ou|zX`O}s8a{dV*9V>OvwO+ zg#dXn{_q=feg9SfXY}D|LX{>s^vt-~{1~=kxG(pGH?9uCU=su*@AfbP05-)Ga~Fnx ztYsSkhsdXqe|+ps2IJKHLxKffdjWU2!1teu`WIQqp)-?&@e8?Ui%OUNGZN?~kz`#SkiucS*0Qv-ko9>sL&@HYK_Id#bV~PQYvZH;608`xEweA5I%iI5| z5%Y-^Y#NOHc$^CW(h^fwO9d-U!ET9)$vn49v7zoU0K&VxckYgdt6le_KYOssw>9B% zF2_AjlFUnHUj8Yo|GIWO))bsb7~M(MU7j3y0D=v6fA_H^B?f;jbHLVof9_bMP1W6p zz*siF8W&mIzlM+Ya&@&c&pwI)OQ{9m!Vl6zC)Lk++q*{G1R|;zr<%gJ2>0=s7yw!; z9KV!X1nf}60&E;!6{yIl$J|5eZNj!WhiZ;bUI4p9pdg4pj~-l#eazhw^3V8908+zf zeP#N(yVq8cu>XV;e*F#jKhh2GFj5Nu_y|a;R{8cV?}qCWL&jk$28W6RCdaEZG*yKE zGO*|SIl8Wt)j+!0OkkAc~q7ckImf)q7R)Z{m zb^IxBBYPcK%1I$THZadgiE~jB%0iE^W$p!H75zCs{5apGn3tHAn=*m3O-$bUN(0{9 z&mP|&FZ)j$=g}dvp?`}}O=jO12Y6;$IMB*`4;wND?6aP0o2H+fa<`d)`#0!eTlx9t zaDOV!i|@S)@5;o>^0CCP5yY%cH z&zx^E?M*f>=?g6pJl6jzxKWr1Tzbxe+v@BB;T5$M znU?Ojk((rTFpahA>53MGbJ+R`OS9bWO%~@{>Et2hsDbLLc&n~)Hvs?Q0xmQw zNR!$b)i$Hjz1#zWzn2vlN~$-XdKbh+&Vnx>d=k=Qxf^=;y>^|lbnAO7OYX68%GK^~ zjW^(^oKQy$PG}FJ?%IIx@oHy&eWvF#94j7=6 zLn9Rg8q{8$1({q_tWacf4DFy#>|e*NP-2(ewVBpn$nj?SZD$HaB}zSP|BMGa%j-{fy)06uKn)3m>O4LPx89|l85cte`80rSlx#&@o@;;lVDWh7 z;5`tgf4JW5<+8jRh3r~CI_J62nFFGKuKIyf2fhhLGNSjU#O0axr@|jQL7Yy~8HpjR z13<*eZjX3Llcu}pL3Mx^2>dyw%3uiQBKmS4Js@~|esed_m=0)I*Gi_H(Q!OgbF~!s z`gA0Jr=Nw$HAa+ift|=Vu|N@bVZ20uhdFp+8xp5mDw5~+`?T13Df??t<3iiQT>n%m z$uNSXimbvy-c5GjLO_sofr56)*o_DGs!K0_9`pu*TZNWAt$DOFryO748F6c`dcg!n zbhaS2Y&O3-V8w&N`6D<$qt1SJOoI*f??*5I`#NP?Hz_J;&jhd|xfRLp|3r#2sqVj* zV(qaCJRlzYPc@NmK97>lcte3MJ(Wa@4lk$8-TF3?J9cX^$NcBRBE@%I6`Kgij*XrQ zD%)ClEle*qwD9ZKT}!eN-VHskcu_WH@+b=IpQ@Fc)ckd*agl8xO#g_HT`=)A`r$P8 zv(dWiMI4P;&nl{UH|+iuWb8iYQK}hTT6jrH#q($MtWN#uu>cX5J5P2g5H2x-?#qXg zCFf6e%@SacN5OrVK?HPZU$va^>^jgx+#hQh|b_JI0)AWbMvhTV>3ryf~Z{-zx%EqTWii3+Q zcr-H579ygTUYWW0=%;b1vt--N+ln<=Td>&)_}H+UMg$i(6w9T|gVS|89wa%!yBc2}oIN zjR^tV-_=~%5f&q%-NZ@&+q2?Vonjx`L3-pDO@x9Z`UG&*HQFwLbd5KczcO`m!H4<< zB!0elo~)`>G_*(>qY!uVKu;_LI@8{TFg1FDRj`JsQw7~>!kobWAOIn_J~{)^Q=+f< z5{ktex_k+efGh~zzRm`o_JGt;hYd&_-QSDYcupE&?&MIHW4tqB~0y>Q8>kM%OdSO_v-%P}a{-qQyfD#c+2}J@7VXtFD+< zz^G|vqG?exuGh4RgKnZVAH@lR#l+6nY!bw^GIZXc^kJ`e=#y+r6D zF2@CRd6CH=4Vg0hDY5v%7SWStdI46|=7|M(vYj0ed1 zlp;EpkW&{b`EdR`vBd)Vy}uI+V%r%hV$XZu%-7al&PUf2)7b0kjFpU`uj~^L{pu2O zJ9e5m8$r4zyAny+Xu(|3s(756 zr8L9~17L?MWupNz^V~YJNmc#kqqek@5kJ<%cC5?AfL|u}6PMk#x>E?zfPl;Qo zo%NNuUf@828@BuEPF!|f9a}15B#%94`>29Xr)}c|FTul|NObhulMy?M8dEl)?#ssm zG@uKhcCYv)91TI}ZS4>Zuinxq`gD==ZA>#)O`G#Fh zoG$(%SS$~+{5HC3G*W57TjsZE)cRJ%1ElK>G1dzH&gH)~I(X#eKS-gMe0d$t z(h<$z))(UKWf^g>&Ub6_a7r2{OQ@iJ-kjLe&;+LCyChRv@n;(^QxCVGhEQ&`FU4$M0oJ; z&F~TC>C)d}u?T!#HVeqL>mNjzmqk3ZMfYc4i1w6?fcXTjk$LCfVFP<+@Cr=oAmUwj z96!EtGIlAU_-h;h3`8#i$TIq{&fo-Vx;NI50bp+5%bqQ$zrdMj8r!~G+kcZ4yy2TZ zfcVZV=>#A!`MnL-r>>b+P(;bwYoUqYWV^{~Bhcx*M<9c+$`~O?3rk~n`Ou18{Kp2) z?0ZB=AubEXb?c8#OT)j{_$eaTGc$_hL`GlF(`n4WFU1^CHGcvSGW|Vg$;XLG9C6*E zm-TGpJ`3W3g&=h2)ajsNds|LA_s19OPU)(3 zzOex5Cw?cP@UiI&9^dJ>#>?j|P3ep~#0030yl7TbLXl8Gm%YE|#2z>#QNoTg1( z+CCAWM)IykNQeC8&q+=0IuXE87sS9o{chM~y2jr*#V6Rp%*jAOEpGf(g`6rCNpYok zMHOZuviwhg#P<^Y1b~ECgC6(?JuJr)oKM|JjEtQlfexzOMQ4IK#Wg|T9y!c_QR(cp zHKQLU0cNCO36NMCbgJJuVT8H3Gy)uaJu&JW+sU+$sE@g9 zPIf`l#@s6{M5SXP#P`@jukT`zR|9}y?d!yhT4rvC^hY0+{UDa;Y`d5s7Hc%}Nev!0 zNn@%xjG3%CI&}ebHX(l_GJs8Ns z;|!0b)l*TWZ>C|8nT=5x7Y7uerjPd) zNu~qI_IS~-G7r+}o-JYYWqQnef zd96#D3{sj$!L7`h&GgPjkx5mbu=NJqyp|a(;m@;D z*`|$xzj*tRqbMS;;Q)n|pP*pSHiE@pn!0qkmv*!+#!*5BiW?WDF;kPTEYRfZ9L=I- zJWm0srP9_O2S-VLtSZ;bz&%nZX-n6S2#}X(VX9>j9v?TtM%LaHs4qHD?uGE{Q-<>xY z)|*0Bptt`L-%Ae-J_6&$A8?B&8REG;H=|>dngnPp{(Pj!Z>JvR;5_U&UvP%8lg1T= zTpvBO3}+!lSJZT4lOGN#3rn$ltnb-xfX5*D>0<5qlmk^qLgv7ef4pl= zd7JLyCg4yIm1u1iz_n6L7$L;P1*PN4X-9zUUVxtiz=U0?l8F#rStc}thG=vPa|~yc zwl=?~9(WmNt;2+5u}B1hgn#W_L06g5(51AtV``)|f))OILEA}95$G-XM%CzPiDW~( z=vzUnrk6zoh>-p=+!&o%%%sGj$ouY`XcDsAE6y~wG}eZ^fX7h-HAtb~)4EHn6~IqC zjC+KVX9#Y9Nw`7G*$}0LEIpw~C(;EE=BjL`oSZ;a+|v2r{dfezx%20Lm{iNz~0&6k{F)xGc7w^={# z3H!@Q_WrOqaDDKOHTfRd0Y{m5sCGk=neM#%I|-h9MVMoF>4yLVtWPPETAvBf*!+3? z+3GBPv_a4!GB%V+iEy_-)dN&&g6;8hB?#xG<5H#2g|O3s!i4=V?-r}RQ)DkXy>=R_ zRGz(mL8XdQO)B0H-<=Cy+`23KpIQkdKLWqQAu4qZFc-=C_*PK*EAR+TY@A1vLv#N} zzK^LY8zqYD!a}rAl3oEb__2v}LSX*I7n9?w(~q%YcyojIY#lsw&tVA4+uCtHpfvHS=0T$J^0RR91 literal 0 HcmV?d00001 diff --git a/lang/de.json b/lang/de.json index 2c0dfd5f..55518a83 100644 --- a/lang/de.json +++ b/lang/de.json @@ -149,6 +149,10 @@ "DS4.CooldownDuration100R": "100 Kampfrunden", "DS4.CooldownDuration1D": "1 Tag", "DS4.CooldownDurationD20D": "W20 Tage", + "DS4.ManaCost": "Manakosten", + "DS4.ManaCostDescription": "The Anzahl an Manapunkten, die es kostet, den Zauber zu wirken.", + "DS4.CalculateManaCost": "Manakosten Automatisch Berechnen", + "DS4.CalculateManaCostConfirmationQuestion": "

Die Manakosten des Zaubers werden automatisch an Hand der Tabelle auf Seite 19 der 5. Ausgabe der Slay! berechnet.

Achtung: Dieser Vorgang überschreibt die bestehenden Werte und kann nicht automatisch rückgängig gemacht werden.

", "DS4.SpellMinimumLevel": "Zugangsstufe", "DS4.SpellMinimumLevelDescription": "Die minimale Stufe, ab der ein Zauberwirker den Zauberspruch erlernen kann.", "DS4.SpellCasterClassHealer": "Heiler", @@ -189,6 +193,9 @@ "DS4.CombatValuesRangedAttack": "Schießen", "DS4.CombatValuesSpellcasting": "Zaubern", "DS4.CombatValuesTargetedSpellcasting": "Zielzaubern", + "DS4.CombatValuesMana": "Mana", + "DS4.CombatValuesManaCurrent": "Aktuelles Mana", + "DS4.CombatValuesManaCurrentAbbr": "MP", "DS4.CombatValuesHitPointsSheet": "Lebenskraft", "DS4.CombatValuesDefenseSheet": "Abwehr", "DS4.CombatValuesInitiativeSheet": "Initiative", @@ -197,6 +204,7 @@ "DS4.CombatValuesRangedAttackSheet": "Schießen", "DS4.CombatValuesSpellcastingSheet": "Zaubern", "DS4.CombatValuesTargetedSpellcastingSheet": "Zielzaubern", + "DS4.CombatValuesManaSheet": "Mana", "DS4.CharacterBaseInfoRace": "Volk", "DS4.CharacterBaseInfoClass": "Klasse", "DS4.CharacterBaseInfoHeroClass": "Heldenklasse", @@ -311,9 +319,11 @@ "DS4.TooltipModifier": "Modifikator", "DS4.TooltipEffects": "Effekte", "DS4.SettingUseSlayingDiceForAutomatedChecksName": "Slayende Würfel", - "DS4.SettingUseSlayingDiceForAutomatedChecksHint": "Benutze Slayende Würfel bei automatisierten Proben.", + "DS4.SettingUseSlayingDiceForAutomatedChecksHint": "Verwende Slayende Würfel bei automatisierten Proben.", "DS4.SettingShowSlayerPointsName": "Slayerpunkte", "DS4.SettingShowSlayerPointsHint": "Zeige Slayerpunkte im Charakterbogen an.", + "DS4.SettingUseManaSystemName": "Manasystem", + "DS4.SettingUseManaSystemHint": "Verwende das Manasystem für Zauber.", "DS4.Checks": "Proben", "DS4.ChecksAppraise": "Schätzen", "DS4.ChecksChangeSpell": "Zauber Wechseln", diff --git a/lang/en.json b/lang/en.json index 19973936..0b3d5b3b 100644 --- a/lang/en.json +++ b/lang/en.json @@ -149,6 +149,10 @@ "DS4.CooldownDuration100R": "100 Rounds", "DS4.CooldownDuration1D": "1 Day", "DS4.CooldownDurationD20D": "D20 Days", + "DS4.ManaCost": "Mana Cost", + "DS4.ManaCostDescription": "The amount of mana points casting the spell costs.", + "DS4.CalculateManaCost": "Automatically Calculate Mana Cost", + "DS4.CalculateManaCostConfirmationQuestion": "

The mana cost of the spell is automatically calculated using the table on page 19 of the 5th edition of the Slay!.

Warning: This process overwrites the existing values and connot be reverted automatically.

", "DS4.SpellMinimumLevel": "Minimum Level", "DS4.SpellMinimumLevelDescription": "The minimum level at which a spell caster may learn the spell.", "DS4.SpellCasterClassHealer": "Healer", @@ -189,6 +193,9 @@ "DS4.CombatValuesRangedAttack": "Ranged Attack", "DS4.CombatValuesSpellcasting": "Spellcasting", "DS4.CombatValuesTargetedSpellcasting": "Targeted Spellcasting", + "DS4.CombatValuesMana": "Mana", + "DS4.CombatValuesManaCurrent": "Current Mana", + "DS4.CombatValuesManaCurrentAbbr": "MP", "DS4.CombatValuesHitPointsSheet": "Hit Points", "DS4.CombatValuesDefenseSheet": "Defense", "DS4.CombatValuesInitiativeSheet": "Initiative", @@ -197,6 +204,7 @@ "DS4.CombatValuesRangedAttackSheet": "RAT", "DS4.CombatValuesSpellcastingSheet": "Spellcasting", "DS4.CombatValuesTargetedSpellcastingSheet": "TSC", + "DS4.CombatValuesManaSheet": "Mana", "DS4.CharacterBaseInfoRace": "Race", "DS4.CharacterBaseInfoClass": "Class", "DS4.CharacterBaseInfoHeroClass": "Hero Class", @@ -314,6 +322,8 @@ "DS4.SettingUseSlayingDiceForAutomatedChecksHint": "Use Slaying Dice for automated checks.", "DS4.SettingShowSlayerPointsName": "Slayer Points", "DS4.SettingShowSlayerPointsHint": "Show Slayer Points in the character sheet.", + "DS4.SettingUseManaSystemName": "Mana System", + "DS4.SettingUseManaSystemHint": "Use the Mana System for spells.", "DS4.Checks": "Checks", "DS4.ChecksAppraise": "Appraise", "DS4.ChecksChangeSpell": "Change Spell", diff --git a/package.json b/package.json index 76898f6f..61ba020f 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@commitlint/cli": "16.2.1", "@commitlint/config-conventional": "16.2.1", "@guanghechen/rollup-plugin-copy": "1.8.6", - "@league-of-foundry-developers/foundry-vtt-types": "9.249.3", + "@league-of-foundry-developers/foundry-vtt-types": "9.249.4", "@rollup/plugin-typescript": "8.3.0", "@seald-io/nedb": "2.2.1", "@types/fs-extra": "9.0.13", diff --git a/scss/components/actor/_combat_value.scss b/scss/components/actor/_combat_value.scss index 40e30685..5e5a12d9 100644 --- a/scss/components/actor/_combat_value.scss +++ b/scss/components/actor/_combat_value.scss @@ -14,7 +14,7 @@ place-items: center; row-gap: 0.125em; - &__value { + &__total { $combat-values-icons-path: "#{variables.$official-icons-path}/combat-values"; @include mixins.centered-content; @@ -49,6 +49,9 @@ &--targetedSpellcasting { background-image: url("#{$combat-values-icons-path}/targeted-spellcasting.png"); } + &--mana { + background-image: url("#{$combat-values-icons-path}/mana.png"); + } } &__label { diff --git a/scss/components/shared/_form_field_icon_button.scss b/scss/components/shared/_form_field_icon_button.scss new file mode 100644 index 00000000..685d56b2 --- /dev/null +++ b/scss/components/shared/_form_field_icon_button.scss @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2021 Johannes Loher + * + * SPDX-License-Identifier: MIT + */ + +.ds4-form-field-icon-button { + &__icon { + margin: 0; + } +} diff --git a/scss/components/shared/_form_field_input_extra_slim.scss b/scss/components/shared/_form_field_input_extra_slim.scss new file mode 100644 index 00000000..59594984 --- /dev/null +++ b/scss/components/shared/_form_field_input_extra_slim.scss @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2021 Johannes Loher + * + * SPDX-License-Identifier: MIT + */ + +.ds4-form-field-input-extra-slim { + &[type="number"], + &[type="text"] { + flex: 0 0 32px !important; // needs to be made more specific to override foundry's style + text-align: center; + } +} diff --git a/scss/ds4.scss b/scss/ds4.scss index acedd839..a6ebcc28 100644 --- a/scss/ds4.scss +++ b/scss/ds4.scss @@ -15,6 +15,8 @@ @use "components/shared/control_button_group"; @use "components/shared/editor"; @use "components/shared/embedded_document_list"; +@use "components/shared/form_field_icon_button"; +@use "components/shared/form_field_input_extra_slim"; @use "components/shared/rollable_image"; @use "components/shared/sheet_body"; @use "components/shared/sheet_form"; diff --git a/spec/item/spell/calculate-spell-price.spec.ts b/spec/item/spell/calculate-spell-price.spec.ts index 56d49f6d..0edb7533 100644 --- a/spec/item/spell/calculate-spell-price.spec.ts +++ b/spec/item/spell/calculate-spell-price.spec.ts @@ -25,6 +25,11 @@ const defaultData: DS4SpellDataSourceData = { unit: "custom", }, cooldownDuration: "0r", + manaCost: { + healer: null, + wizard: null, + sorcerer: null, + }, minimumLevels: { healer: null, wizard: null, diff --git a/src/actor/actor-data-properties-base.ts b/src/actor/actor-data-properties-base.ts index 9aa9e335..7650b32d 100644 --- a/src/actor/actor-data-properties-base.ts +++ b/src/actor/actor-data-properties-base.ts @@ -24,7 +24,7 @@ type DS4ActorDataPropertiesDataAttributes = { type DS4ActorDataPropertiesDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBaseTotal }; type DS4ActorDataPropertiesDataCombatValues = { - [Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints" + [Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints" | "mana" ? ResourceDataBaseTotalMax : ModifiableDataBaseTotal; }; diff --git a/src/actor/actor.ts b/src/actor/actor.ts index ecad7f40..396fcf39 100644 --- a/src/actor/actor.ts +++ b/src/actor/actor.ts @@ -47,12 +47,12 @@ export class DS4Actor extends Actor { const attributes = data.data.attributes; Object.values(attributes).forEach( - (attribute: ModifiableDataBaseTotal) => (attribute.total = attribute.base + attribute.mod), + (attribute: ModifiableDataBaseTotal) => (attribute.total = attribute.base + (attribute.mod ?? 0)), ); const traits = data.data.traits; Object.values(traits).forEach( - (trait: ModifiableDataBaseTotal) => (trait.total = trait.base + trait.mod), + (trait: ModifiableDataBaseTotal) => (trait.total = trait.base + (trait.mod ?? 0)), ); } @@ -158,6 +158,8 @@ export class DS4Actor extends Actor { }); this.data.data.combatValues.hitPoints.max = this.data.data.combatValues.hitPoints.total; + this.data.data.combatValues.mana.max = this.data.data.combatValues.mana.total; + this.data.data.checks.defend = this.data.data.combatValues.defense.total; } @@ -166,7 +168,7 @@ export class DS4Actor extends Actor { * given in dot notation. */ get finalDerivedDataProperties(): string[] { - return ["data.combatValues.hitPoints.max", "data.checks.defend"]; + return ["data.combatValues.hitPoints.max", "data.combatValues.mana.max", "data.checks.defend"]; } /** @@ -203,9 +205,11 @@ export class DS4Actor extends Actor { data.attributes.mind.total + data.traits.aura.total - spellMalusOfEquippedItems; data.combatValues.targetedSpellcasting.base = data.attributes.mind.total + data.traits.dexterity.total - spellMalusOfEquippedItems; + data.combatValues.mana.base = data.attributes.mind.total + data.traits.aura.total; Object.values(data.combatValues).forEach( - (combatValue: ModifiableDataBaseTotal) => (combatValue.total = combatValue.base + combatValue.mod), + (combatValue: ModifiableDataBaseTotal) => + (combatValue.total = combatValue.base + (combatValue.mod ?? 0)), ); } diff --git a/src/actor/character/character.ts b/src/actor/character/character.ts index b8dd87a6..efa72092 100644 --- a/src/actor/character/character.ts +++ b/src/actor/character/character.ts @@ -22,6 +22,13 @@ export class DS4Character extends DS4Actor { get ownableItemTypes(): Array { return [...super.ownableItemTypes, "talent", "racialAbility", "language", "alphabet"]; } + + /** @override */ + protected prepareCombatValues(): void { + super.prepareCombatValues(); + this.data.data.combatValues.mana.base += this.data.data.progression.level; + this.data.data.combatValues.mana.total += this.data.data.progression.level; + } } export interface DS4Character { diff --git a/src/actor/creature/creature.ts b/src/actor/creature/creature.ts index f6ae06a7..9b91e9a6 100644 --- a/src/actor/creature/creature.ts +++ b/src/actor/creature/creature.ts @@ -11,6 +11,13 @@ export class DS4Creature extends DS4Actor { get ownableItemTypes(): Array { return [...super.ownableItemTypes, "specialCreatureAbility"]; } + + /** @override */ + protected prepareCombatValues(): void { + super.prepareCombatValues(); + this.data.data.combatValues.mana.base += this.data.data.baseInfo.foeFactor; + this.data.data.combatValues.mana.total += this.data.data.baseInfo.foeFactor; + } } export interface DS4Creature { diff --git a/src/common/common-data.ts b/src/common/common-data.ts index c2c1a527..3b3ae642 100644 --- a/src/common/common-data.ts +++ b/src/common/common-data.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT export interface ModifiableData { - mod: T; + mod?: T | null | undefined; } export interface HasBase { diff --git a/src/config.ts b/src/config.ts index 76c206ca..1f82bbe3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -158,10 +158,11 @@ const i18nKeys = { rangedAttack: "DS4.CombatValuesRangedAttack", spellcasting: "DS4.CombatValuesSpellcasting", targetedSpellcasting: "DS4.CombatValuesTargetedSpellcasting", + mana: "DS4.CombatValuesMana", }, /** - * The what do display in the actor sheets for the combat value text (in some languages, abbreviations are necessary) + * The what to display in the actor sheets for the combat value text (in some languages, abbreviations are necessary) */ combatValuesSheet: { hitPoints: "DS4.CombatValuesHitPointsSheet", @@ -172,6 +173,7 @@ const i18nKeys = { rangedAttack: "DS4.CombatValuesRangedAttackSheet", spellcasting: "DS4.CombatValuesSpellcastingSheet", targetedSpellcasting: "DS4.CombatValuesTargetedSpellcastingSheet", + mana: "DS4.CombatValuesManaSheet", }, /** diff --git a/src/global.d.ts b/src/global.d.ts index e557d128..4a6f1914 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -12,6 +12,7 @@ declare global { "ds4.showSlayerPoints": boolean; "ds4.classes": Classes; "ds4.heroClasses": HeroClasses; + "ds4.useManaSystem": boolean; } } diff --git a/src/handlebars/handlebars-partials.ts b/src/handlebars/handlebars-partials.ts index 0a0ca83e..5a9f1a23 100644 --- a/src/handlebars/handlebars-partials.ts +++ b/src/handlebars/handlebars-partials.ts @@ -50,6 +50,7 @@ export default async function registerHandlebarsPartials(): Promise { "systems/ds4/templates/sheets/item/tabs/properties.hbs", "systems/ds4/templates/sheets/shared/components/add-button.hbs", "systems/ds4/templates/sheets/shared/components/control-button-group.hbs", + "systems/ds4/templates/sheets/shared/components/form-field-icon-button.hbs", "systems/ds4/templates/sheets/shared/components/rollable-image.hbs", ]; await loadTemplates(templatePaths); diff --git a/src/hooks/init.ts b/src/hooks/init.ts index 0aa55ef3..916ffd33 100644 --- a/src/hooks/init.ts +++ b/src/hooks/init.ts @@ -16,6 +16,7 @@ import registerHandlebarsPartials from "../handlebars/handlebars-partials"; import { getGame } from "../helpers"; import { DS4ItemSheet } from "../item/item-sheet"; import { DS4ItemProxy } from "../item/proxy"; +import { DS4SpellSheet } from "../item/spell/spell-sheet"; import logger from "../logger"; import { macros } from "../macros/macros"; import { migration } from "../migrations"; @@ -69,7 +70,25 @@ async function init() { Actors.registerSheet("ds4", DS4CharacterSheet, { types: ["character"], makeDefault: true }); Actors.registerSheet("ds4", DS4CreatureSheet, { types: ["creature"], makeDefault: true }); Items.unregisterSheet("core", ItemSheet); - Items.registerSheet("ds4", DS4ItemSheet, { makeDefault: true }); + Items.registerSheet("ds4", DS4ItemSheet, { + types: [ + "weapon", + "armor", + "shield", + "equipment", + "loot", + "talent", + "racialAbility", + "language", + "alphabet", + "specialCreatureAbility", + ], + makeDefault: true, + }); + Items.registerSheet("ds4", DS4SpellSheet, { + types: ["spell"], + makeDefault: true, + }); preloadFonts(); await registerHandlebarsPartials(); diff --git a/src/item/item-sheet.ts b/src/item/item-sheet.ts index 245b1cfe..cfa49479 100644 --- a/src/item/item-sheet.ts +++ b/src/item/item-sheet.ts @@ -7,6 +7,7 @@ import { DS4ActiveEffect } from "../active-effect"; import { DS4 } from "../config"; import { getGame } from "../helpers"; +import { DS4Settings, getDS4Settings } from "../settings"; import notifications from "../ui/notifications"; import { enforce } from "../utils"; import { isDS4ItemDataTypePhysical } from "./item-data-source-base"; @@ -40,6 +41,7 @@ export class DS4ItemSheet extends ItemSheet isOwned: this.item.isOwned, actor: this.item.actor, isPhysical: isDS4ItemDataTypePhysical(this.item.data.data), + settings: getDS4Settings(), }; return data; } @@ -128,6 +130,7 @@ interface DS4ItemSheetData extends ItemSheet.Data { isOwned: boolean; actor: DS4ItemSheet["item"]["actor"]; isPhysical: boolean; + settings: DS4Settings; } /** diff --git a/src/item/spell/calculate-mana-const.ts b/src/item/spell/calculate-mana-const.ts new file mode 100644 index 00000000..3adaabb3 --- /dev/null +++ b/src/item/spell/calculate-mana-const.ts @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2022 Johannes Loher +// +// SPDX-License-Identifier: MIT + +import type { CooldownDuration, DS4SpellDataSourceData } from "./spell-data-source"; + +type Level = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20; + +export function calculateManaCost({ + minimumLevels, + cooldownDuration, +}: DS4SpellDataSourceData): DS4SpellDataSourceData["manaCost"] { + // prettier-ignore + const magicalManaTable: Record> = { + 1: { "0r": 1, "1r": 2, "2r": 2, "5r": 2, "10r": 2, "100r": 3, "1d": 4, "d20d": 5 }, + 2: { "0r": 1, "1r": 2, "2r": 2, "5r": 2, "10r": 3, "100r": 3, "1d": 4, "d20d": 5 }, + 3: { "0r": 2, "1r": 2, "2r": 2, "5r": 3, "10r": 3, "100r": 4, "1d": 5, "d20d": 6 }, + 4: { "0r": 3, "1r": 3, "2r": 3, "5r": 4, "10r": 4, "100r": 5, "1d": 6, "d20d": 7 }, + 5: { "0r": 4, "1r": 4, "2r": 4, "5r": 5, "10r": 5, "100r": 6, "1d": 7, "d20d": 8 }, + 6: { "0r": 4, "1r": 5, "2r": 5, "5r": 6, "10r": 6, "100r": 7, "1d": 7, "d20d": 8 }, + 7: { "0r": 5, "1r": 6, "2r": 6, "5r": 7, "10r": 7, "100r": 7, "1d": 8, "d20d": 9 }, + 8: { "0r": 6, "1r": 6, "2r": 7, "5r": 7, "10r": 7, "100r": 8, "1d": 8, "d20d": 9 }, + 9: { "0r": 7, "1r": 7, "2r": 7, "5r": 8, "10r": 8, "100r": 8, "1d": 9, "d20d": 10 }, + 10: { "0r": 7, "1r": 8, "2r": 8, "5r": 8, "10r": 9, "100r": 8, "1d": 10, "d20d": 11 }, + 11: { "0r": 8, "1r": 8, "2r": 9, "5r": 9, "10r": 9, "100r": 10, "1d": 10, "d20d": 11 }, + 12: { "0r": 9, "1r": 9, "2r": 9, "5r": 9, "10r": 10, "100r": 11, "1d": 11, "d20d": 12 }, + 13: { "0r": 10, "1r": 10, "2r": 10, "5r": 10, "10r": 11, "100r": 12, "1d": 12, "d20d": 13 }, + 14: { "0r": 10, "1r": 11, "2r": 11, "5r": 12, "10r": 12, "100r": 13, "1d": 13, "d20d": 14 }, + 15: { "0r": 11, "1r": 12, "2r": 12, "5r": 12, "10r": 12, "100r": 13, "1d": 14, "d20d": 15 }, + 16: { "0r": 12, "1r": 12, "2r": 12, "5r": 12, "10r": 13, "100r": 14, "1d": 15, "d20d": 15 }, + 17: { "0r": 12, "1r": 13, "2r": 13, "5r": 13, "10r": 14, "100r": 15, "1d": 16, "d20d": 16 }, + 18: { "0r": 13, "1r": 14, "2r": 14, "5r": 14, "10r": 14, "100r": 15, "1d": 16, "d20d": 17 }, + 19: { "0r": 14, "1r": 15, "2r": 15, "5r": 15, "10r": 15, "100r": 15, "1d": 16, "d20d": 17 }, + 20: { "0r": 14, "1r": 15, "2r": 15, "5r": 16, "10r": 16, "100r": 16, "1d": 17, "d20d": 18 }, + }; + + const getManaCostForSpellcasterClass = (spellcasterClass: keyof DS4SpellDataSourceData["manaCost"]) => { + const minimumLevel = minimumLevels[spellcasterClass]; + return minimumLevel !== null + ? magicalManaTable[Math.clamped(minimumLevel, 1, 20) as Level][cooldownDuration] + : null; + }; + + return { + healer: getManaCostForSpellcasterClass("healer"), + wizard: getManaCostForSpellcasterClass("wizard"), + sorcerer: getManaCostForSpellcasterClass("sorcerer"), + }; +} diff --git a/src/item/spell/spell-data-source.ts b/src/item/spell/spell-data-source.ts index 7c578dc9..0675115b 100644 --- a/src/item/spell/spell-data-source.ts +++ b/src/item/spell/spell-data-source.ts @@ -18,6 +18,11 @@ export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4It effectRadius: UnitData; duration: UnitData; cooldownDuration: CooldownDuration; + manaCost: { + healer: number | null; + wizard: number | null; + sorcerer: number | null; + }; minimumLevels: { healer: number | null; wizard: number | null; diff --git a/src/item/spell/spell-sheet.ts b/src/item/spell/spell-sheet.ts new file mode 100644 index 00000000..e334eb59 --- /dev/null +++ b/src/item/spell/spell-sheet.ts @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2021 Johannes Loher +// +// SPDX-License-Identifier: MIT + +import { getGame } from "../../helpers"; +import { DS4ItemSheet } from "../item-sheet"; +import { calculateManaCost } from "./calculate-mana-const"; + +import type { DS4Item } from "../item"; + +export class DS4SpellSheet extends DS4ItemSheet { + /** @override */ + activateListeners(html: JQuery): void { + super.activateListeners(html); + + if (!this.options.editable) return; + + html.find(".ds4-calculate-mana-cost").on("click", this.onCalculateManaCost.bind(this)); + } + + /** Calculates the mana cost of the spell automatically. */ + protected onCalculateManaCost(): void { + const game = getGame(); + const manaCost = calculateManaCost(this.item.data.data); + Dialog.confirm({ + title: game.i18n.localize("DS4.CalculateManaCost"), + content: game.i18n.localize("DS4.CalculateManaCostConfirmationQuestion"), + yes: () => this.item.update({ data: { manaCost } }), + }); + } +} + +export interface DS4SpellSheet { + object: DS4Item & { data: { type: "spell"; _source: { type: "spell" } } }; +} diff --git a/src/item/talent/talent.ts b/src/item/talent/talent.ts index dea5782f..2133c84b 100644 --- a/src/item/talent/talent.ts +++ b/src/item/talent/talent.ts @@ -9,7 +9,7 @@ export class DS4Talent extends DS4Item { prepareDerivedData(): void { super.prepareDerivedData(); const data = this.data.data; - data.rank.total = data.rank.base + data.rank.mod; + data.rank.total = data.rank.base + (data.rank.mod ?? 0); } /** @override */ diff --git a/src/settings.ts b/src/settings.ts index bf52e439..d48fcf7b 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -58,6 +58,16 @@ export function registerSystemSettings(): void { restricted: true, type: ClassConfig, }); + + game.settings.register("ds4", "useManaSystem", { + name: "DS4.SettingUseManaSystemName", + hint: "DS4.SettingUseManaSystemHint", + scope: "world", + config: true, + type: Boolean, + default: false, + onChange: renderActorAndSpellSheets, + }); } export interface DS4Settings { @@ -66,6 +76,7 @@ export interface DS4Settings { showSlayerPoints: boolean; classes: Classes; heroClasses: HeroClasses; + useManaSystem: boolean; } export function getDS4Settings(): DS4Settings { @@ -76,6 +87,7 @@ export function getDS4Settings(): DS4Settings { showSlayerPoints: game.settings.get("ds4", "showSlayerPoints"), classes: game.settings.get("ds4", "classes"), heroClasses: game.settings.get("ds4", "heroClasses"), + useManaSystem: game.settings.get("ds4", "useManaSystem"), }; } diff --git a/template.json b/template.json index 64fac089..b6df7e79 100644 --- a/template.json +++ b/template.json @@ -68,6 +68,10 @@ }, "targetedSpellcasting": { "mod": 0 + }, + "mana": { + "mod": 0, + "value": 0 } } } @@ -188,6 +192,11 @@ "unit": "custom" }, "cooldownDuration": "0r", + "manaCost": { + "healer": null, + "wizard": null, + "sorcerer": null + }, "minimumLevels": { "healer": null, "wizard": null, diff --git a/templates/sheets/actor/components/actor-progression.hbs b/templates/sheets/actor/components/actor-progression.hbs index bfb3a860..ee438152 100644 --- a/templates/sheets/actor/components/actor-progression.hbs +++ b/templates/sheets/actor/components/actor-progression.hbs @@ -16,6 +16,18 @@ SPDX-License-Identifier: MIT id="data.combatValues.hitPoints.value-{{data._id}}" value="{{data.data.combatValues.hitPoints.value}}" data-dtype="Number" /> + {{!-- TODO: Find a better place for this --}} + {{#if settings.useManaSystem}} +
+

+

+ +
+ {{/if}} {{#if (eq data.type "character")}} {{#if settings.showSlayerPoints}}
diff --git a/templates/sheets/actor/components/combat-value.hbs b/templates/sheets/actor/components/combat-value.hbs index 3037f92d..e23b702f 100644 --- a/templates/sheets/actor/components/combat-value.hbs +++ b/templates/sheets/actor/components/combat-value.hbs @@ -15,7 +15,7 @@ SPDX-License-Identifier: MIT --}}
-
{{combat-value-data.total}}
diff --git a/templates/sheets/actor/components/combat-values.hbs b/templates/sheets/actor/components/combat-values.hbs index 63ebe6e6..d4aa1707 100644 --- a/templates/sheets/actor/components/combat-values.hbs +++ b/templates/sheets/actor/components/combat-values.hbs @@ -7,9 +7,11 @@ SPDX-License-Identifier: MIT
{{#each config.i18n.combatValues as |combat-value-title combat-value-key|}} + {{#unless (and (eq combat-value-key "mana") (not ../settings.useManaSystem)) }} {{> systems/ds4/templates/sheets/actor/components/combat-value.hbs combat-value-key=combat-value-key combat-value-data=(lookup ../data.data.combatValues combat-value-key) combat-value-label=(lookup ../config.i18n.combatValuesSheet combat-value-key) combat-value-title=combat-value-title actor-id=../data._id}} + {{/unless}} {{/each}}
diff --git a/templates/sheets/item/components/properties/spell.hbs b/templates/sheets/item/components/properties/spell.hbs index eb48d9f2..455e0b1d 100644 --- a/templates/sheets/item/components/properties/spell.hbs +++ b/templates/sheets/item/components/properties/spell.hbs @@ -94,18 +94,42 @@ SPDX-License-Identifier: MIT
+ {{#if settings.useManaSystem}} +
+ +
+ {{> systems/ds4/templates/sheets/shared/components/form-field-icon-button.hbs + class="ds4-calculate-mana-cost" title="DS4.CalculateManaCost" faClasses="fas fa-magic"}} + + + + + + +
+
+ {{/if}}
- + - + - +
diff --git a/templates/sheets/shared/components/form-field-icon-button.hbs b/templates/sheets/shared/components/form-field-icon-button.hbs new file mode 100644 index 00000000..605cf322 --- /dev/null +++ b/templates/sheets/shared/components/form-field-icon-button.hbs @@ -0,0 +1,16 @@ +{{!-- +SPDX-FileCopyrightText: 2021 Johannes Loher + +SPDX-License-Identifier: MIT +--}} + +{{! +!-- Render a form-field icon button. +!-- @param class: A class to identify the button by +!-- @param title: The title to use for the button (will be localized) +!-- @param faClasses: The Fontawesom classes to use for the icon +}} + + diff --git a/yarn.lock b/yarn.lock index 2dda5023..f0476f29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -892,9 +892,9 @@ __metadata: languageName: node linkType: hard -"@league-of-foundry-developers/foundry-vtt-types@npm:9.249.3": - version: 9.249.3 - resolution: "@league-of-foundry-developers/foundry-vtt-types@npm:9.249.3" +"@league-of-foundry-developers/foundry-vtt-types@npm:9.249.4": + version: 9.249.4 + resolution: "@league-of-foundry-developers/foundry-vtt-types@npm:9.249.4" dependencies: "@types/jquery": ~3.5.9 "@types/simple-peer": ~9.11.1 @@ -903,7 +903,7 @@ __metadata: pixi.js: 5.3.11 socket.io-client: 4.3.2 tinymce: 5.10.1 - checksum: 13bbcafd57637a59f44ee136e5edd78b1fedb3f0327027bd2bae8971f2eed6ad5ad84755cebc32e4b08f788e0bf5c08dd06cbe4709ae89746198658ebb511451 + checksum: 612721d98bb6e8496d8000e86619615b2ae968ecb643273545c1a73c7d2faeca611f6d3adbc84b99cdf47b706a604226582731e0be560701a801a6a23bb75cae languageName: node linkType: hard @@ -3262,7 +3262,7 @@ __metadata: "@commitlint/cli": 16.2.1 "@commitlint/config-conventional": 16.2.1 "@guanghechen/rollup-plugin-copy": 1.8.6 - "@league-of-foundry-developers/foundry-vtt-types": 9.249.3 + "@league-of-foundry-developers/foundry-vtt-types": 9.249.4 "@rollup/plugin-typescript": 8.3.0 "@seald-io/nedb": 2.2.1 "@types/fs-extra": 9.0.13