Using WH datasets
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import im from 'immutable'
|
import { List, Map, fromJS } from 'immutable'
|
||||||
import { TalentTree } from './TalentTree';
|
import { TalentTree } from './TalentTree';
|
||||||
import { setPointsInTree } from '../lib/tree';
|
import { modifyPointsInTree, modifyKnownTalents } from '../lib/tree';
|
||||||
|
import { talentsBySpec, specNames } from '../data/talents';
|
||||||
|
import { classByName } from '../data/classes';
|
||||||
|
import { number } from 'prop-types';
|
||||||
|
|
||||||
const createTalent = (name: string, row: number, column: number, ranks: string | string[], type: Talent['type'] = 'talent'): Talent => {
|
const createTalent = (name: string, row: number, column: number, ranks: string | string[], type: Talent['type'] = 'talent'): Talent => {
|
||||||
return {
|
return {
|
||||||
@@ -94,33 +97,41 @@ interface Props {
|
|||||||
pointString?: string // e.g. 2305302300--001
|
pointString?: string // e.g. 2305302300--001
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialSpentPoints: im.List<im.List<number>> = im.fromJS([
|
const initialSpentPoints: List<List<number>> = fromJS([
|
||||||
[], [], []
|
[], [], []
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const initMap = Map<number, number>()
|
||||||
|
|
||||||
export const Calculator: React.FC<Props> = ({ forClass, pointString = '' }) => {
|
export const Calculator: React.FC<Props> = ({ forClass = 'warlock', pointString = '' }) => {
|
||||||
|
const [knownTalents, setKnownTalents] = useState(initMap)
|
||||||
const [spentPoints, setSpentPoints] = useState(initialSpentPoints)
|
const [spentPoints, setSpentPoints] = useState(initialSpentPoints)
|
||||||
const points = pointString.split('')
|
|
||||||
|
|
||||||
console.log({spentPoints})
|
const selectedClass = classByName[forClass]
|
||||||
|
|
||||||
|
console.log(knownTalents)
|
||||||
|
|
||||||
|
const handleTalentPress = (specId: number, talentId: number, modifier: 1 | -1) => {
|
||||||
|
console.log('onTalentPress', { specId, talentId, modifier })
|
||||||
|
|
||||||
|
const talent = talentsBySpec[specId][talentId]
|
||||||
|
|
||||||
|
|
||||||
|
setKnownTalents(modifyKnownTalents(knownTalents, talent, modifier))
|
||||||
|
|
||||||
const onTalentPress = (treeIndex, talentId, clickType) => {
|
|
||||||
console.log('onTalentPress')
|
|
||||||
const newSpentPoints = spentPoints.set(
|
|
||||||
treeIndex,
|
|
||||||
setPointsInTree(spentPoints.get(treeIndex), talentId, 9)
|
|
||||||
)
|
|
||||||
setSpentPoints(newSpentPoints)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="calculator">
|
<div className="calculator">
|
||||||
<TalentTree
|
{selectedClass.specs.map((specId, specIndex) => (
|
||||||
tree={warlockTalents[0]}
|
<TalentTree
|
||||||
spentPoints={spentPoints.get(0)}
|
key={specId}
|
||||||
onTalentPress={(talentId, clickType) => onTalentPress(0, talentId, clickType)}
|
specId={specId}
|
||||||
/>
|
knownTalents={knownTalents}
|
||||||
|
spentPoints={spentPoints.get(specIndex)}
|
||||||
|
onTalentPress={handleTalentPress}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.icon {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
&--medium {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-5
@@ -1,12 +1,18 @@
|
|||||||
import React from 'react'
|
import React, { FC } from 'react'
|
||||||
|
import './Icon.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
name: string
|
||||||
|
size?: 'small' | 'medium' | 'large'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Icon: React.FC<Props> = () => {
|
export const Icon: FC<Props> = ({ name, size = 'medium', children }) => {
|
||||||
|
const url = `https://wow.zamimg.com/images/wow/icons/${size}/${name}.jpg`
|
||||||
|
const className = `icon icon--${size}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="icon">
|
<div className={className} style={{ backgroundImage: `url(${url})`}}>
|
||||||
icon
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,29 @@
|
|||||||
import React from 'react'
|
import React, { FC } from 'react'
|
||||||
import './TalentSlot.scss'
|
import './TalentSlot.scss'
|
||||||
|
import { Icon } from './Icon'
|
||||||
|
import { spells } from '../data/spells'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
key: number
|
key: number
|
||||||
talent: Talent
|
talent: TalentData
|
||||||
/** Points spent */
|
/** Points spent */
|
||||||
points: number
|
points: number
|
||||||
onClick?: (e: any) => void
|
onClick?: (e: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TalentSlot: React.FC<Props> = (props) => {
|
export const TalentSlot: FC<Props> = (props) => {
|
||||||
const { talent, points } = props
|
const { talent, points } = props
|
||||||
const requiredPointsSpent = talent.row * 5
|
const requiredPointsSpent = talent.row * 5
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="talent"
|
className="talent"
|
||||||
title={talent.name}
|
title={talent.ranks[0].toString()}
|
||||||
data-row={talent.row}
|
data-row={talent.row}
|
||||||
data-col={talent.column}
|
data-col={talent.col}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
>
|
||||||
<small>{talent.name}</small>
|
<Icon name={talent.icon} />
|
||||||
<div className="talent__rank">{points}/{talent.ranks.length}</div>
|
<div className="talent__rank">{points}/{talent.ranks.length}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,33 +1,38 @@
|
|||||||
import React from 'react'
|
import React, { MouseEvent } from 'react'
|
||||||
import { List } from 'immutable'
|
import { List, Map } from 'immutable'
|
||||||
import { TalentSlot } from './TalentSlot';
|
import { TalentSlot } from './TalentSlot';
|
||||||
|
import { getTreePointCount } from '../lib/tree';
|
||||||
|
import { talentsBySpec, specNames } from '../data/talents';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tree: TalentTree
|
specId: number
|
||||||
spentPoints: List<number>
|
spentPoints: List<number>
|
||||||
|
knownTalents: Map<number, number>
|
||||||
onTalentPress: TalentClickHandler
|
onTalentPress: TalentClickHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TalentTree: React.FC<Props> = ({ tree, spentPoints, onTalentPress }) => {
|
export const TalentTree: React.FC<Props> = ({ specId, spentPoints, knownTalents, onTalentPress }) => {
|
||||||
const { talents } = tree
|
const talents = Object.values(talentsBySpec[specId])
|
||||||
|
|
||||||
const handleTalentPress = (index) => {
|
const handleTalentPress = (talentId: number) => {
|
||||||
return (e) => {
|
return (e: MouseEvent) => {
|
||||||
onTalentPress(index, 'add')
|
onTalentPress(specId, talentId, e.shiftKey ? -1 : 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tree">
|
<div className="tree">
|
||||||
<h2>{tree.name}</h2>
|
<h2>{specNames[specId]}</h2>
|
||||||
{talents.map((talent, index) =>
|
{talents.map((talent, index) =>
|
||||||
<TalentSlot
|
<TalentSlot
|
||||||
key={index}
|
key={talent.id}
|
||||||
talent={talent}
|
talent={talent}
|
||||||
points={spentPoints.get(index, 0)}
|
points={knownTalents.get(talent.id, 0)}
|
||||||
onClick={handleTalentPress(index)}
|
onClick={handleTalentPress(talent.id)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
Spent: {getTreePointCount(spentPoints)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
interface ClassData {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
specs: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const classes: ClassData[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Warrior',
|
||||||
|
icon: 'class_warrior',
|
||||||
|
specs: [161, 164, 163]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Paladin',
|
||||||
|
icon: 'class_paladin',
|
||||||
|
specs: [382, 383, 381]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Hunter',
|
||||||
|
icon: 'class_hunter',
|
||||||
|
specs: [361, 363, 362]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Rogue',
|
||||||
|
icon: 'class_rogue',
|
||||||
|
specs: [182, 181, 183]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'Priest',
|
||||||
|
icon: 'class_priest',
|
||||||
|
specs: [201, 202, 203]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: 'Shaman',
|
||||||
|
icon: 'class_shaman',
|
||||||
|
specs: [261, 263, 262]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: 'Mage',
|
||||||
|
icon: 'class_mage',
|
||||||
|
specs: [81, 41, 61]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: 'Warlock',
|
||||||
|
icon: 'class_warlock',
|
||||||
|
specs: [302, 303, 301]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: 'Druid',
|
||||||
|
icon: 'class_druid',
|
||||||
|
specs: [283, 281, 282]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const classById: {[key: number]: ClassData} =
|
||||||
|
classes.reduce((previousValue: object, currentValue: ClassData) => {
|
||||||
|
return {
|
||||||
|
...previousValue,
|
||||||
|
[currentValue.id]: currentValue
|
||||||
|
}
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
export const classByName: {[key: string]: ClassData} =
|
||||||
|
classes.reduce((previousValue: object, currentValue: ClassData) => {
|
||||||
|
return {
|
||||||
|
...previousValue,
|
||||||
|
[currentValue.name.toLowerCase()]: currentValue
|
||||||
|
}
|
||||||
|
}, {})
|
||||||
+8637
File diff suppressed because it is too large
Load Diff
+3745
File diff suppressed because it is too large
Load Diff
+34
-15
@@ -1,21 +1,40 @@
|
|||||||
import im from 'immutable'
|
import im from 'immutable'
|
||||||
import { setPointsInTree } from './tree'
|
import {
|
||||||
|
setTalentPointsInTree,
|
||||||
|
getTreePointCount
|
||||||
|
} from './tree'
|
||||||
|
|
||||||
it('sets points on an empty tree', () => {
|
|
||||||
const tree = im.List()
|
describe('setTalentPointsInTree', () => {
|
||||||
expect(setPointsInTree(tree, 2, 5).toJS()).toEqual([0, 0, 5])
|
it('sets points on an empty tree', () => {
|
||||||
|
const tree = im.List()
|
||||||
|
expect(setTalentPointsInTree(tree, 2, 5).toJS()).toEqual([0, 0, 5])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets points in the end of the current range', () => {
|
||||||
|
const tree = im.List([0, 1])
|
||||||
|
expect(setTalentPointsInTree(tree, 2, 5).toJS()).toEqual([0, 1, 5])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets points in the middle of the current range', () => {
|
||||||
|
const tree = im.List([0, 0, 0, 0, 0, 0, 5])
|
||||||
|
expect(setTalentPointsInTree(tree, 2, 5).toJS()).toEqual([0, 0, 5, 0, 0, 0, 5])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not mutate the tree for points already set', () => {
|
||||||
|
const tree = im.List([0, 3, 2, 0, 5])
|
||||||
|
expect(setTalentPointsInTree(tree, 1, 3)).toStrictEqual(tree)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets points in the end of the current range', () => {
|
describe('getTreePointCount', () => {
|
||||||
const tree = im.List([0, 1])
|
it('returns proper count', () => {
|
||||||
expect(setPointsInTree(tree, 2, 5).toJS()).toEqual([0, 1, 5])
|
const result = getTreePointCount(im.List([0, 0, 4, 5, 3, 0, 0]))
|
||||||
})
|
expect(result).toBe(12)
|
||||||
it('sets points in the middle of the current range', () => {
|
})
|
||||||
const tree = im.List([0, 0, 0, 0, 0, 0, 5])
|
|
||||||
expect(setPointsInTree(tree, 2, 5).toJS()).toEqual([0, 0, 5, 0, 0, 0, 5])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not mutate the tree for points already set', () => {
|
it('returns 0 for empty list', () => {
|
||||||
const tree = im.List([0, 3, 2, 0, 5])
|
const result = getTreePointCount(im.List())
|
||||||
expect(setPointsInTree(tree, 1, 3)).toStrictEqual(tree)
|
expect(result).toBe(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
+89
-5
@@ -1,9 +1,93 @@
|
|||||||
import im from 'immutable'
|
import { List, Map, fromJS } from 'immutable'
|
||||||
|
|
||||||
export function setPointsInTree(tree: im.List<number>, index: number, points: number) {
|
/**
|
||||||
|
* Sets proper values for a tree, filling in 0s in between.
|
||||||
|
*/
|
||||||
|
export function setTalentPointsInTree(tree: List<number>, talentIndex: number, points: number): List<number> {
|
||||||
// Ensure all values until `index` are set, otherwise set to 0
|
// Ensure all values until `index` are set, otherwise set to 0
|
||||||
for (let i = tree.size; i <= index; i++) {
|
for (let i = tree.size; i < talentIndex; i++) {
|
||||||
tree = tree.set(i, i === index ? points : 0)
|
tree = tree.set(i, 0)
|
||||||
}
|
}
|
||||||
return tree
|
return tree.set(talentIndex, points)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the overall points spent in the tree.
|
||||||
|
*/
|
||||||
|
export function getTreePointCount(tree: List<number>): number {
|
||||||
|
return tree.reduce((reduction, value) => value + reduction, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canRemovePoint() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canSetPoint() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const modifyPointsInTree = (tree: List<number>, talent: TalentData, talentIndex: number, modifier: 1 | -1): List<number> => {
|
||||||
|
const currentPoints = tree.get(talentIndex, 0)
|
||||||
|
|
||||||
|
// TODO: We should prevent reducing talent points on a row when it is a dependency for points already spent in the next row.
|
||||||
|
|
||||||
|
// TODO: Support for specific Talent dependency requirement.
|
||||||
|
|
||||||
|
// TODO: Spend a maximum of 51 points
|
||||||
|
|
||||||
|
// Check we have the required amount of points spent in the tree for this talent
|
||||||
|
const requiredPoints = talent.row * 5
|
||||||
|
const spentPointCount = getTreePointCount(tree)
|
||||||
|
if (requiredPoints > spentPointCount) return tree
|
||||||
|
|
||||||
|
let newPoints = currentPoints
|
||||||
|
if (modifier === 1) {
|
||||||
|
if (currentPoints === talent.ranks.length) return tree
|
||||||
|
newPoints = currentPoints + 1
|
||||||
|
} else if (modifier === -1) {
|
||||||
|
if (currentPoints === 0) return tree
|
||||||
|
newPoints = currentPoints - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return setTalentPointsInTree(tree, talentIndex, newPoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const modifyKnownTalents = (known: Map<number, number>, talent: TalentData, modifier: 1 | -1): Map<number, number> => {
|
||||||
|
const currentPoints = known.get(talent.id, 0)
|
||||||
|
|
||||||
|
// TODO: We should prevent reducing talent points on a row when it is a dependency for points already spent in the next row.
|
||||||
|
|
||||||
|
// TODO: Support for specific Talent dependency requirement.
|
||||||
|
|
||||||
|
// TODO: Spend a maximum of 51 points
|
||||||
|
|
||||||
|
// TODO: Check we have the required amount of points spent in the tree for this talent
|
||||||
|
const requiredPoints = talent.row * 5
|
||||||
|
// const spentPointCount = known.get(talent.id, 0)
|
||||||
|
// if (requiredPoints > spentPointCount) return tree
|
||||||
|
|
||||||
|
let newPoints = currentPoints
|
||||||
|
if (modifier === 1) {
|
||||||
|
if (currentPoints === talent.ranks.length) return known
|
||||||
|
newPoints = currentPoints + 1
|
||||||
|
} else if (modifier === -1) {
|
||||||
|
if (currentPoints === 0) return known
|
||||||
|
newPoints = currentPoints - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPoints === 0
|
||||||
|
? known.remove(talent.id)
|
||||||
|
: known.set(talent.id, newPoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parsePointString(str: string): List<List<number>> {
|
||||||
|
const list: Array<number[]> = []
|
||||||
|
const trees = str.split('-')
|
||||||
|
|
||||||
|
trees.map((stringForTree, index) => {
|
||||||
|
const points = stringForTree.split('').map(a => parseInt(a, 2))
|
||||||
|
list[index] = points
|
||||||
|
})
|
||||||
|
|
||||||
|
return fromJS(list)
|
||||||
}
|
}
|
||||||
Vendored
+21
-1
@@ -5,6 +5,26 @@ interface TalentTree {
|
|||||||
talents: Talent[]
|
talents: Talent[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TalentData {
|
||||||
|
/** ID for the Talent */
|
||||||
|
id: number
|
||||||
|
/** Row for talent. Starts at 0 */
|
||||||
|
row: number
|
||||||
|
/** Column for talent. Starts at 0 */
|
||||||
|
col: number
|
||||||
|
/** Icon */
|
||||||
|
icon: string
|
||||||
|
/** Array of spell IDs */
|
||||||
|
ranks: number[]
|
||||||
|
/** Talent dependencies for this talent */
|
||||||
|
requires: Array<{
|
||||||
|
/** Talent ID */
|
||||||
|
id: number
|
||||||
|
/** Amount of points required in this talent */
|
||||||
|
qty: number
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
interface Talent {
|
interface Talent {
|
||||||
name: string
|
name: string
|
||||||
row: number
|
row: number
|
||||||
@@ -14,4 +34,4 @@ interface Talent {
|
|||||||
prerequisite?: [number, number] | number // [row, column] OR index
|
prerequisite?: [number, number] | number // [row, column] OR index
|
||||||
}
|
}
|
||||||
|
|
||||||
type TalentClickHandler = (talentId, clickType: 'add' | 'remove') => void
|
type TalentClickHandler = (specId: number, talentId: number, modifier: 1 | -1) => void
|
||||||
Reference in New Issue
Block a user