Using WH datasets
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import im from 'immutable'
|
||||
import { List, Map, fromJS } from 'immutable'
|
||||
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 => {
|
||||
return {
|
||||
@@ -94,33 +97,41 @@ interface Props {
|
||||
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 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 (
|
||||
<div className="calculator">
|
||||
<TalentTree
|
||||
tree={warlockTalents[0]}
|
||||
spentPoints={spentPoints.get(0)}
|
||||
onTalentPress={(talentId, clickType) => onTalentPress(0, talentId, clickType)}
|
||||
/>
|
||||
{selectedClass.specs.map((specId, specIndex) => (
|
||||
<TalentTree
|
||||
key={specId}
|
||||
specId={specId}
|
||||
knownTalents={knownTalents}
|
||||
spentPoints={spentPoints.get(specIndex)}
|
||||
onTalentPress={handleTalentPress}
|
||||
/>
|
||||
))}
|
||||
</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 (
|
||||
<div className="icon">
|
||||
icon
|
||||
<div className={className} style={{ backgroundImage: `url(${url})`}}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +1,29 @@
|
||||
import React from 'react'
|
||||
import React, { FC } from 'react'
|
||||
import './TalentSlot.scss'
|
||||
import { Icon } from './Icon'
|
||||
import { spells } from '../data/spells'
|
||||
|
||||
interface Props {
|
||||
key: number
|
||||
talent: Talent
|
||||
talent: TalentData
|
||||
/** Points spent */
|
||||
points: number
|
||||
onClick?: (e: any) => void
|
||||
}
|
||||
|
||||
export const TalentSlot: React.FC<Props> = (props) => {
|
||||
export const TalentSlot: FC<Props> = (props) => {
|
||||
const { talent, points } = props
|
||||
const requiredPointsSpent = talent.row * 5
|
||||
|
||||
return (
|
||||
<div
|
||||
className="talent"
|
||||
title={talent.name}
|
||||
title={talent.ranks[0].toString()}
|
||||
data-row={talent.row}
|
||||
data-col={talent.column}
|
||||
data-col={talent.col}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<small>{talent.name}</small>
|
||||
<Icon name={talent.icon} />
|
||||
<div className="talent__rank">{points}/{talent.ranks.length}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,33 +1,38 @@
|
||||
import React from 'react'
|
||||
import { List } from 'immutable'
|
||||
import React, { MouseEvent } from 'react'
|
||||
import { List, Map } from 'immutable'
|
||||
import { TalentSlot } from './TalentSlot';
|
||||
import { getTreePointCount } from '../lib/tree';
|
||||
import { talentsBySpec, specNames } from '../data/talents';
|
||||
|
||||
interface Props {
|
||||
tree: TalentTree
|
||||
specId: number
|
||||
spentPoints: List<number>
|
||||
knownTalents: Map<number, number>
|
||||
onTalentPress: TalentClickHandler
|
||||
}
|
||||
|
||||
export const TalentTree: React.FC<Props> = ({ tree, spentPoints, onTalentPress }) => {
|
||||
const { talents } = tree
|
||||
export const TalentTree: React.FC<Props> = ({ specId, spentPoints, knownTalents, onTalentPress }) => {
|
||||
const talents = Object.values(talentsBySpec[specId])
|
||||
|
||||
const handleTalentPress = (index) => {
|
||||
return (e) => {
|
||||
onTalentPress(index, 'add')
|
||||
const handleTalentPress = (talentId: number) => {
|
||||
return (e: MouseEvent) => {
|
||||
onTalentPress(specId, talentId, e.shiftKey ? -1 : 1)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tree">
|
||||
<h2>{tree.name}</h2>
|
||||
<h2>{specNames[specId]}</h2>
|
||||
{talents.map((talent, index) =>
|
||||
<TalentSlot
|
||||
key={index}
|
||||
key={talent.id}
|
||||
talent={talent}
|
||||
points={spentPoints.get(index, 0)}
|
||||
onClick={handleTalentPress(index)}
|
||||
points={knownTalents.get(talent.id, 0)}
|
||||
onClick={handleTalentPress(talent.id)}
|
||||
/>
|
||||
)}
|
||||
|
||||
Spent: {getTreePointCount(spentPoints)}
|
||||
</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 { setPointsInTree } from './tree'
|
||||
import {
|
||||
setTalentPointsInTree,
|
||||
getTreePointCount
|
||||
} from './tree'
|
||||
|
||||
it('sets points on an empty tree', () => {
|
||||
const tree = im.List()
|
||||
expect(setPointsInTree(tree, 2, 5).toJS()).toEqual([0, 0, 5])
|
||||
|
||||
describe('setTalentPointsInTree', () => {
|
||||
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', () => {
|
||||
const tree = im.List([0, 1])
|
||||
expect(setPointsInTree(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(setPointsInTree(tree, 2, 5).toJS()).toEqual([0, 0, 5, 0, 0, 0, 5])
|
||||
})
|
||||
describe('getTreePointCount', () => {
|
||||
it('returns proper count', () => {
|
||||
const result = getTreePointCount(im.List([0, 0, 4, 5, 3, 0, 0]))
|
||||
expect(result).toBe(12)
|
||||
})
|
||||
|
||||
it('does not mutate the tree for points already set', () => {
|
||||
const tree = im.List([0, 3, 2, 0, 5])
|
||||
expect(setPointsInTree(tree, 1, 3)).toStrictEqual(tree)
|
||||
it('returns 0 for empty list', () => {
|
||||
const result = getTreePointCount(im.List())
|
||||
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
|
||||
for (let i = tree.size; i <= index; i++) {
|
||||
tree = tree.set(i, i === index ? points : 0)
|
||||
for (let i = tree.size; i < talentIndex; i++) {
|
||||
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[]
|
||||
}
|
||||
|
||||
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 {
|
||||
name: string
|
||||
row: number
|
||||
@@ -14,4 +34,4 @@ interface Talent {
|
||||
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