diff --git a/src/App.tsx b/src/App.tsx index 3e20e87..df5d1e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -38,12 +38,12 @@ function App() { - + - + diff --git a/src/cc.scss b/src/cc.scss index 342ca5c..ab58bac 100644 --- a/src/cc.scss +++ b/src/cc.scss @@ -76,6 +76,10 @@ button.btn-primary { text-align: center; } +.type-display { + max-width: 10rem; +} + .damage-display-container { margin: 20px; background-color: rgb(240,240,240); @@ -112,8 +116,8 @@ button.btn-primary { } -.h-100vh { - height: 100vh; +.h-95vh { + height: 95vh; } .overflow-scroll { overflow-x: hidden; diff --git a/src/features/item/DamageCalculator.tsx b/src/features/item/DamageCalculator.tsx index 1b1d452..7fbe5eb 100644 --- a/src/features/item/DamageCalculator.tsx +++ b/src/features/item/DamageCalculator.tsx @@ -10,10 +10,9 @@ import RangeSlider from 'react-bootstrap-range-slider'; import { DamageGraph } from "./DamageGraph"; import NumericInput from 'react-numeric-input'; -import heart from './images/half_heart_lg.png'; import { DamageSummaryTable } from "./DamageSummary"; import { WeaponEditor } from "../weapon/WeaponEditor"; -import { Collapseable } from "./Parts"; +import { Collapseable, HalfHeart } from "./Parts"; import EditInPlace from "./EditInPlace"; import { baseDamageType } from "./damageTypes"; import Icon from "./Icons"; @@ -47,7 +46,7 @@ export function DamageCalculator(props : DamageCalculatorType) { - +
void, inner?: React.ReactNode, - title?: string, + title?: React.ReactNode, options?: React.ReactNode, className?: string } @@ -66,4 +78,66 @@ export function Collapseable(props: CollapseableType) { ); +} +export function HalfHeart() { + return ( + + ); +} +export function HealthBar(props: {health: number}) { + return +} +export function ArmorBar(props: {armor: number}) { + return +} +export function ToughnessBar(props: {toughness: number}) { + return +} +interface DynamicBarType { + value: number, + max?: number, + full: string, + half: string, + empty?: string +} +export function DynamicBar(props: DynamicBarType) { + let val = Math.round(props.value); + let full = Math.floor(val / 2); + let half = val % 2 === 1; + let elements = []; + for (let i = 0 ; i < full ; i++) { + elements.push( + + ) + } + if (half) { + elements.push( + + ) + } + if (props.max && props.empty) { + let left = Math.floor((props.max - val)/2); + for (let i = 0 ; i < left ; i++) { + elements.push( + + ); + } + } + return {elements}; } \ No newline at end of file diff --git a/src/features/item/SetupContainer.tsx b/src/features/item/SetupContainer.tsx index 6f75192..6055a6d 100644 --- a/src/features/item/SetupContainer.tsx +++ b/src/features/item/SetupContainer.tsx @@ -4,10 +4,11 @@ import { Row, Col, ButtonGroup, Button } from "react-bootstrap"; import { useSelector } from "react-redux"; import { useAppDispatch } from "../../app/hooks"; import { addSetup, removeSetup, selectEntity } from "./activeSlice"; -import { Entity, getDefaultSetup, maxSetups, summary } from "./entity"; +import { getSetArmor, getSetToughness } from "./armor"; +import { Entity, getDefaultSetup, maxHealth, maxSetups, summary } from "./entity"; import { EntityContainer } from "./EntityContainer"; import Icon from "./Icons"; -import { Collapseable } from "./Parts"; +import { ArmorBar, Collapseable, HealthBar, ToughnessBar } from "./Parts"; import { getPresetColor } from "./Utils"; const _ = require('lodash'); @@ -37,9 +38,19 @@ export function SetupContainer() { } > } - title={summary(item)} + title={ + <> + {" " + summary(item) + " "} + + | + + | + + + } options={ + {!maxSetups(entities) ? : "" } diff --git a/src/features/item/Utils.ts b/src/features/item/Utils.ts index eccca2d..6913bd4 100644 --- a/src/features/item/Utils.ts +++ b/src/features/item/Utils.ts @@ -1,4 +1,5 @@ import functionPlot from "function-plot"; +import { bound } from "./maths"; var Color = require('color'); export function range(from: number, to: number) { @@ -12,7 +13,14 @@ export function round(num : number) { return Math.round((num + Number.EPSILON) * 100) / 100; } export function getColor(max: number, value: number) { - return Color.hsl(180*(value/max),70,70); + return Color.hsl(180*(bound(value/max,0,1)),70,70); +} +export function getDeltaColor(max: number, value: number) { + if (value > 0) { + return Color.rgb(255 - (-value/max*255), 255, 255 - (-value/max*255)); + } else { + return Color.rgb(255, 255 - (-value/max*255), 255 - (-value/max*255)); + } } export function getPresetColor(index: number) { return functionPlot.globals.COLORS[index]; diff --git a/src/features/item/activeSlice.ts b/src/features/item/activeSlice.ts index fac3813..a62534e 100644 --- a/src/features/item/activeSlice.ts +++ b/src/features/item/activeSlice.ts @@ -6,7 +6,7 @@ import * as e from "./enchants"; import * as d from "./damageTypes"; import * as f from "./effects"; import { Entity, MAX_SETUPS, removeSetup as removeEntity } from "./entity"; -import { Damage, DamageItem } from "./damage"; +import { Damage, DamageItem, find } from "./damage"; import { defaultWeapon, Weapon } from "../weapon/weapon"; export interface matchState { @@ -53,7 +53,8 @@ const initialState : matchState = { ticks: 10 }, id: 0, - visible: true + visible: true, + times: 1 }, { dmg: { @@ -62,7 +63,8 @@ const initialState : matchState = { ticks: 10 }, id: 1, - visible: true + visible: true, + times: 1 }, { dmg: { @@ -71,7 +73,8 @@ const initialState : matchState = { ticks: 10 }, id: 2, - visible: true + visible: true, + times: 1 }, ], id: 99 @@ -118,10 +121,13 @@ export const activeSlice = createSlice( { state.damage.ticks = action.payload; }, toggleDamage: (state, action: PayloadAction) => { - for (let i = 0 ; i < state.damages.length ; i++) { - if (state.damages[i].id === action.payload) { - state.damages[i].visible = !state.damages[i].visible; - } + find(state.damages, action.payload).visible = !find(state.damages, action.payload).visible; + }, + addDamageTimes: (state, action: PayloadAction<{id: number, change: number}>) => { + let p = action.payload; + find(state.damages, p.id).times += p.change; + if (find(state.damages, p.id).times < 0) { + find(state.damages, p.id).times = 0; } }, removeSetup: (state, action: PayloadAction) => { @@ -132,7 +138,7 @@ export const activeSlice = createSlice( { }, saveDamage: (state, action: PayloadAction) => { state.id = state.id + 1; - state.damages.push({dmg:action.payload, id:state.id+1, visible: true}); + state.damages.push({dmg:action.payload, id:state.id+1, visible: true, times: 1}); }, //number is ID value! removeDamage: (state, action: PayloadAction) => { @@ -152,7 +158,7 @@ export const activeSlice = createSlice( { }); export const {setType, setEnchant, removeEnchant, removeEffect, setEffect, setDamageType, setDamage, - setDamageTicks, toggleDamage, removeSetup, addSetup, saveDamage, removeDamage, moveDamage} = activeSlice.actions; + setDamageTicks, toggleDamage, addDamageTimes, removeSetup, addSetup, saveDamage, removeDamage, moveDamage} = activeSlice.actions; // The function below is called a selector and allows us to select a value from // the state. Selectors can also be defined inline where they're used instead of // in the slice file. For example: `useSelector((state: RootState) => state.counter.value)` diff --git a/src/features/item/damage.ts b/src/features/item/damage.ts index cc14433..58446a6 100644 --- a/src/features/item/damage.ts +++ b/src/features/item/damage.ts @@ -11,7 +11,16 @@ export interface Damage { export interface DamageItem { dmg: Damage, id: number, - visible: boolean + visible: boolean, + times: number +} +export function find(items: DamageItem[], id: number) { + for (let i = 0 ; i < items.length ; i++) { + if (items[i].id === id) { + return items[i]; + } + } + return items[0]; } /* export class Damage implements DamageType { diff --git a/src/features/item/entity.ts b/src/features/item/entity.ts index 0695ef9..417548a 100644 --- a/src/features/item/entity.ts +++ b/src/features/item/entity.ts @@ -28,12 +28,12 @@ export function summary(e : Entity) { let str = ""; str += e.armor[0].type.charAt(0) + "/" + e.armor[1].type.charAt(0) + "/" + e.armor[2].type.charAt(0) + "/" + e.armor[3].type.charAt(0); - str += " A: " + getSetArmor(e.armor); - str += " T: " + getSetToughness(e.armor); + //str += " A: " + getSetArmor(e.armor); + //str += " T: " + getSetToughness(e.armor); if (getEffect(e.effects, RESISTANCE.key)) { str += " R: " + nomar(getEffect(e.effects, RESISTANCE.key)!.value); } - str += " HP: " + maxHealth(e); + //str += " HP: " + maxHealth(e); return str; } export function removeSetup(setups : Entity[], i : number) { diff --git a/src/features/item/images/full_armor.png b/src/features/item/images/full_armor.png new file mode 100644 index 0000000..0518198 Binary files /dev/null and b/src/features/item/images/full_armor.png differ diff --git a/src/features/item/images/full_heart.png b/src/features/item/images/full_heart.png new file mode 100644 index 0000000..63949de Binary files /dev/null and b/src/features/item/images/full_heart.png differ diff --git a/src/features/item/images/half_armor.png b/src/features/item/images/half_armor.png new file mode 100644 index 0000000..4225fe7 Binary files /dev/null and b/src/features/item/images/half_armor.png differ diff --git a/src/features/item/images/no_armor.png b/src/features/item/images/no_armor.png new file mode 100644 index 0000000..2998a14 Binary files /dev/null and b/src/features/item/images/no_armor.png differ diff --git a/src/features/item/images/no_heart.png b/src/features/item/images/no_heart.png new file mode 100644 index 0000000..d337d07 Binary files /dev/null and b/src/features/item/images/no_heart.png differ diff --git a/src/features/simulator/DamageCard.tsx b/src/features/simulator/DamageCard.tsx index ae6ba53..cec73c8 100644 --- a/src/features/simulator/DamageCard.tsx +++ b/src/features/simulator/DamageCard.tsx @@ -7,7 +7,9 @@ import { useAppDispatch } from '../../app/hooks' import { Damage } from '../item/damage' import { removeSetup } from '../item/entity' import Icon from '../item/Icons' -import { removeDamage, saveDamage, toggleDamage } from '../item/activeSlice' +import { addDamageTimes, removeDamage, saveDamage, toggleDamage } from '../item/activeSlice' +import { HalfHeart } from '../item/Parts' +import { round } from '../item/Utils' const _ = require('lodash'); const style = { @@ -21,6 +23,7 @@ const style = { export interface CardProps { id: any visible: boolean + times: number damage: Damage index: number moveCard: (dragIndex: number, hoverIndex: number) => void @@ -32,7 +35,7 @@ interface DragItem { type: string } -export const Card: FC = ({ id, visible, damage, index, moveCard }) => { +export const Card: FC = ({ id, visible, times, damage, index, moveCard }) => { const dispatch = useAppDispatch(); const ref = useRef(null) @@ -109,18 +112,29 @@ export const Card: FC = ({ id, visible, damage, index, moveCard }) =>
- dispatch(toggleDamage(id))} defaultChecked - /> - {damage.type} + /> + + + + + {times} x + + { " " + round(damage.amount) + " · " + damage.type} - + diff --git a/src/features/simulator/DragContainer.tsx b/src/features/simulator/DragContainer.tsx index ea2578e..13891ac 100644 --- a/src/features/simulator/DragContainer.tsx +++ b/src/features/simulator/DragContainer.tsx @@ -43,6 +43,7 @@ export const DragContainer: FC = () => { key={card.id} index={index} visible={card.visible} + times={card.times} id={card.id} damage={card.dmg} moveCard={moveCard} diff --git a/src/features/simulator/Simulator.tsx b/src/features/simulator/Simulator.tsx index b321338..a3498e9 100644 --- a/src/features/simulator/Simulator.tsx +++ b/src/features/simulator/Simulator.tsx @@ -1,78 +1,148 @@ import { useState } from "react"; -import { Table } from "react-bootstrap"; +import { Form, Table } from "react-bootstrap"; import { useSelector } from "react-redux"; import { selectDamages, selectEntity } from "../item/activeSlice"; import { Damage, DamageItem } from "../item/damage"; import { maxHealth, summary } from "../item/entity"; +import { Tip } from "../item/Icons"; import { takeDamage } from "../item/maths"; -import { elementwiseAdd, getPresetColor, round } from "../item/Utils"; +import { elementwiseAdd, getColor, getDeltaColor, getPresetColor, round } from "../item/Utils"; import { getDamage, getSeconds, toString, Weapon } from "../weapon/weapon"; -const ABSOLUTE = 'Absolute'; -const PERCENT = 'Percent'; -const SETTINGS = [ABSOLUTE, PERCENT]; +const CHANGES = 'Changes'; +const STEPS = 'Steps'; export function Simulator() { const entities = useSelector(selectEntity); const damages = useSelector(selectDamages); - const [setting, setSetting] = useState(ABSOLUTE); - + const [percent, setPercent] = useState(false); + const [showChanges, setShowChanges] = useState(false); + const [showSteps, setShowSteps] = useState(true); + const renderSettings = () => { + return ( +
+
+ setShowSteps(!showSteps)} + /> + setShowChanges(!showChanges)} + /> + + setPercent(!percent)} + /> +
+
+ ); + } const renderHeader = () => { - return entities.map( (item, index) => { - return ( - - {summary(item)} - - ); - }) + return ( + <> + + Attack + + + Time + + {entities.map( (item, index) => { + return ( + <> + + + {summary(item)} + + + )})} + + ); } const renderBody = () => { let hp = entities.map((item) => maxHealth(item)); let time = 0; let maxHp = entities.map((item) => maxHealth(item)); - return ( - damages.map( (damageItem: DamageItem, index) => { + const renderDamages = () => { + let initial = ( + 0)} + newHp={hp} + maxHp={maxHp} + index={-1} + percent={percent} + type={"Initial"} + showChanges={false} + showResults={true} + /> + ); + let rows = damages.map( (damageItem: DamageItem, index) => { if (damageItem.visible === false) { return ""; } + let elements = []; + for (let i = 0 ; i < damageItem.times ; i++) { + let damage = damageItem.dmg; - let damage = damageItem.dmg; - - //update the values one step forward - time += damage.ticks; - let deltaHp = entities.map( (entity) => { - return -takeDamage(damage, entity); - }); - hp = elementwiseAdd(hp, deltaHp); - - return (); + //update the values one step forward + time += damage.ticks; + let deltaHp = entities.map( (entity) => { + return -takeDamage(damage, entity); + }); + hp = elementwiseAdd(hp, deltaHp); + + elements.push (); + } + return <>{elements} }) + + return (<> + {initial} + + {showChanges || showSteps ? renderHeader() : ""} + {rows} + ); + } + return ( + renderDamages() ); } return (
+ {renderSettings()} - - + {renderHeader()} @@ -90,31 +160,37 @@ interface SimulatorRowType { newHp: number[], maxHp: number[], index: number, - show: string, - type: string + percent: boolean, + type: string, + showChanges: boolean, + showResults: boolean } export function SimulatorRow(props: SimulatorRowType) { + const [hover, setHover] = useState(false); + const getPercent = (num: number, index: number) => { + return round(num*100 / props.maxHp[index]); + } const getContent = (num: number, index: number) => { - if (props.show === ABSOLUTE) { - return num; - } - if (props.show === PERCENT) { - return num / props.maxHp[index]; + if (props.percent) { + return getPercent(num, index) + "%"; + } else { + return round(num); } } const renderDeltas = () => { return ( - - setHover(true)} onMouseLeave={() => setHover(false)}> + { props.deltaHp.map( (num, index) => { - return (); }) } @@ -123,27 +199,35 @@ export function SimulatorRow(props: SimulatorRowType) { } const renderResults = () => { return ( - - setHover(true)} onMouseLeave={() => setHover(false)}> + { props.newHp.map( (num, index) => { - return () }) } ); } + const renderName = () => { + return ( + hover ? + props.type : + props.type.substring(0, 25) + ) + } return ( <> - {renderDeltas()} - {renderResults()} + {props.showChanges ? renderDeltas() : ""} + {props.showResults ? renderResults() : ""} ); } \ No newline at end of file diff --git a/src/features/weapon/WeaponEditor.tsx b/src/features/weapon/WeaponEditor.tsx index 45c0bf4..296252a 100644 --- a/src/features/weapon/WeaponEditor.tsx +++ b/src/features/weapon/WeaponEditor.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Button, ButtonGroup, Col, Form, Row, Image } from "react-bootstrap"; import NumericInput from "react-numeric-input"; -import { getDamage, getDamageEquation, getSeconds, getTicks, preset, toString } from "./weapon"; +import { getDamage, getDamageEquation, getSeconds, getTicks, percentCharge, preset, toString } from "./weapon"; import { DropInput } from "../item/Parts"; import { defaultWeapon, FIST, makeWeapon, TRIDENT, Weapon, WEAPONS, WEAPON_MATERIALS } from "./weapon"; import { WeaponDamageGraph } from "./WeaponDamageGraph"; @@ -67,7 +67,7 @@ export function WeaponEditor() { Wait - { //Poor documentation @@ -75,7 +75,8 @@ export function WeaponEditor() { const x = c(); x.ticksSinceLast = valueAsNumber*20; setWeapon(x); } }} - format={(num) => num + " sec (" + (getTicks(num||0)) + " ticks)"} + format={(num) => num + " sec (" + (getTicks(num||0)) + " ticks, " + + percentCharge(weapon) + "%)"} /> before attack diff --git a/src/features/weapon/weapon.ts b/src/features/weapon/weapon.ts index ce13a16..d0d41fd 100644 --- a/src/features/weapon/weapon.ts +++ b/src/features/weapon/weapon.ts @@ -4,6 +4,7 @@ import * as e from "../item/enchants"; import { DIAMOND, IRON, GOLDEN, NETHERITE, NONE } from "../item/armor"; import { Effect, getEffectLevel, makeEffect, setEffect, STRENGTH, WEAKNESS } from "../item/effects"; import functionPlot from "function-plot"; +import { round } from "../item/Utils"; const _ = require('lodash'); const nomar = require('nomar'); //cooldown T = 20 / attackSpeed @@ -223,7 +224,7 @@ export function toString(w: Weapon) { } else { temp += w.material + " " + w.type + " · "; } - temp += w.ticksSinceLast + " · "; + temp += percentCharge(w) + "% · "; if (w.critical) { temp += "Critical · "; } @@ -238,7 +239,12 @@ export function toString(w: Weapon) { } return temp + " Melee" } - +export function ticksToFullCharge(w: Weapon) { + return 20 / w.attackSpeed; +} +export function percentCharge(w: Weapon) { + return round(Math.min(100, w.ticksSinceLast*100/ticksToFullCharge(w))); +} /* export class MeleeWeapon implements Weapon { constructor (
- Order - - Time -
- {props.type} +
+ {renderName()} - +{getSeconds(props.deltaTime)} sec (+{props.deltaTime}) ticks + +{getSeconds(props.deltaTime)} sec - {getContent(round(num), index)} + return ( + + {getContent(num, index)}
- +
+ {!props.showChanges ? renderName() : ""} - {getSeconds(props.newTime)} sec ({props.deltaTime}) ticks + {getSeconds(props.newTime)} sec - {getContent(round(num), index)} + return ( + + {getContent(num, index)}