Renamed ItemKind to ItemType for consistency with NpcType.

This commit is contained in:
Daan Vanden Bosch 2019-06-20 14:06:11 +02:00
parent 4a3f5991ed
commit a684bb65c8
11 changed files with 5940 additions and 5938 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -315,18 +315,18 @@ export class AreaVariant {
}
// Abstract base class of all item kinds.
export class ItemKind {
export class ItemType {
constructor(
readonly id: number,
readonly name: string
) {
if (Object.getPrototypeOf(this) === Object.getPrototypeOf(ItemKind))
if (Object.getPrototypeOf(this) === Object.getPrototypeOf(ItemType))
throw new Error('Abstract class should not be instantiated directly.');
}
}
export class WeaponItemKind extends ItemKind {
export class WeaponItemType extends ItemType {
constructor(
id: number,
name: string,
@ -340,7 +340,7 @@ export class WeaponItemKind extends ItemKind {
}
}
export class ArmorItemKind extends ItemKind {
export class ArmorItemType extends ItemType {
constructor(
id: number,
name: string,
@ -349,7 +349,7 @@ export class ArmorItemKind extends ItemKind {
}
}
export class ShieldItemKind extends ItemKind {
export class ShieldItemType extends ItemType {
constructor(
id: number,
name: string,
@ -358,7 +358,7 @@ export class ShieldItemKind extends ItemKind {
}
}
export class UnitItemKind extends ItemKind {
export class UnitItemType extends ItemType {
constructor(
id: number,
name: string,
@ -367,7 +367,7 @@ export class UnitItemKind extends ItemKind {
}
}
export class ToolItemKind extends ItemKind {
export class ToolItemType extends ItemType {
constructor(
id: number,
name: string,
@ -377,7 +377,7 @@ export class ToolItemKind extends ItemKind {
}
type ItemDrop = {
item: ItemKind,
itemType: ItemType,
anythingRate: number,
rareRate: number
}
@ -389,7 +389,7 @@ export class EnemyDrop implements ItemDrop {
readonly difficulty: Difficulty,
readonly sectionId: SectionId,
readonly npcType: NpcType,
readonly item: ItemKind,
readonly itemType: ItemType,
readonly anythingRate: number,
readonly rareRate: number
) {

View File

@ -1,11 +1,11 @@
export type ItemKindDto = WeaponItemKindDto
| ArmorItemKindDto
| ShieldItemKindDto
| UnitItemKindDto
| ToolItemKindDto
export type ItemTypeDto = WeaponItemTypeDto
| ArmorItemTypeDto
| ShieldItemTypeDto
| UnitItemTypeDto
| ToolItemTypeDto
export type WeaponItemKindDto = {
type: 'weapon',
export type WeaponItemTypeDto = {
class: 'weapon',
id: number,
name: string,
minAtp: number,
@ -15,26 +15,26 @@ export type WeaponItemKindDto = {
requiredAtp: number,
}
export type ArmorItemKindDto = {
type: 'armor',
export type ArmorItemTypeDto = {
class: 'armor',
id: number,
name: string,
}
export type ShieldItemKindDto = {
type: 'shield',
export type ShieldItemTypeDto = {
class: 'shield',
id: number,
name: string,
}
export type UnitItemKindDto = {
type: 'unit',
export type UnitItemTypeDto = {
class: 'unit',
id: number,
name: string,
}
export type ToolItemKindDto = {
type: 'tool',
export type ToolItemTypeDto = {
class: 'tool',
id: number,
name: string,
}
@ -44,7 +44,7 @@ export type EnemyDropDto = {
episode: number,
sectionId: string,
enemy: string,
itemKindId: number,
itemTypeId: number,
dropRate: number,
rareRate: number,
}
@ -54,6 +54,6 @@ export type BoxDropDto = {
episode: number,
sectionId: string,
areaId: number,
itemKindId: number,
itemTypeId: number,
dropRate: number,
}

View File

@ -1,24 +1,24 @@
import solver from 'javascript-lp-solver';
import { autorun, IObservableArray, observable, computed } from "mobx";
import { Difficulties, Difficulty, HuntMethod, ItemKind, KONDRIEU_PROB, NpcType, RARE_ENEMY_PROB, SectionId, SectionIds, Server } from "../domain";
import { Difficulties, Difficulty, HuntMethod, ItemType, KONDRIEU_PROB, NpcType, RARE_ENEMY_PROB, SectionId, SectionIds, Server } from "../domain";
import { applicationStore } from './ApplicationStore';
import { huntMethodStore } from "./HuntMethodStore";
import { itemDropStores } from './ItemDropStore';
import { itemKindStores } from './ItemKindStore';
import { itemTypeStores } from './ItemTypeStore';
export class WantedItem {
@observable readonly itemKind: ItemKind;
@observable readonly itemType: ItemType;
@observable amount: number;
constructor(itemKind: ItemKind, amount: number) {
this.itemKind = itemKind;
constructor(itemType: ItemType, amount: number) {
this.itemType = itemType;
this.amount = amount;
}
}
export class OptimalResult {
constructor(
readonly wantedItems: Array<ItemKind>,
readonly wantedItems: Array<ItemType>,
readonly optimalMethods: Array<OptimalMethod>
) { }
}
@ -32,7 +32,7 @@ export class OptimalMethod {
readonly methodName: string,
readonly methodTime: number,
readonly runs: number,
readonly itemCounts: Map<ItemKind, number>
readonly itemCounts: Map<ItemType, number>
) {
this.totalTime = runs * methodTime;
}
@ -44,10 +44,10 @@ export class OptimalMethod {
// Can be useful when deciding which item to hunt first.
// TODO: boxes.
class HuntOptimizerStore {
@computed get huntableItems(): Array<ItemKind> {
@computed get huntableItemTypes(): Array<ItemType> {
const itemDropStore = itemDropStores.current.value;
return itemKindStores.current.value.itemKinds.filter(i =>
itemDropStore.enemyDrops.getDropsForItemKind(i.id).length
return itemTypeStores.current.value.itemTypes.filter(i =>
itemDropStore.enemyDrops.getDropsForItemType(i.id).length
);
}
@ -73,13 +73,15 @@ class HuntOptimizerStore {
);
if (wantedItemsJson) {
const itemStore = await itemKindStores.current.promise;
const itemStore = await itemTypeStores.current.promise;
const wi = JSON.parse(wantedItemsJson);
const wantedItems: WantedItem[] = [];
for (const { itemKindId, amount } of wi) {
const item = itemStore.getById(itemKindId);
for (const { itemTypeId, itemKindId, amount } of wi) {
const item = itemTypeId != null
? itemStore.getById(itemTypeId)
: itemStore.getById(itemKindId); // Legacy name.
if (item) {
wantedItems.push(new WantedItem(item, amount));
@ -95,8 +97,8 @@ class HuntOptimizerStore {
localStorage.setItem(
`HuntOptimizerStore.wantedItems.${Server[applicationStore.currentServer]}`,
JSON.stringify(
this.wantedItems.map(({ itemKind, amount }) => ({
itemKindId: itemKind.id,
this.wantedItems.map(({ itemType, amount }) => ({
itemTypeId: itemType.id,
amount
}))
)
@ -114,7 +116,7 @@ class HuntOptimizerStore {
// Initialize this set before awaiting data, so user changes don't affect this optimization
// run from this point on.
const wantedItems = new Set(this.wantedItems.filter(w => w.amount > 0).map(w => w.itemKind));
const wantedItems = new Set(this.wantedItems.filter(w => w.amount > 0).map(w => w.itemType));
const methods = await huntMethodStore.methods.current.promise;
const dropTable = (await itemDropStores.current.promise).enemyDrops;
@ -123,7 +125,7 @@ class HuntOptimizerStore {
const constraints: { [itemName: string]: { min: number } } = {};
for (const wanted of this.wantedItems) {
constraints[wanted.itemKind.name] = { min: wanted.amount };
constraints[wanted.itemType.name] = { min: wanted.amount };
}
// Add a variable to the LP model per method per difficulty per section ID.
@ -216,9 +218,9 @@ class HuntOptimizerStore {
for (const [npcType, count] of counts.entries()) {
const drop = dropTable.getDrop(diff, sectionId, npcType);
if (drop && wantedItems.has(drop.item)) {
const value = variable[drop.item.name] || 0;
variable[drop.item.name] = value + count * drop.rate;
if (drop && wantedItems.has(drop.itemType)) {
const value = variable[drop.itemType.name] || 0;
variable[drop.itemType.name] = value + count * drop.rate;
addVariable = true;
}
}
@ -271,7 +273,7 @@ class HuntOptimizerStore {
const runs = runsOrOther as number;
const variable = variables[variableName];
const items = new Map<ItemKind, number>();
const items = new Map<ItemType, number>();
for (const [itemName, expectedAmount] of Object.entries(variable)) {
for (const item of wantedItems) {

View File

@ -1,9 +1,9 @@
import { observable } from "mobx";
import { Difficulties, Difficulty, EnemyDrop, NpcType, SectionId, SectionIds, Server, ItemKind } from "../domain";
import { Difficulties, Difficulty, EnemyDrop, NpcType, SectionId, SectionIds, Server, ItemType } from "../domain";
import { NpcTypes } from "../domain/NpcType";
import { EnemyDropDto } from "../dto";
import { Loadable } from "../Loadable";
import { itemKindStores } from "./ItemKindStore";
import { itemTypeStores } from "./ItemTypeStore";
import { ServerMap } from "./ServerMap";
class EnemyDropTable {
@ -11,8 +11,8 @@ class EnemyDropTable {
private table: Array<EnemyDrop> =
new Array(Difficulties.length * SectionIds.length * NpcTypes.length);
// Mapping of ItemKind ids to EnemyDrops.
private itemKindToDrops: Array<Array<EnemyDrop>> = new Array();
// Mapping of ItemType ids to EnemyDrops.
private itemTypeToDrops: Array<Array<EnemyDrop>> = new Array();
getDrop(difficulty: Difficulty, sectionId: SectionId, npcType: NpcType): EnemyDrop | undefined {
return this.table[
@ -29,18 +29,18 @@ class EnemyDropTable {
+ npcType.id
] = drop;
let drops = this.itemKindToDrops[drop.item.id];
let drops = this.itemTypeToDrops[drop.itemType.id];
if (!drops) {
drops = [];
this.itemKindToDrops[drop.item.id] = drops;
this.itemTypeToDrops[drop.itemType.id] = drops;
}
drops.push(drop);
}
getDropsForItemKind(itemKindId: number): Array<EnemyDrop> {
return this.itemKindToDrops[itemKindId] || [];
getDropsForItemType(itemTypeId: number): Array<EnemyDrop> {
return this.itemTypeToDrops[itemTypeId] || [];
}
}
@ -48,7 +48,7 @@ class ItemDropStore {
@observable enemyDrops: EnemyDropTable = new EnemyDropTable();
load = async (server: Server): Promise<ItemDropStore> => {
const itemKindStore = await itemKindStores.current.promise;
const itemTypeStore = await itemTypeStores.current.promise;
const response = await fetch(
`${process.env.PUBLIC_URL}/enemyDrops.${Server[server].toLowerCase()}.json`
);
@ -65,10 +65,10 @@ class ItemDropStore {
}
const difficulty = (Difficulty as any)[dropDto.difficulty];
const itemKind = itemKindStore.getById(dropDto.itemKindId);
const itemType = itemTypeStore.getById(dropDto.itemTypeId);
if (!itemKind) {
console.warn(`Couldn't find item kind ${dropDto.itemKindId}.`);
if (!itemType) {
console.warn(`Couldn't find item kind ${dropDto.itemTypeId}.`);
continue;
}
@ -83,7 +83,7 @@ class ItemDropStore {
difficulty,
sectionId,
npcType,
itemKind,
itemType,
dropDto.dropRate,
dropDto.rareRate
));

View File

@ -1,80 +0,0 @@
import { observable } from "mobx";
import { ItemKind, Server, WeaponItemKind, ArmorItemKind, ShieldItemKind, ToolItemKind, UnitItemKind } from "../domain";
import { Loadable } from "../Loadable";
import { ServerMap } from "./ServerMap";
import { ItemKindDto } from "../dto";
class ItemKindStore {
private idToItemKind: Array<ItemKind> = [];
@observable itemKinds: Array<ItemKind> = [];
getById(id: number): ItemKind | undefined {
return this.idToItemKind[id];
}
load = async (server: Server): Promise<ItemKindStore> => {
const response = await fetch(
`${process.env.PUBLIC_URL}/itemKinds.${Server[server].toLowerCase()}.json`
);
const data: Array<ItemKindDto> = await response.json();
const itemKinds = new Array<ItemKind>();
for (const itemKindDto of data) {
let itemKind: ItemKind;
switch (itemKindDto.type) {
case 'weapon':
itemKind = new WeaponItemKind(
itemKindDto.id,
itemKindDto.name,
itemKindDto.minAtp,
itemKindDto.maxAtp,
itemKindDto.ata,
itemKindDto.maxGrind,
itemKindDto.requiredAtp,
);
break;
case 'armor':
itemKind = new ArmorItemKind(
itemKindDto.id,
itemKindDto.name,
);
break;
case 'shield':
itemKind = new ShieldItemKind(
itemKindDto.id,
itemKindDto.name,
);
break;
case 'unit':
itemKind = new UnitItemKind(
itemKindDto.id,
itemKindDto.name,
);
break;
case 'tool':
itemKind = new ToolItemKind(
itemKindDto.id,
itemKindDto.name,
);
break;
default:
continue;
}
this.idToItemKind[itemKind.id] = itemKind;
itemKinds.push(itemKind);
}
this.itemKinds = itemKinds;
return this;
}
}
export const itemKindStores: ServerMap<Loadable<ItemKindStore>> = new ServerMap(server => {
const store = new ItemKindStore();
return new Loadable(store, () => store.load(server));
});

View File

@ -0,0 +1,80 @@
import { observable } from "mobx";
import { ItemType, Server, WeaponItemType, ArmorItemType, ShieldItemType, ToolItemType, UnitItemType } from "../domain";
import { Loadable } from "../Loadable";
import { ServerMap } from "./ServerMap";
import { ItemTypeDto } from "../dto";
class ItemTypeStore {
private idToItemType: Array<ItemType> = [];
@observable itemTypes: Array<ItemType> = [];
getById(id: number): ItemType | undefined {
return this.idToItemType[id];
}
load = async (server: Server): Promise<ItemTypeStore> => {
const response = await fetch(
`${process.env.PUBLIC_URL}/itemTypes.${Server[server].toLowerCase()}.json`
);
const data: Array<ItemTypeDto> = await response.json();
const itemTypes = new Array<ItemType>();
for (const itemTypeDto of data) {
let itemType: ItemType;
switch (itemTypeDto.class) {
case 'weapon':
itemType = new WeaponItemType(
itemTypeDto.id,
itemTypeDto.name,
itemTypeDto.minAtp,
itemTypeDto.maxAtp,
itemTypeDto.ata,
itemTypeDto.maxGrind,
itemTypeDto.requiredAtp,
);
break;
case 'armor':
itemType = new ArmorItemType(
itemTypeDto.id,
itemTypeDto.name,
);
break;
case 'shield':
itemType = new ShieldItemType(
itemTypeDto.id,
itemTypeDto.name,
);
break;
case 'unit':
itemType = new UnitItemType(
itemTypeDto.id,
itemTypeDto.name,
);
break;
case 'tool':
itemType = new ToolItemType(
itemTypeDto.id,
itemTypeDto.name,
);
break;
default:
continue;
}
this.idToItemType[itemType.id] = itemType;
itemTypes.push(itemType);
}
this.itemTypes = itemTypes;
return this;
}
}
export const itemTypeStores: ServerMap<Loadable<ItemTypeStore>> = new ServerMap(server => {
const store = new ItemTypeStore();
return new Loadable(store, () => store.load(server));
});

View File

@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import React from "react";
import { AutoSizer, Column, Table, TableCellRenderer } from "react-virtualized";
import { huntOptimizerStore, WantedItem } from "../../stores/HuntOptimizerStore";
import { itemKindStores } from "../../stores/ItemKindStore";
import { itemTypeStores } from "../../stores/ItemTypeStore";
import { BigSelect } from "../BigSelect";
import './WantedItemsComponent.less';
@ -35,9 +35,9 @@ export class WantedItemsComponent extends React.Component {
placeholder="Add an item"
value={undefined}
style={{ width: 200 }}
options={huntOptimizerStore.huntableItems.map(itemKind => ({
label: itemKind.name,
value: itemKind.id
options={huntOptimizerStore.huntableItemTypes.map(itemType => ({
label: itemType.name,
value: itemType.id
}))}
onChange={this.addWanted}
/>
@ -74,7 +74,7 @@ export class WantedItemsComponent extends React.Component {
width={150}
flexGrow={1}
cellDataGetter={({ rowData }) =>
(rowData as WantedItem).itemKind.name
(rowData as WantedItem).itemType.name
}
/>
<Column
@ -92,11 +92,11 @@ export class WantedItemsComponent extends React.Component {
private addWanted = (selected: any) => {
if (selected) {
let added = huntOptimizerStore.wantedItems.find(w => w.itemKind.id === selected.value);
let added = huntOptimizerStore.wantedItems.find(w => w.itemType.id === selected.value);
if (!added) {
const itemKind = itemKindStores.current.value.getById(selected.value)!;
huntOptimizerStore.wantedItems.push(new WantedItem(itemKind, 1));
const itemType = itemTypeStores.current.value.getById(selected.value)!;
huntOptimizerStore.wantedItems.push(new WantedItem(itemType, 1));
}
}
}

View File

@ -2,13 +2,13 @@ import cheerio from 'cheerio';
import fs from 'fs';
import 'isomorphic-fetch';
import { Difficulty, NpcType, SectionId, SectionIds } from '../src/domain';
import { BoxDropDto, EnemyDropDto, ItemKindDto } from '../src/dto';
import { BoxDropDto, EnemyDropDto, ItemTypeDto } from '../src/dto';
export async function updateDropsFromWebsite(items: ItemKindDto[]) {
const normal = await download(items, Difficulty.Normal);
const hard = await download(items, Difficulty.Hard);
const vhard = await download(items, Difficulty.VHard, 'very-hard');
const ultimate = await download(items, Difficulty.Ultimate);
export async function updateDropsFromWebsite(itemTypes: ItemTypeDto[]) {
const normal = await download(itemTypes, Difficulty.Normal);
const hard = await download(itemTypes, Difficulty.Hard);
const vhard = await download(itemTypes, Difficulty.VHard, 'very-hard');
const ultimate = await download(itemTypes, Difficulty.Ultimate);
const enemyJson = JSON.stringify([
...normal.enemyDrops,
@ -30,7 +30,7 @@ export async function updateDropsFromWebsite(items: ItemKindDto[]) {
}
async function download(
items: ItemKindDto[],
itemTypes: ItemTypeDto[],
difficulty: Difficulty,
difficultyUrl: string = Difficulty[difficulty].toLowerCase()
) {
@ -109,10 +109,10 @@ async function download(
}
try {
const itemKind = items.find(i => i.name === item);
const itemType = itemTypes.find(i => i.name === item);
if (!itemKind) {
throw new Error(`No item kind found with name "${item}".`)
if (!itemType) {
throw new Error(`No item type found with name "${item}".`)
}
const npcType = NpcType.byNameAndEpisode(enemyOrBox, episode);
@ -132,7 +132,7 @@ async function download(
episode,
sectionId: SectionId[sectionId],
enemy: npcType.code,
itemKindId: itemKind.id,
itemTypeId: itemType.id,
dropRate: dropRateNum / dropRateDenom,
rareRate: rareRateNum / rareRateDenom,
});

View File

@ -4,7 +4,7 @@ import { parseItemPmt } 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';
import { BoxDropDto, EnemyDropDto, ItemKindDto } from '../src/dto';
import { BoxDropDto, EnemyDropDto, ItemTypeDto } from '../src/dto';
import { updateDropsFromWebsite } from './updateDropsEphinea';
/**
@ -48,13 +48,13 @@ async function loadUnitxt(): Promise<Unitxt> {
return unitxt;
}
async function updateItems(itemNames: Array<string>): Promise<ItemKindDto[]> {
async function updateItems(itemNames: Array<string>): Promise<ItemTypeDto[]> {
const buf = await fs.promises.readFile(
`${RESOURCE_DIR}/ship-config/param/ItemPMT.bin`
);
const itemPmt = parseItemPmt(new ArrayBufferCursor(buf.buffer, true));
const items = new Array<ItemKindDto>();
const itemTypes = new Array<ItemTypeDto>();
const ids = new Set<number>();
itemPmt.weapons.forEach((category, categoryI) => {
@ -63,8 +63,8 @@ async function updateItems(itemNames: Array<string>): Promise<ItemKindDto[]> {
if (!ids.has(id)) {
ids.add(id);
items.push({
type: 'weapon',
itemTypes.push({
class: 'weapon',
id,
name: itemNames[weapon.id],
minAtp: weapon.minAtp,
@ -82,8 +82,8 @@ async function updateItems(itemNames: Array<string>): Promise<ItemKindDto[]> {
if (!ids.has(id)) {
ids.add(id);
items.push({
type: 'armor',
itemTypes.push({
class: 'armor',
id,
name: itemNames[armor.id],
});
@ -95,8 +95,8 @@ async function updateItems(itemNames: Array<string>): Promise<ItemKindDto[]> {
if (!ids.has(id)) {
ids.add(id);
items.push({
type: 'shield',
itemTypes.push({
class: 'shield',
id,
name: itemNames[shield.id],
});
@ -108,8 +108,8 @@ async function updateItems(itemNames: Array<string>): Promise<ItemKindDto[]> {
if (!ids.has(id)) {
ids.add(id);
items.push({
type: 'unit',
itemTypes.push({
class: 'unit',
id,
name: itemNames[unit.id],
});
@ -122,8 +122,8 @@ async function updateItems(itemNames: Array<string>): Promise<ItemKindDto[]> {
if (!ids.has(id)) {
ids.add(id);
items.push({
type: 'tool',
itemTypes.push({
class: 'tool',
id,
name: itemNames[tool.id],
});
@ -132,11 +132,11 @@ async function updateItems(itemNames: Array<string>): Promise<ItemKindDto[]> {
});
await fs.promises.writeFile(
`${PUBLIC_DIR}/itemKinds.ephinea.json`,
JSON.stringify(items, null, 4)
`${PUBLIC_DIR}/itemTypes.ephinea.json`,
JSON.stringify(itemTypes, null, 4)
);
return items;
return itemTypes;
}
async function updateDrops(itemPt: ItemPt) {
@ -408,18 +408,18 @@ async function loadEnemyDrops(
if (enemy) {
const rareRate = expandDropRate(parseInt(prevLine, 10));
const itemId = parseInt(trimmed, 16);
const itemTypeId = parseInt(trimmed, 16);
const dar = itemPt[episode][difficulty][sectionId].darTable.get(enemy);
if (dar == null) {
console.error(`No DAR found for ${enemy.name}.`);
} else if (rareRate > 0 && itemId) {
} else if (rareRate > 0 && itemTypeId) {
drops.push({
difficulty: Difficulty[difficulty],
episode: episode,
sectionId: SectionId[sectionId],
enemy: enemy.code,
itemKindId: itemId,
itemTypeId,
dropRate: dar,
rareRate,
});
@ -455,15 +455,15 @@ async function loadBoxDrops(
if (lineNo % 3 == 2) {
const areaId = parseInt(prevPrevLine, 10);
const dropRate = expandDropRate(parseInt(prevLine, 10));
const itemId = parseInt(trimmed, 16);
const itemTypeId = parseInt(trimmed, 16);
if (dropRate > 0 && itemId) {
if (dropRate > 0 && itemTypeId) {
drops.push({
difficulty: Difficulty[difficulty],
episode: episode,
sectionId: SectionId[sectionId],
areaId,
itemKindId: itemId,
itemTypeId,
dropRate,
});
}