From 9d91bc32fb7e28a27dae6dbabb9b0b5f2da154f0 Mon Sep 17 00:00:00 2001 From: Melvin Valster Date: Mon, 22 Jul 2019 21:16:55 +0200 Subject: [PATCH] Add talent decoding and spiffing up icon frames --- TODO.md | 15 +-- public/images/down.png | Bin 0 -> 1100 bytes public/images/down2.png | Bin 0 -> 2367 bytes public/images/icons/large/default.png | Bin 0 -> 1946 bytes public/images/icons/large/gold.png | Bin 0 -> 1983 bytes public/images/icons/medium/default.png | Bin 0 -> 784 bytes public/images/icons/medium/gold.png | Bin 0 -> 789 bytes public/images/tooltip-background.png | Bin 0 -> 6051 bytes src/App.scss | 15 ++- src/App.tsx | 2 +- src/components/Calculator.tsx | 27 ++++- src/components/ClassPicker.tsx | 5 +- src/components/Icon.scss | 41 +++++++- src/components/Icon.tsx | 21 +++- src/components/IndexRoute.tsx | 16 ++- src/components/TalentSlot.scss | 130 ++++++++++++++++--------- src/components/TalentSlot.tsx | 21 ++-- src/lib/tree.ts | 58 +++++++++-- 18 files changed, 269 insertions(+), 82 deletions(-) create mode 100644 public/images/down.png create mode 100644 public/images/down2.png create mode 100644 public/images/icons/large/default.png create mode 100644 public/images/icons/large/gold.png create mode 100644 public/images/icons/medium/default.png create mode 100644 public/images/icons/medium/gold.png create mode 100644 public/images/tooltip-background.png diff --git a/TODO.md b/TODO.md index 05442f3..30c4050 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,14 @@ # TODO -- [ ] Add redux -- [ ] Talent tooltips -- [ ] Generate URL for chosen talents -- [ ] Responsive on mobile -- [ ] Prettier talent frames -- [ ] Prettier icon frames & coloring +- [ ] General: Add redux +- [ ] General: Talent tooltips +- [ ] General: Responsive on mobile +- [ ] System: Generate URL for chosen talents +- [ ] Talent tree: Prettier talent frames +- [ ] Talent tree: Downward arrow for dependencies +- [ ] Talent tree: Colour markings on icons +- [ ] Talent tree: Reset button per tree (?) +- [x] Prettier icon frames - [x] Pretty ClassPicker - [x] Add react-router - [x] Prevent reducing talent points on a row when it is a dependency for points already spent in the next row diff --git a/public/images/down.png b/public/images/down.png new file mode 100644 index 0000000000000000000000000000000000000000..19a59ae36377c14b8f368214d8a1e59dc5c8d21e GIT binary patch literal 1100 zcmeAS@N?(olHy`uVBq!ia0vp^{0t0?92{&wR>P8YZa|79-O<;Pfnog#bJnhxK)z&& zYeY$Kep*R+Vo@rCV@iHfs)A>3VtQ&&YGO)d;mK4R1_tJPo-U3d6}R3*-}V!Bl$dAx zC1ycyn|5pluK)M=jQgfAi8iLQo7OConZ-^`)%+Eb_Jh$`au{qZN zf;I+-^-k~Q{BZI5;?A9%1-a|gUTxh~wJt>K(D&M}HFb4%vTt^+TXFsMlCBRn`t#;< zxt)Gml%)}=_;O8xid%m z)YDC~E>D|Nytt{LxLBF(-hrIk3_NVjyN{ZwSa17$U2lu3a)f|Hhl!2!Psujth@d2u z){Ix%6a~2#G$oj5Ip5IYdG>RjhNyQM+v{(;yw2Cy&7T!IvrcX9-vj28-|Xl+aZ*0w zPQ<47Ke;%XGsT;4BqXL7yzo7^h_@}JK`}DY_Th!oS1k3FydPVhJpb~ij9XyqV=?7R z!S@p!i|$@Jt))4UYcE&eanlPoJ+B34-`G zl#4Yr_igZ|`|nrBt-c!a)Fn-I>LM3|sc}|WhZL@qhi;dCYILXdl6R>Ow~e*+>L!+B zP5(5U-B>5|g>VJ#67^$U-F&xOn7D_3(-3rmouYuQazz+Uco_vzE~GYi!Ps zGMXfEt+^M-&MOe!d1?(0@4+YQ+y!o*nLpFsxIXRQnLjgs8h=iIcAk0ya%*pXG0hA6 zS$a8w*I~uoNb&OUeP5#bvN!4Xh}ZOJ)J<7+_}Y~E=t;@a#y_$u5=&FJn)>}d&Rsw$1`yY6I|Jd##mELSP0ukUZj&AC18+L3y3zBXa? z-POBiR}^k}SbnCTsd2NLorL?sD5cZ;BV)5oXTMI$uJ+zy{g!jVp`KrbHH|-iE??W- oE#G;7v)~d_ro)!|Ogs$BqcmR6yL>SR62Ty85}Sb4q9e0KMrFVE_OC literal 0 HcmV?d00001 diff --git a/public/images/down2.png b/public/images/down2.png new file mode 100644 index 0000000000000000000000000000000000000000..8419744ea1364ad7549645a71d14b86259f0e98d GIT binary patch literal 2367 zcmV-F3BdM=P)p;t00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU)(Md!>RCwC# zTT725#SxCIcURAAmz_22N-I{2kU<%5Z#{S>5xf>dEX~W01S0QavW4GBP9L%lNWl z6f+ZkN~xQ2x_RaC{I*hZWgpw0+@smue@$PLyE`It2agjv^?UldqVGPH(%zSi5yEfx zv6w#ov=ZTyZO;gQ|GA8Q_~L**`A0Pue)@V!fBM^){`$dWx$uQ8MNeFg=>At~t}^Z~ zY^u{u&k7SaRO^bguH8LRU5lV@&NPkaA%6cQVRh`Rx|<}OPoh(=O}o4KCXRV6>?eHp zi&|p0rW;Qxdh8-mEOt+0`9tg>+&KSFL@0`s1Bz}h``?ZEjBm$LLs>j?z(7ES4#pw@t@HUrl^_CTk+NXp-8)4*;F2`A|^~ z8&}k+@)A=i2@11#&~$?NQ+~pTyjBpZ4~F7txxGGD?G$5`H{=D;Ft^XLJkY~nowheP z3hMx2AV?yP5sMb7I8Nf@Bn`tYnd-)(3S87YV-Mj9>#+zN9m>s-Tq8lqyM=qzo=vU2 z%WavkB4EPM2?Vmi$<_9J57Va(I(5BN7^#S;%+rNa>alz7TyBOaJ2@K~X zQL?xWimIYiv{l&abO0~o^Jg3FGm%hYV;;%d;UuE|bmVJiSsv^p>EINg86M+z3Gu zwa5|>U3DD@Lx?C~7vM*CyLP{+B&VTjY~wQ@2Ku&vAHBE^5=M`$H!JoOPQ|(7LRu%v zASc$T*cZ2BamgsN-2wD!fzGaoS`PY&)g8{#L2UZQykUZJB<8ZZ2 z&K){o;LliVu3Z%_xg#b{k%My>e+J;kkyF3N%aZfVqg4N>m)g0VH1F94e@+};Z6Jlp z5YLV9=h~A$yPiN)kQD(*zYY9Z7~K5_3;uj@8~nNPaU zCOgB0BcgHIoe_5OXSlMOVc$SBV9O=_`Sa;WO9T0{@$0lBM+wTqLY$-pK+F#|mnIV% zjzRpna$lU-#dH9FUNG6B4BS|1{Ta&*xN{#7jg~nH_B{=M<^cZ84t8s{seYP=xF_4h z-Xu5TVjRYw*^;s)+_|4WW3M2V^deSf+_CRoui?+BV>Lig-OsU>;qDst7Jp$!Jp3s0}jgc5t232{zxzOOAw=a6)*@TWy*p5 zJe^9^|KUeXe)K&j{XOx+2rtgcm!Q~>nh`y9J)wOGtAi1eI^V>9#iTmN?nTP7*uHani>VxFS+{0F$~*{)GGnworAcHGnnpm}o0O?XGv*aYSzRgm z=e=fLobkkE_(lW?9m1Wa*|rs8021&wvIKK>M&AfOeO19p4#Hz zfZ3L@ODr{!w6p1wSqz+J0Yo&m zYqVK`G>4Hgq!XE|=}o0)e^Mq{TU%+`XnP+}X{yaEHtPT<8P0BJc(d57$sP`X_9)gr@Y^pF)hDytFup%jIw=B7(N4}(NT3equ641N1 zhqRgfNf~~JBeD!uuRzLRn%zrs?8+)~0Z4chHrq3C(?bc(S>1$1RXMar3kE@zrr5gM ze_*e-x2rJsgr?Q1m8Qyre|M6yK`HJykbDGdRSp+N}_ ziY1Wqbm3-^3b3>3ZT=;pe_}c-O~oz5N%S#F+4xtQDpiZx%{;F%TII7ENF##XIoTaDK&_S^`Uw2SxCj^wPGmtt@N!v^u?D51t}J> zP+tQ2CW0VRFKyEnQze(CwN-4J>u&eHvorqR*)z#xlU*j@OXuuKerEQZnKS2nbN<^o z=O4}UJnE#?q<0`#n@s;i)3jjTR;HayPcuErl!!=wOH6l}rkHLrEigG70@4J=Zl*WK z#>S4t;|VfNlXP99VzEeuVbGQ>TST7gx|Gl7DVbCf-J@IaiS9PF4%rD$<+k$QT1 z#QlEi>gu9Gp+NC?oMN#UEi5dEYpd0&$V0uT2lwHK7lt~s*(^DZ!|l<hm$jHc9E*cAh5(H!~AKpEA@{?mv?%YY$nn#C+ z4^zEb{Fd{lnQ{-g{ODoYWyQJ&zdnDCM{b#>rl#ol@#CjC|2)$brsW_YhnP*ic=oyH z8GVC>4h;c}&$;M3DS(O(IUrq9QM_&0UBhS3o)IHx{PJZwb?Vf|oG)H{P^dnB@#00w z=JGW9*=c`d0E{0ulEx3IM!kcRTD7=+Xz0-3$VVU2_3PJ>^$gRmOt*q|ND=}mpDzd= zm27e|B4Cj0!-DM7G~`WzwGaBt3G%a#DHa68fONKFwl6$$l1-ujMi3PADF?M|Tei?h zlASB-t@U zpWBWQ2vx0>qZKihflzHbLI4I_YE^g!eQnmN>x8Pb+#;V0Ho;OosrEv(x3^c!I1xpE z;#o6vVT7X3Edha}gCXQ;(U&2PQ>S8S#kt!U5Z}4ebu}jAV|Hwvh*&37;oRw(rUW1X z=dQKdRmHh;-FkTE&YvBRwp7)^J9nA_EQ1;Q(9WH0nb990a>lIV+^u*rT6pKKu7C`I zPz~+eDPX9I6~@rcouPnX1|UK^cUb9H0g%wnoeH~(uyh~Vxl_O_Ys|Wbb?!U`w2Z|T zYG~&UdnqEXc6o$^cJ6So3Wix|=T3$B5NzWrg^kg8LYLb3{6f> zieP7GVQ^71ATtva6JM3f<@a{%*g^aD?WGGBei>#^5RVp_Fjast@Y;Sc>pnX>OP*6e zS)QrV49E?ppJrxe9L#;**D;ABl}?FDxw98O@y<<%7g0i)s`KLiQ$Lu z0u%~)E?-sOcsLr41+d`#6+R*#V796k^D_unhl+#%^et&@48&3b>Q3^@!@a)yftfI9 z5L+0To_G;r*I_&+>YksUr}=xgY4)$X5Cy+eBNX=sa7hplT@EY6u)YP#WHPV&stMPj z0ca>14Z?3!E*Mmr!TyH~aE?afLkUZ4>}_;^tNeEL`e5M03CL#_7wz*uw1Ah0a{LDQ zA8qjaAU?v7o5RSsAw>b=b$@0;0PkE75MS%~pzLGnXL^DuNzwV%2!tmD$3GH;OH6+< zL4g#T)u}6m=Oj~E^43$-2=cWe0*EU$CIG>AL}q+2nq^3{eF8O=mbyC`AwYaJ3;si- g_p%QL3jPyd0L@m~8Gf`|M*si-07*qoM6N<$f-GjGUH||9 literal 0 HcmV?d00001 diff --git a/public/images/icons/large/gold.png b/public/images/icons/large/gold.png new file mode 100644 index 0000000000000000000000000000000000000000..eba088a1c66f778984843239b025404211f0974a GIT binary patch literal 1983 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 Tz^!*X00000NkvXXu0mjfR;qJC literal 0 HcmV?d00001 diff --git a/public/images/tooltip-background.png b/public/images/tooltip-background.png new file mode 100644 index 0000000000000000000000000000000000000000..806edf050d54a5d5cdaa75c7495610737d98f2d0 GIT binary patch literal 6051 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGmw%jag8Vm&QB{TPb^Aha7@Wh zN>%X8O-xS>N=;0uEIgTN!@wZ!>gnPbQgQ3eZNscX2@(wtU1XT}(#ogbTKS0AYRaYH zl2yw!SgN?VS@j=8E$LPlunv9kF0b9Gt?BRo2-i8gRc!eGw5+(iy*A%x*X_5HG<+}n z?f7!|r5ez7hr6|{pC!NjdtY(cF8ZJQ!!KtzfTCIXpSOPw7hF)g?~lG%x6(g_^Tmsq z7#JF6o!qo$7av>mqYsfi44FIl_)q_@+N+ngIr7twFPlt^fI3$!@Be=Nq9;qof0l@C zlm1%&-+MJH^(Q+}FWZhiyH=O|{;|OR=Kfdrb7l9;aTI66&@(rN0O=RL$0IS7n3`Siwnnch7s{za>C0K|HXa$hD z4IqLhu{hcgMhjUIVr8_*!5UZ%pb(`=Vg(x805W@k4ae$hj1p1->{77WK~O3*AY6mw zFg#3%2I5?f#~AFvLMy|Gb1stYq_~_E!%0XY4Gg0VVFn;LP|wm}a~?82`=sQ$JzK*j zoyoUfcTfBXlyvxeRM!68FZN7}!v&8X-Iwk^?)v9L<*#ifT0qGa%iDj?e#Q7d#i+CT zpW2*M9)`IEzm^LsoHkky>|z|y-(8l&tiZ#}DByqK-)ciwjTOE^oKt!dUiuY1ymog# zPe4F+Zr#uI5BHxJomuH}B#g;I9BAm2@9qER{hc4lz~jwXuuBjmQeOYCUS~Z6GsB-F VC1;!R&V!UOc)I$ztaD0e0sxQ6hFSms literal 0 HcmV?d00001 diff --git a/src/App.scss b/src/App.scss index 55eba5e..870190a 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,5 +1,6 @@ body { background-color: #111; + font-family: Verdana; } .calculator { @@ -19,6 +20,7 @@ body { min-width: 300px; color: white; margin-right: 1em; + background-color: #111; &:last-child { margin-right: 0; @@ -26,6 +28,11 @@ body { &__header { text-align: center; + + h3 { + margin-top: .75em; + margin-bottom: .75em; + } } &__body { @@ -40,10 +47,14 @@ body { .class-picker { display: flex; justify-content: center; + list-style: none; + margin-top: 2em; + margin-bottom: 2em; &__class { - margin-right: 2em; + margin-right: 1em; opacity: .8; + transition: all .1s ease-out; &:hover { opacity: 1; @@ -57,7 +68,7 @@ body { opacity: .4; &:hover { - opacity: .6; + opacity: .5; } } } diff --git a/src/App.tsx b/src/App.tsx index f78a301..9cf2b7d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ const App: React.FC = () => { return (
- +
); diff --git a/src/components/Calculator.tsx b/src/components/Calculator.tsx index 21295f8..6255931 100644 --- a/src/components/Calculator.tsx +++ b/src/components/Calculator.tsx @@ -13,9 +13,9 @@ import { History } from 'history' interface Props { selectedClass: string history: History + initialTalents?: Map } -// const EMPTY_TALENTS = Map() const EMPTY_TALENTS = Map() // .set(30, 5) // .set(26, 5) @@ -33,6 +33,13 @@ export class Calculator extends React.PureComponent { knownTalents: EMPTY_TALENTS } + componentDidMount() { + if (this.props.initialTalents) { + this.setState({ knownTalents: this.props.initialTalents }) + this.updateURL(this.props.initialTalents) + } + } + componentDidUpdate(prevProps: Props) { if (prevProps.selectedClass !== this.props.selectedClass) { this.setState({ @@ -40,17 +47,22 @@ export class Calculator extends React.PureComponent { }) } } + + updateURL(knownTalents: Map) { + const { selectedClass } = this.props + const pointString = encodeKnownTalents(knownTalents, selectedClass) + this.props.history.replace(`/${selectedClass}` + (pointString ? `/${pointString}` : '')) + } handleTalentPress = (specId: number, talentId: number, modifier: 1 | -1) => { - const { selectedClass } = this.props const talent = talentsBySpec[specId][talentId] console.log('Clicked talent: ' + talentId) const newKnownTalents = modifyTalentPoint(this.state.knownTalents, talent, modifier) + if (newKnownTalents !== this.state.knownTalents) { + this.updateURL(newKnownTalents) + } this.setState({ knownTalents: newKnownTalents }) - - const pointString = encodeKnownTalents(newKnownTalents, selectedClass) - this.props.history.replace(`/${selectedClass}` + (pointString ? `/${pointString}` : '')) } render() { @@ -77,6 +89,11 @@ export class Calculator extends React.PureComponent {
Points: {availablePoints}
+ + ) } diff --git a/src/components/ClassPicker.tsx b/src/components/ClassPicker.tsx index 0bc3b57..f22ebf8 100644 --- a/src/components/ClassPicker.tsx +++ b/src/components/ClassPicker.tsx @@ -29,7 +29,10 @@ export class ClassPicker extends React.PureComponent { {Object.values(classByName).map((c) =>
  • - +
  • )} diff --git a/src/components/Icon.scss b/src/components/Icon.scss index 7758797..e7d6210 100644 --- a/src/components/Icon.scss +++ b/src/components/Icon.scss @@ -1,10 +1,49 @@ .icon { + position: relative; background-position: center; background-repeat: no-repeat; - background-size: cover; + background-size: contain; + background-color: #222; + &:hover { + .icon__bg { + box-shadow: inset 0px 0px 6px 3px rgba(99, 150, 214, .8); + } + } + &--medium { width: 40px; height: 40px; + border-radius: 5px; + + .icon__bg { + width: 36px; + height: 36px; + top: 2px; + left: 2px; + } + } + + &--golden { + .icon__frame { + background-image: url('/images/icons/large/gold.png'); + } + } + + &__bg { + position: absolute; + background-size: cover; + border-radius: 5px; + } + + &__frame { + position: absolute; + width: 44px; + height: 44px; + top: -2px; + left: -2px; + background-image: url('/images/icons/large/default.png'); + background-repeat: no-repeat; + background-size: contain; } } \ No newline at end of file diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index efa7a60..d5b6986 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -1,17 +1,30 @@ import React, { FC } from 'react' +import classNames from 'classnames' import './Icon.scss' interface Props { name: string size?: 'small' | 'medium' | 'large' + golden?: boolean } -export const Icon: FC = ({ name, size = 'medium', children }) => { - const url = `https://wow.zamimg.com/images/wow/icons/${size}/${name}.jpg` - const className = `icon icon--${size}` +export const Icon: FC = ({ name, size = 'medium', golden = false, children }) => { + const className = classNames( + 'icon', + `icon--${size}`, { + 'icon--golden': golden + } + ) + + const bgSize = size === 'medium' ? 'large' : 'medium' + const bgStyle = { + backgroundImage: `url(https://wow.zamimg.com/images/wow/icons/${bgSize}/${name}.jpg)` + } return ( -
    +
    +
    +
    {children}
    ) diff --git a/src/components/IndexRoute.tsx b/src/components/IndexRoute.tsx index 765aaa7..da63762 100644 --- a/src/components/IndexRoute.tsx +++ b/src/components/IndexRoute.tsx @@ -3,6 +3,8 @@ import { Calculator } from './Calculator' import { ClassPicker } from './ClassPicker' import { match } from 'react-router-dom' import { RouteComponentProps } from 'react-router' +import { decodeKnownTalents } from '../lib/tree' +import { classByName } from '../data/classes' interface Props extends RouteComponentProps { match: match<{ @@ -14,16 +16,28 @@ interface Props extends RouteComponentProps { export class IndexRoute extends React.PureComponent { static whyDidYouRender = true + componentDidMount() { + const { selectedClass } = this.props.match.params + if (selectedClass && !classByName[selectedClass]) { + this.props.history.replace('/') + } + } + render() { const { match, history } = this.props const { selectedClass, pointString } = match.params - + + if (selectedClass && !classByName[selectedClass]) { + return null + } + return (
    {selectedClass && diff --git a/src/components/TalentSlot.scss b/src/components/TalentSlot.scss index 7cd8cb7..457508f 100644 --- a/src/components/TalentSlot.scss +++ b/src/components/TalentSlot.scss @@ -1,66 +1,102 @@ +$row-offset: 30px; $row-distance: 70px; -@mixin rowStyle($rowNr) { - top: 30px + (($rowNr) * 70px); -} +$col-offset: 44px; +$col-distance: 56px; + +$color-yellow: #ffd100; +$color-green: #1eff00; +$color-dark-green: #40bf40; +$color-subtle: #9d9d9d; .talent { position: absolute; width: 40px; height: 40px; - border: 1px solid black; - border-radius: 2px; + border-radius: 5px; + transition: filter .1s linear; + filter: none; + cursor: pointer; - &:after { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 10; - } + &--available { + .talent__status::after { + // background-color: rgba($color-green, .8); + box-shadow: inset 0px 0px 6px 3px rgba($color-green, .8); + } - small { - font-size: 8px; - line-height: 1em; - } - - &--disabled { - filter: grayscale(100%) - } - - &:not(&--disabled) { - cursor: pointer; - } - - &--maxed { - - } - - @for $i from 0 through 6 { - &[data-row="#{$i}"] { - @include rowStyle($i) + .point-label { + color: $color-green; } } + &--maxed { + .talent__status::after { + box-shadow: inset 0px 0px 6px 3px rgba($color-yellow, .8); + } - $col-distance-offset: 44px; - $col-distance: 56px; + .point-label { + color: $color-yellow; + } + } - &[data-col="0"] { left: $col-distance-offset; } - &[data-col="1"] { left: $col-distance-offset + ($col-distance * 1); } - &[data-col="2"] { left: $col-distance-offset + ($col-distance * 2); } - &[data-col="3"] { left: $col-distance-offset + ($col-distance * 3); } + &--disabled { + filter: grayscale(100%); - &__points { + .talent__status { + opacity: .7; + } + } + + // Rows + @for $i from 0 through 6 { + &[data-row="#{$i}"] { + top: $row-offset + (($i) * $row-distance); + } + } + + // Columns + @for $i from 0 through 3 { + &[data-col="#{$i}"] { + left: $col-offset + ($col-distance * $i); + } + } + + &__status { position: absolute; - padding: 1px 2px; - bottom: -2px; - right: -2px; + width: 48px; + height: 46px; + bottom: -1px; + left: -4px; + background-image: url('/images/icons/large/default.png'); + background-size: cover; + + &:after { + content: ''; + position: absolute; + width: 44px; + height: 44px; + top: 2px; + left: 2px; + border-radius: 5px; + } + } +} + +.point-label { + position: absolute; + bottom: -5px; + right: -5px; + min-width: 7px; + text-align: center; + padding: 1px 3px; + color: #999; + font-size: 11px; + font-family: Arial, Helvetica, sans-serif; + background: #111; + border-radius: 4px; + user-select: none; + + &--enabled { color: white; - font-size: 12px; - background: black; - border-radius: 2px; } } \ No newline at end of file diff --git a/src/components/TalentSlot.tsx b/src/components/TalentSlot.tsx index 6707bbe..802f7aa 100644 --- a/src/components/TalentSlot.tsx +++ b/src/components/TalentSlot.tsx @@ -37,9 +37,14 @@ export const TalentSlot: FC = (props) => { const showPoints = points > 0 || availablePoints > 0 const disabled = props.disabled || !showPoints || !isAvailable(talent, specId, knownTalents) - const cn = classNames('talent', { + const containerClassNames = classNames('talent', { 'talent--disabled': !!disabled, - 'talent--maxed': points >= talent.ranks.length + 'talent--available': !disabled && points < talent.ranks.length, + 'talent--maxed': points >= talent.ranks.length || (points > 0 && availablePoints === 0) + }) + + const pointsClassNames = classNames('point-label', { + 'point-label--enabled': !disabled }) const handleContextMenu = (e) => { @@ -50,17 +55,21 @@ export const TalentSlot: FC = (props) => { return (
    props.onClick(talent.id) : () => {}} onContextMenu={handleContextMenu} > - +
    + - {showPoints && -
    {points}/{talent.ranks.length}
    + {showPoints && !disabled && +
    + {points} + /{talent.ranks.length} +
    }
    ) diff --git a/src/lib/tree.ts b/src/lib/tree.ts index 51ab682..efc7483 100644 --- a/src/lib/tree.ts +++ b/src/lib/tree.ts @@ -9,6 +9,13 @@ import { classByName } from '../data/classes' export const MAX_POINTS = 51 export const MAX_ROWS = 7 +export const SORT_TALENTS = (a: TalentData, b: TalentData) => { + if (a.row === b.row) { + return a.col - b.col + } + return a.row - b.row +} + /** * Returns the overall points spent in the tree. */ @@ -76,6 +83,7 @@ export const removeTalentPoint = (known: Map, talent: TalentData // No points to reduce for this talent if (currentPoints === 0) { + console.warn('no points to reduce') return known } @@ -85,8 +93,11 @@ export const removeTalentPoint = (known: Map, talent: TalentData known.forEach((points, talentId) => { const t = talentsBySpec[specId][talentId] - if (t) { + if (t && points > 0) { isDependency = isDependency || t.requires.some((req) => req.id === talent.id) + if (t.row > highestRow) { + console.info('new highest row:', t) + } highestRow = t.row > highestRow ? t.row : highestRow for (let row = t.row; row < MAX_ROWS; row++) { cumulativePointsPerRow[row] = (cumulativePointsPerRow[row] || 0) + points @@ -98,11 +109,18 @@ export const removeTalentPoint = (known: Map, talent: TalentData const pointsUntilHighestRow = cumulativePointsPerRow[highestRow - 1] const targetPointsHighestRow = highestRow * 5 if (talent.row < highestRow && pointsUntilHighestRow - 1 < targetPointsHighestRow) { + console.warn('would not break the requirements for talents spent in later rows', { + talent, + highestRow, + pointsUntilHighestRow, + targetPointsHighestRow + }) return known } // Prevent if another talent depends on this if (isDependency) { + console.warn('is dependency') return known } @@ -143,12 +161,7 @@ export function encodeKnownTalents(known: Map, className: string const { specs } = classByName[className] for (let i = 0; i < specs.length; i++) { const specId = specs[i] - const talents = talentsBySpecArray[specId].sort((a, b) => { - if (a.row === b.row) { - return a.col - b.col - } - return a.row - b.row - }) + const talents = talentsBySpecArray[specId].sort(SORT_TALENTS) string += i > 0 ? '-' : '' string += removeTrailingCharacters( talents.map((talent) => known.get(talent.id, 0)).join(''), @@ -162,7 +175,36 @@ export function encodeKnownTalents(known: Map, className: string * Decodes a string of points into a Map of talents. */ export function decodeKnownTalents(pointString: string, className: string): Map { - return Map() + console.log(pointString, className) + + const { specs } = classByName[className] + let known = Map() + + // TODO: Make sure we validate the point string + const parts = pointString.split('-') + for (let i = 0; i < parts.length; i++) { + const specId = specs[i] + const specPointStr = parts[i] + console.log(specPointStr, { specId }) + const talents = talentsBySpecArray[specId].sort(SORT_TALENTS) + + for (let y = 0; y < specPointStr.length; y++) { + const talent = talents[y] + const points = parseInt(specPointStr[y], 10) + + // Validation: break out loop if there's more points in the string than this talent can have + if (points > talent.ranks.length) { + break + } + + if (points > 0) { + console.log(`Spent ${points} in ${talent.id}`) + known = known.set(talent.id, points) + } + } + } + + return known } /**