diff --git a/src/App.css b/src/App.css index 2f1a372..c2bfa7e 100644 --- a/src/App.css +++ b/src/App.css @@ -24,6 +24,10 @@ h1 { font-size: 10pt; } +.CommandItemInvalid { + color: red; +} + .CommandItem { padding: 2px 2px 2px 10px; cursor: pointer; @@ -110,7 +114,7 @@ p.Reference { padding-left: 20px; } -p.Example { +.Example { color:#eeee00; } @@ -123,3 +127,65 @@ h3.Reference2 { margin-top: 0; margin-bottom: 0; } + +button.MainButton { + font-family: CalamitySans; + font-weight: bold; + display: inline-block; + padding: 5px; + width: 100px; + margin: 5px; + background-color: #00000099; + color: #ffffff; + border-radius: 0; + border: 2px solid #888888; +} + +button.MainButton:hover { + border-color: #eeeeee; + box-shadow: 0 0 5px #eeeeee; +} + +button.MainButton:active { + background-color: #888888; +} + +.FullWidth { + width: 100% !important; +} + +div.OtherPage { + height: 100%; + width: 100%; + color: #ffffff; +} + +div.OtherPageContent{ + padding: 10px; +} + +.MainInput { + display: block; + width: 100%; + background-color: #00000099; + color: #ffffff; + border-radius: 0; + border: 2px solid #888888; + margin-bottom: 5px; +} + +input.MainInput { + font-size: 14pt; + font-family: CalamitySans; +} + +textarea { + resize: none; + height: 300px; +} + +.MainInput:focus-visible { + outline: none; + border-color: #eeeeee; + box-shadow: 0 0 5px #eeeeee; +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index c9eadc0..9614a4d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,49 +1,46 @@ -import { Command, CommandNothing } from "core/Command"; -import { Inventory } from "core/Inventory"; +import { Command, CommandNop } from "core/Command"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import "./App.css"; import { CommandItem } from "./components/CommandItem"; 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"; +import { OptionPage } from "surfaces/OptionPage"; const getDefaultCommands = (): Command[]=>{ const encoded = localStorage.getItem("HDS.CurrentCommandsText"); if(encoded){ const lines = encoded.split("\n"); - return lines.map(l=>parseCommand(l)).filter(c=>c) as Command[]; + return lines.map(parseCommand); } return [ parseCommand("Get 5 Diamond 1 Slate 1 Glider 4 SpiritOrb"), parseCommand("Save"), + parseCommand("# Magically break 4 slots"), parseCommand("Break 4 Slots"), parseCommand("Reload"), parseCommand("Save"), parseCommand("Reload"), - ] as Command[];; + ] as Command[]; }; - export const App: React.FC = () => { const [page, setPageInState] = useState("#simulation"); - const [overlaySave, setOverlaySave] = useState(false); + // Option States + const [interlaceInventory, setInterlaceInventory] = 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); - const [contextMenuShowing, setContextMenuShowing] = useState(false); const [contextIndex, setContextIndex] = useState(-1); - const uploadRef = useRef(null); const contextMenuRef = useRef(null); // compute props const simulationStates = useMemo(()=>{ @@ -55,6 +52,9 @@ export const App: React.FC = () => { }); return simulationStates; }, [commands]); + const commandText = useMemo(()=>{ + return commands.map(c=>c.getDisplayString()).join("\n"); + }, [commands]); const setPage = useCallback((hash: string)=>{ window.location.hash = hash; @@ -68,16 +68,25 @@ export const App: React.FC = () => { useEffect(()=>{ window.onkeydown=(e)=>{ if(e.code==="ArrowDown"){ - if(displayIndex===commands.length-1){ + let nextCommandIndex = displayIndex+1; + while(nextCommandIndex=0 && !commands[nextCommandIndex].isValid()){ + nextCommandIndex--; + } + setDisplayIndex(Math.max(0, nextCommandIndex)); } }; }, [commands, displayIndex]); @@ -92,40 +101,32 @@ export const App: React.FC = () => { }, [commands]); useEffect(()=>{ - if(contextMenuRef.current && contextMenuShowing){ + if(contextIndex < 0 || contextIndex >= commands.length){ + setContextIndex(-1); + }else if(contextMenuRef.current){ const rect = contextMenuRef.current.getBoundingClientRect(); if (rect.bottom > window.innerHeight){ setContextMenuY(contextMenuY-rect.height); } } - }, [contextMenuRef, contextMenuShowing]); + }, [contextMenuRef, contextIndex, commands]); return (
- { - const files = e.target.files; - if(files?.length && files[0]){ - const file = files[0]; - file.text().then(text=>{ - const lines = text.split("\n"); - const parsedCommands: Command[] = lines.map(l=>parseCommand(l)).filter(c=>c) as Command[]; - setDisplayIndex(0); - setContextIndex(-1); - setContextMenuShowing(false); - setCommands(parsedCommands); - }); - } - }}/> +
{ { !!simulationStates[displayIndex].getManualSave() && { - setSelectedSaveName(""); - }} - comment={false} - isSelected={selectedSaveName===""} + onClick={()=>{ + setSelectedSaveName(""); + setPage("#simulation"); + }} + useListItem + isSelected={selectedSaveName===""} - > + > Manual Save - + } - { - Object.entries(simulationStates[displayIndex].getNamedSaves()).map(([name, _gamedata])=>( - { - setSelectedSaveName(name); - }} - comment={false} - isSelected={selectedSaveName===name} - > - {name} - - )) - } - + { + Object.entries(simulationStates[displayIndex].getNamedSaves()).map(([name, _gamedata])=> + { + setSelectedSaveName(name); + setPage("#simulation"); + }} + isSelected={selectedSaveName===name} + useListItem + > + {name} + + ) + } + } @@ -185,55 +188,46 @@ export const App: React.FC = () => { }}> -
    - { - commands.map((c,i)=> - { - setDisplayIndex(i); - const inputField = document.getElementById("CommandInputField"); - if(inputField){ - inputField.focus(); - } - }} - onContextMenu={(x,y)=>{ - setContextIndex(i); - setContextMenuX(x); - setContextMenuY(y); - setContextMenuShowing(true); - }} - key={i} - isSelected={displayIndex===i} - isContextSelected={contextIndex===i} - comment={c.getDisplayString().startsWith("#")} - > - {c.getDisplayString()} - - ) - } - { - const arrCopy = [...commands]; - arrCopy.push(new CommandNothing()); - setCommands(arrCopy); - }} onContextMenu={()=>{ - const arrCopy = [...commands]; - arrCopy.push(new CommandNothing()); - setCommands(arrCopy); - }}>(new) - { - setContextIndex(-1); - setContextMenuX(x); - setContextMenuY(y); - setContextMenuShowing(true); - }} onContextMenu={(x,y)=>{ - setContextIndex(-1); - setContextMenuX(x); - setContextMenuY(y); - setContextMenuShowing(true); - }}>(options) +
      + { + commands.map((c,i)=> + { + setDisplayIndex(i); + setPage("#simulation"); + const inputField = document.getElementById("CommandInputField"); + if(inputField){ + inputField.focus(); + } + }} + onContextMenu={(x,y)=>{ + setContextIndex(i); + setContextMenuX(x); + setContextMenuY(y); + }} + key={i} + isSelected={displayIndex===i} + isContextSelected={contextIndex===i} + isComment={c.getDisplayString().startsWith("#")} + useListItem={!c.getDisplayString().startsWith("#")} + isInvalid={!c.isValid()} + > + {c.getDisplayString()} + + ) + } + { + const arrCopy = [...commands]; + arrCopy.push(new CommandNop("")); + setCommands(arrCopy); + }} onContextMenu={()=>{ + const arrCopy = [...commands]; + arrCopy.push(new CommandNop("")); + setCommands(arrCopy); + }}>(new) -
    +
@@ -249,47 +243,51 @@ export const App: React.FC = () => { }}> { page === "#simulation" && <> -
- { - (displayIndex >= 0 && displayIndex < commands.length) ? - +
{ - selectedSaveName === "" && !!simulationStates[displayIndex].getManualSave() && - + displayIndex >= 0 && displayIndex < commands.length ? + + { + (()=>{ + if (selectedSaveName === ""){ + const manualSave = simulationStates[displayIndex].getManualSave(); + if(manualSave){ + return ; + } + }else if(selectedSaveName){ + const namedSaves = simulationStates[displayIndex].getNamedSaves(); + if(selectedSaveName in namedSaves){ + const save = namedSaves[selectedSaveName]; + return ; + } + } + return null; + })() + } + + : + + + } - { - selectedSaveName !== "" && !!simulationStates[displayIndex].getNamedSaves()[selectedSaveName] && - - } - - : - - - - } - - - -
-
- {displayIndex >= 0 && displayIndex < commands.length && +
+
+ {displayIndex >= 0 && displayIndex < commands.length && { }} /> - } -
- + } +
+ } { page === "#reference" && } + { + page === "#options" && + { + if(value !== commandText){ + const commands = value.split("\n").map(parseCommand); + setCommands(commands); + } + }} + /> + }
- - - - {/*
- - -
-
*/} - {/*
- - - -
*/} - - - {/*
*/} - - { - contextMenuShowing &&
= 0 && contextIndex < commands.length &&
{ - setContextMenuShowing(false); setContextIndex(-1); }} onContextMenu={(e)=>{ - setContextMenuShowing(false); setContextIndex(-1); e.preventDefault(); }}> @@ -367,93 +347,33 @@ export const App: React.FC = () => { listStyleType: "none", paddingInlineStart: 0 }}> - {contextIndex >= 0 ? <> - { + + { + const arrCopy = [...commands]; + arrCopy.splice(contextIndex, 0, new CommandNop("")); + setCommands(arrCopy); + setContextIndex(-1); + }}>Insert Above + { + if(contextIndex > 0){ const arrCopy = [...commands]; - arrCopy.splice(contextIndex, 0, new CommandNothing()); + const temp = arrCopy[contextIndex]; + arrCopy[contextIndex] = arrCopy[contextIndex-1]; + arrCopy[contextIndex-1] = temp; setCommands(arrCopy); - setContextMenuShowing(false); setContextIndex(-1); - }}>Insert Above - { - if(contextIndex > 0){ - const arrCopy = [...commands]; - const temp = arrCopy[contextIndex]; - arrCopy[contextIndex] = arrCopy[contextIndex-1]; - arrCopy[contextIndex-1] = temp; - setCommands(arrCopy); - setContextMenuShowing(false); - setContextIndex(-1); - } + } - }}>Move Up - { - if(confirm("Delete?")){ - setCommands(commands.filter((_,i)=>i!==contextIndex)); - if(displayIndex >= commands.length){ - setDisplayIndex(commands.length-1); - } - setContextMenuShowing(false); - setContextIndex(-1); + }}>Move Up + { + if(confirm("Delete?")){ + setCommands(commands.filter((_,i)=>i!==contextIndex)); + if(displayIndex >= commands.length){ + setDisplayIndex(commands.length-1); } - }}>Delete : - <> - { - setOverlaySave(!overlaySave); - }}>Toggle Save Overlay - { - if(uploadRef.current){ - uploadRef.current.click(); - } - }}>Import - { - const lines = commands.map(c=>c.getDisplayString()); - const text = lines.join("\n"); - saveAs(text, "dupe.txt"); - }}>Export - { - alert(`Available Commands: -Initialize X Item1 Y Item2 Z Item3 ... -Break X Slots - add X broken slots -Save -Reload -Sort Key/Material - sort key items or material -Get/Add/Cook/Pickup X ITEM -Remove/Drop/Sell X ITEM From Slot Y -Remove/Sell/Eat MEAL From Slot X - -Limitations: -Inventory corruption is not implemented yet - -`); - alert(`Available Items: -Slate -Glider -SpiritOrb -SpeedFood -Lotus -SilentPrincess -Honey -Acorn -FaroshScale -FaroshClaw -FaroshHorn -HeartyBass -Beetle -Opal -Diamond -Tail -Spring -Shaft -Core -Wood -Weapon - `); - }}>Reference - - - - } + setContextIndex(-1); + } + }}>Delete
diff --git a/src/assets/img/ZoraArmor.png b/src/assets/img/ZoraArmor.png new file mode 100644 index 0000000..3f33f43 Binary files /dev/null and b/src/assets/img/ZoraArmor.png differ diff --git a/src/components/CommandItem.tsx b/src/components/CommandItem.tsx index 8c7fcea..1b00137 100644 --- a/src/components/CommandItem.tsx +++ b/src/components/CommandItem.tsx @@ -1,29 +1,54 @@ import clsx from "clsx"; -import { PropsWithChildren } from "react"; +import React, { PropsWithChildren, useCallback } from "react"; type CommandItemProps = PropsWithChildren<{ + useListItem?: boolean, isSelected?: boolean, - comment?:boolean, + isComment?: boolean, + isInvalid?: boolean, isContextSelected?: boolean, onClick: (x: number, y: number)=>void, onContextMenu?: (x: number, y: number)=>void }>; -export const CommandItem: React.FC = ({isSelected, isContextSelected, comment,children, onClick, onContextMenu}) => { - if(comment){ - return
{children}
+export const CommandItem: React.FC = ({ + useListItem, + isSelected, + isContextSelected, + isComment, + isInvalid, + onClick, + onContextMenu, + children +}) => { + const className = clsx( + "CommandItem", + isSelected && "CommandItemSelected", + isContextSelected && "CommandItemContextSelected", + isComment && "CommandItemComment", + isInvalid && !isComment && "CommandItemInvalid", + ); + + const clickHandler = useCallback((e: React.MouseEvent)=>{ + onClick(e.clientX, e.clientY); + }, [onClick]); + const contextMenuHandler = useCallback((e: React.MouseEvent)=>{ + if(onContextMenu){ + onContextMenu(e.clientX,e.clientY); + e.preventDefault(); + } + }, [onContextMenu]); + + if(!useListItem){ + return ( +
+ {children}  +
+ ); } - return
  • { - onClick(e.clientX, e.clientY); - }} - onContextMenu={(e)=>{ - if(onContextMenu){ - onContextMenu(e.clientX,e.clientY); - e.preventDefault(); - } - - }} - >{children} 
  • ; + return ( +
  • + {children}  +
  • + ); }; diff --git a/src/components/ItemSlot.tsx b/src/components/ItemSlot.tsx index dae35df..51327c2 100644 --- a/src/components/ItemSlot.tsx +++ b/src/components/ItemSlot.tsx @@ -2,7 +2,6 @@ import clsx from "clsx"; import { DisplayableSlot } from "core/DisplayableInventory"; import Background from "assets/Background.png"; - type ItemSlotProps = { slot: DisplayableSlot }; diff --git a/src/components/TitledList.tsx b/src/components/TitledList.tsx index 3725c61..9ccfaf2 100644 --- a/src/components/TitledList.tsx +++ b/src/components/TitledList.tsx @@ -1,25 +1,25 @@ -import { PropsWithChildren } from "react" +import { PropsWithChildren } from "react"; type TitledListProps = PropsWithChildren<{ title: string }> export const TitledList: React.FC = ({title, children}) => { - return ( - <> -

    - {title} -

    -
    - {children} -
    - - ); -} + return ( + <> +

    + {title} +

    +
    + {children} +
    + + ); +}; diff --git a/src/core/Command.ts b/src/core/Command.ts index e807557..167df5a 100644 --- a/src/core/Command.ts +++ b/src/core/Command.ts @@ -1,27 +1,29 @@ -import { Inventory } from "./Inventory"; -import { Item, ItemStack, itemToArrowType } from "./Item"; +import { Item, ItemStack } from "./Item"; import { SimulationState } from "./SimulationState"; export interface Command { + isValid(): boolean, execute(state: SimulationState): void, getDisplayString(): string, } -export class CommandNothing implements Command { - - execute(_state: Inventory): void { +class CommandImpl implements Command{ + isValid(): boolean { + return true; + } + execute(_state: SimulationState): void { // nothing } getDisplayString(): string { - return ""; + throw new Error("Method not implemented."); } - } -export class CommandInitialize implements Command { +export class CommandInitialize extends CommandImpl { private stacks: ItemStack[]; constructor(stacks: ItemStack[]){ + super(); this.stacks = stacks; } @@ -34,7 +36,7 @@ export class CommandInitialize implements Command { } -export class CommandSave implements Command { +export class CommandSave extends CommandImpl { public execute(state: SimulationState): void { state.save(); @@ -44,9 +46,10 @@ export class CommandSave implements Command { } } -export class CommandSaveAs implements Command { +export class CommandSaveAs extends CommandImpl { private name: string; constructor(name: string){ + super(); this.name = name; } public execute(state: SimulationState): void { @@ -57,9 +60,10 @@ export class CommandSaveAs implements Command { } } -export class CommandReload implements Command { +export class CommandReload extends CommandImpl { private name?: string; constructor(name?: string){ + super(); this.name = name; } public execute(state: SimulationState): void { @@ -70,10 +74,10 @@ export class CommandReload implements Command { } } - -export class CommandUse implements Command { +export class CommandUse extends CommandImpl{ private name: string; constructor(name: string){ + super(); this.name = name; } public execute(state: SimulationState): void { @@ -82,12 +86,16 @@ export class CommandUse implements Command { public getDisplayString(): string { return `Use ${this.name}`; } + public isValid(): boolean { + return false; // this command is deprecated + } } -export class CommandBreakSlots implements Command { +export class CommandBreakSlots extends CommandImpl { private numToBreak: number; constructor(numToBreak: number){ + super(); this.numToBreak = numToBreak; } @@ -99,11 +107,12 @@ export class CommandBreakSlots implements Command { } } -export class CommandAdd implements Command { +export class CommandAdd extends CommandImpl { private verb: string; private count: number; private item: Item; constructor(verb: string, count: number, item: Item){ + super(); this.verb = verb; this.count = count; this.item = item; @@ -117,10 +126,11 @@ export class CommandAdd implements Command { } } -export class CommandAddWithoutCount implements Command { +export class CommandAddWithoutCount extends CommandImpl { private verb: string; private item: Item; constructor(verb: string, item: Item){ + super(); this.verb = verb; this.item = item; } @@ -133,10 +143,11 @@ export class CommandAddWithoutCount implements Command { } } -export class CommandAddMultiple implements Command { +export class CommandAddMultiple extends CommandImpl { private verb: string; private stacks: ItemStack[]; constructor(verb: string, stacks: ItemStack[]){ + super(); this.verb = verb; this.stacks = stacks; } @@ -150,13 +161,14 @@ export class CommandAddMultiple implements Command { } } -export class CommandRemove implements Command { +export class CommandRemove extends CommandImpl { private verb: string; private count: number; private item: Item; private slot: number; private noSlot: boolean; constructor(verb: string, count: number, item: Item, slot: number, noSlot: boolean){ + super(); this.verb = verb; this.count = count; this.item = item; @@ -172,12 +184,13 @@ export class CommandRemove implements Command { } } -export class CommandRemoveWithoutCount implements Command { +export class CommandRemoveWithoutCount extends CommandImpl { private verb: string; private item: Item; private slot: number; private noSlot: boolean; constructor(verb: string, item: Item, slot: number, noSlot: boolean){ + super(); this.verb = verb; this.item = item; this.slot = slot; @@ -192,10 +205,11 @@ export class CommandRemoveWithoutCount implements Command { } } -export class CommandRemoveMultiple implements Command { +export class CommandRemoveMultiple extends CommandImpl { private verb: string; private stacks: ItemStack[]; constructor(verb: string, stacks: ItemStack[]){ + super(); this.verb = verb; this.stacks = stacks; } @@ -215,30 +229,33 @@ const joinItemStackString = (initial: string, stacks: ItemStack[]): string => { parts.push(item); }); return parts.join(" "); -} +}; -export class CommandDaP implements Command { - private count: number; - private item: Item; +export class CommandDaP extends CommandImpl { + private stacks: ItemStack[]; - constructor(count: number, item: Item,){ - this.count = count; - this.item = item; + constructor(stacks: ItemStack[]){ + super(); + this.stacks = stacks; } public execute(state: SimulationState): void { - state.remove(this.item, this.count, 0); - state.obtain(this.item, this.count); + this.stacks.forEach(({item,count})=>{ + state.remove(item, count, 0); + state.obtain(item, count); + }); + } public getDisplayString(): string { - return `D&P ${this.count} ${this.item}`; + return joinItemStackString("D&P", this.stacks); } } -export class CommandEquip implements Command { +export class CommandEquip extends CommandImpl { private item: Item; private slot: number; private noSlot: boolean; constructor(item: Item, slot: number, noSlot: boolean){ + super(); this.item = item; this.slot = slot; this.noSlot = noSlot; @@ -253,11 +270,12 @@ export class CommandEquip implements Command { } } -export class CommandUnequip implements Command { +export class CommandUnequip extends CommandImpl { private item: Item; private slot: number; private noSlot: boolean; constructor(item: Item, slot: number, noSlot: boolean){ + super(); this.item = item; this.slot = slot; this.noSlot = noSlot; @@ -272,9 +290,10 @@ export class CommandUnequip implements Command { } } -export class CommandShootArrow implements Command { - private count: number +export class CommandShootArrow extends CommandImpl { + private count: number; constructor(count: number){ + super(); this.count = count; } @@ -286,85 +305,94 @@ export class CommandShootArrow implements Command { } } +export class CommandCloseGame extends CommandImpl { + public execute(state: SimulationState): void { + state.closeGame(); + } + public getDisplayString(): string { + return "Close Game"; + } +} -// 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 CommandSync extends CommandImpl { + private actionString: string; + constructor(actionString: string){ + super(); + this.actionString = actionString; + } + public execute(state: SimulationState): void { + state.syncGameDataWithPouch(); + } + public getDisplayString(): string { + return this.actionString; + } +} +export class CommandEventide extends CommandImpl { + private enter: boolean; + constructor(enter: boolean){ + super(); + this.enter = enter; + } + public execute(state: SimulationState): void { + state.setEventide(this.enter); + } + public getDisplayString(): string { + return `${this.enter? "Enter":"Exit"} Eventide`; + } +} - - -// 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; +export class CommandNop extends CommandImpl { + private text: string; + constructor(text: string){ + super(); + this.text = text; + } + public isValid(): boolean { + return false; } public execute(_state: SimulationState): void { // nothing } public getDisplayString(): string { - return `# ${this.name}`; + return this.text; } } -// 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 CommandSortKey extends CommandImpl { + 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(_state: SimulationState): void { + // wip + } + 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"; -// } -// } +export class CommandSortMaterial extends CommandImpl { + 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(_state: SimulationState): void { + // wip + } + public getDisplayString(): string { + return "Sort Material"; + } +} diff --git a/src/core/DisplayableInventory.ts b/src/core/DisplayableInventory.ts index 41fd330..09d4c88 100644 --- a/src/core/DisplayableInventory.ts +++ b/src/core/DisplayableInventory.ts @@ -1,4 +1,4 @@ -import { Item, ItemStack, itemToItemData, ItemType } from "./Item" +import { ItemStack, itemToItemData, ItemType } from "./Item"; export type DisplayableSlot = { image: string, @@ -13,12 +13,13 @@ export interface DisplayableInventory { } 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 - } -} + const data = itemToItemData(item); + return { + image: data.image, + // for unstackable items (meal/key items) display count if count > 1, even if it's unstackable + displayCount: data.stackable ? data.type === ItemType.Arrow || count > 0 : count > 1, + count, + isEquipped: equipped, + isBrokenSlot + }; +}; diff --git a/src/core/GameData.ts b/src/core/GameData.ts index 545d36e..dbb9180 100644 --- a/src/core/GameData.ts +++ b/src/core/GameData.ts @@ -1,5 +1,4 @@ import { DisplayableInventory, DisplayableSlot, itemStackToDisplayableSlot } from "./DisplayableInventory"; -import { Item, itemToItemData } from "./Item"; import { Slots } from "./Slots"; import { VisibleInventory } from "./VisibleInventory"; @@ -8,28 +7,28 @@ import { VisibleInventory } from "./VisibleInventory"; */ export class GameData implements DisplayableInventory { - private slots: Slots = new Slots([]); - constructor(slots: Slots){ - this.slots = slots; - } + private slots: Slots = new Slots([]); + constructor(slots: Slots){ + this.slots = slots; + } - public deepClone(): GameData { - return new GameData(this.slots.deepClone()); - } + public deepClone(): GameData { + return new GameData(this.slots.deepClone()); + } - public syncWith(pouch: VisibleInventory) { - this.slots = pouch.getSlots().deepClone(); - } + public syncWith(pouch: VisibleInventory) { + this.slots = pouch.getSlots().deepClone(); + } - public updateDurability(durability: number, slot: number){ - this.slots.corrupt(durability, slot); - } + public updateDurability(durability: number, slot: number){ + this.slots.corrupt(durability, slot); + } - public addAllToPouchOnReload(pouch: VisibleInventory) { - this.slots.getSlotsRef().forEach(stack=>pouch.addWhenReload(stack.item, stack.count, stack.equipped)); - } + 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)); - } + public getDisplayedSlots(): DisplayableSlot[] { + return this.slots.getSlotsRef().map(stack=>itemStackToDisplayableSlot(stack, false)); + } } diff --git a/src/core/Inventory.ts b/src/core/Inventory.ts deleted file mode 100644 index 6f262fc..0000000 --- a/src/core/Inventory.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { Item, ItemStack, itemToItemData, ItemType, ItemTypes } from "./Item"; -import { Slots } from "./Slots"; - -export class Inventory { - // private slots: Slots = new Slots([]); - // private savedSlots: Slots = new Slots([]); - // private namedSlots: {[name: string]: Slots} = {}; - // private numBroken = 0; - // private isInitialSort = false; - // private isAltered = true; - // private inaccurate = false; - // private turnedInOrbs = 0; - // public deepClone(): Inventory { - // const other = new Inventory(); - // other.slots = this.slots.deepClone(); - // other.savedSlots = this.savedSlots.deepClone(); - // other.numBroken = this.numBroken; - // other.isInitialSort = this.isInitialSort; - // other.isAltered = this.isAltered; - // other.inaccurate = this.inaccurate; - // other.turnedInOrbs = this.turnedInOrbs; - // other.namedSlots = {}; - // for(const name in this.namedSlots){ - // other.namedSlots[name] = this.namedSlots[name].deepClone(); - // } - // return other; - // } - - // public getSlots(): Slots { - // return this.slots; - // } - - // public getSavedSlots(): Slots { - // return this.savedSlots; - // } - - // public getNumBroken(): number { - // return this.numBroken; - // } - - // public isInaccurate(): boolean { - // return this.inaccurate; - // } - - // public getTurnedInOrbs(): number { - // return this.turnedInOrbs; - // } - - // public rawInit(stacks: ItemStack[]) { - // this.slots = new Slots(stacks); - // } - - // public init(stacks: ItemStack[]) { - // this.slots = new Slots([]); - // stacks.forEach(s=>{ - // this.slots.add(s.item, s.count); - // }); - // this.numBroken = 0; - // this.isInitialSort = false; - // this.isAltered = true; - // this.inaccurate = false; - // } - - // public closeGame() { - // this.numBroken = 0; - // this.isInitialSort = false; - // this.isAltered = true; - // this.inaccurate = false; - // this.slots = new Slots([]); - // } - - // public addBrokenSlots(num: number) { - // this.numBroken+=num; - // } - - // public setTag(name: string){ - // this.namedSlots[name] = this.savedSlots.deepClone(); - // } - - // public applyTag(name: string){ - // if(name in this.namedSlots){ - // this.savedSlots = this.namedSlots[name].deepClone(); - // }else{ - // this.savedSlots = new Slots([]); - // } - // } - - // public save() { - // if(this.isAltered){ - // this.savedSlots = this.slots.deepClone(); - // } - // // Inventory Corruption - // // get durability transfer slots - // const durabilityTransferSlots: number[] = []; - // const equippedWeapon = this.slots.getFirstEquippedSlotIndex(ItemType.Weapon); - // if(equippedWeapon>=0){ - // durabilityTransferSlots.push(equippedWeapon); - // } - // const equippedBow = this.slots.getFirstEquippedSlotIndex(ItemType.Bow); - // if(equippedBow>=0){ - // durabilityTransferSlots.push(equippedBow); - // } - // const equippedShield = this.slots.getFirstEquippedSlotIndex(ItemType.Shield); - // if(equippedShield>=0){ - // durabilityTransferSlots.push(equippedShield); - // } - // durabilityTransferSlots.forEach(s=>{ - // if(s { - - // // } - // ItemTypes.forEach(type=>{ - // this.slots.addSlotsToEnd(dupeMap[type]); - // this.slots.addSlotsToEnd(this.savedSlots.getByType(type).deepClone()); - // }); - - // this.slots.sortArrows(); - // this.isInitialSort = true; - // this.isAltered = false; - // } - - // public sortKey() { - // const nonKeyItems = this.slots.getBeforeType(ItemType.Key); - // const keyItems = this.slots.getByType(ItemType.Key); - // keyItems.sort(); - // nonKeyItems.addSlotsToEnd(keyItems); - // this.slots = nonKeyItems; - // this.isAltered=true; - // this.isInitialSort=false; - // } - - // public sortMaterial() { - // const beforeMaterial = this.slots.getBeforeType(ItemType.Material); - // const afterMaterial = this.slots.getAfterType(ItemType.Material); - // const materials = this.slots.getByType(ItemType.Material); - // if(this.isInitialSort){ - // // the materials in broken slots are not sorted - // const brokenSlots = Math.max(0, this.numBroken - afterMaterial.length); - // const noSortPart = materials.removeFromEnd(brokenSlots); - // materials.sort(); - // beforeMaterial.addSlotsToEnd(materials); - // beforeMaterial.addSlotsToEnd(noSortPart); - // beforeMaterial.addSlotsToEnd(afterMaterial); - // }else{ - // materials.sort(); - // beforeMaterial.addSlotsToEnd(materials); - // beforeMaterial.addSlotsToEnd(afterMaterial); - // } - - // this.slots = beforeMaterial; - // this.isInitialSort = false; - // this.isAltered=true; - // } - - // public remove(item: Item, count: number, slot: number) { - // this.slots.remove(item, count, slot); - // if(item===Item.SpiritOrb){ - // this.turnedInOrbs+=count; - // } - // this.isAltered=true; - // } - - // public add(item: Item, count: number) { - // this.slots.add(item, count); - // if(itemToItemData(item).type===ItemType.Arrow){ - // this.slots.sortArrows(); - // } - // this.isAltered=true; - // } - - // public equipEquipmentOrArrow(item: Item, slot: number) { - // this.slots.equip(item, slot); - // this.isAltered=true; - // } - - // public unequipEquipment(item: Item, slot: number){ - // this.slots.unequip(item, slot); - // this.isAltered=true; - // } - - // public shootArrow(item: Item, count: number){ - // this.slots.shoot(item, count); - // this.isAltered=true; - // } - -} diff --git a/src/core/Item.ts b/src/core/Item.ts index d928ecb..c2ca4c3 100644 --- a/src/core/Item.ts +++ b/src/core/Item.ts @@ -76,6 +76,7 @@ export enum Item { Fairy = "Fairy", MasterSword = "MasterSword", + ZoraArmor = "ZoraArmor", } type ItemData = { @@ -167,7 +168,7 @@ register(0x50, Item.Weapon, ItemType.Weapon, { }); register(0, Item.MasterSword, ItemType.Weapon, { stackable: false, -}) +}); register(0x60, Item.Bow, ItemType.Bow, { image: Images.ForestDwellerBow, @@ -184,6 +185,10 @@ register(0x80, Item.Shield, ItemType.Shield, { stackable: false }); +register(9, Item.ZoraArmor, ItemType.Armor, { + stackable: false +}); + //export const idToItemData = (id: number): ItemData => IdToData[id]; export const itemToItemData = (item: Item): ItemData => ItemToData[item] as ItemData; export const itemToArrowType = (item: Item): string => { @@ -193,3 +198,5 @@ export const itemToArrowType = (item: Item): string => { } return ""; }; + +export const getAllItems = (): string[] => Object.keys(ItemToData); diff --git a/src/core/Parser.ts b/src/core/Parser.ts index 4b7e4b9..de74340 100644 --- a/src/core/Parser.ts +++ b/src/core/Parser.ts @@ -4,11 +4,12 @@ import { CommandAddMultiple, CommandAddWithoutCount, CommandBreakSlots, - CommandComment, + CommandCloseGame, CommandDaP, CommandEquip, + CommandEventide, CommandInitialize, - CommandNothing, + CommandNop, CommandReload, CommandRemove, CommandRemoveMultiple, @@ -16,30 +17,32 @@ import { CommandSave, CommandSaveAs, CommandShootArrow, + CommandSortKey, + CommandSortMaterial, + CommandSync, CommandUnequip, CommandUse } from "./Command"; import { Item, ItemStack } from "./Item"; -export const parseCommand = (cmdString: string): Command | undefined => { +export const parseCommand = (cmdString: string): Command => { - if(cmdString.startsWith("# ")){ - return new CommandComment(cmdString.substring(2)); - } const tokens = cmdString.split(" ").filter(i=>i); if(tokens.length===0){ - return new CommandNothing(); + return new CommandNop(""); } // intialize if(tokens.length>1 && tokens[0] === "Initialize"){ const stacks = parseItemStacks(tokens, 1); - return stacks ? new CommandInitialize(stacks) : undefined; + if(stacks){ + return new CommandInitialize(stacks); + } } // Save/Reload if(tokens.length===1 && tokens[0] === "Save"){ return new CommandSave(); } - // // Multi Save + // Multi Save if (tokens.length === 3 && tokens[0] === "Save" && tokens[1] === "As"){ const name = tokens[2]; return new CommandSaveAs(name); @@ -54,7 +57,7 @@ export const parseCommand = (cmdString: string): Command | undefined => { if(tokens.length===2 && tokens[0] === "Reload"){ return new CommandReload(tokens[1]); } - // break + // break if (tokens.length > 2 && tokens[0] === "Break" && tokens[2]=== "Slots" ){ const slots = parseInt(tokens[1]); if(Number.isInteger(slots)){ @@ -69,18 +72,18 @@ export const parseCommand = (cmdString: string): Command | undefined => { if(Number.isInteger(count) && item in Item){ return new CommandAdd(tokens[0], count, Item[item as keyof typeof Item]); } - return undefined; } if (tokens.length === 2 && isAddVerb(tokens[0])){ const item = tokens[1]; if(item in Item){ return new CommandAddWithoutCount(tokens[0], Item[item as keyof typeof Item]); } - return undefined; } if(tokens.length>2 && isAddVerb(tokens[0])){ const stacks = parseItemStacks(tokens, 1); - return stacks ? new CommandAddMultiple(tokens[0], stacks) : undefined; + if(stacks){ + return new CommandAddMultiple(tokens[0], stacks); + } } // remove X item From Slot Y if (tokens.length === 6 && isRemoveVerb(tokens[0]) && tokens[3] === "From" && tokens[4] ==="Slot" ){ @@ -90,7 +93,6 @@ export const parseCommand = (cmdString: string): Command | undefined => { if(Number.isInteger(count) && Number.isInteger(slot) && item in Item){ return new CommandRemove(tokens[0], count, Item[item as keyof typeof Item], slot-1, false); } - return undefined; } // remove X item if (tokens.length === 3 && isRemoveVerb(tokens[0]) ){ @@ -99,7 +101,6 @@ export const parseCommand = (cmdString: string): Command | undefined => { if(Number.isInteger(count) && item in Item){ return new CommandRemove(tokens[0], count, Item[item as keyof typeof Item], 0, true); } - return undefined; } // remove item From Slot Y if (tokens.length === 5 && isRemoveVerb(tokens[0]) && tokens[2] === "From" && tokens[3] ==="Slot" ){ @@ -108,7 +109,6 @@ export const parseCommand = (cmdString: string): Command | undefined => { if(Number.isInteger(slot) && item in Item){ return new CommandRemoveWithoutCount(tokens[0], Item[item as keyof typeof Item], slot-1, false); } - return undefined; } // remove item if (tokens.length === 2 && isRemoveVerb(tokens[0]) ){ @@ -116,21 +116,20 @@ export const parseCommand = (cmdString: string): Command | undefined => { if(item in Item){ return new CommandRemoveWithoutCount(tokens[0], Item[item as keyof typeof Item], 0, true); } - return undefined; } // remove multiple if(tokens.length>2 && isRemoveVerb(tokens[0])){ const stacks = parseItemStacks(tokens, 1); - return stacks ? new CommandRemoveMultiple(tokens[0], stacks) : undefined; + if(stacks){ + return new CommandRemoveMultiple(tokens[0], stacks); + } } //Shortcut for drop and pick up - if (tokens.length === 3 && tokens[0] === "D&P" ){ - const count = parseInt(tokens[1]); - const item = tokens[2]; - if(Number.isInteger(count) && item in Item){ - return new CommandDaP(count, Item[item as keyof typeof Item]); + if (tokens.length >2 && tokens[0] === "D&P" ){ + const stacks = parseItemStacks(tokens, 1); + if(stacks){ + return new CommandDaP(stacks); } - return undefined; } // Equip item In Slot X @@ -140,7 +139,6 @@ export const parseCommand = (cmdString: string): Command | undefined => { if( Number.isInteger(slot) && item in Item){ return new CommandEquip(Item[item as keyof typeof Item], slot-1, false); } - return undefined; } // Equip item if (tokens.length === 2 && tokens[0] === "Equip"){ @@ -148,7 +146,6 @@ export const parseCommand = (cmdString: string): Command | undefined => { if( item in Item){ return new CommandEquip(Item[item as keyof typeof Item], 0, true); } - return undefined; } // Unequip item in slot X if (tokens.length === 5 && tokens[0] === "Unequip" && tokens[2] === "In" && tokens[3] ==="Slot" ){ @@ -157,7 +154,6 @@ export const parseCommand = (cmdString: string): Command | undefined => { if( Number.isInteger(slot) && item in Item){ return new CommandUnequip(Item[item as keyof typeof Item], slot-1, false); } - return undefined; } // Unequip item if (tokens.length === 2 && tokens[0] === "Unequip"){ @@ -165,7 +161,6 @@ export const parseCommand = (cmdString: string): Command | undefined => { if( item in Item){ return new CommandUnequip(Item[item as keyof typeof Item], -1, true); } - return undefined; } // Shoot X Arrow if (tokens.length === 3 && tokens[0] === "Shoot" && tokens[2] === "Arrow"){ @@ -173,53 +168,34 @@ export const parseCommand = (cmdString: string): Command | undefined => { if( Number.isInteger(count) ){ return new CommandShootArrow(count); } - return undefined; } - // if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Key"){ - // return new CommandSortKey(); - // } - // if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Material"){ - // return new CommandSortMaterial(); - // } - // if(tokens.length===2 && tokens[0] === "Close" && tokens[1] === "Game"){ - // return new CommandCloseGame(); - // } + if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Key"){ + return new CommandSortKey(); + } + if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Material"){ + return new CommandSortMaterial(); + } + if(tokens.length===2 && tokens[0] === "Close" && tokens[1] === "Game"){ + return new CommandCloseGame(); + } + if(tokens.length===2 && tokens[0] === "Sync" && tokens[1] === "GameData"){ + return new CommandSync("Sync GameData"); + } + if(tokens.length===2 && (tokens[0] === "Enter" || tokens[0] === "Exit") && tokens[1] === "Eventide"){ + return new CommandEventide(tokens[0] === "Enter"); + } - // return undefined; - // } - // // remove material - - - - - // // 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 new CommandNop(cmdString); }; const isAddVerb = (token: string): boolean => { - return token === "Get" || token === "Cook" || token === "Add" || token === "Pickup" -} + return token === "Get" || token === "Cook" || token === "Add" || token === "Pickup" || token === "Buy"; +}; const isRemoveVerb = (token: string): boolean => { - return token === "Remove" || token === "Sell" || token === "Eat" || token === "Drop" -} + return token === "Remove" || token === "Sell" || token === "Eat" || token === "Drop" || token === "With"; +}; const parseItemStacks = (tokens: string[], from: number): ItemStack[] | undefined => { if((tokens.length-from)%2 !== 0){ @@ -242,4 +218,4 @@ const parseItemStacks = (tokens: string[], from: number): ItemStack[] | undefine } } return stacks; -} +}; diff --git a/src/core/SimulationState.ts b/src/core/SimulationState.ts index a31a382..6a1061e 100644 --- a/src/core/SimulationState.ts +++ b/src/core/SimulationState.ts @@ -5,160 +5,178 @@ 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) - ); -} + 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; - private isOnEventide: boolean = false; + private gameData: GameData; + private manualSave: GameData | null; + private namedSaves: {[name: string]: GameData} = {}; + private pouch: VisibleInventory; + private nextReloadName?: string; + private isOnEventide = false; - constructor(gameData: GameData, manualSave: GameData | null, namedSaves: {[name: string]: GameData}, pouch: VisibleInventory){ - this.gameData = gameData; - this.manualSave = manualSave; - this.namedSaves = namedSaves; - this.pouch = pouch; - } + 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(); - } - const newState = new SimulationState( - this.gameData.deepClone(), - this.manualSave ? this.manualSave.deepClone() : null, - copyNamedSaves, - this.pouch.deepClone() - ); - newState.nextReloadName = this.nextReloadName; - newState.isOnEventide = this.isOnEventide; + public deepClone(): SimulationState { + const copyNamedSaves: {[name: string]: GameData} = {}; + for(const name in this.namedSaves){ + copyNamedSaves[name] = this.namedSaves[name].deepClone(); + } + const newState = new SimulationState( + this.gameData.deepClone(), + this.manualSave ? this.manualSave.deepClone() : null, + copyNamedSaves, + this.pouch.deepClone() + ); + newState.nextReloadName = this.nextReloadName; + newState.isOnEventide = this.isOnEventide; - return newState; - } + return newState; + } - public initialize(stacks: ItemStack[]) { - this.pouch = new VisibleInventory(new Slots([]), 0); - stacks.forEach((stack)=>this.pouch.addDirectly(stack)); - this.gameData.syncWith(this.pouch); - } + 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 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); - } - } - } - } + 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); - this.pouch.updateEquipmentDurability(this.gameData); - } + private reloadFrom(data: GameData) { + this.gameData = data.deepClone(); + this.pouch.clearForReload(); + this.gameData.addAllToPouchOnReload(this.pouch); + this.pouch.updateEquipmentDurability(this.gameData); + this.isOnEventide = false; + } - public useSaveForNextReload(name: string){ - this.nextReloadName = name; - } + public useSaveForNextReload(name: string){ + this.nextReloadName = name; + } - public breakSlots(n: number) { - this.pouch.modifyCount(-n); - } + public breakSlots(n: number) { + this.pouch.modifyCount(-n); + } - public obtain(item: Item, count: number) { - this.pouch.addInGame(item, count); - this.syncGameDataWithPouch(); - } + public obtain(item: Item, count: number) { + this.pouch.addInGame(item, count); + this.syncGameDataWithPouch(); + } - public remove(item: Item, count: number, slot: number) { - this.pouch.remove(item, count, slot); - this.syncGameDataWithPouch(); - } + public remove(item: Item, count: number, slot: number) { + this.pouch.remove(item, count, slot); + this.syncGameDataWithPouch(); + } - public equip(item: Item, slot: number) { - this.pouch.equip(item, slot); - this.syncGameDataWithPouch(); - } + public equip(item: Item, slot: number) { + this.pouch.equip(item, slot); + this.syncGameDataWithPouch(); + } - public unequip(item: Item, slot: number){ - this.pouch.unequip(item, slot); - this.syncGameDataWithPouch(); - } + public unequip(item: Item, slot: number){ + this.pouch.unequip(item, slot); + this.syncGameDataWithPouch(); + } - public shootArrow(count: number){ - this.pouch.shootArrow(count, this.gameData); - // does not sync - } + public shootArrow(count: number){ + this.pouch.shootArrow(count, this.gameData); + // does not sync + } - public syncGameDataWithPouch() { - if(!this.isOnEventide){ - this.gameData.syncWith(this.pouch); - } - } + public closeGame() { + this.pouch = new VisibleInventory(new Slots([]), 0); + this.gameData = new GameData(new Slots([])); + this.isOnEventide = false; + } - public get displayableGameData(): DisplayableInventory { - return this.gameData; - } + public setEventide(onEventide: boolean){ + if(this.isOnEventide !== onEventide){ + if(onEventide){ + // clear everything except for key items + this.pouch.clearForEventide(); + // game data is not updated (?) + + }else{ + // reload pouch from gamedata as if reloading a save + this.reloadFrom(this.gameData); + } + this.isOnEventide = onEventide; + } + + } - public get displayablePouch(): DisplayableInventory { - return this.pouch; - } + public syncGameDataWithPouch() { + if(!this.isOnEventide){ + this.gameData.syncWith(this.pouch); + } + } - public get inventoryMCount(): number { - return this.pouch.getCount(); - } + public get displayableGameData(): DisplayableInventory { + return this.gameData; + } - public getManualSave(): GameData | null { - return this.manualSave; - } + public get displayablePouch(): DisplayableInventory { + return this.pouch; + } - public getNamedSaves(): {[name: string]: GameData} { - return this.namedSaves; - } + public get inventoryMCount(): number { + return this.pouch.getCount(); + } - // public get displayableGameData(): DisplayableInventory { - // return this.gameData; - // } + public getManualSave(): GameData | null { + return this.manualSave; + } + public getNamedSaves(): {[name: string]: GameData} { + return this.namedSaves; + } + + // public get displayableGameData(): DisplayableInventory { + // return this.gameData; + // } } - - - // Shoot X Arrow, x can be ommited and default to 1 -// Close Game // Close Inventory, same as Resync GameData // Enter Eventide / Leave Eventide // Sort Key (In Tab X) - need more research on which tab is sorted. (might not be possible to select which tab to sort) diff --git a/src/core/Slots.ts b/src/core/Slots.ts index 874151c..c72219e 100644 --- a/src/core/Slots.ts +++ b/src/core/Slots.ts @@ -1,8 +1,6 @@ -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 */ @@ -43,21 +41,7 @@ export class Slots { 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){ @@ -73,18 +57,6 @@ export class Slots { 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); - // } // remove item(s) start from slot // return number of slots removed @@ -179,34 +151,29 @@ export class Slots { } if(reloading){ - for(let i=0;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{ - 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= this.internalSlots.length){ return; } - const thisData = itemToItemData(this.internalSlots[slot].item); - if(thisData.stackable){ + const thisData = itemToItemData(this.internalSlots[slot].item); + // Currently only supports corrupting arrows, material, meal and key items as durability values are not simulated on equipments + if(thisData.type >= ItemType.Material || thisData.stackable){ this.internalSlots[slot].count = durability; } } @@ -268,20 +236,22 @@ export class Slots { // shoot count arrows. return the slot that was updated, or -1 public shootArrow(count: number): number { // first find equipped arrow, search entire inventory + // this is the last equipped arrow before armor let i=0; + let equippedArrow: Item | undefined = undefined; for(;i ItemType.Shield){ + break; + } + if(this.internalSlots[i].equipped && data.type === ItemType.Arrow){ + equippedArrow = data.item; } } if(i>=this.internalSlots.length){ //can't find equipped arrow return -1; } - const equippedArrow = this.internalSlots[i].item; // now find the first slot of that arrow and update for(let j=0;jitemToItemData(stack.item).type === ItemType.Key); + const removedCount = this.internalSlots.length - newslots.length; + this.internalSlots = newslots; + return removedCount; + } - // public getFirstEquippedSlotIndex(type: ItemType): number { - // for(let i = 0; iitemStackToDisplayableSlot(stack, i>=this.count)); - } + public getDisplayedSlots(): DisplayableSlot[] { + return this.slots.getSlotsRef().map((stack, i)=>itemStackToDisplayableSlot(stack, i>=this.count)); + } - public getSlots(): Slots { - return this.slots; - } + public getSlots(): Slots { + return this.slots; + } - public addDirectly(stack: ItemStack){ - this.count+=this.slots.addStackDirectly(stack); - } + 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 addWhenReload(item: Item, count: number, equippedDuringReload: boolean) { + const slotsAdded = this.slots.add(item, count, equippedDuringReload, true, this.count); + this.count+=slotsAdded; + } - public addInGame(item: Item, count: number) { - const slotsAdded = this.slots.add(item, count, false, false, this.count); - this.count+=slotsAdded; - } + public addInGame(item: Item, count: number) { + const slotsAdded = this.slots.add(item, count, false, false, this.count); + this.count+=slotsAdded; + } - public remove(item: Item, count: number, slot: number) { - const slotsRemoved = this.slots.remove(item, count, slot); - this.count-=slotsRemoved; - } + public remove(item: Item, count: number, slot: number) { + const slotsRemoved = this.slots.remove(item, count, slot); + this.count-=slotsRemoved; + } - public equip(item: Item, slot: number) { - this.slots.equip(item, slot); - } + public equip(item: Item, slot: number) { + this.slots.equip(item, slot); + } - public unequip(item: Item, slot: number) { - this.slots.unequip(item, slot); - } + public unequip(item: Item, slot: number) { + this.slots.unequip(item, slot); + } - // Only clears first this.count - public clearForReload() { - if(this.count > 0){ - this.slots.clearFirst(this.count); - this.count = 0; - } - } + // Only clears first this.count + public clearForReload() { + if(this.count > 0){ + this.slots.clearFirst(this.count); + this.count = 0; + } + } - public updateEquipmentDurability(gameData: GameData) { - // find first weapon/bow/shield. this one searches entire inventory - let foundWeapon = false; - let foundBow = false; - let foundShield = false; - this.slots.getSlotsRef().forEach(({item, equipped}, i)=>{ - if(equipped){ - const type = itemToItemData(item).type; - if(type === ItemType.Weapon && !foundWeapon){ - gameData.updateDurability(999, i); - foundWeapon = true; - } - if(type === ItemType.Bow && !foundBow){ - gameData.updateDurability(999, i); - foundBow = true; - } - if(type === ItemType.Shield && !foundShield){ - gameData.updateDurability(999, i); - foundShield = true; - } - } - }) - } + public updateEquipmentDurability(gameData: GameData) { + // find first weapon/bow/shield. this one searches entire inventory + let foundWeapon = false; + let foundBow = false; + let foundShield = false; + this.slots.getSlotsRef().forEach(({item, equipped}, i)=>{ + if(equipped){ + const type = itemToItemData(item).type; + if(type === ItemType.Weapon && !foundWeapon){ + gameData.updateDurability(999, i); + foundWeapon = true; + } + if(type === ItemType.Bow && !foundBow){ + gameData.updateDurability(999, i); + foundBow = true; + } + if(type === ItemType.Shield && !foundShield){ + gameData.updateDurability(999, i); + foundShield = true; + } + } + }); + } - public shootArrow(count: number, gameData: GameData) { - const updatedSlot = this.slots.shootArrow(count); - if(updatedSlot>=0){ - const durability = this.slots.getSlotsRef()[updatedSlot].count; - gameData.updateDurability(durability, updatedSlot); - } - } + public shootArrow(count: number, gameData: GameData) { + const updatedSlot = this.slots.shootArrow(count); + if(updatedSlot>=0){ + const durability = this.slots.getSlotsRef()[updatedSlot].count; + gameData.updateDurability(durability, updatedSlot); + } + } - public getCount(): number { - return this.count; - } + public getCount(): number { + return this.count; + } - public modifyCount(delta: number): void { - this.count+=delta; - } + public modifyCount(delta: number): void { + this.count+=delta; + } - public resetCount(): void { - this.count = this.slots.length; - } + public resetCount(): void { + this.count = this.slots.length; + } + + public clearForEventide(): void { + this.count-=this.slots.clearAllButKeyItems(); + } } diff --git a/src/data/mergeSort.ts b/src/data/mergeSort.ts index 4fdea6a..c0b3e40 100644 --- a/src/data/mergeSort.ts +++ b/src/data/mergeSort.ts @@ -1,14 +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); + const stabilizedThis: [T, number][] = array.map((el, index) => [el, index]); + const stableCmp = (a: [T, number], b: [T, number]) => { + const 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 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 DisplayPane: React.FC = ({command,editCommand,displayIndex,simulationState, overlaySave})=>{ const [commandString, setCommandString] = useState(""); const [hasError, setHasError] = useState(false); @@ -72,104 +53,96 @@ export const DisplayPane: React.FC = ({command,editCommand,dis outline: "none", }}value={commandString} placeholder="Type command here..." + spellCheck={false} onChange={(e)=>{ const cmdString = e.target.value; setCommandString(cmdString); const parsedCommand = parseCommand(cmdString); - if(parsedCommand){ - editCommand(parsedCommand); - setHasError(false); - }else{ - setHasError(true); - } + editCommand(parsedCommand); + setHasError(cmdString!=="" &&!cmdString.startsWith("#") && !parsedCommand.isValid()); }}>
    - {overlaySave ? -
    + {overlaySave ? +
    - { - (()=>{ - const doubleSlots: JSX.Element[] = []; - const gameDataSlots = simulationState.displayableGameData.getDisplayedSlots(); - const inventorySlots = simulationState.displayablePouch.getDisplayedSlots(); - console.log(inventorySlots); - for(let i=0;i); - } - if(inventorySlots.length>gameDataSlots.length){ - for(let i=inventorySlots.length;i{ + const doubleSlots: JSX.Element[] = []; + const gameDataSlots = simulationState.displayableGameData.getDisplayedSlots(); + const inventorySlots = simulationState.displayablePouch.getDisplayedSlots(); + for(let i=0;i); - } - }else if(inventorySlots.length > gameDataSlots.length){ - for(let i=gameDataSlots.length;i); } - } - return doubleSlots; - })() - } + if(gameDataSlots.length>inventorySlots.length){ + for(let i=inventorySlots.length;i); + } + }else if(inventorySlots.length > gameDataSlots.length){ + for(let i=gameDataSlots.length;i); + } + } + return doubleSlots; + })() + } - - -
    - - :<> - -
    - - - -
    -
    - - - + + :<> + +
    + + + +
    +
    + + + -
    - } +
    + }
    -
    ; }; diff --git a/src/surfaces/OptionPage.tsx b/src/surfaces/OptionPage.tsx new file mode 100644 index 0000000..b422d99 --- /dev/null +++ b/src/surfaces/OptionPage.tsx @@ -0,0 +1,106 @@ +import { TitledList } from "components/TitledList"; +import { saveAs } from "data/FileSaver"; +import { useRef, useState } from "react"; + +type OptionPageProps = { + interlaceInventory: boolean, + setInterlaceInventory: (value: boolean)=>void, + commandText: string, + setCommandText: (value: string)=>void, +} + +export const OptionPage: React.FC = ({ + interlaceInventory, + setInterlaceInventory, + commandText, + setCommandText +}) => { + const [currentText, setCurrentText] = useState(commandText); + const [fileName, setFileName] = useState(""); + const uploadRef = useRef(null); + + return ( +
    + + { + const files = e.target.files; + if(files?.length && files[0]){ + const file = files[0]; + const fileName = file.name.endsWith(".txt") ? file.name.substring(0, file.name.length-4) : file.name; + setFileName(fileName); + file.text().then(text=>{ + setCurrentText(text); + setCommandText(text); + }); + } + }}/> + + +
    + +

    + Interlace Inventory with GameData + +

    +

    + Toggle whether Visible Inventory should be displayed separetely from Game Data or interlaced. +

    + +

    Import / Export

    +

    + You can also directly copy, paste, or edit the commands here +

    +

    + + + { + setFileName(e.target.value); + }} + placeholder="File name" + /> +