diff --git a/javascripts/core/automator/automator-backend.js b/javascripts/core/automator/automator-backend.js
index db361cd8e..7b34d5f0e 100644
--- a/javascripts/core/automator/automator-backend.js
+++ b/javascripts/core/automator/automator-backend.js
@@ -1,3 +1,5 @@
+import { AutomatorPanels } from "../../../src/components/tabs/automator/AutomatorDocs";
+
/** @abstract */
class AutomatorCommandInterface {
constructor(id) {
@@ -180,6 +182,12 @@ export const AutomatorData = {
MAX_ALLOWED_SCRIPT_CHARACTERS: 10000,
MAX_ALLOWED_TOTAL_CHARACTERS: 60000,
+ MAX_ALLOWED_SCRIPT_NAME_LENGTH: 15,
+ MAX_ALLOWED_SCRIPT_COUNT: 20,
+ MAX_ALLOWED_CONSTANT_NAME_LENGTH: 20,
+ // Note that a study string with ALL studies in unshortened form without duplicated studies is ~230 characters
+ MAX_ALLOWED_CONSTANT_VALUE_LENGTH: 250,
+ MAX_ALLOWED_CONSTANT_COUNT: 30,
scriptIndex() {
return player.reality.automator.state.editorScript;
@@ -428,7 +436,7 @@ export const AutomatorBackend = {
step() {
if (this.stack.isEmpty) return false;
- for (let steps = 0; steps < 100; steps++) {
+ for (let steps = 0; steps < 100 && !this.hasJustCompleted; steps++) {
switch (this.runCurrentCommand()) {
case AUTOMATOR_COMMAND_STATUS.SAME_INSTRUCTION:
return true;
@@ -442,14 +450,22 @@ export const AutomatorBackend = {
case AUTOMATOR_COMMAND_STATUS.SKIP_INSTRUCTION:
this.nextCommand();
}
+
+ // We need to break out of the loop if the last commands are all SKIP_INSTRUCTION, or else it'll start
+ // trying to execute from an undefined stack if it isn't set to automatically repeat
+ if (!this.stack.top) this.hasJustCompleted = true;
}
// This should in practice never happen by accident due to it requiring 100 consecutive commands that don't do
// anything (looping a smaller group of no-ops will instead trigger the loop check every tick). Nevertheless,
// better to not have an explicit infinite loop so that the game doesn't hang if the player decides to be funny
- // and input 3000 comments in a row
- GameUI.notify.error("Automator halted - too many consecutive no-ops detected");
- AutomatorData.logCommandEvent("Automator halted due to excessive no-op commands", this.currentLineNumber);
+ // and input 3000 comments in a row. If hasJustCompleted is true, then we actually broke out because the end of
+ // the script has no-ops and we just looped through them, and therefore shouldn't show these messages
+ if (!this.hasJustCompleted) {
+ GameUI.notify.error("Automator halted - too many consecutive no-ops detected");
+ AutomatorData.logCommandEvent("Automator halted due to excessive no-op commands", this.currentLineNumber);
+ }
+
this.stop();
return false;
},
@@ -612,6 +628,26 @@ export const AutomatorBackend = {
this.reset(this.stack._data[0].commands);
},
+ changeModes(scriptID) {
+ Tutorial.moveOn(TUTORIAL_STATE.AUTOMATOR);
+ if (player.reality.automator.type === AUTOMATOR_TYPE.BLOCK) {
+ // This saves the script after converting it.
+ BlockAutomator.parseTextFromBlocks();
+ player.reality.automator.type = AUTOMATOR_TYPE.TEXT;
+ if (player.reality.automator.currentInfoPane === AutomatorPanels.BLOCKS) {
+ player.reality.automator.currentInfoPane = AutomatorPanels.COMMANDS;
+ }
+ } else {
+ const toConvert = AutomatorTextUI.editor.getDoc().getValue();
+ // Needs to be called to update the lines prop in the BlockAutomator object
+ BlockAutomator.fromText(toConvert);
+ AutomatorBackend.saveScript(scriptID, toConvert);
+ player.reality.automator.type = AUTOMATOR_TYPE.BLOCK;
+ player.reality.automator.currentInfoPane = AutomatorPanels.BLOCKS;
+ }
+ AutomatorHighlighter.clearAllHighlightedLines();
+ },
+
stack: {
_data: [],
push(commands) {
diff --git a/javascripts/core/automator/compiler.js b/javascripts/core/automator/compiler.js
index 9542150ed..39d5ec6ae 100644
--- a/javascripts/core/automator/compiler.js
+++ b/javascripts/core/automator/compiler.js
@@ -525,6 +525,10 @@ import { AutomatorLexer } from "./lexer";
foundChildren += nestedCommands
? nestedCommands.map(c => validatedCount(c) + 1).reduce((sum, val) => sum + val, 0)
: 0;
+
+ // Trailing newlines get turned into a command with a single EOF argument; we return -1 because one level up
+ // on the recursion this looks like an otherwise valid command and would be counted as such
+ if (key === "EOF") return -1;
}
return foundChildren;
};
diff --git a/javascripts/core/secret-formula/h2p.js b/javascripts/core/secret-formula/h2p.js
index 9b8e40aff..3f6ff0502 100644
--- a/javascripts/core/secret-formula/h2p.js
+++ b/javascripts/core/secret-formula/h2p.js
@@ -912,32 +912,42 @@ simply completing more Realities.
The Automator uses a scripting language that allows you to automate nearly the entire game.
-The interface has two panes, a script pane on the left where you enter the commands to automate the game, and a
-multiple function pane on the right.
+The interface has two panes, a script pane on the left where you enter the commands to automate the game and a
+pane on the right which has multiple panels which do many different things. The panels on the right side include:
-These functions include:
+- The command list, with information on all the commands available to you (some may not be available until you have
+ unlocked certain things)
-- a brief introduction to the Automator
+- The template creator, which allows you to generate premade script templates to accomplish certain tasks
-- the command list, with information on all the commands available to you
+- All errors in the current Automator script, as well as possible suggestions on how to fix them; the button for
+ this panel will light up if you have any errors in your current script
-- the template creator, which allows you to fill in premade templates to suit your own purposes
+- All recently executed commands, what those commands did, and how recently they were executed
-- a list of all errors in the current Automator script
+- A constant definition panel where you can define as shorthand for values within the automator (eg. special numbers
+ or certain Time Study trees)
-- a list of recently executed commands and what those commands did
-
-- if you are in the Block mode of the Automator, the command blocks used to write the script
+- If you are in the block mode of the Automator, there will also be a panel for the command blocks used to write the
+ script
-You can use as many rows as you need.
-
-Some commands are gated behind unlocks, which will only become visible once you have unlocked them.
+There are a few limitations to scripts in order to reduce lag and prevent save file size from getting too large.
+Individual scripts are limited to a maximum of ${formatInt(AutomatorData.MAX_ALLOWED_SCRIPT_CHARACTERS)}
+characters each, and all your scripts together cannot exceed a total character count of
+${formatInt(AutomatorData.MAX_ALLOWED_TOTAL_CHARACTERS)}. Any changes made to scripts while above these limits
+will not be saved if you refresh the page.
-You are able to create new scripts by clicking on the dropdown, and then clicking the "Create New..." option.
-To rename a script, click the pencil next to the dropdown. Scripts are automatically saved as you edit them.
-You can create as many scripts as you want.
+You are able to create new scripts by clicking on the dropdown, and then clicking the "Create new script..." option.
+To rename a script, click the pencil next to the dropdown and edit the name to whatever you with the script to be
+called (max ${formatInt(AutomatorData.MAX_ALLOWED_SCRIPT_NAME_LENGTH)} characters). You are allowed to create a
+maximum of ${formatInt(AutomatorData.MAX_ALLOWED_SCRIPT_COUNT)} scripts.
+
+
+Scripts are automatically saved as you edit them, but are not saved to your game save until the global autosave timer
+(ie. "Time since last save") triggers a full game save. If you make changes to scripts right before closing the game,
+you should wait until the game saves afterwards in order to not lose your changes.
If you want a larger workspace, you can press the button in the top right corner of the documentation pane of the
@@ -946,11 +956,15 @@ panes if you want more room to write your script or read documentation.
By pressing the top-right button on the script pane, you can switch to block mode, which may be more approachable if
-you are unfamiliar with programming. To enter commands in block mode, drag the box for the relevant command from the
-documentation pane into the script pane and drop it where you want the command to go. Commands can be freely
-rearranged by dragging the blocks around if needed. Clicking the top-right button in block mode will switch back to
-text mode, and switching between block and text mode will automatically translate your script as well.
-Note that scripts can only be converted into block mode if they have no errors!
+you are unfamiliar with programming. To enter commands in block mode, select the command block pane on the right and
+drag the box for the relevant command into the script pane and drop it where you want the command to go. Commands can be
+freely rearranged by dragging the blocks around if needed.
+
+
+Clicking the top-right button in block mode will switch back to text mode, and switching between block and text mode
+will automatically translate your script as well. If you have a script in text mode which has errors, the Automator
+may not be able to figure out what blocks to convert the lines with errors into. This may result in part of your
+script being lost if you attempt to convert a text script with errors into a bock script.
Just like your entire savefile, individual Automator scripts can be imported and exported from the game.
diff --git a/javascripts/core/storage/dev-migrations.js b/javascripts/core/storage/dev-migrations.js
index e1a62cfad..c58300842 100644
--- a/javascripts/core/storage/dev-migrations.js
+++ b/javascripts/core/storage/dev-migrations.js
@@ -1447,6 +1447,15 @@ GameStorage.devMigrations = {
}
player.reality.automator.scripts[key].content = lines.join("\n");
}
+
+ // Migrate IDs for all saves made during wave 3 testing, to prevent odd overwriting behavior on importing
+ const newScripts = {};
+ const oldScriptKeys = Object.keys(player.reality.automator.scripts);
+ for (let newID = 1; newID <= oldScriptKeys.length; newID++) {
+ newScripts[newID] = player.reality.automator.scripts[oldScriptKeys[newID - 1]];
+ newScripts[newID].id = newID;
+ }
+ player.reality.automator.scripts = newScripts;
}
],
diff --git a/public/stylesheets/automator.css b/public/stylesheets/automator.css
index fd050ecb9..b218e8bbd 100644
--- a/public/stylesheets/automator.css
+++ b/public/stylesheets/automator.css
@@ -14,7 +14,7 @@
--color-blockmator-block-background: #f5f5f5;
--color-blockmator-block-command: #401090;
--color-blockmator-block-required: #50aaaa;
- --color-blockmator-block-optional: #bbbbbb;
+ --color-blockmator-block-optional: #684700;
--color-blockmator-editor-background: white;
}
@@ -31,7 +31,7 @@
--color-blockmator-block-background: #000115;
--color-blockmator-block-command: #a142ff;
--color-blockmator-block-required: #005050;
- --color-blockmator-block-optional: #555555;
+ --color-blockmator-block-optional: #684700;
--color-blockmator-editor-background: black;
}
diff --git a/src/components/modals/StudyStringModal.vue b/src/components/modals/StudyStringModal.vue
index 4272bcd7d..724853377 100644
--- a/src/components/modals/StudyStringModal.vue
+++ b/src/components/modals/StudyStringModal.vue
@@ -25,10 +25,9 @@ export default {
};
},
computed: {
- // This modal is used by both study importing and preset editing but only has a prop actually passed in when
- // editing (which is the preset index). Needs to be an undefined check because index can be zero
+ // This modal is used by both study importing and preset editing, but is given an id of -1 when importing
isImporting() {
- return this.id === undefined;
+ return this.id === -1;
},
// This represents the state reached from importing into an empty tree
importedTree() {
diff --git a/src/components/modals/SwitchAutomatorEditorModal.vue b/src/components/modals/SwitchAutomatorEditorModal.vue
index a5fd852ec..c116a6cec 100644
--- a/src/components/modals/SwitchAutomatorEditorModal.vue
+++ b/src/components/modals/SwitchAutomatorEditorModal.vue
@@ -38,19 +38,7 @@ export default {
this.isCurrentlyBlocks = player.reality.automator.type === AUTOMATOR_TYPE.BLOCK;
},
toggleAutomatorMode() {
- const scriptID = this.currentScriptID;
- Tutorial.moveOn(TUTORIAL_STATE.AUTOMATOR);
- if (this.isCurrentlyBlocks) {
- // This saves the script after converting it.
- BlockAutomator.parseTextFromBlocks();
- player.reality.automator.type = AUTOMATOR_TYPE.TEXT;
- } else {
- const toConvert = AutomatorTextUI.editor.getDoc().getValue();
- // Needs to be called to update the lines prop in the BlockAutomator object
- BlockAutomator.fromText(toConvert);
- AutomatorBackend.saveScript(scriptID, toConvert);
- player.reality.automator.type = AUTOMATOR_TYPE.BLOCK;
- }
+ AutomatorBackend.changeModes(this.currentScriptID);
this.callback?.();
}
}
@@ -73,7 +61,8 @@ export default {
commands.
Warning: If these errors are caused by malformed loops or IFs, this may end up deleting large portions of
- your script! Changing editor modes currently will delete {{ quantifyInt("block", lostBlocks) }}!
+ your script! Changing editor modes currently will cause {{ quantifyInt("line", lostBlocks) }} of code to be
+ lost!
diff --git a/src/components/tabs/automator/AutomatorBlockEditor.vue b/src/components/tabs/automator/AutomatorBlockEditor.vue
index 553ff3044..bdef8d6a5 100644
--- a/src/components/tabs/automator/AutomatorBlockEditor.vue
+++ b/src/components/tabs/automator/AutomatorBlockEditor.vue
@@ -24,6 +24,7 @@ export default {
},
mounted() {
BlockAutomator.initialize();
+ AutomatorData.recalculateErrors();
BlockAutomator.editor.scrollTo(0, BlockAutomator.previousScrollPosition);
BlockAutomator.gutter.style.bottom = `${BlockAutomator.editor.scrollTop}px`;
},
diff --git a/src/components/tabs/automator/AutomatorBlockSingleInput.vue b/src/components/tabs/automator/AutomatorBlockSingleInput.vue
index 364417dcc..a479b90bc 100644
--- a/src/components/tabs/automator/AutomatorBlockSingleInput.vue
+++ b/src/components/tabs/automator/AutomatorBlockSingleInput.vue
@@ -234,7 +234,6 @@ export default {
}
this.recalculateErrorCount();
},
-
// This gets called whenever blocks are changed, but we also need to halt execution if the currently visible script
// is also the one being run
recalculateErrorCount() {
@@ -246,10 +245,17 @@ export default {
},
errorTooltip() {
if (!this.hasError || this.suppressTooltip) return undefined;
+
+ // We want to keep the verbose error info for the error panel, but we need to shorten it for the tooltips here
+ // The problematic errors all seem to have the same format, which we can explicitly modify
+ let errorInfo = this.errors.find(e => e.startLine === this.lineNumber).info;
+ errorInfo = errorInfo
+ .replaceAll("\n", "")
+ .replace(/Expecting: one of these possible Token sequences:.*but found: (.*)/ui, "Unexpected input format: $1");
return {
content:
`
- Inputs with a gray color are optional, while inputs with a + Inputs with a brown color are optional, while inputs with a teal color are required. For more details, check the Scripting Information pane.
diff --git a/src/components/tabs/automator/AutomatorDefinePage.vue b/src/components/tabs/automator/AutomatorDefinePage.vue index aa4104c79..f15eaabfe 100644 --- a/src/components/tabs/automator/AutomatorDefinePage.vue +++ b/src/components/tabs/automator/AutomatorDefinePage.vue @@ -13,7 +13,13 @@ export default { }, computed: { maxConstantCount() { - return 30; + return AutomatorData.MAX_ALLOWED_CONSTANT_COUNT; + }, + maxNameLength() { + return AutomatorData.MAX_ALLOWED_CONSTANT_NAME_LENGTH; + }, + maxValueLength() { + return AutomatorData.MAX_ALLOWED_CONSTANT_VALUE_LENGTH; }, }, methods: { @@ -29,7 +35,8 @@ export default {