add eventide; dnp allows multiple; update docs
This commit is contained in:
parent
d9d7fb6a7e
commit
15715e2a11
10 changed files with 246 additions and 159 deletions
61
src/App.tsx
61
src/App.tsx
|
@ -5,7 +5,6 @@ import "./App.css";
|
|||
import { CommandItem } from "./components/CommandItem";
|
||||
|
||||
import { DisplayPane } from "surfaces/DisplayPane";
|
||||
import { saveAs } from "data/FileSaver";
|
||||
import { parseCommand } from "core/Parser";
|
||||
import { ItemList } from "components/ItemList";
|
||||
import { TitledList } from "components/TitledList";
|
||||
|
@ -69,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<commands.length && !commands[nextCommandIndex].isValid()){
|
||||
nextCommandIndex++;
|
||||
}
|
||||
if(nextCommandIndex===commands.length-1){
|
||||
const arrCopy = [...commands];
|
||||
arrCopy.push(new CommandNop(""));
|
||||
setCommands(arrCopy);
|
||||
setDisplayIndex(arrCopy.length-1);
|
||||
}else{
|
||||
setDisplayIndex(Math.min(commands.length-1, displayIndex+1));
|
||||
|
||||
setDisplayIndex(Math.min(commands.length-1, nextCommandIndex));
|
||||
}
|
||||
}else if(e.code==="ArrowUp"){
|
||||
setDisplayIndex(Math.max(0, displayIndex-1));
|
||||
let nextCommandIndex = displayIndex-1;
|
||||
while(nextCommandIndex>=0 && !commands[nextCommandIndex].isValid()){
|
||||
nextCommandIndex--;
|
||||
}
|
||||
setDisplayIndex(Math.max(0, nextCommandIndex));
|
||||
}
|
||||
};
|
||||
}, [commands, displayIndex]);
|
||||
|
@ -219,7 +227,6 @@ export const App: React.FC = () => {
|
|||
setCommands(arrCopy);
|
||||
}}>(new)</CommandItem>
|
||||
|
||||
|
||||
</ol>
|
||||
</TitledList>
|
||||
|
||||
|
@ -306,7 +313,7 @@ export const App: React.FC = () => {
|
|||
commandText={commandText}
|
||||
setCommandText={(value)=>{
|
||||
if(value !== commandText){
|
||||
const commands = value.split("\n").map(parseCommand)
|
||||
const commands = value.split("\n").map(parseCommand);
|
||||
setCommands(commands);
|
||||
}
|
||||
}}
|
||||
|
@ -341,32 +348,32 @@ export const App: React.FC = () => {
|
|||
paddingInlineStart: 0
|
||||
}}>
|
||||
|
||||
<CommandItem onClick={()=>{
|
||||
<CommandItem onClick={()=>{
|
||||
const arrCopy = [...commands];
|
||||
arrCopy.splice(contextIndex, 0, new CommandNop(""));
|
||||
setCommands(arrCopy);
|
||||
setContextIndex(-1);
|
||||
}}>Insert Above</CommandItem>
|
||||
<CommandItem onClick={()=>{
|
||||
if(contextIndex > 0){
|
||||
const arrCopy = [...commands];
|
||||
arrCopy.splice(contextIndex, 0, new CommandNop(""));
|
||||
const temp = arrCopy[contextIndex];
|
||||
arrCopy[contextIndex] = arrCopy[contextIndex-1];
|
||||
arrCopy[contextIndex-1] = temp;
|
||||
setCommands(arrCopy);
|
||||
setContextIndex(-1);
|
||||
}}>Insert Above</CommandItem>
|
||||
<CommandItem onClick={()=>{
|
||||
if(contextIndex > 0){
|
||||
const arrCopy = [...commands];
|
||||
const temp = arrCopy[contextIndex];
|
||||
arrCopy[contextIndex] = arrCopy[contextIndex-1];
|
||||
arrCopy[contextIndex-1] = temp;
|
||||
setCommands(arrCopy);
|
||||
setContextIndex(-1);
|
||||
}
|
||||
}
|
||||
|
||||
}}>Move Up</CommandItem>
|
||||
<CommandItem onClick={()=>{
|
||||
if(confirm("Delete?")){
|
||||
setCommands(commands.filter((_,i)=>i!==contextIndex));
|
||||
if(displayIndex >= commands.length){
|
||||
setDisplayIndex(commands.length-1);
|
||||
}
|
||||
setContextIndex(-1);
|
||||
}}>Move Up</CommandItem>
|
||||
<CommandItem onClick={()=>{
|
||||
if(confirm("Delete?")){
|
||||
setCommands(commands.filter((_,i)=>i!==contextIndex));
|
||||
if(displayIndex >= commands.length){
|
||||
setDisplayIndex(commands.length-1);
|
||||
}
|
||||
}}>Delete</CommandItem>
|
||||
setContextIndex(-1);
|
||||
}
|
||||
}}>Delete</CommandItem>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -30,7 +30,7 @@ export const CommandItem: React.FC<CommandItemProps> = ({
|
|||
);
|
||||
|
||||
const clickHandler = useCallback((e: React.MouseEvent)=>{
|
||||
onClick(e.clientX, e.clientY)
|
||||
onClick(e.clientX, e.clientY);
|
||||
}, [onClick]);
|
||||
const contextMenuHandler = useCallback((e: React.MouseEvent)=>{
|
||||
if(onContextMenu){
|
||||
|
|
|
@ -232,20 +232,21 @@ const joinItemStackString = (initial: string, stacks: ItemStack[]): string => {
|
|||
};
|
||||
|
||||
export class CommandDaP extends CommandImpl {
|
||||
private count: number;
|
||||
private item: Item;
|
||||
private stacks: ItemStack[];
|
||||
|
||||
constructor(count: number, item: Item){
|
||||
constructor(stacks: ItemStack[]){
|
||||
super();
|
||||
this.count = count;
|
||||
this.item = item;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,6 +329,21 @@ export class CommandSync extends CommandImpl {
|
|||
}
|
||||
}
|
||||
|
||||
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 CommandNop extends CommandImpl {
|
||||
private text: string;
|
||||
constructor(text: string){
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
CommandCloseGame,
|
||||
CommandDaP,
|
||||
CommandEquip,
|
||||
CommandEventide,
|
||||
CommandInitialize,
|
||||
CommandNop,
|
||||
CommandReload,
|
||||
|
@ -124,11 +125,10 @@ export const parseCommand = (cmdString: string): Command => {
|
|||
}
|
||||
}
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,6 +182,9 @@ export const parseCommand = (cmdString: string): Command => {
|
|||
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 new CommandNop(cmdString);
|
||||
};
|
||||
|
|
|
@ -85,6 +85,7 @@ export class SimulationState {
|
|||
this.pouch.clearForReload();
|
||||
this.gameData.addAllToPouchOnReload(this.pouch);
|
||||
this.pouch.updateEquipmentDurability(this.gameData);
|
||||
this.isOnEventide = false;
|
||||
}
|
||||
|
||||
public useSaveForNextReload(name: string){
|
||||
|
@ -126,6 +127,22 @@ export class SimulationState {
|
|||
this.isOnEventide = false;
|
||||
}
|
||||
|
||||
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 syncGameDataWithPouch() {
|
||||
if(!this.isOnEventide){
|
||||
this.gameData.syncWith(this.pouch);
|
||||
|
|
|
@ -264,4 +264,12 @@ export class Slots {
|
|||
|
||||
}
|
||||
|
||||
// return how many slots are removed
|
||||
public clearAllButKeyItems(): number {
|
||||
const newslots = this.internalSlots.filter(stack=>itemToItemData(stack.item).type === ItemType.Key);
|
||||
const removedCount = this.internalSlots.length - newslots.length;
|
||||
this.internalSlots = newslots;
|
||||
return removedCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -105,4 +105,8 @@ export class VisibleInventory implements DisplayableInventory{
|
|||
public resetCount(): void {
|
||||
this.count = this.slots.length;
|
||||
}
|
||||
|
||||
public clearForEventide(): void {
|
||||
this.count-=this.slots.clearAllButKeyItems();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,12 +58,8 @@ export const DisplayPane: React.FC<DisplayPaneProps> = ({command,editCommand,dis
|
|||
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());
|
||||
}}></input>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,36 +1,35 @@
|
|||
import { TitledList } from "components/TitledList"
|
||||
import { parseCommand } from "core/Parser";
|
||||
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,
|
||||
interlaceInventory: boolean,
|
||||
setInterlaceInventory: (value: boolean)=>void,
|
||||
commandText: string,
|
||||
setCommandText: (value: string)=>void,
|
||||
}
|
||||
|
||||
export const OptionPage: React.FC<OptionPageProps> = ({
|
||||
interlaceInventory,
|
||||
setInterlaceInventory,
|
||||
commandText,
|
||||
setCommandText
|
||||
interlaceInventory,
|
||||
setInterlaceInventory,
|
||||
commandText,
|
||||
setCommandText
|
||||
}) => {
|
||||
const [currentText, setCurrentText] = useState<string>(commandText);
|
||||
const [fileName, setFileName] = useState<string>("");
|
||||
const uploadRef = useRef<HTMLInputElement>(null);
|
||||
const [currentText, setCurrentText] = useState<string>(commandText);
|
||||
const [fileName, setFileName] = useState<string>("");
|
||||
const uploadRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
return (
|
||||
<div className="OtherPage">
|
||||
return (
|
||||
<div className="OtherPage">
|
||||
|
||||
<input ref={uploadRef} id="Upload" type="File" hidden onChange={(e)=>{
|
||||
<input ref={uploadRef} id="Upload" type="File" hidden onChange={(e)=>{
|
||||
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);
|
||||
const fileName = file.name.endsWith(".txt") ? file.name.substring(0, file.name.length-4) : file.name;
|
||||
setFileName(fileName);
|
||||
file.text().then(text=>{
|
||||
setCurrentText(text);
|
||||
setCurrentText(text);
|
||||
setCommandText(text);
|
||||
});
|
||||
}
|
||||
|
@ -39,69 +38,69 @@ export const OptionPage: React.FC<OptionPageProps> = ({
|
|||
<TitledList title="Options">
|
||||
<div className="OtherPageContent">
|
||||
|
||||
<h3 className="Reference">
|
||||
Interlace Inventory with GameData
|
||||
<button className="MainButton" onClick={()=>{
|
||||
setInterlaceInventory(!interlaceInventory);
|
||||
}}>
|
||||
{interlaceInventory ? "ON" : "OFF"}
|
||||
</button>
|
||||
</h3>
|
||||
<h4 className="Reference">
|
||||
Toggle whether Visible Inventory should be displayed separetely from Game Data or interlaced.
|
||||
<h3 className="Reference">
|
||||
Interlace Inventory with GameData
|
||||
<button className="MainButton" onClick={()=>{
|
||||
setInterlaceInventory(!interlaceInventory);
|
||||
}}>
|
||||
{interlaceInventory ? "ON" : "OFF"}
|
||||
</button>
|
||||
</h3>
|
||||
<h4 className="Reference">
|
||||
Toggle whether Visible Inventory should be displayed separetely from Game Data or interlaced.
|
||||
</h4>
|
||||
|
||||
<h3 className="Reference">Import / Export</h3>
|
||||
<h4 className="Reference">
|
||||
You can also directly copy, paste, or edit the commands here
|
||||
<h3 className="Reference">Import / Export</h3>
|
||||
<h4 className="Reference">
|
||||
You can also directly copy, paste, or edit the commands here
|
||||
</h4>
|
||||
<p className="Reference">
|
||||
<button className="MainButton" onClick={()=>{
|
||||
if(uploadRef.current){
|
||||
uploadRef.current.click();
|
||||
}
|
||||
}}>
|
||||
Import
|
||||
</button>
|
||||
<button className="MainButton" onClick={()=>{
|
||||
saveAs(currentText, fileName+".txt" || "dupe.txt");
|
||||
}}>
|
||||
Export
|
||||
</button>
|
||||
<input
|
||||
className="MainInput"
|
||||
spellCheck={false}
|
||||
value={fileName}
|
||||
onChange={(e)=>{
|
||||
setFileName(e.target.value);
|
||||
}}
|
||||
placeholder="File name"
|
||||
/>
|
||||
<textarea
|
||||
className="MainInput"
|
||||
spellCheck={false}
|
||||
value={currentText}
|
||||
onChange={(e)=>{
|
||||
setCurrentText(e.target.value);
|
||||
}}
|
||||
<p className="Reference">
|
||||
<button className="MainButton" onClick={()=>{
|
||||
if(uploadRef.current){
|
||||
uploadRef.current.click();
|
||||
}
|
||||
}}>
|
||||
Import
|
||||
</button>
|
||||
<button className="MainButton" onClick={()=>{
|
||||
saveAs(currentText, fileName+".txt" || "dupe.txt");
|
||||
}}>
|
||||
Export
|
||||
</button>
|
||||
<input
|
||||
className="MainInput"
|
||||
spellCheck={false}
|
||||
value={fileName}
|
||||
onChange={(e)=>{
|
||||
setFileName(e.target.value);
|
||||
}}
|
||||
placeholder="File name"
|
||||
/>
|
||||
<textarea
|
||||
className="MainInput"
|
||||
spellCheck={false}
|
||||
value={currentText}
|
||||
onChange={(e)=>{
|
||||
setCurrentText(e.target.value);
|
||||
}}
|
||||
|
||||
/>
|
||||
{
|
||||
currentText !== commandText &&
|
||||
<>
|
||||
<button className="MainButton" onClick={()=>{
|
||||
setCommandText(currentText);
|
||||
}}>
|
||||
Save
|
||||
</button>
|
||||
<span className="Example">Don't forget to save changes</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{
|
||||
currentText !== commandText &&
|
||||
<>
|
||||
<button className="MainButton" onClick={()=>{
|
||||
setCommandText(currentText);
|
||||
}}>
|
||||
Save
|
||||
</button>
|
||||
<span className="Example">Don't forget to save changes</span>
|
||||
</>
|
||||
}
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</TitledList>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</TitledList>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -13,6 +13,9 @@ export const ReferencePage: React.FC = React.memo(()=>{
|
|||
getAllItems().map((item, i)=><h4 key={i} className="Reference">{item}</h4>)
|
||||
}
|
||||
<h2>Commands</h2>
|
||||
<p className="Reference">
|
||||
This is a list of available commands. All commands and items are case-sensitive
|
||||
</p>
|
||||
<h3 className="Reference">Initialize X item1 Y item2 Z item3 ...</h3>
|
||||
<h4 className="Reference">Used for initializing inventory before simulation</h4>
|
||||
<p className="Reference">
|
||||
|
@ -29,12 +32,17 @@ export const ReferencePage: React.FC = React.memo(()=>{
|
|||
</p>
|
||||
<p className="Reference Example">Example: Initialize 1 Apple 2 Axe 3 Slate 4 SpiritOrb</p>
|
||||
|
||||
<h3 className="Reference">Save / Save As NAME</h3>
|
||||
<h3 className="Reference">Save</h3>
|
||||
<h3 className="Reference2">Save As NAME</h3>
|
||||
|
||||
<h4 className="Reference">Simulates a hard save or auto save action</h4>
|
||||
<p className="Reference">
|
||||
Writes Game Data to the corresponding save slot. The auto saves are specified by NAME.
|
||||
You can have as many auto saves as you want in the simulator.
|
||||
</p>
|
||||
<p className="Reference">
|
||||
You cannot save on Eventide/ToTS. However, the simulator does not enforce that.
|
||||
</p>
|
||||
|
||||
<p className="Reference Example">Example 1: Save</p>
|
||||
<p className="Reference Example">Example 2: Save As MySave</p>
|
||||
|
@ -128,12 +136,18 @@ export const ReferencePage: React.FC = React.memo(()=>{
|
|||
<p className="Reference Example">Example 3: Sell 10 Apple 5 Diamond</p>
|
||||
<p className="Reference Example">Example 4: Sell 5 Apple From Slot 3</p>
|
||||
|
||||
<h3 className="Reference">D&P X item</h3>
|
||||
<h3 className="Reference">D&P X item1 Y item2 Z item3 ...</h3>
|
||||
<h4 className="Reference">Shortcut for drop and pick up, for sorting inventory</h4>
|
||||
<p className="Reference">
|
||||
This command drops X item from the first slot, then pick them up
|
||||
This command drops and pick up each item stack in the specified order.
|
||||
You can also repeat items if you are combining more than 2 slots.
|
||||
</p>
|
||||
<p className="Reference Example">Example: D&P 5 Diamond</p>
|
||||
<p className="Reference">
|
||||
You can only drop from slot 1 with this shortcut.
|
||||
</p>
|
||||
<p className="Reference Example">Example 1: D&P 5 Diamond</p>
|
||||
<p className="Reference Example">Example 2: D&P 20 Shaft 5 Diamond</p>
|
||||
<p className="Reference Example">Example 3: D&P 5 Diamond 10 Diamond</p>
|
||||
|
||||
<h3 className="Reference">Equip item</h3>
|
||||
<h3 className="Reference2">Equip item In Slot X</h3>
|
||||
|
@ -170,7 +184,7 @@ export const ReferencePage: React.FC = React.memo(()=>{
|
|||
</p>
|
||||
<p className="Reference Example">Example: Close Game</p>
|
||||
|
||||
<h3 className="Reference">Sync GameData</h3>
|
||||
<h3 className="Reference">Sync GameData</h3>
|
||||
<h4 className="Reference">Copy Visible Inventory to Game Data</h4>
|
||||
<p className="Reference">
|
||||
Usually done in game by opening and closing inventory.
|
||||
|
@ -183,6 +197,29 @@ export const ReferencePage: React.FC = React.memo(()=>{
|
|||
This command is currently broken
|
||||
</p>
|
||||
|
||||
<h3 className="Reference">Shoot X Arrow</h3>
|
||||
<h4 className="Reference">Simulates shooting arrow without opening inventory</h4>
|
||||
<p className="Reference">
|
||||
When reloading a save with desynced game data, the equipped weapon/bow/shield are automatically corrupted, but not the arrows.
|
||||
To corrupt the equipped arrow slot, you need to shoot an arrow.
|
||||
</p>
|
||||
<p className="Reference">
|
||||
This command does not let you select which arrow to shoot.
|
||||
When you reload a save, Link should have the last equipped arrow slot equipped in the overworld.
|
||||
<span className="Example">[needs confirmation]</span>
|
||||
</p>
|
||||
<p className="Reference Example">Example: Shoot 1 Arrow</p>
|
||||
|
||||
<h3 className="Reference">Enter/Exit Eventide</h3>
|
||||
<h4 className="Reference">Simulates entering/exiting Eventide or Trial of the Sword</h4>
|
||||
<p className="Reference">
|
||||
When entering Eventide or TotS, the entire inventory is cleared except for key items regardless of inventory count.
|
||||
While the challenge is active, none of the inventory changes are synced to game data.
|
||||
</p>
|
||||
<p className="Reference">
|
||||
When exiting the challenge, the game reloads the game data as if reloading a save
|
||||
</p>
|
||||
<p className="Reference Example">Example: Enter Eventide</p>
|
||||
</div>
|
||||
|
||||
</TitledList>
|
||||
|
|
Reference in a new issue