1
0
Fork 0

add eventide; dnp allows multiple; update docs

This commit is contained in:
iTNTPiston 2022-06-29 14:25:49 -07:00
parent d9d7fb6a7e
commit 15715e2a11
10 changed files with 246 additions and 159 deletions

View file

@ -5,7 +5,6 @@ import "./App.css";
import { CommandItem } from "./components/CommandItem"; import { CommandItem } from "./components/CommandItem";
import { DisplayPane } from "surfaces/DisplayPane"; import { DisplayPane } from "surfaces/DisplayPane";
import { saveAs } from "data/FileSaver";
import { parseCommand } from "core/Parser"; import { parseCommand } from "core/Parser";
import { ItemList } from "components/ItemList"; import { ItemList } from "components/ItemList";
import { TitledList } from "components/TitledList"; import { TitledList } from "components/TitledList";
@ -69,16 +68,25 @@ export const App: React.FC = () => {
useEffect(()=>{ useEffect(()=>{
window.onkeydown=(e)=>{ window.onkeydown=(e)=>{
if(e.code==="ArrowDown"){ 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]; const arrCopy = [...commands];
arrCopy.push(new CommandNop("")); arrCopy.push(new CommandNop(""));
setCommands(arrCopy); setCommands(arrCopy);
setDisplayIndex(arrCopy.length-1); setDisplayIndex(arrCopy.length-1);
}else{ }else{
setDisplayIndex(Math.min(commands.length-1, displayIndex+1));
setDisplayIndex(Math.min(commands.length-1, nextCommandIndex));
} }
}else if(e.code==="ArrowUp"){ }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]); }, [commands, displayIndex]);
@ -219,7 +227,6 @@ export const App: React.FC = () => {
setCommands(arrCopy); setCommands(arrCopy);
}}>(new)</CommandItem> }}>(new)</CommandItem>
</ol> </ol>
</TitledList> </TitledList>
@ -306,7 +313,7 @@ export const App: React.FC = () => {
commandText={commandText} commandText={commandText}
setCommandText={(value)=>{ setCommandText={(value)=>{
if(value !== commandText){ if(value !== commandText){
const commands = value.split("\n").map(parseCommand) const commands = value.split("\n").map(parseCommand);
setCommands(commands); setCommands(commands);
} }
}} }}
@ -341,32 +348,32 @@ export const App: React.FC = () => {
paddingInlineStart: 0 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]; const arrCopy = [...commands];
arrCopy.splice(contextIndex, 0, new CommandNop("")); const temp = arrCopy[contextIndex];
arrCopy[contextIndex] = arrCopy[contextIndex-1];
arrCopy[contextIndex-1] = temp;
setCommands(arrCopy); setCommands(arrCopy);
setContextIndex(-1); 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> }}>Move Up</CommandItem>
<CommandItem onClick={()=>{ <CommandItem onClick={()=>{
if(confirm("Delete?")){ if(confirm("Delete?")){
setCommands(commands.filter((_,i)=>i!==contextIndex)); setCommands(commands.filter((_,i)=>i!==contextIndex));
if(displayIndex >= commands.length){ if(displayIndex >= commands.length){
setDisplayIndex(commands.length-1); setDisplayIndex(commands.length-1);
}
setContextIndex(-1);
} }
}}>Delete</CommandItem> setContextIndex(-1);
}
}}>Delete</CommandItem>
</ul> </ul>
</div> </div>

View file

@ -30,7 +30,7 @@ export const CommandItem: React.FC<CommandItemProps> = ({
); );
const clickHandler = useCallback((e: React.MouseEvent)=>{ const clickHandler = useCallback((e: React.MouseEvent)=>{
onClick(e.clientX, e.clientY) onClick(e.clientX, e.clientY);
}, [onClick]); }, [onClick]);
const contextMenuHandler = useCallback((e: React.MouseEvent)=>{ const contextMenuHandler = useCallback((e: React.MouseEvent)=>{
if(onContextMenu){ if(onContextMenu){

View file

@ -232,20 +232,21 @@ const joinItemStackString = (initial: string, stacks: ItemStack[]): string => {
}; };
export class CommandDaP extends CommandImpl { export class CommandDaP extends CommandImpl {
private count: number; private stacks: ItemStack[];
private item: Item;
constructor(count: number, item: Item){ constructor(stacks: ItemStack[]){
super(); super();
this.count = count; this.stacks = stacks;
this.item = item;
} }
public execute(state: SimulationState): void { public execute(state: SimulationState): void {
state.remove(this.item, this.count, 0); this.stacks.forEach(({item,count})=>{
state.obtain(this.item, this.count); state.remove(item, count, 0);
state.obtain(item, count);
});
} }
public getDisplayString(): string { 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 { export class CommandNop extends CommandImpl {
private text: string; private text: string;
constructor(text: string){ constructor(text: string){

View file

@ -7,6 +7,7 @@ import {
CommandCloseGame, CommandCloseGame,
CommandDaP, CommandDaP,
CommandEquip, CommandEquip,
CommandEventide,
CommandInitialize, CommandInitialize,
CommandNop, CommandNop,
CommandReload, CommandReload,
@ -124,11 +125,10 @@ export const parseCommand = (cmdString: string): Command => {
} }
} }
//Shortcut for drop and pick up //Shortcut for drop and pick up
if (tokens.length === 3 && tokens[0] === "D&P" ){ if (tokens.length >2 && tokens[0] === "D&P" ){
const count = parseInt(tokens[1]); const stacks = parseItemStacks(tokens, 1);
const item = tokens[2]; if(stacks){
if(Number.isInteger(count) && item in Item){ return new CommandDaP(stacks);
return new CommandDaP(count, Item[item as keyof typeof Item]);
} }
} }
@ -182,6 +182,9 @@ export const parseCommand = (cmdString: string): Command => {
if(tokens.length===2 && tokens[0] === "Sync" && tokens[1] === "GameData"){ if(tokens.length===2 && tokens[0] === "Sync" && tokens[1] === "GameData"){
return new CommandSync("Sync 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); return new CommandNop(cmdString);
}; };

View file

@ -85,6 +85,7 @@ export class SimulationState {
this.pouch.clearForReload(); this.pouch.clearForReload();
this.gameData.addAllToPouchOnReload(this.pouch); this.gameData.addAllToPouchOnReload(this.pouch);
this.pouch.updateEquipmentDurability(this.gameData); this.pouch.updateEquipmentDurability(this.gameData);
this.isOnEventide = false;
} }
public useSaveForNextReload(name: string){ public useSaveForNextReload(name: string){
@ -126,6 +127,22 @@ export class SimulationState {
this.isOnEventide = false; 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() { public syncGameDataWithPouch() {
if(!this.isOnEventide){ if(!this.isOnEventide){
this.gameData.syncWith(this.pouch); this.gameData.syncWith(this.pouch);

View file

@ -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;
}
} }

View file

@ -105,4 +105,8 @@ export class VisibleInventory implements DisplayableInventory{
public resetCount(): void { public resetCount(): void {
this.count = this.slots.length; this.count = this.slots.length;
} }
public clearForEventide(): void {
this.count-=this.slots.clearAllButKeyItems();
}
} }

View file

@ -58,12 +58,8 @@ export const DisplayPane: React.FC<DisplayPaneProps> = ({command,editCommand,dis
const cmdString = e.target.value; const cmdString = e.target.value;
setCommandString(cmdString); setCommandString(cmdString);
const parsedCommand = parseCommand(cmdString); const parsedCommand = parseCommand(cmdString);
if(parsedCommand){ editCommand(parsedCommand);
editCommand(parsedCommand); setHasError(cmdString!=="" &&!cmdString.startsWith("#") && !parsedCommand.isValid());
setHasError(false);
}else{
setHasError(true);
}
}}></input> }}></input>
</div> </div>

View file

@ -1,36 +1,35 @@
import { TitledList } from "components/TitledList" import { TitledList } from "components/TitledList";
import { parseCommand } from "core/Parser";
import { saveAs } from "data/FileSaver"; import { saveAs } from "data/FileSaver";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
type OptionPageProps = { type OptionPageProps = {
interlaceInventory: boolean, interlaceInventory: boolean,
setInterlaceInventory: (value: boolean)=>void, setInterlaceInventory: (value: boolean)=>void,
commandText: string, commandText: string,
setCommandText: (value: string)=>void, setCommandText: (value: string)=>void,
} }
export const OptionPage: React.FC<OptionPageProps> = ({ export const OptionPage: React.FC<OptionPageProps> = ({
interlaceInventory, interlaceInventory,
setInterlaceInventory, setInterlaceInventory,
commandText, commandText,
setCommandText setCommandText
}) => { }) => {
const [currentText, setCurrentText] = useState<string>(commandText); const [currentText, setCurrentText] = useState<string>(commandText);
const [fileName, setFileName] = useState<string>(""); const [fileName, setFileName] = useState<string>("");
const uploadRef = useRef<HTMLInputElement>(null); const uploadRef = useRef<HTMLInputElement>(null);
return ( return (
<div className="OtherPage"> <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; const files = e.target.files;
if(files?.length && files[0]){ if(files?.length && files[0]){
const file = files[0]; const file = files[0];
const fileName = file.name.endsWith(".txt") ? file.name.substring(0, file.name.length-4) : file.name; const fileName = file.name.endsWith(".txt") ? file.name.substring(0, file.name.length-4) : file.name;
setFileName(fileName); setFileName(fileName);
file.text().then(text=>{ file.text().then(text=>{
setCurrentText(text); setCurrentText(text);
setCommandText(text); setCommandText(text);
}); });
} }
@ -39,69 +38,69 @@ export const OptionPage: React.FC<OptionPageProps> = ({
<TitledList title="Options"> <TitledList title="Options">
<div className="OtherPageContent"> <div className="OtherPageContent">
<h3 className="Reference"> <h3 className="Reference">
Interlace Inventory with GameData Interlace Inventory with GameData
<button className="MainButton" onClick={()=>{ <button className="MainButton" onClick={()=>{
setInterlaceInventory(!interlaceInventory); setInterlaceInventory(!interlaceInventory);
}}> }}>
{interlaceInventory ? "ON" : "OFF"} {interlaceInventory ? "ON" : "OFF"}
</button> </button>
</h3> </h3>
<h4 className="Reference"> <h4 className="Reference">
Toggle whether Visible Inventory should be displayed separetely from Game Data or interlaced. Toggle whether Visible Inventory should be displayed separetely from Game Data or interlaced.
</h4> </h4>
<h3 className="Reference">Import / Export</h3> <h3 className="Reference">Import / Export</h3>
<h4 className="Reference"> <h4 className="Reference">
You can also directly copy, paste, or edit the commands here You can also directly copy, paste, or edit the commands here
</h4> </h4>
<p className="Reference"> <p className="Reference">
<button className="MainButton" onClick={()=>{ <button className="MainButton" onClick={()=>{
if(uploadRef.current){ if(uploadRef.current){
uploadRef.current.click(); uploadRef.current.click();
} }
}}> }}>
Import Import
</button> </button>
<button className="MainButton" onClick={()=>{ <button className="MainButton" onClick={()=>{
saveAs(currentText, fileName+".txt" || "dupe.txt"); saveAs(currentText, fileName+".txt" || "dupe.txt");
}}> }}>
Export Export
</button> </button>
<input <input
className="MainInput" className="MainInput"
spellCheck={false} spellCheck={false}
value={fileName} value={fileName}
onChange={(e)=>{ onChange={(e)=>{
setFileName(e.target.value); setFileName(e.target.value);
}} }}
placeholder="File name" placeholder="File name"
/> />
<textarea <textarea
className="MainInput" className="MainInput"
spellCheck={false} spellCheck={false}
value={currentText} value={currentText}
onChange={(e)=>{ onChange={(e)=>{
setCurrentText(e.target.value); setCurrentText(e.target.value);
}} }}
/> />
{ {
currentText !== commandText && currentText !== commandText &&
<> <>
<button className="MainButton" onClick={()=>{ <button className="MainButton" onClick={()=>{
setCommandText(currentText); setCommandText(currentText);
}}> }}>
Save Save
</button> </button>
<span className="Example">Don't forget to save changes</span> <span className="Example">Don't forget to save changes</span>
</> </>
} }
</p> </p>
</div> </div>
</TitledList> </TitledList>
</div> </div>
) );
} };

View file

@ -13,6 +13,9 @@ export const ReferencePage: React.FC = React.memo(()=>{
getAllItems().map((item, i)=><h4 key={i} className="Reference">{item}</h4>) getAllItems().map((item, i)=><h4 key={i} className="Reference">{item}</h4>)
} }
<h2>Commands</h2> <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> <h3 className="Reference">Initialize X item1 Y item2 Z item3 ...</h3>
<h4 className="Reference">Used for initializing inventory before simulation</h4> <h4 className="Reference">Used for initializing inventory before simulation</h4>
<p className="Reference"> <p className="Reference">
@ -29,12 +32,17 @@ export const ReferencePage: React.FC = React.memo(()=>{
</p> </p>
<p className="Reference Example">Example: Initialize 1 Apple 2 Axe 3 Slate 4 SpiritOrb</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> <h4 className="Reference">Simulates a hard save or auto save action</h4>
<p className="Reference"> <p className="Reference">
Writes Game Data to the corresponding save slot. The auto saves are specified by NAME. 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. You can have as many auto saves as you want in the simulator.
</p> </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 1: Save</p>
<p className="Reference Example">Example 2: Save As MySave</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 3: Sell 10 Apple 5 Diamond</p>
<p className="Reference Example">Example 4: Sell 5 Apple From Slot 3</p> <p className="Reference Example">Example 4: Sell 5 Apple From Slot 3</p>
<h3 className="Reference">D&amp;P X item</h3> <h3 className="Reference">D&amp;P X item1 Y item2 Z item3 ...</h3>
<h4 className="Reference">Shortcut for drop and pick up, for sorting inventory</h4> <h4 className="Reference">Shortcut for drop and pick up, for sorting inventory</h4>
<p className="Reference"> <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>
<p className="Reference Example">Example: D&amp;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&amp;P 5 Diamond</p>
<p className="Reference Example">Example 2: D&amp;P 20 Shaft 5 Diamond</p>
<p className="Reference Example">Example 3: D&amp;P 5 Diamond 10 Diamond</p>
<h3 className="Reference">Equip item</h3> <h3 className="Reference">Equip item</h3>
<h3 className="Reference2">Equip item In Slot X</h3> <h3 className="Reference2">Equip item In Slot X</h3>
@ -170,7 +184,7 @@ export const ReferencePage: React.FC = React.memo(()=>{
</p> </p>
<p className="Reference Example">Example: Close Game</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> <h4 className="Reference">Copy Visible Inventory to Game Data</h4>
<p className="Reference"> <p className="Reference">
Usually done in game by opening and closing inventory. 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 This command is currently broken
</p> </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> </div>
</TitledList> </TitledList>