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 { .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 { .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 { .ItemSlot {
@ -42,7 +61,7 @@ h1 {
width: 64px; width: 64px;
height: 64px; height: 64px;
background-color: #333333; background-color: #333333bb;
box-sizing: content-box; box-sizing: content-box;
display: inline-block; display: inline-block;
} }
@ -51,7 +70,7 @@ h1 {
background-color: #660000; background-color: #660000;
} }
.ItemSlotSave { .GameDataBackground {
background-color: #003300; background-color: #003300;
} }
@ -81,3 +100,21 @@ h3.ListHeader {
margin: 0; margin: 0;
padding: 10px; 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 { 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 "./App.css";
import { CommandItem } from "./components/CommandItem"; import { CommandItem } from "./components/CommandItem";
import { DisplayPane, stacksToItemListProps } from "surfaces/DisplayPane"; import { DisplayPane } from "surfaces/DisplayPane";
import { Item } from "core/Item"; import { Item } from "core/Item";
import { saveAs } from "data/FileSaver"; import { saveAs } from "data/FileSaver";
import { parseCommand } from "core/Parser"; import { parseCommand } from "core/Parser";
import { ItemList } from "components/ItemList"; import { ItemList } from "components/ItemList";
import { TitledList } from "components/TitledList"; 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 getDefaultCommands = (): Command[]=>{
const encoded = localStorage.getItem("HDS.CurrentCommandsText"); 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 lines.map(l=>parseCommand(l)).filter(c=>c) as Command[];
} }
return [ return [
new CommandInitialize([ parseCommand("Get 5 Diamond 1 Slate 1 Glider 4 SpiritOrb"),
{ parseCommand("Save"),
item: Item.Diamond, parseCommand("Break 4 Slots"),
count: 5, parseCommand("Reload"),
equipped:false, parseCommand("Save"),
}, parseCommand("Reload"),
{ ] as Command[];;
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()
];
}; };
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 = () => { export const App: React.FC = () => {
const [page, setPageInState] = useState<string>("#simulation");
const [overlaySave, setOverlaySave] = useState<boolean>(false); const [overlaySave, setOverlaySave] = useState<boolean>(false);
const [commands, setCommands] = useState<Command[]>(getDefaultCommands()); const [commands, setCommands] = useState<Command[]>(getDefaultCommands());
const [selectedSaveName, setSelectedSaveName] = useState<string>("");
const [displayIndex, setDisplayIndex] = useState<number>(0); const [displayIndex, setDisplayIndex] = useState<number>(0);
const [contextMenuX, setContextMenuX] = useState<number>(0); const [contextMenuX, setContextMenuX] = useState<number>(0);
const [contextMenuY, setContextMenuY] = useState<number>(0); const [contextMenuY, setContextMenuY] = useState<number>(0);
@ -86,20 +46,36 @@ export const App: React.FC = () => {
const uploadRef = useRef<HTMLInputElement>(null); const uploadRef = useRef<HTMLInputElement>(null);
const contextMenuRef = useRef<HTMLDivElement>(null); const contextMenuRef = useRef<HTMLDivElement>(null);
// compute props // compute props
const inventories = useMemo(()=>{ const simulationStates = useMemo(()=>{
const inventories: Inventory[] = []; const simulationStates: SimulationState[] = [];
const inv = new Inventory(); const state = createSimulationState();
commands.forEach(c=>{ commands.forEach(c=>{
c.execute(inv); c.execute(state);
inventories.push(inv.deepClone()); simulationStates.push(state.deepClone());
}); });
return inventories; return simulationStates;
}, [commands]); }, [commands]);
const setPage = useCallback((hash: string)=>{
window.location.hash = hash;
setPageInState(hash);
}, []);
useEffect(()=>{
setPage(window.location.hash || "#simulation");
}, [window.location.hash]);
useEffect(()=>{ useEffect(()=>{
window.onkeydown=(e)=>{ window.onkeydown=(e)=>{
if(e.code==="ArrowDown"){ if(e.code==="ArrowDown"){
setDisplayIndex(Math.min(commands.length-1, displayIndex+1)); 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"){ }else if(e.code==="ArrowUp"){
setDisplayIndex(Math.max(0, displayIndex-1)); setDisplayIndex(Math.max(0, displayIndex-1));
} }
@ -143,8 +119,12 @@ export const App: React.FC = () => {
<div id="NavBar" style={{ <div id="NavBar" style={{
height: 40 height: 40
}}> }}>
<button>Simulation</button> <button onClick={()=>{
<button>Reference</button> setPage("#simulation")
}}>Simulation</button>
<button onClick={()=>{
setPage("#reference")
}}>Reference</button>
<button>Options</button> <button>Options</button>
</div> </div>
@ -156,56 +136,51 @@ export const App: React.FC = () => {
maxHeight: 220, maxHeight: 220,
height: "30vh", height: "30vh",
border: "1px solid black", border: "1px solid black",
boxSizing: "content-box", boxSizing: "border-box",
overflowY: "hidden" overflowY: "hidden",
}}> }}>
<TitledList title="Saves"> <TitledList title="Saves">
<ol> {
<CommandItem displayIndex >=0 && displayIndex < simulationStates.length &&
onClick={()=>{}} <ol>
{
!!simulationStates[displayIndex].getManualSave() &&
<CommandItem
onClick={()=>{
setSelectedSaveName("");
}}
comment={false} comment={false}
isSelected={selectedSaveName===""}
> >
Manual Save Manual Save
</CommandItem> </CommandItem>
<CommandItem }
onClick={()=>{}}
{
Object.entries(simulationStates[displayIndex].getNamedSaves()).map(([name, _gamedata])=>(
<CommandItem
onClick={()=>{
setSelectedSaveName(name);
}}
comment={false} comment={false}
isSelected={selectedSaveName===name}
> >
Auto Save 1 {name}
</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
</CommandItem> </CommandItem>
))
}
</ol> </ol>
}
</TitledList> </TitledList>
</div> </div>
<div style={{ <div style={{
minHeight: "calc( 70vh - 45px )", minHeight: "calc( 70vh - 40px )",
height: "calc( 100vh - 45px - 220px )", height: "calc( 100vh - 40px - 220px )",
border: "1px solid black", border: "1px solid black",
boxSizing: "content-box", boxSizing: "border-box",
overflowY: "hidden" overflowY: "hidden"
}}> }}>
@ -215,7 +190,13 @@ export const App: React.FC = () => {
{ {
commands.map((c,i)=> commands.map((c,i)=>
<CommandItem <CommandItem
onClick={()=>setDisplayIndex(i)} onClick={()=>{
setDisplayIndex(i);
const inputField = document.getElementById("CommandInputField");
if(inputField){
inputField.focus();
}
}}
onContextMenu={(x,y)=>{ onContextMenu={(x,y)=>{
setContextIndex(i); setContextIndex(i);
setContextMenuX(x); setContextMenuX(x);
@ -259,39 +240,73 @@ export const App: React.FC = () => {
</div> </div>
<div id="MainPane" style={{ <div id="MainPane" style={{
width: "calc ( 100% - 300px )" position: "absolute",
top: 40,
right: 0,
bottom: 0,
left: 300,
backgroundColor: "#262626"
}}> }}>
<div style={{ {
maxHeight: 220, page === "#simulation" && <>
height: "30vh", <div style={{
border: "1px solid black", maxHeight: 220,
boxSizing: "content-box", height: "30vh",
overflowY: "hidden" border: "1px solid black",
} }> boxSizing: "border-box",
<TitledList title="Save Data"> overflowY: "hidden",
<ItemList {...listProps}/> color: "white",
</TitledList> backgroundColor: "#262626"
} }>
{
(displayIndex >= 0 && displayIndex < commands.length) ?
<TitledList title="Save Data">
{
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 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()}
simulationState={simulationStates[displayIndex]}
editCommand={(c)=>{
const arrCopy = [...commands];
arrCopy[displayIndex] = c;
setCommands(arrCopy);
}}
/>
}
</div> </div>
<div > </>
{displayIndex >= 0 && displayIndex < commands.length && }
<DisplayPane {
overlaySave={overlaySave} page === "#reference" && <ReferencePage />
displayIndex={displayIndex} }
command={commands[displayIndex].getDisplayString()}
orbs={inventories[displayIndex].getTurnedInOrbs()}
slots={inventories[displayIndex].getSlots()}
savedSlots={inventories[displayIndex].getSavedSlots()}
numBroken={inventories[displayIndex].getNumBroken()}
editCommand={(c)=>{
const arrCopy = [...commands];
arrCopy[displayIndex] = c;
setCommands(arrCopy);
}}
/>
}
</div>
</div> </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"; import { ItemSlot } from "./ItemSlot";
export type ItemListItemProps = {
image: string,
count: number,
isEquipped: boolean,
}
export type ItemListProps = { export type ItemListProps = {
isSave: boolean, slots: DisplayableSlot[]
items: ItemListItemProps[],
numBroken: number
} }
export const ItemList: React.FC<ItemListProps> = ({items, numBroken, isSave}) => { export const ItemList: React.FC<ItemListProps> = ({slots}) => {
return <> return <>
{ {
items.map(({image, count, isEquipped}, i)=>{ slots.map((slot, i)=>{
const broken = i+numBroken >= items.length; return <ItemSlot key={i} slot={slot}/>;
return <ItemSlot key={i} image={image} count={count} isBroken={broken} isSave={isSave} isEquipped={isEquipped}/>;
}) })
} }
</>; </>;

View file

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

View file

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

View file

@ -1,17 +1,15 @@
import { Inventory } from "./Inventory"; import { Inventory } from "./Inventory";
import { Item, ItemStack, itemToArrowType } from "./Item"; import { Item, ItemStack, itemToArrowType } from "./Item";
import { SimulationState } from "./SimulationState";
export interface Command { export interface Command {
execute(inv: Inventory): void, execute(state: SimulationState): void,
getDisplayString(): string, getDisplayString(): string,
// fromBuffer(buf: Buffer): number,
// toBuffer(): Buffer,
} }
export class CommandNothing implements Command { export class CommandNothing implements Command {
static Op = 0x0;
execute(_inv: Inventory): void { execute(_state: Inventory): void {
// nothing // nothing
} }
getDisplayString(): string { getDisplayString(): string {
@ -21,44 +19,14 @@ export class CommandNothing implements Command {
} }
export class CommandInitialize implements Command { export class CommandInitialize implements Command {
static Op = 0x1;
private stacks: ItemStack[]; private stacks: ItemStack[];
constructor(stacks: ItemStack[]){ constructor(stacks: ItemStack[]){
this.stacks = stacks; 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 { public execute(state: SimulationState): void {
inv.init(this.stacks); state.initialize(this.stacks);
} }
public getDisplayString(): string { public getDisplayString(): string {
const parts = ["Initialize"]; 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 { export class CommandSave implements Command {
static Op = 0x3;
// public fromBuffer(_buf: Buffer): number { public execute(state: SimulationState): void {
// return 0; state.save();
// }
// public toBuffer(): Buffer {
// const buf: Buffer = Buffer.alloc(1);
// buf.writeInt8(CommandSave.Op);
// return buf;
// }
public execute(inv: Inventory): void {
inv.save();
} }
public getDisplayString(): string { public getDisplayString(): string {
return "Save"; return "Save";
} }
} }
export class CommandReload implements Command { export class CommandSaveAs 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 {
private name: string; private name: string;
constructor(name: string){ constructor(name: string){
this.name = name; this.name = name;
} }
public execute(inv: Inventory): void { public execute(state: SimulationState): void {
inv.save(); state.save(this.name);
inv.setTag(this.name);
} }
public getDisplayString(): string { public getDisplayString(): string {
return `Save As ${this.name}`; 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; private name: string;
constructor(name: string){ constructor(name: string){
this.name = name; this.name = name;
} }
public execute(inv: Inventory): void { public execute(state: SimulationState): void {
inv.applyTag(this.name); state.useSaveForNextReload(this.name);
} }
public getDisplayString(): string { public getDisplayString(): string {
return `Use ${this.name}`; return `Use ${this.name}`;
} }
} }
export class CommandCloseGame implements Command { export class CommandBreakSlots implements Command {
public execute(inv: Inventory): void {
inv.closeGame(); private numToBreak: number;
constructor(numToBreak: number){
this.numToBreak = numToBreak;
}
public execute(state: SimulationState): void {
state.breakSlots(this.numToBreak);
} }
public getDisplayString(): string { 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 { export class CommandComment implements Command {
private name: string; private name: string;
constructor(name: string){ constructor(name: string){
this.name = name; this.name = name;
} }
public execute(_inv: Inventory): void { public execute(_state: SimulationState): void {
// nothing // nothing
} }
public getDisplayString(): string { 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"; import { Slots } from "./Slots";
export class Inventory { export class Inventory {
private slots: Slots = new Slots([]); // private slots: Slots = new Slots([]);
private savedSlots: Slots = new Slots([]); // private savedSlots: Slots = new Slots([]);
private namedSlots: {[name: string]: Slots} = {}; // private namedSlots: {[name: string]: Slots} = {};
private numBroken = 0; // private numBroken = 0;
private isInitialSort = false; // private isInitialSort = false;
private isAltered = true; // private isAltered = true;
private inaccurate = false; // private inaccurate = false;
private turnedInOrbs = 0; // private turnedInOrbs = 0;
public deepClone(): Inventory { // public deepClone(): Inventory {
const other = new Inventory(); // const other = new Inventory();
other.slots = this.slots.deepClone(); // other.slots = this.slots.deepClone();
other.savedSlots = this.savedSlots.deepClone(); // other.savedSlots = this.savedSlots.deepClone();
other.numBroken = this.numBroken; // other.numBroken = this.numBroken;
other.isInitialSort = this.isInitialSort; // other.isInitialSort = this.isInitialSort;
other.isAltered = this.isAltered; // other.isAltered = this.isAltered;
other.inaccurate = this.inaccurate; // other.inaccurate = this.inaccurate;
other.turnedInOrbs = this.turnedInOrbs; // other.turnedInOrbs = this.turnedInOrbs;
other.namedSlots = {}; // other.namedSlots = {};
for(const name in this.namedSlots){ // for(const name in this.namedSlots){
other.namedSlots[name] = this.namedSlots[name].deepClone(); // other.namedSlots[name] = this.namedSlots[name].deepClone();
} // }
return other; // return other;
} // }
public getSlots(): Slots { // public getSlots(): Slots {
return this.slots; // return this.slots;
} // }
public getSavedSlots(): Slots { // public getSavedSlots(): Slots {
return this.savedSlots; // return this.savedSlots;
} // }
public getNumBroken(): number { // public getNumBroken(): number {
return this.numBroken; // return this.numBroken;
} // }
public isInaccurate(): boolean { // public isInaccurate(): boolean {
return this.inaccurate; // return this.inaccurate;
} // }
public getTurnedInOrbs(): number { // public getTurnedInOrbs(): number {
return this.turnedInOrbs; // return this.turnedInOrbs;
} // }
public rawInit(stacks: ItemStack[]) { // public rawInit(stacks: ItemStack[]) {
this.slots = new Slots(stacks); // this.slots = new Slots(stacks);
} // }
public init(stacks: ItemStack[]) { // public init(stacks: ItemStack[]) {
this.slots = new Slots([]); // this.slots = new Slots([]);
stacks.forEach(s=>{ // stacks.forEach(s=>{
this.slots.add(s.item, s.count); // this.slots.add(s.item, s.count);
}); // });
this.numBroken = 0; // this.numBroken = 0;
this.isInitialSort = false; // this.isInitialSort = false;
this.isAltered = true; // this.isAltered = true;
this.inaccurate = false; // this.inaccurate = false;
} // }
public closeGame() { // public closeGame() {
this.numBroken = 0; // this.numBroken = 0;
this.isInitialSort = false; // this.isInitialSort = false;
this.isAltered = true; // this.isAltered = true;
this.inaccurate = false; // this.inaccurate = false;
this.slots = new Slots([]); // this.slots = new Slots([]);
} // }
public addBrokenSlots(num: number) { // public addBrokenSlots(num: number) {
this.numBroken+=num; // this.numBroken+=num;
} // }
public setTag(name: string){ // public setTag(name: string){
this.namedSlots[name] = this.savedSlots.deepClone(); // this.namedSlots[name] = this.savedSlots.deepClone();
} // }
public applyTag(name: string){ // public applyTag(name: string){
if(name in this.namedSlots){ // if(name in this.namedSlots){
this.savedSlots = this.namedSlots[name].deepClone(); // this.savedSlots = this.namedSlots[name].deepClone();
}else{ // }else{
this.savedSlots = new Slots([]); // this.savedSlots = new Slots([]);
} // }
} // }
public save() { // public save() {
if(this.isAltered){ // if(this.isAltered){
this.savedSlots = this.slots.deepClone(); // this.savedSlots = this.slots.deepClone();
} // }
// Inventory Corruption // // Inventory Corruption
// get durability transfer slots // // get durability transfer slots
const durabilityTransferSlots: number[] = []; // const durabilityTransferSlots: number[] = [];
const equippedWeapon = this.slots.getFirstEquippedSlotIndex(ItemType.Weapon); // const equippedWeapon = this.slots.getFirstEquippedSlotIndex(ItemType.Weapon);
if(equippedWeapon>=0){ // if(equippedWeapon>=0){
durabilityTransferSlots.push(equippedWeapon); // durabilityTransferSlots.push(equippedWeapon);
} // }
const equippedBow = this.slots.getFirstEquippedSlotIndex(ItemType.Bow); // const equippedBow = this.slots.getFirstEquippedSlotIndex(ItemType.Bow);
if(equippedBow>=0){ // if(equippedBow>=0){
durabilityTransferSlots.push(equippedBow); // durabilityTransferSlots.push(equippedBow);
} // }
const equippedShield = this.slots.getFirstEquippedSlotIndex(ItemType.Shield); // const equippedShield = this.slots.getFirstEquippedSlotIndex(ItemType.Shield);
if(equippedShield>=0){ // if(equippedShield>=0){
durabilityTransferSlots.push(equippedShield); // durabilityTransferSlots.push(equippedShield);
} // }
durabilityTransferSlots.forEach(s=>{ // durabilityTransferSlots.forEach(s=>{
if(s<this.savedSlots.length){ // if(s<this.savedSlots.length){
// We ignore the case where durability transfer happens from equipment to equipment // // We ignore the case where durability transfer happens from equipment to equipment
if(itemToItemData(this.savedSlots.get(s).item).stackable){ // if(itemToItemData(this.savedSlots.get(s).item).stackable){
this.savedSlots.get(s).count = 999; // this.savedSlots.get(s).count = 999;
} // }
} // }
}); // });
} // }
public reload() { // public reload() {
// get things to dupe // // get things to dupe
const dupeMap: {[k in ItemType]: Slots} = { // const dupeMap: {[k in ItemType]: Slots} = {
[ItemType.Weapon]: new Slots([]), // [ItemType.Weapon]: new Slots([]),
[ItemType.Bow]: new Slots([]), // [ItemType.Bow]: new Slots([]),
[ItemType.Arrow]: new Slots([]), // [ItemType.Arrow]: new Slots([]),
[ItemType.Shield]: new Slots([]), // [ItemType.Shield]: new Slots([]),
[ItemType.Material]: new Slots([]), // [ItemType.Material]: new Slots([]),
[ItemType.Meal]: new Slots([]), // [ItemType.Meal]: new Slots([]),
[ItemType.Key]: new Slots([]) // [ItemType.Key]: new Slots([])
}; // };
for(let i=Math.max(0, this.slots.length-this.numBroken);i<this.slots.length;i++){ // for(let i=Math.max(0, this.slots.length-this.numBroken);i<this.slots.length;i++){
const stack = this.slots.get(i); // const stack = this.slots.get(i);
const itemData = itemToItemData(stack.item); // const itemData = itemToItemData(stack.item);
dupeMap[itemData.type].addStackCopy(stack); // dupeMap[itemData.type].addStackCopy(stack);
} // }
// apply dupe // // apply dupe
//console.log(dupeMap); // //console.log(dupeMap);
this.slots = new Slots([]); // this.slots = new Slots([]);
// const dupeType = (type: ItemType) => { // // const dupeType = (type: ItemType) => {
// } // // }
ItemTypes.forEach(type=>{ // ItemTypes.forEach(type=>{
this.slots.addSlotsToEnd(dupeMap[type]); // this.slots.addSlotsToEnd(dupeMap[type]);
this.slots.addSlotsToEnd(this.savedSlots.getByType(type).deepClone()); // this.slots.addSlotsToEnd(this.savedSlots.getByType(type).deepClone());
}); // });
this.slots.sortArrows(); // this.slots.sortArrows();
this.isInitialSort = true; // this.isInitialSort = true;
this.isAltered = false; // this.isAltered = false;
} // }
public sortKey() { // public sortKey() {
const nonKeyItems = this.slots.getBeforeType(ItemType.Key); // const nonKeyItems = this.slots.getBeforeType(ItemType.Key);
const keyItems = this.slots.getByType(ItemType.Key); // const keyItems = this.slots.getByType(ItemType.Key);
keyItems.sort(); // keyItems.sort();
nonKeyItems.addSlotsToEnd(keyItems); // nonKeyItems.addSlotsToEnd(keyItems);
this.slots = nonKeyItems; // this.slots = nonKeyItems;
this.isAltered=true; // this.isAltered=true;
this.isInitialSort=false; // this.isInitialSort=false;
} // }
public sortMaterial() { // public sortMaterial() {
const beforeMaterial = this.slots.getBeforeType(ItemType.Material); // const beforeMaterial = this.slots.getBeforeType(ItemType.Material);
const afterMaterial = this.slots.getAfterType(ItemType.Material); // const afterMaterial = this.slots.getAfterType(ItemType.Material);
const materials = this.slots.getByType(ItemType.Material); // const materials = this.slots.getByType(ItemType.Material);
if(this.isInitialSort){ // if(this.isInitialSort){
// the materials in broken slots are not sorted // // the materials in broken slots are not sorted
const brokenSlots = Math.max(0, this.numBroken - afterMaterial.length); // const brokenSlots = Math.max(0, this.numBroken - afterMaterial.length);
const noSortPart = materials.removeFromEnd(brokenSlots); // const noSortPart = materials.removeFromEnd(brokenSlots);
materials.sort(); // materials.sort();
beforeMaterial.addSlotsToEnd(materials); // beforeMaterial.addSlotsToEnd(materials);
beforeMaterial.addSlotsToEnd(noSortPart); // beforeMaterial.addSlotsToEnd(noSortPart);
beforeMaterial.addSlotsToEnd(afterMaterial); // beforeMaterial.addSlotsToEnd(afterMaterial);
}else{ // }else{
materials.sort(); // materials.sort();
beforeMaterial.addSlotsToEnd(materials); // beforeMaterial.addSlotsToEnd(materials);
beforeMaterial.addSlotsToEnd(afterMaterial); // beforeMaterial.addSlotsToEnd(afterMaterial);
} // }
this.slots = beforeMaterial; // this.slots = beforeMaterial;
this.isInitialSort = false; // this.isInitialSort = false;
this.isAltered=true; // this.isAltered=true;
} // }
public remove(item: Item, count: number, slot: number) { // public remove(item: Item, count: number, slot: number) {
this.slots.remove(item, count, slot); // this.slots.remove(item, count, slot);
if(item===Item.SpiritOrb){ // if(item===Item.SpiritOrb){
this.turnedInOrbs+=count; // this.turnedInOrbs+=count;
} // }
this.isAltered=true; // this.isAltered=true;
} // }
public add(item: Item, count: number) { // public add(item: Item, count: number) {
this.slots.add(item, count); // this.slots.add(item, count);
if(itemToItemData(item).type===ItemType.Arrow){ // if(itemToItemData(item).type===ItemType.Arrow){
this.slots.sortArrows(); // this.slots.sortArrows();
} // }
this.isAltered=true; // this.isAltered=true;
} // }
public equipEquipmentOrArrow(item: Item, slot: number) { // public equipEquipmentOrArrow(item: Item, slot: number) {
this.slots.equip(item, slot); // this.slots.equip(item, slot);
this.isAltered=true; // this.isAltered=true;
} // }
public unequipEquipment(item: Item, slot: number){ // public unequipEquipment(item: Item, slot: number){
this.slots.unequip(item, slot); // this.slots.unequip(item, slot);
this.isAltered=true; // this.isAltered=true;
} // }
public shootArrow(item: Item, count: number){ // public shootArrow(item: Item, count: number){
this.slots.shoot(item, count); // this.slots.shoot(item, count);
this.isAltered=true; // this.isAltered=true;
} // }
} }

View file

@ -5,9 +5,10 @@ export enum ItemType {
Bow = 1, Bow = 1,
Arrow = 2, Arrow = 2,
Shield = 3, Shield = 3,
Material = 4, Armor = 4,
Meal = 5, Material = 5,
Key = 6 Meal = 6,
Key = 7
} }
export const ItemTypes = [ export const ItemTypes = [
@ -73,6 +74,8 @@ export enum Item {
HeartyRadish = "HeartyRadish", HeartyRadish = "HeartyRadish",
BigHeartyRadish = "BigHeartyRadish", BigHeartyRadish = "BigHeartyRadish",
Fairy = "Fairy", Fairy = "Fairy",
MasterSword = "MasterSword",
} }
type ItemData = { type ItemData = {
@ -90,6 +93,7 @@ const TypeToCount = {
[ItemType.Bow]: 0, [ItemType.Bow]: 0,
[ItemType.Arrow]: 0, [ItemType.Arrow]: 0,
[ItemType.Shield]: 0, [ItemType.Shield]: 0,
[ItemType.Armor]: 0,
[ItemType.Material]: 0, [ItemType.Material]: 0,
[ItemType.Key]: 0, [ItemType.Key]: 0,
[ItemType.Meal]: 0, [ItemType.Meal]: 0,
@ -161,6 +165,9 @@ register(0x50, Item.Weapon, ItemType.Weapon, {
image: Images.Axe, image: Images.Axe,
stackable: false stackable: false
}); });
register(0, Item.MasterSword, ItemType.Weapon, {
stackable: false,
})
register(0x60, Item.Bow, ItemType.Bow, { register(0x60, Item.Bow, ItemType.Bow, {
image: Images.ForestDwellerBow, 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"; import { Item, ItemStack } from "./Item";
export const parseCommand = (cmdString: string): Command | undefined => { export const parseCommand = (cmdString: string): Command | undefined => {
if(cmdString.startsWith("# ")){ if(cmdString.startsWith("# ")){
return new CommandComment(cmdString.substring(2)); return new CommandComment(cmdString.substring(2));
} }
@ -28,130 +39,135 @@ export const parseCommand = (cmdString: string): Command | undefined => {
} }
return new CommandInitialize(stacks); return new CommandInitialize(stacks);
} }
// no var // Save/Reload
if(tokens.length===1 && tokens[0] === "Save"){ if(tokens.length===1 && tokens[0] === "Save"){
return new CommandSave(); 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"){ if(tokens.length===1 && tokens[0] === "Reload"){
return new CommandReload(); return new CommandReload();
} }
if(tokens.length===2 && tokens[0] === "Reload"){
if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Key"){ return new CommandReload(tokens[1]);
return new CommandSortKey();
} }
if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Material"){ // break
return new CommandSortMaterial();
}
if(tokens.length===2 && tokens[0] === "Close" && tokens[1] === "Game"){
return new CommandCloseGame();
}
// break
if (tokens.length > 2 && tokens[0] === "Break" && tokens[2]=== "Slots" ){ if (tokens.length > 2 && tokens[0] === "Break" && tokens[2]=== "Slots" ){
const slots = parseInt(tokens[1]); const slots = parseInt(tokens[1]);
if(Number.isInteger(slots)){ if(Number.isInteger(slots)){
return new CommandBreakSlots(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; 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"; import { Item, ItemStack, itemToItemData, ItemType } from "./Item";
/*
* This is the data model common to GameData and VisibleInventory
*/
export class Slots { export class Slots {
private internalSlots: ItemStack[] = []; private internalSlots: ItemStack[] = [];
constructor(slots: ItemStack[]) { constructor(slots: ItemStack[]) {
@ -14,184 +20,301 @@ export class Slots {
public get length(): number { public get length(): number {
return this.internalSlots.length; 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 getByType(type: ItemType): Slots { public sortItemType(mCount: number | null) {
return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type===type)); if(mCount === null){
} mCount = this.internalSlots.length;
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){
return;
}
}
} }
this.internalSlots.push(stack); if(mCount <= 1){
} return;
public addStackCopy(stack: ItemStack) { }
this.addStack({...stack}); stableSort(this.internalSlots, (a,b)=>{
} const aData = itemToItemData(a.item);
public sort() { const bData = itemToItemData(b.item);
this.internalSlots.sort((a,b)=>{ if(aData.type === ItemType.Arrow && bData.type === ItemType.Arrow){
return itemToItemData(a.item).sortOrder - itemToItemData(b.item).sortOrder; return aData.sortOrder - bData.sortOrder;
}
return aData.type - bData.type;
}); });
} }
public removeFromEnd(count: number): Slots {
const end = this.internalSlots.splice(-count, count); public clearFirst(count: number) {
return new Slots(end); this.internalSlots.splice(0, count);
} }
public remove(item: Item, count: number, slot: number) { // public get(i: number): ItemStack{
let s = 0; // return this.internalSlots[i];
for(let i = 0; i<this.internalSlots.length && count > 0;i++){ // }
if(this.internalSlots[i].item === item){ // public getByType(type: ItemType): Slots {
if(s<slot){ // return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type===type));
s++; // }
}else{ // public getBeforeType(type: ItemType): Slots {
if(this.internalSlots[i].count<count){ // return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type<type));
count-=this.internalSlots[i].count; // }
this.internalSlots[i].count=0; // public getAfterType(type: ItemType): Slots {
// return new Slots(this.internalSlots.filter(s=>itemToItemData(s.item).type>type));
}else{ // }
this.internalSlots[i].count-=count; // public addSlotsToEnd(slots: Slots) {
break; // 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;
} }
this.internalSlots = this.internalSlots.filter(({count})=>count>0); 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;
public add(item: Item, count: number) { // }else{
let added = false; // this.internalSlots[i].count-=count;
// break;
// }
// }
// }
// }
// 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); 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){ if(data.stackable){
for(let i = 0; i<this.internalSlots.length;i++){ for(let i = 0; i<this.internalSlots.length;i++){
if(this.internalSlots[i].item === item){ if(this.internalSlots[i].item === item){
this.internalSlots[i].count+=count; if(reloading){
added = true; if(this.internalSlots[i].count + count > 999){
break; // 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){ // Need to add new slot
const after = this.removeFromEnd(this.getAfterType(data.type).length); // Key item check: if the key item or master sword already exists in the first tab, do not add
if(data.stackable){ if(mCount != 0){
if(data.type===ItemType.Arrow){ if(data.type === ItemType.Key || item === Item.MasterSword) {
// if currently equipped arrow == 0. new arrows are equiped let i=0;
const shouldEquipNew = this.internalSlots.filter(s=>{ while(i<this.internalSlots.length && itemToItemData(this.internalSlots[i].item).type < data.type){
const sData = itemToItemData(s.item); i++;
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});
} }
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.addSlot({item,count,equipped:shouldEquipNew}, mCount+1);
}else{ }else{
if(data.type===ItemType.Weapon || data.type===ItemType.Bow || data.type===ItemType.Shield){ this.addSlot({item,count,equipped:false}, mCount+1);
//Check equip }
const shouldEquipNew = this.internalSlots.filter(s=>{ return 1;
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});
} if(data.type===ItemType.Weapon || data.type===ItemType.Bow || data.type===ItemType.Shield){
}else{ //Check equip
for(let i=0;i<count;i++){ const shouldEquipNew = !reloading && this.internalSlots.filter(s=>{
this.addStack({item,count:1,equipped: false}); const sData = itemToItemData(s.item);
} return sData.type === data.type && s.equipped;
} }).length === 0;
this.addSlot({item,count:1,equipped: shouldEquipNew}, mCount+1);
for(let i=1;i<count;i++){
this.addSlot({item,count:1,equipped: false}, mCount+i+1);
} }
}else{
this.addSlotsToEnd(after); for(let i=0;i<count;i++){
} this.addSlot({item,count:1,equipped: false}, mCount+i+1);
}
// 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 count;
} }
// Difference between shoot and remove: // return how many slots are added
// 1. can only be from first (leftmost) slot // public add(item: Item, count: number, equipped: boolean, isReloading: boolean, mCount: number): number {
// 2. empty slots not removed // let added = false;
public shoot(item: Item, count: number) { // const data = itemToItemData(item);
for(let i = 0; i<this.internalSlots.length;i++){ // if(data.stackable){
if(this.internalSlots[i].item === item){ // for(let i = 0; i<this.internalSlots.length;i++){
this.internalSlots[i].count-=count; // 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;
// }
public sortArrows() { // break;
const after = this.removeFromEnd(this.getAfterType(ItemType.Arrow).length); // }
const arrows = this.removeFromEnd(this.getByType(ItemType.Arrow).length); // }
arrows.sort(); // }
this.addSlotsToEnd(arrows); // if(!added){
this.addSlotsToEnd(after); // 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 getFirstEquippedSlotIndex(type: ItemType): number { // }else{
for(let i = 0; i<this.internalSlots.length;i++){ // if(data.type===ItemType.Weapon || data.type===ItemType.Bow || data.type===ItemType.Shield){
if(this.internalSlots[i].equipped){ // //Check equip
const data = itemToItemData(this.internalSlots[i].item); // const shouldEquipNew = this.internalSlots.filter(s=>{
if(data.type === type){ // const sData = itemToItemData(s.item);
return i; // return sData.type === data.type && s.equipped;
} // }).length === 0;
} // this.addStack({item,count:1,equipped: shouldEquipNew});
} // for(let i=1;i<count;i++){
return -1; // this.addStack({item,count:1,equipped: false});
}
// }
// }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 clsx from "clsx";
import { ItemList, ItemListItemProps, ItemListProps } from "components/ItemList"; import { ItemList, ItemListProps } from "components/ItemList";
import { DoubleItemSlot } from "components/ItemSlot"; import { DoubleItemSlot } from "components/ItemSlot";
import { TitledList } from "components/TitledList";
import { Command } from "core/Command"; import { Command } from "core/Command";
import { ItemStack, itemToItemData } from "core/Item"; import { ItemStack, itemToItemData } from "core/Item";
import { parseCommand } from "core/Parser"; import { parseCommand } from "core/Parser";
import { SimulationState } from "core/SimulationState";
import { Slots } from "core/Slots"; import { Slots } from "core/Slots";
import Background from "assets/Background.png";
import InGameBackground from "assets/InGame.png";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
type DisplayPaneProps = { type DisplayPaneProps = {
command: string, command: string,
orbs: number,
displayIndex: number, displayIndex: number,
slots: Slots, simulationState: SimulationState,
savedSlots: Slots,
numBroken: number,
overlaySave: boolean, overlaySave: boolean,
editCommand: (c: Command)=>void editCommand: (c: Command)=>void
} }
export const stacksToItemListProps = (slots: Slots, numBroken: number, isSave: boolean): ItemListProps => { // export const stacksToItemListProps = (slots: Slots, numBroken: number, isSave: boolean): ItemListProps => {
return { // return {
items: stacksToItemProps(slots.getSlotsRef()), // items: stacksToItemProps(slots.getSlotsRef()),
numBroken, // numBroken,
isSave, // isSave,
}; // };
}; // };
export const stacksToItemProps = (stacks: ItemStack[]): ItemListItemProps[] => { // export const stacksToItemProps = (stacks: ItemStack[]): ItemListItemProps[] => {
return stacks.map(stackToItemProps); // return stacks.map(stackToItemProps);
}; // };
export const stackToItemProps = ({item, count, equipped}: ItemStack): ItemListItemProps => { // export const stackToItemProps = ({item, count, equipped}: ItemStack): ItemListItemProps => {
const data = itemToItemData(item); // const data = itemToItemData(item);
return {image: data.image, count: data.stackable ? count : 0, isEquipped:equipped}; // 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 [commandString, setCommandString] = useState<string>("");
const [hasError, setHasError] = useState<boolean>(false); const [hasError, setHasError] = useState<boolean>(false);
const listProps = stacksToItemListProps(slots, numBroken, false); //const listProps = stacksToItemListProps(slots, numBroken, false);
const listSaveProps = stacksToItemListProps(savedSlots, 0, true); //const listSaveProps = stacksToItemListProps(savedSlots, 0, true);
useEffect(()=>{ useEffect(()=>{
if(commandString!==command){ if(commandString!==command){
setCommandString(command); setCommandString(command);
@ -50,22 +51,25 @@ export const DisplayPane: React.FC<DisplayPaneProps> = ({command,orbs,editComman
}, [command, displayIndex]); }, [command, displayIndex]);
return <div id="DisplayPane" style={{ return <div id="DisplayPane" style={{
width: "calc( 100% - 300px - 5px )", height: "100%",
float: "right", // width: "calc( 100% - 300px - 5px )",
border: "1px solid black", // float: "right",
boxSizing: "content-box" // border: "1px solid black",
// boxSizing: "content-box"
} }> } }>
<div style={{ <div style={{
marginBottom: 2, boxSizing: "border-box",
boxSizing: "content-box", height: "40px"
height: "50px"
} }> } }>
<input className={clsx("Calamity", hasError && "InputError")} style={{ <input id="CommandInputField" className={clsx("Calamity", "CommandInput", hasError && "InputError")} style={{
marginTop: 2, background: `url(${Background})`,
width: "80%", width: "100%",
height: "40px", height: "40px",
fontSize: "20pt", paddingLeft: 10,
margin: 0,
boxSizing: "border-box",
fontSize: "16pt",
outline: "none",
}}value={commandString} }}value={commandString}
placeholder="Type command here..." placeholder="Type command here..."
onChange={(e)=>{ onChange={(e)=>{
@ -79,45 +83,55 @@ export const DisplayPane: React.FC<DisplayPaneProps> = ({command,orbs,editComman
setHasError(true); setHasError(true);
} }
}}></input> }}></input>
<span>
Orbs: {orbs}
</span>
</div> </div>
<div style={{
height: "calc( 100% - 40px )"
}}>
{overlaySave ? {overlaySave ?
<div style={{ <div style={{
borderTop: "1px solid black", borderTop: "1px solid black",
boxSizing: "content-box", boxSizing: "border-box",
height: "calc( ( 99vh - 60px ))", height: "100%",
overflowY: "auto" 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[] = []; 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} doubleSlots.push(<DoubleItemSlot key={i}
first={{...stackToItemProps(savedSlots.get(i)), isBroken:false, isSave:true}} first={{slot: gameDataSlots[i]}}
second={{...stackToItemProps(slots.get(i)), isBroken:i>=slots.length-numBroken, isSave:false}} second={{slot: inventorySlots[i]}}
/>); />);
} }
if(savedSlots.length>slots.length){ if(inventorySlots.length>gameDataSlots.length){
for(let i=slots.length;i<savedSlots.length;i++){ for(let i=inventorySlots.length;i<gameDataSlots.length;i++){
doubleSlots.push(<DoubleItemSlot key={i+slots.length} doubleSlots.push(<DoubleItemSlot key={i+inventorySlots.length}
first={{...stackToItemProps(savedSlots.get(i)), isBroken:false, isSave:true}} first={{slot: gameDataSlots[i]}}
/>); />);
} }
}else if(slots.length > savedSlots.length){ }else if(inventorySlots.length > gameDataSlots.length){
for(let i=savedSlots.length;i<slots.length;i++){ for(let i=gameDataSlots.length;i<inventorySlots.length;i++){
doubleSlots.push(<DoubleItemSlot key={i + savedSlots.length} doubleSlots.push(<DoubleItemSlot key={i + gameDataSlots.length}
second={{...stackToItemProps(slots.get(i)), isBroken:i>=slots.length-numBroken, isSave:false}} second={{slot: inventorySlots[i]}}
/>); />);
} }
} }
return doubleSlots; return doubleSlots;
})() })()
} }
</div> </TitledList>
</div> </div>
@ -125,25 +139,37 @@ export const DisplayPane: React.FC<DisplayPaneProps> = ({command,orbs,editComman
<div style={{ <div style={{
borderTop: "1px solid black", borderTop: "1px solid black",
background: `url(${Background})`,
color: "white",
borderBottom: "1px solid black", borderBottom: "1px solid black",
marginBottom: 2, boxSizing: "border-box",
boxSizing: "content-box", height: "50%",
height: "calc( ( 99vh - 60px ) / 2)",
overflowY: "auto" overflowY: "auto"
} }> } }>
<div>Inventory of Save</div> <TitledList title="Game Data">
<ItemList {...listSaveProps}/> <ItemList slots={simulationState.displayableGameData.getDisplayedSlots()}/>
</TitledList>
</div> </div>
<div style={{ <div style={{
borderTop: "1px solid black", borderTop: "1px solid black",
boxSizing: "content-box", background: `url(${InGameBackground})`,
height: "calc( ( 99vh - 60px ) / 2)", backgroundPosition: "center",
overflowY: "auto" backgroundSize: "100%",
boxSizing: "border-box",
height: "50%",
overflowY: "auto",
color: "white"
} }> } }>
<div>Current Inventory</div> <TitledList title={`Visible Inventory (Count=${simulationState.inventoryMCount})`}>
<ItemList {...listProps}/> <ItemList slots={simulationState.displayablePouch.getDisplayedSlots()}/>
</TitledList>
</div> </div>
</>} </>}
</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>
)
});