Ported the DPS calc store to the new observables system.

This commit is contained in:
Daan Vanden Bosch 2019-09-14 17:13:08 +02:00
parent d9b6c7015a
commit 8411d75b7f
4 changed files with 198 additions and 294 deletions

View File

@ -49,6 +49,14 @@ export function map<R, P1, P2, P3, P4>(
prop_3: Property<P3>,
prop_4: Property<P4>,
): Property<R>;
export function map<R, P1, P2, P3, P4, P5>(
f: (prop_1: P1, prop_2: P2, prop_3: P3, prop_4: P4, prop_5: P5) => R,
prop_1: Property<P1>,
prop_2: Property<P2>,
prop_3: Property<P3>,
prop_4: Property<P4>,
prop_5: Property<P5>,
): Property<R>;
export function map<R>(
f: (...props: Property<any>[]) => R,
...props: Property<any>[]

View File

@ -0,0 +1,190 @@
import { WeaponItem, WeaponItemType, ArmorItemType, ShieldItemType } from "../../core/model/items";
import { item_type_stores, ItemTypeStore } from "../../core/stores/ItemTypeStore";
import { Property } from "../../core/observable/property/Property";
import { list_property, map, property } from "../../core/observable";
import { WritableProperty } from "../../core/observable/property/WritableProperty";
import { ListProperty } from "../../core/observable/property/list/ListProperty";
import { WritableListProperty } from "../../core/observable/property/list/WritableListProperty";
import { sequential } from "../../core/sequential";
import { Disposable } from "../../core/observable/Disposable";
const NORMAL_DAMAGE_FACTOR = 0.2 * 0.9;
const HEAVY_DAMAGE_FACTOR = NORMAL_DAMAGE_FACTOR * 1.89;
// const SAC_DAMAGE_FACTOR = NORMAL_DAMAGE_FACTOR * 3.32;
// const VJAYA_DAMAGE_FACTOR = NORMAL_DAMAGE_FACTOR * 5.56;
// const CRIT_FACTOR = 1.5;
class Weapon {
readonly shifta_atp: Property<number> = this.store.shifta_factor.map(shifta_factor => {
if (this.item.type.min_atp === this.item.type.max_atp) {
return 0;
} else {
return this.item.type.max_atp * shifta_factor;
}
});
readonly min_atp: Property<number> = this.item.grind_atp.map(
grind_atp => this.item.type.min_atp + grind_atp,
);
readonly max_atp: Property<number> = map(
(grind_atp, shifta_atp) => this.item.type.max_atp + grind_atp + shifta_atp,
this.item.grind_atp,
this.shifta_atp,
);
readonly final_min_atp: Property<number> = map(
(min_atp, armor_atp, shield_atp, base_atp, base_shifta_atp) =>
min_atp + armor_atp + shield_atp + base_atp + base_shifta_atp,
this.min_atp,
this.store.armor_atp,
this.store.shield_atp,
this.store.base_atp,
this.store.base_shifta_atp,
);
readonly final_max_atp: Property<number> = map(
(max_atp, armor_atp, shield_atp, base_atp, base_shifta_atp) =>
max_atp + armor_atp + shield_atp + base_atp + base_shifta_atp,
this.max_atp,
this.store.armor_atp,
this.store.shield_atp,
this.store.base_atp,
this.store.base_shifta_atp,
);
readonly min_normal_damage: Property<number> = map(
(final_min_atp, enemy_dfp) => (final_min_atp - enemy_dfp) * NORMAL_DAMAGE_FACTOR,
this.final_min_atp,
this.store.enemy_dfp,
);
readonly max_normal_damage: Property<number> = map(
(final_max_atp, enemy_dfp) => (final_max_atp - enemy_dfp) * NORMAL_DAMAGE_FACTOR,
this.final_max_atp,
this.store.enemy_dfp,
);
readonly avg_normal_damage: Property<number> = map(
(min_normal_damage, max_normal_damage) => (min_normal_damage + max_normal_damage) / 2,
this.min_normal_damage,
this.max_normal_damage,
);
readonly min_heavy_damage: Property<number> = map(
(final_min_atp, enemy_dfp) => (final_min_atp - enemy_dfp) * HEAVY_DAMAGE_FACTOR,
this.final_min_atp,
this.store.enemy_dfp,
);
readonly max_heavy_damage: Property<number> = map(
(final_max_atp, enemy_dfp) => (final_max_atp - enemy_dfp) * HEAVY_DAMAGE_FACTOR,
this.final_max_atp,
this.store.enemy_dfp,
);
readonly avg_heavy_damage: Property<number> = map(
(min_heavy_damage, max_heavy_damage) => (min_heavy_damage + max_heavy_damage) / 2,
this.min_heavy_damage,
this.max_heavy_damage,
);
constructor(private readonly store: DpsCalcStore, readonly item: WeaponItem) {}
}
class DpsCalcStore implements Disposable {
private readonly _weapon_types: WritableListProperty<WeaponItemType> = list_property();
private readonly _armor_types: WritableListProperty<ArmorItemType> = list_property();
private readonly _shield_types: WritableListProperty<ShieldItemType> = list_property();
private readonly _char_atp = property(0);
private readonly _mag_pow = property(0);
private readonly _shifta_lvl = property(0);
private readonly _weapons: WritableListProperty<Weapon> = list_property();
private readonly _armor_type: WritableProperty<ArmorItemType | undefined> = property(undefined);
private readonly _shield_type: WritableProperty<ShieldItemType | undefined> = property(
undefined,
);
private readonly _enemy_dfp = property(0);
private readonly disposable: Disposable;
//
// Public Properties
//
readonly weapon_types: ListProperty<WeaponItemType> = this._weapon_types;
readonly armor_types: ListProperty<ArmorItemType> = this._armor_types;
readonly shield_types: ListProperty<ShieldItemType> = this._shield_types;
//
// Character Details
//
readonly char_atp: Property<number> = this._char_atp;
readonly mag_pow: Property<number> = this._mag_pow;
readonly armor_atp: Property<number> = this._armor_type.map(armor_type =>
armor_type ? armor_type.atp : 0,
);
readonly shield_atp: Property<number> = this._shield_type.map(shield_type =>
shield_type ? shield_type.atp : 0,
);
readonly shifta_lvl: Property<number> = this._shifta_lvl;
readonly base_atp: Property<number> = map(
(char_atp, mag_pow) => char_atp + 2 * mag_pow,
this.char_atp,
this.mag_pow,
);
readonly shifta_factor: Property<number> = this.shifta_lvl.map(shifta_lvl =>
shifta_lvl ? 0.013 * (shifta_lvl - 1) + 0.1 : 0,
);
readonly base_shifta_atp: Property<number> = map(
(base_atp, shifta_factor) => base_atp * shifta_factor,
this.base_atp,
this.shifta_factor,
);
readonly weapons: ListProperty<Weapon> = this._weapons;
readonly armor_type: Property<ArmorItemType | undefined> = this._armor_type;
readonly shield_type: Property<ShieldItemType | undefined> = this._shield_type;
//
// Enemy Details
//
readonly enemy_dfp: Property<number> = this._enemy_dfp;
constructor() {
this.disposable = item_type_stores.current.observe(
sequential(async ({ value: item_type_store }: { value: Promise<ItemTypeStore> }) => {
const weapon_types: WeaponItemType[] = [];
const armor_types: ArmorItemType[] = [];
const shield_types: ShieldItemType[] = [];
for (const item_type of (await item_type_store).item_types) {
if (item_type instanceof WeaponItemType) {
weapon_types.push(item_type);
} else if (item_type instanceof ArmorItemType) {
armor_types.push(item_type);
} else if (item_type instanceof ShieldItemType) {
shield_types.push(item_type);
}
}
this._weapon_types.val = weapon_types;
this._armor_types.val = armor_types;
this._shield_types.val = shield_types;
}),
);
}
dispose = (): void => {
this.disposable.dispose();
};
add_weapon = (type: WeaponItemType) => {
this._weapons.push(new Weapon(this, new WeaponItem(type)));
};
}
export const dps_calc_store = new DpsCalcStore();

View File

@ -1,142 +0,0 @@
import { observable, IObservableArray, computed } from "mobx";
import { WeaponItem, WeaponItemType, ArmorItemType, ShieldItemType } from "../../../core/model/items";
import { item_type_stores } from "../../../core/stores/ItemTypeStore";
const NORMAL_DAMAGE_FACTOR = 0.2 * 0.9;
const HEAVY_DAMAGE_FACTOR = NORMAL_DAMAGE_FACTOR * 1.89;
// const SAC_DAMAGE_FACTOR = NORMAL_DAMAGE_FACTOR * 3.32;
// const VJAYA_DAMAGE_FACTOR = NORMAL_DAMAGE_FACTOR * 5.56;
// const CRIT_FACTOR = 1.5;
class Weapon {
private readonly store: DpsCalcStore;
readonly item: WeaponItem;
@computed get shifta_atp(): number {
if (this.item.type.min_atp === this.item.type.max_atp) {
return 0;
} else {
return this.item.type.max_atp * this.store.shifta_factor;
}
}
@computed get min_atp(): number {
return this.item.type.min_atp + this.item.grind_atp;
}
@computed get max_atp(): number {
return this.item.type.max_atp + this.item.grind_atp + this.shifta_atp;
}
@computed get final_min_atp(): number {
return (
this.min_atp +
this.store.armor_atp +
this.store.shield_atp +
this.store.base_atp +
this.store.base_shifta_atp
);
}
@computed get final_max_atp(): number {
return (
this.max_atp +
this.store.armor_atp +
this.store.shield_atp +
this.store.base_atp +
this.store.base_shifta_atp
);
}
@computed get min_normal_damage(): number {
return (this.final_min_atp - this.store.enemy_dfp) * NORMAL_DAMAGE_FACTOR;
}
@computed get max_normal_damage(): number {
return (this.final_max_atp - this.store.enemy_dfp) * NORMAL_DAMAGE_FACTOR;
}
@computed get avg_normal_damage(): number {
return (this.min_normal_damage + this.max_normal_damage) / 2;
}
@computed get min_heavy_damage(): number {
return (this.final_min_atp - this.store.enemy_dfp) * HEAVY_DAMAGE_FACTOR;
}
@computed get max_heavy_damage(): number {
return (this.final_max_atp - this.store.enemy_dfp) * HEAVY_DAMAGE_FACTOR;
}
@computed get avg_heavy_damage(): number {
return (this.min_heavy_damage + this.max_heavy_damage) / 2;
}
constructor(store: DpsCalcStore, item: WeaponItem) {
this.store = store;
this.item = item;
}
}
class DpsCalcStore {
@computed get weapon_types(): WeaponItemType[] {
return item_type_stores.current.value.item_types.filter(
it => it instanceof WeaponItemType,
) as WeaponItemType[];
}
@computed get armor_types(): ArmorItemType[] {
return item_type_stores.current.value.item_types.filter(
it => it instanceof ArmorItemType,
) as ArmorItemType[];
}
@computed get shield_types(): ShieldItemType[] {
return item_type_stores.current.value.item_types.filter(
it => it instanceof ShieldItemType,
) as ShieldItemType[];
}
//
// Character Details
//
@observable char_atp: number = 0;
@observable mag_pow: number = 0;
@computed get armor_atp(): number {
return this.armor_type ? this.armor_type.atp : 0;
}
@computed get shield_atp(): number {
return this.shield_type ? this.shield_type.atp : 0;
}
@observable shifta_lvl: number = 0;
@computed get base_atp(): number {
return this.char_atp + 2 * this.mag_pow;
}
@computed get shifta_factor(): number {
return this.shifta_lvl ? 0.013 * (this.shifta_lvl - 1) + 0.1 : 0;
}
@computed get base_shifta_atp(): number {
return this.base_atp * this.shifta_factor;
}
@observable readonly weapons: IObservableArray<Weapon> = observable.array();
add_weapon = (type: WeaponItemType) => {
this.weapons.push(new Weapon(this, new WeaponItem(type)));
};
@observable armor_type?: ArmorItemType;
@observable shield_type?: ShieldItemType;
//
// Enemy Details
//
@observable enemy_dfp: number = 0;
}
export const dps_calc_store = new DpsCalcStore();

View File

@ -1,152 +0,0 @@
import { InputNumber } from "antd";
import { observer } from "mobx-react";
import React, { Component, ReactNode } from "react";
import { ArmorItemType, ShieldItemType, WeaponItemType } from "../../../core/model/items";
import { dps_calc_store } from "../stores/DpsCalcStore";
import { item_type_stores } from "../../../core/stores/ItemTypeStore";
import { BigSelect } from "../../core/ui/BigSelect";
@observer
export class DpsCalcComponent extends Component {
render(): ReactNode {
return (
<section>
<section>
<div>Weapons:</div>
<BigSelect
placeholder="Add a weapon"
value={undefined}
options={dps_calc_store.weapon_types.map(wt => ({
label: wt.name,
value: wt.id,
}))}
onChange={this.add_weapon}
/>
<table>
<thead>
<tr>
<td>Weapon</td>
<td>Min. ATP</td>
<td>Max. ATP</td>
<td>Grind</td>
<td>Grind ATP</td>
<td>Shifta ATP</td>
<td>Final Min. ATP</td>
<td>Final Max. ATP</td>
<td>Min. Normal Damage</td>
<td>Max. Normal Damage</td>
<td>Avg. Normal Damage</td>
<td>Min. Heavy Damage</td>
<td>Max. Heavy Damage</td>
<td>Avg. Heavy Damage</td>
</tr>
</thead>
<tbody>
{dps_calc_store.weapons.map((weapon, i) => (
<tr key={i}>
<td>{weapon.item.type.name}</td>
<td>{weapon.item.type.min_atp}</td>
<td>{weapon.item.type.max_atp}</td>
<td>
<InputNumber
size="small"
value={weapon.item.grind}
min={0}
max={weapon.item.type.max_grind}
step={1}
onChange={value => (weapon.item.grind = value || 0)}
/>
</td>
<td>{weapon.item.grind_atp}</td>
<td>{weapon.shifta_atp.toFixed(1)}</td>
<td>{weapon.final_min_atp.toFixed(1)}</td>
<td>{weapon.final_max_atp.toFixed(1)}</td>
<td>{weapon.min_normal_damage.toFixed(1)}</td>
<td>{weapon.max_normal_damage.toFixed(1)}</td>
<td>{weapon.avg_normal_damage.toFixed(1)}</td>
<td>{weapon.min_heavy_damage.toFixed(1)}</td>
<td>{weapon.max_heavy_damage.toFixed(1)}</td>
<td>{weapon.avg_heavy_damage.toFixed(1)}</td>
</tr>
))}
</tbody>
</table>
<div>Character ATP:</div>
<InputNumber
value={dps_calc_store.char_atp}
min={0}
step={1}
onChange={value => (dps_calc_store.char_atp = value || 0)}
/>
<div>MAG POW:</div>
<InputNumber
value={dps_calc_store.mag_pow}
min={0}
max={200}
step={1}
onChange={value => (dps_calc_store.mag_pow = value || 0)}
/>
<div>Armor:</div>
<BigSelect
placeholder="Choose an armor"
value={dps_calc_store.armor_type && dps_calc_store.armor_type.id}
options={dps_calc_store.armor_types.map(at => ({
label: at.name,
value: at.id,
}))}
onChange={this.armor_changed}
/>
<span>Armor ATP: {dps_calc_store.armor_atp}</span>
<div>Shield:</div>
<BigSelect
placeholder="Choose a shield"
value={dps_calc_store.shield_type && dps_calc_store.shield_type.id}
options={dps_calc_store.shield_types.map(st => ({
label: st.name,
value: st.id,
}))}
onChange={this.shield_changed}
/>
<span>Shield ATP: {dps_calc_store.shield_atp}</span>
<div>Shifta level:</div>
<InputNumber
value={dps_calc_store.shifta_lvl}
min={0}
max={30}
step={1}
onChange={value => (dps_calc_store.shifta_lvl = value || 0)}
/>
<div>Shifta factor:</div>
<div>{dps_calc_store.shifta_factor.toFixed(3)}</div>
<div>Base shifta ATP:</div>
<div>{dps_calc_store.base_shifta_atp.toFixed(2)}</div>
</section>
</section>
);
}
private add_weapon = (selected: any) => {
if (selected) {
let type = item_type_stores.current.value.get_by_id(selected.value)!;
dps_calc_store.add_weapon(type as WeaponItemType);
}
};
private armor_changed = (selected: any) => {
if (selected) {
let item_type = item_type_stores.current.value.get_by_id(selected.value)!;
dps_calc_store.armor_type = item_type as ArmorItemType;
} else {
dps_calc_store.armor_type = undefined;
}
};
private shield_changed = (selected: any) => {
if (selected) {
let item_type = item_type_stores.current.value.get_by_id(selected.value)!;
dps_calc_store.shield_type = item_type as ShieldItemType;
} else {
dps_calc_store.shield_type = undefined;
}
};
}