1
0
Fork 0

add import/export

This commit is contained in:
iTNTPiston 2022-06-19 13:09:39 -07:00
parent 7d7e11bdb1
commit 3a92f0fc1d
22 changed files with 1185 additions and 983 deletions

View file

@ -1,16 +1,16 @@
{
"files": {
"main.css": "/static/css/main.7948f1f9.css",
"main.js": "/static/js/main.22757a4a.js",
"main.js": "/static/js/main.6564ce4b.js",
"static/js/787.ada1a5f8.chunk.js": "/static/js/787.ada1a5f8.chunk.js",
"static/media/Calamity-Regular.otf": "/static/media/Calamity-Regular.cbeefc650e6ac39335b6.otf",
"index.html": "/index.html",
"main.7948f1f9.css.map": "/static/css/main.7948f1f9.css.map",
"main.22757a4a.js.map": "/static/js/main.22757a4a.js.map",
"main.6564ce4b.js.map": "/static/js/main.6564ce4b.js.map",
"787.ada1a5f8.chunk.js.map": "/static/js/787.ada1a5f8.chunk.js.map"
},
"entrypoints": [
"static/css/main.7948f1f9.css",
"static/js/main.22757a4a.js"
"static/js/main.6564ce4b.js"
]
}

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="for Breath of the Wild"/><meta property="og:site_name" content="itntpiston.app"/><meta property="og:title" content="Hundo Duplication Simulator"><meta property="og:type" content="website"><meta property="og:url" content="https://dupl.itntpiston.app/#/"><meta property="og:description" content="for Breath of the Wild"><link rel="manifest" href="/manifest.json"/><title>Hundo Duplication Simulator</title><script defer="defer" src="/static/js/main.22757a4a.js"></script><link href="/static/css/main.7948f1f9.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="for Breath of the Wild"/><meta property="og:site_name" content="itntpiston.app"/><meta property="og:title" content="Hundo Duplication Simulator"><meta property="og:type" content="website"><meta property="og:url" content="https://dupl.itntpiston.app/#/"><meta property="og:description" content="for Breath of the Wild"><link rel="manifest" href="/manifest.json"/><title>Hundo Duplication Simulator</title><script defer="defer" src="/static/js/main.6564ce4b.js"></script><link href="/static/css/main.7948f1f9.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
docs/static/js/main.6564ce4b.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,193 +1,212 @@
import { Command, CommandBreakSlots, CommandInitialize, CommandNothing, CommandReload, CommandSave, CommandSortKey } from 'core/Command';
import { Inventory } from 'core/Inventory';
import React, { useEffect, useMemo, useState } from 'react';
import { Command, CommandBreakSlots, CommandInitialize, CommandNothing, CommandReload, CommandSave, CommandSortKey } from "core/Command";
import { Inventory } from "core/Inventory";
import React, { useEffect, useMemo, useRef, useState } from "react";
import './App.css';
import { CommandItem } from './components/CommandItem';
import { ItemStack as ISC } from './components/ItemStack';
import "./App.css";
import { CommandItem } from "./components/CommandItem";
import { ItemStack, ItemType } from 'core/ItemStack';
import { DisplayPane } from 'surfaces/DisplayPane';
import { Item } from 'core/Item';
import { deserialzeCommands, serializeCommands } from 'core/serialize';
const Buffer = require("buffer/").Buffer;
import { DisplayPane } from "surfaces/DisplayPane";
import { Item } from "core/Item";
import { deserialzeCommands, serializeCommands } from "core/serialize";
import { saveAs } from "data/FileSaver";
import { parseCommand } from "core/Parser";
const getDefaultCommands = (): Command[]=>{
const encoded = localStorage.getItem("HDS.CurrentCommands");
if(encoded){
return deserialzeCommands(encoded);
}
return [
new CommandInitialize([
{
item: Item.Diamond,
count: 5,
},
{
item: Item.Slate,
count: 1,
},
{
item: Item.Glider,
count: 1,
},
{
item: Item.SpiritOrb,
count: 4,
}
]),
new CommandBreakSlots(4),
new CommandReload(),
new CommandSortKey(),
new CommandSave(),
new CommandReload()
];
}
const encoded = localStorage.getItem("HDS.CurrentCommands");
if(encoded){
return deserialzeCommands(encoded);
}
return [
new CommandInitialize([
{
item: Item.Diamond,
count: 5,
},
{
item: Item.Slate,
count: 1,
},
{
item: Item.Glider,
count: 1,
},
{
item: Item.SpiritOrb,
count: 4,
}
]),
new CommandBreakSlots(4),
new CommandReload(),
new CommandSortKey(),
new CommandSave(),
new CommandReload()
];
};
export const App: React.FC = () => {
const [commands, setCommands] = useState<Command[]>(getDefaultCommands());
const [displayIndex, setDisplayIndex] = useState<number>(0);
const [contextMenuX, setContextMenuX] = useState<number>(0);
const [contextMenuY, setContextMenuY] = useState<number>(0);
const [contextMenuShowing, setContextMenuShowing] = useState<boolean>(false);
const [contextIndex, setContextIndex] = useState<number>(-1);
// compute props
const inventories = useMemo(()=>{
const inventories: Inventory[] = [];
const inv = new Inventory();
commands.forEach(c=>{
c.execute(inv);
inventories.push(inv.clone());
});
return inventories;
}, [commands]);
const [commands, setCommands] = useState<Command[]>(getDefaultCommands());
const [displayIndex, setDisplayIndex] = useState<number>(0);
const [contextMenuX, setContextMenuX] = useState<number>(0);
const [contextMenuY, setContextMenuY] = useState<number>(0);
const [contextMenuShowing, setContextMenuShowing] = useState<boolean>(false);
const [contextIndex, setContextIndex] = useState<number>(-1);
// compute props
const inventories = useMemo(()=>{
const inventories: Inventory[] = [];
const inv = new Inventory();
commands.forEach(c=>{
c.execute(inv);
inventories.push(inv.clone());
});
return inventories;
}, [commands]);
useEffect(()=>{
window.onkeydown=(e)=>{
if(e.code==="ArrowDown"){
setDisplayIndex(Math.min(commands.length-1, displayIndex+1));
}else if(e.code==="ArrowUp"){
setDisplayIndex(Math.max(0, displayIndex-1));
}
}
}, [commands, displayIndex]);
useEffect(()=>{
const encoded = serializeCommands(commands);
localStorage.setItem("HDS.CurrentCommands", encoded);
}, [commands]);
useEffect(()=>{
window.onkeydown=(e)=>{
if(e.code==="ArrowDown"){
setDisplayIndex(Math.min(commands.length-1, displayIndex+1));
}else if(e.code==="ArrowUp"){
setDisplayIndex(Math.max(0, displayIndex-1));
}
};
}, [commands, displayIndex]);
useEffect(()=>{
const encoded = serializeCommands(commands);
localStorage.setItem("HDS.CurrentCommands", encoded);
}, [commands]);
const uploadRef = useRef<HTMLInputElement>(null);
return (
<div className='Calamity'
>
<div id="CommandList" style={{
width: "300px",
height: "calc( 100vh - 5px )",
overflowY: "auto",
float: "left",
border: "1px solid black",
boxSizing: "content-box"
} }>
<ul style={{
listStyleType: "none",
paddingInlineStart: 0
}}>
{
commands.map((c,i)=>(
<CommandItem
onClick={()=>setDisplayIndex(i)}
onContextMenu={(x,y)=>{
setContextIndex(i)
setContextMenuX(x);
setContextMenuY(y);
setContextMenuShowing(true);
}}
key={i}
isSelected={displayIndex===i}
isContextSelected={contextIndex===i}
error={inventories[i].isInaccurate()}
>
{c.getDisplayString()}
</CommandItem>
))
}
<CommandItem onClick={()=>{
const arrCopy = [...commands];
arrCopy.push(new CommandNothing());
setCommands(arrCopy);
}}>(new)</CommandItem>
</ul>
</div>
{displayIndex >= 0 && displayIndex < commands.length &&
<DisplayPane
displayIndex={displayIndex}
command={commands[displayIndex].getDisplayString()}
stacks={inventories[displayIndex].getSlots()}
numBroken={inventories[displayIndex].getNumBroken()}
editCommand={(c)=>{
const arrCopy = [...commands];
arrCopy[displayIndex] = c;
setCommands(arrCopy);
}}/>
return (
<div className='Calamity'
>
<input ref={uploadRef} id="Upload" type="File" hidden onChange={(e)=>{
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);
});
}
}}/>
<div id="CommandList" style={{
width: "300px",
height: "calc( 100vh - 5px )",
overflowY: "auto",
float: "left",
border: "1px solid black",
boxSizing: "content-box"
} }>
<ul style={{
listStyleType: "none",
paddingInlineStart: 0
}}>
{
commands.map((c,i)=>
<CommandItem
onClick={()=>setDisplayIndex(i)}
onContextMenu={(x,y)=>{
setContextIndex(i);
setContextMenuX(x);
setContextMenuY(y);
setContextMenuShowing(true);
}}
key={i}
isSelected={displayIndex===i}
isContextSelected={contextIndex===i}
error={inventories[i].isInaccurate()}
>
{c.getDisplayString()}
</CommandItem>
)
}
<CommandItem onClick={()=>{
const arrCopy = [...commands];
arrCopy.push(new CommandNothing());
setCommands(arrCopy);
}}>(new)</CommandItem>
<CommandItem onClick={()=>{
if(uploadRef.current){
uploadRef.current.click();
}
}}>(import)</CommandItem>
<CommandItem onClick={()=>{
const lines = commands.map(c=>c.getDisplayString());
const text = lines.join("\n");
saveAs(text, "dupe.txt");
}}>(export)</CommandItem>
</ul>
</div>
{displayIndex >= 0 && displayIndex < commands.length &&
<DisplayPane
displayIndex={displayIndex}
command={commands[displayIndex].getDisplayString()}
stacks={inventories[displayIndex].getSlots()}
numBroken={inventories[displayIndex].getNumBroken()}
editCommand={(c)=>{
const arrCopy = [...commands];
arrCopy[displayIndex] = c;
setCommands(arrCopy);
}}
/>
}
}
{
contextMenuShowing && <div style={{
position: "absolute",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
}} onClick={()=>{
setContextMenuShowing(false);
setContextIndex(-1);
}} onContextMenu={(e)=>{
setContextMenuShowing(false);
setContextIndex(-1);
e.preventDefault();
}}>
<div style={{
position: "absolute",
top: contextMenuY,
left: contextMenuX,
width: "200px",
backgroundColor: "white",
border: "1px solid black"
}}>
<ul style={{
margin: 0,
listStyleType: "none",
paddingInlineStart: 0
}}>
<CommandItem onClick={()=>{
const arrCopy = [...commands];
arrCopy.splice(contextIndex, 0, new CommandNothing());
setCommands(arrCopy);
setContextMenuShowing(false);
setContextIndex(-1);
}}>Insert Above</CommandItem>
<CommandItem error onClick={()=>{
if(confirm("Delete?")){
setCommands(commands.filter((_,i)=>i!==contextIndex));
if(displayIndex >= commands.length){
setDisplayIndex(commands.length-1);
}
setContextMenuShowing(false);
setContextIndex(-1);
}
}}>Delete</CommandItem>
</ul>
</div>
</div>
}
</div>
);
}
{
contextMenuShowing && <div style={{
position: "absolute",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
}} onClick={()=>{
setContextMenuShowing(false);
setContextIndex(-1);
}} onContextMenu={(e)=>{
setContextMenuShowing(false);
setContextIndex(-1);
e.preventDefault();
}}>
<div style={{
position: "absolute",
top: contextMenuY,
left: contextMenuX,
width: "200px",
backgroundColor: "white",
border: "1px solid black"
}}>
<ul style={{
margin: 0,
listStyleType: "none",
paddingInlineStart: 0
}}>
<CommandItem onClick={()=>{
const arrCopy = [...commands];
arrCopy.splice(contextIndex, 0, new CommandNothing());
setCommands(arrCopy);
setContextMenuShowing(false);
setContextIndex(-1);
}}>Insert Above</CommandItem>
<CommandItem error onClick={()=>{
if(confirm("Delete?")){
setCommands(commands.filter((_,i)=>i!==contextIndex));
if(displayIndex >= commands.length){
setDisplayIndex(commands.length-1);
}
setContextMenuShowing(false);
setContextIndex(-1);
}
}}>Delete</CommandItem>
</ul>
</div>
</div>
}
</div>
);
};

View file

@ -10,17 +10,16 @@ type CommandItemProps = PropsWithChildren<{
}>;
export const CommandItem: React.FC<CommandItemProps> = ({isSelected, isContextSelected,error, children, onClick, onContextMenu}) => {
return <li
className={clsx("CommandItem", isSelected && "CommandItemSelected", isContextSelected&& "CommandItemContextSelected",error && "CommandItemError")}
onClick={onClick}
onContextMenu={(e)=>{
if(onContextMenu){
console.log(e);
onContextMenu(e.clientX,e.clientY);
e.preventDefault();
}
return <li
className={clsx("CommandItem", isSelected && "CommandItemSelected", isContextSelected&& "CommandItemContextSelected",error && "CommandItemError")}
onClick={onClick}
onContextMenu={(e)=>{
if(onContextMenu){
onContextMenu(e.clientX,e.clientY);
e.preventDefault();
}
}}
>{children}&nbsp;</li>
}
}}
>{children}&nbsp;</li>;
};

View file

@ -6,13 +6,13 @@ export type ItemListProps = {
}
export const ItemList: React.FC<ItemListProps> = ({items, numBroken}) => {
return <>
{
items.map(({name, count}, i)=>{
const broken = i+numBroken >= items.length;
return <ItemStack key={i} name={name} count={count} isBroken={broken}/>
})
}
</>;
}
return <>
{
items.map(({name, count}, i)=>{
const broken = i+numBroken >= items.length;
return <ItemStack key={i} name={name} count={count} isBroken={broken}/>;
})
}
</>;
};

View file

@ -5,5 +5,5 @@ type ItemStackProps = {
};
export const ItemStack: React.FC<ItemStackProps> = ({name, count, isBroken})=>{
return <span>[{name} x{count}]{isBroken && " (broken)"}&nbsp;<br/></span>
}
return <span>[{name} x{count}]{isBroken && " (broken)"}&nbsp;<br/></span>;
};

View file

@ -1,341 +1,337 @@
import { Inventory } from "./Inventory";
import { Item, ItemIds } from "./Item";
import { ItemStack, ItemType } from "./ItemStack";
import { ItemStack } from "./ItemStack";
const Buffer = require("buffer/").Buffer;
const Buffer = require("buffer/").Buffer; /* eslint-disable-line @typescript-eslint/no-var-requires*/
export interface Command {
execute(inv: Inventory): void,
getDisplayString(): string,
fromBuffer(buf: Buffer): number,
toBuffer(): Buffer,
execute(inv: Inventory): void,
getDisplayString(): string,
fromBuffer(buf: Buffer): number,
toBuffer(): Buffer,
}
export class CommandNothing implements Command {
static Op = 0x0;
constructor(){
}
static Op = 0x0;
fromBuffer(buf: Buffer): number {
return 0;
}
toBuffer(): Buffer {
const buf: Buffer = Buffer.alloc(1);
buf.writeInt8(CommandNothing.Op);
return buf;
}
execute(inv: Inventory): void {
}
getDisplayString(): string {
return "";
}
fromBuffer(_buf: Buffer): number {
return 0;
}
toBuffer(): Buffer {
const buf: Buffer = Buffer.alloc(1);
buf.writeInt8(CommandNothing.Op);
return buf;
}
execute(_inv: Inventory): void {
// nothing
}
getDisplayString(): string {
return "";
}
}
export class CommandInitialize implements Command {
static Op = 0x1;
private stacks: ItemStack[]
constructor(stacks: ItemStack[]){
this.stacks = stacks;
}
public fromBuffer(buf: Buffer): number {
let read = 0;
const size = buf.readUInt16LE();
read+=2;
const stacks: ItemStack[] = [];
for(let i=0;i<size;i++){
const count = buf.readInt16LE(read);
read+=2;
const id = buf.readInt8(read);
read++;
for(const item in ItemIds){
if(ItemIds[item as Item] === id){
stacks.push({item: item as Item, count});
}
}
}
this.stacks = stacks;
return read;
}
public toBuffer(): Buffer {
const buf: Buffer = Buffer.alloc(3*this.stacks.length+3);
let write = 0;
buf.writeInt8(CommandInitialize.Op);
write++;
buf.writeInt16LE(this.stacks.length, write);
write+=2;
this.stacks.forEach(({item,count})=>{
console.log(count);
buf.writeInt16LE(count&0xffff, write);
write+=2;
buf.writeInt8(ItemIds[item], write);
write++;
});
return buf;
}
static Op = 0x1;
private stacks: ItemStack[];
constructor(stacks: ItemStack[]){
this.stacks = stacks;
}
public fromBuffer(buf: Buffer): number {
let read = 0;
const size = buf.readUInt16LE();
read+=2;
const stacks: ItemStack[] = [];
for(let i=0;i<size;i++){
const count = buf.readInt16LE(read);
read+=2;
const id = buf.readInt8(read);
read++;
for(const item in ItemIds){
if(ItemIds[item as Item] === id){
stacks.push({item: item as Item, count});
}
}
}
this.stacks = stacks;
return read;
}
public toBuffer(): Buffer {
const buf: Buffer = Buffer.alloc(3*this.stacks.length+3);
let write = 0;
buf.writeInt8(CommandInitialize.Op);
write++;
buf.writeInt16LE(this.stacks.length, write);
write+=2;
this.stacks.forEach(({item,count})=>{
buf.writeInt16LE(count&0xffff, write);
write+=2;
buf.writeInt8(ItemIds[item], write);
write++;
});
return buf;
}
public execute(inv: Inventory): void {
inv.init(this.stacks);
}
public getDisplayString(): string {
const parts = ["Initialize"];
this.stacks.forEach(({item, count})=>{
parts.push(""+count);
parts.push(item);
})
return parts.join(" ");
}
public execute(inv: Inventory): void {
inv.init(this.stacks);
}
public getDisplayString(): string {
const parts = ["Initialize"];
this.stacks.forEach(({item, count})=>{
parts.push(""+count);
parts.push(item);
});
return parts.join(" ");
}
}
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`;
}
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 getDisplayString(): string {
return "Save";
}
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 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";
}
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";
}
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";
}
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
}
"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;
constructor(verb: string, count: number, item: Item, slot: number){
this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;
this.count = count;
this.item = item;
this.slot = slot;
}
public fromBuffer(buf: Buffer): number {
let read = 0;
this.count = buf.readInt16LE(read);
read+=2;
const id = buf.readInt8(read);
read+=1;
for(const item in ItemIds){
if(ItemIds[item as Item] === id){
this.item = item as Item;
}
}
this.slot = buf.readInt16LE(read);
read+=2;
this.verb = buf.readInt8(read);
read++;
return read;
}
public toBuffer(): Buffer {
const buf: Buffer = Buffer.alloc(1+2+1+2+1);
let write = 0;
buf.writeInt8(CommandRemoveMaterial.Op);
write++;
buf.writeInt16LE(this.count, write);
write+=2;
buf.writeInt8(ItemIds[this.item], write);
write++;
buf.writeInt16LE(this.slot, write);
write+=2;
buf.writeInt8(this.verb, write);
return buf;
}
public execute(inv: Inventory): void {
inv.remove(this.item, this.count, this.slot);
}
public getDisplayString(): string {
return `${Verbs[this.verb]} ${this.count} ${this.item} From Slot ${this.slot+1}`;
}
static Op = 0x7;
private verb: number;
private count: number;
private item: Item;
private slot: number;
constructor(verb: string, count: number, item: Item, slot: number){
this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;
this.count = count;
this.item = item;
this.slot = slot;
}
public fromBuffer(buf: Buffer): number {
let read = 0;
this.count = buf.readInt16LE(read);
read+=2;
const id = buf.readInt8(read);
read+=1;
for(const item in ItemIds){
if(ItemIds[item as Item] === id){
this.item = item as Item;
}
}
this.slot = buf.readInt16LE(read);
read+=2;
this.verb = buf.readInt8(read);
read++;
return read;
}
public toBuffer(): Buffer {
const buf: Buffer = Buffer.alloc(1+2+1+2+1);
let write = 0;
buf.writeInt8(CommandRemoveMaterial.Op);
write++;
buf.writeInt16LE(this.count, write);
write+=2;
buf.writeInt8(ItemIds[this.item], write);
write++;
buf.writeInt16LE(this.slot, write);
write+=2;
buf.writeInt8(this.verb, write);
return buf;
}
public execute(inv: Inventory): void {
inv.remove(this.item, this.count, this.slot);
}
public getDisplayString(): string {
return `${Verbs[this.verb]} ${this.count} ${this.item} From Slot ${this.slot+1}`;
}
}
export class CommandRemoveUnstackableMaterial implements Command {
static Op = 0x8;
private verb: number;
private item: Item;
private slot: number;
constructor(verb: string,item: Item, slot: number){
this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;;
this.item = item;
this.slot = slot;
}
public fromBuffer(buf: Buffer): number {
let read = 0;
const id = buf.readInt8(read);
read+=1;
for(const item in ItemIds){
if(ItemIds[item as Item] === id){
this.item = item as Item;
}
}
this.slot = 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(CommandRemoveUnstackableMaterial.Op);
write++;
buf.writeInt8(ItemIds[this.item], write);
write++;
buf.writeInt16LE(this.slot, write);
write+=2;
buf.writeInt8(this.verb, write);
return buf;
}
public execute(inv: Inventory): void {
inv.remove(this.item, 1, this.slot);
}
public getDisplayString(): string {
return `${Verbs[this.verb]} ${this.item} From Slot ${this.slot+1}`;
}
static Op = 0x8;
private verb: number;
private item: Item;
private slot: number;
constructor(verb: string,item: Item, slot: number){
this.verb = VerbToId[verb as keyof typeof VerbToId] || 0;
this.item = item;
this.slot = slot;
}
public fromBuffer(buf: Buffer): number {
let read = 0;
const id = buf.readInt8(read);
read+=1;
for(const item in ItemIds){
if(ItemIds[item as Item] === id){
this.item = item as Item;
}
}
this.slot = 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(CommandRemoveUnstackableMaterial.Op);
write++;
buf.writeInt8(ItemIds[this.item], write);
write++;
buf.writeInt16LE(this.slot, write);
write+=2;
buf.writeInt8(this.verb, write);
return buf;
}
public execute(inv: Inventory): void {
inv.remove(this.item, 1, this.slot);
}
public getDisplayString(): string {
return `${Verbs[this.verb]} ${this.item} From Slot ${this.slot+1}`;
}
}
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;
for(const item in ItemIds){
if(ItemIds[item as Item] === id){
this.item = item as 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(ItemIds[this.item], 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}`;
}
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;
for(const item in ItemIds){
if(ItemIds[item as Item] === id){
this.item = item as 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(ItemIds[this.item], 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}`;
}
}

View file

@ -2,171 +2,176 @@ import { itemToType, getKeyItemSortOrder, shouldIgnoreOnReload, getMaterialSortO
import { ItemStack, ItemType } from "./ItemStack";
export class Inventory {
private slots: ItemStack[] = [];
private savedSlots: ItemStack[] = [];
private numBroken: number = 0;
private isInitialSort: boolean = false;
private isAltered: boolean = true;
private isSaveAltered: boolean = true;
private inaccurate: boolean = false;
public clone(): Inventory {
const other = new Inventory();
other.slots = [...this.slots.map(stack=>({...stack}))];
other.savedSlots = [...this.savedSlots.map(stack=>({...stack}))];
other.numBroken = this.numBroken;
other.isInitialSort = this.isInitialSort;
other.isAltered = this.isAltered;
other.isSaveAltered = this.isSaveAltered;
other.inaccurate = this.inaccurate;
return other;
}
private slots: ItemStack[] = [];
private savedSlots: ItemStack[] = [];
private numBroken = 0;
private isInitialSort = false;
private isAltered = true;
private isSaveAltered = true;
private inaccurate = false;
public clone(): Inventory {
const other = new Inventory();
other.slots = [...this.slots.map(stack=>({...stack}))];
other.savedSlots = [...this.savedSlots.map(stack=>({...stack}))];
other.numBroken = this.numBroken;
other.isInitialSort = this.isInitialSort;
other.isAltered = this.isAltered;
other.isSaveAltered = this.isSaveAltered;
other.inaccurate = this.inaccurate;
return other;
}
public getSlots(): ItemStack[] {
return this.slots;
}
public getSlots(): ItemStack[] {
return this.slots;
}
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 init(stacks: ItemStack[]) {
this.savedSlots = [...stacks.map((stack)=>({...stack}))];
this.slots = [...stacks.map((stack)=>({...stack}))];
}
public init(stacks: ItemStack[]) {
this.savedSlots = [...stacks.map((stack)=>({...stack}))];
this.slots = [...stacks.map((stack)=>({...stack}))];
this.numBroken = 0;
this.isInitialSort = false;
this.isAltered = true;
this.isSaveAltered = true;
this.inaccurate = false;
}
public addBrokenSlots(num: number) {
this.numBroken+=num;
}
public addBrokenSlots(num: number) {
this.numBroken+=num;
}
public save() {
this.isSaveAltered = this.isAltered;
this.savedSlots = [...this.slots];
}
public save() {
this.isSaveAltered = this.isAltered;
this.savedSlots = [...this.slots];
}
public reload() {
if(!this.isSaveAltered){
this.inaccurate = true;
}
// get things to dupe
const dupeMap: {[k in ItemType]: ItemStack[]} = {
[ItemType.Material]: [],
[ItemType.Meal]: [],
[ItemType.Key]: []
}
for(let i=Math.max(0, this.slots.length-this.numBroken);i<this.slots.length;i++){
const stack = this.slots[i];
if(!shouldIgnoreOnReload(stack.item)){
dupeMap[itemToType(stack.item)].push(stack);
}
}
// get materials, food, and key items
const materials = this.savedSlots.filter(stack=>itemToType(stack.item)===ItemType.Material);
const meals = this.savedSlots.filter(stack=>itemToType(stack.item)===ItemType.Meal);
const keyItems = this.savedSlots.filter(stack=>itemToType(stack.item)===ItemType.Key);
// apply dupe
this.slots = [];
// duped materials go to the left
this.slots.push(...dupeMap[ItemType.Material].map(stack=>({...stack})));
this.slots.push(...materials.map(stack=>({...stack})));
this.slots.push(...dupeMap[ItemType.Meal].map(stack=>({...stack})));
this.slots.push(...meals.map(stack=>({...stack})));
// key items to the right
this.slots.push(...keyItems.map(stack=>({...stack})));
this.slots.push(...dupeMap[ItemType.Key].map(stack=>({...stack})));
public reload() {
if(!this.isSaveAltered){
this.inaccurate = true;
}
// get things to dupe
const dupeMap: {[k in ItemType]: ItemStack[]} = {
[ItemType.Material]: [],
[ItemType.Meal]: [],
[ItemType.Key]: []
};
for(let i=Math.max(0, this.slots.length-this.numBroken);i<this.slots.length;i++){
const stack = this.slots[i];
if(!shouldIgnoreOnReload(stack.item)){
dupeMap[itemToType(stack.item)].push(stack);
}
}
// get materials, food, and key items
const materials = this.savedSlots.filter(stack=>itemToType(stack.item)===ItemType.Material);
const meals = this.savedSlots.filter(stack=>itemToType(stack.item)===ItemType.Meal);
const keyItems = this.savedSlots.filter(stack=>itemToType(stack.item)===ItemType.Key);
// apply dupe
this.slots = [];
// duped materials go to the left
this.slots.push(...dupeMap[ItemType.Material].map(stack=>({...stack})));
this.slots.push(...materials.map(stack=>({...stack})));
this.slots.push(...dupeMap[ItemType.Meal].map(stack=>({...stack})));
this.slots.push(...meals.map(stack=>({...stack})));
// key items to the right
this.slots.push(...keyItems.map(stack=>({...stack})));
this.slots.push(...dupeMap[ItemType.Key].map(stack=>({...stack})));
this.isInitialSort = true;
this.isAltered = false;
this.isSaveAltered = false;
}
this.isInitialSort = true;
this.isAltered = false;
this.isSaveAltered = false;
}
public sortKey() {
const nonKeyItems = this.slots.filter(stack=>itemToType(stack.item)!==ItemType.Key);
const keyItems = this.slots.filter(stack=>itemToType(stack.item)===ItemType.Key);
keyItems.sort((a,b)=>{
return getKeyItemSortOrder(a.item) - getKeyItemSortOrder(b.item);
});
this.slots = [...nonKeyItems, ...keyItems];
this.isAltered=true;
this.isInitialSort=false;
}
public sortKey() {
const nonKeyItems = this.slots.filter(stack=>itemToType(stack.item)!==ItemType.Key);
const keyItems = this.slots.filter(stack=>itemToType(stack.item)===ItemType.Key);
keyItems.sort((a,b)=>{
return getKeyItemSortOrder(a.item) - getKeyItemSortOrder(b.item);
});
this.slots = [...nonKeyItems, ...keyItems];
this.isAltered=true;
this.isInitialSort=false;
}
public sortMaterial() {
const nonMaterial = this.slots.filter(stack=>itemToType(stack.item)!==ItemType.Material);
const materials = this.slots.filter(stack=>itemToType(stack.item)===ItemType.Material);
if(this.isInitialSort){
// the materials in broken slots are not sorted
const brokenSlots = Math.max(0, this.numBroken - nonMaterial.length);
const sortPart = materials.splice(0, materials.length-brokenSlots);
sortPart.sort((a,b)=>{
return getMaterialSortOrder(a.item) - getMaterialSortOrder(b.item);
});
this.slots = [...sortPart, ...materials, ...nonMaterial];
this.isInitialSort = false;
}else{
materials.sort((a,b)=>{
return getMaterialSortOrder(a.item) - getMaterialSortOrder(b.item);
});
this.slots = [...materials, ...nonMaterial];
}
this.isAltered=true;
}
public sortMaterial() {
const nonMaterial = this.slots.filter(stack=>itemToType(stack.item)!==ItemType.Material);
const materials = this.slots.filter(stack=>itemToType(stack.item)===ItemType.Material);
if(this.isInitialSort){
// the materials in broken slots are not sorted
const brokenSlots = Math.max(0, this.numBroken - nonMaterial.length);
const sortPart = materials.splice(0, materials.length-brokenSlots);
sortPart.sort((a,b)=>{
return getMaterialSortOrder(a.item) - getMaterialSortOrder(b.item);
});
this.slots = [...sortPart, ...materials, ...nonMaterial];
this.isInitialSort = false;
}else{
materials.sort((a,b)=>{
return getMaterialSortOrder(a.item) - getMaterialSortOrder(b.item);
});
this.slots = [...materials, ...nonMaterial];
}
this.isAltered=true;
}
public remove(item: Item, count: number, slot: number) {
let s = 0;
for(let i = 0; i<this.slots.length;i++){
if(this.slots[i].item === item){
if(s<slot){
s++;
}else{
this.slots[i].count-=count;
break;
}
}
}
this.slots = this.slots.filter(({count})=>count>0);
this.isAltered=true;
}
public remove(item: Item, count: number, slot: number) {
let s = 0;
for(let i = 0; i<this.slots.length;i++){
if(this.slots[i].item === item){
if(s<slot){
s++;
}else{
this.slots[i].count-=count;
break;
}
}
}
this.slots = this.slots.filter(({count})=>count>0);
this.isAltered=true;
}
public add(item: Item, count: number) {
let added = false;
if(isStackable(item)){
for(let i = 0; i<this.slots.length;i++){
if(this.slots[i].item === item){
this.slots[i].count+=count;
added = true;
break;
}
}
}
if(!added){
// add to the correct type
switch(itemToType(item)){
case ItemType.Material: {
const materials = this.slots.filter(stack=>itemToType(stack.item)===ItemType.Material);
this.slots.splice(materials.length, 0, {
item, count
});
break;
}
case ItemType.Meal: {
const keyItems = this.slots.filter(stack=>itemToType(stack.item)===ItemType.Key);
this.slots.splice(-keyItems.length, 0, {
item, count
});
break;
}
case ItemType.Key: {
this.slots.push({
item, count
});
break;
}
}
}
this.isAltered=true;
}
public add(item: Item, count: number) {
let added = false;
if(isStackable(item)){
for(let i = 0; i<this.slots.length;i++){
if(this.slots[i].item === item){
this.slots[i].count+=count;
added = true;
break;
}
}
}
if(!added){
// add to the correct type
switch(itemToType(item)){
case ItemType.Material: {
const materials = this.slots.filter(stack=>itemToType(stack.item)===ItemType.Material);
this.slots.splice(materials.length, 0, {
item, count
});
break;
}
case ItemType.Meal: {
const keyItems = this.slots.filter(stack=>itemToType(stack.item)===ItemType.Key);
this.slots.splice(-keyItems.length, 0, {
item, count
});
break;
}
case ItemType.Key: {
this.slots.push({
item, count
});
break;
}
}
}
this.isAltered=true;
}
}

View file

@ -4,7 +4,6 @@ export enum Item {
Slate = "Slate",
Glider = "Glider",
SpiritOrb = "SpiritOrb",
Lotus = "Lotus",
SilentPrincess = "SilentPrincess",
@ -27,86 +26,86 @@ export enum Item {
}
export const ItemIds = {
/* Do not change the ID once created. Otherwise you would break existing codes */
[Item.Slate]: 0x00,
[Item.Glider]: 0x01,
[Item.SpiritOrb]: 0x02,
/* Do not change the ID once created. Otherwise you would break existing codes */
[Item.Slate]: 0x00,
[Item.Glider]: 0x01,
[Item.SpiritOrb]: 0x02,
[Item.Diamond]: 0x10,
[Item.Lotus]: 0x11,
[Item.SilentPrincess]: 0x12,
[Item.Honey]: 0x13,
[Item.Acorn]: 0x14,
[Item.FaroshScale]: 0x15,
[Item.FaroshClaw]: 0x16,
[Item.FaroshHorn]: 0x17,
[Item.HeartyBass]: 0x18,
[Item.Beetle]: 0x19,
[Item.Opal]: 0x1a,
[Item.Tail]: 0x1b,
[Item.Spring]: 0x1c,
[Item.Shaft]: 0x1d,
[Item.Core]: 0x1e,
[Item.Wood]: 0x1f,
[Item.Diamond]: 0x10,
[Item.Lotus]: 0x11,
[Item.SilentPrincess]: 0x12,
[Item.Honey]: 0x13,
[Item.Acorn]: 0x14,
[Item.FaroshScale]: 0x15,
[Item.FaroshClaw]: 0x16,
[Item.FaroshHorn]: 0x17,
[Item.HeartyBass]: 0x18,
[Item.Beetle]: 0x19,
[Item.Opal]: 0x1a,
[Item.Tail]: 0x1b,
[Item.Spring]: 0x1c,
[Item.Shaft]: 0x1d,
[Item.Core]: 0x1e,
[Item.Wood]: 0x1f,
[Item.SpeedFood]: 0x40,
}
[Item.SpeedFood]: 0x40,
};
export const itemToType = (item: Item): ItemType => {
if (item === Item.Slate || item === Item.Glider || item === Item.SpiritOrb){
return ItemType.Key;
}
if (item === Item.SpeedFood) {
return ItemType.Meal;
}
return ItemType.Material;
}
if (item === Item.Slate || item === Item.Glider || item === Item.SpiritOrb){
return ItemType.Key;
}
if (item === Item.SpeedFood) {
return ItemType.Meal;
}
return ItemType.Material;
};
export const shouldIgnoreOnReload = (item: Item): boolean => {
return item === Item.Slate || item === Item.Glider;
}
return item === Item.Slate || item === Item.Glider;
};
export const isStackable = (item: Item): boolean => {
return item !==Item.Slate && item !== Item.Glider && item !== Item.SpeedFood;
}
return item !==Item.Slate && item !== Item.Glider && item !== Item.SpeedFood;
};
const KeyItemSortOrderMap = (()=>{
const map: {[k in Item]?: number} = {};
[
Item.Slate,
Item.Glider,
Item.SpiritOrb
].forEach((item, i)=>map[item] = i);
return map;
const map: {[k in Item]?: number} = {};
[
Item.Slate,
Item.Glider,
Item.SpiritOrb
].forEach((item, i)=>map[item] = i);
return map;
})();
export const getKeyItemSortOrder = (item: Item): number => {
return KeyItemSortOrderMap[item] || -1;
}
return KeyItemSortOrderMap[item] || -1;
};
const MaterialSortOrderMap = (()=>{
const map: {[k in Item]?: number} = {};
[
Item.Lotus,
Item.SilentPrincess,
Item.Honey,
Item.Acorn,
Item.FaroshScale,
Item.FaroshClaw,
Item.FaroshHorn,
Item.HeartyBass,
Item.Beetle,
Item.Opal,
Item.Diamond,
Item.Tail,
Item.Spring,
Item.Shaft,
Item.Core,
Item.Wood,
].forEach((item, i)=>map[item] = i);
return map;
const map: {[k in Item]?: number} = {};
[
Item.Lotus,
Item.SilentPrincess,
Item.Honey,
Item.Acorn,
Item.FaroshScale,
Item.FaroshClaw,
Item.FaroshHorn,
Item.HeartyBass,
Item.Beetle,
Item.Opal,
Item.Diamond,
Item.Tail,
Item.Spring,
Item.Shaft,
Item.Core,
Item.Wood,
].forEach((item, i)=>map[item] = i);
return map;
})();
export const getMaterialSortOrder = (item: Item): number => {
return MaterialSortOrderMap[item] || -1;
}
return MaterialSortOrderMap[item] || -1;
};

View file

@ -3,79 +3,79 @@ import { Item } from "./Item";
import { ItemStack } from "./ItemStack";
export const parseCommand = (cmdString: string): Command | undefined => {
const tokens = cmdString.split(" ").filter(i=>i);
if(tokens.length===0){
return new CommandNothing();
}
// intialize
if(tokens.length>1 && tokens[0] === "Initialize" && tokens.length%2 === 1){
const stacks: ItemStack[] = [];
for(let i=1;i<tokens.length;i+=2){
const count = parseInt(tokens[i]);
if(!Number.isInteger(count)){
return undefined;
}
const item = tokens[i+1];
if (item in Item){
stacks.push({
item: Item[item as keyof typeof Item], count
});
}else{
return undefined;
}
}
return new CommandInitialize(stacks);
}
// no var
if(tokens.length===1 && tokens[0] === "Save"){
return new CommandSave();
}
if(tokens.length===1 && tokens[0] === "Reload"){
return new CommandReload();
}
const tokens = cmdString.split(" ").filter(i=>i);
if(tokens.length===0){
return new CommandNothing();
}
// intialize
if(tokens.length>1 && tokens[0] === "Initialize" && tokens.length%2 === 1){
const stacks: ItemStack[] = [];
for(let i=1;i<tokens.length;i+=2){
const count = parseInt(tokens[i]);
if(!Number.isInteger(count)){
return undefined;
}
const item = tokens[i+1];
if (item in Item){
stacks.push({
item: Item[item as keyof typeof Item], count
});
}else{
return undefined;
}
}
return new CommandInitialize(stacks);
}
// no var
if(tokens.length===1 && tokens[0] === "Save"){
return new CommandSave();
}
if(tokens.length===1 && tokens[0] === "Reload"){
return new CommandReload();
}
if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Key"){
return new CommandSortKey();
}
if(tokens.length===2 && tokens[0] === "Sort" && tokens[1] === "Material"){
return new CommandSortMaterial();
}
// 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[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);
}
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);
}
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;
}
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();
}
// 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[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);
}
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);
}
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;
}
return undefined;
}
return undefined;
};

View file

@ -1,65 +1,65 @@
import { Command, CommandAddMaterial, CommandBreakSlots, CommandInitialize, CommandNothing, CommandReload, CommandRemoveMaterial, CommandRemoveUnstackableMaterial, CommandSave, CommandSortKey, CommandSortMaterial } from "./Command";
import { Item } from "./Item";
const Buffer = require("buffer/").Buffer;
const Buffer = require("buffer/").Buffer; /* eslint-disable-line @typescript-eslint/no-var-requires*/
export const serializeCommands = (commands: Command[]): string => {
const sizeBuf: Buffer = Buffer.alloc(4);
sizeBuf.writeInt32LE(commands.length);
const sizeBuf: Buffer = Buffer.alloc(4);
sizeBuf.writeInt32LE(commands.length);
const commandBuffers = commands.map(c=>c.toBuffer());
const allBufs = Buffer.concat([sizeBuf, ...commandBuffers]);
console.log(allBufs);
return allBufs.toString("base64");
}
const commandBuffers = commands.map(c=>c.toBuffer());
const allBufs = Buffer.concat([sizeBuf, ...commandBuffers]);
return allBufs.toString("base64");
};
export const deserialzeCommands = (base64str: string): Command[] => {
const buf: Buffer = Buffer.from(base64str, "base64");
const size = buf.readInt32LE();
let off = 4;
const commands: Command[] = [];
for(let i=0;i<size;i++){
const op = buf.readUInt8(off);
off++;
let command: Command | undefined = undefined;
switch(op){
case CommandNothing.Op:
command = new CommandNothing();
break;
case CommandInitialize.Op:
command = new CommandInitialize([]);
break;
case CommandBreakSlots.Op:
command = new CommandBreakSlots(0);
break;
case CommandSave.Op:
command = new CommandSave();
break;
case CommandReload.Op:
command = new CommandReload();
break;
case CommandSortKey.Op:
command = new CommandSortKey();
break;
case CommandSortMaterial.Op:
command = new CommandSortMaterial();
break;
case CommandRemoveMaterial.Op:
command = new CommandRemoveMaterial("",0,Item.Slate,0);
break;
case CommandRemoveUnstackableMaterial.Op:
command = new CommandRemoveUnstackableMaterial("", Item.Slate, 0);
break;
case CommandAddMaterial.Op:
command = new CommandAddMaterial("",0,Item.Slate);
break;
}
if(command){
off += command.fromBuffer(buf.slice(off));
commands.push(command);
}else{
console.error("invalid opcode: "+op);
}
}
return commands;
}
const buf: Buffer = Buffer.from(base64str, "base64");
const size = buf.readInt32LE();
let off = 4;
const commands: Command[] = [];
for(let i=0;i<size;i++){
const op = buf.readUInt8(off);
off++;
let command: Command | undefined = undefined;
switch(op){
case CommandNothing.Op:
command = new CommandNothing();
break;
case CommandInitialize.Op:
command = new CommandInitialize([]);
break;
case CommandBreakSlots.Op:
command = new CommandBreakSlots(0);
break;
case CommandSave.Op:
command = new CommandSave();
break;
case CommandReload.Op:
command = new CommandReload();
break;
case CommandSortKey.Op:
command = new CommandSortKey();
break;
case CommandSortMaterial.Op:
command = new CommandSortMaterial();
break;
case CommandRemoveMaterial.Op:
command = new CommandRemoveMaterial("",0,Item.Slate,0);
break;
case CommandRemoveUnstackableMaterial.Op:
command = new CommandRemoveUnstackableMaterial("", Item.Slate, 0);
break;
case CommandAddMaterial.Op:
command = new CommandAddMaterial("",0,Item.Slate);
break;
}
if(command){
off += command.fromBuffer(buf.slice(off));
commands.push(command);
}else{
console.error("invalid opcode: "+op);
}
}
return commands;
};

View file

@ -0,0 +1,168 @@
/* eslint no-empty:0 */
/*
* FileSaver.js
* A saveAs() FileSaver implementation.
*
* By Eli Grey, http://eligrey.com
*
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
* source : http://purl.eligrey.com/github/FileSaver.js
*/
// The one and only way of getting global scope in all environments
// https://stackoverflow.com/q/3277182/1008999
var _global = typeof window === "object" && window.window === window
? window : typeof window.self === "object" && window.self.self === window.self
? window.self : typeof window.global === "object" && window.global.global === window.global
? window.global
: this;
function bom (blob, opts) {
if (typeof opts === "undefined") {opts = { autoBom: false };}
else if (typeof opts !== "object") {
console.error("Deprecated: Expected third argument to be a object");
opts = { autoBom: !opts };
}
// prepend BOM for UTF-8 XML and text/* types (including HTML)
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type });
}
return blob;
}
function download (url, name, opts) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "blob";
xhr.onload = function () {
saveAs(xhr.response, name, opts);
};
xhr.onerror = function () {
console.error("could not download file");
};
xhr.send();
}
function corsEnabled (url) {
var xhr = new XMLHttpRequest();
// use sync to avoid popup blocker
xhr.open("HEAD", url, false);
try {
xhr.send();
} catch (e) {}
return xhr.status >= 200 && xhr.status <= 299;
}
// `a.click()` doesn't work for all browsers (#465)
function click (node) {
try {
node.dispatchEvent(new MouseEvent("click"));
} catch (e) {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null);
node.dispatchEvent(evt);
}
}
// Detect WebView inside a native macOS app by ruling out all browsers
// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent);
var saveAs = _global.saveAs || (
// probably in some web worker
typeof window !== "object" || window !== _global
? function saveAs () { /* noop */ }
// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
: "download" in HTMLAnchorElement.prototype && !isMacOSWebView
? function saveAs (blob, name, opts) {
var URL = _global.URL || _global.webkitURL;
var a = document.createElement("a");
name = name || blob.name || "download";
a.download = name;
a.rel = "noopener"; // tabnabbing
// TODO: detect chrome extensions & packaged apps
// a.target = '_blank'
if (typeof blob === "string") {
// Support regular links
a.href = blob;
if (a.origin !== window.location.origin) {
corsEnabled(a.href)
? download(blob, name, opts)
: click(a, a.target = "_blank");
} else {
click(a);
}
} else {
// Support blobs
a.href = URL.createObjectURL(blob);
setTimeout(function () { URL.revokeObjectURL(a.href); }, 4E4); // 40s
setTimeout(function () { click(a); }, 0);
}
}
// Use msSaveOrOpenBlob as a second approach
: "msSaveOrOpenBlob" in navigator
? function saveAs (blob, name, opts) {
name = name || blob.name || "download";
if (typeof blob === "string") {
if (corsEnabled(blob)) {
download(blob, name, opts);
} else {
var a = document.createElement("a");
a.href = blob;
a.target = "_blank";
setTimeout(function () { click(a); });
}
} else {
navigator.msSaveOrOpenBlob(bom(blob, opts), name);
}
}
// Fallback to using FileReader and a popup
: function saveAs (blob, name, opts, popup) {
// Open a popup immediately do go around popup blocker
// Mostly only available on user interaction and the fileReader is async so...
popup = popup || window.open("", "_blank");
if (popup) {
popup.document.title =
popup.document.body.innerText = "downloading...";
}
if (typeof blob === "string") {return download(blob, name, opts);}
var force = blob.type === "application/octet-stream";
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari;
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== "undefined") {
// Safari doesn't allow downloading of blob URLs
var reader = new FileReader();
reader.onloadend = function () {
var url = reader.result;
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, "data:attachment/file;");
if (popup) {popup.location.href = url;}
else {window.location = url;}
popup = null; // reverse-tabnabbing #460
};
reader.readAsDataURL(blob);
} else {
var URL = _global.URL || _global.webkitURL;
var url = URL.createObjectURL(blob);
if (popup) {popup.location = url;}
else {window.location.href = url;}
popup = null; // reverse-tabnabbing #460
setTimeout(function () { URL.revokeObjectURL(url); }, 4E4); // 40s
}
}
);
export default saveAs;

View file

@ -0,0 +1,9 @@
The MIT License
Copyright © 2016 Eli Grey.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,8 @@
import FileSaverFunction from "./FileSaver";
export const saveAs = (content: string, filename: string): void =>{
const blob = new Blob([content], {
type: "text/plain;charset=utf-8"
});
FileSaverFunction(blob, filename);
};

View file

@ -1,16 +1,16 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import {App} from './App';
import reportWebVitals from './reportWebVitals';
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import {App} from "./App";
import reportWebVitals from "./reportWebVitals";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function

View file

@ -1,15 +1,15 @@
import { ReportHandler } from 'web-vitals';
import { ReportHandler } from "web-vitals";
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
if (onPerfEntry && onPerfEntry instanceof Function) {
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View file

@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import "@testing-library/jest-dom";

View file

@ -1,11 +1,11 @@
import clsx from "clsx"
import { ItemList, ItemListProps } from "components/ItemList"
import { Command } from "core/Command"
import { itemToType } from "core/Item"
import { ItemStack, ItemType } from "core/ItemStack"
import { parseCommand } from "core/Parser"
import clsx from "clsx";
import { ItemList, ItemListProps } from "components/ItemList";
import { Command } from "core/Command";
import { itemToType } from "core/Item";
import { ItemStack, ItemType } from "core/ItemStack";
import { parseCommand } from "core/Parser";
import React, { useEffect, useState } from "react"
import React, { useEffect, useState } from "react";
type DisplayPaneProps = {
command: string,
@ -16,72 +16,72 @@ type DisplayPaneProps = {
}
const stacksToItemListProps = (stacks: ItemStack[], numBroken: number): [ItemListProps, ItemListProps, ItemListProps] => {
const materials = stacks.filter(stack=>itemToType(stack.item)==ItemType.Material);
const meals = stacks.filter(stack=>itemToType(stack.item)==ItemType.Meal);
const keyItems = stacks.filter(stack=>itemToType(stack.item)==ItemType.Key);
return [
{
items: stacksToNameAndCount(materials),
numBroken: Math.max(0, numBroken - keyItems.length - meals.length )
},
{
items: stacksToNameAndCount(meals),
numBroken: Math.max(0, numBroken - keyItems.length)
},{
items: stacksToNameAndCount(keyItems),
numBroken
},
]
}
const materials = stacks.filter(stack=>itemToType(stack.item)==ItemType.Material);
const meals = stacks.filter(stack=>itemToType(stack.item)==ItemType.Meal);
const keyItems = stacks.filter(stack=>itemToType(stack.item)==ItemType.Key);
return [
{
items: stacksToNameAndCount(materials),
numBroken: Math.max(0, numBroken - keyItems.length - meals.length )
},
{
items: stacksToNameAndCount(meals),
numBroken: Math.max(0, numBroken - keyItems.length)
},{
items: stacksToNameAndCount(keyItems),
numBroken
},
];
};
const stacksToNameAndCount = (stacks: ItemStack[]): ItemListProps["items"] => {
return stacks.map(({item, count})=>({name: item, count}));
}
return stacks.map(({item, count})=>({name: item, count}));
};
export const DisplayPane: React.FC<DisplayPaneProps> = ({command,editCommand,displayIndex, stacks, numBroken})=>{
const [commandString, setCommandString] = useState<string>("");
const [hasError, setHasError] = useState<boolean>(false);
const [materialListProps, mealListProps, keyItemListProps] = stacksToItemListProps(stacks, numBroken);
useEffect(()=>{
if(commandString!==command){
setCommandString(command);
setHasError(false);
}
const [commandString, setCommandString] = useState<string>("");
const [hasError, setHasError] = useState<boolean>(false);
const [materialListProps, mealListProps, keyItemListProps] = stacksToItemListProps(stacks, numBroken);
useEffect(()=>{
if(commandString!==command){
setCommandString(command);
setHasError(false);
}
}, [command, displayIndex]);
}, [command, displayIndex]);
return <div id="DisplayPane" style={{
width: "calc( 100% - 300px - 5px )",
float: "right",
border: "1px solid black",
boxSizing: "content-box"
} }>
<div style={{
marginBottom: 2,
boxSizing: "content-box",
height: "50px"
} }>
<input className={clsx("Calamity", hasError && "InputError")} style={{
marginTop: 2,
width: "80%",
height: "40px",
fontSize: "20pt",
return <div id="DisplayPane" style={{
width: "calc( 100% - 300px - 5px )",
float: "right",
border: "1px solid black",
boxSizing: "content-box"
} }>
<div style={{
marginBottom: 2,
boxSizing: "content-box",
height: "50px"
} }>
<input className={clsx("Calamity", hasError && "InputError")} style={{
marginTop: 2,
width: "80%",
height: "40px",
fontSize: "20pt",
}}value={commandString}
placeholder="Type command here..."
onChange={(e)=>{
const cmdString = e.target.value;
setCommandString(cmdString);
const parsedCommand = parseCommand(cmdString);
if(parsedCommand){
editCommand(parsedCommand);
setHasError(false);
}else{
setHasError(true);
}
}}></input>
<button onClick={()=>{
alert(`Available Commands:
}}value={commandString}
placeholder="Type command here..."
onChange={(e)=>{
const cmdString = e.target.value;
setCommandString(cmdString);
const parsedCommand = parseCommand(cmdString);
if(parsedCommand){
editCommand(parsedCommand);
setHasError(false);
}else{
setHasError(true);
}
}}></input>
<button onClick={()=>{
alert(`Available Commands:
Initialize X Item1 Y Item2 Z Item3 ...
Break X Slots - add X broken slots
Save
@ -94,38 +94,37 @@ Remove/Sell/Eat MEAL From Slot X
Limitations:
When you reload without altering inventory, things become weird. It won't be handled correctly and the commands will become red
`);
}}>Reference</button>
</div>
}}>Reference</button>
</div>
<div style={{
borderTop: "1px solid black",
borderBottom: "1px solid black",
marginBottom: 2,
boxSizing: "content-box",
height: "calc( ( 99vh - 60px ) / 3)",
overflowY: "auto"
} }>
<ItemList {...materialListProps}/>
</div>
<div style={{
borderTop: "1px solid black",
boxSizing: "content-box",
borderBottom: "1px solid black",
marginBottom: 2,
height: "calc( ( 99vh - 60px ) / 3)",
overflowY: "auto"
} }>
<ItemList {...mealListProps}/>
</div>
<div style={{
borderTop: "1px solid black",
boxSizing: "content-box",
height: "calc( ( 99vh - 60px ) / 3)",
overflowY: "auto"
} }>
<ItemList {...keyItemListProps}/>
</div>
<div style={{
borderTop: "1px solid black",
borderBottom: "1px solid black",
marginBottom: 2,
boxSizing: "content-box",
height: "calc( ( 99vh - 60px ) / 3)",
overflowY: "auto"
} }>
<ItemList {...materialListProps}/>
</div>
<div style={{
borderTop: "1px solid black",
boxSizing: "content-box",
borderBottom: "1px solid black",
marginBottom: 2,
height: "calc( ( 99vh - 60px ) / 3)",
overflowY: "auto"
} }>
<ItemList {...mealListProps}/>
</div>
<div style={{
borderTop: "1px solid black",
boxSizing: "content-box",
height: "calc( ( 99vh - 60px ) / 3)",
overflowY: "auto"
} }>
<ItemList {...keyItemListProps}/>
</div>
</div>
}
</div>;
};