diff --git a/src/App.css b/src/App.css index 3974024..974ba49 100644 --- a/src/App.css +++ b/src/App.css @@ -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; } @@ -80,4 +99,22 @@ h1 { h3.ListHeader { margin: 0; padding: 10px; -} \ No newline at end of file +} + +h4.Reference { + margin: 0; + color: #cccccc; +} + +p.Reference { + padding-left: 20px; +} + +p.Example { + color:#eeee00; +} + +h3.Reference { + margin-top: 30px; + margin-bottom: 0 +} diff --git a/src/App.tsx b/src/App.tsx index 3f60a7c..c9eadc0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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("#simulation"); const [overlaySave, setOverlaySave] = useState(false); const [commands, setCommands] = useState(getDefaultCommands()); + const [selectedSaveName, setSelectedSaveName] = useState(""); const [displayIndex, setDisplayIndex] = useState(0); const [contextMenuX, setContextMenuX] = useState(0); const [contextMenuY, setContextMenuY] = useState(0); @@ -86,20 +46,36 @@ export const App: React.FC = () => { const uploadRef = useRef(null); const contextMenuRef = useRef(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"){ - 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"){ setDisplayIndex(Math.max(0, displayIndex-1)); } @@ -143,8 +119,12 @@ export const App: React.FC = () => { @@ -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", }}> -
    - {}} + { + displayIndex >=0 && displayIndex < simulationStates.length && +
      + { + !!simulationStates[displayIndex].getManualSave() && + { + setSelectedSaveName(""); + }} comment={false} + isSelected={selectedSaveName===""} + > Manual Save - {}} + } + + { + Object.entries(simulationStates[displayIndex].getNamedSaves()).map(([name, _gamedata])=>( + { + setSelectedSaveName(name); + }} comment={false} + isSelected={selectedSaveName===name} > - Auto Save 1 - - {}} - comment={false} - > - Auto Save 2 - - {}} - comment={false} - > - Auto Save 3 - - {}} - comment={false} - > - Auto Save 4 - - {}} - comment={false} - > - Auto Save 5 + {name} + )) + }
    + } +
    @@ -215,7 +190,13 @@ export const App: React.FC = () => { { commands.map((c,i)=> 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 = () => {
    -
    - - - - + { + page === "#simulation" && <> +
    + { + (displayIndex >= 0 && displayIndex < commands.length) ? + + { + selectedSaveName === "" && !!simulationStates[displayIndex].getManualSave() && + + } + { + selectedSaveName !== "" && !!simulationStates[displayIndex].getNamedSaves()[selectedSaveName] && + + } + + : + + + + + } + + + +
    +
    + {displayIndex >= 0 && displayIndex < commands.length && + { + const arrCopy = [...commands]; + arrCopy[displayIndex] = c; + setCommands(arrCopy); + }} + /> + }
    -
    - {displayIndex >= 0 && displayIndex < commands.length && - { - const arrCopy = [...commands]; - arrCopy[displayIndex] = c; - setCommands(arrCopy); - }} - /> - } -
    + + } + { + page === "#reference" && + }
    diff --git a/src/assets/Background.png b/src/assets/Background.png new file mode 100644 index 0000000..605800d Binary files /dev/null and b/src/assets/Background.png differ diff --git a/src/assets/Crash.png b/src/assets/Crash.png new file mode 100644 index 0000000..ac5657d Binary files /dev/null and b/src/assets/Crash.png differ diff --git a/src/assets/InGame.png b/src/assets/InGame.png new file mode 100644 index 0000000..d743167 Binary files /dev/null and b/src/assets/InGame.png differ diff --git a/src/components/ItemList.tsx b/src/components/ItemList.tsx index e91c07c..af52e7d 100644 --- a/src/components/ItemList.tsx +++ b/src/components/ItemList.tsx @@ -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 = ({items, numBroken, isSave}) => { +export const ItemList: React.FC = ({slots}) => { return <> { - items.map(({image, count, isEquipped}, i)=>{ - const broken = i+numBroken >= items.length; - return ; + slots.map((slot, i)=>{ + return ; }) } ; diff --git a/src/components/ItemSlot.tsx b/src/components/ItemSlot.tsx index 22110c4..dae35df 100644 --- a/src/components/ItemSlot.tsx +++ b/src/components/ItemSlot.tsx @@ -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 = ({image, count, isBroken, isSave, isEquipped})=>{ +export const ItemSlot: React.FC = ({slot: {image, count, isBrokenSlot, isEquipped, displayCount}})=>{ return ( - - + + { - count > 0 && + displayCount && x{count} } @@ -23,8 +23,12 @@ export const ItemSlot: React.FC = ({image, count, isBroken, isSav export const DoubleItemSlot: React.FC<{first?: ItemSlotProps, second?: ItemSlotProps}> = ({first, second})=>{ return ( - {first ? :
    } - {second ? :
    } +
    + {first && } +
    +
    + {second && } +
    ); -}; \ No newline at end of file +}; diff --git a/src/components/TitledList.tsx b/src/components/TitledList.tsx index 52d3c11..f45694f 100644 --- a/src/components/TitledList.tsx +++ b/src/components/TitledList.tsx @@ -8,9 +8,9 @@ export const TitledList: React.FC = ({title, children}) => { return ( <>

    {title}

    @@ -22,4 +22,4 @@ export const TitledList: React.FC = ({title, children}) => {
    ); -} \ No newline at end of file +} diff --git a/src/core/Command.ts b/src/core/Command.ts index b11cd83..3c4ac33 100644 --- a/src/core/Command.ts +++ b/src/core/Command.ts @@ -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{ - // 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 { diff --git a/src/core/DisplayableInventory.ts b/src/core/DisplayableInventory.ts new file mode 100644 index 0000000..41fd330 --- /dev/null +++ b/src/core/DisplayableInventory.ts @@ -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 + } +} diff --git a/src/core/GameData.ts b/src/core/GameData.ts new file mode 100644 index 0000000..7a1e395 --- /dev/null +++ b/src/core/GameData.ts @@ -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)); + } +} diff --git a/src/core/Inventory.ts b/src/core/Inventory.ts index 739d0e0..6f262fc 100644 --- a/src/core/Inventory.ts +++ b/src/core/Inventory.ts @@ -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; - } + // 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 getSlots(): Slots { + // return this.slots; + // } - public getSavedSlots(): Slots { - return this.savedSlots; - } + // public getSavedSlots(): Slots { + // return this.savedSlots; + // } - public getNumBroken(): number { - return this.numBroken; - } + // public getNumBroken(): number { + // return this.numBroken; + // } - public isInaccurate(): boolean { - return this.inaccurate; - } + // public isInaccurate(): boolean { + // return this.inaccurate; + // } - public getTurnedInOrbs(): number { - return this.turnedInOrbs; - } + // public getTurnedInOrbs(): number { + // return this.turnedInOrbs; + // } - public rawInit(stacks: ItemStack[]) { - this.slots = new Slots(stacks); - } + // 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 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 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 addBrokenSlots(num: number) { + // this.numBroken+=num; + // } - public setTag(name: string){ - this.namedSlots[name] = this.savedSlots.deepClone(); - } + // 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 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=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 { + // // 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 { - // } - ItemTypes.forEach(type=>{ - this.slots.addSlotsToEnd(dupeMap[type]); - this.slots.addSlotsToEnd(this.savedSlots.getByType(type).deepClone()); - }); + // // } + // 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; - } + // 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 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); - } + // 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; - } + // 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 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 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 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 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; - } + // public shootArrow(item: Item, count: number){ + // this.slots.shoot(item, count); + // this.isAltered=true; + // } } diff --git a/src/core/Item.ts b/src/core/Item.ts index 4793168..d928ecb 100644 --- a/src/core/Item.ts +++ b/src/core/Item.ts @@ -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, diff --git a/src/core/Parser.ts b/src/core/Parser.ts index 6059ef8..f6bf5dd 100644 --- a/src/core/Parser.ts +++ b/src/core/Parser.ts @@ -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,130 +39,135 @@ 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] === "Reload"){ + return new CommandReload(tokens[1]); } - 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(); - } - // break + // break if (tokens.length > 2 && tokens[0] === "Break" && tokens[2]=== "Slots" ){ const slots = parseInt(tokens[1]); 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; }; diff --git a/src/core/SimulationState.ts b/src/core/SimulationState.ts new file mode 100644 index 0000000..9d8ce06 --- /dev/null +++ b/src/core/SimulationState.ts @@ -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 - save to a auto save slot +// Reload - reload hard save +// Reload - reload a named auto save +// Use - 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 , X can be omitted and default to 1 +// Get/Add/Cook/Pickup X Y ... +// Remove/Drop/Sell/Eat X From Slot Y, X can be omitted and default to 1 +// Remove/Drop/Sell/Eat X Y ... +// D&P X , drop and pick up (to sort) +// Equip (In Slot X) +// Unequip (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 diff --git a/src/core/Slots.ts b/src/core/Slots.ts index 5ae618e..2d1572f 100644 --- a/src/core/Slots.ts +++ b/src/core/Slots.ts @@ -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]; - } - 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).typeitemToItemData(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{ - return itemToItemData(a.item).sortOrder - itemToItemData(b.item).sortOrder; + 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; + } + 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 0;i++){ - if(this.internalSlots[i].item === item){ - if(scount>0); - } - public add(item: Item, count: number) { - let added = false; + 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).typeitemToItemData(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{ + // 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 0;i++){ + // if(this.internalSlots[i].item === item){ + // if(scount>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 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); - 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}); + // 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{ + 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{ - 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{ + 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 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; + // } + + // 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}); + // } - } + // }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;iitemToItemData(s.item).type === type); - for(let i = 0; iitemToItemData(s.item).type === type); + // for(let i = 0; iitemStackToDisplayableSlot(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; + } +} diff --git a/src/data/mergeSort.ts b/src/data/mergeSort.ts new file mode 100644 index 0000000..4fdea6a --- /dev/null +++ b/src/data/mergeSort.ts @@ -0,0 +1,14 @@ +//https://medium.com/@fsufitch/is-javascript-array-sort-stable-46b90822543f +export const stableSort = (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; ivoid } -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 = ({command,orbs,editCommand,displayIndex, slots, savedSlots, numBroken, overlaySave})=>{ +export const DisplayPane: React.FC = ({command,editCommand,displayIndex,simulationState, overlaySave})=>{ const [commandString, setCommandString] = useState(""); const [hasError, setHasError] = useState(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 = ({command,orbs,editComman }, [command, displayIndex]); return
    - { @@ -79,45 +83,55 @@ export const DisplayPane: React.FC = ({command,orbs,editComman setHasError(true); } }}> - - Orbs: {orbs} - +
    +
    {overlaySave ?
    -
    Save / Current
    -
    + + { (()=>{ const doubleSlots: JSX.Element[] = []; - for(let i=0;i=slots.length-numBroken, isSave:false}} + first={{slot: gameDataSlots[i]}} + second={{slot: inventorySlots[i]}} />); } - if(savedSlots.length>slots.length){ - for(let i=slots.length;igameDataSlots.length){ + for(let i=inventorySlots.length;i); } - }else if(slots.length > savedSlots.length){ - for(let i=savedSlots.length;i=slots.length-numBroken, isSave:false}} + }else if(inventorySlots.length > gameDataSlots.length){ + for(let i=gameDataSlots.length;i); } } return doubleSlots; })() } -
    + + +
    @@ -125,25 +139,37 @@ export const DisplayPane: React.FC = ({command,orbs,editComman
    -
    Inventory of Save
    - + + + +
    -
    Current Inventory
    - + + + + +
    } +
    +
    ; }; diff --git a/src/surfaces/ReferencePage.tsx b/src/surfaces/ReferencePage.tsx new file mode 100644 index 0000000..5ab5322 --- /dev/null +++ b/src/surfaces/ReferencePage.tsx @@ -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 ( +
    + +
    +

    Commands

    +

    Initialize X item1 Y item2 Z item3 ...

    +

    Used for initializing inventory before simulation

    +

    + 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 +

    +

    + If you specify count > 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 +

    +

    + Note that this will not clear saves. You can use this command to initialize multiple saves +

    +

    Example: Initialize 1 Apple 2 Axe 3 Slate 4 SpiritOrb

    + +

    Save / Save As NAME

    +

    Simulates a hard save or auto save action

    +

    + 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. +

    + +

    Example 1: Save

    +

    Example 2: Save As MySave

    +

    + 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 +

    + +

    Reload (NAME)

    +

    Simulates reloading a save

    +

    + 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 +

    +

    + 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. +

    + +

    Example 1: Reload

    +

    Example 2: Reload MySave

    + +

    Use NAME

    +

    (Deprecated) Specify which save to load on the subsequent reload

    +

    + This command is only for backward compatibility. Use "Reload" instead +

    +

    + Specify the save named NAME to be reloaded on the next "Reload" command +

    + +

    Example: Use MySave

    + +

    Break X Slots

    +

    Simulate making X broken slots with hold smuggle glitch

    +

    + Decrease inventory Count by X +

    +

    + This command does not automatically simulate the hold smuggle and sell process. + It just changes count (i.e. make broken slots) with magic. +

    + +

    Example: Break 4 Slots

    +
    + +
    +
    + ) +});