Better prototype with functioning dependencies and right-clicking
This commit is contained in:
+28
-114
@@ -1,137 +1,51 @@
|
||||
import React, { useState } from 'react';
|
||||
import { List, Map, fromJS } from 'immutable'
|
||||
import { Map } from 'immutable'
|
||||
import { TalentTree } from './TalentTree';
|
||||
import { modifyPointsInTree, modifyKnownTalents } from '../lib/tree';
|
||||
import { talentsBySpec, specNames } from '../data/talents';
|
||||
import {
|
||||
modifyTalentPoint,
|
||||
calcAvailablePoints
|
||||
} from '../lib/tree';
|
||||
import { talentsBySpec } 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 {
|
||||
name,
|
||||
row,
|
||||
column,
|
||||
ranks: typeof ranks === 'string' ? [ranks] : ranks,
|
||||
type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Max rows: 7
|
||||
* Max cols: 4
|
||||
*
|
||||
* verify: no talent on same [row, column] combination
|
||||
* potentially: sort talents based on row and column so order doesn't matter
|
||||
*/
|
||||
|
||||
const warlockTalents: TalentTree[] = [
|
||||
// Affliction
|
||||
{
|
||||
id: 164,
|
||||
name: 'Affliction',
|
||||
icon: 'https://wow.zamimg.com/images/wow/icons/small/spell_shadow_deathcoil.jpg',
|
||||
talents: [
|
||||
// Row 1
|
||||
createTalent('Suppression', 0, 1, [
|
||||
'Reduces the chance for enemies to resist your Affliction spells by 2%.',
|
||||
'Reduces the chance for enemies to resist your Affliction spells by 4%.',
|
||||
'Reduces the chance for enemies to resist your Affliction spells by 6%.',
|
||||
'Reduces the chance for enemies to resist your Affliction spells by 8%.',
|
||||
'Reduces the chance for enemies to resist your Affliction spells by 10%.',
|
||||
]),
|
||||
createTalent('Improved Corruption', 0, 2, [
|
||||
'Reduces the casting time of your corruption spell by 0.4 sec.',
|
||||
'Reduces the casting time of your corruption spell by 0.8 sec.',
|
||||
'Reduces the casting time of your corruption spell by 1.2 sec.',
|
||||
'Reduces the casting time of your corruption spell by 1.6 sec.',
|
||||
'Reduces the casting time of your corruption spell by 2 sec.',
|
||||
]),
|
||||
|
||||
// Row 2
|
||||
createTalent('Improved Curse of Weakness', 1, 0, [
|
||||
'Increases the effect of your Curse of Weakness by 6%.',
|
||||
'Increases the effect of your Curse of Weakness by 13%.',
|
||||
'Increases the effect of your Curse of Weakness by 20%.'
|
||||
]),
|
||||
createTalent('Improved Drain Soul', 1, 1, [
|
||||
'Gives you a 50% chance to get a 100% increase to your Mana regeneration for 10 sec if the target is killed by you while you drain its soul. In addition your Mana may continue to regenerate while casting at 50% of normal.',
|
||||
'Gives you a 100% chance to get a 100% increase to your Mana regeneration for 10 sec if the target is killed by you while you drain its soul. In addition your Mana may continue to regenerate while casting at 50% of normal.',
|
||||
]),
|
||||
createTalent('Improved Life Tap', 1, 2, [
|
||||
'Increases the amount of Mana awarded by your Life Tap spell by 10%.',
|
||||
'Increases the amount of Mana awarded by your Life Tap spell by 20%.',
|
||||
]),
|
||||
createTalent('Improved Drain Life', 1, 3, [
|
||||
'Increases the Health drained by your Drain Life spell by 2%.',
|
||||
'Increases the Health drained by your Drain Life spell by 4%.',
|
||||
'Increases the Health drained by your Drain Life spell by 6%.',
|
||||
'Increases the Health drained by your Drain Life spell by 8%.',
|
||||
'Increases the Health drained by your Drain Life spell by 10%.',
|
||||
]),
|
||||
|
||||
// Row 3
|
||||
createTalent('Improved Curse of Agony', 2, 0, [
|
||||
'Increases the damage done by your Curse of Agony by 2%.',
|
||||
'Increases the damage done by your Curse of Agony by 4%.',
|
||||
'Increases the damage done by your Curse of Agony by 6%.',
|
||||
]),
|
||||
createTalent('Fel Concentration', 2, 1, [
|
||||
'Gives you a 14% chance to avoid interruption caused by damage while channeling the Drain Life, Drain Mana, or Drain Soul spell.',
|
||||
'Gives you a 28% chance to avoid interruption caused by damage while channeling the Drain Life, Drain Mana, or Drain Soul spell.',
|
||||
'Gives you a 42% chance to avoid interruption caused by damage while channeling the Drain Life, Drain Mana, or Drain Soul spell.',
|
||||
'Gives you a 56% chance to avoid interruption caused by damage while channeling the Drain Life, Drain Mana, or Drain Soul spell.',
|
||||
'Gives you a 70% chance to avoid interruption caused by damage while channeling the Drain Life, Drain Mana, or Drain Soul spell.',
|
||||
]),
|
||||
createTalent('Amplify Curse', 2, 2, [
|
||||
'Increases the effect of your next Curse of Weakness or Curse of Agony by 50%, or your next Curse of Exhaustion by 20%. Lasts 30 sec.',
|
||||
]),
|
||||
]
|
||||
}
|
||||
// Demonoloy
|
||||
|
||||
// Destruction
|
||||
]
|
||||
|
||||
interface Props {
|
||||
forClass: string
|
||||
pointString?: string // e.g. 2305302300--001
|
||||
}
|
||||
|
||||
const initialSpentPoints: List<List<number>> = fromJS([
|
||||
[], [], []
|
||||
])
|
||||
|
||||
const initMap = Map<number, number>()
|
||||
|
||||
export const Calculator: React.FC<Props> = ({ forClass = 'warlock', pointString = '' }) => {
|
||||
const [knownTalents, setKnownTalents] = useState(initMap)
|
||||
const [spentPoints, setSpentPoints] = useState(initialSpentPoints)
|
||||
|
||||
const selectedClass = classByName[forClass]
|
||||
|
||||
console.log(knownTalents)
|
||||
const availablePoints = calcAvailablePoints(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))
|
||||
|
||||
setKnownTalents(
|
||||
modifyTalentPoint(knownTalents, talent, modifier)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="calculator">
|
||||
{selectedClass.specs.map((specId, specIndex) => (
|
||||
<TalentTree
|
||||
key={specId}
|
||||
specId={specId}
|
||||
knownTalents={knownTalents}
|
||||
spentPoints={spentPoints.get(specIndex)}
|
||||
onTalentPress={handleTalentPress}
|
||||
/>
|
||||
))}
|
||||
<div className="trees">
|
||||
{selectedClass.specs.map((specId, specIndex) => (
|
||||
<TalentTree
|
||||
key={specId}
|
||||
specId={specId}
|
||||
availablePoints={availablePoints}
|
||||
knownTalents={knownTalents}
|
||||
onTalentPress={handleTalentPress}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="calculator__points">
|
||||
Points: {availablePoints}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
(Calculator as any).whyDidYouRender = true
|
||||
@@ -1,4 +1,5 @@
|
||||
.icon {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
.talent {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid black;
|
||||
border-radius: 2px;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 8px;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
filter: grayscale(100%)
|
||||
}
|
||||
|
||||
&:not(&--disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&--maxed {
|
||||
|
||||
}
|
||||
|
||||
$row-distance: 70px;
|
||||
|
||||
&[data-row="0"] { top: $row-distance; }
|
||||
&[data-row="1"] { top: $row-distance * 2; }
|
||||
&[data-row="2"] { top: $row-distance * 3; }
|
||||
&[data-row="3"] { top: $row-distance * 4; }
|
||||
&[data-row="4"] { top: $row-distance * 5; }
|
||||
&[data-row="5"] { top: $row-distance * 6; }
|
||||
&[data-row="6"] { top: $row-distance * 7; }
|
||||
|
||||
$col-distance-offset: 30px;
|
||||
$col-distance: 50px;
|
||||
|
||||
&[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); }
|
||||
|
||||
&__points {
|
||||
position: absolute;
|
||||
padding: 1px 2px;
|
||||
bottom: -2px;
|
||||
right: -2px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
background: black;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,65 @@
|
||||
import React, { FC } from 'react'
|
||||
import './TalentSlot.scss'
|
||||
import React, { FC } from 'react'
|
||||
import { Icon } from './Icon'
|
||||
import classNames from 'classnames'
|
||||
import { spells } from '../data/spells'
|
||||
import { Map } from 'immutable';
|
||||
import { getPointsInSpec, calcMeetsRequirements } from '../lib/tree';
|
||||
|
||||
interface Props {
|
||||
key: number
|
||||
talent: TalentData
|
||||
/** Points spent */
|
||||
points: number
|
||||
specId: number
|
||||
availablePoints: number
|
||||
/** All spent talents */
|
||||
knownTalents: Map<number, number>
|
||||
/** Disabled override */
|
||||
disabled?: boolean
|
||||
onClick?: (e: any) => void
|
||||
onRightClick?: (e: any) => void
|
||||
}
|
||||
|
||||
const isAvailable = (talent: TalentData, specId: number, knownTalents: Map<number, number>): boolean => {
|
||||
// Dependent on other talents?
|
||||
if (!calcMeetsRequirements(talent, knownTalents)) {
|
||||
return false
|
||||
}
|
||||
const pointsInSpec = getPointsInSpec(specId, knownTalents)
|
||||
return talent.row * 5 <= pointsInSpec
|
||||
}
|
||||
|
||||
export const TalentSlot: FC<Props> = (props) => {
|
||||
const { talent, points } = props
|
||||
const requiredPointsSpent = talent.row * 5
|
||||
const { talent, specId, knownTalents, availablePoints } = props
|
||||
const points = knownTalents.get(talent.id, 0)
|
||||
const showPoints = points > 0 || availablePoints > 0
|
||||
const disabled = false // props.disabled || !showPoints || !isAvailable(talent, specId, knownTalents)
|
||||
|
||||
const cn = classNames('talent', {
|
||||
'talent--disabled': !!disabled,
|
||||
'talent--maxed': points >= talent.ranks.length
|
||||
})
|
||||
|
||||
const handleContextMenu = (e) => {
|
||||
if (props.onRightClick) props.onRightClick(e)
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="talent"
|
||||
className={cn}
|
||||
title={talent.ranks[0].toString()}
|
||||
data-row={talent.row}
|
||||
data-col={talent.col}
|
||||
onClick={props.onClick}
|
||||
onClick={!disabled ? props.onClick : () => {}}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<Icon name={talent.icon} />
|
||||
<div className="talent__rank">{points}/{talent.ranks.length}</div>
|
||||
|
||||
{showPoints &&
|
||||
<div className="talent__points">{points}/{talent.ranks.length}</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
(TalentSlot as any).whyDidYouRender = true
|
||||
@@ -1,38 +1,45 @@
|
||||
import React, { MouseEvent } from 'react'
|
||||
import { List, Map } from 'immutable'
|
||||
import React, { MouseEvent, useCallback } from 'react'
|
||||
import { Map } from 'immutable'
|
||||
import { TalentSlot } from './TalentSlot';
|
||||
import { getTreePointCount } from '../lib/tree';
|
||||
import { getPointsInSpec } from '../lib/tree';
|
||||
import { talentsBySpec, specNames } from '../data/talents';
|
||||
|
||||
interface Props {
|
||||
specId: number
|
||||
spentPoints: List<number>
|
||||
availablePoints: number
|
||||
knownTalents: Map<number, number>
|
||||
onTalentPress: TalentClickHandler
|
||||
}
|
||||
|
||||
export const TalentTree: React.FC<Props> = ({ specId, spentPoints, knownTalents, onTalentPress }) => {
|
||||
export const TalentTree: React.FC<Props> = ({ specId, knownTalents, availablePoints, onTalentPress }) => {
|
||||
const talents = Object.values(talentsBySpec[specId])
|
||||
|
||||
const handleTalentPress = (talentId: number) => {
|
||||
const handleTalentPress = useCallback((talentId: number, modifier: 1 | -1) => {
|
||||
return (e: MouseEvent) => {
|
||||
onTalentPress(specId, talentId, e.shiftKey ? -1 : 1)
|
||||
onTalentPress(specId, talentId, modifier)
|
||||
}
|
||||
}, [specId, onTalentPress])
|
||||
|
||||
const style = {
|
||||
backgroundImage: `url("https://wow.zamimg.com/images/wow/talents/backgrounds/classic/${specId}.jpg")`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tree">
|
||||
<h2>{specNames[specId]}</h2>
|
||||
<div className="tree" style={style}>
|
||||
<h2>{specNames[specId]} ({getPointsInSpec(specId, knownTalents)})</h2>
|
||||
{talents.map((talent, index) =>
|
||||
<TalentSlot
|
||||
key={talent.id}
|
||||
specId={specId}
|
||||
talent={talent}
|
||||
points={knownTalents.get(talent.id, 0)}
|
||||
onClick={handleTalentPress(talent.id)}
|
||||
availablePoints={availablePoints}
|
||||
knownTalents={knownTalents}
|
||||
onClick={handleTalentPress(talent.id, 1)}
|
||||
onRightClick={handleTalentPress(talent.id, -1)}
|
||||
/>
|
||||
)}
|
||||
|
||||
Spent: {getTreePointCount(spentPoints)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
(TalentTree as any).whyDidYouRender = true
|
||||
Reference in New Issue
Block a user