Add talent decoding and spiffing up icon frames
@@ -1,11 +1,14 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- [ ] Add redux
|
- [ ] General: Add redux
|
||||||
- [ ] Talent tooltips
|
- [ ] General: Talent tooltips
|
||||||
- [ ] Generate URL for chosen talents
|
- [ ] General: Responsive on mobile
|
||||||
- [ ] Responsive on mobile
|
- [ ] System: Generate URL for chosen talents
|
||||||
- [ ] Prettier talent frames
|
- [ ] Talent tree: Prettier talent frames
|
||||||
- [ ] Prettier icon frames & coloring
|
- [ ] 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] Pretty ClassPicker
|
||||||
- [x] Add react-router
|
- [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
|
- [x] Prevent reducing talent points on a row when it is a dependency for points already spent in the next row
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 784 B |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 5.9 KiB |
@@ -1,5 +1,6 @@
|
|||||||
body {
|
body {
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
|
font-family: Verdana;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calculator {
|
.calculator {
|
||||||
@@ -19,6 +20,7 @@ body {
|
|||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
color: white;
|
color: white;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
|
background-color: #111;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
@@ -26,6 +28,11 @@ body {
|
|||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: .75em;
|
||||||
|
margin-bottom: .75em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__body {
|
&__body {
|
||||||
@@ -40,10 +47,14 @@ body {
|
|||||||
.class-picker {
|
.class-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
list-style: none;
|
||||||
|
margin-top: 2em;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
|
||||||
&__class {
|
&__class {
|
||||||
margin-right: 2em;
|
margin-right: 1em;
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
|
transition: all .1s ease-out;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -57,7 +68,7 @@ body {
|
|||||||
opacity: .4;
|
opacity: .4;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: .6;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const App: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Route path="/:selectedClass?/:points?" component={IndexRoute} />
|
<Route path="/:selectedClass?/:pointString?" component={IndexRoute} />
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import { History } from 'history'
|
|||||||
interface Props {
|
interface Props {
|
||||||
selectedClass: string
|
selectedClass: string
|
||||||
history: History
|
history: History
|
||||||
|
initialTalents?: Map<number, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
// const EMPTY_TALENTS = Map<number, number>()
|
|
||||||
const EMPTY_TALENTS = Map<number, number>()
|
const EMPTY_TALENTS = Map<number, number>()
|
||||||
// .set(30, 5)
|
// .set(30, 5)
|
||||||
// .set(26, 5)
|
// .set(26, 5)
|
||||||
@@ -33,6 +33,13 @@ export class Calculator extends React.PureComponent<Props> {
|
|||||||
knownTalents: EMPTY_TALENTS
|
knownTalents: EMPTY_TALENTS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.initialTalents) {
|
||||||
|
this.setState({ knownTalents: this.props.initialTalents })
|
||||||
|
this.updateURL(this.props.initialTalents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props) {
|
componentDidUpdate(prevProps: Props) {
|
||||||
if (prevProps.selectedClass !== this.props.selectedClass) {
|
if (prevProps.selectedClass !== this.props.selectedClass) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -41,16 +48,21 @@ export class Calculator extends React.PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTalentPress = (specId: number, talentId: number, modifier: 1 | -1) => {
|
updateURL(knownTalents: Map<number, number>) {
|
||||||
const { selectedClass } = this.props
|
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 talent = talentsBySpec[specId][talentId]
|
const talent = talentsBySpec[specId][talentId]
|
||||||
console.log('Clicked talent: ' + talentId)
|
console.log('Clicked talent: ' + talentId)
|
||||||
|
|
||||||
const newKnownTalents = modifyTalentPoint(this.state.knownTalents, talent, modifier)
|
const newKnownTalents = modifyTalentPoint(this.state.knownTalents, talent, modifier)
|
||||||
|
if (newKnownTalents !== this.state.knownTalents) {
|
||||||
|
this.updateURL(newKnownTalents)
|
||||||
|
}
|
||||||
this.setState({ knownTalents: newKnownTalents })
|
this.setState({ knownTalents: newKnownTalents })
|
||||||
|
|
||||||
const pointString = encodeKnownTalents(newKnownTalents, selectedClass)
|
|
||||||
this.props.history.replace(`/${selectedClass}` + (pointString ? `/${pointString}` : ''))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -77,6 +89,11 @@ export class Calculator extends React.PureComponent<Props> {
|
|||||||
<div className="calculator__points">
|
<div className="calculator__points">
|
||||||
Points: {availablePoints}
|
Points: {availablePoints}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="/shaman/-5505000055523051-55">Shaman test</a>
|
||||||
|
<a href="/shaman/-5595000055523051-55">Shaman test broken</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ export class ClassPicker extends React.PureComponent<Props> {
|
|||||||
{Object.values(classByName).map((c) =>
|
{Object.values(classByName).map((c) =>
|
||||||
<li key={c.id} className={classNameForItem(c, selected)}>
|
<li key={c.id} className={classNameForItem(c, selected)}>
|
||||||
<Link to={`/${c.name.toLowerCase()}`} title={c.name}>
|
<Link to={`/${c.name.toLowerCase()}`} title={c.name}>
|
||||||
<Icon name={c.icon} />
|
<Icon
|
||||||
|
name={c.icon}
|
||||||
|
golden={selected === c.name.toLowerCase()}
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,10 +1,49 @@
|
|||||||
.icon {
|
.icon {
|
||||||
|
position: relative;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
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 {
|
&--medium {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,30 @@
|
|||||||
import React, { FC } from 'react'
|
import React, { FC } from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
import './Icon.scss'
|
import './Icon.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string
|
name: string
|
||||||
size?: 'small' | 'medium' | 'large'
|
size?: 'small' | 'medium' | 'large'
|
||||||
|
golden?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Icon: FC<Props> = ({ name, size = 'medium', children }) => {
|
export const Icon: FC<Props> = ({ name, size = 'medium', golden = false, children }) => {
|
||||||
const url = `https://wow.zamimg.com/images/wow/icons/${size}/${name}.jpg`
|
const className = classNames(
|
||||||
const className = `icon icon--${size}`
|
'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 (
|
return (
|
||||||
<div className={className} style={{ backgroundImage: `url(${url})`}}>
|
<div className={className}>
|
||||||
|
<div className="icon__bg" style={bgStyle} />
|
||||||
|
<div className="icon__frame" />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Calculator } from './Calculator'
|
|||||||
import { ClassPicker } from './ClassPicker'
|
import { ClassPicker } from './ClassPicker'
|
||||||
import { match } from 'react-router-dom'
|
import { match } from 'react-router-dom'
|
||||||
import { RouteComponentProps } from 'react-router'
|
import { RouteComponentProps } from 'react-router'
|
||||||
|
import { decodeKnownTalents } from '../lib/tree'
|
||||||
|
import { classByName } from '../data/classes'
|
||||||
|
|
||||||
interface Props extends RouteComponentProps {
|
interface Props extends RouteComponentProps {
|
||||||
match: match<{
|
match: match<{
|
||||||
@@ -14,16 +16,28 @@ interface Props extends RouteComponentProps {
|
|||||||
export class IndexRoute extends React.PureComponent<Props> {
|
export class IndexRoute extends React.PureComponent<Props> {
|
||||||
static whyDidYouRender = true
|
static whyDidYouRender = true
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { selectedClass } = this.props.match.params
|
||||||
|
if (selectedClass && !classByName[selectedClass]) {
|
||||||
|
this.props.history.replace('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { match, history } = this.props
|
const { match, history } = this.props
|
||||||
const { selectedClass, pointString } = match.params
|
const { selectedClass, pointString } = match.params
|
||||||
|
|
||||||
|
if (selectedClass && !classByName[selectedClass]) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="index">
|
<div className="index">
|
||||||
<ClassPicker selected={selectedClass} />
|
<ClassPicker selected={selectedClass} />
|
||||||
|
|
||||||
{selectedClass &&
|
{selectedClass &&
|
||||||
<Calculator
|
<Calculator
|
||||||
|
initialTalents={pointString && decodeKnownTalents(pointString, selectedClass)}
|
||||||
selectedClass={selectedClass}
|
selectedClass={selectedClass}
|
||||||
history={history}
|
history={history}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,66 +1,102 @@
|
|||||||
|
$row-offset: 30px;
|
||||||
$row-distance: 70px;
|
$row-distance: 70px;
|
||||||
|
|
||||||
@mixin rowStyle($rowNr) {
|
$col-offset: 44px;
|
||||||
top: 30px + (($rowNr) * 70px);
|
$col-distance: 56px;
|
||||||
}
|
|
||||||
|
$color-yellow: #ffd100;
|
||||||
|
$color-green: #1eff00;
|
||||||
|
$color-dark-green: #40bf40;
|
||||||
|
$color-subtle: #9d9d9d;
|
||||||
|
|
||||||
.talent {
|
.talent {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border: 1px solid black;
|
border-radius: 5px;
|
||||||
border-radius: 2px;
|
transition: filter .1s linear;
|
||||||
|
filter: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:after {
|
&--available {
|
||||||
content: "";
|
.talent__status::after {
|
||||||
position: absolute;
|
// background-color: rgba($color-green, .8);
|
||||||
top: 0;
|
box-shadow: inset 0px 0px 6px 3px rgba($color-green, .8);
|
||||||
left: 0;
|
}
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
small {
|
.point-label {
|
||||||
font-size: 8px;
|
color: $color-green;
|
||||||
line-height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--disabled {
|
|
||||||
filter: grayscale(100%)
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(&--disabled) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--maxed {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@for $i from 0 through 6 {
|
|
||||||
&[data-row="#{$i}"] {
|
|
||||||
@include rowStyle($i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--maxed {
|
||||||
|
.talent__status::after {
|
||||||
|
box-shadow: inset 0px 0px 6px 3px rgba($color-yellow, .8);
|
||||||
|
}
|
||||||
|
|
||||||
$col-distance-offset: 44px;
|
.point-label {
|
||||||
$col-distance: 56px;
|
color: $color-yellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&[data-col="0"] { left: $col-distance-offset; }
|
&--disabled {
|
||||||
&[data-col="1"] { left: $col-distance-offset + ($col-distance * 1); }
|
filter: grayscale(100%);
|
||||||
&[data-col="2"] { left: $col-distance-offset + ($col-distance * 2); }
|
|
||||||
&[data-col="3"] { left: $col-distance-offset + ($col-distance * 3); }
|
|
||||||
|
|
||||||
&__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;
|
position: absolute;
|
||||||
padding: 1px 2px;
|
width: 48px;
|
||||||
bottom: -2px;
|
height: 46px;
|
||||||
right: -2px;
|
bottom: -1px;
|
||||||
color: white;
|
left: -4px;
|
||||||
font-size: 12px;
|
background-image: url('/images/icons/large/default.png');
|
||||||
background: black;
|
background-size: cover;
|
||||||
border-radius: 2px;
|
|
||||||
|
&: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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,9 +37,14 @@ export const TalentSlot: FC<Props> = (props) => {
|
|||||||
const showPoints = points > 0 || availablePoints > 0
|
const showPoints = points > 0 || availablePoints > 0
|
||||||
const disabled = props.disabled || !showPoints || !isAvailable(talent, specId, knownTalents)
|
const disabled = props.disabled || !showPoints || !isAvailable(talent, specId, knownTalents)
|
||||||
|
|
||||||
const cn = classNames('talent', {
|
const containerClassNames = classNames('talent', {
|
||||||
'talent--disabled': !!disabled,
|
'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) => {
|
const handleContextMenu = (e) => {
|
||||||
@@ -50,17 +55,21 @@ export const TalentSlot: FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn}
|
className={containerClassNames}
|
||||||
title={talent.ranks[0].toString()}
|
title={talent.ranks[0].toString()}
|
||||||
data-row={talent.row}
|
data-row={talent.row}
|
||||||
data-col={talent.col}
|
data-col={talent.col}
|
||||||
onClick={!disabled ? () => props.onClick(talent.id) : () => {}}
|
onClick={!disabled ? () => props.onClick(talent.id) : () => {}}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
<Icon name={talent.icon} />
|
<div className="talent__status" />
|
||||||
|
<Icon name={talent.icon} size="medium" />
|
||||||
|
|
||||||
{showPoints &&
|
{showPoints && !disabled &&
|
||||||
<div className="talent__points">{points}/{talent.ranks.length}</div>
|
<div className={pointsClassNames}>
|
||||||
|
{points}
|
||||||
|
/{talent.ranks.length}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ import { classByName } from '../data/classes'
|
|||||||
export const MAX_POINTS = 51
|
export const MAX_POINTS = 51
|
||||||
export const MAX_ROWS = 7
|
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.
|
* Returns the overall points spent in the tree.
|
||||||
*/
|
*/
|
||||||
@@ -76,6 +83,7 @@ export const removeTalentPoint = (known: Map<number, number>, talent: TalentData
|
|||||||
|
|
||||||
// No points to reduce for this talent
|
// No points to reduce for this talent
|
||||||
if (currentPoints === 0) {
|
if (currentPoints === 0) {
|
||||||
|
console.warn('no points to reduce')
|
||||||
return known
|
return known
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +93,11 @@ export const removeTalentPoint = (known: Map<number, number>, talent: TalentData
|
|||||||
|
|
||||||
known.forEach((points, talentId) => {
|
known.forEach((points, talentId) => {
|
||||||
const t = talentsBySpec[specId][talentId]
|
const t = talentsBySpec[specId][talentId]
|
||||||
if (t) {
|
if (t && points > 0) {
|
||||||
isDependency = isDependency || t.requires.some((req) => req.id === talent.id)
|
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
|
highestRow = t.row > highestRow ? t.row : highestRow
|
||||||
for (let row = t.row; row < MAX_ROWS; row++) {
|
for (let row = t.row; row < MAX_ROWS; row++) {
|
||||||
cumulativePointsPerRow[row] = (cumulativePointsPerRow[row] || 0) + points
|
cumulativePointsPerRow[row] = (cumulativePointsPerRow[row] || 0) + points
|
||||||
@@ -98,11 +109,18 @@ export const removeTalentPoint = (known: Map<number, number>, talent: TalentData
|
|||||||
const pointsUntilHighestRow = cumulativePointsPerRow[highestRow - 1]
|
const pointsUntilHighestRow = cumulativePointsPerRow[highestRow - 1]
|
||||||
const targetPointsHighestRow = highestRow * 5
|
const targetPointsHighestRow = highestRow * 5
|
||||||
if (talent.row < highestRow && pointsUntilHighestRow - 1 < targetPointsHighestRow) {
|
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
|
return known
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent if another talent depends on this
|
// Prevent if another talent depends on this
|
||||||
if (isDependency) {
|
if (isDependency) {
|
||||||
|
console.warn('is dependency')
|
||||||
return known
|
return known
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,12 +161,7 @@ export function encodeKnownTalents(known: Map<number, number>, className: string
|
|||||||
const { specs } = classByName[className]
|
const { specs } = classByName[className]
|
||||||
for (let i = 0; i < specs.length; i++) {
|
for (let i = 0; i < specs.length; i++) {
|
||||||
const specId = specs[i]
|
const specId = specs[i]
|
||||||
const talents = talentsBySpecArray[specId].sort((a, b) => {
|
const talents = talentsBySpecArray[specId].sort(SORT_TALENTS)
|
||||||
if (a.row === b.row) {
|
|
||||||
return a.col - b.col
|
|
||||||
}
|
|
||||||
return a.row - b.row
|
|
||||||
})
|
|
||||||
string += i > 0 ? '-' : ''
|
string += i > 0 ? '-' : ''
|
||||||
string += removeTrailingCharacters(
|
string += removeTrailingCharacters(
|
||||||
talents.map((talent) => known.get(talent.id, 0)).join(''),
|
talents.map((talent) => known.get(talent.id, 0)).join(''),
|
||||||
@@ -162,7 +175,36 @@ export function encodeKnownTalents(known: Map<number, number>, className: string
|
|||||||
* Decodes a string of points into a Map of talents.
|
* Decodes a string of points into a Map of talents.
|
||||||
*/
|
*/
|
||||||
export function decodeKnownTalents(pointString: string, className: string): Map<number, number> {
|
export function decodeKnownTalents(pointString: string, className: string): Map<number, number> {
|
||||||
return Map()
|
console.log(pointString, className)
|
||||||
|
|
||||||
|
const { specs } = classByName[className]
|
||||||
|
let known = Map<number, number>()
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||