mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Added unstyled methods table to hunt optimizer.
This commit is contained in:
parent
76620f612c
commit
e2b1cc9282
@ -59,7 +59,7 @@ export class Loadable<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns valid data as soon as possible.
|
* This property returns valid data as soon as possible.
|
||||||
* If the Loadable is uninitialized a data load will be triggered, otherwise the current value will be returned.
|
* If the Loadable is uninitialized a data load will be triggered, otherwise the current value will be returned.
|
||||||
*/
|
*/
|
||||||
get promise(): Promise<T> {
|
get promise(): Promise<T> {
|
||||||
|
@ -447,3 +447,190 @@ export class NpcType {
|
|||||||
NpcType.SaintMilion.rareType = NpcType.Kondrieu;
|
NpcType.SaintMilion.rareType = NpcType.Kondrieu;
|
||||||
NpcType.Shambertin.rareType = NpcType.Kondrieu;
|
NpcType.Shambertin.rareType = NpcType.Kondrieu;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
export const NpcTypes: Array<NpcType> = [
|
||||||
|
|
||||||
|
//
|
||||||
|
// Unknown NPCs
|
||||||
|
//
|
||||||
|
|
||||||
|
NpcType.Unknown,
|
||||||
|
|
||||||
|
//
|
||||||
|
// Friendly NPCs
|
||||||
|
//
|
||||||
|
|
||||||
|
NpcType.FemaleFat,
|
||||||
|
NpcType.FemaleMacho,
|
||||||
|
NpcType.FemaleTall,
|
||||||
|
NpcType.MaleDwarf,
|
||||||
|
NpcType.MaleFat,
|
||||||
|
NpcType.MaleMacho,
|
||||||
|
NpcType.MaleOld,
|
||||||
|
NpcType.BlueSoldier,
|
||||||
|
NpcType.RedSoldier,
|
||||||
|
NpcType.Principal,
|
||||||
|
NpcType.Tekker,
|
||||||
|
NpcType.GuildLady,
|
||||||
|
NpcType.Scientist,
|
||||||
|
NpcType.Nurse,
|
||||||
|
NpcType.Irene,
|
||||||
|
NpcType.ItemShop,
|
||||||
|
NpcType.Nurse2,
|
||||||
|
|
||||||
|
//
|
||||||
|
// Enemy NPCs
|
||||||
|
//
|
||||||
|
|
||||||
|
// Episode I Forest
|
||||||
|
|
||||||
|
NpcType.Hildebear,
|
||||||
|
NpcType.Hildeblue,
|
||||||
|
NpcType.RagRappy,
|
||||||
|
NpcType.AlRappy,
|
||||||
|
NpcType.Monest,
|
||||||
|
NpcType.Mothmant,
|
||||||
|
NpcType.SavageWolf,
|
||||||
|
NpcType.BarbarousWolf,
|
||||||
|
NpcType.Booma,
|
||||||
|
NpcType.Gobooma,
|
||||||
|
NpcType.Gigobooma,
|
||||||
|
NpcType.Dragon,
|
||||||
|
|
||||||
|
// Episode I Caves
|
||||||
|
|
||||||
|
NpcType.GrassAssassin,
|
||||||
|
NpcType.PoisonLily,
|
||||||
|
NpcType.NarLily,
|
||||||
|
NpcType.NanoDragon,
|
||||||
|
NpcType.EvilShark,
|
||||||
|
NpcType.PalShark,
|
||||||
|
NpcType.GuilShark,
|
||||||
|
NpcType.PofuillySlime,
|
||||||
|
NpcType.PouillySlime,
|
||||||
|
NpcType.PanArms,
|
||||||
|
NpcType.Migium,
|
||||||
|
NpcType.Hidoom,
|
||||||
|
NpcType.DeRolLe,
|
||||||
|
|
||||||
|
// Episode I Mines
|
||||||
|
|
||||||
|
NpcType.Dubchic,
|
||||||
|
NpcType.Gilchic,
|
||||||
|
NpcType.Garanz,
|
||||||
|
NpcType.SinowBeat,
|
||||||
|
NpcType.SinowGold,
|
||||||
|
NpcType.Canadine,
|
||||||
|
NpcType.Canane,
|
||||||
|
NpcType.Dubswitch,
|
||||||
|
NpcType.VolOpt,
|
||||||
|
|
||||||
|
// Episode I Ruins
|
||||||
|
|
||||||
|
NpcType.Delsaber,
|
||||||
|
NpcType.ChaosSorcerer,
|
||||||
|
NpcType.DarkGunner,
|
||||||
|
NpcType.DeathGunner,
|
||||||
|
NpcType.ChaosBringer,
|
||||||
|
NpcType.DarkBelra,
|
||||||
|
NpcType.Dimenian,
|
||||||
|
NpcType.LaDimenian,
|
||||||
|
NpcType.SoDimenian,
|
||||||
|
NpcType.Bulclaw,
|
||||||
|
NpcType.Bulk,
|
||||||
|
NpcType.Claw,
|
||||||
|
NpcType.DarkFalz,
|
||||||
|
|
||||||
|
// Episode II VR Temple
|
||||||
|
|
||||||
|
NpcType.Hildebear2,
|
||||||
|
NpcType.Hildeblue2,
|
||||||
|
NpcType.RagRappy2,
|
||||||
|
NpcType.LoveRappy,
|
||||||
|
NpcType.StRappy,
|
||||||
|
NpcType.HalloRappy,
|
||||||
|
NpcType.EggRappy,
|
||||||
|
NpcType.Monest2,
|
||||||
|
NpcType.Mothmant2,
|
||||||
|
NpcType.PoisonLily2,
|
||||||
|
NpcType.NarLily2,
|
||||||
|
NpcType.GrassAssassin2,
|
||||||
|
NpcType.Dimenian2,
|
||||||
|
NpcType.LaDimenian2,
|
||||||
|
NpcType.SoDimenian2,
|
||||||
|
NpcType.DarkBelra2,
|
||||||
|
NpcType.BarbaRay,
|
||||||
|
|
||||||
|
// Episode II VR Spaceship
|
||||||
|
|
||||||
|
NpcType.SavageWolf2,
|
||||||
|
NpcType.BarbarousWolf2,
|
||||||
|
NpcType.PanArms2,
|
||||||
|
NpcType.Migium2,
|
||||||
|
NpcType.Hidoom2,
|
||||||
|
NpcType.Dubchic2,
|
||||||
|
NpcType.Gilchic2,
|
||||||
|
NpcType.Garanz2,
|
||||||
|
NpcType.Dubswitch2,
|
||||||
|
NpcType.Delsaber2,
|
||||||
|
NpcType.ChaosSorcerer2,
|
||||||
|
NpcType.GolDragon,
|
||||||
|
|
||||||
|
// Episode II Central Control Area
|
||||||
|
|
||||||
|
NpcType.SinowBerill,
|
||||||
|
NpcType.SinowSpigell,
|
||||||
|
NpcType.Merillia,
|
||||||
|
NpcType.Meriltas,
|
||||||
|
NpcType.Mericarol,
|
||||||
|
NpcType.Mericus,
|
||||||
|
NpcType.Merikle,
|
||||||
|
NpcType.UlGibbon,
|
||||||
|
NpcType.ZolGibbon,
|
||||||
|
NpcType.Gibbles,
|
||||||
|
NpcType.Gee,
|
||||||
|
NpcType.GiGue,
|
||||||
|
NpcType.GalGryphon,
|
||||||
|
|
||||||
|
// Episode II Seabed
|
||||||
|
|
||||||
|
NpcType.Deldepth,
|
||||||
|
NpcType.Delbiter,
|
||||||
|
NpcType.Dolmolm,
|
||||||
|
NpcType.Dolmdarl,
|
||||||
|
NpcType.Morfos,
|
||||||
|
NpcType.Recobox,
|
||||||
|
NpcType.Recon,
|
||||||
|
NpcType.Epsilon,
|
||||||
|
NpcType.SinowZoa,
|
||||||
|
NpcType.SinowZele,
|
||||||
|
NpcType.IllGill,
|
||||||
|
NpcType.DelLily,
|
||||||
|
NpcType.OlgaFlow,
|
||||||
|
|
||||||
|
// Episode IV
|
||||||
|
|
||||||
|
NpcType.SandRappy,
|
||||||
|
NpcType.DelRappy,
|
||||||
|
NpcType.Astark,
|
||||||
|
NpcType.SatelliteLizard,
|
||||||
|
NpcType.Yowie,
|
||||||
|
NpcType.MerissaA,
|
||||||
|
NpcType.MerissaAA,
|
||||||
|
NpcType.Girtablulu,
|
||||||
|
NpcType.Zu,
|
||||||
|
NpcType.Pazuzu,
|
||||||
|
NpcType.Boota,
|
||||||
|
NpcType.ZeBoota,
|
||||||
|
NpcType.BaBoota,
|
||||||
|
NpcType.Dorphon,
|
||||||
|
NpcType.DorphonEclair,
|
||||||
|
NpcType.Goran,
|
||||||
|
NpcType.PyroGoran,
|
||||||
|
NpcType.GoranDetonator,
|
||||||
|
NpcType.SaintMilion,
|
||||||
|
NpcType.Shambertin,
|
||||||
|
NpcType.Kondrieu,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const EnemyNpcTypes = NpcTypes.filter(type => type.enemy);
|
||||||
|
@ -338,27 +338,37 @@ export class EnemyDrop implements ItemDrop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class HuntMethod {
|
export class HuntMethod {
|
||||||
|
readonly npcs: Array<SimpleNpc>;
|
||||||
|
readonly enemies: Array<SimpleNpc>;
|
||||||
|
readonly enemyCounts: Map<NpcType, number>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
/**
|
/**
|
||||||
* The time it takes to complete the quest in hours.
|
* The time it takes to complete the quest in hours.
|
||||||
*/
|
*/
|
||||||
public time: number,
|
public readonly time: number,
|
||||||
public name: string,
|
public readonly name: string,
|
||||||
public quest: SimpleQuest
|
public readonly quest: SimpleQuest
|
||||||
) { }
|
) {
|
||||||
|
if (time <= 0) throw new Error('time must be greater than zero.');
|
||||||
|
|
||||||
|
this.npcs = this.quest.npcs;
|
||||||
|
this.enemies = this.npcs.filter(npc => npc.type.enemy);
|
||||||
|
this.enemyCounts = new Map();
|
||||||
|
|
||||||
|
for (const npc of this.enemies) {
|
||||||
|
this.enemyCounts.set(npc.type, (this.enemyCounts.get(npc.type) || 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SimpleQuest {
|
export class SimpleQuest {
|
||||||
enemies: SimpleNpc[];
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public name: string,
|
public readonly name: string,
|
||||||
public npcs: SimpleNpc[]
|
public readonly npcs: SimpleNpc[]
|
||||||
) {
|
) {
|
||||||
if (!name) throw new Error('name is required.');
|
if (!name) throw new Error('name is required.');
|
||||||
if (!npcs) throw new Error('npcs is required.');
|
if (!npcs) throw new Error('npcs is required.');
|
||||||
|
|
||||||
this.enemies = npcs.filter(npc => npc.type.enemy);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ class HuntOptimizerStore {
|
|||||||
// Counts include rare enemies, so they are fractional.
|
// Counts include rare enemies, so they are fractional.
|
||||||
const counts = new Map<NpcType, number>();
|
const counts = new Map<NpcType, number>();
|
||||||
|
|
||||||
for (const enemy of method.quest.enemies) {
|
for (const enemy of method.enemies) {
|
||||||
const count = counts.get(enemy.type);
|
const count = counts.get(enemy.type);
|
||||||
|
|
||||||
if (enemy.type.rareType == null) {
|
if (enemy.type.rareType == null) {
|
||||||
|
@ -5,7 +5,23 @@
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ho-HuntOptimizerComponent > *:nth-child(2) {
|
.ho-HuntOptimizerComponent > .ant-tabs {
|
||||||
flex-grow: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
display: flex;
|
||||||
}
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ho-HuntOptimizerComponent > .ant-tabs > .ant-tabs-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ho-HuntOptimizerComponent > .ant-tabs > .ant-tabs-content > .ant-tabs-tabpane-active {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
|
import { Tabs } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import './HuntOptimizerComponent.css';
|
import './HuntOptimizerComponent.css';
|
||||||
import { WantedItemsComponent } from "./WantedItemsComponent";
|
import { OptimizerComponent } from "./OptimizerComponent";
|
||||||
import { OptimizationResultComponent } from "./OptimizationResultComponent";
|
import { MethodsComponent } from "./MethodsComponent";
|
||||||
|
|
||||||
|
const TabPane = Tabs.TabPane;
|
||||||
|
|
||||||
export function HuntOptimizerComponent() {
|
export function HuntOptimizerComponent() {
|
||||||
return (
|
return (
|
||||||
<section className="ho-HuntOptimizerComponent">
|
<section className="ho-HuntOptimizerComponent">
|
||||||
<WantedItemsComponent />
|
<Tabs type="card">
|
||||||
<OptimizationResultComponent />
|
<TabPane tab="Optimize" key="optimize">
|
||||||
|
<OptimizerComponent />
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab="Methods" key="methods">
|
||||||
|
<MethodsComponent />
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
3
src/ui/hunt-optimizer/MethodsComponent.css
Normal file
3
src/ui/hunt-optimizer/MethodsComponent.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.ho-MethodsComponent {
|
||||||
|
flex: 1;
|
||||||
|
}
|
110
src/ui/hunt-optimizer/MethodsComponent.tsx
Normal file
110
src/ui/hunt-optimizer/MethodsComponent.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { observer } from "mobx-react";
|
||||||
|
import React from "react";
|
||||||
|
import { AutoSizer, GridCellRenderer, MultiGrid, Index } from "react-virtualized";
|
||||||
|
import { HuntMethod } from "../../domain";
|
||||||
|
import { EnemyNpcTypes } from "../../domain/NpcType";
|
||||||
|
import { huntMethodStore } from "../../stores/HuntMethodStore";
|
||||||
|
import "./MethodsComponent.css";
|
||||||
|
|
||||||
|
type Column = {
|
||||||
|
name: string,
|
||||||
|
width: number,
|
||||||
|
cellValue: (method: HuntMethod) => string,
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class MethodsComponent extends React.Component {
|
||||||
|
static columns: Array<Column> = (() => {
|
||||||
|
// Standard columns.
|
||||||
|
const columns: Column[] = [
|
||||||
|
{
|
||||||
|
name: 'Method',
|
||||||
|
width: 250,
|
||||||
|
cellValue: (method) => method.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hours',
|
||||||
|
width: 50,
|
||||||
|
cellValue: (method) => method.time.toString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// One column per enemy type.
|
||||||
|
for (const enemy of EnemyNpcTypes) {
|
||||||
|
columns.push({
|
||||||
|
name: enemy.name,
|
||||||
|
width: 50,
|
||||||
|
cellValue: (method) => {
|
||||||
|
const count = method.enemyCounts.get(enemy);
|
||||||
|
return count == null ? '' : count.toString();
|
||||||
|
},
|
||||||
|
className: 'number',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
})();
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const methods = huntMethodStore.methods.current.value;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="ho-MethodsComponent">
|
||||||
|
<AutoSizer>
|
||||||
|
{({ width, height }) => (
|
||||||
|
<MultiGrid
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
rowHeight={28}
|
||||||
|
rowCount={methods.length}
|
||||||
|
fixedRowCount={1}
|
||||||
|
columnWidth={this.columnWidth}
|
||||||
|
columnCount={2 + EnemyNpcTypes.length}
|
||||||
|
fixedColumnCount={2}
|
||||||
|
cellRenderer={this.cellRenderer}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private columnWidth = ({ index }: Index): number => {
|
||||||
|
return MethodsComponent.columns[index].width;
|
||||||
|
}
|
||||||
|
|
||||||
|
private cellRenderer: GridCellRenderer = ({ columnIndex, rowIndex, style }) => {
|
||||||
|
const column = MethodsComponent.columns[columnIndex];
|
||||||
|
let text: string;
|
||||||
|
const classes = [];
|
||||||
|
|
||||||
|
if (columnIndex === MethodsComponent.columns.length - 1) {
|
||||||
|
classes.push('last-in-row');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowIndex === 0) {
|
||||||
|
// Header row
|
||||||
|
text = column.name;
|
||||||
|
} else {
|
||||||
|
// Method row
|
||||||
|
if (column.className) {
|
||||||
|
classes.push(column.className);
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = huntMethodStore.methods.current.value[rowIndex - 1];
|
||||||
|
|
||||||
|
text = column.cellValue(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classes.join(' ')}
|
||||||
|
key={`${columnIndex}, ${rowIndex}`}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
<span>{text}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
10
src/ui/hunt-optimizer/OptimizerComponent.css
Normal file
10
src/ui/hunt-optimizer/OptimizerComponent.css
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.ho-OptimizerComponent {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ho-OptimizerComponent > *:nth-child(2) {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
13
src/ui/hunt-optimizer/OptimizerComponent.tsx
Normal file
13
src/ui/hunt-optimizer/OptimizerComponent.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { WantedItemsComponent } from "./WantedItemsComponent";
|
||||||
|
import { OptimizationResultComponent } from "./OptimizationResultComponent";
|
||||||
|
import "./OptimizerComponent.css";
|
||||||
|
|
||||||
|
export function OptimizerComponent() {
|
||||||
|
return (
|
||||||
|
<section className="ho-OptimizerComponent">
|
||||||
|
<WantedItemsComponent />
|
||||||
|
<OptimizationResultComponent />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user