Now extracting shield and armor stats from ItemPMT for Ephinea.

This commit is contained in:
Daan Vanden Bosch 2019-06-20 18:11:36 +02:00
parent a684bb65c8
commit 3bc50a1e63
10 changed files with 2943 additions and 358 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
import { ArrayBufferCursor } from "../ArrayBufferCursor";
export type ItemPmt = {
statBoosts: PmtStatBoost[],
armors: PmtArmor[],
shields: PmtShield[],
units: PmtUnit[],
@ -8,6 +9,13 @@ export type ItemPmt = {
weapons: PmtWeapon[][],
}
export type PmtStatBoost = {
stat1: number,
stat2: number,
amount1: number,
amount2: number,
}
export type PmtWeapon = {
id: number,
type: number,
@ -107,6 +115,8 @@ export function parseItemPmt(cursor: ArrayBufferCursor): ItemPmt {
}
const itemPmt: ItemPmt = {
// This size (65268) of this table seems wrong, so we pass in a hard-coded value.
statBoosts: parseStatBoosts(cursor, tableOffsets[305].offset, 52),
armors: parseArmors(cursor, tableOffsets[7].offset, tableOffsets[7].size),
shields: parseShields(cursor, tableOffsets[8].offset, tableOffsets[8].size),
units: parseUnits(cursor, tableOffsets[9].offset, tableOffsets[9].size),
@ -127,6 +137,22 @@ export function parseItemPmt(cursor: ArrayBufferCursor): ItemPmt {
return itemPmt;
}
function parseStatBoosts(cursor: ArrayBufferCursor, offset: number, size: number): PmtStatBoost[] {
cursor.seekStart(offset);
const statBoosts: PmtStatBoost[] = [];
for (let i = 0; i < size; i++) {
statBoosts.push({
stat1: cursor.u8(),
stat2: cursor.u8(),
amount1: cursor.i16(),
amount2: cursor.i16(),
});
}
return statBoosts;
}
function parseWeapons(cursor: ArrayBufferCursor, offset: number, size: number): PmtWeapon[] {
cursor.seekStart(offset);
const weapons: PmtWeapon[] = [];

View File

@ -5,9 +5,11 @@ import { DatNpc, DatObject, DatUnknown } from '../bin-data/parsing/dat';
import { NpcType } from './NpcType';
import { ObjectType } from './ObjectType';
import { enumValues } from '../enums';
import { ItemType } from './items';
export { NpcType } from './NpcType';
export { ObjectType } from './ObjectType';
export * from './items';
export * from './NpcType';
export * from './ObjectType';
export const RARE_ENEMY_PROB = 1 / 512;
export const KONDRIEU_PROB = 1 / 10;
@ -314,68 +316,6 @@ export class AreaVariant {
}
}
// Abstract base class of all item kinds.
export class ItemType {
constructor(
readonly id: number,
readonly name: string
) {
if (Object.getPrototypeOf(this) === Object.getPrototypeOf(ItemType))
throw new Error('Abstract class should not be instantiated directly.');
}
}
export class WeaponItemType extends ItemType {
constructor(
id: number,
name: string,
readonly minAtp: number,
readonly maxAtp: number,
readonly ata: number,
readonly maxGrind: number,
readonly requiredAtp: number,
) {
super(id, name);
}
}
export class ArmorItemType extends ItemType {
constructor(
id: number,
name: string,
) {
super(id, name);
}
}
export class ShieldItemType extends ItemType {
constructor(
id: number,
name: string,
) {
super(id, name);
}
}
export class UnitItemType extends ItemType {
constructor(
id: number,
name: string,
) {
super(id, name);
}
}
export class ToolItemType extends ItemType {
constructor(
id: number,
name: string,
) {
super(id, name);
}
}
type ItemDrop = {
itemType: ItemType,
anythingRate: number,

124
src/domain/items.ts Normal file
View File

@ -0,0 +1,124 @@
import { observable, computed } from "mobx";
//
// Item types.
// Instances of these classes contain the data that is the same for every item of a specific type.
// E.g. all spread needles are called "Spread Needle" and they all have the same ATA.
//
export interface ItemType {
readonly id: number,
readonly name: string
}
export class WeaponItemType implements ItemType {
constructor(
readonly id: number,
readonly name: string,
readonly minAtp: number,
readonly maxAtp: number,
readonly ata: number,
readonly maxGrind: number,
readonly requiredAtp: number,
) { }
}
export class ArmorItemType implements ItemType {
constructor(
readonly id: number,
readonly name: string,
readonly atp: number,
readonly ata: number,
readonly minEvp: number,
readonly maxEvp: number,
readonly minDfp: number,
readonly maxDfp: number,
readonly mst: number,
readonly hp: number,
readonly lck: number,
) { }
}
export class ShieldItemType implements ItemType {
constructor(
readonly id: number,
readonly name: string,
readonly atp: number,
readonly ata: number,
readonly minEvp: number,
readonly maxEvp: number,
readonly minDfp: number,
readonly maxDfp: number,
readonly mst: number,
readonly hp: number,
readonly lck: number,
) { }
}
export class UnitItemType implements ItemType {
constructor(
readonly id: number,
readonly name: string,
) { }
}
export class ToolItemType implements ItemType {
constructor(
readonly id: number,
readonly name: string,
) { }
}
//
// Item instances.
// Instances of these classes contain the data that is unique to each item.
// E.g. a specific spread needle dropped by an enemy or in an inventory.
//
export interface Item {
readonly type: ItemType,
}
export class WeaponItem implements Item {
/**
* Integer from 0 to 100.
*/
@observable attribute: number = 0;
/**
* Integer from 0 to 100.
*/
@observable hit: number = 0;
@observable grind: number = 0;
@computed get grindAtp(): number {
return 2 * this.grind;
}
constructor(
readonly type: WeaponItemType,
) { }
}
export class ArmorItem implements Item {
constructor(
readonly type: ArmorItemType,
) { }
}
export class ShieldItem implements Item {
constructor(
readonly type: ShieldItemType,
) { }
}
export class UnitItem implements Item {
constructor(
readonly type: UnitItemType,
) { }
}
export class ToolItem implements Item {
constructor(
readonly type: ToolItemType,
) { }
}

View File

@ -19,12 +19,30 @@ export type ArmorItemTypeDto = {
class: 'armor',
id: number,
name: string,
atp: number,
ata: number,
minEvp: number,
maxEvp: number,
minDfp: number,
maxDfp: number,
mst: number,
hp: number,
lck: number,
}
export type ShieldItemTypeDto = {
class: 'shield',
id: number,
name: string,
atp: number,
ata: number,
minEvp: number,
maxEvp: number,
minDfp: number,
maxDfp: number,
mst: number,
hp: number,
lck: number,
}
export type UnitItemTypeDto = {

View File

@ -1,4 +1,6 @@
import { observable, IObservableArray, computed } from "mobx";
import { WeaponItem, WeaponItemType, ArmorItemType, ShieldItemType } from "../domain";
import { itemTypeStores } from "./ItemTypeStore";
const NORMAL_DAMAGE_FACTOR = 0.2 * 0.9;
const HEAVY_DAMAGE_FACTOR = NORMAL_DAMAGE_FACTOR * 1.89;
@ -6,40 +8,23 @@ const SAC_DAMAGE_FACTOR = NORMAL_DAMAGE_FACTOR * 3.32;
const VJAYA_DAMAGE_FACTOR = NORMAL_DAMAGE_FACTOR * 5.56;
const CRIT_FACTOR = 1.5;
class WeaponType {
constructor(
readonly minAtp: number,
readonly maxAtp: number
) { }
}
class Weapon {
readonly type: WeaponType;
/**
* Integer from 0 to 100.
*/
@observable attributePercentage: number = 0;
@observable grind: number = 0;
readonly item: WeaponItem;
@computed get shiftaAtp(): number {
if (this.type.minAtp === this.type.maxAtp) {
if (this.item.type.minAtp === this.item.type.maxAtp) {
return 0;
} else {
return this.type.maxAtp * this.store.shiftaFactor;
return this.item.type.maxAtp * this.store.shiftaFactor;
}
}
@computed get grindAtp(): number {
return 2 * this.grind;
}
@computed get minAtp(): number {
return this.type.minAtp + this.grindAtp;
return this.item.type.minAtp + this.item.grindAtp;
}
@computed get maxAtp(): number {
return this.type.maxAtp + this.grindAtp + this.shiftaAtp;
return this.item.type.maxAtp + this.item.grindAtp + this.shiftaAtp;
}
@computed get finalMinAtp(): number {
@ -84,21 +69,39 @@ class Weapon {
constructor(
private store: DpsCalcStore,
type: WeaponType,
item: WeaponItem,
) {
this.type = type;
this.item = item;
}
}
class DpsCalcStore {
@computed get weaponTypes(): WeaponItemType[] {
return itemTypeStores.current.value.itemTypes.filter(it =>
it instanceof WeaponItemType
) as WeaponItemType[];
}
@computed get armorTypes(): ArmorItemType[] {
return itemTypeStores.current.value.itemTypes.filter(it =>
it instanceof ArmorItemType
) as ArmorItemType[];
}
@computed get shieldTypes(): ShieldItemType[] {
return itemTypeStores.current.value.itemTypes.filter(it =>
it instanceof ShieldItemType
) as ShieldItemType[];
}
//
// Character Details
//
@observable charAtp: number = 0;
@observable magPow: number = 0;
@observable armorAtp: number = 0;
@observable shieldAtp: number = 0;
@computed get armorAtp(): number { return this.armorType ? this.armorType.atp : 0 }
@computed get shieldAtp(): number { return this.shieldType ? this.shieldType.atp : 0 }
@observable shiftaLvl: number = 0;
@computed get baseAtp(): number {
@ -115,6 +118,16 @@ class DpsCalcStore {
@observable readonly weapons: IObservableArray<Weapon> = observable.array();
addWeapon = (type: WeaponItemType) => {
this.weapons.push(new Weapon(
this,
new WeaponItem(type)
));
}
@observable armorType?: ArmorItemType;
@observable shieldType?: ShieldItemType;
//
// Enemy Details
//

View File

@ -40,12 +40,30 @@ class ItemTypeStore {
itemType = new ArmorItemType(
itemTypeDto.id,
itemTypeDto.name,
itemTypeDto.atp,
itemTypeDto.ata,
itemTypeDto.minEvp,
itemTypeDto.maxEvp,
itemTypeDto.minDfp,
itemTypeDto.maxDfp,
itemTypeDto.mst,
itemTypeDto.hp,
itemTypeDto.lck,
);
break;
case 'shield':
itemType = new ShieldItemType(
itemTypeDto.id,
itemTypeDto.name,
itemTypeDto.atp,
itemTypeDto.ata,
itemTypeDto.minEvp,
itemTypeDto.maxEvp,
itemTypeDto.minDfp,
itemTypeDto.maxDfp,
itemTypeDto.mst,
itemTypeDto.hp,
itemTypeDto.lck,
);
break;
case 'unit':

View File

@ -8,6 +8,10 @@
border-radius: @border-radius-base;
}
& .Select-control .Select-value .Select-value-label {
color: white !important;
}
& .Select-placeholder, & .Select--single > .Select-control .Select-value {
line-height: 32px;
}

View File

@ -1,7 +1,10 @@
import { InputNumber } from "antd";
import { observer } from "mobx-react";
import React from "react";
import { WeaponItemType, ArmorItemType, ShieldItemType } from "../../domain";
import { dpsCalcStore } from "../../stores/DpsCalcStore";
import { itemTypeStores } from "../../stores/ItemTypeStore";
import { BigSelect } from "../BigSelect";
@observer
export class DpsCalcComponent extends React.Component {
@ -9,6 +12,53 @@ export class DpsCalcComponent extends React.Component {
return (
<section>
<section>
<div>Weapons:</div>
<BigSelect
placeholder="Add a weapon"
value={undefined}
options={dpsCalcStore.weaponTypes.map(wt => ({
label: wt.name,
value: wt.id
}))}
onChange={this.addWeapon}
/>
<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>
</tr>
</thead>
<tbody>
{dpsCalcStore.weapons.map((weapon, i) => (
<tr key={i}>
<td>{weapon.item.type.name}</td>
<td>{weapon.item.type.minAtp}</td>
<td>{weapon.item.type.maxAtp}</td>
<td>
<InputNumber
size="small"
value={weapon.item.grind}
min={0}
max={weapon.item.type.maxGrind}
step={1}
onChange={(value) => weapon.item.grind = value || 0}
/>
</td>
<td>{weapon.item.grindAtp}</td>
<td>{weapon.shiftaAtp}</td>
<td>{weapon.finalMinAtp}</td>
<td>{weapon.finalMaxAtp}</td>
</tr>
))}
</tbody>
</table>
<div>Character ATP:</div>
<InputNumber
value={dpsCalcStore.charAtp}
@ -24,20 +74,28 @@ export class DpsCalcComponent extends React.Component {
step={1}
onChange={(value) => dpsCalcStore.magPow = value || 0}
/>
<div>Armor ATP:</div>
<InputNumber
value={dpsCalcStore.armorAtp}
min={0}
step={1}
onChange={(value) => dpsCalcStore.armorAtp = value || 0}
<div>Armor:</div>
<BigSelect
placeholder="Choose an armor"
value={dpsCalcStore.armorType && dpsCalcStore.armorType.id}
options={dpsCalcStore.armorTypes.map(at => ({
label: at.name,
value: at.id
}))}
onChange={this.armorChanged}
/>
<div>Shield ATP:</div>
<InputNumber
value={dpsCalcStore.shieldAtp}
min={0}
step={1}
onChange={(value) => dpsCalcStore.shieldAtp = value || 0}
<span>Armor ATP: {dpsCalcStore.armorAtp}</span>
<div>Shield:</div>
<BigSelect
placeholder="Choose a shield"
value={dpsCalcStore.shieldType && dpsCalcStore.shieldType.id}
options={dpsCalcStore.shieldTypes.map(st => ({
label: st.name,
value: st.id
}))}
onChange={this.shieldChanged}
/>
<span>Shield ATP: {dpsCalcStore.shieldAtp}</span>
<div>Shifta level:</div>
<InputNumber
value={dpsCalcStore.shiftaLvl}
@ -54,4 +112,29 @@ export class DpsCalcComponent extends React.Component {
</section>
);
}
private addWeapon = (selected: any) => {
if (selected) {
let type = itemTypeStores.current.value.getById(selected.value)!;
dpsCalcStore.addWeapon(type as WeaponItemType);
}
}
private armorChanged = (selected: any) => {
if (selected) {
let type = itemTypeStores.current.value.getById(selected.value)!;
dpsCalcStore.armorType = (type as ArmorItemType);
} else {
dpsCalcStore.armorType = undefined;
}
}
private shieldChanged = (selected: any) => {
if (selected) {
let type = itemTypeStores.current.value.getById(selected.value)!;
dpsCalcStore.shieldType = (type as ShieldItemType);
} else {
dpsCalcStore.shieldType = undefined;
}
}
}

View File

@ -1,6 +1,6 @@
import fs from 'fs';
import { ArrayBufferCursor } from '../src/bin-data/ArrayBufferCursor';
import { parseItemPmt } from '../src/bin-data/parsing/itempmt';
import { parseItemPmt, ItemPmt } from '../src/bin-data/parsing/itempmt';
import { parseUnitxt, Unitxt } from '../src/bin-data/parsing/unitxt';
import { Difficulties, Difficulty, Episode, Episodes, NpcType, SectionId, SectionIds } from '../src/domain';
import { NpcTypes } from '../src/domain/NpcType';
@ -82,10 +82,18 @@ async function updateItems(itemNames: Array<string>): Promise<ItemTypeDto[]> {
if (!ids.has(id)) {
ids.add(id);
const stats = getStatBoosts(itemPmt, armor.statBoost);
stats.minEvp += armor.evp;
stats.minDfp += armor.dfp;
itemTypes.push({
class: 'armor',
id,
name: itemNames[armor.id],
...stats,
maxEvp: stats.minEvp + armor.evpRange,
maxDfp: stats.minDfp + armor.dfpRange,
});
}
});
@ -95,10 +103,18 @@ async function updateItems(itemNames: Array<string>): Promise<ItemTypeDto[]> {
if (!ids.has(id)) {
ids.add(id);
const stats = getStatBoosts(itemPmt, shield.statBoost);
stats.minEvp += shield.evp;
stats.minDfp += shield.dfp;
itemTypes.push({
class: 'shield',
id,
name: itemNames[shield.id],
...stats,
maxEvp: stats.minEvp + shield.evpRange,
maxDfp: stats.minDfp + shield.dfpRange,
});
}
});
@ -477,6 +493,54 @@ async function loadBoxDrops(
return drops;
}
function getStatBoosts(itemPmt: ItemPmt, statBoostIndex: number) {
const statBoost = itemPmt.statBoosts[statBoostIndex];
let atp = 0;
let ata = 0;
let minEvp = 0;
let minDfp = 0;
let mst = 0;
let hp = 0;
let lck = 0;
switch (statBoost.stat1) {
case 1: atp += statBoost.amount1; break;
case 2: ata += statBoost.amount1; break;
case 3: minEvp += statBoost.amount1; break;
case 4: minDfp += statBoost.amount1; break;
case 5: mst += statBoost.amount1; break;
case 6: hp += statBoost.amount1; break;
case 7: lck += statBoost.amount1; break;
case 8:
atp += statBoost.amount1;
ata += statBoost.amount1;
minEvp += statBoost.amount1;
minDfp += statBoost.amount1;
mst += statBoost.amount1;
hp += statBoost.amount1;
lck += statBoost.amount1;
break;
case 9: atp -= statBoost.amount1; break;
case 10: ata -= statBoost.amount1; break;
case 11: minEvp -= statBoost.amount1; break;
case 12: minDfp -= statBoost.amount1; break;
case 13: mst -= statBoost.amount1; break;
case 14: hp -= statBoost.amount1; break;
case 15: lck -= statBoost.amount1; break;
case 16:
atp -= statBoost.amount1;
ata -= statBoost.amount1;
minEvp -= statBoost.amount1;
minDfp -= statBoost.amount1;
mst -= statBoost.amount1;
hp -= statBoost.amount1;
lck -= statBoost.amount1;
break;
}
return { atp, ata, minEvp, minDfp, mst, hp, lck };
}
function getEnemyType(episode: Episode, index: number) {
if (episode === Episode.I) {
return [