1
0
Fork 0

more ui updates and core logic

This commit is contained in:
iTNTPiston 2022-06-23 04:51:26 -07:00
parent 0cf48a91aa
commit 8567bec9f9
20 changed files with 1502 additions and 983 deletions

View file

@ -30,11 +30,30 @@ h1 {
}
.CommandItem:hover {
background-color: #eeeeee;
border: 1px solid black;
}
.CommandInput {
border-top: none;
border-left: none;
border-right: none;
border-bottom: 2px solid #0074d9;
color: #b7f1ff;
text-shadow: 0 0 5px #3aa0ff,0 0 5px #3aa0ff,0 0 5px #3aa0ff;
}
.CommandInput:focus-visible {
border-color: #b7f1ff;
}
.InputError {
background-color: #eeaaaa;
color: #ff3333;
text-shadow: 0 0 5px #ee7777,0 0 5px #ee7777,0 0 5px #ee7777;
border-bottom: 2px solid #dd0000;
}
.InputError:focus-visible {
border-bottom: 2px solid #ff3333;
}
.ItemSlot {
@ -42,7 +61,7 @@ h1 {
width: 64px;
height: 64px;
background-color: #333333;
background-color: #333333bb;
box-sizing: content-box;
display: inline-block;
}
@ -51,7 +70,7 @@ h1 {
background-color: #660000;
}
.ItemSlotSave {
.GameDataBackground {
background-color: #003300;
}
@ -81,3 +100,21 @@ h3.ListHeader {
margin: 0;
padding: 10px;
}
h4.Reference {
margin: 0;
color: #cccccc;
}
p.Reference {
padding-left: 20px;
}
p.Example {
color:#eeee00;
}
h3.Reference {
margin-top: 30px;
margin-bottom: 0
}

View file

@ -1,16 +1,19 @@
import { Command, CommandBreakSlots, CommandInitialize, CommandNothing, CommandReload, CommandSave, CommandSortKey } from "core/Command";
import { Command, CommandNothing } from "core/Command";
import { Inventory } from "core/Inventory";
import React, { useEffect, useMemo, useRef, useState } from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import "./App.css";
import { CommandItem } from "./components/CommandItem";
import { DisplayPane, stacksToItemListProps } from "surfaces/DisplayPane";
import { DisplayPane } from "surfaces/DisplayPane";
import { Item } from "core/Item";
import { saveAs } from "data/FileSaver";
import { parseCommand } from "core/Parser";
import { ItemList } from "components/ItemList";
import { TitledList } from "components/TitledList";
import { createSimulationState, SimulationState } from "core/SimulationState";
import { ReferencePage } from "surfaces/ReferencePage";
import { GameData } from "core/GameData";
const getDefaultCommands = (): Command[]=>{
const encoded = localStorage.getItem("HDS.CurrentCommandsText");
@ -19,64 +22,21 @@ const getDefaultCommands = (): Command[]=>{
return lines.map(l=>parseCommand(l)).filter(c=>c) as Command[];
}
return [
new CommandInitialize([
{
item: Item.Diamond,
count: 5,
equipped:false,
},
{
item: Item.Slate,
count: 1,
equipped:false,
},
{
item: Item.Glider,
count: 1,
equipped:false,
},
{
item: Item.SpiritOrb,
count: 4,
equipped:false,
}
]),
new CommandBreakSlots(4),
new CommandReload(),
new CommandSortKey(),
new CommandSave(),
new CommandReload()
];
parseCommand("Get 5 Diamond 1 Slate 1 Glider 4 SpiritOrb"),
parseCommand("Save"),
parseCommand("Break 4 Slots"),
parseCommand("Reload"),
parseCommand("Save"),
parseCommand("Reload"),
] as Command[];;
};
const tempSaveInventory = new Inventory();
tempSaveInventory.rawInit([
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
{item: Item.Acorn, count: 55, equipped: false},
])
const listProps = stacksToItemListProps(tempSaveInventory.getSlots(), 0, true);
export const App: React.FC = () => {
const [page, setPageInState] = useState<string>("#simulation");
const [overlaySave, setOverlaySave] = useState<boolean>(false);
const [commands, setCommands] = useState<Command[]>(getDefaultCommands());
const [selectedSaveName, setSelectedSaveName] = useState<string>("");
const [displayIndex, setDisplayIndex] = useState<number>(0);
const [contextMenuX, setContextMenuX] = useState<number>(0);
const [contextMenuY, setContextMenuY] = useState<number>(0);
@ -86,20 +46,36 @@ export const App: React.FC = () => {
const uploadRef = useRef<HTMLInputElement>(null);
const contextMenuRef = useRef<HTMLDivElement>(null);
// compute props
const inventories = useMemo(()=>{
const inventories: Inventory[] = [];
const inv = new Inventory();
const simulationStates = useMemo(()=>{
const simulationStates: SimulationState[] = [];
const state = createSimulationState();
commands.forEach(c=>{
c.execute(inv);
inventories.push(inv.deepClone());
c.execute(state);
simulationStates.push(state.deepClone());
});
return inventories;
return simulationStates;
}, [commands]);
const setPage = useCallback((hash: string)=>{
window.location.hash = hash;
setPageInState(hash);
}, []);
useEffect(()=>{
setPage(window.location.hash || "#simulation");
}, [window.location.hash]);
useEffect(()=>{
window.onkeydown=(e)=>{
if(e.code==="ArrowDown"){
if(displayIndex===commands.length-1){
const arrCopy = [...commands];
arrCopy.push(new CommandNothing());
setCommands(arrCopy);
setDisplayIndex(arrCopy.length-1);
}else{
setDisplayIndex(Math.min(commands.length-1, displayIndex+1));
}
}else if(e.code==="ArrowUp"){
setDisplayIndex(Math.max(0, displayIndex-1));
}
@ -143,8 +119,12 @@ export const App: React.FC = () => {
<div id="NavBar" style={{
height: 40
}}>
<button>Simulation</button>
<button>Reference</button>
<button onClick={()=>{
setPage("#simulation")
}}>Simulation</button>
<button onClick={()=>{
setPage("#reference")
}}>Reference</button>
<button>Options</button>
</div>
@ -156,56 +136,51 @@ export const App: React.FC = () => {
maxHeight: 220,
height: "30vh",
border: "1px solid black",
boxSizing: "content-box",
overflowY: "hidden"
boxSizing: "border-box",
overflowY: "hidden",
}}>
<TitledList title="Saves">
{
displayIndex >=0 && displayIndex < simulationStates.length &&
<ol>
{
!!simulationStates[displayIndex].getManualSave() &&
<CommandItem
onClick={()=>{}}
onClick={()=>{
setSelectedSaveName("");
}}
comment={false}
isSelected={selectedSaveName===""}
>
Manual Save
</CommandItem>
}
{
Object.entries(simulationStates[displayIndex].getNamedSaves()).map(([name, _gamedata])=>(
<CommandItem
onClick={()=>{}}
onClick={()=>{
setSelectedSaveName(name);
}}
comment={false}
isSelected={selectedSaveName===name}
>
Auto Save 1
</CommandItem>
<CommandItem
onClick={()=>{}}
comment={false}
>
Auto Save 2
</CommandItem>
<CommandItem
onClick={()=>{}}
comment={false}
>
Auto Save 3
</CommandItem>
<CommandItem
onClick={()=>{}}
comment={false}
>
Auto Save 4
</CommandItem>
<CommandItem
onClick={()=>{}}
comment={false}
>
Auto Save 5
{name}
</CommandItem>
))
}
</ol>
}
</TitledList>
</div>
<div style={{
minHeight: "calc( 70vh - 45px )",
height: "calc( 100vh - 45px - 220px )",
minHeight: "calc( 70vh - 40px )",
height: "calc( 100vh - 40px - 220px )",
border: "1px solid black",
boxSizing: "content-box",
boxSizing: "border-box",
overflowY: "hidden"
}}>
@ -215,7 +190,13 @@ export const App: React.FC = () => {
{
commands.map((c,i)=>
<CommandItem
onClick={()=>setDisplayIndex(i)}
onClick={()=>{
setDisplayIndex(i);
const inputField = document.getElementById("CommandInputField");
if(inputField){
inputField.focus();
}
}}
onContextMenu={(x,y)=>{
setContextIndex(i);
setContextMenuX(x);
@ -259,39 +240,73 @@ export const App: React.FC = () => {
</div>
<div id="MainPane" style={{
width: "calc ( 100% - 300px )"
position: "absolute",
top: 40,
right: 0,
bottom: 0,
left: 300,
backgroundColor: "#262626"
}}>
{
page === "#simulation" && <>
<div style={{
maxHeight: 220,
height: "30vh",
border: "1px solid black",
boxSizing: "content-box",
overflowY: "hidden"
boxSizing: "border-box",
overflowY: "hidden",
color: "white",
backgroundColor: "#262626"
} }>
{
(displayIndex >= 0 && displayIndex < commands.length) ?
<TitledList title="Save Data">
<ItemList {...listProps}/>
{
selectedSaveName === "" && !!simulationStates[displayIndex].getManualSave() &&
<ItemList slots={(simulationStates[displayIndex].getManualSave() as GameData).getDisplayedSlots()}/>
}
{
selectedSaveName !== "" && !!simulationStates[displayIndex].getNamedSaves()[selectedSaveName] &&
<ItemList slots={(simulationStates[displayIndex].getNamedSaves()[selectedSaveName] as GameData).getDisplayedSlots()}/>
}
</TitledList>
:
<TitledList title="Select an instruction on the left to view it">
</TitledList>
}
</div>
<div >
<div style={{
minHeight: "calc( 70vh - 40px )",
height: "calc( 100vh - 40px - 220px )",
border: "1px solid black",
boxSizing: "border-box",
overflowY: "hidden"
}}>
{displayIndex >= 0 && displayIndex < commands.length &&
<DisplayPane
overlaySave={overlaySave}
displayIndex={displayIndex}
command={commands[displayIndex].getDisplayString()}
orbs={inventories[displayIndex].getTurnedInOrbs()}
slots={inventories[displayIndex].getSlots()}
savedSlots={inventories[displayIndex].getSavedSlots()}
numBroken={inventories[displayIndex].getNumBroken()}
simulationState={simulationStates[displayIndex]}
editCommand={(c)=>{
const arrCopy = [...commands];
arrCopy[displayIndex] = c;
setCommands(arrCopy);
}}
/>
}
</div>
</>
}
{
page === "#reference" && <ReferencePage />
}
</div>

BIN
src/assets/Background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/assets/Crash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
src/assets/InGame.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View file

@ -1,23 +1,15 @@
import { DisplayableSlot } from "core/DisplayableInventory";
import { ItemSlot } from "./ItemSlot";
export type ItemListItemProps = {
image: string,
count: number,
isEquipped: boolean,
}
export type ItemListProps = {
isSave: boolean,
items: ItemListItemProps[],
numBroken: number
slots: DisplayableSlot[]
}
export const ItemList: React.FC<ItemListProps> = ({items, numBroken, isSave}) => {
export const ItemList: React.FC<ItemListProps> = ({slots}) => {
return <>
{
items.map(({image, count, isEquipped}, i)=>{
const broken = i+numBroken >= items.length;
return <ItemSlot key={i} image={image} count={count} isBroken={broken} isSave={isSave} isEquipped={isEquipped}/>;
slots.map((slot, i)=>{
return <ItemSlot key={i} slot={slot}/>;
})
}
</>;

View file

@ -1,18 +1,18 @@
import clsx from "clsx";
import { DisplayableSlot } from "core/DisplayableInventory";
import Background from "assets/Background.png";
type ItemSlotProps = {
image: string,
count: number,
isBroken: boolean,
isSave: boolean,
isEquipped: boolean,
slot: DisplayableSlot
};
export const ItemSlot: React.FC<ItemSlotProps> = ({image, count, isBroken, isSave, isEquipped})=>{
export const ItemSlot: React.FC<ItemSlotProps> = ({slot: {image, count, isBrokenSlot, isEquipped, displayCount}})=>{
return (
<span className={clsx("ItemSlot", isBroken && "ItemSlotBroken", isSave && "ItemSlotSave", isEquipped && "ItemSlotEquipped")}>
<img className={clsx("ItemImage", isSave && "ItemImageSave")}src={image} />
<span className={clsx("ItemSlot", isBrokenSlot && "ItemSlotBroken", isEquipped && "ItemSlotEquipped")}>
<img className={clsx("ItemImage")} src={image} />
{
count > 0 && <span className={"ItemCount"}>
displayCount && <span className={"ItemCount"}>
x{count}
</span>
}
@ -23,8 +23,12 @@ export const ItemSlot: React.FC<ItemSlotProps> = ({image, count, isBroken, isSav
export const DoubleItemSlot: React.FC<{first?: ItemSlotProps, second?: ItemSlotProps}> = ({first, second})=>{
return (
<span style={{display: "inline-block", width: 72, height: 144, verticalAlign:"top"}}>
{first ? <ItemSlot {...first}/> : <div style={{height: 72}}/>}
{second ? <ItemSlot {...second}/> : <div style={{height: 72}}/> }
<div style={{height: 72, background: `url(${Background})`}} >
{first && <ItemSlot {...first}/>}
</div>
<div style={{height: 72}}>
{second && <ItemSlot {...second}/>}
</div>
</span>
);
};

View file

@ -8,9 +8,9 @@ export const TitledList: React.FC<TitledListProps> = ({title, children}) => {
return (
<>
<h3 className="ListHeader" style={{
height: 20,
border: "1px solid red",
boxSizing: "content-box"
height: 40,
borderBottom: "2px solid",
boxSizing: "border-box",
}}>
{title}
</h3>

View file

@ -1,17 +1,15 @@
import { Inventory } from "./Inventory";
import { Item, ItemStack, itemToArrowType } from "./Item";
import { SimulationState } from "./SimulationState";
export interface Command {
execute(inv: Inventory): void,
execute(state: SimulationState): void,
getDisplayString(): string,
// fromBuffer(buf: Buffer): number,
// toBuffer(): Buffer,
}
export class CommandNothing implements Command {
static Op = 0x0;
execute(_inv: Inventory): void {
execute(_state: Inventory): void {
// nothing
}
getDisplayString(): string {
@ -21,44 +19,14 @@ export class CommandNothing implements Command {
}
export class CommandInitialize implements Command {
static Op = 0x1;
private stacks: ItemStack[];
constructor(stacks: ItemStack[]){
this.stacks = stacks;
}
// public fromBuffer(buf: Buffer): number {
// let read = 0;
// const size = buf.readUInt16LE();
// read+=2;
// const stacks: ItemStack[] = [];
// for(let i=0;i<size;i++){
// const count = buf.readInt16LE(read);
// read+=2;
// const id = buf.readInt8(read);
// read++;
// stacks.push({item: idToItemData(id).item, count, equipped: false});
// }
// this.stacks = stacks;
// return read;
// }
// public toBuffer(): Buffer {
// const buf: Buffer = Buffer.alloc(3*this.stacks.length+3);
// let write = 0;
// buf.writeInt8(CommandInitialize.Op);
// write++;
// buf.writeInt16LE(this.stacks.length, write);
// write+=2;
// this.stacks.forEach(({item,count})=>{
// buf.writeInt16LE(count&0xffff, write);
// write+=2;
// buf.writeInt8(itemToItemData(item).id, write);
// write++;
// });
// return buf;
// }
public execute(inv: Inventory): void {
inv.init(this.stacks);
public execute(state: SimulationState): void {
state.initialize(this.stacks);
}
public getDisplayString(): string {
const parts = ["Initialize"];
@ -71,299 +39,281 @@ export class CommandInitialize implements Command {
}
export class CommandBreakSlots implements Command {
static Op = 0x2;
private numToBreak: number;
constructor(numToBreak: number){
this.numToBreak = numToBreak;
}
// public fromBuffer(buf: Buffer): number {
// this.numToBreak = buf.readInt16LE();
// return 2;
// }
// public toBuffer(): Buffer {
// const buf: Buffer = Buffer.alloc(3);
// buf.writeUInt8(CommandBreakSlots.Op);
// buf.writeInt16LE(this.numToBreak, 1);
// return buf;
// }
public execute(inv: Inventory): void {
inv.addBrokenSlots(this.numToBreak);
}
public getDisplayString(): string {
return `Break ${this.numToBreak} Slots`;
}
}
export class CommandSave implements Command {
static Op = 0x3;
// public fromBuffer(_buf: Buffer): number {
// return 0;
// }
// public toBuffer(): Buffer {
// const buf: Buffer = Buffer.alloc(1);
// buf.writeInt8(CommandSave.Op);
// return buf;
// }
public execute(inv: Inventory): void {
inv.save();
public execute(state: SimulationState): void {
state.save();
}
public getDisplayString(): string {
return "Save";
}
}
export class CommandReload implements Command {
static Op = 0x4;
// public fromBuffer(_buf: Buffer): number {
// return 0;
// }
// public toBuffer(): Buffer {
// const buf: Buffer = Buffer.alloc(1);
// buf.writeInt8(CommandReload.Op);
// return buf;
// }
public execute(inv: Inventory): void {
inv.reload();
}
public getDisplayString(): string {
return "Reload";
}
}
export class CommandSortKey implements Command {
static Op = 0x5;
// public fromBuffer(_buf: Buffer): number {
// return 0;
// }
// public toBuffer(): Buffer {
// const buf: Buffer = Buffer.alloc(1);
// buf.writeInt8(CommandSortKey.Op);
// return buf;
// }
public execute(inv: Inventory): void {
inv.sortKey();
}
public getDisplayString(): string {
return "Sort Key";
}
}
export class CommandSortMaterial implements Command {
static Op = 0x6;
// public fromBuffer(_buf: Buffer): number {
// return 0;
// }
// public toBuffer(): Buffer {
// const buf: Buffer = Buffer.alloc(1);
// buf.writeInt8(CommandSortMaterial.Op);
// return buf;
// }
public execute(inv: Inventory): void {
inv.sortMaterial();
}
public getDisplayString(): string {
return "Sort Material";
}
}
const Verbs = ["?", "Remove", "Drop", "Sell", "Eat", "Cook", "Get", "Add", "Pickup"];
const VerbToId = {
"Remove" : 1,
"Drop": 2,
"Sell": 3,
"Eat": 4,
"Cook": 5,
"Get": 6,
"Add": 7,
"Pickup": 8
};
export class CommandRemoveMaterial implements Command {
static Op = 0x7;
private verb: number;
private count: number;
private item: Item;
private slot: number;
private noSlot: boolean;
constructor(verb: string, count: number, item: Item, slot: number, noSlot: boolean){
this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;
this.count = count;
this.item = item;
this.slot = slot;
this.noSlot = noSlot;
}
public execute(inv: Inventory): void {
inv.remove(this.item, this.count, this.slot);
}
public getDisplayString(): string {
const slotString = this.noSlot ? "" : ` From Slot ${this.slot+1}`;
return `${Verbs[this.verb]} ${this.count} ${this.item}${slotString}`;
}
}
export class CommandRemoveUnstackableMaterial implements Command {
static Op = 0x8;
private verb: number;
private item: Item;
private slot: number;
private noSlot: boolean;
constructor(verb: string,item: Item, slot: number, noSlot: boolean){
this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;
this.item = item;
this.slot = slot;
this.noSlot = noSlot;
}
public execute(inv: Inventory): void {
inv.remove(this.item, 1, this.slot);
}
public getDisplayString(): string {
const slotString = this.noSlot ? "" : ` From Slot ${this.slot+1}`;
return `${Verbs[this.verb]} ${this.item}${slotString}`;
}
}
export class CommandAddMaterial implements Command {
static Op = 0x9;
private verb: number;
private count: number;
private item: Item;
constructor(verb: string, count: number, item: Item){
this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;
this.count = count;
this.item = item;
}
// public fromBuffer(buf: Buffer): number {
// let read = 0;
// const id = buf.readInt8(read);
// read+=1;
// this.item = idToItemData(id).item;
// this.count = buf.readInt16LE(read);
// read+=2;
// this.verb = buf.readInt8(read);
// read++;
// return read;
// }
// public toBuffer(): Buffer {
// const buf: Buffer = Buffer.alloc(1+1+2+1);
// let write = 0;
// buf.writeInt8(CommandAddMaterial.Op);
// write++;
// buf.writeInt8(itemToItemData(this.item).id, write);
// write++;
// buf.writeInt16LE(this.count, write);
// write+=2;
// buf.writeInt8(this.verb, write);
// return buf;
// }
public execute(inv: Inventory): void {
inv.add(this.item, this.count);
}
public getDisplayString(): string {
return `${Verbs[this.verb]} ${this.count} ${this.item}`;
}
}
export class CommandEquipArrow implements Command {
private item: Item;
private slot: number;
private noSlot: boolean;
constructor(item: Item, slot: number, noSlot: boolean){
this.item = item;
this.slot = slot;
this.noSlot = noSlot;
}
public execute(inv: Inventory): void {
inv.equipEquipmentOrArrow(this.item, this.slot);
}
public getDisplayString(): string {
const slotString = this.noSlot ? "" : ` In Slot ${this.slot+1}`;
return `Equip ${itemToArrowType(this.item)} Arrow${slotString}`;
}
}
export class CommandEquip implements Command {
private item: Item;
private slot: number;
private noSlot: boolean;
constructor(item: Item, slot: number, noSlot: boolean){
this.item = item;
this.slot = slot;
this.noSlot = noSlot;
}
public execute(inv: Inventory): void {
inv.equipEquipmentOrArrow(this.item, this.slot);
}
public getDisplayString(): string {
const slotString = this.noSlot ? "" : ` In Slot ${this.slot+1}`;
return `Equip ${this.item}${slotString}`;
}
}
export class CommandUnequip implements Command {
private item: Item;
private slot: number;
private noSlot: boolean;
constructor(item: Item, slot: number, noSlot: boolean){
this.item = item;
this.slot = slot;
this.noSlot = noSlot;
}
public execute(inv: Inventory): void {
inv.unequipEquipment(this.item, this.slot);
}
public getDisplayString(): string {
const slotString = this.noSlot ? "" : ` In Slot ${this.slot+1}`;
return `Unequip ${this.item}${slotString}`;
}
}
export class CommandSetTag implements Command {
export class CommandSaveAs implements Command {
private name: string;
constructor(name: string){
this.name = name;
}
public execute(inv: Inventory): void {
inv.save();
inv.setTag(this.name);
public execute(state: SimulationState): void {
state.save(this.name);
}
public getDisplayString(): string {
return `Save As ${this.name}`;
}
}
export class CommandApplyTag implements Command {
export class CommandReload implements Command {
private name?: string;
constructor(name?: string){
this.name = name;
}
public execute(state: SimulationState): void {
state.reload(this.name);
}
public getDisplayString(): string {
return `Reload${this.name?` ${this.name}`:""}`;
}
}
export class CommandUse implements Command {
private name: string;
constructor(name: string){
this.name = name;
}
public execute(inv: Inventory): void {
inv.applyTag(this.name);
public execute(state: SimulationState): void {
state.useSaveForNextReload(this.name);
}
public getDisplayString(): string {
return `Use ${this.name}`;
}
}
export class CommandCloseGame implements Command {
public execute(inv: Inventory): void {
inv.closeGame();
export class CommandBreakSlots implements Command {
private numToBreak: number;
constructor(numToBreak: number){
this.numToBreak = numToBreak;
}
public execute(state: SimulationState): void {
state.breakSlots(this.numToBreak);
}
public getDisplayString(): string {
return "Close Game";
return `Break ${this.numToBreak} Slots`;
}
}
// export class CommandSortKey implements Command {
// static Op = 0x5;
// // public fromBuffer(_buf: Buffer): number {
// // return 0;
// // }
// // public toBuffer(): Buffer {
// // const buf: Buffer = Buffer.alloc(1);
// // buf.writeInt8(CommandSortKey.Op);
// // return buf;
// // }
// public execute(inv: Inventory): void {
// inv.sortKey();
// }
// public getDisplayString(): string {
// return "Sort Key";
// }
// }
// export class CommandSortMaterial implements Command {
// static Op = 0x6;
// // public fromBuffer(_buf: Buffer): number {
// // return 0;
// // }
// // public toBuffer(): Buffer {
// // const buf: Buffer = Buffer.alloc(1);
// // buf.writeInt8(CommandSortMaterial.Op);
// // return buf;
// // }
// public execute(inv: Inventory): void {
// inv.sortMaterial();
// }
// public getDisplayString(): string {
// return "Sort Material";
// }
// }
// const Verbs = ["?", "Remove", "Drop", "Sell", "Eat", "Cook", "Get", "Add", "Pickup"];
// const VerbToId = {
// "Remove" : 1,
// "Drop": 2,
// "Sell": 3,
// "Eat": 4,
// "Cook": 5,
// "Get": 6,
// "Add": 7,
// "Pickup": 8
// };
// export class CommandRemoveMaterial implements Command {
// static Op = 0x7;
// private verb: number;
// private count: number;
// private item: Item;
// private slot: number;
// private noSlot: boolean;
// constructor(verb: string, count: number, item: Item, slot: number, noSlot: boolean){
// this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;
// this.count = count;
// this.item = item;
// this.slot = slot;
// this.noSlot = noSlot;
// }
// public execute(inv: Inventory): void {
// inv.remove(this.item, this.count, this.slot);
// }
// public getDisplayString(): string {
// const slotString = this.noSlot ? "" : ` From Slot ${this.slot+1}`;
// return `${Verbs[this.verb]} ${this.count} ${this.item}${slotString}`;
// }
// }
// export class CommandRemoveUnstackableMaterial implements Command {
// static Op = 0x8;
// private verb: number;
// private item: Item;
// private slot: number;
// private noSlot: boolean;
// constructor(verb: string,item: Item, slot: number, noSlot: boolean){
// this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;
// this.item = item;
// this.slot = slot;
// this.noSlot = noSlot;
// }
// public execute(inv: Inventory): void {
// inv.remove(this.item, 1, this.slot);
// }
// public getDisplayString(): string {
// const slotString = this.noSlot ? "" : ` From Slot ${this.slot+1}`;
// return `${Verbs[this.verb]} ${this.item}${slotString}`;
// }
// }
// export class CommandAddMaterial implements Command {
// static Op = 0x9;
// private verb: number;
// private count: number;
// private item: Item;
// constructor(verb: string, count: number, item: Item){
// this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;
// this.count = count;
// this.item = item;
// }
// // public fromBuffer(buf: Buffer): number {
// // let read = 0;
// // const id = buf.readInt8(read);
// // read+=1;
// // this.item = idToItemData(id).item;
// // this.count = buf.readInt16LE(read);
// // read+=2;
// // this.verb = buf.readInt8(read);
// // read++;
// // return read;
// // }
// // public toBuffer(): Buffer {
// // const buf: Buffer = Buffer.alloc(1+1+2+1);
// // let write = 0;
// // buf.writeInt8(CommandAddMaterial.Op);
// // write++;
// // buf.writeInt8(itemToItemData(this.item).id, write);
// // write++;
// // buf.writeInt16LE(this.count, write);
// // write+=2;
// // buf.writeInt8(this.verb, write);
// // return buf;
// // }
// public execute(inv: Inventory): void {
// inv.add(this.item, this.count);
// }
// public getDisplayString(): string {
// return `${Verbs[this.verb]} ${this.count} ${this.item}`;
// }
// }
// export class CommandEquipArrow implements Command {
// private item: Item;
// private slot: number;
// private noSlot: boolean;
// constructor(item: Item, slot: number, noSlot: boolean){
// this.item = item;
// this.slot = slot;
// this.noSlot = noSlot;
// }
// public execute(inv: Inventory): void {
// inv.equipEquipmentOrArrow(this.item, this.slot);
// }
// public getDisplayString(): string {
// const slotString = this.noSlot ? "" : ` In Slot ${this.slot+1}`;
// return `Equip ${itemToArrowType(this.item)} Arrow${slotString}`;
// }
// }
// export class CommandEquip implements Command {
// private item: Item;
// private slot: number;
// private noSlot: boolean;
// constructor(item: Item, slot: number, noSlot: boolean){
// this.item = item;
// this.slot = slot;
// this.noSlot = noSlot;
// }
// public execute(inv: Inventory): void {
// inv.equipEquipmentOrArrow(this.item, this.slot);
// }
// public getDisplayString(): string {
// const slotString = this.noSlot ? "" : ` In Slot ${this.slot+1}`;
// return `Equip ${this.item}${slotString}`;
// }
// }
// export class CommandUnequip implements Command {
// private item: Item;
// private slot: number;
// private noSlot: boolean;
// constructor(item: Item, slot: number, noSlot: boolean){
// this.item = item;
// this.slot = slot;
// this.noSlot = noSlot;
// }
// public execute(inv: Inventory): void {
// inv.unequipEquipment(this.item, this.slot);
// }
// public getDisplayString(): string {
// const slotString = this.noSlot ? "" : ` In Slot ${this.slot+1}`;
// return `Unequip ${this.item}${slotString}`;
// }
// }
// export class CommandCloseGame implements Command {
// public execute(inv: Inventory): void {
// inv.closeGame();
// }
// public getDisplayString(): string {
// return "Close Game";
// }
// }
export class CommandComment implements Command {
private name: string;
constructor(name: string){
this.name = name;
}
public execute(_inv: Inventory): void {
public execute(_state: SimulationState): void {
// nothing
}
public getDisplayString(): string {

View file

@ -0,0 +1,24 @@
import { Item, ItemStack, itemToItemData, ItemType } from "./Item"
export type DisplayableSlot = {
image: string,
count: number,
displayCount: boolean,
isEquipped: boolean,
isBrokenSlot: boolean,
}
export interface DisplayableInventory {
getDisplayedSlots: ()=>DisplayableSlot[]
}
export const itemStackToDisplayableSlot = ({item, count, equipped}: ItemStack, isBrokenSlot: boolean): DisplayableSlot => {
const data = itemToItemData(item);
return {
image: data.image,
displayCount: data.stackable && (data.type === ItemType.Arrow || count > 0),
count,
isEquipped: equipped,
isBrokenSlot
}
}

30
src/core/GameData.ts Normal file
View file

@ -0,0 +1,30 @@
import { DisplayableInventory, DisplayableSlot, itemStackToDisplayableSlot } from "./DisplayableInventory";
import { Slots } from "./Slots";
import { VisibleInventory } from "./VisibleInventory";
/*
* Implementation of GameData in botw
*/
export class GameData implements DisplayableInventory {
private slots: Slots = new Slots([]);
constructor(slots: Slots){
this.slots = slots;
}
public deepClone(): GameData {
return new GameData(this.slots.deepClone());
}
public syncWith(pouch: VisibleInventory) {
this.slots = pouch.getSlots().deepClone();
}
public addAllToPouchOnReload(pouch: VisibleInventory) {
this.slots.getSlotsRef().forEach(stack=>pouch.addWhenReload(stack.item, stack.count, stack.equipped));
}
public getDisplayedSlots(): DisplayableSlot[] {
return this.slots.getSlotsRef().map(stack=>itemStackToDisplayableSlot(stack, false));
}
}

View file

@ -2,214 +2,214 @@ import { Item, ItemStack, itemToItemData, ItemType, ItemTypes } from "./Item";
import { Slots } from "./Slots";
export class Inventory {
private slots: Slots = new Slots([]);
private savedSlots: Slots = new Slots([]);
private namedSlots: {[name: string]: Slots} = {};
private numBroken = 0;
private isInitialSort = false;
private isAltered = true;
private inaccurate = false;
private turnedInOrbs = 0;
public deepClone(): Inventory {
const other = new Inventory();
other.slots = this.slots.deepClone();
other.savedSlots = this.savedSlots.deepClone();
other.numBroken = this.numBroken;
other.isInitialSort = this.isInitialSort;
other.isAltered = this.isAltered;
other.inaccurate = this.inaccurate;
other.turnedInOrbs = this.turnedInOrbs;
other.namedSlots = {};
for(const name in this.namedSlots){
other.namedSlots[name] = this.namedSlots[name].deepClone();
}
return other;
}
public getSlots(): Slots {
return this.slots;
}
public getSavedSlots(): Slots {
return this.savedSlots;
}
public getNumBroken(): number {
return this.numBroken;
}
public isInaccurate(): boolean {
return this.inaccurate;
}
public getTurnedInOrbs(): number {
return this.turnedInOrbs;
}
public rawInit(stacks: ItemStack[]) {
this.slots = new Slots(stacks);
}
public init(stacks: ItemStack[]) {
this.slots = new Slots([]);
stacks.forEach(s=>{
this.slots.add(s.item, s.count);
});
this.numBroken = 0;
this.isInitialSort = false;
this.isAltered = true;
this.inaccurate = false;
}
public closeGame() {
this.numBroken = 0;
this.isInitialSort = false;
this.isAltered = true;
this.inaccurate = false;
this.slots = new Slots([]);
}
public addBrokenSlots(num: number) {
this.numBroken+=num;
}
public setTag(name: string){
this.namedSlots[name] = this.savedSlots.deepClone();
}
public applyTag(name: string){
if(name in this.namedSlots){
this.savedSlots = this.namedSlots[name].deepClone();
}else{
this.savedSlots = new Slots([]);
}
}
public save() {
if(this.isAltered){
this.savedSlots = this.slots.deepClone();
}
// Inventory Corruption
// get durability transfer slots
const durabilityTransferSlots: number[] = [];
const equippedWeapon = this.slots.getFirstEquippedSlotIndex(ItemType.Weapon);
if(equippedWeapon>=0){
durabilityTransferSlots.push(equippedWeapon);
}
const equippedBow = this.slots.getFirstEquippedSlotIndex(ItemType.Bow);
if(equippedBow>=0){
durabilityTransferSlots.push(equippedBow);
}
const equippedShield = this.slots.getFirstEquippedSlotIndex(ItemType.Shield);
if(equippedShield>=0){
durabilityTransferSlots.push(equippedShield);
}
durabilityTransferSlots.forEach(s=>{
if(s<this.savedSlots.length){
// We ignore the case where durability transfer happens from equipment to equipment
if(itemToItemData(this.savedSlots.get(s).item).stackable){
this.savedSlots.get(s).count = 999;
}
}
});
}
public reload() {
// get things to dupe
const dupeMap: {[k in ItemType]: Slots} = {
[ItemType.Weapon]: new Slots([]),
[ItemType.Bow]: new Slots([]),
[ItemType.Arrow]: new Slots([]),
[ItemType.Shield]: new Slots([]),
[ItemType.Material]: new Slots([]),
[ItemType.Meal]: new Slots([]),
[ItemType.Key]: new Slots([])
};
for(let i=Math.max(0, this.slots.length-this.numBroken);i<this.slots.length;i++){
const stack = this.slots.get(i);
const itemData = itemToItemData(stack.item);
dupeMap[itemData.type].addStackCopy(stack);
}
// apply dupe
//console.log(dupeMap);
this.slots = new Slots([]);
// const dupeType = (type: ItemType) => {
// private slots: Slots = new Slots([]);
// private savedSlots: Slots = new Slots([]);
// private namedSlots: {[name: string]: Slots} = {};
// private numBroken = 0;
// private isInitialSort = false;
// private isAltered = true;
// private inaccurate = false;
// private turnedInOrbs = 0;
// public deepClone(): Inventory {
// const other = new Inventory();
// other.slots = this.slots.deepClone();
// other.savedSlots = this.savedSlots.deepClone();
// other.numBroken = this.numBroken;
// other.isInitialSort = this.isInitialSort;
// other.isAltered = this.isAltered;
// other.inaccurate = this.inaccurate;
// other.turnedInOrbs = this.turnedInOrbs;
// other.namedSlots = {};
// for(const name in this.namedSlots){
// other.namedSlots[name] = this.namedSlots[name].deepClone();
// }
// return other;
// }
ItemTypes.forEach(type=>{
this.slots.addSlotsToEnd(dupeMap[type]);
this.slots.addSlotsToEnd(this.savedSlots.getByType(type).deepClone());
});
this.slots.sortArrows();
this.isInitialSort = true;
this.isAltered = false;
}
// public getSlots(): Slots {
// return this.slots;
// }
public sortKey() {
const nonKeyItems = this.slots.getBeforeType(ItemType.Key);
const keyItems = this.slots.getByType(ItemType.Key);
keyItems.sort();
nonKeyItems.addSlotsToEnd(keyItems);
this.slots = nonKeyItems;
this.isAltered=true;
this.isInitialSort=false;
}
// public getSavedSlots(): Slots {
// return this.savedSlots;
// }
public sortMaterial() {
const beforeMaterial = this.slots.getBeforeType(ItemType.Material);
const afterMaterial = this.slots.getAfterType(ItemType.Material);
const materials = this.slots.getByType(ItemType.Material);
if(this.isInitialSort){
// the materials in broken slots are not sorted
const brokenSlots = Math.max(0, this.numBroken - afterMaterial.length);
const noSortPart = materials.removeFromEnd(brokenSlots);
materials.sort();
beforeMaterial.addSlotsToEnd(materials);
beforeMaterial.addSlotsToEnd(noSortPart);
beforeMaterial.addSlotsToEnd(afterMaterial);
}else{
materials.sort();
beforeMaterial.addSlotsToEnd(materials);
beforeMaterial.addSlotsToEnd(afterMaterial);
}
// public getNumBroken(): number {
// return this.numBroken;
// }
this.slots = beforeMaterial;
this.isInitialSort = false;
this.isAltered=true;
}
// public isInaccurate(): boolean {
// return this.inaccurate;
// }
public remove(item: Item, count: number, slot: number) {
this.slots.remove(item, count, slot);
if(item===Item.SpiritOrb){
this.turnedInOrbs+=count;
}
this.isAltered=true;
}
// public getTurnedInOrbs(): number {
// return this.turnedInOrbs;
// }
public add(item: Item, count: number) {
this.slots.add(item, count);
if(itemToItemData(item).type===ItemType.Arrow){
this.slots.sortArrows();
}
this.isAltered=true;
}
// public rawInit(stacks: ItemStack[]) {
// this.slots = new Slots(stacks);
// }
public equipEquipmentOrArrow(item: Item, slot: number) {
this.slots.equip(item, slot);
this.isAltered=true;
}
// public init(stacks: ItemStack[]) {
// this.slots = new Slots([]);
// stacks.forEach(s=>{
// this.slots.add(s.item, s.count);
// });
// this.numBroken = 0;
// this.isInitialSort = false;
// this.isAltered = true;
// this.inaccurate = false;
// }
public unequipEquipment(item: Item, slot: number){
this.slots.unequip(item, slot);
this.isAltered=true;
}
// public closeGame() {
// this.numBroken = 0;
// this.isInitialSort = false;
// this.isAltered = true;
// this.inaccurate = false;
// this.slots = new Slots([]);
// }
public shootArrow(item: Item, count: number){
this.slots.shoot(item, count);
this.isAltered=true;
}
// public addBrokenSlots(num: number) {
// this.numBroken+=num;
// }
// public setTag(name: string){
// this.namedSlots[name] = this.savedSlots.deepClone();
// }
// public applyTag(name: string){
// if(name in this.namedSlots){
// this.savedSlots = this.namedSlots[name].deepClone();
// }else{
// this.savedSlots = new Slots([]);
// }
// }
// public save() {
// if(this.isAltered){
// this.savedSlots = this.slots.deepClone();
// }
// // Inventory Corruption
// // get durability transfer slots
// const durabilityTransferSlots: number[] = [];
// const equippedWeapon = this.slots.getFirstEquippedSlotIndex(ItemType.Weapon);
// if(equippedWeapon>=0){
// durabilityTransferSlots.push(equippedWeapon);
// }
// const equippedBow = this.slots.getFirstEquippedSlotIndex(ItemType.Bow);
// if(equippedBow>=0){
// durabilityTransferSlots.push(equippedBow);
// }
// const equippedShield = this.slots.getFirstEquippedSlotIndex(ItemType.Shield);
// if(equippedShield>=0){
// durabilityTransferSlots.push(equippedShield);
// }
// durabilityTransferSlots.forEach(s=>{
// if(s<this.savedSlots.length){
// // We ignore the case where durability transfer happens from equipment to equipment
// if(itemToItemData(this.savedSlots.get(s).item).stackable){
// this.savedSlots.get(s).count = 999;
// }
// }
// });
// }
// public reload() {
// // get things to dupe
// const dupeMap: {[k in ItemType]: Slots} = {
// [ItemType.Weapon]: new Slots([]),
// [ItemType.Bow]: new Slots([]),
// [ItemType.Arrow]: new Slots([]),
// [ItemType.Shield]: new Slots([]),
// [ItemType.Material]: new Slots([]),
// [ItemType.Meal]: new Slots([]),
// [ItemType.Key]: new Slots([])
// };
// for(let i=Math.max(0, this.slots.length-this.numBroken);i<this.slots.length;i++){
// const stack = this.slots.get(i);
// const itemData = itemToItemData(stack.item);
// dupeMap[itemData.type].addStackCopy(stack);
// }
// // apply dupe
// //console.log(dupeMap);
// this.slots = new Slots([]);
// // const dupeType = (type: ItemType) => {
// // }
// ItemTypes.forEach(type=>{
// this.slots.addSlotsToEnd(dupeMap[type]);
// this.slots.addSlotsToEnd(this.savedSlots.getByType(type).deepClone());
// });
// this.slots.sortArrows();
// this.isInitialSort = true;
// this.isAltered = false;
// }
// public sortKey() {
// const nonKeyItems = this.slots.getBeforeType(ItemType.Key);
// const keyItems = this.slots.getByType(ItemType.Key);
// keyItems.sort();
// nonKeyItems.addSlotsToEnd(keyItems);
// this.slots = nonKeyItems;
// this.isAltered=true;
// this.isInitialSort=false;
// }
// public sortMaterial() {
// const beforeMaterial = this.slots.getBeforeType(ItemType.Material);
// const afterMaterial = this.slots.getAfterType(ItemType.Material);
// const materials = this.slots.getByType(ItemType.Material);
// if(this.isInitialSort){
// // the materials in broken slots are not sorted
// const brokenSlots = Math.max(0, this.numBroken - afterMaterial.length);
// const noSortPart = materials.removeFromEnd(brokenSlots);
// materials.sort();
// beforeMaterial.addSlotsToEnd(materials);
// beforeMaterial.addSlotsToEnd(noSortPart);
// beforeMaterial.addSlotsToEnd(afterMaterial);
// }else{
// materials.sort();
// beforeMaterial.addSlotsToEnd(materials);
// beforeMaterial.addSlotsToEnd(afterMaterial);
// }
// this.slots = beforeMaterial;
// this.isInitialSort = false;
// this.isAltered=true;
// }
// public remove(item: Item, count: number, slot: number) {
// this.slots.remove(item, count, slot);
// if(item===Item.SpiritOrb){
// this.turnedInOrbs+=count;
// }
// this.isAltered=true;
// }
// public add(item: Item, count: number) {
// this.slots.add(item, count);
// if(itemToItemData(item).type===ItemType.Arrow){
// this.slots.sortArrows();
// }
// this.isAltered=true;
// }
// public equipEquipmentOrArrow(item: Item, slot: number) {
// this.slots.equip(item, slot);
// this.isAltered=true;
// }
// public unequipEquipment(item: Item, slot: number){
// this.slots.unequip(item, slot);
// this.isAltered=true;
// }
// public shootArrow(item: Item, count: number){
// this.slots.shoot(item, count);
// this.isAltered=true;
// }
}

View file

@ -5,9 +5,10 @@ export enum ItemType {
Bow = 1,
Arrow = 2,
Shield = 3,
Material = 4,
Meal = 5,
Key = 6
Armor = 4,
Material = 5,
Meal = 6,
Key = 7
}
export const ItemTypes = [
@ -73,6 +74,8 @@ export enum Item {
HeartyRadish = "HeartyRadish",
BigHeartyRadish = "BigHeartyRadish",
Fairy = "Fairy",
MasterSword = "MasterSword",
}
type ItemData = {
@ -90,6 +93,7 @@ const TypeToCount = {
[ItemType.Bow]: 0,
[ItemType.Arrow]: 0,
[ItemType.Shield]: 0,
[ItemType.Armor]: 0,
[ItemType.Material]: 0,
[ItemType.Key]: 0,
[ItemType.Meal]: 0,
@ -161,6 +165,9 @@ register(0x50, Item.Weapon, ItemType.Weapon, {
image: Images.Axe,
stackable: false
});
register(0, Item.MasterSword, ItemType.Weapon, {
stackable: false,
})
register(0x60, Item.Bow, ItemType.Bow, {
image: Images.ForestDwellerBow,

View file

@ -1,7 +1,18 @@
import { Command, CommandAddMaterial, CommandApplyTag, CommandBreakSlots, CommandCloseGame, CommandComment, CommandEquip, CommandEquipArrow, CommandInitialize, CommandNothing, CommandReload, CommandRemoveMaterial, CommandRemoveUnstackableMaterial, CommandSave, CommandSetTag, CommandSortKey, CommandSortMaterial, CommandUnequip } from "./Command";
import {
Command,
CommandBreakSlots,
CommandComment,
CommandInitialize,
CommandNothing,
CommandReload,
CommandSave,
CommandSaveAs,
CommandUse
} from "./Command";
import { Item, ItemStack } from "./Item";
export const parseCommand = (cmdString: string): Command | undefined => {
if(cmdString.startsWith("# ")){
return new CommandComment(cmdString.substring(2));
}
@ -28,22 +39,24 @@ export const parseCommand = (cmdString: string): Command | undefined => {
}
return new CommandInitialize(stacks);
}
// no var
// Save/Reload
if(tokens.length===1 && tokens[0] === "Save"){
return new CommandSave();
}
// // Multi Save
if (tokens.length === 3 && tokens[0] === "Save" && tokens[1] === "As"){
const name = tokens[2];
return new CommandSaveAs(name);
}
if (tokens.length === 2 && tokens[0] === "Use"){
const name = tokens[1];
return new CommandUse(name);
}
if(tokens.length===1 && tokens[0] === "Reload"){
return new CommandReload();
}
if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Key"){
return new CommandSortKey();
}
if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Material"){
return new CommandSortMaterial();
}
if(tokens.length===2 && tokens[0] === "Close" && tokens[1] === "Game"){
return new CommandCloseGame();
if(tokens.length===2 && tokens[0] === "Reload"){
return new CommandReload(tokens[1]);
}
// break
if (tokens.length > 2 && tokens[0] === "Break" && tokens[2]=== "Slots" ){
@ -51,107 +64,110 @@ export const parseCommand = (cmdString: string): Command | undefined => {
if(Number.isInteger(slots)){
return new CommandBreakSlots(slots);
}
return undefined;
}
// remove material
if (tokens.length === 6 && (tokens[0] === "Remove" || tokens[0] === "Sell" || tokens[0] === "Drop"|| tokens[0] === "Eat") && tokens[3] === "From" && tokens[4] ==="Slot" ){
const count = parseInt(tokens[1]);
const item = tokens[2];
const slot = parseInt(tokens[5]);
if(Number.isInteger(count) && Number.isInteger(slot) && item in Item){
return new CommandRemoveMaterial(tokens[0], count, Item[item as keyof typeof Item], slot-1, false);
}
return undefined;
}
if (tokens.length === 3 && (tokens[0] === "Remove" || tokens[0] === "Sell" || tokens[0] === "Drop"|| tokens[0] === "Eat")){
const count = parseInt(tokens[1]);
const item = tokens[2];
if(Number.isInteger(count) && item in Item){
return new CommandRemoveMaterial(tokens[0], count, Item[item as keyof typeof Item], 0, true);
}
return undefined;
}
// remove 1 material
if (tokens.length === 5 && (tokens[0] === "Remove" || tokens[0] === "Sell" || tokens[0] === "Eat") && tokens[2] === "From" && tokens[3] ==="Slot" ){
const item = tokens[1];
const slot = parseInt(tokens[4]);
if(Number.isInteger(slot) && item in Item){
return new CommandRemoveUnstackableMaterial(tokens[0], Item[item as keyof typeof Item], slot-1, false);
}
return undefined;
}
if (tokens.length === 2 && (tokens[0] === "Remove" || tokens[0] === "Sell" || tokens[0] === "Eat")){
const item = tokens[1];
if(item in Item){
return new CommandRemoveUnstackableMaterial(tokens[0], Item[item as keyof typeof Item], 0, true);
}
return undefined;
}
// add material
if (tokens.length === 3 && (tokens[0] === "Get" || tokens[0] === "Cook" || tokens[0] === "Add" || tokens[0] === "Pickup")){
const count = parseInt(tokens[1]);
const item = tokens[2];
if(Number.isInteger(count) && item in Item){
return new CommandAddMaterial(tokens[0], count, Item[item as keyof typeof Item]);
}
return undefined;
}
// Equip Equipment
if (tokens.length === 5 && tokens[0] === "Equip" && tokens[2] === "In" && tokens[3] ==="Slot" ){
const item = tokens[1];
const slot = parseInt(tokens[4]);
if( Number.isInteger(slot) && item in Item){
return new CommandEquip(Item[item as keyof typeof Item], slot-1, false);
}
return undefined;
}
if (tokens.length === 2 && tokens[0] === "Equip"){
const item = tokens[1];
if( item in Item){
return new CommandEquip(Item[item as keyof typeof Item], 0, true);
}
return undefined;
}
// Unequip Equipment
if (tokens.length === 5 && tokens[0] === "Unequip" && tokens[2] === "In" && tokens[3] ==="Slot" ){
const item = tokens[1];
const slot = parseInt(tokens[4]);
if( Number.isInteger(slot) && item in Item){
return new CommandUnequip(Item[item as keyof typeof Item], slot-1, false);
}
return undefined;
}
if (tokens.length === 2 && tokens[0] === "Unequip"){
const item = tokens[1];
if( item in Item){
return new CommandUnequip(Item[item as keyof typeof Item], -1, true);
}
return undefined;
}
// Equip Arrow
if (tokens.length === 6 && tokens[0] === "Equip" && tokens[2] === "Arrow" && tokens[3] === "In" && tokens[4] ==="Slot" ){
const item = tokens[1]+"Arrow";
const slot = parseInt(tokens[5]);
if( Number.isInteger(slot) && item in Item){
return new CommandEquipArrow(Item[item as keyof typeof Item], slot-1, false);
}
return undefined;
}
if (tokens.length === 3 && tokens[0] === "Equip" && tokens[2] === "Arrow" ){
const item = tokens[1]+"Arrow";
if(item in Item){
return new CommandEquipArrow(Item[item as keyof typeof Item], 0, true);
}
return undefined;
}
// Multi Save
if (tokens.length === 3 && tokens[0] === "Save" && tokens[1] === "As"){
const name = tokens[2];
return new CommandSetTag(name);
}
if (tokens.length === 2 && tokens[0] === "Use"){
const name = tokens[1];
return new CommandApplyTag(name);
}
// if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Key"){
// return new CommandSortKey();
// }
// if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Material"){
// return new CommandSortMaterial();
// }
// if(tokens.length===2 && tokens[0] === "Close" && tokens[1] === "Game"){
// return new CommandCloseGame();
// }
// return undefined;
// }
// // remove material
// if (tokens.length === 6 && (tokens[0] === "Remove" || tokens[0] === "Sell" || tokens[0] === "Drop"|| tokens[0] === "Eat") && tokens[3] === "From" && tokens[4] ==="Slot" ){
// const count = parseInt(tokens[1]);
// const item = tokens[2];
// const slot = parseInt(tokens[5]);
// if(Number.isInteger(count) && Number.isInteger(slot) && item in Item){
// return new CommandRemoveMaterial(tokens[0], count, Item[item as keyof typeof Item], slot-1, false);
// }
// return undefined;
// }
// if (tokens.length === 3 && (tokens[0] === "Remove" || tokens[0] === "Sell" || tokens[0] === "Drop"|| tokens[0] === "Eat")){
// const count = parseInt(tokens[1]);
// const item = tokens[2];
// if(Number.isInteger(count) && item in Item){
// return new CommandRemoveMaterial(tokens[0], count, Item[item as keyof typeof Item], 0, true);
// }
// return undefined;
// }
// // remove 1 material
// if (tokens.length === 5 && (tokens[0] === "Remove" || tokens[0] === "Sell" || tokens[0] === "Eat") && tokens[2] === "From" && tokens[3] ==="Slot" ){
// const item = tokens[1];
// const slot = parseInt(tokens[4]);
// if(Number.isInteger(slot) && item in Item){
// return new CommandRemoveUnstackableMaterial(tokens[0], Item[item as keyof typeof Item], slot-1, false);
// }
// return undefined;
// }
// if (tokens.length === 2 && (tokens[0] === "Remove" || tokens[0] === "Sell" || tokens[0] === "Eat")){
// const item = tokens[1];
// if(item in Item){
// return new CommandRemoveUnstackableMaterial(tokens[0], Item[item as keyof typeof Item], 0, true);
// }
// return undefined;
// }
// // add material
// if (tokens.length === 3 && (tokens[0] === "Get" || tokens[0] === "Cook" || tokens[0] === "Add" || tokens[0] === "Pickup")){
// const count = parseInt(tokens[1]);
// const item = tokens[2];
// if(Number.isInteger(count) && item in Item){
// return new CommandAddMaterial(tokens[0], count, Item[item as keyof typeof Item]);
// }
// return undefined;
// }
// // Equip Equipment
// if (tokens.length === 5 && tokens[0] === "Equip" && tokens[2] === "In" && tokens[3] ==="Slot" ){
// const item = tokens[1];
// const slot = parseInt(tokens[4]);
// if( Number.isInteger(slot) && item in Item){
// return new CommandEquip(Item[item as keyof typeof Item], slot-1, false);
// }
// return undefined;
// }
// if (tokens.length === 2 && tokens[0] === "Equip"){
// const item = tokens[1];
// if( item in Item){
// return new CommandEquip(Item[item as keyof typeof Item], 0, true);
// }
// return undefined;
// }
// // Unequip Equipment
// if (tokens.length === 5 && tokens[0] === "Unequip" && tokens[2] === "In" && tokens[3] ==="Slot" ){
// const item = tokens[1];
// const slot = parseInt(tokens[4]);
// if( Number.isInteger(slot) && item in Item){
// return new CommandUnequip(Item[item as keyof typeof Item], slot-1, false);
// }
// return undefined;
// }
// if (tokens.length === 2 && tokens[0] === "Unequip"){
// const item = tokens[1];
// if( item in Item){
// return new CommandUnequip(Item[item as keyof typeof Item], -1, true);
// }
// return undefined;
// }
// // Equip Arrow
// if (tokens.length === 6 && tokens[0] === "Equip" && tokens[2] === "Arrow" && tokens[3] === "In" && tokens[4] ==="Slot" ){
// const item = tokens[1]+"Arrow";
// const slot = parseInt(tokens[5]);
// if( Number.isInteger(slot) && item in Item){
// return new CommandEquipArrow(Item[item as keyof typeof Item], slot-1, false);
// }
// return undefined;
// }
// if (tokens.length === 3 && tokens[0] === "Equip" && tokens[2] === "Arrow" ){
// const item = tokens[1]+"Arrow";
// if(item in Item){
// return new CommandEquipArrow(Item[item as keyof typeof Item], 0, true);
// }
// return undefined;
// }
return undefined;
};

136
src/core/SimulationState.ts Normal file
View file

@ -0,0 +1,136 @@
import { DisplayableInventory } from "./DisplayableInventory";
import { GameData } from "./GameData";
import { ItemStack } from "./Item";
import { Slots } from "./Slots";
import { VisibleInventory } from "./VisibleInventory";
export const createSimulationState = (): SimulationState => {
return new SimulationState(
new GameData(new Slots([])),
null,
{},
new VisibleInventory(new Slots([]), 0)
);
}
/*
* The state of simulation, including game data, visible inventory, and all save slots
*/
export class SimulationState {
private gameData: GameData;
private manualSave: GameData | null;
private namedSaves: {[name: string]: GameData} = {};
private pouch: VisibleInventory;
private nextReloadName?: string;
constructor(gameData: GameData, manualSave: GameData | null, namedSaves: {[name: string]: GameData}, pouch: VisibleInventory){
this.gameData = gameData;
this.manualSave = manualSave;
this.namedSaves = namedSaves;
this.pouch = pouch;
}
public deepClone(): SimulationState {
const copyNamedSaves: {[name: string]: GameData} = {};
for(const name in this.namedSaves){
copyNamedSaves[name] = this.namedSaves[name].deepClone();
}
return new SimulationState(
this.gameData.deepClone(),
this.manualSave ? this.manualSave.deepClone() : null,
copyNamedSaves,
this.pouch.deepClone()
);
}
public initialize(stacks: ItemStack[]) {
this.pouch = new VisibleInventory(new Slots([]), 0);
stacks.forEach((stack)=>this.pouch.addDirectly(stack));
this.gameData.syncWith(this.pouch);
}
public save(name?: string) {
if(name){
this.namedSaves[name] = this.gameData.deepClone();
}else{
this.manualSave = this.gameData.deepClone();
}
}
public reload(name?: string) {
if(name){
if(name in this.namedSaves){
this.reloadFrom(this.namedSaves[name]);
}
}else{
if(this.nextReloadName){
if(this.nextReloadName in this.namedSaves){
this.reloadFrom(this.namedSaves[this.nextReloadName]);
}
}else{
const save = this.manualSave;
if(save){
this.reloadFrom(save);
}
}
}
}
private reloadFrom(data: GameData) {
this.gameData = data.deepClone();
this.pouch.clearForReload();
this.gameData.addAllToPouchOnReload(this.pouch);
}
public useSaveForNextReload(name: string){
this.nextReloadName = name;
}
public breakSlots(n: number) {
this.pouch.modifyCount(-n);
}
public get displayableGameData(): DisplayableInventory {
return this.gameData;
}
public get displayablePouch(): DisplayableInventory {
return this.pouch;
}
public get inventoryMCount(): number {
return this.pouch.getCount();
}
public getManualSave(): GameData | null {
return this.manualSave;
}
public getNamedSaves(): {[name: string]: GameData} {
return this.namedSaves;
}
// public get displayableGameData(): DisplayableInventory {
// return this.gameData;
// }
}
// Save - save to hard save slot
// Save As <name> - save to a auto save slot
// Reload - reload hard save
// Reload <name> - reload a named auto save
// Use <name> - no effect, but next Reload reloads the named auto save
// Break X Slots
// Sort Key (In Tab X)
// Sort Material (In Tab X)
// Get/Add/Cook/Pickup X <item>, X can be omitted and default to 1
// Get/Add/Cook/Pickup X <item> Y <item2> ...
// Remove/Drop/Sell/Eat X <item> From Slot Y, X can be omitted and default to 1
// Remove/Drop/Sell/Eat X <item1> Y <item2> ...
// D&P X <item>, drop and pick up (to sort)
// Equip <item> (In Slot X)
// Unequip <item> (In Slot X), without slot, it unequipps the first equipped
// Shoot X Arrow, x can be ommited and default to 1
// Close Game
// Close Inventory, same as Resync GameData

View file

@ -1,5 +1,11 @@
import { count } from "console";
import { stableSort } from "data/mergeSort";
import { Item, ItemStack, itemToItemData, ItemType } from "./Item";
/*
* This is the data model common to GameData and VisibleInventory
*/
export class Slots {
private internalSlots: ItemStack[] = [];
constructor(slots: ItemStack[]) {
@ -14,184 +20,301 @@ export class Slots {
public get length(): number {
return this.internalSlots.length;
}
public get(i: number): ItemStack{
return this.internalSlots[i];
// Sort the item types as they appear in game. Arrows are also sorted amongst each other
// input mCount = null will skip the optimization. Otherwise if mCount <= 1, do nothing
public sortItemType(mCount: number | null) {
if(mCount === null){
mCount = this.internalSlots.length;
}
public getByType(type: ItemType): Slots {
return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type===type));
}
public getBeforeType(type: ItemType): Slots {
return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type<type));
}
public getAfterType(type: ItemType): Slots {
return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type>type));
}
public addSlotsToEnd(slots: Slots) {
slots.internalSlots.forEach(s=>this.addStack(s));
}
public addStack(stack: ItemStack) {
// Scan non-repeatables
const data = itemToItemData(stack.item);
if(!data.repeatable){
for(let i=0;i<this.internalSlots.length;i++){
if(this.internalSlots[i].item===stack.item){
if(mCount <= 1){
return;
}
stableSort(this.internalSlots, (a,b)=>{
const aData = itemToItemData(a.item);
const bData = itemToItemData(b.item);
if(aData.type === ItemType.Arrow && bData.type === ItemType.Arrow){
return aData.sortOrder - bData.sortOrder;
}
}
this.internalSlots.push(stack);
}
public addStackCopy(stack: ItemStack) {
this.addStack({...stack});
}
public sort() {
this.internalSlots.sort((a,b)=>{
return itemToItemData(a.item).sortOrder - itemToItemData(b.item).sortOrder;
return aData.type - bData.type;
});
}
public removeFromEnd(count: number): Slots {
const end = this.internalSlots.splice(-count, count);
return new Slots(end);
}
public remove(item: Item, count: number, slot: number) {
let s = 0;
for(let i = 0; i<this.internalSlots.length && count > 0;i++){
if(this.internalSlots[i].item === item){
if(s<slot){
s++;
}else{
if(this.internalSlots[i].count<count){
count-=this.internalSlots[i].count;
this.internalSlots[i].count=0;
}else{
this.internalSlots[i].count-=count;
break;
public clearFirst(count: number) {
this.internalSlots.splice(0, count);
}
// public get(i: number): ItemStack{
// return this.internalSlots[i];
// }
// public getByType(type: ItemType): Slots {
// return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type===type));
// }
// public getBeforeType(type: ItemType): Slots {
// return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type<type));
// }
// public getAfterType(type: ItemType): Slots {
// return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type>type));
// }
// public addSlotsToEnd(slots: Slots) {
// slots.internalSlots.forEach(s=>this.addStack(s));
// }
public addStackDirectly(stack: ItemStack): number {
const data = itemToItemData(stack.item);
if(data.stackable){
this.internalSlots.push(stack);
return 1;
}
for(let i=0;i<stack.count;i++){
this.internalSlots.push({...stack, count: 1});
}
return stack.count;
}
public addSlot(stack: ItemStack, mCount: number | null) {
this.internalSlots.push(stack);
this.sortItemType(mCount);
}
// public addStackCopy(stack: ItemStack) {
// this.addStack({...stack});
// }
// public sort() {
// this.internalSlots.sort((a,b)=>{
// return itemToItemData(a.item).sortOrder - itemToItemData(b.item).sortOrder;
// });
// }
// public removeFromEnd(count: number): Slots {
// const end = this.internalSlots.splice(-count, count);
// return new Slots(end);
// }
// public remove(item: Item, count: number, slot: number) {
// let s = 0;
// for(let i = 0; i<this.internalSlots.length && count > 0;i++){
// if(this.internalSlots[i].item === item){
// if(s<slot){
// s++;
// }else{
// if(this.internalSlots[i].count<count){
// count-=this.internalSlots[i].count;
// this.internalSlots[i].count=0;
}
}
}
this.internalSlots = this.internalSlots.filter(({count})=>count>0);
}
// }else{
// this.internalSlots[i].count-=count;
// break;
// }
public add(item: Item, count: number) {
let added = false;
// }
// }
// }
// this.internalSlots = this.internalSlots.filter(({count})=>count>0);
// }
// Add something to inventory in game
// returns number of slots added
public add(item: Item, count: number, equippedDuringReload: boolean, reloading: boolean, mCount: number | null): number {
if(mCount === null){
mCount = this.internalSlots.length;
}
//let added = false;
const data = itemToItemData(item);
// If item is stackable (arrow, material, spirit orbs)
// Check if there's already a slot, if so, add it to that and cap it at 999
if(data.stackable){
for(let i = 0; i<this.internalSlots.length;i++){
if(this.internalSlots[i].item === item){
this.internalSlots[i].count+=count;
added = true;
break;
if(reloading){
if(this.internalSlots[i].count + count > 999){
// do not add new stack during loading save, if it would exceed 999
return 0;
}
// Otherwise add the stack directly
this.addSlot({item, count, equipped: equippedDuringReload}, mCount+1);
return 1;
}
this.internalSlots[i].count = Math.min(999, this.internalSlots[i].count+count);
return 0;
}
}
}
if(!added){
const after = this.removeFromEnd(this.getAfterType(data.type).length);
// Need to add new slot
// Key item check: if the key item or master sword already exists in the first tab, do not add
if(mCount != 0){
if(data.type === ItemType.Key || item === Item.MasterSword) {
let i=0;
while(i<this.internalSlots.length && itemToItemData(this.internalSlots[i].item).type < data.type){
i++;
}
for(;i<this.internalSlots.length && itemToItemData(this.internalSlots[i].item).type === data.type;i++){
if(this.internalSlots[i].item === item){
// Found the key item/master sword, do not add
return 0;
}
}
}
}
// Checks finish, do add new slot
if(data.stackable){
if(data.type===ItemType.Arrow){
// if currently equipped arrow == 0. new arrows are equiped
// TODO: botw needs more testing on how arrows are handled in various cases
const shouldEquipNew = this.internalSlots.filter(s=>{
const sData = itemToItemData(s.item);
return sData.type === data.type && s.equipped && s.count > 0;
}).length === 0;
this.addStack({item,count,equipped:shouldEquipNew});
this.addSlot({item,count,equipped:shouldEquipNew}, mCount+1);
}else{
this.addStack({item,count,equipped:false});
this.addSlot({item,count,equipped:false}, mCount+1);
}
return 1;
}
}else{
if(data.type===ItemType.Weapon || data.type===ItemType.Bow || data.type===ItemType.Shield){
//Check equip
const shouldEquipNew = this.internalSlots.filter(s=>{
const shouldEquipNew = !reloading && this.internalSlots.filter(s=>{
const sData = itemToItemData(s.item);
return sData.type === data.type && s.equipped;
}).length === 0;
this.addStack({item,count:1,equipped: shouldEquipNew});
this.addSlot({item,count:1,equipped: shouldEquipNew}, mCount+1);
for(let i=1;i<count;i++){
this.addStack({item,count:1,equipped: false});
this.addSlot({item,count:1,equipped: false}, mCount+i+1);
}
}else{
for(let i=0;i<count;i++){
this.addStack({item,count:1,equipped: false});
this.addSlot({item,count:1,equipped: false}, mCount+i+1);
}
}
return count;
}
this.addSlotsToEnd(after);
}
}
// this is for both equipments and arrows
public equip(item: Item, slot: number) {
let s = 0;
const type = itemToItemData(item).type;
const filtered = this.internalSlots.filter(s=>itemToItemData(s.item).type === type);
for(let i = 0; i<filtered.length;i++){
filtered[i].equipped=false;
if(filtered[i].item === item){
if (s===slot){
filtered[i].equipped=true;
}
s++;
}
}
}
public unequip(item:Item, slot: number) {
let s = 0;
const type = itemToItemData(item).type;
if (type===ItemType.Arrow){
return; // cannot unequip arrow
}
for(let i = 0; i<this.internalSlots.length;i++){
if(this.internalSlots[i].item === item){
if(slot < 0){
if(this.internalSlots[i].equipped){
this.internalSlots[i].equipped=false;
break;
}
}else{
if(s<slot){
s++;
}else{
this.internalSlots[i].equipped=false;
break;
}
}
}
}
}
// return how many slots are added
// public add(item: Item, count: number, equipped: boolean, isReloading: boolean, mCount: number): number {
// let added = false;
// const data = itemToItemData(item);
// if(data.stackable){
// for(let i = 0; i<this.internalSlots.length;i++){
// if(this.internalSlots[i].item === item){
// if(isReloading){
// if(mCount > 0 && this.internalSlots[i].count + count > 999){
// return 0; // Skip, do not add new stack at all
// }
// // Otherwise push the entire new stack
// this.internalSlots.push({item, count, equipped});
// }else{
// this.internalSlots[i].count = Math.min(999, this.internalSlots[i].count+count);
// added = true;
// }
// Difference between shoot and remove:
// 1. can only be from first (leftmost) slot
// 2. empty slots not removed
public shoot(item: Item, count: number) {
for(let i = 0; i<this.internalSlots.length;i++){
if(this.internalSlots[i].item === item){
this.internalSlots[i].count-=count;
}
}
}
// break;
// }
// }
// }
// if(!added){
// const after = this.removeFromEnd(this.getAfterType(data.type).length);
// if(data.stackable){
// if(data.type===ItemType.Arrow){
// // if currently equipped arrow == 0. new arrows are equiped
// const shouldEquipNew = this.internalSlots.filter(s=>{
// const sData = itemToItemData(s.item);
// return sData.type === data.type && s.equipped && s.count > 0;
// }).length === 0;
// this.addStack({item,count,equipped:shouldEquipNew});
// }else{
// this.addStack({item,count,equipped:false});
// }
public sortArrows() {
const after = this.removeFromEnd(this.getAfterType(ItemType.Arrow).length);
const arrows = this.removeFromEnd(this.getByType(ItemType.Arrow).length);
arrows.sort();
this.addSlotsToEnd(arrows);
this.addSlotsToEnd(after);
}
// }else{
// if(data.type===ItemType.Weapon || data.type===ItemType.Bow || data.type===ItemType.Shield){
// //Check equip
// const shouldEquipNew = this.internalSlots.filter(s=>{
// const sData = itemToItemData(s.item);
// return sData.type === data.type && s.equipped;
// }).length === 0;
// this.addStack({item,count:1,equipped: shouldEquipNew});
// for(let i=1;i<count;i++){
// this.addStack({item,count:1,equipped: false});
public getFirstEquippedSlotIndex(type: ItemType): number {
for(let i = 0; i<this.internalSlots.length;i++){
if(this.internalSlots[i].equipped){
const data = itemToItemData(this.internalSlots[i].item);
if(data.type === type){
return i;
}
}
}
return -1;
}
// }
// }else{
// for(let i=0;i<count;i++){
// this.addStack({item,count:1,equipped: false});
// }
// }
// }
// this.addSlotsToEnd(after);
// }
// }
// // this is for both equipments and arrows
// public equip(item: Item, slot: number) {
// let s = 0;
// const type = itemToItemData(item).type;
// const filtered = this.internalSlots.filter(s=>itemToItemData(s.item).type === type);
// for(let i = 0; i<filtered.length;i++){
// filtered[i].equipped=false;
// if(filtered[i].item === item){
// if (s===slot){
// filtered[i].equipped=true;
// }
// s++;
// }
// }
// }
// public unequip(item:Item, slot: number) {
// let s = 0;
// const type = itemToItemData(item).type;
// if (type===ItemType.Arrow){
// return; // cannot unequip arrow
// }
// for(let i = 0; i<this.internalSlots.length;i++){
// if(this.internalSlots[i].item === item){
// if(slot < 0){
// if(this.internalSlots[i].equipped){
// this.internalSlots[i].equipped=false;
// break;
// }
// }else{
// if(s<slot){
// s++;
// }else{
// this.internalSlots[i].equipped=false;
// break;
// }
// }
// }
// }
// }
// // Difference between shoot and remove:
// // 1. can only be from first (leftmost) slot
// // 2. empty slots not removed
// public shoot(item: Item, count: number) {
// for(let i = 0; i<this.internalSlots.length;i++){
// if(this.internalSlots[i].item === item){
// this.internalSlots[i].count-=count;
// }
// }
// }
// public sortArrows() {
// const after = this.removeFromEnd(this.getAfterType(ItemType.Arrow).length);
// const arrows = this.removeFromEnd(this.getByType(ItemType.Arrow).length);
// arrows.sort();
// this.addSlotsToEnd(arrows);
// this.addSlotsToEnd(after);
// }
// public getFirstEquippedSlotIndex(type: ItemType): number {
// for(let i = 0; i<this.internalSlots.length;i++){
// if(this.internalSlots[i].equipped){
// const data = itemToItemData(this.internalSlots[i].item);
// if(data.type === type){
// return i;
// }
// }
// }
// return -1;
// }
}

View file

@ -0,0 +1,59 @@
import { DisplayableInventory, DisplayableSlot, itemStackToDisplayableSlot } from "./DisplayableInventory";
import { Item, ItemStack } from "./Item";
import { Slots } from "./Slots";
/*
* Implementation of Visible Inventory (PauseMenuDataMgr) in botw
*/
export class VisibleInventory implements DisplayableInventory{
private slots: Slots = new Slots([]);
/* Implementation of mCount in botw */
private count: number = 0;
constructor(slots: Slots, count: number){
this.slots = slots;
this.count = count;
}
public deepClone(): VisibleInventory {
return new VisibleInventory(this.slots.deepClone(), this.count);
}
public getDisplayedSlots(): DisplayableSlot[] {
return this.slots.getSlotsRef().map((stack, i)=>itemStackToDisplayableSlot(stack, i>=this.count));
}
public getSlots(): Slots {
return this.slots;
}
public addDirectly(stack: ItemStack){
this.count+=this.slots.addStackDirectly(stack);
}
public addWhenReload(item: Item, count: number, equippedDuringReload: boolean) {
const slotsAdded = this.slots.add(item, count, equippedDuringReload, true, this.count);
this.count+=slotsAdded;
}
//public addInGame
// Only clears first this.count
public clearForReload() {
if(this.count > 0){
this.slots.clearFirst(this.count);
this.count = 0;
}
}
public getCount(): number {
return this.count;
}
public modifyCount(delta: number): void {
this.count+=delta;
}
public resetCount(): void {
this.count = this.slots.length;
}
}

14
src/data/mergeSort.ts Normal file
View file

@ -0,0 +1,14 @@
//https://medium.com/@fsufitch/is-javascript-array-sort-stable-46b90822543f
export const stableSort = <T>(array: T[], cmp: (a:T, b:T) => number): void => {
const stabilizedThis: [T, number][] = array.map((el, index) => [el, index]);
const stableCmp = (a: [T, number], b: [T, number]) => {
let order = cmp(a[0], b[0]);
if (order != 0) return order;
return a[1] - b[1];
}
stabilizedThis.sort(stableCmp);
for (let i=0; i<array.length; i++) {
array[i] = stabilizedThis[i][0];
}
}

View file

@ -1,46 +1,47 @@
import clsx from "clsx";
import { ItemList, ItemListItemProps, ItemListProps } from "components/ItemList";
import { ItemList, ItemListProps } from "components/ItemList";
import { DoubleItemSlot } from "components/ItemSlot";
import { TitledList } from "components/TitledList";
import { Command } from "core/Command";
import { ItemStack, itemToItemData } from "core/Item";
import { parseCommand } from "core/Parser";
import { SimulationState } from "core/SimulationState";
import { Slots } from "core/Slots";
import Background from "assets/Background.png";
import InGameBackground from "assets/InGame.png";
import React, { useEffect, useState } from "react";
type DisplayPaneProps = {
command: string,
orbs: number,
displayIndex: number,
slots: Slots,
savedSlots: Slots,
numBroken: number,
simulationState: SimulationState,
overlaySave: boolean,
editCommand: (c: Command)=>void
}
export const stacksToItemListProps = (slots: Slots, numBroken: number, isSave: boolean): ItemListProps => {
return {
items: stacksToItemProps(slots.getSlotsRef()),
numBroken,
isSave,
};
};
// export const stacksToItemListProps = (slots: Slots, numBroken: number, isSave: boolean): ItemListProps => {
// return {
// items: stacksToItemProps(slots.getSlotsRef()),
// numBroken,
// isSave,
// };
// };
export const stacksToItemProps = (stacks: ItemStack[]): ItemListItemProps[] => {
return stacks.map(stackToItemProps);
};
// export const stacksToItemProps = (stacks: ItemStack[]): ItemListItemProps[] => {
// return stacks.map(stackToItemProps);
// };
export const stackToItemProps = ({item, count, equipped}: ItemStack): ItemListItemProps => {
const data = itemToItemData(item);
return {image: data.image, count: data.stackable ? count : 0, isEquipped:equipped};
};
// export const stackToItemProps = ({item, count, equipped}: ItemStack): ItemListItemProps => {
// const data = itemToItemData(item);
// return {image: data.image, count: data.stackable ? count : 0, isEquipped:equipped};
// };
export const DisplayPane: React.FC<DisplayPaneProps> = ({command,orbs,editCommand,displayIndex, slots, savedSlots, numBroken, overlaySave})=>{
export const DisplayPane: React.FC<DisplayPaneProps> = ({command,editCommand,displayIndex,simulationState, overlaySave})=>{
const [commandString, setCommandString] = useState<string>("");
const [hasError, setHasError] = useState<boolean>(false);
const listProps = stacksToItemListProps(slots, numBroken, false);
const listSaveProps = stacksToItemListProps(savedSlots, 0, true);
//const listProps = stacksToItemListProps(slots, numBroken, false);
//const listSaveProps = stacksToItemListProps(savedSlots, 0, true);
useEffect(()=>{
if(commandString!==command){
setCommandString(command);
@ -50,22 +51,25 @@ export const DisplayPane: React.FC<DisplayPaneProps> = ({command,orbs,editComman
}, [command, displayIndex]);
return <div id="DisplayPane" style={{
width: "calc( 100% - 300px - 5px )",
float: "right",
border: "1px solid black",
boxSizing: "content-box"
height: "100%",
// width: "calc( 100% - 300px - 5px )",
// float: "right",
// border: "1px solid black",
// boxSizing: "content-box"
} }>
<div style={{
marginBottom: 2,
boxSizing: "content-box",
height: "50px"
boxSizing: "border-box",
height: "40px"
} }>
<input className={clsx("Calamity", hasError && "InputError")} style={{
marginTop: 2,
width: "80%",
<input id="CommandInputField" className={clsx("Calamity", "CommandInput", hasError && "InputError")} style={{
background: `url(${Background})`,
width: "100%",
height: "40px",
fontSize: "20pt",
paddingLeft: 10,
margin: 0,
boxSizing: "border-box",
fontSize: "16pt",
outline: "none",
}}value={commandString}
placeholder="Type command here..."
onChange={(e)=>{
@ -79,45 +83,55 @@ export const DisplayPane: React.FC<DisplayPaneProps> = ({command,orbs,editComman
setHasError(true);
}
}}></input>
<span>
Orbs: {orbs}
</span>
</div>
<div style={{
height: "calc( 100% - 40px )"
}}>
{overlaySave ?
<div style={{
borderTop: "1px solid black",
boxSizing: "content-box",
height: "calc( ( 99vh - 60px ))",
overflowY: "auto"
boxSizing: "border-box",
height: "100%",
overflowY: "auto",
background: `url(${InGameBackground})`,
backgroundPosition: "center",
backgroundSize: "auto 100%",
color: "white",
} }>
<div>Save / Current</div>
<div>
<TitledList title={`Game Data / Visible Inventory (Count=${simulationState.inventoryMCount})`}>
{
(()=>{
const doubleSlots: JSX.Element[] = [];
for(let i=0;i<savedSlots.length && i<slots.length;i++){
const gameDataSlots = simulationState.displayableGameData.getDisplayedSlots();
const inventorySlots = simulationState.displayablePouch.getDisplayedSlots();
console.log(inventorySlots);
for(let i=0;i<gameDataSlots.length && i<inventorySlots.length;i++){
doubleSlots.push(<DoubleItemSlot key={i}
first={{...stackToItemProps(savedSlots.get(i)), isBroken:false, isSave:true}}
second={{...stackToItemProps(slots.get(i)), isBroken:i>=slots.length-numBroken, isSave:false}}
first={{slot: gameDataSlots[i]}}
second={{slot: inventorySlots[i]}}
/>);
}
if(savedSlots.length>slots.length){
for(let i=slots.length;i<savedSlots.length;i++){
doubleSlots.push(<DoubleItemSlot key={i+slots.length}
first={{...stackToItemProps(savedSlots.get(i)), isBroken:false, isSave:true}}
if(inventorySlots.length>gameDataSlots.length){
for(let i=inventorySlots.length;i<gameDataSlots.length;i++){
doubleSlots.push(<DoubleItemSlot key={i+inventorySlots.length}
first={{slot: gameDataSlots[i]}}
/>);
}
}else if(slots.length > savedSlots.length){
for(let i=savedSlots.length;i<slots.length;i++){
doubleSlots.push(<DoubleItemSlot key={i + savedSlots.length}
second={{...stackToItemProps(slots.get(i)), isBroken:i>=slots.length-numBroken, isSave:false}}
}else if(inventorySlots.length > gameDataSlots.length){
for(let i=gameDataSlots.length;i<inventorySlots.length;i++){
doubleSlots.push(<DoubleItemSlot key={i + gameDataSlots.length}
second={{slot: inventorySlots[i]}}
/>);
}
}
return doubleSlots;
})()
}
</div>
</TitledList>
</div>
@ -125,25 +139,37 @@ export const DisplayPane: React.FC<DisplayPaneProps> = ({command,orbs,editComman
<div style={{
borderTop: "1px solid black",
background: `url(${Background})`,
color: "white",
borderBottom: "1px solid black",
marginBottom: 2,
boxSizing: "content-box",
height: "calc( ( 99vh - 60px ) / 2)",
boxSizing: "border-box",
height: "50%",
overflowY: "auto"
} }>
<div>Inventory of Save</div>
<ItemList {...listSaveProps}/>
<TitledList title="Game Data">
<ItemList slots={simulationState.displayableGameData.getDisplayedSlots()}/>
</TitledList>
</div>
<div style={{
borderTop: "1px solid black",
boxSizing: "content-box",
height: "calc( ( 99vh - 60px ) / 2)",
overflowY: "auto"
background: `url(${InGameBackground})`,
backgroundPosition: "center",
backgroundSize: "100%",
boxSizing: "border-box",
height: "50%",
overflowY: "auto",
color: "white"
} }>
<div>Current Inventory</div>
<ItemList {...listProps}/>
<TitledList title={`Visible Inventory (Count=${simulationState.inventoryMCount})`}>
<ItemList slots={simulationState.displayablePouch.getDisplayedSlots()}/>
</TitledList>
</div>
</>}
</div>
</div>;
};

View file

@ -0,0 +1,86 @@
import { ItemList } from "components/ItemList";
import { TitledList } from "components/TitledList";
import React from "react";
export const ReferencePage: React.FC = React.memo(()=>{
return (
<div style={{height: "100%", width: "100%", color: "white"}}>
<TitledList title="Reference">
<div style={{padding: 10}}>
<h2>Commands</h2>
<h3 className="Reference">Initialize X item1 Y item2 Z item3 ...</h3>
<h4 className="Reference">Used for initializing inventory before simulation</h4>
<p className="Reference">
Fully resets the inventory by clearing all items and set Count to 0, then forcefully write the item list to inventory.
This would reset any broken slot you already have, and any in-game checks that happen when adding items are disabled.
For example, the items will appear in the order you specify, not in the in-game tab order
</p>
<p className="Reference">
If you specify count &gt; 1 for unstackable items like weapon or sheika slate, multiple of that item would be added.
Game Data will be synced with Visible Inventory after the reset
</p>
<p className="Reference">
Note that this will not clear saves. You can use this command to initialize multiple saves
</p>
<p className="Reference Example">Example: Initialize 1 Apple 2 Axe 3 Slate 4 SpiritOrb</p>
<h3 className="Reference">Save / Save As NAME</h3>
<h4 className="Reference">Simulates a hard save or auto save action</h4>
<p className="Reference">
Writes Game Data to the corresponding save slot. The auto saves are specified by NAME.
You can have as many auto saves as you want in the simulator.
</p>
<p className="Reference Example">Example 1: Save</p>
<p className="Reference Example">Example 2: Save As MySave</p>
<p className="Reference">
Example 1 will save to the manual save slot, while example 2 will save to the slot named "MySave".
There cannot be spaces in the name. If "MySave" doesn't exist, a new slot is created
</p>
<h3 className="Reference">Reload (NAME)</h3>
<h4 className="Reference">Simulates reloading a save</h4>
<p className="Reference">
First, reads Game Data from the corresponding save slot.
If NAME is not given, the manual save is used unless "Use" commands are used before this (see below).
If NAME is given, the corresponding save slot with that name is used
</p>
<p className="Reference">
After that, the first Count items in the visible inventory is removed, and Count is decreased accordingly.
Then, each item slot in the Game Data is added to the inventory.
</p>
<p className="Reference Example">Example 1: Reload</p>
<p className="Reference Example">Example 2: Reload MySave</p>
<h3 className="Reference">Use NAME</h3>
<h4 className="Reference">(Deprecated) Specify which save to load on the subsequent reload</h4>
<p className="Reference Example">
This command is only for backward compatibility. Use "Reload" instead
</p>
<p className="Reference">
Specify the save named NAME to be reloaded on the next "Reload" command
</p>
<p className="Reference Example">Example: Use MySave</p>
<h3 className="Reference">Break X Slots</h3>
<h4 className="Reference">Simulate making X broken slots with hold smuggle glitch</h4>
<p className="Reference">
Decrease inventory Count by X
</p>
<p className="Reference">
This command does not automatically simulate the hold smuggle and sell process.
It just changes count (i.e. make broken slots) with magic.
</p>
<p className="Reference Example">Example: Break 4 Slots</p>
</div>
</TitledList>
</div>
)
});