mirror of
https://github.com/IvarK/AntimatterDimensionsSourceCode.git
synced 2024-11-21 19:42:17 +00:00
G420 automator work (#616)
* WIP automator backend * backend is now capable of running a really tiny test program * Automator WIP * Make nested things in AutomatorBackend lower case * For now, translate strings to Decimals in stored scripts as possible * change "currency" to "comparable" in automator backend * rebase razen's automator work onto current master, fix rebase errors * block automator code that doesn't even work * update vue-draggable version * update index.html for new vue draggable * trying to get draggable to work * update sortable.min.js * more block automator work * plenty of more functionality like nesting and deleting rows * add while * text parse from blocks * parse blocks into automator and delete nested commands * scratch file with grammar * scratch file with grammar * Automator grammar with CST visitor that reports some semantic errors * add in codemirror css and js * checkpoint of automator work so far * automator state of affairs * refactor big crunch code a bit * automator work checkpoint * small fixes to non-automato stuff * script storage in player sketched out * update to loading scripts from save * Nuke old code * include font awesome stylesheet * Add GAME_LOAD event * automato can now run a simple script (triggered from console at the moment) * Added automato controls * add repeat support to automato * a little more work on automato buttons * refactor code for getting current EC completions a bit to support automato * automato working well enough to run an automated reality script * Beautify automator UI * fix issue with numeric vars in automato; remove some console logs * fix auto eternity and script looping logic in automato * fix pause with variable-defined time and plain pause command * add support for black hole and stored time on/off in automato * crude support for renaming scripts * hide rename icon when renaming * support creating and opening scripts in automato * new tomato tutorials * add store time use to automator * add more automato command docs * add working tooltips for automato buttons * indicate which script is running in dropdown menu * remove more old automator cruft * remove console printouts * fix active button colors in automato * indicate active automato line in gutter
This commit is contained in:
parent
c5e23a6f58
commit
4fe1131e55
@ -108,6 +108,7 @@
|
||||
"computed-property-spacing": "error",
|
||||
"consistent-this": "error",
|
||||
"func-call-spacing": "error",
|
||||
"guard-for-in": "warn",
|
||||
"id-blacklist": [
|
||||
"error",
|
||||
"ret",
|
||||
|
251
index.html
251
index.html
@ -8,14 +8,20 @@
|
||||
<script type="text/javascript" src='https://cdn1.kongregate.com/javascripts/kongregate_api.js'></script>
|
||||
<script type="text/javascript" src="javascripts/lib/jquery-3.2.1.min.js"></script>
|
||||
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/codemirror/codemirror.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/codemirror/show-hint.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/codemirror/lint.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/codemirror/panda-syntax.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/codemirror/liquibyte.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/components.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/ad-slider-component.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/glyphs.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/styles.css?3">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/automator.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/time-studies.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/tooltips.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/vis.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/tln.css">
|
||||
<script>
|
||||
(function(i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
@ -110,219 +116,7 @@
|
||||
<reality-upgrades-tab v-if="view.tabs.current === 'reality-tab' && view.tabs.reality.subtab === 'upgrades'"></reality-upgrades-tab>
|
||||
</div>
|
||||
<div id="automation" class="realitytab" style="display: none;">
|
||||
<span id="automatorUnlock"><br><br><br>You need to get 4 Realities to unlock the automator</span>
|
||||
<div class="automator-container">
|
||||
<div id="rowsAvailable">Your automator can use 4 rows; next row at 9 realities.</div>
|
||||
<canvas id="automatorTreeCanvas"></canvas>
|
||||
<div>
|
||||
<div style="display: flex; flex-direction: row; justify-content: center; align-items: center; margin-bottom: 3px;">
|
||||
<p id="automatorloadsavetext">load:</p>
|
||||
<automator-save-load-button v-for="saveslot in 3" :key="saveslot" :saveslot="saveslot"></automator-save-load-button>
|
||||
<label for="automatorOn" style="margin-left: 40px;">on:</label>
|
||||
<input type="checkbox" name="automatorOn" id="automatorOn" onChange="automatorOnOff()">
|
||||
</div>
|
||||
<textarea name="automator" id="automator" cols="30" spellcheck="false" onChange="updateAutomatorState()"></textarea>
|
||||
</div>
|
||||
<table class="table" id="studytable">
|
||||
<tr>
|
||||
<td>
|
||||
<automator-shop-button name="WAIT">
|
||||
<template slot="button-content">
|
||||
WAIT<br>
|
||||
Cost: 1 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
WAIT [target] [amount]
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
Waits for something to pass a certain point, "Wait EP 1e2000" waits until you have 1e2000 ep. "Wait time 10"
|
||||
waits 10 seconds.
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
<td><button class="automatorinstruction" style="opacity: 0;"></button></td>
|
||||
<td>
|
||||
<automator-shop-button name="BUY">
|
||||
<template slot="button-content">
|
||||
BUY<br>
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
BUY [target] [amount/ID]
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
Buys stuff, "Buy study 11" buys the first timestudy. Hold shift to see study IDs.</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<table class="table" id="studytable">
|
||||
<tr>
|
||||
<td><automator-shop-button name="IF">
|
||||
</automator-shop-button></td>
|
||||
<td><automator-shop-button name="ANTIMATTER"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="EP"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="STUDY"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="STUDYUNTIL"></automator-shop-button></td>
|
||||
<td>
|
||||
<automator-shop-button name="STUDYPATH">
|
||||
<template slot="button-content">
|
||||
STUDYPATH<br>Cost: 50 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
BUY STUDYPATH [all/none] [study ids and path names]
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
Buys studies top-down using arguments after the mode specification. The 2 modes specify to buy "all" or
|
||||
"none" of the studies on non-locked splits (incl. 62 and 33). Locked paths must be specified by ID # or split
|
||||
name.
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table" id="studytable">
|
||||
<tr>
|
||||
<td><automator-shop-button name="GOTO"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="IP"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="REPLICANTI"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="TTEP"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="TTIP"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="STUDYIMPORT"></automator-shop-button></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table" id="studytable">
|
||||
<tr>
|
||||
<td><automator-shop-button name="TIME"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="RG"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="TTAM"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="TTMAX"></automator-shop-button></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table" id="studytable">
|
||||
<tr>
|
||||
<td>
|
||||
<automator-shop-button name="LOAD">
|
||||
<template slot="button-content">
|
||||
LOAD<br>Cost: 30 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
LOAD [automatorscriptnumber]
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
You can load different automatorscripts with this, "Load 2" loads the second script
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
<td><automator-shop-button name="MAX"></automator-shop-button></td>
|
||||
<td>
|
||||
<automator-shop-button name="TOGGLE">
|
||||
<template slot="button-content">
|
||||
TOGGLE<br>Cost: 30 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
TOGGLE [autobuyer]
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
Toggles autobuyers on and off, "Toggle D1" for example. Targets include: DX, Tickspeed, Galaxy, Dimboost,
|
||||
Infinity, Eternity, Sacrifice, and RG
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
<td>
|
||||
<automator-shop-button name="UNLOCK">
|
||||
<template slot="button-content">
|
||||
UNLOCK<br>Cost: 30 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
UNLOCK [target] [(id)]
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
Unlocks either ECs "Unlock EC 3", or dilation "Unlock dilation"
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<automator-shop-button name="RESPEC">
|
||||
<template slot="button-content">
|
||||
RESPEC<br>Cost: 30 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
RESPEC
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
Toggles respec on, so next eternity your time studies will reset.
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
<td>
|
||||
<automator-shop-button name="ETERNITY">
|
||||
<template slot="button-content">
|
||||
ETERNITY<br>Cost: 30 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
ETERNITY
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
Does an eternity if able, if not it waits until it can eternity.
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
<td><automator-shop-button name="UNLOCK_DILATION"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="UNLOCK_EC"></automator-shop-button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<automator-shop-button name="CHANGE">
|
||||
<template slot="button-content">
|
||||
CHANGE<br>Cost: 30 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
CHANGE [IPAUTOBUYER/EPAUTOBUYER] [amount]
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
You can change the amount in your autobuyers "CHANGE IPAUTOBUYER 1e2000" for example
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
<td>
|
||||
<automator-shop-button name="STOP">
|
||||
<template slot="button-content">
|
||||
STOP<br>Cost: 30 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
STOP
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
Stops the whole automator.
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
<td><button class="automatorinstruction" style="opacity: 0;"></button></td>
|
||||
<td>
|
||||
<automator-shop-button name="START">
|
||||
<template slot="button-content">
|
||||
START<br>Cost: 30 RM
|
||||
</template>
|
||||
<template slot="tooltip-header">
|
||||
START [target] [(id)]
|
||||
</template>
|
||||
<template slot="tooltip-content">
|
||||
Starts either ECs "Start EC 3", or dilation "Start dilation"
|
||||
</template>
|
||||
</automator-shop-button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><automator-shop-button name="IPAUTOBUYER"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="EPAUTOBUYER"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="START_DILATION"></automator-shop-button></td>
|
||||
<td><automator-shop-button name="START_EC"></automator-shop-button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<automator-tab v-if="view.tabs.current === 'reality-tab' && view.tabs.reality.subtab === 'automation'"></automator-tab>
|
||||
</div>
|
||||
<div id="blackhole" class="realitytab" style="display: none;">
|
||||
<black-hole-tab v-if="view.tabs.current === 'reality-tab' && view.tabs.reality.subtab === 'blackhole'"></black-hole-tab>
|
||||
@ -367,12 +161,18 @@
|
||||
<script type="text/javascript" src="javascripts/lib/sha512.min.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/deepmerge.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/Sortable.min.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/vuedraggable.min.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/vuedraggable.umd.min.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/Tween.min.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/gamma.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/vue-split-pane.min.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/chevrotain.min.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/codemirror.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/simple.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/show-hint.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/lint.js"></script>
|
||||
<script type="text/javascript" src="javascripts/lib/active-line.js"></script>
|
||||
<script type="text/javascript" src="PlayFab/PlayFabClientApi.js"></script>
|
||||
<script type="text/javascript" src="javascripts/DragDropTouch.js"></script>
|
||||
<script type="text/javascript" src="javascripts/tln.js"></script>
|
||||
<script type="text/javascript" src="javascripts/longpress.js"></script>
|
||||
|
||||
<script type="text/javascript" src="javascripts/core/polyfill.js"></script>
|
||||
@ -383,6 +183,7 @@
|
||||
<script type="text/javascript" src="javascripts/core/constants.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/math.js"></script>
|
||||
|
||||
<script type="text/javascript" src="javascripts/core/automator/automator-backend.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/secret-formula/effects.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/secret-formula/game-database.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/glyph-effects.js"></script>
|
||||
@ -439,6 +240,7 @@
|
||||
<script type="text/javascript" src="javascripts/core/secret-formula/eternity/dilation-upgrades.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/secret-formula/reality/reality-upgrades.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/secret-formula/reality/perks.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/secret-formula/reality/automator.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/secret-formula/reality/glyph-sacrifices.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/secret-formula/celestials/effarig.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/secret-formula/celestials/v.js"></script>
|
||||
@ -597,6 +399,17 @@
|
||||
<script type="text/javascript" src="javascripts/components/reality/black-hole/black-hole-upgrade-button.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/black-hole/black-hole-upgrade-row.js"></script>
|
||||
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/automator-tab.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/automator-button.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/automator-editor.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/docs/automator-docs.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/docs/automator-docs-main-page.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/docs/automator-man-page.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/automator-blocks.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/automator-block-editor.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/automator-block-tab.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/reality/automator/automator-single-block.js"></script>
|
||||
|
||||
<script type="text/javascript" src="javascripts/components/modals/modal-popup.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/modals/modal-shortcuts.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/modals/modal-message.js"></script>
|
||||
@ -622,6 +435,11 @@
|
||||
<script type="text/javascript" src="javascripts/components/new-ui/sidebar-resources/sidebar-ep.js"></script>
|
||||
<script type="text/javascript" src="javascripts/components/new-ui/sidebar-resources/sidebar-rm.js"></script>
|
||||
|
||||
<script type="text/javascript" src="javascripts/core/automator/automator-codemirror.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/automator/lexer.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/automator/automator-commands.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/automator/parser.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/automator/compiler.js"></script>
|
||||
|
||||
<script type="text/javascript" src="javascripts/core/app/ui.init.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/app/player-progress.js"></script>
|
||||
@ -651,7 +469,6 @@
|
||||
<script type="text/javascript" src="javascripts/core/replicanti.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/timestudies.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/rm.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/automator.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/perks.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/canvases.js"></script>
|
||||
<script type="text/javascript" src="javascripts/core/dilation.js"></script>
|
||||
|
@ -74,21 +74,21 @@ Vue.component("automator-shop-button", {
|
||||
"automatorinstruction": this.available,
|
||||
"automatorinstructionlocked": !this.available
|
||||
};
|
||||
classObject[Automator.Instructions[this.name].type] = true;
|
||||
classObject[AutomatorInstructions.Instructions[this.name].type] = true;
|
||||
return classObject;
|
||||
},
|
||||
// This assumes prices are constant
|
||||
cost() {
|
||||
return Automator.Instructions[this.name].price;
|
||||
return AutomatorInstructions.Instructions[this.name].price;
|
||||
},
|
||||
instructionID() {
|
||||
return Automator.Instructions[this.name].id;
|
||||
return AutomatorInstructions.Instructions[this.name].id;
|
||||
},
|
||||
domID() {
|
||||
return "automator" + Automator.Instructions[this.name].id;
|
||||
return "automator" + AutomatorInstructions.Instructions[this.name].id;
|
||||
},
|
||||
displayName() {
|
||||
return Automator.Instructions[this.name].displayName;
|
||||
return AutomatorInstructions.Instructions[this.name].displayName;
|
||||
},
|
||||
popoverTrigger() {
|
||||
// If the html did not specify a tooltip, we set the trigger to
|
||||
|
@ -34,7 +34,7 @@ Vue.component("game-header-big-crunch-button", {
|
||||
`<button
|
||||
v-if="isVisible"
|
||||
class="o-prestige-btn o-prestige-btn--big-crunch l-game-header__big-crunch-btn"
|
||||
onclick="bigCrunchReset()"
|
||||
onclick="bigCrunchResetRequest()"
|
||||
>
|
||||
<b>Big Crunch for {{shortenDimensions(gainedIP)}} Infinity {{ "point" | pluralize(gainedIP) }}.</b>
|
||||
<template v-if="isPeakIPPMVisible">
|
||||
|
@ -63,37 +63,13 @@ Vue.component("game-header-eternity-button", {
|
||||
this.peakEPPM.copyFrom(EPminpeak);
|
||||
},
|
||||
updateChallengeWithRUPG() {
|
||||
const currentEC = EternityChallenge.current;
|
||||
const currentCompletions = currentEC.completions;
|
||||
this.fullyCompleted = currentCompletions === 5;
|
||||
const status = EternityChallenge.gainedCompletionStatus();
|
||||
this.fullyCompleted = status.fullyCompleted;
|
||||
if (this.fullyCompleted) return;
|
||||
let gainedCompletions = 1;
|
||||
while (
|
||||
player.infinityPoints.gte(currentEC.goalAtCompletions(currentCompletions + gainedCompletions)) &&
|
||||
gainedCompletions < 5 - currentCompletions
|
||||
) {
|
||||
gainedCompletions++;
|
||||
}
|
||||
const totalCompletions = currentCompletions + gainedCompletions;
|
||||
let maxEC4Valid = 0;
|
||||
if(player.infinitied.lte(16)) maxEC4Valid = 5 - Math.ceil(player.infinitied.toNumber() / 4);
|
||||
if (EternityChallenge(4).isRunning && totalCompletions >= maxEC4Valid && gainedCompletions > 1) {
|
||||
this.gainedCompletions = Math.min(totalCompletions, maxEC4Valid) - currentCompletions;
|
||||
this.failedCondition = "(Too many infinities for more)";
|
||||
return;
|
||||
}
|
||||
|
||||
const maxEC12Valid = 5 - Math.floor(player.thisEternity / 200);
|
||||
if (EternityChallenge(12).isRunning && totalCompletions >= maxEC12Valid && gainedCompletions > 1) {
|
||||
this.gainedCompletions = Math.min(totalCompletions, maxEC12Valid) - currentCompletions;
|
||||
this.failedCondition = "(Too slow for more)";
|
||||
return;
|
||||
}
|
||||
|
||||
this.gainedCompletions = gainedCompletions;
|
||||
this.failedCondition = undefined;
|
||||
this.hasMoreCompletions = totalCompletions < 5;
|
||||
this.nextGoalAt.copyFrom(currentEC.goalAtCompletions(totalCompletions));
|
||||
this.gainedCompletions = status.gainedCompletions;
|
||||
this.failedCondition = status.failedCondition;
|
||||
this.hasMoreCompletions = status.hasMoreCompletions;
|
||||
this.nextGoalAt.copyFrom(status.nextGoalAt);
|
||||
}
|
||||
},
|
||||
template:
|
||||
|
@ -23,7 +23,7 @@ Vue.component("new-ui", {
|
||||
<component :is="$viewModel.page" v-if="!showCrunch"></component>
|
||||
<div v-else>
|
||||
<h3>The world has collapsed due to excess antimatter.</h3>
|
||||
<button class="btn-big-crunch" onclick="bigCrunchReset()">Big Crunch</button>
|
||||
<button class="btn-big-crunch" onclick="bigCrunchResetRequest()">Big Crunch</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@ Vue.component("sidebar-ip", {
|
||||
},
|
||||
infinity() {
|
||||
if (this.showCrunch) {
|
||||
bigCrunchReset();
|
||||
bigCrunchResetRequest();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -0,0 +1,69 @@
|
||||
"use strict";
|
||||
|
||||
function parseBlock(block, indentation = 0) {
|
||||
let ret = "\t".repeat(indentation) + block.cmd
|
||||
|
||||
if (block.target) ret += " " + block.target
|
||||
if (block.secondaryTarget) ret += " " + block.secondaryTarget
|
||||
if (block.inputValue) ret += " " + block.inputValue
|
||||
if (block.cmd == "IF" || block.cmd == "WHILE") ret += " " + "{"
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
function parseLines(l, indentation = 0) {
|
||||
let lines = []
|
||||
for (let i = 0; i < l.length; i++) {
|
||||
lines.push(parseBlock(l[i], indentation))
|
||||
if (l[i].cmd == "IF" || l[i].cmd == "WHILE") {
|
||||
lines.push( ...parseLines(l[i].nest, indentation + 1) )
|
||||
lines.push("\t".repeat(indentation) + "}")
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
Vue.component("automator-block-editor", {
|
||||
data() {
|
||||
return {
|
||||
lines: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lineNumbersCount() {
|
||||
return Math.max(this.lines.length, 1);
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
updateBlock(block, id) {
|
||||
this.lines[this.lines.findIndex( x => x.id == id)] = block
|
||||
console.log(this.lines)
|
||||
},
|
||||
deleteBlock(id) {
|
||||
let idx = this.lines.findIndex( x => x.id == id)
|
||||
this.lines.splice(idx, 1)
|
||||
},
|
||||
parseLines() {
|
||||
$("#automator").val( parseLines(this.lines).join("\n") )
|
||||
updateState()
|
||||
console.log(parseLines(this.lines))
|
||||
}
|
||||
},
|
||||
template:
|
||||
`<div class="c-automator-block-editor l-automator-editor">
|
||||
<button @click="parseLines">Parse <br>into <br>automator</button>
|
||||
<draggable v-model="lines" group="code-blocks" class="c-automator-blocks">
|
||||
<automator-single-block
|
||||
v-for="(block, index) in lines"
|
||||
:key="block.id"
|
||||
:lineNumber="index"
|
||||
:block="block"
|
||||
:updateBlock="updateBlock"
|
||||
:deleteBlock="deleteBlock"></automator-single-block>
|
||||
</draggable>
|
||||
</div>`
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
"use strict";
|
||||
|
||||
Vue.component("automator-block-tab", {
|
||||
data: function() {
|
||||
return {
|
||||
lines: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
//console.log(this.lines.length)
|
||||
},
|
||||
updateBlocks(lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
},
|
||||
template:
|
||||
// TODO: fix css classes - they were adjusted for text editor tab
|
||||
`<div class="c-automator l-automator l-automator-tab">
|
||||
<split-pane :min-percent="20" :default-percent="80" split="vertical" class="_-automator-split-pane-fix">
|
||||
<automator-block-editor slot="paneL"/>
|
||||
<automator-blocks slot="paneR"/>
|
||||
</split-pane>
|
||||
</div>`
|
||||
});
|
89
javascripts/components/reality/automator/automator-blocks.js
Normal file
89
javascripts/components/reality/automator/automator-blocks.js
Normal file
@ -0,0 +1,89 @@
|
||||
const automator_blocks = [
|
||||
{
|
||||
cmd: 'WAIT',
|
||||
targets: ['IP', 'EP', 'AM', 'TIME', 'REPLICANTI', 'RG', 'TT'],
|
||||
hasInput: true
|
||||
} , {
|
||||
cmd: 'BUY',
|
||||
targets: ['STUDY', 'STUDYUNTIL', 'TTIP', 'TTEP', 'TTAM', 'TTMAX'],
|
||||
hasInput: true,
|
||||
targetsWithoutInput: ['TTMAX']
|
||||
}, {
|
||||
cmd: 'IF',
|
||||
targets: ['IP', 'EP', 'AM', 'REPLICANTI', 'RG', 'TT'],
|
||||
secondaryTargets: ['=', '<', '>', '>=', '<=', '!='],
|
||||
hasInput: true,
|
||||
nested: true
|
||||
}, {
|
||||
cmd: 'WHILE',
|
||||
targets: ['IP', 'EP', 'AM', 'REPLICANTI', 'RG', 'TT'],
|
||||
secondaryTargets: ['=', '<', '>', '>=', '<=', '!='],
|
||||
hasInput: true,
|
||||
nested: true
|
||||
}, {
|
||||
cmd: 'GOTO',
|
||||
hasInput: true
|
||||
}, {
|
||||
cmd: 'UNLOCK',
|
||||
targets: ['EC', 'DILATION'],
|
||||
hasInput: true,
|
||||
targetsWithoutInput: ['DILATION']
|
||||
}, {
|
||||
cmd: 'START',
|
||||
targets: ['EC', 'DILATION'],
|
||||
hasInput: true,
|
||||
targetsWithoutInput: ['DILATION']
|
||||
}, {
|
||||
cmd: 'CHANGE',
|
||||
targets: ['IP-autobuyer', 'EP-autobuyer'],
|
||||
hasInput: true
|
||||
}, {
|
||||
cmd: 'RESPEC'
|
||||
}, {
|
||||
cmd: 'ETERNITY'
|
||||
}, {
|
||||
cmd: 'STOP'
|
||||
}, {
|
||||
cmd: 'LOAD',
|
||||
hasInput: true
|
||||
}, {
|
||||
cmd: 'BLOCK',
|
||||
getTargets: () => [], // TODO
|
||||
targets: []
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
|
||||
Vue.component("automator-blocks", {
|
||||
data() {
|
||||
return {
|
||||
blocks: automator_blocks
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clone(block) {
|
||||
let b = {
|
||||
...block,
|
||||
id: UIID.next()
|
||||
}
|
||||
|
||||
if (block.nested) b.nest = []
|
||||
if (block.targets) b.target = ""
|
||||
if (block.hasInput) b.inputValue = ""
|
||||
if (block.secondaryTargets) b.secondaryTarget = ""
|
||||
return b
|
||||
}
|
||||
},
|
||||
template:
|
||||
`<div class="c-automator-docs">
|
||||
<draggable
|
||||
:list="blocks"
|
||||
:group="{ name: 'code-blocks', pull: 'clone', put: false }"
|
||||
:sort="false"
|
||||
:clone="clone"
|
||||
class="c-automator-command-list">
|
||||
<div v-for="block in blocks" :key="block.id" class="o-automator-command"> {{ block.cmd }}</div>
|
||||
</draggable>
|
||||
</div>`
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
Vue.component("automator-button", {
|
||||
template: `
|
||||
<button class="c-automator__button l-automator__button fas" @click="emitClick" />
|
||||
`
|
||||
});
|
256
javascripts/components/reality/automator/automator-editor.js
Normal file
256
javascripts/components/reality/automator/automator-editor.js
Normal file
@ -0,0 +1,256 @@
|
||||
"use strict";
|
||||
|
||||
const AutomatorUI = {
|
||||
wrapper: null,
|
||||
editor: null,
|
||||
mode: {
|
||||
mode: "automato",
|
||||
lint: "automato",
|
||||
lineNumbers: true,
|
||||
styleActiveLine: true,
|
||||
theme: "liquibyte",
|
||||
},
|
||||
documents: {},
|
||||
initialize() {
|
||||
if (this.container) return;
|
||||
this.container = document.createElement("div");
|
||||
this.container.className = "l-automator-editor__codemirror-container";
|
||||
const textArea = document.createElement("textarea");
|
||||
this.container.appendChild(textArea);
|
||||
this.editor = CodeMirror.fromTextArea(textArea, this.mode);
|
||||
this.editor.on("keydown", (editor, event) => {
|
||||
if (editor.state.completionActive) return;
|
||||
const key = event.key;
|
||||
if (event.ctrlKey || event.altKey || event.metaKey || !/^[a-zA-Z0-9 \t]$/u.test(key)) return;
|
||||
CodeMirror.commands.autocomplete(editor, null, { completeSingle: false });
|
||||
});
|
||||
this.editor.on("change", editor => {
|
||||
const scriptID = ui.view.tabs.reality.automator.editorScriptID;
|
||||
AutomatorBackend.saveScript(scriptID, editor.getDoc().getValue());
|
||||
});
|
||||
EventHub.ui.on(GameEvent.GAME_LOAD, () => this.documents = {});
|
||||
}
|
||||
};
|
||||
|
||||
Vue.component("automator-editor", {
|
||||
data() {
|
||||
return {
|
||||
code: null,
|
||||
activeLine: 0,
|
||||
isRunning: false,
|
||||
isPaused: false,
|
||||
repeatOn: false,
|
||||
editingName: false,
|
||||
runningScriptID: 0,
|
||||
scripts: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
activeLine(newVal, oldVal) {
|
||||
if (oldVal > 0) {
|
||||
AutomatorUI.editor.removeLineClass(oldVal - 1, "background", "c-automator-editor__active-line");
|
||||
AutomatorUI.editor.removeLineClass(oldVal - 1, "gutter", "c-automator-editor__active-line-gutter");
|
||||
}
|
||||
if (newVal > 0) {
|
||||
AutomatorUI.editor.addLineClass(newVal - 1, "background", "c-automator-editor__active-line");
|
||||
AutomatorUI.editor.addLineClass(newVal - 1, "gutter", "c-automator-editor__active-line-gutter");
|
||||
}
|
||||
},
|
||||
fullScreen() {
|
||||
this.$nextTick(() => AutomatorUI.editor.refresh());
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fullScreen() {
|
||||
return this.$viewModel.tabs.reality.automator.fullScreen;
|
||||
},
|
||||
currentScriptID: {
|
||||
get() {
|
||||
return this.$viewModel.tabs.reality.automator.editorScriptID;
|
||||
},
|
||||
set(value) {
|
||||
this.$viewModel.tabs.reality.automator.editorScriptID = value;
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
get() {
|
||||
return this.$viewModel.tabs.reality.automator.mode;
|
||||
},
|
||||
set(value) {
|
||||
this.$viewModel.tabs.reality.automator.mode = value;
|
||||
}
|
||||
},
|
||||
modeIconClass() {
|
||||
return this.mode ? "fa-cubes" : "fa-code";
|
||||
},
|
||||
playTooltip() {
|
||||
if (this.isRunning) return undefined;
|
||||
if (this.isPaused) return "Resume automator execution";
|
||||
return "Start automator";
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
this.isRunning = AutomatorBackend.isRunning;
|
||||
this.isPaused = AutomatorBackend.isOn && !this.isRunning;
|
||||
this.repeatOn = AutomatorBackend.state.repeat;
|
||||
this.runningScriptID = AutomatorBackend.state.topLevelScript;
|
||||
if (AutomatorBackend.state.topLevelScript !== this.currentScriptID || !AutomatorBackend.isOn) {
|
||||
this.activeLine = 0;
|
||||
return;
|
||||
}
|
||||
const newLineNumber = AutomatorBackend.stack.top.lineNumber;
|
||||
if (newLineNumber > AutomatorUI.editor.getDoc().lineCount()) {
|
||||
this.activeLine = 0;
|
||||
return;
|
||||
}
|
||||
this.activeLine = newLineNumber;
|
||||
},
|
||||
onGameLoad() {
|
||||
AutomatorUI.documents = {};
|
||||
this.updateCurrentScriptID();
|
||||
this.updateScriptList();
|
||||
},
|
||||
updateScriptList() {
|
||||
this.scripts = Object.values(player.reality.automator.scripts).map(script => ({
|
||||
id: script.id,
|
||||
name: script.name,
|
||||
}));
|
||||
},
|
||||
updateCurrentScriptID() {
|
||||
const storedScripts = player.reality.automator.scripts;
|
||||
this.currentScriptID = player.reality.automator.state.editorScript;
|
||||
// This shouldn't happen if things are loaded in the right order, but might as well be sure.
|
||||
if (storedScripts[this.currentScriptID] === undefined) {
|
||||
this.currentScriptID = Object.keys(storedScripts)[0];
|
||||
player.reality.automator.state.editorScript = this.currentScriptID;
|
||||
}
|
||||
if (AutomatorUI.documents[this.currentScriptID] === undefined) {
|
||||
AutomatorUI.documents[this.currentScriptID] =
|
||||
CodeMirror.Doc(storedScripts[this.currentScriptID].content, "automato");
|
||||
}
|
||||
AutomatorUI.editor.swapDoc(AutomatorUI.documents[this.currentScriptID]);
|
||||
},
|
||||
rewind: () => AutomatorBackend.restart(),
|
||||
play() {
|
||||
if (AutomatorBackend.isOn) AutomatorBackend.mode = AutomatorMode.RUN;
|
||||
else AutomatorBackend.start(this.currentScriptID);
|
||||
},
|
||||
pause: () => AutomatorBackend.pause(),
|
||||
stop: () => AutomatorBackend.stop(),
|
||||
step() {
|
||||
if (AutomatorBackend.isOn) AutomatorBackend.mode = AutomatorMode.SINGLE_STEP;
|
||||
else AutomatorBackend.start(this.currentScriptID, AutomatorMode.SINGLE_STEP);
|
||||
},
|
||||
repeat: () => AutomatorBackend.toggleRepeat(),
|
||||
rename() {
|
||||
this.editingName = true;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.renameInput.value = player.reality.automator.scripts[this.currentScriptID].name;
|
||||
this.$refs.renameInput.focus();
|
||||
});
|
||||
},
|
||||
selectedScriptAttribute(id) {
|
||||
return id === this.currentScriptID ? { selected: "selected" } : {};
|
||||
},
|
||||
createNewScript() {
|
||||
const newScript = AutomatorBackend.newScript();
|
||||
player.reality.automator.state.editorScript = newScript.id;
|
||||
this.updateScriptList();
|
||||
this.rename();
|
||||
},
|
||||
onScriptDropdown(event) {
|
||||
const menu = event.target;
|
||||
if (menu.selectedIndex === menu.length - 1) this.createNewScript();
|
||||
else player.reality.automator.state.editorScript = this.scripts[menu.selectedIndex].id;
|
||||
this.updateCurrentScriptID();
|
||||
},
|
||||
nameEdited() {
|
||||
// Trim off leading and trailing whitespace
|
||||
const trimmed = this.$refs.renameInput.value.match(/^\s*(.*?)\s*$/u);
|
||||
if (trimmed.length === 2 && trimmed[1].length > 0) {
|
||||
player.reality.automator.scripts[this.currentScriptID].name = trimmed[1];
|
||||
this.updateScriptList();
|
||||
}
|
||||
this.$nextTick(() => this.editingName = false);
|
||||
},
|
||||
dropdownLabel(script) {
|
||||
let label = script.name;
|
||||
if (script.id === this.runningScriptID) {
|
||||
if (this.isRunning) label += " (Running)";
|
||||
else if (this.isPaused) label += " (Paused)";
|
||||
}
|
||||
return label;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
AutomatorUI.initialize();
|
||||
EventHub.ui.on(GameEvent.GAME_LOAD, () => this.onGameLoad(), this);
|
||||
this.updateCurrentScriptID();
|
||||
this.updateScriptList();
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.container.appendChild(AutomatorUI.container);
|
||||
this.$nextTick(() => AutomatorUI.editor.refresh());
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.activeLine > 0) {
|
||||
// This will stick around, otherwise
|
||||
AutomatorUI.editor.removeLineClass(this.activeLine - 1, "background", "c-automator-editor__active-line");
|
||||
}
|
||||
this.$refs.container.removeChild(AutomatorUI.container);
|
||||
EventHub.ui.offAll(this);
|
||||
},
|
||||
template:
|
||||
`<div class="l-automator-pane">
|
||||
<div class="c-automator__controls l-automator__controls l-automator-pane__controls">
|
||||
<automator-button class="fa-fast-backward"
|
||||
@click="rewind"
|
||||
v-tooltip="'rewind automator to the first command'"/>
|
||||
<automator-button
|
||||
class="fa-play"
|
||||
:class="{ 'c-automator__button-play--active' : isRunning }"
|
||||
@click="play"
|
||||
v-tooltip="playTooltip"
|
||||
/>
|
||||
<automator-button class="fa-pause"
|
||||
:class="{ 'c-automator__button--active': isPaused }"
|
||||
@click="pause"
|
||||
v-tooltip="'Pause automator on current command'"/>
|
||||
<automator-button class="fa-stop"
|
||||
@click="stop"
|
||||
v-tooltip="'Stop automator and reset position'"/>
|
||||
<automator-button class="fa-step-forward"
|
||||
@click="step"
|
||||
v-tooltip="'Step forward one line'"/>
|
||||
<automator-button
|
||||
class="fa-sync-alt"
|
||||
:class="{ 'c-automator__button--active' : repeatOn }"
|
||||
@click="repeat"
|
||||
v-tooltip="'Restart script automatically when it completes'"
|
||||
/>
|
||||
<div class="l-automator__script-names">
|
||||
<template v-if="!editingName">
|
||||
<select class="l-automator__scripts-dropdown"
|
||||
@input="onScriptDropdown">
|
||||
<option v-for="script in scripts"
|
||||
v-bind="selectedScriptAttribute(script.id)"
|
||||
:value="script.id">{{dropdownLabel(script)}}</option>
|
||||
<option value="createNewScript">Create new...</option>
|
||||
</select>
|
||||
<automator-button class="far fa-edit" @click="rename"/>
|
||||
</template>
|
||||
<input v-else ref="renameInput"
|
||||
class="l-automator__rename-input"
|
||||
@blur="nameEdited"
|
||||
@keyup.enter="$refs.renameInput.blur()"/>
|
||||
</div>
|
||||
<automator-button
|
||||
class="l-automator__button--corner"
|
||||
:class="modeIconClass"
|
||||
@click="mode = !mode"
|
||||
/>
|
||||
</div>
|
||||
<div class="c-automator-editor l-automator-editor l-automator-pane__content" ref="container" />
|
||||
</div>`
|
||||
});
|
@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
|
||||
Vue.component("automator-single-block", {
|
||||
data() {
|
||||
return {
|
||||
b: {}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
block: Object,
|
||||
updateBlock: Function,
|
||||
deleteBlock: Function,
|
||||
lineNumber: Number
|
||||
},
|
||||
mounted() {
|
||||
this.b = this.block
|
||||
},
|
||||
methods: {
|
||||
deleteBlockFromNest(id) {
|
||||
let idx = this.b.nest.findIndex( x => x.id == id)
|
||||
this.b.nest.splice(idx, 1)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasInput() {
|
||||
return this.b.hasInput && ( this.b.targetsWithoutInput ? !this.b.targetsWithoutInput.includes(this.b.target) : true )
|
||||
}
|
||||
},
|
||||
template:
|
||||
`<div>
|
||||
<div class="c-automator-block-row">
|
||||
<div class="o-automator-linenumber">{{ lineNumber + 1 }}</div>
|
||||
<div class="o-automator-command">{{ b.cmd }}</div>
|
||||
<select v-if="b.targets" @change="updateBlock(block, b.id)" v-model="b.target" class="o-automator-block-input">
|
||||
<option v-for="target in b.targets" :value="target">{{ target }}</option>
|
||||
</select>
|
||||
<select v-if="b.secondaryTargets" @change="updateBlock(block, b.id)" v-model="b.secondaryTarget" class="o-automator-block-input">
|
||||
<option v-for="target in b.secondaryTargets" :value="target">{{ target }}</option>
|
||||
</select>
|
||||
<input v-if="hasInput" v-model="b.inputValue" @change="updateBlock(b, b.id)" class="o-automator-block-input"/>
|
||||
<div @click="deleteBlock(b.id)" class="o-automator-block-delete">X</div>
|
||||
</div>
|
||||
<draggable v-if="block.nested" class="l-automator-nested-block" v-model="block.nest" group="code-blocks">
|
||||
<automator-single-block
|
||||
v-for="(block, index) in block.nest"
|
||||
:key="block.id"
|
||||
:lineNumber="index"
|
||||
:block="block"
|
||||
:updateBlock="updateBlock"
|
||||
:deleteBlock="deleteBlockFromNest"></automator-single-block>
|
||||
</draggable>
|
||||
</div>`
|
||||
});
|
23
javascripts/components/reality/automator/automator-tab.js
Normal file
23
javascripts/components/reality/automator/automator-tab.js
Normal file
@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
|
||||
Vue.component("automator-tab", {
|
||||
computed: {
|
||||
fullScreen() {
|
||||
return this.$viewModel.tabs.reality.automator.fullScreen;
|
||||
},
|
||||
tabClass() {
|
||||
if (!this.fullScreen) return undefined;
|
||||
return ["c-automator-tab--full-screen", "l-automator-tab--full-screen"];
|
||||
},
|
||||
fullScreenIconClass() {
|
||||
return this.fullScreen ? "fa-compress-arrows-alt" : "fa-expand-arrows-alt";
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div :class="tabClass" class="c-automator-tab l-automator-tab" >
|
||||
<split-pane :min-percent="20" :default-percent="50" split="vertical" class="_-automator-split-pane-fix">
|
||||
<automator-editor slot="paneL" />
|
||||
<automator-docs slot="paneR" />
|
||||
</split-pane>
|
||||
</div>`
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
|
||||
Vue.component("automator-docs-main-page", {
|
||||
computed: {
|
||||
commands: () => GameDatabase.reality.automator.commands
|
||||
},
|
||||
template: `
|
||||
<div class="c-automator-docs-page">
|
||||
<span>Commands:</span>
|
||||
<span
|
||||
v-for="command in commands"
|
||||
class="c-automator-docs-page__link"
|
||||
@click="$emit('select', command.id)"
|
||||
>{{command.keyword}}</span>
|
||||
</div>
|
||||
`
|
||||
});
|
@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
|
||||
Vue.component("automator-docs", {
|
||||
data() {
|
||||
return {
|
||||
commandID: -1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
command() {
|
||||
return GameDatabase.reality.automator.commands[this.commandID];
|
||||
},
|
||||
fullScreen: {
|
||||
get() {
|
||||
return this.$viewModel.tabs.reality.automator.fullScreen;
|
||||
},
|
||||
set(value) {
|
||||
this.$viewModel.tabs.reality.automator.fullScreen = value;
|
||||
}
|
||||
},
|
||||
fullScreenIconClass() {
|
||||
return this.fullScreen ? "fa-compress-arrows-alt" : "fa-expand-arrows-alt";
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeCommand(event) {
|
||||
this.commandID = event;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="l-automator-pane">
|
||||
<div class="c-automator__controls l-automator__controls l-automator-pane__controls" >
|
||||
<automator-button class="fa-long-arrow-alt-left" @click="commandID = -1"/>
|
||||
<automator-button
|
||||
:class="fullScreenIconClass"
|
||||
class="l-automator__button--corner"
|
||||
@click="fullScreen = !fullScreen"
|
||||
/>
|
||||
</div>
|
||||
<div class="c-automator-docs l-automator-pane__content">
|
||||
<automator-docs-main-page
|
||||
v-if="command === undefined"
|
||||
@select="changeCommand"
|
||||
/>
|
||||
<automator-man-page v-else :command="command" />
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
"use strict";
|
||||
|
||||
Vue.component("automator-man-page", {
|
||||
props: {
|
||||
command: Object
|
||||
},
|
||||
template: `
|
||||
<div class="c-automator-docs-page">
|
||||
<b>NAME</b>
|
||||
<div class="c-automator-docs-page__indented" v-html="command.name" />
|
||||
<b>SYNTAX</b>
|
||||
<div class="c-automator-docs-page__indented" v-html="command.syntax" />
|
||||
<template v-if="command.description">
|
||||
<b>DESCRIPTION</b>
|
||||
<div class="c-automator-docs-page__indented"v-html="command.description" />
|
||||
</template>
|
||||
<template v-for="section in command.sections">
|
||||
<b>{{section.name}}</b>
|
||||
<template v-for="item in section.items">
|
||||
<div class="c-automator-docs-page__indented">
|
||||
<div v-html="item.header" />
|
||||
<div class="c-automator-docs-page__indented" v-html="item.description" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="command.examples">
|
||||
<b>EXAMPLES</b>
|
||||
<div v-for="example in command.examples" class="c-automator-docs-page__indented" v-html="example" />
|
||||
</template>
|
||||
</div>
|
||||
`
|
||||
});
|
@ -38,6 +38,12 @@ let ui = {
|
||||
subtab: "",
|
||||
openGlyphWeights: false,
|
||||
currentGlyphTooltip: -1,
|
||||
automator: {
|
||||
fullScreen: false,
|
||||
editorScriptID: "",
|
||||
// TODO: enum
|
||||
mode: true
|
||||
}
|
||||
},
|
||||
celestials: {
|
||||
subtab: ""
|
||||
|
@ -604,7 +604,7 @@ class InfinityAutobuyerState extends AutobuyerState {
|
||||
}
|
||||
}
|
||||
if (proc) {
|
||||
bigCrunchReset();
|
||||
bigCrunchResetRequest();
|
||||
}
|
||||
this.resetTicks();
|
||||
}
|
||||
@ -625,7 +625,7 @@ Autobuyer.unlockables = Autobuyer.allDims
|
||||
]);
|
||||
|
||||
Autobuyer.tryUnlockAny = function() {
|
||||
for (let autobuyer of this.unlockables) {
|
||||
for (const autobuyer of this.unlockables) {
|
||||
autobuyer.tryUnlock();
|
||||
}
|
||||
};
|
||||
|
@ -1,409 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
*
|
||||
* BUY STUDY 11:
|
||||
* Buys study
|
||||
* also can do BUY STUDYUNTIL X
|
||||
*
|
||||
* You can also BUY TTMAX or BUY TTEP 3 or BUY TTAM 4 etc.
|
||||
*
|
||||
*
|
||||
*
|
||||
* WAIT EP 1e20:
|
||||
* waits untils 1e20 ep
|
||||
* possible targets: ep, ip, antimatter, replicanti, rg, seconds
|
||||
* replicantigalaxy has a MAX option, which waits until you have as much as you can.
|
||||
*
|
||||
* UNLOCK EC 1: you know
|
||||
*
|
||||
* START EC 1: duh
|
||||
*
|
||||
* CHANGE IPautobuyer 1e20:
|
||||
* changes the settings of autobuyers, currently only ip
|
||||
*
|
||||
* RESPEC: makes next eternity respec
|
||||
*
|
||||
* ETERNITY: does an eternity
|
||||
*
|
||||
* STOP: stops the script
|
||||
*
|
||||
* LOAD SCRIPT 2: loads the second script
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* The player can use rows equal to Math.ceil(realities^0.5)
|
||||
*/
|
||||
var automatorRows = []
|
||||
var automatorIdx = 0
|
||||
var tryingToBuy = 0
|
||||
|
||||
function getAutomatorRows() {
|
||||
const realityFactor = Effects.max(0.7, Perk.automatorRowScaling);
|
||||
const realityRows = Math.ceil(Math.pow(player.realities, realityFactor));
|
||||
const perkRows = Effects.sum(
|
||||
Perk.automatorRowIncrease1,
|
||||
Perk.automatorRowIncrease2
|
||||
);
|
||||
return 6 + realityRows + perkRows;
|
||||
}
|
||||
|
||||
function automatorOnOff() {
|
||||
automatorOn = !automatorOn;
|
||||
automatorIdx = 0
|
||||
if (!automatorOn) {
|
||||
$("#automator")[0].blur()
|
||||
}
|
||||
GameUI.notify.info(automatorOn ? "Automator turned on" : "Automator turned off");
|
||||
}
|
||||
|
||||
function highlightcurrent() {
|
||||
var row = automatorRows[automatorIdx];
|
||||
var idx = automatorRows.slice(0, automatorIdx).reduce(function (acc, x) {
|
||||
return acc + x.length + 1;
|
||||
}, 0);
|
||||
|
||||
if (idx >= 0) {
|
||||
$("#automator")[0].focus();
|
||||
$("#automator")[0].setSelectionRange(idx, idx + row.length);
|
||||
}
|
||||
}
|
||||
|
||||
var automatorOn = false
|
||||
var timeStamp = 0
|
||||
var buying = false
|
||||
function mainIteration() {
|
||||
if (automatorRows[0] === undefined) return false;
|
||||
var cont = false
|
||||
if (automatorRows[automatorIdx][0] == "*") cont = true
|
||||
if (automatorOn) {
|
||||
var row = automatorRows[automatorIdx].split(" ")
|
||||
if (cont) row.splice(0, 1);
|
||||
if (row.length == 1) {
|
||||
var current = {
|
||||
action: row[0]
|
||||
}
|
||||
} else if (row.length == 2) {
|
||||
var current = {
|
||||
action: row[0],
|
||||
target: row[1]
|
||||
}
|
||||
} else if (row.length == 3) {
|
||||
var current = {
|
||||
action: row[0],
|
||||
target: row[1],
|
||||
id: row[2]
|
||||
}
|
||||
} else if (row.length >= 4) { //added more flexibility to allow for more arguments in automator commands
|
||||
var current = {
|
||||
action: row[0],
|
||||
target: row[1],
|
||||
id: row[2],
|
||||
args: row.slice(3)
|
||||
}
|
||||
}
|
||||
switch(current.action) {
|
||||
case "buy":
|
||||
if (buy(current) || cont) automatorIdx+=1
|
||||
break;
|
||||
case "wait":
|
||||
if (wait(current)) automatorIdx+=1
|
||||
break;
|
||||
case "unlock":
|
||||
if (unlock(current) || cont) automatorIdx+=1
|
||||
break;
|
||||
case "start":
|
||||
if (start(current) || cont) automatorIdx+=1
|
||||
break;
|
||||
case "change":
|
||||
if (change(current) || cont) automatorIdx+=1
|
||||
break;
|
||||
case "respec":
|
||||
if (!player.reality.automatorCommands.has(61)) return false
|
||||
player.respec = true
|
||||
automatorIdx+=1
|
||||
break;
|
||||
case "eternity":
|
||||
if (!player.reality.automatorCommands.has(62)) return false
|
||||
if (eternity(false, true) || cont) automatorIdx+=1
|
||||
break;
|
||||
case "stop":
|
||||
if (!player.reality.automatorCommands.has(72)) return false
|
||||
automatorOn = false
|
||||
$("#automatorOn")[0].checked = false
|
||||
break;
|
||||
case "load":
|
||||
if (!player.reality.automatorCommands.has(51)) return false
|
||||
automatorIdx = 0
|
||||
loadScript(current.id)
|
||||
break;
|
||||
case "toggle":
|
||||
if (!player.reality.automatorCommands.has(53)) return false
|
||||
toggle(current)
|
||||
automatorIdx+=1
|
||||
break;
|
||||
case "goto":
|
||||
if (!player.reality.automatorCommands.has(31)) return false
|
||||
automatorIdx = parseInt(current.target)-1
|
||||
break;
|
||||
|
||||
case "if":
|
||||
if (!player.reality.automatorCommands.has(21)) return false;
|
||||
if (wait(current)) automatorIdx += 1
|
||||
else automatorIdx += 2
|
||||
}
|
||||
|
||||
|
||||
if (automatorRows.length - 1 < automatorIdx || automatorIdx + 1 > getAutomatorRows() ) automatorIdx = 0 //The player can use rows equal to Math.ceil(realities^0.7) + 6
|
||||
if ( $("#reality").css("display") == "block" && $("#automation").css("display") == "block") highlightcurrent()
|
||||
}
|
||||
}
|
||||
|
||||
function buy(current) {
|
||||
let id;
|
||||
switch (current.target) {
|
||||
case "study":
|
||||
id = parseInt(current.id)
|
||||
if (TimeStudy(id).isBought) return true;
|
||||
if (TimeStudy(id).purchase()) return true;
|
||||
return false;
|
||||
case "studyuntil":
|
||||
id = parseInt(current.id);
|
||||
if (!TimeStudy(id).isBought) {
|
||||
studiesUntil(id);//passes arguments into the studies until function.
|
||||
return true
|
||||
} else return false
|
||||
break;
|
||||
case "studypath":
|
||||
if (!player.reality.automatorCommands.has(26)) return false;
|
||||
studyPath(current.id, current.args, true);
|
||||
return true;
|
||||
break;
|
||||
case "studyimport":
|
||||
if (!player.reality.automatorCommands.has(36)) return false;
|
||||
importStudyTree(current.id, true);
|
||||
return true;
|
||||
break;
|
||||
case "ttmax":
|
||||
if (!player.reality.automatorCommands.has(44)) return false
|
||||
TimeTheorems.buyMax();
|
||||
return true
|
||||
break;
|
||||
case "ttip":
|
||||
if (!player.reality.automatorCommands.has(35)) return false
|
||||
if (!buying) {
|
||||
buying = true
|
||||
tryingToBuy = 0
|
||||
}
|
||||
if (TimeTheorems.buyWithIP()) tryingToBuy++;
|
||||
if (tryingToBuy == parseInt(current.id)) {
|
||||
buying = false
|
||||
return true
|
||||
}
|
||||
else return false
|
||||
break;
|
||||
case "ttep":
|
||||
if (!player.reality.automatorCommands.has(34)) return false
|
||||
if (!buying) {
|
||||
buying = true
|
||||
tryingToBuy = 0
|
||||
}
|
||||
if (TimeTheorems.buyWithEP()) tryingToBuy++;
|
||||
if (tryingToBuy == parseInt(current.id)) {
|
||||
buying = false
|
||||
return true
|
||||
}
|
||||
else return false
|
||||
break;
|
||||
case "ttam":
|
||||
if (!player.reality.automatorCommands.has(43)) return false
|
||||
if (!buying) {
|
||||
buying = true
|
||||
tryingToBuy = 0
|
||||
}
|
||||
if (TimeTheorems.buyWithAntimatter()) tryingToBuy++;
|
||||
if (tryingToBuy == parseInt(current.id)) {
|
||||
buying = false
|
||||
return true
|
||||
}
|
||||
else return false
|
||||
break;
|
||||
case "ttgen":
|
||||
if (buyDilationUpgrade(10)) return true
|
||||
else return false
|
||||
}
|
||||
}
|
||||
|
||||
function unlock(current) {
|
||||
if (!player.reality.automatorCommands.has(54)) return false
|
||||
switch(current.target) {
|
||||
case "ec":
|
||||
if (!player.reality.automatorCommands.has(64)) return false
|
||||
if (player.challenge.eternity.unlocked === parseInt(current.id, 10)) return true;
|
||||
return TimeStudy.eternityChallenge(current.id).purchase(true);
|
||||
case "dilation":
|
||||
if (!player.reality.automatorCommands.has(63)) return false
|
||||
if (TimeStudy.dilation.purchase(true)) return true;
|
||||
else return false
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function wait(current) {
|
||||
if (!player.reality.automatorCommands.has(11)) return false
|
||||
let id;
|
||||
if (current.id !== "max" && current.target !== "time") id = new Decimal(current.id)
|
||||
switch(current.target) {
|
||||
case "ep":
|
||||
if (!player.reality.automatorCommands.has(23)) return false
|
||||
if (id.gt(player.eternityPoints)) return false
|
||||
else return true
|
||||
break;
|
||||
case "ip":
|
||||
if (!player.reality.automatorCommands.has(32)) return false
|
||||
if (id.gt(player.infinityPoints)) return false
|
||||
else return true
|
||||
break;
|
||||
case "antimatter":
|
||||
if (!player.reality.automatorCommands.has(22)) return false
|
||||
if (id.gt(player.money)) return false
|
||||
else return true
|
||||
break;
|
||||
case "replicanti":
|
||||
if (!player.reality.automatorCommands.has(33)) return false
|
||||
if (id.gt(player.replicanti.amount)) return false
|
||||
else return true
|
||||
break;
|
||||
case "rg":
|
||||
if (!player.reality.automatorCommands.has(42)) return false
|
||||
if (current.id == "max") {
|
||||
if (!player.reality.automatorCommands.has(51)) return false
|
||||
if ((!TimeStudy(131).isBought ? player.replicanti.gal : Math.floor(player.replicanti.gal * 1.5)) == player.replicanti.galaxies) return true
|
||||
else return false
|
||||
}
|
||||
if (id.gt(player.replicanti.galaxies)) return false
|
||||
else return true
|
||||
break;
|
||||
case "time":
|
||||
if (!player.reality.automatorCommands.has(41)) return false
|
||||
if (timeStamp == 0) {
|
||||
timeStamp = new Date().getTime()
|
||||
return false
|
||||
} else {
|
||||
if (timeStamp + current.id * 1000 < new Date().getTime()) {
|
||||
timeStamp = 0
|
||||
return true
|
||||
}
|
||||
else return false
|
||||
}
|
||||
|
||||
break;
|
||||
case "tt":
|
||||
if (id.gt(player.timestudy.theorem)) return false
|
||||
else return true
|
||||
}
|
||||
}
|
||||
|
||||
function start(current) {
|
||||
if (!player.reality.automatorCommands.has(73)) return false;
|
||||
let ec;
|
||||
switch (current.target) {
|
||||
case "ec":
|
||||
if (!player.reality.automatorCommands.has(84)) return false;
|
||||
ec = EternityChallenge(current.id);
|
||||
if (ec.isRunning) return true;
|
||||
return ec.start(true);
|
||||
case "dilation":
|
||||
if (!player.reality.automatorCommands.has(83)) return false;
|
||||
return startDilatedEternity(true);
|
||||
}
|
||||
}
|
||||
|
||||
function change(current) {
|
||||
if (!player.reality.automatorCommands.has(71)) return false
|
||||
switch(current.target) {
|
||||
case "ipautobuyer":
|
||||
Autobuyer.infinity.limit = new Decimal(current.id);
|
||||
return true
|
||||
case "epautobuyer":
|
||||
Autobuyer.eternity.limit = new Decimal(current.id);
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function toggle(current) {
|
||||
if (current.target === "rg") { //RG is handled differently
|
||||
replicantiGalaxyAutoToggle(current.id);
|
||||
return true;
|
||||
}
|
||||
let options = Array.range(1, 8)
|
||||
.map(tier => { return { name: `d${tier}`, autobuyer: Autobuyer.dim(tier) }; });
|
||||
options.push({ name: "tickspeed", autobuyer: Autobuyer.tickspeed });
|
||||
options.push({ name: "dimboost", autobuyer: Autobuyer.dimboost });
|
||||
options.push({ name: "galaxy", autobuyer: Autobuyer.galaxy });
|
||||
options.push({ name: "infinity", autobuyer: Autobuyer.infinity });
|
||||
options.push({ name: "sacrifice", autobuyer: Autobuyer.sacrifice });
|
||||
options.push({ name: "eternity", autobuyer: Autobuyer.eternity });
|
||||
let state;
|
||||
let id = options.map(o => o.name).indexOf(current.target);
|
||||
if (id === -1) return false; //Fails if the specified autobuyer doesnt exist
|
||||
const autobuyer = options[id].autobuyer;
|
||||
if (current.id === "on") state = true;
|
||||
else if (current.id === "off") state = false;
|
||||
else state = !autobuyer.isOn;
|
||||
autobuyer.isOn = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function automatorSaveButton(num, forceSave) {
|
||||
if (shiftDown || forceSave) {
|
||||
localStorage.setItem("automatorScript"+num, JSON.stringify(automatorRows));
|
||||
GameUI.notify.info(`Automator script ${num} saved`);
|
||||
} else {
|
||||
loadScript(num)
|
||||
}
|
||||
}
|
||||
|
||||
function loadScript(num) {
|
||||
if (localStorage.getItem("automatorScript"+num) !== null && localStorage.getItem("automatorScript"+num) !== "|0") {
|
||||
importAutomatorScript(localStorage.getItem("automatorScript"+num));
|
||||
automatorIdx = 0
|
||||
GameUI.notify.info(`Automator script ${num} loaded`);
|
||||
}
|
||||
}
|
||||
|
||||
function importAutomatorScript(script) {
|
||||
var outputString = JSON.parse(script).join("\n")
|
||||
document.getElementById("automator").value = outputString
|
||||
updateAutomatorState()
|
||||
}
|
||||
|
||||
function updateAutomatorState() {
|
||||
automatorRows = $("#automator").val().toLowerCase().split("\n").filter(function(row) { return row !== "" })
|
||||
automatorIdx = 0
|
||||
}
|
||||
|
||||
function buyAutomatorInstruction(id) {
|
||||
if (!canBuyAutomatorInstruction(id)) return false
|
||||
if (player.reality.automatorCommands.has(id)) return false
|
||||
player.reality.realityMachines = player.reality.realityMachines.minus(Automator.InstructionsById[id].price)
|
||||
player.reality.automatorCommands.add(id)
|
||||
return true
|
||||
}
|
||||
|
||||
function canBuyAutomatorInstruction(id) {
|
||||
var info = Automator.InstructionsById[id];
|
||||
if (player.reality.realityMachines.lt(info.price)) return false;
|
||||
return info.parent === undefined || player.reality.automatorCommands.has(info.parent);
|
||||
}
|
||||
|
||||
function updateAutomatorRows() {
|
||||
const pow = Effects.max(0.7, Perk.automatorRowScaling);
|
||||
var rows = 6 + Math.ceil(Math.pow(player.realities, pow))
|
||||
var next = Math.ceil( Math.pow(rows - 6, 1 / pow) )
|
||||
$("#rowsAvailable").text("Your automator can use " + getAutomatorRows() + " rows; next row at " + next + " realities.")
|
||||
}
|
||||
|
||||
setInterval(mainIteration, 50)
|
374
javascripts/core/automator/automator-backend.js
Normal file
374
javascripts/core/automator/automator-backend.js
Normal file
@ -0,0 +1,374 @@
|
||||
"use strict";
|
||||
|
||||
/** @abstract */
|
||||
class AutomatorCommandInterface {
|
||||
constructor(id) {
|
||||
AutomatorCommandInterface.all[id] = this;
|
||||
}
|
||||
|
||||
/** @abstract */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
run(command) { throw NotImplementedCrash(); }
|
||||
}
|
||||
|
||||
AutomatorCommandInterface.all = [];
|
||||
|
||||
function AutomatorCommand(id) {
|
||||
return AutomatorCommandInterface.all[id];
|
||||
}
|
||||
|
||||
const AutomatorCommandStatus = Object.freeze({
|
||||
NEXT_INSTRUCTION: 0,
|
||||
NEXT_TICK_SAME_INSTRUCTION: 1,
|
||||
NEXT_TICK_NEXT_INSTRUCTION: 2,
|
||||
// This is used to handle some special cases, like branches/loops:
|
||||
SAME_INSTRUCTION: 3,
|
||||
});
|
||||
|
||||
const AutomatorMode = Object.freeze({
|
||||
PAUSE: 1,
|
||||
RUN: 2,
|
||||
SINGLE_STEP: 3,
|
||||
});
|
||||
|
||||
|
||||
const AutomatorVarTypes = {
|
||||
NUMBER: { id: 0, name: "number" },
|
||||
STUDIES: { id: 1, name: "studies" },
|
||||
DURATION: { id: 2, name: "duration" },
|
||||
UNKNOWN: { id: -1, name: "unknown" },
|
||||
};
|
||||
|
||||
/**
|
||||
* This object represents a single entry on the execution stack. It's a combination
|
||||
* of transient and persistent values -- we don't store the compiled script or indices
|
||||
* in the player object, but they are part of the stack.
|
||||
*/
|
||||
class AutomatorStackEntry {
|
||||
constructor(stackIndex) {
|
||||
this._stackIndex = stackIndex;
|
||||
this._commandIndex = 0;
|
||||
}
|
||||
|
||||
// This is used when a new thing is put on the stack (rather than us creating objects
|
||||
// when loading a game)
|
||||
initializeNew(commands) {
|
||||
this._commands = commands;
|
||||
this._commandIndex = 0;
|
||||
this.persistent = {
|
||||
lineNumber: commands[0].lineNumber,
|
||||
commandState: null,
|
||||
};
|
||||
}
|
||||
|
||||
get commandIndex() {
|
||||
return this._commandIndex;
|
||||
}
|
||||
|
||||
set commandIndex(value) {
|
||||
this._commandIndex = value;
|
||||
this.lineNumber = this._commands[value].lineNumber;
|
||||
}
|
||||
|
||||
get lineNumber() {
|
||||
return this.persistent.lineNumber;
|
||||
}
|
||||
|
||||
set lineNumber(value) {
|
||||
this.persistent.lineNumber = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object|null} commandState used by commands to track their own data, such as remaining wait time
|
||||
*/
|
||||
get commandState() {
|
||||
return this.persistent.commandState;
|
||||
}
|
||||
|
||||
set commandState(value) {
|
||||
this.persistent.commandState = value;
|
||||
}
|
||||
|
||||
get persistent() {
|
||||
return player.reality.automator.state.stack[this._stackIndex];
|
||||
}
|
||||
|
||||
set persistent(value) {
|
||||
player.reality.automator.state.stack[this._stackIndex] = value;
|
||||
}
|
||||
|
||||
get commands() {
|
||||
return this._commands;
|
||||
}
|
||||
|
||||
set commands(value) {
|
||||
this._commands = value;
|
||||
}
|
||||
}
|
||||
|
||||
class AutomatorScript {
|
||||
constructor(id) {
|
||||
if (!id) throw crash("Invalid script ID");
|
||||
this._id = id;
|
||||
this.compile();
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.persistent.name;
|
||||
}
|
||||
|
||||
set name(value) {
|
||||
this.persistent.name = value;
|
||||
}
|
||||
|
||||
get persistent() {
|
||||
return player.reality.automator.scripts[this._id];
|
||||
}
|
||||
|
||||
get commands() {
|
||||
return this._compiled;
|
||||
}
|
||||
|
||||
get text() {
|
||||
return this.persistent.content;
|
||||
}
|
||||
|
||||
save(content) {
|
||||
this.persistent.content = content;
|
||||
this.compile();
|
||||
}
|
||||
|
||||
compile() {
|
||||
this._compiled = AutomatorGrammar.compile(this.text).compiled;
|
||||
}
|
||||
|
||||
static create(name) {
|
||||
const id = (++player.reality.automator.lastID).toString();
|
||||
player.reality.automator.scripts[id] = {
|
||||
id,
|
||||
name,
|
||||
content: "",
|
||||
};
|
||||
return new AutomatorScript(id);
|
||||
}
|
||||
}
|
||||
|
||||
const AutomatorBackend = {
|
||||
MAX_COMMANDS_PER_UPDATE: 100,
|
||||
_scripts: [],
|
||||
|
||||
get state() {
|
||||
return player.reality.automator.state;
|
||||
},
|
||||
|
||||
// The automator may be paused at some instruction, but still be on.
|
||||
get isOn() {
|
||||
return !this.stack.isEmpty;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {AutomatorMode}
|
||||
*/
|
||||
get mode() {
|
||||
return this.state.mode;
|
||||
},
|
||||
|
||||
set mode(value) {
|
||||
this.state.mode = value;
|
||||
},
|
||||
|
||||
get isRunning() {
|
||||
return this.isOn && this.mode === AutomatorMode.RUN;
|
||||
},
|
||||
|
||||
update() {
|
||||
if (!this.isOn) return;
|
||||
switch (this.mode) {
|
||||
case AutomatorMode.PAUSE:
|
||||
return;
|
||||
case AutomatorMode.SINGLE_STEP:
|
||||
this.step();
|
||||
this.state.mode = AutomatorMode.PAUSE;
|
||||
return;
|
||||
case AutomatorMode.RUN:
|
||||
break;
|
||||
default:
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
for (let count = 0; count < AutomatorBackend.MAX_COMMANDS_PER_UPDATE && this.isRunning; ++count) {
|
||||
if (!this.step()) break;
|
||||
}
|
||||
},
|
||||
|
||||
step() {
|
||||
if (this.stack.isEmpty) return false;
|
||||
switch (this.runCurrentCommand()) {
|
||||
case AutomatorCommandStatus.SAME_INSTRUCTION:
|
||||
return true;
|
||||
case AutomatorCommandStatus.NEXT_INSTRUCTION:
|
||||
return this.nextCommand();
|
||||
case AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION:
|
||||
return false;
|
||||
case AutomatorCommandStatus.NEXT_TICK_NEXT_INSTRUCTION:
|
||||
this.nextCommand();
|
||||
return false;
|
||||
}
|
||||
throw crash("Unrecognized return code from command");
|
||||
},
|
||||
|
||||
runCurrentCommand() {
|
||||
const S = this.stack.top;
|
||||
const cmdState = S.commands[S.commandIndex].run(S);
|
||||
return cmdState;
|
||||
},
|
||||
|
||||
nextCommand() {
|
||||
const S = this.stack.top;
|
||||
if (S.commandIndex >= S.commands.length - 1) {
|
||||
this.stack.pop();
|
||||
if (this.stack.isEmpty) {
|
||||
// With the debug output on, running short scripts gets very spammy, working around that
|
||||
// return false here makes sure that a single instruction script executes one tick at a time
|
||||
if (this.state.repeat) {
|
||||
this.start(this.state.topLevelScript, AutomatorMode.RUN, false);
|
||||
return false;
|
||||
}
|
||||
this.stop();
|
||||
}
|
||||
} else {
|
||||
S.commandState = null;
|
||||
++S.commandIndex;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
push(commands) {
|
||||
// We do not allow empty scripts on the stack.
|
||||
if (commands.length === 0) return;
|
||||
this.stack.push(commands);
|
||||
},
|
||||
|
||||
findScript(id) {
|
||||
return this._scripts.find(e => e.id === id);
|
||||
},
|
||||
|
||||
initializeFromSave() {
|
||||
const scriptIds = Object.keys(player.reality.automator.scripts);
|
||||
if (scriptIds.length === 0) {
|
||||
const defaultScript = AutomatorScript.create("Untitled");
|
||||
this._scripts = [defaultScript];
|
||||
scriptIds.push(defaultScript.id);
|
||||
} else {
|
||||
this._scripts = scriptIds.map(s => new AutomatorScript(s));
|
||||
}
|
||||
if (!scriptIds.includes(this.state.topLevelScript)) this.state.topLevelScript = scriptIds[0];
|
||||
const currentScript = this._scripts.find(e => e.id === this.state.topLevelScript);
|
||||
if (currentScript.commands) {
|
||||
const commands = currentScript.commands;
|
||||
if (!this.stack.initializeFromSave(commands)) this.reset(commands);
|
||||
}
|
||||
},
|
||||
|
||||
saveScript(id, data) {
|
||||
this.findScript(id).save(data);
|
||||
if (id === this.state.topLevelScript) this.stop();
|
||||
},
|
||||
|
||||
newScript() {
|
||||
const newScript = AutomatorScript.create("Untitled");
|
||||
this._scripts.push(newScript);
|
||||
return newScript;
|
||||
},
|
||||
|
||||
toggleRepeat() {
|
||||
this.state.repeat = !this.state.repeat;
|
||||
},
|
||||
|
||||
reset(commands) {
|
||||
this.stack.clear();
|
||||
this.push(commands);
|
||||
},
|
||||
|
||||
stop() {
|
||||
this.stack.clear();
|
||||
this.state.mode = AutomatorMode.PAUSE;
|
||||
},
|
||||
|
||||
pause() {
|
||||
this.state.mode = AutomatorMode.PAUSE;
|
||||
},
|
||||
|
||||
start(scriptID = this.state.topLevelScript, initialMode = AutomatorMode.RUN, compile = true) {
|
||||
this.state.topLevelScript = scriptID;
|
||||
const scriptObject = this._scripts.find(s => s.id === scriptID);
|
||||
if (compile) scriptObject.compile();
|
||||
if (scriptObject.commands) {
|
||||
this.reset(scriptObject.commands);
|
||||
this.state.mode = initialMode;
|
||||
}
|
||||
},
|
||||
|
||||
restart() {
|
||||
if (this.stack.isEmpty) return;
|
||||
this.reset(this.stack._data[0].commands);
|
||||
},
|
||||
|
||||
stack: {
|
||||
_data: [],
|
||||
push(commands) {
|
||||
const newEntry = new AutomatorStackEntry(this.length);
|
||||
newEntry.initializeNew(commands);
|
||||
this._data.push(newEntry);
|
||||
},
|
||||
pop() {
|
||||
if (this._data.length === 0) return;
|
||||
player.reality.automator.state.stack.pop();
|
||||
this._data.pop();
|
||||
},
|
||||
clear() {
|
||||
this._data = [];
|
||||
player.reality.automator.state.stack.length = 0;
|
||||
},
|
||||
initializeFromSave(commands) {
|
||||
this._data = [];
|
||||
const playerStack = player.reality.automator.state.stack;
|
||||
let currentCommands = commands;
|
||||
for (let depth = 0; depth < playerStack.length; ++depth) {
|
||||
const playerEntry = playerStack[depth];
|
||||
const newEntry = new AutomatorStackEntry(depth);
|
||||
newEntry.commands = currentCommands;
|
||||
const foundIndex = currentCommands.findIndex(e => e.lineNumber === playerEntry.lineNumber);
|
||||
if (foundIndex === -1) {
|
||||
// Could not match stack state to script, have to reset automato
|
||||
return false;
|
||||
}
|
||||
newEntry.commandIndex = foundIndex;
|
||||
this._data.push(newEntry);
|
||||
// Are we inside a code block?
|
||||
if (depth !== playerStack.length - 1) {
|
||||
if (currentCommands[foundIndex].blockCommands === undefined) {
|
||||
return false;
|
||||
}
|
||||
currentCommands = currentCommands[foundIndex].blockCommands;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
get top() {
|
||||
return this._data[this.length - 1];
|
||||
},
|
||||
get length() {
|
||||
if (this._data.length !== player.reality.automator.state.stack.length) throw crash("inconsistent stack length");
|
||||
return this._data.length;
|
||||
},
|
||||
get isEmpty() {
|
||||
return this._data.length === 0;
|
||||
}
|
||||
},
|
||||
|
||||
};
|
97
javascripts/core/automator/automator-codemirror.js
Normal file
97
javascripts/core/automator/automator-codemirror.js
Normal file
@ -0,0 +1,97 @@
|
||||
"use strict";
|
||||
|
||||
(function() {
|
||||
function walkSuggestion(suggestion, prefix, output) {
|
||||
if (suggestion.$autocomplete &&
|
||||
suggestion.$autocomplete.startsWith(prefix) && suggestion.$autocomplete !== prefix) {
|
||||
output.add(suggestion.$autocomplete);
|
||||
}
|
||||
for (const s of suggestion.categoryMatches) {
|
||||
walkSuggestion(AutomatorLexer.tokenIds[s], prefix, output);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
CodeMirror.registerHelper("lint", "automato", (contents, _, editor) => {
|
||||
const doc = editor.getDoc();
|
||||
const errors = AutomatorGrammar.compile(contents, true).errors;
|
||||
return errors.map(e => ({
|
||||
message: e.info,
|
||||
severity: "error",
|
||||
from: doc.posFromIndex(e.startOffset),
|
||||
to: doc.posFromIndex(e.endOffset + 1),
|
||||
}));
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("hint", "anyword", editor => {
|
||||
const cursor = editor.getDoc().getCursor();
|
||||
let start = cursor.ch;
|
||||
const end = cursor.ch;
|
||||
const line = editor.getLine(cursor.line);
|
||||
while (start && /\w/u.test(line.charAt(start - 1)))--start;
|
||||
const lineStart = line.slice(0, start);
|
||||
const currentPrefix = line.slice(start, end);
|
||||
const lineLex = AutomatorLexer.lexer.tokenize(lineStart);
|
||||
if (lineLex.errors.length > 0) return undefined;
|
||||
const rawSuggestions = AutomatorGrammar.parser.computeContentAssist("command", lineLex.tokens);
|
||||
const suggestions = new Set();
|
||||
for (const s of rawSuggestions) {
|
||||
if (s.ruleStack[1] === "badCommand") continue;
|
||||
walkSuggestion(s.nextTokenType, currentPrefix, suggestions);
|
||||
}
|
||||
return {
|
||||
list: Array.from(suggestions),
|
||||
from: CodeMirror.Pos(cursor.line, start),
|
||||
to: CodeMirror.Pos(cursor.line, end)
|
||||
};
|
||||
});
|
||||
|
||||
const commentRule = { regex: /(\/\/|#).*/u, token: "comment", next: "start" };
|
||||
|
||||
CodeMirror.defineSimpleMode("automato", {
|
||||
// The start state contains the rules that are intially used
|
||||
start: [
|
||||
commentRule,
|
||||
{
|
||||
regex: /auto\s|define\s|if\s|pause\s|start\s|studies\s|tt\s|time theorems\s|unlock\s|until\s|wait\s|while\s|black[ \t]+hole\s|stored?[ \t]time\s/ui,
|
||||
token: "keyword",
|
||||
next: "commandArgs"
|
||||
},
|
||||
{ regex: /infinity\S+|eternity\S+|reality\S+|pause\S+|restart\S+/ui, token: "error", next: "commandDone" },
|
||||
{ regex: /infinity|eternity|reality|pause|restart/ui, token: "keyword", next: "commandDone" },
|
||||
{ regex: /\}/ui, dedent: true },
|
||||
{ regex: /\S+\s/ui, token: "error", next: "commandDone" },
|
||||
],
|
||||
commandDone: [
|
||||
commentRule,
|
||||
{ sol: true, next: "start" },
|
||||
// This seems necessary to have a closing curly brace de-indent automatically in some cases
|
||||
{ regex: /\}/ui, dedent: true },
|
||||
{ regex: /\S+/ui, token: "error" },
|
||||
],
|
||||
commandArgs: [
|
||||
commentRule,
|
||||
{ sol: true, next: "start" },
|
||||
{ regex: /<=|>=|<|>/ui, token: "operator" },
|
||||
{ regex: /on(\s|$)|off(\s|$)|dilation(\s|$)|load(\s|$)|respec(\s|$)|nowait(\s|$)/ui, token: "variable-2" },
|
||||
{ regex: /preset(\s|$)|infinity(\s|$)|eternity(\s|$)|reality(\s|$)|use(\s|$)/ui, token: "variable-2" },
|
||||
{ regex: /am|ip|ep|tt|sec(onds?)?|min(utes?)?|hours?/ui, token: "attribute" },
|
||||
{ regex: /([0-9]+:[0-5][0-9]:[0-5][0-9]|[0-5]?[0-9]:[0-5][0-9])/ui, token: "number" },
|
||||
{ regex: /-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?\s/ui, token: "number" },
|
||||
{ regex: /-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/ui, token: "number" },
|
||||
{ regex: /\{/ui, indent: true, next: "commandDone" },
|
||||
// This seems necessary to have a closing curly brace de-indent automatically in some cases
|
||||
{ regex: /\}/ui, dedent: true },
|
||||
],
|
||||
|
||||
// The meta property contains global information about the mode. It
|
||||
// can contain properties like lineComment, which are supported by
|
||||
// all modes, and also directives like dontIndentStates, which are
|
||||
// specific to simple modes.
|
||||
meta: {
|
||||
lineComment: "//",
|
||||
electricChars: "}",
|
||||
}
|
||||
});
|
||||
|
||||
}());
|
545
javascripts/core/automator/automator-commands.js
Normal file
545
javascripts/core/automator/automator-commands.js
Normal file
@ -0,0 +1,545 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Note: the $ shorthand for the parser object is required by Chevrotain. Don't mess with it.
|
||||
*/
|
||||
|
||||
const AutomatorCommands = ((() => {
|
||||
const T = AutomatorLexer.tokenMap;
|
||||
// The splitter tries to get a number 1 through 6, or anything else. Note: eslint complains
|
||||
// about lack of u flag here for some reason.
|
||||
// eslint-disable-next-line require-unicode-regexp
|
||||
const presetSplitter = new RegExp(/preset[ \t]+(?:([1-6]$)|(.+$))/ui);
|
||||
|
||||
function prestigeNotify(flag) {
|
||||
if (!AutomatorBackend.isOn) return;
|
||||
const state = AutomatorBackend.stack.top.commandState;
|
||||
if (state && state.prestigeLevel !== undefined) {
|
||||
state.prestigeLevel = Math.max(state.prestigeLevel, flag);
|
||||
}
|
||||
}
|
||||
|
||||
EventHub.logic.on(GameEvent.BIG_CRUNCH_AFTER, () => prestigeNotify(T.Infinity.$prestigeLevel));
|
||||
EventHub.logic.on(GameEvent.ETERNITY_RESET_AFTER, () => prestigeNotify(T.Eternity.$prestigeLevel));
|
||||
EventHub.logic.on(GameEvent.REALITY_RESET_AFTER, () => prestigeNotify(T.Reality.$prestigeLevel));
|
||||
|
||||
// Used by while and until
|
||||
function compileConditionLoop(evalComparison, commands) {
|
||||
return {
|
||||
run: () => {
|
||||
if (!evalComparison()) return AutomatorCommandStatus.NEXT_TICK_NEXT_INSTRUCTION;
|
||||
AutomatorBackend.push(commands);
|
||||
return AutomatorCommandStatus.SAME_INSTRUCTION;
|
||||
},
|
||||
blockCommands: commands,
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: "auto",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Auto);
|
||||
$.CONSUME(T.PrestigeEvent);
|
||||
$.OR([
|
||||
{ ALT: () => $.CONSUME(T.On) },
|
||||
{ ALT: () => $.CONSUME(T.Off) },
|
||||
{ ALT: () => $.SUBRULE($.duration) },
|
||||
]);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.Auto[0].startLine;
|
||||
if (ctx.PrestigeEvent && ctx.PrestigeEvent[0].tokenType === T.Reality && ctx.duration) {
|
||||
V.addError(ctx.duration[0], "auto reality cannot be set to a duration");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
compile: ctx => {
|
||||
const on = Boolean(ctx.On || ctx.duration);
|
||||
const duration = ctx.duration ? ctx.duration[0].children.$value : undefined;
|
||||
const durationMode = ctx.PrestigeEvent[0].tokenType.$autobuyerDurationMode;
|
||||
const autobuyer = ctx.PrestigeEvent[0].tokenType.$autobuyer;
|
||||
return () => {
|
||||
autobuyer.isOn = on;
|
||||
if (duration !== undefined) {
|
||||
autobuyer.mode = durationMode;
|
||||
autobuyer.limit = new Decimal(1e-3 * duration);
|
||||
}
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "blackHole",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.BlackHole);
|
||||
$.OR([
|
||||
{ ALT: () => $.CONSUME(T.On) },
|
||||
{ ALT: () => $.CONSUME(T.Off) },
|
||||
]);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.BlackHole[0].startLine;
|
||||
if (!BlackHole(1).isUnlocked) {
|
||||
V.addError(ctx.BlackHole[0], "black hole is not unlocked");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
compile: ctx => {
|
||||
const on = Boolean(ctx.On);
|
||||
return () => {
|
||||
if (on === BlackHoles.arePaused) BlackHoles.togglePause();
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "define",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Define);
|
||||
$.CONSUME(T.Identifier);
|
||||
$.CONSUME(T.EqualSign);
|
||||
$.OR([
|
||||
{ ALT: () => $.SUBRULE($.duration) },
|
||||
{ ALT: () => $.SUBRULE($.studyList) },
|
||||
]);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.Define[0].startLine;
|
||||
if (!ctx.Identifier || ctx.Identifier[0].isInsertedInRecovery || ctx.Identifier[0].image === "") {
|
||||
V.addError(ctx.Define, "missing variable name");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// Since define creates constants, they are all resolved at compile. The actual define instruction
|
||||
// doesn't have to do anything.
|
||||
compile: () => () => AutomatorCommandStatus.NEXT_INSTRUCTION,
|
||||
},
|
||||
{
|
||||
id: "ifBlock",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.If);
|
||||
$.SUBRULE($.comparison);
|
||||
$.CONSUME(T.LCurly);
|
||||
$.CONSUME(T.EOL);
|
||||
$.SUBRULE($.block);
|
||||
$.CONSUME(T.RCurly);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.If[0].startLine;
|
||||
return V.checkBlock(ctx, ctx.If);
|
||||
},
|
||||
compile: (ctx, C) => {
|
||||
const evalComparison = C.visit(ctx.comparison);
|
||||
const commands = C.visit(ctx.block);
|
||||
return {
|
||||
run: S => {
|
||||
// We avoid running the same if condition twice by creating an empty object
|
||||
// on the stack.
|
||||
if (S.commandState !== null) return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
S.commandState = {};
|
||||
if (!evalComparison()) return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
AutomatorBackend.push(commands);
|
||||
return AutomatorCommandStatus.SAME_INSTRUCTION;
|
||||
},
|
||||
blockCommands: commands,
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
// Note: this has to appear before pause
|
||||
id: "pauseTime",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Pause);
|
||||
$.OR([
|
||||
{ ALT: () => $.SUBRULE($.duration) },
|
||||
{ ALT: () => $.CONSUME(T.Identifier) },
|
||||
]);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.Pause[0].startLine;
|
||||
ctx.$duration = ctx.Identifier
|
||||
? V.lookupVar(ctx.Identifier[0], AutomatorVarTypes.DURATION).value
|
||||
: V.visit(ctx.duration);
|
||||
return ctx.$duration !== undefined;
|
||||
},
|
||||
compile: ctx => {
|
||||
const duration = ctx.$duration;
|
||||
return S => {
|
||||
if (S.commandState === null) {
|
||||
S.commandState = { timeMs: 0 };
|
||||
} else {
|
||||
S.commandState.timeMs += Time.unscaledDeltaTime.milliseconds;
|
||||
}
|
||||
return S.commandState.timeMs >= duration
|
||||
? AutomatorCommandStatus.NEXT_INSTRUCTION
|
||||
: AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "pause",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Pause);
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = ctx.Pause[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: () => () => {
|
||||
AutomatorBackend.pause();
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "prestige",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.PrestigeEvent);
|
||||
$.OPTION(() => $.CONSUME(T.Nowait));
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = ctx.PrestigeEvent[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: ctx => {
|
||||
const nowait = ctx.Nowait !== undefined;
|
||||
const prestigeToken = ctx.PrestigeEvent[0].tokenType;
|
||||
return () => {
|
||||
const available = prestigeToken.$prestigeAvailable();
|
||||
if (!available) {
|
||||
return nowait
|
||||
? AutomatorCommandStatus.NEXT_INSTRUCTION
|
||||
: AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
}
|
||||
prestigeToken.$prestige();
|
||||
return AutomatorCommandStatus.NEXT_TICK_NEXT_INSTRUCTION;
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "startDilation",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Start);
|
||||
$.CONSUME(T.Dilation);
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = ctx.Start[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: () => () => {
|
||||
if (player.dilation.active) return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
if (startDilatedEternity(true)) return AutomatorCommandStatus.NEXT_TICK_NEXT_INSTRUCTION;
|
||||
return AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "startEC",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Start);
|
||||
$.SUBRULE($.eternityChallenge);
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = ctx.Start[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: ctx => {
|
||||
const ecNumber = ctx.eternityChallenge[0].children.$ecNumber;
|
||||
return () => {
|
||||
const ec = EternityChallenge(ecNumber);
|
||||
if (ec.isRunning) return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
if (!EternityChallenge(ecNumber).isUnlocked) {
|
||||
if (!TimeStudy.eternityChallenge(ecNumber).purchase(true)) {
|
||||
return AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
}
|
||||
}
|
||||
if (ec.start(true)) return AutomatorCommandStatus.NEXT_TICK_NEXT_INSTRUCTION;
|
||||
return AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "storeTime",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.StoreTime);
|
||||
$.OR([
|
||||
{ ALT: () => $.CONSUME(T.On) },
|
||||
{ ALT: () => $.CONSUME(T.Off) },
|
||||
{ ALT: () => $.CONSUME(T.Use) },
|
||||
]);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.StoreTime[0].startLine;
|
||||
if (!Enslaved.isUnlocked) {
|
||||
V.addError(ctx.StoreTime[0], "Enslaved is not unlocked");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
compile: ctx => {
|
||||
if (ctx.Use) return () => {
|
||||
Enslaved.useStoredTime();
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
};
|
||||
const on = Boolean(ctx.On);
|
||||
return () => {
|
||||
if (on !== player.celestials.enslaved.isStoring) Enslaved.toggleStoreBlackHole();
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "studiesBuy",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Studies);
|
||||
$.OPTION(() => $.CONSUME(T.Nowait));
|
||||
$.OR([
|
||||
{ ALT: () => $.SUBRULE($.studyList) },
|
||||
{ ALT: () => $.CONSUME1(T.Identifier) },
|
||||
]);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.Studies[0].startLine;
|
||||
if (ctx.Identifier) {
|
||||
const varInfo = V.lookupVar(ctx.Identifier[0], AutomatorVarTypes.STUDIES);
|
||||
if (!varInfo) return;
|
||||
ctx.$studies = varInfo.value;
|
||||
} else if (ctx.studyList) {
|
||||
ctx.$studies = V.visit(ctx.studyList);
|
||||
}
|
||||
},
|
||||
compile: ctx => {
|
||||
const studies = ctx.$studies;
|
||||
if (ctx.Nowait === undefined) return () => {
|
||||
for (const tsNumber of studies.normal) {
|
||||
if (TimeStudy(tsNumber).isBought) continue;
|
||||
if (!TimeStudy(tsNumber).purchase()) {
|
||||
if (tsNumber === 201 && DilationUpgrade.timeStudySplit.isBought) continue;
|
||||
return AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
}
|
||||
}
|
||||
if (!studies.ec || TimeStudy.eternityChallenge(studies.ec).isBought) {
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
}
|
||||
return TimeStudy.eternityChallenge(studies.ec).purchase(true)
|
||||
? AutomatorCommandStatus.NEXT_INSTRUCTION
|
||||
: AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
};
|
||||
return () => {
|
||||
for (const tsNumber of studies.normal) TimeStudy(tsNumber).purchase();
|
||||
if (!studies.ec || TimeStudy.eternityChallenge(studies.ec).isBought) {
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
}
|
||||
TimeStudy.eternityChallenge(studies.ec).purchase(true);
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "studiesLoad",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Studies);
|
||||
$.CONSUME(T.Load);
|
||||
$.CONSUME(T.Preset);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.Studies[0].startLine;
|
||||
if (!ctx.Preset || ctx.Preset[0].isInsertedInRecovery || ctx.Preset[0].image === "") {
|
||||
V.addError(ctx, "Missing preset and preset name");
|
||||
return false;
|
||||
}
|
||||
const split = presetSplitter.exec(ctx.Preset[0].image);
|
||||
if (!split) {
|
||||
V.addError(ctx.Preset[0], "Missing preset name or number");
|
||||
return false;
|
||||
}
|
||||
ctx.Preset[0].splitPresetResult = split;
|
||||
let presetIndex;
|
||||
if (split[2]) {
|
||||
// We don't need to do any verification if it's a number; if it's a name, we
|
||||
// check to make sure it exists:
|
||||
presetIndex = player.timestudy.presets.findIndex(e => e.name === split[2]) + 1;
|
||||
if (presetIndex === 0) {
|
||||
V.addError(ctx.Preset[0], `Could not find preset named ${split[2]} (note: names are case sensitive)`);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
presetIndex = parseInt(split[1], 10);
|
||||
}
|
||||
ctx.$presetIndex = presetIndex;
|
||||
return true;
|
||||
},
|
||||
compile: ctx => {
|
||||
const presetIndex = ctx.$presetIndex;
|
||||
return () => {
|
||||
importStudyTree(player.timestudy.presets[presetIndex - 1].studies);
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "studiesRespec",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Studies);
|
||||
$.CONSUME(T.Respec);
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = ctx.Studies[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: () => () => {
|
||||
player.respec = true;
|
||||
return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "tt",
|
||||
rule: $ => () => {
|
||||
$.OPTION(() => $.CONSUME(T.Buy));
|
||||
$.CONSUME(T.TT);
|
||||
$.CONSUME(T.TTCurrency);
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = (ctx.Buy || ctx.TT)[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: ctx => {
|
||||
const buyFunction = ctx.TTCurrency[0].tokenType.$buyTT;
|
||||
return () => (buyFunction()
|
||||
? AutomatorCommandStatus.NEXT_INSTRUCTION
|
||||
: AutomatorCommandStatus.NEXT_TICK_NEXT_INSTRUCTION);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "unlockDilation",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Unlock);
|
||||
$.CONSUME(T.Dilation);
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = ctx.Unlock[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: () => () => {
|
||||
if (TimeStudy.dilation.isBought) return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
return TimeStudy.dilation.purchase(true)
|
||||
? AutomatorCommandStatus.NEXT_INSTRUCTION
|
||||
: AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "unlockEC",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Unlock);
|
||||
$.SUBRULE($.eternityChallenge);
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = ctx.Unlock[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: ctx => {
|
||||
const ecNumber = ctx.eternityChallenge[0].children.$ecNumber;
|
||||
return () => {
|
||||
if (EternityChallenge(ecNumber).isUnlocked) return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
return TimeStudy.eternityChallenge(ecNumber).purchase(true)
|
||||
? AutomatorCommandStatus.NEXT_INSTRUCTION
|
||||
: AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "untilLoop",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Until);
|
||||
$.OR([
|
||||
{ ALT: () => $.SUBRULE($.comparison) },
|
||||
{ ALT: () => $.CONSUME(T.PrestigeEvent) },
|
||||
]);
|
||||
$.CONSUME(T.LCurly);
|
||||
$.CONSUME(T.EOL);
|
||||
$.SUBRULE($.block);
|
||||
$.CONSUME(T.RCurly);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.Until[0].startLine;
|
||||
return V.checkBlock(ctx, ctx.Until);
|
||||
},
|
||||
compile: (ctx, C) => {
|
||||
const commands = C.visit(ctx.block);
|
||||
if (ctx.comparison) {
|
||||
const evalComparison = C.visit(ctx.comparison);
|
||||
return compileConditionLoop(() => !evalComparison(), commands);
|
||||
}
|
||||
const prestigeLevel = ctx.PrestigeEvent[0].tokenType.$prestigeLevel;
|
||||
return {
|
||||
run: S => {
|
||||
if (S.commandState === null) {
|
||||
S.commandState = { prestigeLevel: 0 };
|
||||
}
|
||||
if (S.commandState.prestigeLevel >= prestigeLevel) return AutomatorCommandStatus.NEXT_INSTRUCTION;
|
||||
AutomatorBackend.push(commands);
|
||||
return AutomatorCommandStatus.SAME_INSTRUCTION;
|
||||
},
|
||||
blockCommands: commands
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "waitCondition",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Wait);
|
||||
$.SUBRULE($.comparison);
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = ctx.Wait[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: (ctx, C) => {
|
||||
const evalComparison = C.visit(ctx.comparison);
|
||||
return () => (evalComparison()
|
||||
? AutomatorCommandStatus.NEXT_INSTRUCTION
|
||||
: AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "waitEvent",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.Wait);
|
||||
$.CONSUME(T.PrestigeEvent);
|
||||
},
|
||||
validate: ctx => {
|
||||
ctx.startLine = ctx.Wait[0].startLine;
|
||||
return true;
|
||||
},
|
||||
compile: ctx => {
|
||||
const prestigeLevel = ctx.PrestigeEvent[0].tokenType.$prestigeLevel;
|
||||
return S => {
|
||||
if (S.commandState === null) {
|
||||
S.commandState = { prestigeLevel: 0 };
|
||||
}
|
||||
return S.commandState.prestigeLevel >= prestigeLevel
|
||||
? AutomatorCommandStatus.NEXT_INSTRUCTION
|
||||
: AutomatorCommandStatus.NEXT_TICK_SAME_INSTRUCTION;
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "whileLoop",
|
||||
rule: $ => () => {
|
||||
$.CONSUME(T.While);
|
||||
$.SUBRULE($.comparison);
|
||||
$.CONSUME(T.LCurly);
|
||||
$.CONSUME(T.EOL);
|
||||
$.SUBRULE($.block);
|
||||
$.CONSUME(T.RCurly);
|
||||
},
|
||||
validate: (ctx, V) => {
|
||||
ctx.startLine = ctx.While[0].startLine;
|
||||
return V.checkBlock(ctx, ctx.While);
|
||||
},
|
||||
compile: (ctx, C) => compileConditionLoop(C.visit(ctx.comparison), C.visit(ctx.block)),
|
||||
},
|
||||
];
|
||||
})());
|
378
javascripts/core/automator/compiler.js
Normal file
378
javascripts/core/automator/compiler.js
Normal file
@ -0,0 +1,378 @@
|
||||
"use strict";
|
||||
|
||||
(function() {
|
||||
if (AutomatorGrammar === undefined) {
|
||||
throw crash("AutomatorGrammar must be defined here");
|
||||
}
|
||||
|
||||
const parser = AutomatorGrammar.parser;
|
||||
const BaseVisitor = parser.getBaseCstVisitorConstructorWithDefaults();
|
||||
|
||||
class Validator extends BaseVisitor {
|
||||
constructor() {
|
||||
super();
|
||||
this.validateVisitor();
|
||||
this.reset();
|
||||
// Commands can provide validation hooks; we might also have some here
|
||||
for (const cmd of AutomatorCommands) {
|
||||
if (!cmd.validate) continue;
|
||||
const ownMethod = this[cmd.id];
|
||||
this[cmd.id] = ctx => {
|
||||
if (!cmd.validate(ctx, this)) return;
|
||||
if (ownMethod) ownMethod.call(this, ctx);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
addLexerErrors(errors, input) {
|
||||
for (const err of errors) {
|
||||
this.errors.push({
|
||||
startOffset: err.offset,
|
||||
endOffset: err.offset + err.length,
|
||||
info: `Unexpected characters "${input.substr(err.offset, err.length)}"`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addParserErrors(errors, tokens) {
|
||||
for (const parseError of errors) {
|
||||
let err = Validator.combinePositionRanges(
|
||||
Validator.getPositionRange(parseError.previousToken),
|
||||
Validator.getPositionRange(parseError.token));
|
||||
// In some cases, at the end of the script we don't get any useful tokens out of the parse error
|
||||
if (parseError.token.tokenType.name === "EOF" && parseError.previousToken.tokenType.name === "EOF") {
|
||||
err = Validator.combinePositionRanges(err, Validator.getPositionRange(tokens[tokens.length - 1]));
|
||||
}
|
||||
// Deal with literal EOL in error message:
|
||||
err.info = parseError.message.replace(/'\n\s*'/ui, "End of line");
|
||||
const isEndToken = parseError.token.tokenType.name === "EOF" || parseError.token.tokenType.name === "EOL";
|
||||
if (parseError.name === "NoViableAltException") {
|
||||
if (!isEndToken) {
|
||||
err.info = `Unexpected input ${parseError.token.image}`;
|
||||
}
|
||||
} else if (parseError.name === "EarlyExitException") {
|
||||
err.info = "Unexpected end of command";
|
||||
}
|
||||
this.errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
static combinePositionRanges(r1, r2) {
|
||||
return {
|
||||
startOffset: Math.min(r1.startOffset, r2.startOffset),
|
||||
endOffset: Math.max(r1.endOffset, r2.endOffset),
|
||||
};
|
||||
}
|
||||
|
||||
// Chevrotain doesn't provide position ranges for higher level grammar constructs yet
|
||||
// We have to recursively figure out the range so we can highlight an error
|
||||
static getPositionRange(ctx) {
|
||||
let pos = {
|
||||
startOffset: Number.MAX_VALUE,
|
||||
endOffset: 0,
|
||||
};
|
||||
if (ctx === undefined || ctx === null) return pos;
|
||||
if (ctx.startOffset !== undefined) {
|
||||
return {
|
||||
startOffset: ctx.startOffset,
|
||||
endOffset: ctx.endOffset,
|
||||
};
|
||||
}
|
||||
if (ctx.children && !Array.isArray(ctx.children)) return Validator.getPositionRange(ctx.children);
|
||||
if (Array.isArray(ctx)) {
|
||||
return ctx.reduce((prev, el) => Validator.combinePositionRanges(prev, Validator.getPositionRange(el)), pos);
|
||||
}
|
||||
for (const k in ctx) {
|
||||
if (!Object.prototype.hasOwnProperty.call(ctx, k) || !Array.isArray(ctx[k])) continue;
|
||||
pos = Validator.combinePositionRanges(pos, Validator.getPositionRange(ctx[k]));
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
addError(ctx, errInfo) {
|
||||
const pos = Validator.getPositionRange(ctx);
|
||||
pos.info = errInfo;
|
||||
this.errors.push(pos);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.variables = {};
|
||||
this.errors = [];
|
||||
}
|
||||
|
||||
checkTimeStudyNumber(token) {
|
||||
const tsNumber = parseFloat(token.image);
|
||||
if (!TimeStudy(tsNumber)) {
|
||||
this.addError(token, `Invalid time study identifier ${tsNumber}`);
|
||||
return 0;
|
||||
}
|
||||
return tsNumber;
|
||||
}
|
||||
|
||||
lookupVar(identifier, type) {
|
||||
const varName = identifier.image;
|
||||
const varInfo = this.variables[varName];
|
||||
if (varInfo === undefined) {
|
||||
this.addError(identifier, `Variable ${varName} has not been defined`);
|
||||
return undefined;
|
||||
}
|
||||
if (varInfo.type === AutomatorVarTypes.UNKNOWN) {
|
||||
varInfo.firstUseLineNumber = identifier.image.startLine;
|
||||
varInfo.type = type;
|
||||
if (type === AutomatorVarTypes.STUDIES) {
|
||||
// The only time we have an unknown studies is if there was only one listed
|
||||
varInfo.value = {
|
||||
normal: [varInfo.value.toNumber()],
|
||||
ec: 0
|
||||
};
|
||||
}
|
||||
} else if (varInfo.type !== type) {
|
||||
const inferenceMessage = varInfo.firstUseLineNumber
|
||||
? `\nIts use on line ${varInfo.firstUseLineNumber} identified it as a ${varInfo.type.name}`
|
||||
: "";
|
||||
this.addError(identifier, `Variable ${varName} is not a ${type.name}${inferenceMessage}`);
|
||||
return undefined;
|
||||
}
|
||||
if (varInfo.value === undefined) throw crash("Unexpected undefined variable value");
|
||||
return varInfo;
|
||||
}
|
||||
|
||||
duration(ctx) {
|
||||
if (ctx.$value) return ctx.$value;
|
||||
if (!ctx.TimeUnit || ctx.TimeUnit[0].isInsertedInRecovery) {
|
||||
this.addError(ctx, "Missing time unit");
|
||||
return undefined;
|
||||
}
|
||||
const value = parseFloat(ctx.NumberLiteral[0].image) * ctx.TimeUnit[0].tokenType.$scale;
|
||||
if (isNaN(value)) {
|
||||
this.addError(ctx, "Error parsing time unit");
|
||||
return undefined;
|
||||
}
|
||||
ctx.$value = value;
|
||||
return ctx.$value;
|
||||
}
|
||||
|
||||
define(ctx) {
|
||||
const varName = ctx.Identifier[0].image;
|
||||
if (this.variables[varName] !== undefined) {
|
||||
this.addError(ctx.Identifier[0],
|
||||
`Variable ${varName} already defined on line ${this.variables[varName].definitionLineNumber}`);
|
||||
return;
|
||||
}
|
||||
if (!ctx.duration && !ctx.studyList) return;
|
||||
const def = {
|
||||
name: varName,
|
||||
definitionLineNumber: ctx.Identifier[0].startLine,
|
||||
firstUseLineNumber: 0,
|
||||
type: AutomatorVarTypes.UNKNOWN,
|
||||
value: undefined,
|
||||
};
|
||||
this.variables[varName] = def;
|
||||
if (ctx.duration) {
|
||||
def.type = AutomatorVarTypes.DURATION;
|
||||
def.value = this.visit(ctx.duration);
|
||||
return;
|
||||
}
|
||||
// We don't visit studyList because it might actually be just a number in this case
|
||||
const studies = ctx.studyList[0].children.studyListEntry;
|
||||
if (studies.length > 1 || studies[0].children.studyRange ||
|
||||
studies[0].children.StudyPath || studies[0].children.Comma) {
|
||||
def.type = AutomatorVarTypes.STUDIES;
|
||||
def.value = this.visit(ctx.studyList);
|
||||
return;
|
||||
}
|
||||
// We assume the value is a number; in some cases, we might overwrite it if we see
|
||||
// this variable used in studies
|
||||
def.value = new Decimal(studies[0].children.NumberLiteral[0].image);
|
||||
if (!/^[1-9][0-9]*[1-9]$/u.test(studies[0].children.NumberLiteral[0].image)) {
|
||||
// Study numbers are pretty specific number patterns
|
||||
def.type = AutomatorVarTypes.NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
studyRange(ctx, studiesOut) {
|
||||
if (!ctx.firstStudy || ctx.firstStudy[0].isInsertedInRecovery ||
|
||||
!ctx.lastStudy || ctx.lastStudy[0].isInsertedInRecovery) {
|
||||
this.addError(ctx, "Missing time study number in range");
|
||||
return;
|
||||
}
|
||||
const first = this.checkTimeStudyNumber(ctx.firstStudy[0]);
|
||||
const last = this.checkTimeStudyNumber(ctx.lastStudy[0]);
|
||||
if (!first || !last || !studiesOut) return;
|
||||
for (let id = first; id <= last; ++id) {
|
||||
if (TimeStudy(id)) studiesOut.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
studyListEntry(ctx, studiesOut) {
|
||||
if (ctx.studyRange) {
|
||||
this.visit(ctx.studyRange, studiesOut);
|
||||
return;
|
||||
}
|
||||
if (ctx.NumberLiteral) {
|
||||
if (ctx.NumberLiteral[0].isInsertedInRecovery) {
|
||||
this.addError(ctx, "Missing time study number");
|
||||
return;
|
||||
}
|
||||
const id = this.checkTimeStudyNumber(ctx.NumberLiteral[0]);
|
||||
if (id) studiesOut.push(id);
|
||||
return;
|
||||
}
|
||||
if (ctx.StudyPath) {
|
||||
const pathId = ctx.StudyPath[0].tokenType.$studyPath;
|
||||
const pathStudies = NormalTimeStudies.paths[pathId];
|
||||
studiesOut.push(...pathStudies);
|
||||
}
|
||||
}
|
||||
|
||||
studyList(ctx) {
|
||||
if (ctx.$cached !== undefined) return ctx.$cached;
|
||||
const studiesOut = [];
|
||||
for (const sle of ctx.studyListEntry) this.visit(sle, studiesOut);
|
||||
ctx.$cached = {
|
||||
normal: studiesOut,
|
||||
ec: 0
|
||||
};
|
||||
if (ctx.ECNumber) {
|
||||
if (ctx.ECNumber.isInsertedInRecovery) {
|
||||
this.addError(ctx.Pipe[0], "Missing eternity challenge number");
|
||||
}
|
||||
const ecNumber = parseFloat(ctx.ECNumber[0].image);
|
||||
if (!Number.isInteger(ecNumber) || ecNumber < 1 || ecNumber > 12) {
|
||||
this.addError(ctx.ECNumber, `Invalid eternity challenge ID ${ecNumber}`);
|
||||
}
|
||||
ctx.$cached.ec = ecNumber;
|
||||
}
|
||||
return ctx.$cached;
|
||||
}
|
||||
|
||||
compareValue(ctx) {
|
||||
if (ctx.NumberLiteral) {
|
||||
ctx.$value = new Decimal(ctx.NumberLiteral[0].image);
|
||||
} else if (ctx.Identifier) {
|
||||
ctx.$value = this.lookupVar(ctx.Identifier[0], AutomatorVarTypes.NUMBER).value;
|
||||
}
|
||||
}
|
||||
|
||||
comparison(ctx) {
|
||||
super.comparison(ctx);
|
||||
if (!ctx.compareValue || ctx.compareValue[0].recoveredNode) {
|
||||
this.addError(ctx, "Missing value for comparison");
|
||||
}
|
||||
if (!ctx.Currency || ctx.Currency[0].isInsertedInRecovery) {
|
||||
this.addError(ctx, "Missing currency for comparison");
|
||||
}
|
||||
if (!ctx.ComparisonOperator || ctx.ComparisonOperator[0].isInsertedInRecovery) {
|
||||
this.addError(ctx, "Missing comparison operator (<, >, <=, >=)");
|
||||
}
|
||||
}
|
||||
|
||||
badCommand(ctx) {
|
||||
const firstToken = ctx.badCommandToken[0].children;
|
||||
const firstTokenType = Object.keys(firstToken)[0];
|
||||
this.addError(firstToken[firstTokenType][0], `Unrecognized command ${firstToken[firstTokenType][0].image}`);
|
||||
}
|
||||
|
||||
eternityChallenge(ctx) {
|
||||
let errToken, ecNumber;
|
||||
if (ctx.ECLiteral) {
|
||||
ecNumber = parseFloat(ctx.ECLiteral[0].image.substr(2));
|
||||
errToken = ctx.ECLiteral[0];
|
||||
} else if (ctx.NumberLiteral) {
|
||||
ecNumber = parseFloat(ctx.NumberLiteral[0].image);
|
||||
errToken = ctx.NumberLiteral[0];
|
||||
} else {
|
||||
this.addError(ctx, "Missing eternity challenge number");
|
||||
return;
|
||||
}
|
||||
if (!Number.isInteger(ecNumber) || ecNumber < 1 || ecNumber > 12) {
|
||||
this.addError(errToken, `Invalid eternity challenge ID ${ecNumber}`);
|
||||
}
|
||||
ctx.$ecNumber = ecNumber;
|
||||
}
|
||||
|
||||
checkBlock(ctx, commandToken) {
|
||||
let hadError = false;
|
||||
if (!ctx.RCurly || ctx.RCurly[0].isInsertedInRecovery) {
|
||||
this.addError(commandToken[0], "Missing closing }");
|
||||
hadError = true;
|
||||
}
|
||||
if (!ctx.LCurly || ctx.LCurly[0].isInsertedInRecovery) {
|
||||
this.addError(commandToken[0], "Missing opening {");
|
||||
hadError = true;
|
||||
}
|
||||
return !hadError;
|
||||
}
|
||||
|
||||
script(ctx) {
|
||||
this.reset();
|
||||
if (ctx.block) this.visit(ctx.block);
|
||||
ctx.variables = this.variables;
|
||||
}
|
||||
}
|
||||
|
||||
class Compiler extends BaseVisitor {
|
||||
constructor() {
|
||||
super();
|
||||
// Commands provide compilation hooks; we might also have some here
|
||||
for (const cmd of AutomatorCommands) {
|
||||
if (!cmd.compile) continue;
|
||||
const ownMethod = this[cmd.id];
|
||||
// eslint-disable-next-line no-loop-func
|
||||
this[cmd.id] = (ctx, output) => {
|
||||
// For the compiler, we don't bother doing the default recursive visitation behavior
|
||||
if (ownMethod && ownMethod !== super[cmd.id]) ownMethod.call(this, ctx, output);
|
||||
let compiled = cmd.compile(ctx, this);
|
||||
if (typeof compiled === "function") compiled = { run: compiled };
|
||||
compiled.lineNumber = ctx.startLine;
|
||||
output.push(compiled);
|
||||
};
|
||||
}
|
||||
this.validateVisitor();
|
||||
}
|
||||
|
||||
comparison(ctx) {
|
||||
const flipped = ctx.Currency[0].startOffset > ctx.ComparisonOperator[0].startOffset;
|
||||
const threshold = ctx.compareValue[0].children.$value;
|
||||
const currencyGetter = ctx.Currency[0].tokenType.$getter;
|
||||
const compareFun = ctx.ComparisonOperator[0].tokenType.$compare;
|
||||
return () => {
|
||||
const currency = currencyGetter();
|
||||
return flipped ? compareFun(threshold, currency) : compareFun(currency, threshold);
|
||||
};
|
||||
}
|
||||
|
||||
block(ctx) {
|
||||
const output = [];
|
||||
if (ctx.command) for (const cmd of ctx.command) this.visit(cmd, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
script(ctx) {
|
||||
if (ctx.variables === undefined) {
|
||||
throw crash("Compiler called before Validator");
|
||||
}
|
||||
return ctx.block ? this.visit(ctx.block) : [];
|
||||
}
|
||||
}
|
||||
|
||||
function compile(input, validateOnly = false) {
|
||||
const lexResult = AutomatorLexer.lexer.tokenize(input);
|
||||
const tokens = lexResult.tokens;
|
||||
parser.input = tokens; const t1 = Date.now();
|
||||
const parseResult = parser.script();
|
||||
const validator = new Validator();
|
||||
validator.visit(parseResult);
|
||||
validator.addLexerErrors(lexResult.errors, input);
|
||||
validator.addParserErrors(parser.errors, tokens);
|
||||
let compiled;
|
||||
if (validator.errors.length === 0 && !validateOnly) {
|
||||
compiled = new Compiler().visit(parseResult);
|
||||
}
|
||||
return {
|
||||
errors: validator.errors,
|
||||
compiled,
|
||||
};
|
||||
}
|
||||
AutomatorGrammar.compile = compile;
|
||||
}());
|
287
javascripts/core/automator/lexer.js
Normal file
287
javascripts/core/automator/lexer.js
Normal file
@ -0,0 +1,287 @@
|
||||
// Note: chevrotain doesn't play well with unicode regex
|
||||
/* eslint-disable require-unicode-regexp */
|
||||
/* eslint-disable camelcase */
|
||||
"use strict";
|
||||
|
||||
const AutomatorLexer = (() => {
|
||||
const createToken = chevrotain.createToken;
|
||||
const Lexer = chevrotain.Lexer;
|
||||
|
||||
const createCategory = name => createToken({ name, pattern: Lexer.NA, longer_alt: Identifier });
|
||||
|
||||
// Shorthand for creating tokens and adding them to a list
|
||||
const tokenLists = {};
|
||||
// eslint-disable-next-line max-params
|
||||
const createInCategory = (category, name, pattern, props = {}) => {
|
||||
const categories = [category];
|
||||
if (props.extraCategories) categories.push(...props.extraCategories);
|
||||
const token = createToken({
|
||||
name,
|
||||
pattern,
|
||||
categories,
|
||||
longer_alt: Identifier,
|
||||
});
|
||||
const categoryName = Array.isArray(category) ? category[0].name : category.name;
|
||||
if (tokenLists[categoryName] === undefined) tokenLists[categoryName] = [];
|
||||
tokenLists[categoryName].push(token);
|
||||
const patternWord = pattern.toString().match(/^\/([a-zA-Z0-9]*)\/[a-zA-Z]*$/ui);
|
||||
if (patternWord && patternWord[1]) token.$autocomplete = patternWord[1];
|
||||
Object.assign(token, props);
|
||||
return token;
|
||||
};
|
||||
|
||||
const HSpace = createToken({
|
||||
name: "HSpace",
|
||||
pattern: /[ \t]+/,
|
||||
group: Lexer.SKIPPED
|
||||
});
|
||||
|
||||
const EOL = createToken({
|
||||
name: "EOL",
|
||||
line_breaks: true,
|
||||
pattern: /[ \t\r]*\n\s*/,
|
||||
label: "End of line",
|
||||
});
|
||||
|
||||
const Comment = createToken({
|
||||
name: "Comment",
|
||||
pattern: /(#|\/\/)[^\n]*/,
|
||||
group: Lexer.SKIPPED
|
||||
});
|
||||
|
||||
const NumberLiteral = createToken({
|
||||
name: "NumberLiteral",
|
||||
pattern: /-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/,
|
||||
});
|
||||
|
||||
const Identifier = createToken({
|
||||
name: "Identifier",
|
||||
pattern: /[a-zA-Z_][a-zA-Z_0-9]*/,
|
||||
});
|
||||
|
||||
const ComparisonOperator = createToken({
|
||||
name: "ComparisonOperator",
|
||||
pattern: Lexer.NA,
|
||||
});
|
||||
|
||||
const Currency = createCategory("Currency");
|
||||
const PrestigeEvent = createCategory("PrestigeEvent");
|
||||
const StudyPath = createCategory("StudyPath");
|
||||
const TimeUnit = createCategory("TimeUnit");
|
||||
const TTCurrency = createCategory("TTCurrency");
|
||||
|
||||
createInCategory(ComparisonOperator, "OpGTE", />=/, {
|
||||
$autocomplete: ">=",
|
||||
$compare: (a, b) => Decimal.gte(a, b),
|
||||
});
|
||||
createInCategory(ComparisonOperator, "OpLTE", /<=/, {
|
||||
$autocomplete: "<=",
|
||||
$compare: (a, b) => Decimal.lte(a, b),
|
||||
});
|
||||
createInCategory(ComparisonOperator, "OpGT", />/, {
|
||||
$autocomplete: ">",
|
||||
$compare: (a, b) => Decimal.gt(a, b),
|
||||
});
|
||||
createInCategory(ComparisonOperator, "OpLT", /</, {
|
||||
$autocomplete: "<",
|
||||
$compare: (a, b) => Decimal.lt(a, b),
|
||||
});
|
||||
|
||||
createInCategory(Currency, "EP", /ep/i, {
|
||||
extraCategories: [TTCurrency],
|
||||
$buyTT: () => TimeTheorems.buyWithEP(),
|
||||
$getter: () => player.eternityPoints
|
||||
});
|
||||
createInCategory(Currency, "IP", /ip/i, {
|
||||
extraCategories: [TTCurrency],
|
||||
$buyTT: () => TimeTheorems.buyWithIP(),
|
||||
$getter: () => player.infinityPoints
|
||||
});
|
||||
createInCategory(Currency, "AM", /am/i, {
|
||||
extraCategories: [TTCurrency],
|
||||
$buyTT: () => TimeTheorems.buyWithAntimatter(),
|
||||
$getter: () => player.money
|
||||
});
|
||||
createInCategory(Currency, "DT", /dt/i, { $getter: () => player.dilation.dilatedTime });
|
||||
createInCategory(Currency, "TP", /tp/i, { $getter: () => player.dilation.tachyonParticles });
|
||||
createInCategory(Currency, "RG", /rg/i, { $getter: () => new Decimal(Replicanti.galaxies.total) });
|
||||
createInCategory(Currency, "Rep", /rep(licanti)?/i, {
|
||||
$autocomplete: "rep",
|
||||
$getter: () => player.replicanti.amount,
|
||||
});
|
||||
createInCategory(Currency, "TT", /(tt|time theorems?)/i, {
|
||||
$autocomplete: "tt",
|
||||
$getter: () => player.timestudy.theorem,
|
||||
});
|
||||
createInCategory(Currency, "Completions", /completions/i, {
|
||||
$getter: () => {
|
||||
// If we are not in an EC, pretend like we have a ton of completions so any check for sufficient
|
||||
// completions returns true
|
||||
if (!EternityChallenge.isRunning) return Decimal.MAX_NUMBER;
|
||||
return EternityChallenge.gainedCompletionStatus().totalCompletions;
|
||||
}
|
||||
});
|
||||
|
||||
// $prestigeLevel is used by things that wait for a prestige event. Something waiting for
|
||||
// eternity will be triggered by something waiting for reality, for example.
|
||||
createInCategory(PrestigeEvent, "Infinity", /infinity/i, {
|
||||
extraCategories: [StudyPath],
|
||||
$autobuyer: Autobuyer.infinity,
|
||||
$autobuyerDurationMode: AutoCrunchMode.TIME,
|
||||
$prestigeAvailable: () => canCrunch(),
|
||||
$prestige: () => bigCrunchResetRequest(true),
|
||||
$prestigeLevel: 1,
|
||||
$studyPath: TimeStudyPath.INFINITY_DIM,
|
||||
});
|
||||
createInCategory(PrestigeEvent, "Eternity", /eternity/i, {
|
||||
$autobuyer: Autobuyer.eternity,
|
||||
$autobuyerDurationMode: AutoEternityMode.TIME,
|
||||
$prestigeAvailable: () => canEternity(),
|
||||
$prestigeLevel: 2,
|
||||
$prestige: () => eternity(false, true),
|
||||
});
|
||||
createInCategory(PrestigeEvent, "Reality", /reality/i, {
|
||||
$autobuyer: Autobuyer.reality,
|
||||
$prestigeAvailable: () => isRealityAvailable(),
|
||||
$prestigeLevel: 3,
|
||||
$prestige: () => autoReality(),
|
||||
});
|
||||
|
||||
createInCategory(StudyPath, "Idle", /idle/i, { $studyPath: TimeStudyPath.IDLE });
|
||||
createInCategory(StudyPath, "Passive", /passive/i, { $studyPath: TimeStudyPath.PASSIVE });
|
||||
createInCategory(StudyPath, "Active", /active/i, { $studyPath: TimeStudyPath.ACTIVE });
|
||||
createInCategory(StudyPath, "Normal", /normal/i, { $studyPath: TimeStudyPath.NORMAL_DIM });
|
||||
createInCategory(StudyPath, "Time", /time/i, { $studyPath: TimeStudyPath.TIME_DIM });
|
||||
|
||||
createInCategory(TimeUnit, "Milliseconds", /ms/i, {
|
||||
$autocomplete: "ms",
|
||||
$scale: 1,
|
||||
});
|
||||
createInCategory(TimeUnit, "Seconds", /s(ec(onds?)?)?/i, {
|
||||
$autocomplete: "sec",
|
||||
$scale: 1000,
|
||||
});
|
||||
createInCategory(TimeUnit, "Minutes", /m(in(utes?)?)?/i, {
|
||||
$autocomplete: "min",
|
||||
$scale: 60 * 1000,
|
||||
});
|
||||
createInCategory(TimeUnit, "Hours", /h(ours?)?/i, {
|
||||
$autocomplete: "hours",
|
||||
$scale: 3600 * 1000,
|
||||
});
|
||||
|
||||
const Keyword = createToken({
|
||||
name: "Keyword",
|
||||
pattern: Lexer.NA,
|
||||
longer_alt: Identifier,
|
||||
});
|
||||
|
||||
const keywordTokens = [];
|
||||
const createKeyword = (name, pattern, props = {}) => {
|
||||
const categories = [Keyword];
|
||||
if (props.extraCategories) categories.push(...props.extraCategories);
|
||||
const token = createToken({
|
||||
name,
|
||||
pattern,
|
||||
categories,
|
||||
longer_alt: Identifier,
|
||||
});
|
||||
token.$autocomplete = name.toLocaleLowerCase();
|
||||
keywordTokens.push(token);
|
||||
Object.assign(token, props);
|
||||
return token;
|
||||
};
|
||||
|
||||
createKeyword("Auto", /auto/i);
|
||||
createKeyword("Buy", /buy/i);
|
||||
createKeyword("Define", /define/i);
|
||||
createKeyword("If", /if/i);
|
||||
createKeyword("Load", /load/i);
|
||||
createKeyword("Max", /max/i, {
|
||||
extraCategories: [TTCurrency],
|
||||
$buyTT: () => TimeTheorems.buyMax(),
|
||||
});
|
||||
createKeyword("Nowait", /nowait/i);
|
||||
createKeyword("Off", /off/i);
|
||||
createKeyword("On", /on/i);
|
||||
createKeyword("Pause", /pause/i);
|
||||
// Presets are a little special, because they can be named anything (like ec12 or wait)
|
||||
// So, we consume the label at the same time as we consume the preset. In order to report
|
||||
// errors, we also match just the word preset. And, we have to not match comments.
|
||||
createKeyword("Preset", /preset([ \t]+(\/(?!\/)|[^\s#/])*)?/i);
|
||||
createKeyword("Respec", /respec/i);
|
||||
createKeyword("Restart", /restart/i);
|
||||
createKeyword("Start", /start/i);
|
||||
createKeyword("Studies", /studies/i);
|
||||
createKeyword("Unlock", /unlock/i);
|
||||
createKeyword("Until", /until/i);
|
||||
createKeyword("Use", /use/i);
|
||||
createKeyword("Wait", /wait/i);
|
||||
createKeyword("While", /while/i);
|
||||
createKeyword("BlackHole", /black[ \t]+hole/i, {
|
||||
$autocomplete: "black hole",
|
||||
});
|
||||
createKeyword("StoreTime", /stored?[ \t]+time/i, {
|
||||
$autocomplete: "store time",
|
||||
});
|
||||
|
||||
createKeyword("Dilation", /dilation/i);
|
||||
createKeyword("EC", /ec/i);
|
||||
// We allow ECLiteral to consume lots of digits because that makes error reporting more
|
||||
// clear (it's nice to say ec123 is an invalid ec)
|
||||
const ECLiteral = createToken({
|
||||
name: "ECLiteral",
|
||||
pattern: /ec[1-9][0-9]*/i,
|
||||
longer_alt: Identifier,
|
||||
});
|
||||
|
||||
const LCurly = createToken({ name: "LCurly", pattern: /[ \t]*{/ });
|
||||
const RCurly = createToken({ name: "RCurly", pattern: /[ \t]*}/ });
|
||||
const Comma = createToken({ name: "Comma", pattern: /,/ });
|
||||
const EqualSign = createToken({ name: "EqualSign", pattern: /=/, label: "=" });
|
||||
const Pipe = createToken({ name: "Pipe", pattern: /\|/, label: "|" });
|
||||
const Dash = createToken({ name: "Dash", pattern: /-/, label: "-" });
|
||||
|
||||
// The order here is the order the lexer looks for tokens in.
|
||||
const automatorTokens = [
|
||||
HSpace, Comment, EOL,
|
||||
LCurly, RCurly, Comma, EqualSign, Pipe, Dash,
|
||||
ComparisonOperator, ...tokenLists.ComparisonOperator,
|
||||
NumberLiteral,
|
||||
ECLiteral,
|
||||
Keyword, ...keywordTokens,
|
||||
PrestigeEvent, ...tokenLists.PrestigeEvent,
|
||||
StudyPath, ...tokenLists.StudyPath,
|
||||
Currency, ...tokenLists.Currency,
|
||||
TTCurrency,
|
||||
TimeUnit, ...tokenLists.TimeUnit,
|
||||
Identifier,
|
||||
];
|
||||
|
||||
// Labels only affect error messages and Diagrams.
|
||||
LCurly.LABEL = "'{'";
|
||||
RCurly.LABEL = "'}'";
|
||||
NumberLiteral.LABEL = "Number";
|
||||
Comma.LABEL = "❟";
|
||||
|
||||
const lexer = new Lexer(automatorTokens, {
|
||||
positionTracking: "full",
|
||||
ensureOptimizations: true
|
||||
});
|
||||
|
||||
// The lexer uses an ID system that's separate from indices into the token array
|
||||
const tokenIds = [];
|
||||
for (const token of lexer.lexerDefinition) {
|
||||
tokenIds[token.tokenTypeIdx] = token;
|
||||
}
|
||||
|
||||
// We use this while building up the grammar
|
||||
const tokenMap = automatorTokens.mapToObject(e => e.name, e => e);
|
||||
|
||||
return {
|
||||
lexer,
|
||||
tokens: automatorTokens,
|
||||
tokenIds,
|
||||
tokenMap,
|
||||
};
|
||||
})();
|
140
javascripts/core/automator/parser.js
Normal file
140
javascripts/core/automator/parser.js
Normal file
@ -0,0 +1,140 @@
|
||||
"use strict";
|
||||
|
||||
const AutomatorGrammar = (function() {
|
||||
const Parser = chevrotain.Parser;
|
||||
const T = AutomatorLexer.tokenMap;
|
||||
|
||||
// ----------------- parser -----------------
|
||||
class AutomatorParser extends Parser {
|
||||
constructor() {
|
||||
super(AutomatorLexer.tokens, {
|
||||
recoveryEnabled: true,
|
||||
outputCst: true,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line consistent-this
|
||||
const $ = this;
|
||||
|
||||
$.RULE("script", () => $.SUBRULE($.block));
|
||||
|
||||
$.RULE("block", () => $.MANY_SEP({
|
||||
SEP: T.EOL,
|
||||
DEF: () => $.OPTION(() => $.SUBRULE($.command)),
|
||||
}));
|
||||
|
||||
// This is a bit ugly looking. Chevrotain uses Function.toString() to do crazy
|
||||
// optimizations. That clashes with our desire to build our list of commands dynamically.
|
||||
// We are creating a function body like this one:
|
||||
// $.RULE("command", () => {
|
||||
// $.OR(
|
||||
// $.c1 || ($.c1 = [
|
||||
// { ALT: () => $.SUBRULE($.badCommand) },
|
||||
// { ALT: () => $.SUBRULE($.auto) },
|
||||
// { ALT: () => $.SUBRULE($.define) },
|
||||
// { ALT: () => $.SUBRULE($.ifBlock) },
|
||||
|
||||
const commandAlts = [
|
||||
"$.SUBRULE($.badCommand)",
|
||||
"$.CONSUME(chevrotain.EOF)",
|
||||
];
|
||||
|
||||
for (const cmd of AutomatorCommands) {
|
||||
$.RULE(cmd.id, cmd.rule($));
|
||||
commandAlts.push(`$.SUBRULE($.${cmd.id})`);
|
||||
}
|
||||
|
||||
const commandOr = window.Function("$", `
|
||||
return () => $.OR($.c1 || ($.c1 = [
|
||||
${commandAlts.map(e => `{ ALT: () => ${e} },`).join("\n")}]));
|
||||
`);
|
||||
|
||||
$.RULE("command", commandOr($));
|
||||
|
||||
$.RULE("badCommand", () => $.AT_LEAST_ONE(() => $.SUBRULE($.badCommandToken)),
|
||||
{ resyncEnabled: false, }
|
||||
);
|
||||
|
||||
$.RULE("badCommandToken", () => $.OR([
|
||||
{ ALT: () => $.CONSUME(T.Identifier) },
|
||||
{ ALT: () => $.CONSUME(T.NumberLiteral) },
|
||||
{ ALT: () => $.CONSUME(T.ComparisonOperator) },
|
||||
]), { resyncEnabled: false, });
|
||||
|
||||
$.RULE("comparison", () => {
|
||||
$.OR([
|
||||
{
|
||||
ALT: () => {
|
||||
$.CONSUME(T.Currency);
|
||||
$.CONSUME(T.ComparisonOperator);
|
||||
$.SUBRULE($.compareValue);
|
||||
}
|
||||
},
|
||||
{
|
||||
ALT: () => {
|
||||
$.SUBRULE1($.compareValue);
|
||||
$.CONSUME1(T.ComparisonOperator);
|
||||
$.CONSUME1(T.Currency);
|
||||
}
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
$.RULE("compareValue", () => $.OR([
|
||||
{ ALT: () => $.CONSUME(T.NumberLiteral) },
|
||||
{ ALT: () => $.CONSUME(T.Identifier) },
|
||||
]));
|
||||
|
||||
$.RULE("duration", () => {
|
||||
$.CONSUME(T.NumberLiteral);
|
||||
$.CONSUME(T.TimeUnit);
|
||||
});
|
||||
|
||||
$.RULE("eternityChallenge", () => $.OR([
|
||||
{
|
||||
ALT: () => {
|
||||
$.CONSUME(T.EC);
|
||||
$.CONSUME(T.NumberLiteral);
|
||||
}
|
||||
},
|
||||
{ ALT: () => $.CONSUME(T.ECLiteral) }
|
||||
]));
|
||||
|
||||
$.RULE("studyList", () => {
|
||||
$.AT_LEAST_ONE(() => $.SUBRULE($.studyListEntry));
|
||||
// Support the |3 export format for EC number
|
||||
$.OPTION(() => {
|
||||
$.CONSUME(T.Pipe);
|
||||
$.CONSUME1(T.NumberLiteral, { LABEL: "ECNumber" });
|
||||
});
|
||||
}, { resyncEnabled: false });
|
||||
|
||||
$.RULE("studyListEntry", () => {
|
||||
$.OR([
|
||||
{ ALT: () => $.SUBRULE($.studyRange) },
|
||||
{ ALT: () => $.CONSUME(T.NumberLiteral) },
|
||||
{ ALT: () => $.CONSUME(T.StudyPath) },
|
||||
]);
|
||||
$.OPTION(() => $.CONSUME(T.Comma));
|
||||
});
|
||||
|
||||
$.RULE("studyRange", () => {
|
||||
$.CONSUME(T.NumberLiteral, { LABEL: "firstStudy" });
|
||||
$.CONSUME(T.Dash);
|
||||
$.CONSUME1(T.NumberLiteral, { LABEL: "lastStudy" });
|
||||
});
|
||||
|
||||
// Very important to call this after all the rules have been setup.
|
||||
// otherwise the parser may not work correctly as it will lack information
|
||||
// derived from the self analysis.
|
||||
$.performSelfAnalysis();
|
||||
}
|
||||
}
|
||||
|
||||
const parser = new AutomatorParser();
|
||||
|
||||
return {
|
||||
parser,
|
||||
// This field is filled in by automator-validate.js
|
||||
validate: null,
|
||||
};
|
||||
}());
|
@ -5,31 +5,54 @@ function bigCrunchAnimation() {
|
||||
setTimeout(() => {
|
||||
document.body.style.animation = "";
|
||||
}, 2000);
|
||||
setTimeout(bigCrunchReset(true), 1000);
|
||||
setTimeout(bigCrunchReset(), 1000);
|
||||
}
|
||||
|
||||
function bigCrunchReset(disableAnimation = false) {
|
||||
function canCrunch() {
|
||||
const challenge = NormalChallenge.current || InfinityChallenge.current;
|
||||
if (player.money.lt(Decimal.MAX_NUMBER) || (challenge && player.money.lt(challenge.goal))) {
|
||||
return;
|
||||
const goal = challenge === undefined ? Decimal.MAX_NUMBER : challenge.goal;
|
||||
if (player.money.lt(goal)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleChallengeCompletion() {
|
||||
if (!NormalChallenge(1).isCompleted) {
|
||||
NormalChallenge(1).complete();
|
||||
}
|
||||
const challenge = NormalChallenge.current || InfinityChallenge.current;
|
||||
if (!challenge) return;
|
||||
if (!challenge.isCompleted) {
|
||||
challenge.complete();
|
||||
Autobuyer.tryUnlockAny();
|
||||
}
|
||||
challenge.updateChallengeTime();
|
||||
if (NormalChallenge(9).isRunning) {
|
||||
kong.submitStats("NormalChallenge 9 time record (ms)", Math.floor(player.thisInfinityTime));
|
||||
}
|
||||
if (!player.options.retryChallenge) {
|
||||
player.challenge.normal.current = 0;
|
||||
player.challenge.infinity.current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function bigCrunchResetRequest(disableAnimation = false) {
|
||||
if (!canCrunch()) return;
|
||||
const earlyGame = player.bestInfinityTime > 60000 && !player.break;
|
||||
if (earlyGame && !disableAnimation && player.options.animations.bigCrunch) {
|
||||
bigCrunchAnimation();
|
||||
return;
|
||||
} else {
|
||||
bigCrunchReset();
|
||||
}
|
||||
}
|
||||
|
||||
function bigCrunchReset() {
|
||||
if (!canCrunch()) return;
|
||||
const earlyGame = player.bestInfinityTime > 60000 && !player.break;
|
||||
const challenge = NormalChallenge.current || InfinityChallenge.current;
|
||||
EventHub.dispatch(GameEvent.BIG_CRUNCH_BEFORE);
|
||||
if (challenge) {
|
||||
if (!challenge.isCompleted) {
|
||||
challenge.complete();
|
||||
Autobuyer.tryUnlockAny();
|
||||
}
|
||||
challenge.updateChallengeTime();
|
||||
}
|
||||
handleChallengeCompletion();
|
||||
|
||||
if (earlyGame || (challenge && !player.options.retryChallenge)) showTab("dimensions");
|
||||
if (NormalChallenge(9).isRunning) {
|
||||
kong.submitStats('NormalChallenge 9 time record (ms)', Math.floor(player.thisInfinityTime));
|
||||
}
|
||||
let infinityPoints = gainedInfinityPoints();
|
||||
player.infinityPoints = player.infinityPoints.plus(infinityPoints);
|
||||
addInfinityTime(player.thisInfinityTime, player.thisInfinityRealTime, infinityPoints);
|
||||
@ -39,15 +62,6 @@ function bigCrunchReset(disableAnimation = false) {
|
||||
|
||||
if (EternityChallenge(4).tryFail()) return;
|
||||
|
||||
if (player.infinitied.gt(0) && !NormalChallenge(1).isCompleted) {
|
||||
NormalChallenge(1).complete();
|
||||
Autobuyer.tryUnlockAny();
|
||||
}
|
||||
if (!player.options.retryChallenge) {
|
||||
player.challenge.normal.current = 0;
|
||||
player.challenge.infinity.current = 0;
|
||||
}
|
||||
|
||||
// FIXME: Infinitified is now Decimal so decide what happens here!
|
||||
//kong.submitStats('Infinitied', Player.totalInfinitied);
|
||||
kong.submitStats('Fastest Infinity time (ms)', Math.floor(player.bestInfinityTime));
|
||||
@ -100,7 +114,7 @@ function secondSoftReset() {
|
||||
AchievementTimers.marathon2.reset();
|
||||
}
|
||||
|
||||
document.getElementById("bigcrunch").onclick = bigCrunchReset;
|
||||
document.getElementById("bigcrunch").onclick = bigCrunchResetRequest;
|
||||
|
||||
function totalIPMult() {
|
||||
if (Effarig.isRunning && Effarig.currentStage === EFFARIG_STAGES.INFINITY) {
|
||||
|
@ -1,75 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var canvas4 = document.getElementById("automatorTreeCanvas");
|
||||
var ctx4 = canvas4.getContext("2d");
|
||||
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
|
||||
function resizeCanvas() {
|
||||
canvas4.width = 0;
|
||||
canvas4.height = 0;
|
||||
canvas4.width = document.body.scrollWidth;
|
||||
canvas4.height = document.body.scrollHeight;
|
||||
drawAutomatorTree();
|
||||
}
|
||||
|
||||
function drawAutomatorTreeBranch(num1, num2) {
|
||||
if (document.getElementById("automation").style.display === "none") return
|
||||
var id1 = parseInt(num1.split("automator")[1])
|
||||
var id2 = parseInt(num2.split("automator")[1])
|
||||
var start = document.getElementById(num1).getBoundingClientRect();
|
||||
var end = document.getElementById(num2).getBoundingClientRect();
|
||||
var x1 = start.left + (start.width / 2) + (document.documentElement.scrollLeft || document.body.scrollLeft);
|
||||
var y1 = start.top + (start.height / 2) + (document.documentElement.scrollTop || document.body.scrollTop);
|
||||
var x2 = end.left + (end.width / 2) + (document.documentElement.scrollLeft || document.body.scrollLeft);
|
||||
var y2 = end.top + (end.height / 2) + (document.documentElement.scrollTop || document.body.scrollTop);
|
||||
ctx4.lineWidth=10;
|
||||
ctx4.beginPath();
|
||||
if (player.reality.automatorCommands.has(id1) && player.reality.automatorCommands.has(id2)) {
|
||||
ctx4.strokeStyle="#000";
|
||||
} else {
|
||||
ctx4.strokeStyle="#444";
|
||||
}
|
||||
ctx4.moveTo(x1, y1);
|
||||
ctx4.lineTo(x2, y2);
|
||||
ctx4.stroke();
|
||||
}
|
||||
|
||||
function drawAutomatorTree() {
|
||||
ctx4.clearRect(0, 0, canvas4.width, canvas4.height);
|
||||
drawAutomatorTreeBranch("automator11", "automator21");
|
||||
drawAutomatorTreeBranch("automator11", "automator22");
|
||||
drawAutomatorTreeBranch("automator11", "automator23");
|
||||
drawAutomatorTreeBranch("automator11", "automator32");
|
||||
drawAutomatorTreeBranch("automator11", "automator32");
|
||||
drawAutomatorTreeBranch("automator11", "automator41");
|
||||
drawAutomatorTreeBranch("automator11", "automator42");
|
||||
drawAutomatorTreeBranch("automator21", "automator31");
|
||||
drawAutomatorTreeBranch("automator42", "automator52");
|
||||
drawAutomatorTreeBranch("automator12", "automator24");
|
||||
drawAutomatorTreeBranch("automator12", "automator25");
|
||||
drawAutomatorTreeBranch("automator12", "automator26");
|
||||
drawAutomatorTreeBranch("automator26", "automator36");
|
||||
drawAutomatorTreeBranch("automator12", "automator34");
|
||||
drawAutomatorTreeBranch("automator12", "automator35");
|
||||
drawAutomatorTreeBranch("automator12", "automator43");
|
||||
drawAutomatorTreeBranch("automator12", "automator44");
|
||||
drawAutomatorTreeBranch("automator41", "automator51");
|
||||
drawAutomatorTreeBranch("automator43", "automator53");
|
||||
drawAutomatorTreeBranch("automator44", "automator54");
|
||||
drawAutomatorTreeBranch("automator51", "automator61");
|
||||
drawAutomatorTreeBranch("automator52", "automator62");
|
||||
drawAutomatorTreeBranch("automator54", "automator63");
|
||||
drawAutomatorTreeBranch("automator54", "automator64");
|
||||
drawAutomatorTreeBranch("automator61", "automator71");
|
||||
drawAutomatorTreeBranch("automator62", "automator72");
|
||||
drawAutomatorTreeBranch("automator64", "automator73");
|
||||
drawAutomatorTreeBranch("automator71", "automator81");
|
||||
drawAutomatorTreeBranch("automator71", "automator82");
|
||||
drawAutomatorTreeBranch("automator73", "automator83");
|
||||
drawAutomatorTreeBranch("automator73", "automator84");
|
||||
}
|
||||
|
||||
let nodes = [];
|
||||
let edges = [];
|
||||
const nodeContainer = $(".vis-network")[0];
|
||||
|
@ -111,6 +111,7 @@ class NormalChallengeState extends GameMechanicState {
|
||||
complete() {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
player.challenge.normal.completedBits |= 1 << this.id;
|
||||
Autobuyer.tryUnlockAny();
|
||||
}
|
||||
|
||||
get goal() {
|
||||
|
@ -83,7 +83,19 @@ const AutoGlyphPickMode = {
|
||||
RANDOM: 0,
|
||||
RARITY: 1,
|
||||
ABOVE_SACRIFICE_THRESHOLD: 2,
|
||||
}
|
||||
};
|
||||
|
||||
const TimeStudyPath = {
|
||||
NONE: 0,
|
||||
NORMAL_DIM: 1,
|
||||
INFINITY_DIM: 2,
|
||||
TIME_DIM: 3,
|
||||
ACTIVE: 4,
|
||||
PASSIVE: 5,
|
||||
IDLE: 6,
|
||||
LIGHT: 7,
|
||||
DARK: 8
|
||||
};
|
||||
|
||||
// Use through Automator.Instructions; here to support creation of index by ID
|
||||
const _AutomatorInstructions = Object.freeze({
|
||||
@ -321,7 +333,7 @@ function _makeAutomatorInstructionsById() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const Automator = Object.freeze({
|
||||
const AutomatorInstructions = Object.freeze({
|
||||
Instructions: _AutomatorInstructions,
|
||||
InstructionsById: _makeAutomatorInstructionsById(),
|
||||
});
|
||||
|
@ -69,16 +69,16 @@ class EternityChallengeState extends GameMechanicState {
|
||||
}
|
||||
|
||||
set completions(value) {
|
||||
if (Enslaved.isRunning && this.id === 1) {
|
||||
player.eternityChalls[this.fullId] = Math.min(1000, value);
|
||||
return;
|
||||
}
|
||||
player.eternityChalls[this.fullId] = Math.min(value, TIERS_PER_EC);
|
||||
player.eternityChalls[this.fullId] = Math.min(value, this.maxCompletions);
|
||||
}
|
||||
|
||||
get isFullyCompleted() {
|
||||
if (Enslaved.isRunning && this.id === 1) return this.completions === 1000;
|
||||
return this.completions === TIERS_PER_EC;
|
||||
return this.completions === this.maxCompletions;
|
||||
}
|
||||
|
||||
get maxCompletions() {
|
||||
if (Enslaved.isRunning && this.id === 1) return 1000;
|
||||
return TIERS_PER_EC;
|
||||
}
|
||||
|
||||
get initialGoal() {
|
||||
@ -102,9 +102,15 @@ class EternityChallengeState extends GameMechanicState {
|
||||
}
|
||||
|
||||
goalAtCompletions(completions) {
|
||||
return completions > 0 ?
|
||||
this.initialGoal.times(this.goalIncrease.pow(completions)) :
|
||||
this.initialGoal;
|
||||
return completions > 0
|
||||
? this.initialGoal.times(this.goalIncrease.pow(completions))
|
||||
: this.initialGoal;
|
||||
}
|
||||
|
||||
completionsAtIP(ip) {
|
||||
if (ip.lt(this.initialGoal)) return 0;
|
||||
const completions = (ip.dividedBy(this.initialGoal)).log10() / this.goalIncrease.log10();
|
||||
return Math.min(Math.floor(completions), this.maxCompletions);
|
||||
}
|
||||
|
||||
addCompletion() {
|
||||
@ -243,3 +249,40 @@ EternityChallenge.autoCompleteTick = function() {
|
||||
}
|
||||
player.reality.lastAutoEC %= threshold;
|
||||
};
|
||||
|
||||
EternityChallenge.gainedCompletionStatus = function() {
|
||||
const currentEC = EternityChallenge.current;
|
||||
const currentCompletions = currentEC.completions;
|
||||
const status = {
|
||||
fullyCompleted: currentEC.isFullyCompleted,
|
||||
gainedCompletions: 0,
|
||||
totalCompletions: currentCompletions,
|
||||
};
|
||||
if (status.fullyCompleted) return status;
|
||||
if (!Perk.studyECBulk.isBought) {
|
||||
if (currentEC.canBeCompleted) {
|
||||
++status.totalCompletions;
|
||||
status.gainedCompletions = 1;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
status.totalCompletions = currentEC.completionsAtIP(player.infinityPoints);
|
||||
|
||||
if (EternityChallenge(4).isRunning) {
|
||||
const maxEC4Valid = player.infinitied.lte(16) ? 5 - Math.ceil(player.infinitied.toNumber() / 4) : 0;
|
||||
if (status.totalCompletions >= maxEC4Valid && status.gainedCompletions > 1) {
|
||||
status.totalCompletions = Math.min(status.totalCompletions, maxEC4Valid);
|
||||
status.failedCondition = "(Too many infinities for more)";
|
||||
}
|
||||
} else if (EternityChallenge(12).isRunning) {
|
||||
const maxEC12Valid = 5 - Math.floor(player.thisEternity / 200);
|
||||
if (status.totalCompletions >= maxEC12Valid && status.gainedCompletions > 1) {
|
||||
status.totalCompletions = Math.min(status.totalCompletions, maxEC12Valid);
|
||||
status.failedCondition = "(Too slow for more)";
|
||||
}
|
||||
}
|
||||
status.gainedCompletions = status.totalCompletions - currentCompletions;
|
||||
status.hasMoreCompletions = status.gainedCompletions < currentEC.maxCompletions;
|
||||
status.nextGoalAt = currentEC.goalAtCompletions(status.totalCompletions);
|
||||
return status;
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ class EventHub {
|
||||
}
|
||||
|
||||
offAll(target) {
|
||||
for (const handlers in this._handlers) {
|
||||
for (const handlers of Object.keys(this._handlers)) {
|
||||
this._handlers[handlers] = this._handlers[handlers]
|
||||
.filter(handler => handler.target !== target);
|
||||
}
|
||||
@ -103,6 +103,7 @@ const GameEvent = {
|
||||
REALITY_UPGRADE_BOUGHT: "REALITY_UPGRADE_BOUGHT",
|
||||
PERK_BOUGHT: "PERK_BOUGHT",
|
||||
BLACK_HOLE_UPGRADE_BOUGHT: "BLACK_HOLE_UPGRADE_BOUGHT",
|
||||
GAME_LOAD: "GAME_LOAD",
|
||||
|
||||
// UI Events
|
||||
UPDATE: "UPDATE",
|
||||
|
@ -244,3 +244,7 @@ Array.prototype.compact = function() {
|
||||
};
|
||||
|
||||
Decimal.MAX_NUMBER = new Decimal(Number.MAX_VALUE);
|
||||
|
||||
String.isWhiteSpace = function(value) {
|
||||
return value && !value.trim();
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ GameKeyboard.bindRepeatableHotkey("s", () => sacrificeBtnClick());
|
||||
GameKeyboard.bindRepeatableHotkey("r", () => replicantiGalaxy());
|
||||
GameKeyboard.bindRepeatableHotkey("t", () => buyMaxTickSpeed());
|
||||
GameKeyboard.bindRepeatableHotkey("shift+t", () => buyTickSpeed());
|
||||
GameKeyboard.bindRepeatableHotkey("c", () => bigCrunchReset());
|
||||
GameKeyboard.bindRepeatableHotkey("c", () => bigCrunchResetRequest());
|
||||
GameKeyboard.bindRepeatableHotkey("e", () => eternity());
|
||||
|
||||
(function() {
|
||||
|
@ -35,7 +35,6 @@ class PerkState extends PurchasableMechanicState {
|
||||
if (!super.purchase()) return;
|
||||
GameCache.achSkipPerkCount.invalidate();
|
||||
GameCache.buyablePerks.invalidate();
|
||||
updateAutomatorRows();
|
||||
drawPerkNetwork();
|
||||
EventHub.dispatch(GameEvent.PERK_BOUGHT);
|
||||
}
|
||||
|
@ -220,7 +220,19 @@ let player = {
|
||||
pp: 0,
|
||||
autoEC: true,
|
||||
lastAutoEC: 0,
|
||||
partEternitied: 0
|
||||
partEternitied: 0,
|
||||
automator: {
|
||||
state: {
|
||||
mode: AutomatorMode.STOP,
|
||||
topLevelScript: 0,
|
||||
editorScript: 0,
|
||||
repeat: false,
|
||||
stack: [],
|
||||
},
|
||||
scripts: {
|
||||
},
|
||||
lastID: 0,
|
||||
}
|
||||
},
|
||||
blackHole: Array.range(0, 2).map(id => ({
|
||||
id,
|
||||
@ -447,7 +459,7 @@ function guardFromNaNValues(obj) {
|
||||
if (!obj.hasOwnProperty(key)) continue;
|
||||
|
||||
//TODO: rework autobuyer saving
|
||||
if (key === "autobuyers" || key === "autoSacrifice") continue;
|
||||
if (key === "autobuyers" || key === "autoSacrifice" || key === "automator") continue;
|
||||
|
||||
let value = obj[key];
|
||||
if (isObject(value)) {
|
||||
|
@ -391,7 +391,6 @@ function completeReality(force, reset, auto = false) {
|
||||
Tab.dimensions.normal.show();
|
||||
}
|
||||
AchievementTimers.marathon2.reset();
|
||||
updateAutomatorRows();
|
||||
drawPerkNetwork();
|
||||
|
||||
resetInfinityPoints();
|
||||
|
261
javascripts/core/secret-formula/reality/automator.js
Normal file
261
javascripts/core/secret-formula/reality/automator.js
Normal file
@ -0,0 +1,261 @@
|
||||
"use strict";
|
||||
|
||||
GameDatabase.reality.automator = {
|
||||
commands: [
|
||||
{
|
||||
id: 0,
|
||||
keyword: "wait",
|
||||
name: "<b>wait</b> - wait for something",
|
||||
syntax: "<b>wait</b> condition",
|
||||
description: "Forces automator to wait for some condition or event",
|
||||
sections: [
|
||||
{
|
||||
name: "CONDITIONS",
|
||||
items: [
|
||||
{
|
||||
header: "<i>resource</i> <i>comparison</i> <i>number</i>",
|
||||
description: `
|
||||
Wait until resource amount satisfies the comparison.<br>
|
||||
<b>Resources</b>: am (Antimatter), ip (Infinity Points), ep (Eternity Points), dt (Dilated Time),
|
||||
tp (Tachyon Particles), rg (Replicanti Galaxies), rep/replicanti, tt/time theorems<br>
|
||||
<b>Comparisons</b>: <, <=, > >=<br>
|
||||
<b>Number</b> should be in scientific format, e.g. 1000, 1e100, 1.8e308
|
||||
`
|
||||
},
|
||||
{
|
||||
header: "<i>completions</i> <i>comparison</i> <i>number</i>",
|
||||
description: `
|
||||
Wait for a certain <b>total</b> number of EC completions that you'd get at eternity.<br>
|
||||
<b>Comparisons</b>: >, >=
|
||||
`
|
||||
},
|
||||
{
|
||||
header: "<i>prestige</i>",
|
||||
description: `
|
||||
Wait until certain prestige has been triggered.<br>
|
||||
<b>Prestiges</b>: infinity, eternity, reality
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
examples: [
|
||||
"wait infinity",
|
||||
"wait am >= 1e308",
|
||||
"wait completions >= 5",
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
keyword: "start",
|
||||
name: "<b>start</b> - start Eternity Challenge (this also unlocks said EC) or Time Dilation",
|
||||
syntax: "<b>start</b> [ec<i>X</i>|dilation]",
|
||||
examples: [
|
||||
"start ec12",
|
||||
"start dilation"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
keyword: "infinity / eternity / reality",
|
||||
name: "<b>infinity|eternity|reality</b> - triggers Infinity, Eternity, or Reality",
|
||||
syntax: "<b>infinity</b>,<br> <b>eternity</b>,<br> <b>reality</b>",
|
||||
examples: [
|
||||
"infinity",
|
||||
"reality",
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
keyword: "tt",
|
||||
name: "<b>tt</b> - purchases Time Theorems with a resource or buys the maximum possible",
|
||||
syntax:
|
||||
"<b>tt</b> action",
|
||||
sections: [
|
||||
{
|
||||
name: "ACTIONS",
|
||||
items: [
|
||||
{
|
||||
header: "<i>resources</i>",
|
||||
description: `
|
||||
Buys with a specific resource.<br>
|
||||
<b>Resources</b>: am (Antimatter), ip (Infinity Points), ep (Eternity Points)
|
||||
`
|
||||
},
|
||||
{
|
||||
header: "<i>max</i>",
|
||||
description: `
|
||||
Buys the maximum number of time theorems from all resources.
|
||||
`
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
keyword: "black hole",
|
||||
name: "<b>black hole</b> - turns the Black Hole on and off",
|
||||
syntax: "<b>black hole</b> on/off",
|
||||
examples: [
|
||||
"black hole on",
|
||||
"black hole off",
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
keyword: "store time",
|
||||
name: "<b>store time</b> - either turns on/off the storing of time or can be used to use stored time",
|
||||
syntax: "<b>store time</b> action",
|
||||
sections: [
|
||||
{
|
||||
name: "ACTIONS",
|
||||
items: [
|
||||
{
|
||||
header: "<i>on/off</i>",
|
||||
description: `
|
||||
Turns storing time on or off.
|
||||
`
|
||||
},
|
||||
{
|
||||
header: "<i>use</i>",
|
||||
description: `
|
||||
Uses stored time.
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
keyword: "unlock",
|
||||
name: "<b>unlock</b> - can be used to unlock certain features",
|
||||
syntax: "<b>unlock</b> feature",
|
||||
examples: [
|
||||
"unlock dilation",
|
||||
"unlock ec<i>X</i>"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
keyword: "auto",
|
||||
name: "<b>auto</b> - can turn the Infinity/Eternity/Reality autobuyers on or off and can edit the inputs on the infinity and eternity autobuyers (editing will turn them on)",
|
||||
syntax: "<b>auto</b> [infinity|eternity] [interval]",
|
||||
examples: [
|
||||
"auto infinity on",
|
||||
"auto eternity off",
|
||||
"auto infinity 30s",
|
||||
"auto eternity 10 seconds"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
keyword: "if",
|
||||
name: "<b>if</b> - compares your amount to the game's amount of something, such as a currency",
|
||||
syntax: "<b>if</b> [am|ip|ep|dt|tp|rg|rep|tt|completions] (comparison) [number]",
|
||||
examples: [
|
||||
"if ep <= 1e3000",
|
||||
"if dt >= 1e50",
|
||||
"if ip < 1e1500000"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
keyword: "pause",
|
||||
name: "<b>pause</b> - pauses the automator for a set amount of time",
|
||||
syntax: "<b>pause</b> [interval]",
|
||||
examples: [
|
||||
"pause 10s",
|
||||
"pause 1 minute",
|
||||
"pause 34 seconds"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
keyword: "until",
|
||||
name: "<b>until</b> - repeats commands until a condition or event",
|
||||
syntax: `<b>until</b> [condition | event] {<br>
|
||||
<blockquote>commands</blockquote>
|
||||
}<br>
|
||||
<b>condition</b>: [quantity] (comparison) [number]<br>
|
||||
<b>quantity</b>: [am|ip|ep|dt|tp|rg|rep|tt|completions]<br>
|
||||
<b>event</b>: [infinity|eternity|reality] (can happen at any time after loop starts)`,
|
||||
description: `Commands are repeated; the condition is checked at the start and every
|
||||
time the loop repeats. If an event is specified, then the loop will repeat until the
|
||||
event occurs. (The event has to happen after the loop begins).<br>
|
||||
A variable name may be used in place of <b>number</b>, see <b>define</b>`,
|
||||
examples: [
|
||||
`until ep > 1e500 {<br>
|
||||
<blockquote>
|
||||
tt max<br>
|
||||
studies nowait 11-62</blockquote>
|
||||
}`,
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
keyword: "while",
|
||||
name: "<b>while</b> - repeats commands while a condition is met",
|
||||
syntax: `<b>while</b> [quantity] (comparison) [number]{<br>
|
||||
<blockquote>commands</blockquote>
|
||||
}<br>
|
||||
<b>quantity</b>: [am|ip|ep|dt|tp|rg|rep|tt|completions]<br>
|
||||
<b>comparison</b>: [<|<=|>=|>]<br>
|
||||
<b>number</b>: Number in normal or scientific notation`,
|
||||
description: `Commands are repeated; the condition is checked at the start and every
|
||||
time the loop repeats.<br>
|
||||
A variable name may be used in place of <b>number</b>, see <b>define</b>`,
|
||||
examples: [
|
||||
`while ep < 1e500 {<br>
|
||||
<blockquote>
|
||||
tt max<br>
|
||||
studies nowait 11-62</blockquote>
|
||||
}`,
|
||||
`while myThreshold > am { ...`,
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
keyword: "studies respec",
|
||||
name: "<b>studies respec</b> - respec time studies on next eternity",
|
||||
syntax: `<b>studies respec</b>`,
|
||||
examples: [
|
||||
`studies respec`,
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
keyword: "studies load preset",
|
||||
name: "<b>studies load preset</b> - Load a saved study preset",
|
||||
syntax: `<b>studies load preset [name | number]</b>`,
|
||||
description: `Loads a study preset, as if you'd clicked on the button in the time
|
||||
study tab. Number is 1 to 6 (corresponding to slot). The given name can also be used.`,
|
||||
examples: [
|
||||
`studies load preset 2`,
|
||||
`studies load preset dil`,
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
keyword: "studies",
|
||||
name: "<b>studies</b> - Purchase time studies",
|
||||
syntax: `<b>studies [nowait] <i>[study list]</i></b>`,
|
||||
description: `Purchase time studies specified. If <b>nowait</b> is present, then
|
||||
the automator will purchase as many studies as possible at the moment, and move on
|
||||
to the next command.<br>
|
||||
If <b>nowait</b> is <i>not</i> present, then the automator will buy the studies in order,
|
||||
waiting for them to become available/affordable if necessary.<br>
|
||||
The study list can consist of study numbers, separated by spaces or commas, ranges of
|
||||
studies (for example, <i>11-62</i>) and the following aliases:<br>
|
||||
<blockquote><b>normal, infinity, time, active, passive, idle</b></blockquote>
|
||||
A variable name may be used in place of study list, see <b>define</b>
|
||||
The string produced by "export" in the time study tab can be used with this command.`,
|
||||
examples: [
|
||||
"studies nowait 11,21,31",
|
||||
"studies 11-62, normal, 111, idle",
|
||||
"studies nowait ec6Studies",
|
||||
]
|
||||
},
|
||||
]
|
||||
};
|
@ -76,12 +76,10 @@ const GameStorage = {
|
||||
verifyPlayerObject(save) {
|
||||
return save !== undefined && save !== null && save.money !== undefined;
|
||||
},
|
||||
|
||||
|
||||
save(silent = false) {
|
||||
if (GlyphSelection.active) return;
|
||||
if (++this.saved > 99) SecretAchievement(12).unlock();
|
||||
player.reality.automatorOn = automatorOn;
|
||||
player.reality.automatorCurrentRow = automatorIdx;
|
||||
const root = {
|
||||
current: this.currentSlot,
|
||||
saves: this.saves
|
||||
@ -89,7 +87,7 @@ const GameStorage = {
|
||||
localStorage.setItem(this.localStorageKey, GameSaveSerializer.serialize(root));
|
||||
if (!silent) GameUI.notify.info("Game saved");
|
||||
},
|
||||
|
||||
|
||||
export() {
|
||||
const save = GameSaveSerializer.serialize(player);
|
||||
copyToClipboardAndNotify(save);
|
||||
@ -144,8 +142,6 @@ const GameStorage = {
|
||||
Autobuyer.checkIntervalAchievements();
|
||||
Autobuyer.checkBulkAchievements();
|
||||
Autobuyer.convertPropertiesToDecimal();
|
||||
resizeCanvas();
|
||||
updateAutomatorRows();
|
||||
checkPerkValidity();
|
||||
Teresa.checkPPShopValidity();
|
||||
drawPerkNetwork();
|
||||
@ -155,18 +151,13 @@ const GameStorage = {
|
||||
Theme.set(player.options.theme);
|
||||
Notation.find(player.options.notation).setCurrent();
|
||||
|
||||
if (localStorage.getItem("automatorScript1") !== null) {
|
||||
importAutomatorScript(localStorage.getItem("automatorScript1"));
|
||||
}
|
||||
automatorOn = player.reality.automatorOn;
|
||||
if (automatorOn) $("#automatorOn")[0].checked = true;
|
||||
automatorIdx = player.reality.automatorCurrentRow;
|
||||
|
||||
if (player.options.newUI) {
|
||||
ui.view.newUI = true;
|
||||
ui.view.page = "new-dimensions-tab"
|
||||
ui.view.page = "new-dimensions-tab";
|
||||
}
|
||||
|
||||
EventHub.dispatch(GameEvent.GAME_LOAD);
|
||||
AutomatorBackend.initializeFromSave();
|
||||
Lazy.invalidateAll();
|
||||
|
||||
let diff = Date.now() - player.lastUpdate;
|
||||
|
@ -1,17 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const TimeStudyPath = {
|
||||
NONE: 0,
|
||||
NORMAL_DIM: 1,
|
||||
INFINITY_DIM: 2,
|
||||
TIME_DIM: 3,
|
||||
ACTIVE: 4,
|
||||
PASSIVE: 5,
|
||||
IDLE: 6,
|
||||
LIGHT: 7,
|
||||
DARK: 8
|
||||
};
|
||||
|
||||
const NormalTimeStudies = {};
|
||||
|
||||
NormalTimeStudies.pathList = [
|
||||
|
@ -14,7 +14,6 @@ let until10Setting = true;
|
||||
function showTab(tabName) {
|
||||
tryShowtab(tabName);
|
||||
hideLegacyTabs(tabName);
|
||||
resizeCanvas();
|
||||
Modal.hide();
|
||||
if (document.getElementById("perks").style.display !== "none") network.moveTo({position: {x:0, y:0}, scale: 0.8, offset: {x:0, y:0}})
|
||||
}
|
||||
@ -400,14 +399,6 @@ function randomStuffThatShouldBeRefactored() {
|
||||
if (RealityUpgrades.allBought) $("#celestialsbtn").show() // Rebuyables and that one null value = 6
|
||||
else $("#celestialsbtn").hide()
|
||||
|
||||
if (player.realities > 3) {
|
||||
$("#automatorUnlock").hide()
|
||||
$(".automator-container").show()
|
||||
} else {
|
||||
$("#automatorUnlock").show()
|
||||
$(".automator-container").hide()
|
||||
}
|
||||
|
||||
ttMaxTimer++;
|
||||
if (autoBuyMaxTheorems()) ttMaxTimer = 0;
|
||||
|
||||
@ -796,6 +787,7 @@ function gameLoop(diff, options = {}) {
|
||||
V.checkForUnlocks();
|
||||
Laitela.handleMatterDimensionUnlocks();
|
||||
matterDimensionLoop(realDiff);
|
||||
AutomatorBackend.update();
|
||||
|
||||
EventHub.dispatch(GameEvent.GAME_TICK_AFTER);
|
||||
GameUI.update();
|
||||
@ -993,7 +985,6 @@ function showRealityTab(tabName) {
|
||||
tab.style.display = 'none';
|
||||
}
|
||||
}
|
||||
resizeCanvas()
|
||||
if (document.getElementById("perks").style.display !== "none") network.moveTo({position: {x:0, y:0}, scale: 0.8, offset: {x:0, y:0}})
|
||||
}
|
||||
|
||||
@ -1034,7 +1025,6 @@ function init() {
|
||||
Tab.dimensions.normal.show();
|
||||
GameStorage.load();
|
||||
kong.init();
|
||||
TLN.append_line_numbers("automator") // Automator line numbers
|
||||
|
||||
//if (typeof kongregate === 'undefined') document.getElementById("shopbtn").style.display = "none"
|
||||
}
|
||||
@ -1063,7 +1053,6 @@ window.onload = function() {
|
||||
|
||||
window.onfocus = function() {
|
||||
setShiftKey(false);
|
||||
drawAutomatorTree();
|
||||
};
|
||||
|
||||
window.onblur = function() {
|
||||
@ -1073,7 +1062,6 @@ window.onblur = function() {
|
||||
function setShiftKey(isDown) {
|
||||
shiftDown = isDown;
|
||||
ui.view.shiftDown = isDown;
|
||||
document.getElementById("automatorloadsavetext").textContent = isDown ? "save:" : "load:";
|
||||
if (isDown) showPerkLabels()
|
||||
else hidePerkLabels()
|
||||
}
|
||||
|
4
javascripts/lib/Sortable.min.js
vendored
4
javascripts/lib/Sortable.min.js
vendored
File diff suppressed because one or more lines are too long
72
javascripts/lib/active-line.js
Normal file
72
javascripts/lib/active-line.js
Normal file
@ -0,0 +1,72 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
var WRAP_CLASS = "CodeMirror-activeline";
|
||||
var BACK_CLASS = "CodeMirror-activeline-background";
|
||||
var GUTT_CLASS = "CodeMirror-activeline-gutter";
|
||||
|
||||
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
|
||||
var prev = old == CodeMirror.Init ? false : old;
|
||||
if (val == prev) return
|
||||
if (prev) {
|
||||
cm.off("beforeSelectionChange", selectionChange);
|
||||
clearActiveLines(cm);
|
||||
delete cm.state.activeLines;
|
||||
}
|
||||
if (val) {
|
||||
cm.state.activeLines = [];
|
||||
updateActiveLines(cm, cm.listSelections());
|
||||
cm.on("beforeSelectionChange", selectionChange);
|
||||
}
|
||||
});
|
||||
|
||||
function clearActiveLines(cm) {
|
||||
for (var i = 0; i < cm.state.activeLines.length; i++) {
|
||||
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
|
||||
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
|
||||
cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
function sameArray(a, b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++)
|
||||
if (a[i] != b[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateActiveLines(cm, ranges) {
|
||||
var active = [];
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var range = ranges[i];
|
||||
var option = cm.getOption("styleActiveLine");
|
||||
if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty())
|
||||
continue
|
||||
var line = cm.getLineHandleVisualStart(range.head.line);
|
||||
if (active[active.length - 1] != line) active.push(line);
|
||||
}
|
||||
if (sameArray(cm.state.activeLines, active)) return;
|
||||
cm.operation(function() {
|
||||
clearActiveLines(cm);
|
||||
for (var i = 0; i < active.length; i++) {
|
||||
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
|
||||
cm.addLineClass(active[i], "background", BACK_CLASS);
|
||||
cm.addLineClass(active[i], "gutter", GUTT_CLASS);
|
||||
}
|
||||
cm.state.activeLines = active;
|
||||
});
|
||||
}
|
||||
|
||||
function selectionChange(cm, sel) {
|
||||
updateActiveLines(cm, sel.ranges);
|
||||
}
|
||||
});
|
2
javascripts/lib/chevrotain.min.js
vendored
Normal file
2
javascripts/lib/chevrotain.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
9755
javascripts/lib/codemirror.js
Normal file
9755
javascripts/lib/codemirror.js
Normal file
File diff suppressed because it is too large
Load Diff
252
javascripts/lib/lint.js
Normal file
252
javascripts/lib/lint.js
Normal file
@ -0,0 +1,252 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
var GUTTER_ID = "CodeMirror-lint-markers";
|
||||
|
||||
function showTooltip(e, content) {
|
||||
var tt = document.createElement("div");
|
||||
tt.className = "CodeMirror-lint-tooltip";
|
||||
tt.appendChild(content.cloneNode(true));
|
||||
document.body.appendChild(tt);
|
||||
|
||||
function position(e) {
|
||||
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
|
||||
tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
|
||||
tt.style.left = (e.clientX + 5) + "px";
|
||||
}
|
||||
CodeMirror.on(document, "mousemove", position);
|
||||
position(e);
|
||||
if (tt.style.opacity != null) tt.style.opacity = 1;
|
||||
return tt;
|
||||
}
|
||||
function rm(elt) {
|
||||
if (elt.parentNode) elt.parentNode.removeChild(elt);
|
||||
}
|
||||
function hideTooltip(tt) {
|
||||
if (!tt.parentNode) return;
|
||||
if (tt.style.opacity == null) rm(tt);
|
||||
tt.style.opacity = 0;
|
||||
setTimeout(function() { rm(tt); }, 600);
|
||||
}
|
||||
|
||||
function showTooltipFor(e, content, node) {
|
||||
var tooltip = showTooltip(e, content);
|
||||
function hide() {
|
||||
CodeMirror.off(node, "mouseout", hide);
|
||||
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
|
||||
}
|
||||
var poll = setInterval(function() {
|
||||
if (tooltip) for (var n = node;; n = n.parentNode) {
|
||||
if (n && n.nodeType == 11) n = n.host;
|
||||
if (n == document.body) return;
|
||||
if (!n) { hide(); break; }
|
||||
}
|
||||
if (!tooltip) return clearInterval(poll);
|
||||
}, 400);
|
||||
CodeMirror.on(node, "mouseout", hide);
|
||||
}
|
||||
|
||||
function LintState(cm, options, hasGutter) {
|
||||
this.marked = [];
|
||||
this.options = options;
|
||||
this.timeout = null;
|
||||
this.hasGutter = hasGutter;
|
||||
this.onMouseOver = function(e) { onMouseOver(cm, e); };
|
||||
this.waitingFor = 0
|
||||
}
|
||||
|
||||
function parseOptions(_cm, options) {
|
||||
if (options instanceof Function) return {getAnnotations: options};
|
||||
if (!options || options === true) options = {};
|
||||
return options;
|
||||
}
|
||||
|
||||
function clearMarks(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (state.hasGutter) cm.clearGutter(GUTTER_ID);
|
||||
for (var i = 0; i < state.marked.length; ++i)
|
||||
state.marked[i].clear();
|
||||
state.marked.length = 0;
|
||||
}
|
||||
|
||||
function makeMarker(labels, severity, multiple, tooltips) {
|
||||
var marker = document.createElement("div"), inner = marker;
|
||||
marker.className = "CodeMirror-lint-marker-" + severity;
|
||||
if (multiple) {
|
||||
inner = marker.appendChild(document.createElement("div"));
|
||||
inner.className = "CodeMirror-lint-marker-multiple";
|
||||
}
|
||||
|
||||
if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
|
||||
showTooltipFor(e, labels, inner);
|
||||
});
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
function getMaxSeverity(a, b) {
|
||||
if (a == "error") return a;
|
||||
else return b;
|
||||
}
|
||||
|
||||
function groupByLine(annotations) {
|
||||
var lines = [];
|
||||
for (var i = 0; i < annotations.length; ++i) {
|
||||
var ann = annotations[i], line = ann.from.line;
|
||||
(lines[line] || (lines[line] = [])).push(ann);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function annotationTooltip(ann) {
|
||||
var severity = ann.severity;
|
||||
if (!severity) severity = "error";
|
||||
var tip = document.createElement("div");
|
||||
tip.className = "CodeMirror-lint-message-" + severity;
|
||||
if (typeof ann.messageHTML != 'undefined') {
|
||||
tip.innerHTML = ann.messageHTML;
|
||||
} else {
|
||||
tip.appendChild(document.createTextNode(ann.message));
|
||||
}
|
||||
return tip;
|
||||
}
|
||||
|
||||
function lintAsync(cm, getAnnotations, passOptions) {
|
||||
var state = cm.state.lint
|
||||
var id = ++state.waitingFor
|
||||
function abort() {
|
||||
id = -1
|
||||
cm.off("change", abort)
|
||||
}
|
||||
cm.on("change", abort)
|
||||
getAnnotations(cm.getValue(), function(annotations, arg2) {
|
||||
cm.off("change", abort)
|
||||
if (state.waitingFor != id) return
|
||||
if (arg2 && annotations instanceof CodeMirror) annotations = arg2
|
||||
cm.operation(function() {updateLinting(cm, annotations)})
|
||||
}, passOptions, cm);
|
||||
}
|
||||
|
||||
function startLinting(cm) {
|
||||
var state = cm.state.lint, options = state.options;
|
||||
/*
|
||||
* Passing rules in `options` property prevents JSHint (and other linters) from complaining
|
||||
* about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
|
||||
*/
|
||||
var passOptions = options.options || options;
|
||||
var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
|
||||
if (!getAnnotations) return;
|
||||
if (options.async || getAnnotations.async) {
|
||||
lintAsync(cm, getAnnotations, passOptions)
|
||||
} else {
|
||||
var annotations = getAnnotations(cm.getValue(), passOptions, cm);
|
||||
if (!annotations) return;
|
||||
if (annotations.then) annotations.then(function(issues) {
|
||||
cm.operation(function() {updateLinting(cm, issues)})
|
||||
});
|
||||
else cm.operation(function() {updateLinting(cm, annotations)})
|
||||
}
|
||||
}
|
||||
|
||||
function updateLinting(cm, annotationsNotSorted) {
|
||||
clearMarks(cm);
|
||||
var state = cm.state.lint, options = state.options;
|
||||
|
||||
var annotations = groupByLine(annotationsNotSorted);
|
||||
|
||||
for (var line = 0; line < annotations.length; ++line) {
|
||||
var anns = annotations[line];
|
||||
if (!anns) continue;
|
||||
|
||||
var maxSeverity = null;
|
||||
var tipLabel = state.hasGutter && document.createDocumentFragment();
|
||||
|
||||
for (var i = 0; i < anns.length; ++i) {
|
||||
var ann = anns[i];
|
||||
var severity = ann.severity;
|
||||
if (!severity) severity = "error";
|
||||
maxSeverity = getMaxSeverity(maxSeverity, severity);
|
||||
|
||||
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
|
||||
if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
|
||||
|
||||
if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
|
||||
className: "CodeMirror-lint-mark-" + severity,
|
||||
__annotation: ann
|
||||
}));
|
||||
}
|
||||
|
||||
if (state.hasGutter)
|
||||
cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1,
|
||||
state.options.tooltips));
|
||||
}
|
||||
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
|
||||
}
|
||||
|
||||
function onChange(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (!state) return;
|
||||
clearTimeout(state.timeout);
|
||||
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
|
||||
}
|
||||
|
||||
function popupTooltips(annotations, e) {
|
||||
var target = e.target || e.srcElement;
|
||||
var tooltip = document.createDocumentFragment();
|
||||
for (var i = 0; i < annotations.length; i++) {
|
||||
var ann = annotations[i];
|
||||
tooltip.appendChild(annotationTooltip(ann));
|
||||
}
|
||||
showTooltipFor(e, tooltip, target);
|
||||
}
|
||||
|
||||
function onMouseOver(cm, e) {
|
||||
var target = e.target || e.srcElement;
|
||||
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
|
||||
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
|
||||
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
|
||||
|
||||
var annotations = [];
|
||||
for (var i = 0; i < spans.length; ++i) {
|
||||
var ann = spans[i].__annotation;
|
||||
if (ann) annotations.push(ann);
|
||||
}
|
||||
if (annotations.length) popupTooltips(annotations, e);
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("lint", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
clearMarks(cm);
|
||||
if (cm.state.lint.options.lintOnChange !== false)
|
||||
cm.off("change", onChange);
|
||||
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
|
||||
clearTimeout(cm.state.lint.timeout);
|
||||
delete cm.state.lint;
|
||||
}
|
||||
|
||||
if (val) {
|
||||
var gutters = cm.getOption("gutters"), hasLintGutter = false;
|
||||
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
|
||||
var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
|
||||
if (state.options.lintOnChange !== false)
|
||||
cm.on("change", onChange);
|
||||
if (state.options.tooltips != false && state.options.tooltips != "gutter")
|
||||
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
|
||||
|
||||
startLinting(cm);
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("performLint", function() {
|
||||
if (this.state.lint) startLinting(this);
|
||||
});
|
||||
});
|
460
javascripts/lib/show-hint.js
Normal file
460
javascripts/lib/show-hint.js
Normal file
@ -0,0 +1,460 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var HINT_ELEMENT_CLASS = "CodeMirror-hint";
|
||||
var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
|
||||
|
||||
// This is the old interface, kept around for now to stay
|
||||
// backwards-compatible.
|
||||
CodeMirror.showHint = function(cm, getHints, options) {
|
||||
if (!getHints) return cm.showHint(options);
|
||||
if (options && options.async) getHints.async = true;
|
||||
var newOpts = {hint: getHints};
|
||||
if (options) for (var prop in options) newOpts[prop] = options[prop];
|
||||
return cm.showHint(newOpts);
|
||||
};
|
||||
|
||||
CodeMirror.defineExtension("showHint", function(options) {
|
||||
options = parseOptions(this, this.getCursor("start"), options);
|
||||
var selections = this.listSelections()
|
||||
if (selections.length > 1) return;
|
||||
// By default, don't allow completion when something is selected.
|
||||
// A hint function can have a `supportsSelection` property to
|
||||
// indicate that it can handle selections.
|
||||
if (this.somethingSelected()) {
|
||||
if (!options.hint.supportsSelection) return;
|
||||
// Don't try with cross-line selections
|
||||
for (var i = 0; i < selections.length; i++)
|
||||
if (selections[i].head.line != selections[i].anchor.line) return;
|
||||
}
|
||||
|
||||
if (this.state.completionActive) this.state.completionActive.close();
|
||||
var completion = this.state.completionActive = new Completion(this, options);
|
||||
if (!completion.options.hint) return;
|
||||
|
||||
CodeMirror.signal(this, "startCompletion", this);
|
||||
completion.update(true);
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("closeHint", function() {
|
||||
if (this.state.completionActive) this.state.completionActive.close()
|
||||
})
|
||||
|
||||
function Completion(cm, options) {
|
||||
this.cm = cm;
|
||||
this.options = options;
|
||||
this.widget = null;
|
||||
this.debounce = 0;
|
||||
this.tick = 0;
|
||||
this.startPos = this.cm.getCursor("start");
|
||||
this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
|
||||
|
||||
var self = this;
|
||||
cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
|
||||
}
|
||||
|
||||
var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
|
||||
return setTimeout(fn, 1000/60);
|
||||
};
|
||||
var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
|
||||
|
||||
Completion.prototype = {
|
||||
close: function() {
|
||||
if (!this.active()) return;
|
||||
this.cm.state.completionActive = null;
|
||||
this.tick = null;
|
||||
this.cm.off("cursorActivity", this.activityFunc);
|
||||
|
||||
if (this.widget && this.data) CodeMirror.signal(this.data, "close");
|
||||
if (this.widget) this.widget.close();
|
||||
CodeMirror.signal(this.cm, "endCompletion", this.cm);
|
||||
},
|
||||
|
||||
active: function() {
|
||||
return this.cm.state.completionActive == this;
|
||||
},
|
||||
|
||||
pick: function(data, i) {
|
||||
var completion = data.list[i];
|
||||
if (completion.hint) completion.hint(this.cm, data, completion);
|
||||
else this.cm.replaceRange(getText(completion), completion.from || data.from,
|
||||
completion.to || data.to, "complete");
|
||||
CodeMirror.signal(data, "pick", completion);
|
||||
this.close();
|
||||
},
|
||||
|
||||
cursorActivity: function() {
|
||||
if (this.debounce) {
|
||||
cancelAnimationFrame(this.debounce);
|
||||
this.debounce = 0;
|
||||
}
|
||||
|
||||
var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
|
||||
if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
|
||||
pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
|
||||
(!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
|
||||
this.close();
|
||||
} else {
|
||||
var self = this;
|
||||
this.debounce = requestAnimationFrame(function() {self.update();});
|
||||
if (this.widget) this.widget.disable();
|
||||
}
|
||||
},
|
||||
|
||||
update: function(first) {
|
||||
if (this.tick == null) return
|
||||
var self = this, myTick = ++this.tick
|
||||
fetchHints(this.options.hint, this.cm, this.options, function(data) {
|
||||
if (self.tick == myTick) self.finishUpdate(data, first)
|
||||
})
|
||||
},
|
||||
|
||||
finishUpdate: function(data, first) {
|
||||
if (this.data) CodeMirror.signal(this.data, "update");
|
||||
|
||||
var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
|
||||
if (this.widget) this.widget.close();
|
||||
|
||||
this.data = data;
|
||||
|
||||
if (data && data.list.length) {
|
||||
if (picked && data.list.length == 1) {
|
||||
this.pick(data, 0);
|
||||
} else {
|
||||
this.widget = new Widget(this, data);
|
||||
CodeMirror.signal(data, "shown");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function parseOptions(cm, pos, options) {
|
||||
var editor = cm.options.hintOptions;
|
||||
var out = {};
|
||||
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
|
||||
if (editor) for (var prop in editor)
|
||||
if (editor[prop] !== undefined) out[prop] = editor[prop];
|
||||
if (options) for (var prop in options)
|
||||
if (options[prop] !== undefined) out[prop] = options[prop];
|
||||
if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
|
||||
return out;
|
||||
}
|
||||
|
||||
function getText(completion) {
|
||||
if (typeof completion == "string") return completion;
|
||||
else return completion.text;
|
||||
}
|
||||
|
||||
function buildKeyMap(completion, handle) {
|
||||
var baseMap = {
|
||||
Up: function() {handle.moveFocus(-1);},
|
||||
Down: function() {handle.moveFocus(1);},
|
||||
PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
|
||||
PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
|
||||
Home: function() {handle.setFocus(0);},
|
||||
End: function() {handle.setFocus(handle.length - 1);},
|
||||
Enter: handle.pick,
|
||||
Tab: handle.pick,
|
||||
Esc: handle.close
|
||||
};
|
||||
|
||||
var mac = /Mac/.test(navigator.platform);
|
||||
|
||||
if (mac) {
|
||||
baseMap["Ctrl-P"] = function() {handle.moveFocus(-1);};
|
||||
baseMap["Ctrl-N"] = function() {handle.moveFocus(1);};
|
||||
}
|
||||
|
||||
var custom = completion.options.customKeys;
|
||||
var ourMap = custom ? {} : baseMap;
|
||||
function addBinding(key, val) {
|
||||
var bound;
|
||||
if (typeof val != "string")
|
||||
bound = function(cm) { return val(cm, handle); };
|
||||
// This mechanism is deprecated
|
||||
else if (baseMap.hasOwnProperty(val))
|
||||
bound = baseMap[val];
|
||||
else
|
||||
bound = val;
|
||||
ourMap[key] = bound;
|
||||
}
|
||||
if (custom)
|
||||
for (var key in custom) if (custom.hasOwnProperty(key))
|
||||
addBinding(key, custom[key]);
|
||||
var extra = completion.options.extraKeys;
|
||||
if (extra)
|
||||
for (var key in extra) if (extra.hasOwnProperty(key))
|
||||
addBinding(key, extra[key]);
|
||||
return ourMap;
|
||||
}
|
||||
|
||||
function getHintElement(hintsElement, el) {
|
||||
while (el && el != hintsElement) {
|
||||
if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
|
||||
el = el.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
function Widget(completion, data) {
|
||||
this.completion = completion;
|
||||
this.data = data;
|
||||
this.picked = false;
|
||||
var widget = this, cm = completion.cm;
|
||||
var ownerDocument = cm.getInputField().ownerDocument;
|
||||
var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow;
|
||||
|
||||
var hints = this.hints = ownerDocument.createElement("ul");
|
||||
var theme = completion.cm.options.theme;
|
||||
hints.className = "CodeMirror-hints " + theme;
|
||||
this.selectedHint = data.selectedHint || 0;
|
||||
|
||||
var completions = data.list;
|
||||
for (var i = 0; i < completions.length; ++i) {
|
||||
var elt = hints.appendChild(ownerDocument.createElement("li")), cur = completions[i];
|
||||
var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
|
||||
if (cur.className != null) className = cur.className + " " + className;
|
||||
elt.className = className;
|
||||
if (cur.render) cur.render(elt, data, cur);
|
||||
else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur)));
|
||||
elt.hintId = i;
|
||||
}
|
||||
|
||||
var container = completion.options.container || ownerDocument.body;
|
||||
var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
|
||||
var left = pos.left, top = pos.bottom, below = true;
|
||||
var offsetLeft = 0, offsetTop = 0;
|
||||
if (container !== ownerDocument.body) {
|
||||
// We offset the cursor position because left and top are relative to the offsetParent's top left corner.
|
||||
var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1;
|
||||
var offsetParent = isContainerPositioned ? container : container.offsetParent;
|
||||
var offsetParentPosition = offsetParent.getBoundingClientRect();
|
||||
var bodyPosition = ownerDocument.body.getBoundingClientRect();
|
||||
offsetLeft = (offsetParentPosition.left - bodyPosition.left);
|
||||
offsetTop = (offsetParentPosition.top - bodyPosition.top);
|
||||
}
|
||||
hints.style.left = (left - offsetLeft) + "px";
|
||||
hints.style.top = (top - offsetTop) + "px";
|
||||
|
||||
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
|
||||
var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth);
|
||||
var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight);
|
||||
container.appendChild(hints);
|
||||
var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
|
||||
var scrolls = hints.scrollHeight > hints.clientHeight + 1
|
||||
var startScroll = cm.getScrollInfo();
|
||||
|
||||
if (overlapY > 0) {
|
||||
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
|
||||
if (curTop - height > 0) { // Fits above cursor
|
||||
hints.style.top = (top = pos.top - height - offsetTop) + "px";
|
||||
below = false;
|
||||
} else if (height > winH) {
|
||||
hints.style.height = (winH - 5) + "px";
|
||||
hints.style.top = (top = pos.bottom - box.top - offsetTop) + "px";
|
||||
var cursor = cm.getCursor();
|
||||
if (data.from.ch != cursor.ch) {
|
||||
pos = cm.cursorCoords(cursor);
|
||||
hints.style.left = (left = pos.left - offsetLeft) + "px";
|
||||
box = hints.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
var overlapX = box.right - winW;
|
||||
if (overlapX > 0) {
|
||||
if (box.right - box.left > winW) {
|
||||
hints.style.width = (winW - 5) + "px";
|
||||
overlapX -= (box.right - box.left) - winW;
|
||||
}
|
||||
hints.style.left = (left = pos.left - overlapX - offsetLeft) + "px";
|
||||
}
|
||||
if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
|
||||
node.style.paddingRight = cm.display.nativeBarWidth + "px"
|
||||
|
||||
cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
|
||||
moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
|
||||
setFocus: function(n) { widget.changeActive(n); },
|
||||
menuSize: function() { return widget.screenAmount(); },
|
||||
length: completions.length,
|
||||
close: function() { completion.close(); },
|
||||
pick: function() { widget.pick(); },
|
||||
data: data
|
||||
}));
|
||||
|
||||
if (completion.options.closeOnUnfocus) {
|
||||
var closingOnBlur;
|
||||
cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
|
||||
cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
|
||||
}
|
||||
|
||||
cm.on("scroll", this.onScroll = function() {
|
||||
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
|
||||
var newTop = top + startScroll.top - curScroll.top;
|
||||
var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop);
|
||||
if (!below) point += hints.offsetHeight;
|
||||
if (point <= editor.top || point >= editor.bottom) return completion.close();
|
||||
hints.style.top = newTop + "px";
|
||||
hints.style.left = (left + startScroll.left - curScroll.left) + "px";
|
||||
});
|
||||
|
||||
CodeMirror.on(hints, "dblclick", function(e) {
|
||||
var t = getHintElement(hints, e.target || e.srcElement);
|
||||
if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
|
||||
});
|
||||
|
||||
CodeMirror.on(hints, "click", function(e) {
|
||||
var t = getHintElement(hints, e.target || e.srcElement);
|
||||
if (t && t.hintId != null) {
|
||||
widget.changeActive(t.hintId);
|
||||
if (completion.options.completeOnSingleClick) widget.pick();
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.on(hints, "mousedown", function() {
|
||||
setTimeout(function(){cm.focus();}, 20);
|
||||
});
|
||||
|
||||
CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Widget.prototype = {
|
||||
close: function() {
|
||||
if (this.completion.widget != this) return;
|
||||
this.completion.widget = null;
|
||||
this.hints.parentNode.removeChild(this.hints);
|
||||
this.completion.cm.removeKeyMap(this.keyMap);
|
||||
|
||||
var cm = this.completion.cm;
|
||||
if (this.completion.options.closeOnUnfocus) {
|
||||
cm.off("blur", this.onBlur);
|
||||
cm.off("focus", this.onFocus);
|
||||
}
|
||||
cm.off("scroll", this.onScroll);
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
this.completion.cm.removeKeyMap(this.keyMap);
|
||||
var widget = this;
|
||||
this.keyMap = {Enter: function() { widget.picked = true; }};
|
||||
this.completion.cm.addKeyMap(this.keyMap);
|
||||
},
|
||||
|
||||
pick: function() {
|
||||
this.completion.pick(this.data, this.selectedHint);
|
||||
},
|
||||
|
||||
changeActive: function(i, avoidWrap) {
|
||||
if (i >= this.data.list.length)
|
||||
i = avoidWrap ? this.data.list.length - 1 : 0;
|
||||
else if (i < 0)
|
||||
i = avoidWrap ? 0 : this.data.list.length - 1;
|
||||
if (this.selectedHint == i) return;
|
||||
var node = this.hints.childNodes[this.selectedHint];
|
||||
if (node) node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
|
||||
node = this.hints.childNodes[this.selectedHint = i];
|
||||
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
|
||||
if (node.offsetTop < this.hints.scrollTop)
|
||||
this.hints.scrollTop = node.offsetTop - 3;
|
||||
else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
|
||||
this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
|
||||
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
|
||||
},
|
||||
|
||||
screenAmount: function() {
|
||||
return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
|
||||
}
|
||||
};
|
||||
|
||||
function applicableHelpers(cm, helpers) {
|
||||
if (!cm.somethingSelected()) return helpers
|
||||
var result = []
|
||||
for (var i = 0; i < helpers.length; i++)
|
||||
if (helpers[i].supportsSelection) result.push(helpers[i])
|
||||
return result
|
||||
}
|
||||
|
||||
function fetchHints(hint, cm, options, callback) {
|
||||
if (hint.async) {
|
||||
hint(cm, callback, options)
|
||||
} else {
|
||||
var result = hint(cm, options)
|
||||
if (result && result.then) result.then(callback)
|
||||
else callback(result)
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAutoHints(cm, pos) {
|
||||
var helpers = cm.getHelpers(pos, "hint"), words
|
||||
if (helpers.length) {
|
||||
var resolved = function(cm, callback, options) {
|
||||
var app = applicableHelpers(cm, helpers);
|
||||
function run(i) {
|
||||
if (i == app.length) return callback(null)
|
||||
fetchHints(app[i], cm, options, function(result) {
|
||||
if (result && result.list.length > 0) callback(result)
|
||||
else run(i + 1)
|
||||
})
|
||||
}
|
||||
run(0)
|
||||
}
|
||||
resolved.async = true
|
||||
resolved.supportsSelection = true
|
||||
return resolved
|
||||
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
|
||||
return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
|
||||
} else if (CodeMirror.hint.anyword) {
|
||||
return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
|
||||
} else {
|
||||
return function() {}
|
||||
}
|
||||
}
|
||||
|
||||
CodeMirror.registerHelper("hint", "auto", {
|
||||
resolve: resolveAutoHints
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
|
||||
var cur = cm.getCursor(), token = cm.getTokenAt(cur)
|
||||
var term, from = CodeMirror.Pos(cur.line, token.start), to = cur
|
||||
if (token.start < cur.ch && /\w/.test(token.string.charAt(cur.ch - token.start - 1))) {
|
||||
term = token.string.substr(0, cur.ch - token.start)
|
||||
} else {
|
||||
term = ""
|
||||
from = cur
|
||||
}
|
||||
var found = [];
|
||||
for (var i = 0; i < options.words.length; i++) {
|
||||
var word = options.words[i];
|
||||
if (word.slice(0, term.length) == term)
|
||||
found.push(word);
|
||||
}
|
||||
|
||||
if (found.length) return {list: found, from: from, to: to};
|
||||
});
|
||||
|
||||
CodeMirror.commands.autocomplete = CodeMirror.showHint;
|
||||
|
||||
var defaultOptions = {
|
||||
hint: CodeMirror.hint.auto,
|
||||
completeSingle: true,
|
||||
alignWithWord: true,
|
||||
closeCharacters: /[\s()\[\]{};:>,]/,
|
||||
closeOnUnfocus: true,
|
||||
completeOnSingleClick: true,
|
||||
container: null,
|
||||
customKeys: null,
|
||||
extraKeys: null
|
||||
};
|
||||
|
||||
CodeMirror.defineOption("hintOptions", null);
|
||||
});
|
216
javascripts/lib/simple.js
Normal file
216
javascripts/lib/simple.js
Normal file
@ -0,0 +1,216 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineSimpleMode = function(name, states) {
|
||||
CodeMirror.defineMode(name, function(config) {
|
||||
return CodeMirror.simpleMode(config, states);
|
||||
});
|
||||
};
|
||||
|
||||
CodeMirror.simpleMode = function(config, states) {
|
||||
ensureState(states, "start");
|
||||
var states_ = {}, meta = states.meta || {}, hasIndentation = false;
|
||||
for (var state in states) if (state != meta && states.hasOwnProperty(state)) {
|
||||
var list = states_[state] = [], orig = states[state];
|
||||
for (var i = 0; i < orig.length; i++) {
|
||||
var data = orig[i];
|
||||
list.push(new Rule(data, states));
|
||||
if (data.indent || data.dedent) hasIndentation = true;
|
||||
}
|
||||
}
|
||||
var mode = {
|
||||
startState: function() {
|
||||
return {state: "start", pending: null,
|
||||
local: null, localState: null,
|
||||
indent: hasIndentation ? [] : null};
|
||||
},
|
||||
copyState: function(state) {
|
||||
var s = {state: state.state, pending: state.pending,
|
||||
local: state.local, localState: null,
|
||||
indent: state.indent && state.indent.slice(0)};
|
||||
if (state.localState)
|
||||
s.localState = CodeMirror.copyState(state.local.mode, state.localState);
|
||||
if (state.stack)
|
||||
s.stack = state.stack.slice(0);
|
||||
for (var pers = state.persistentStates; pers; pers = pers.next)
|
||||
s.persistentStates = {mode: pers.mode,
|
||||
spec: pers.spec,
|
||||
state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state),
|
||||
next: s.persistentStates};
|
||||
return s;
|
||||
},
|
||||
token: tokenFunction(states_, config),
|
||||
innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; },
|
||||
indent: indentFunction(states_, meta)
|
||||
};
|
||||
if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop))
|
||||
mode[prop] = meta[prop];
|
||||
return mode;
|
||||
};
|
||||
|
||||
function ensureState(states, name) {
|
||||
if (!states.hasOwnProperty(name))
|
||||
throw new Error("Undefined state " + name + " in simple mode");
|
||||
}
|
||||
|
||||
function toRegex(val, caret) {
|
||||
if (!val) return /(?:)/;
|
||||
var flags = "";
|
||||
if (val instanceof RegExp) {
|
||||
if (val.ignoreCase) flags = "i";
|
||||
val = val.source;
|
||||
} else {
|
||||
val = String(val);
|
||||
}
|
||||
return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags);
|
||||
}
|
||||
|
||||
function asToken(val) {
|
||||
if (!val) return null;
|
||||
if (val.apply) return val
|
||||
if (typeof val == "string") return val.replace(/\./g, " ");
|
||||
var result = [];
|
||||
for (var i = 0; i < val.length; i++)
|
||||
result.push(val[i] && val[i].replace(/\./g, " "));
|
||||
return result;
|
||||
}
|
||||
|
||||
function Rule(data, states) {
|
||||
if (data.next || data.push) ensureState(states, data.next || data.push);
|
||||
this.regex = toRegex(data.regex);
|
||||
this.token = asToken(data.token);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
function tokenFunction(states, config) {
|
||||
return function(stream, state) {
|
||||
if (state.pending) {
|
||||
var pend = state.pending.shift();
|
||||
if (state.pending.length == 0) state.pending = null;
|
||||
stream.pos += pend.text.length;
|
||||
return pend.token;
|
||||
}
|
||||
|
||||
if (state.local) {
|
||||
if (state.local.end && stream.match(state.local.end)) {
|
||||
var tok = state.local.endToken || null;
|
||||
state.local = state.localState = null;
|
||||
return tok;
|
||||
} else {
|
||||
var tok = state.local.mode.token(stream, state.localState), m;
|
||||
if (state.local.endScan && (m = state.local.endScan.exec(stream.current())))
|
||||
stream.pos = stream.start + m.index;
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
|
||||
var curState = states[state.state];
|
||||
for (var i = 0; i < curState.length; i++) {
|
||||
var rule = curState[i];
|
||||
var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex);
|
||||
if (matches) {
|
||||
if (rule.data.next) {
|
||||
state.state = rule.data.next;
|
||||
} else if (rule.data.push) {
|
||||
(state.stack || (state.stack = [])).push(state.state);
|
||||
state.state = rule.data.push;
|
||||
} else if (rule.data.pop && state.stack && state.stack.length) {
|
||||
state.state = state.stack.pop();
|
||||
}
|
||||
|
||||
if (rule.data.mode)
|
||||
enterLocalMode(config, state, rule.data.mode, rule.token);
|
||||
if (rule.data.indent)
|
||||
state.indent.push(stream.indentation() + config.indentUnit);
|
||||
if (rule.data.dedent)
|
||||
state.indent.pop();
|
||||
var token = rule.token
|
||||
if (token && token.apply) token = token(matches)
|
||||
if (matches.length > 2 && rule.token && typeof rule.token != "string") {
|
||||
state.pending = [];
|
||||
for (var j = 2; j < matches.length; j++)
|
||||
if (matches[j])
|
||||
state.pending.push({text: matches[j], token: rule.token[j - 1]});
|
||||
stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0));
|
||||
return token[0];
|
||||
} else if (token && token.join) {
|
||||
return token[0];
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
stream.next();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
function cmp(a, b) {
|
||||
if (a === b) return true;
|
||||
if (!a || typeof a != "object" || !b || typeof b != "object") return false;
|
||||
var props = 0;
|
||||
for (var prop in a) if (a.hasOwnProperty(prop)) {
|
||||
if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false;
|
||||
props++;
|
||||
}
|
||||
for (var prop in b) if (b.hasOwnProperty(prop)) props--;
|
||||
return props == 0;
|
||||
}
|
||||
|
||||
function enterLocalMode(config, state, spec, token) {
|
||||
var pers;
|
||||
if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next)
|
||||
if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p;
|
||||
var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec);
|
||||
var lState = pers ? pers.state : CodeMirror.startState(mode);
|
||||
if (spec.persistent && !pers)
|
||||
state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates};
|
||||
|
||||
state.localState = lState;
|
||||
state.local = {mode: mode,
|
||||
end: spec.end && toRegex(spec.end),
|
||||
endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),
|
||||
endToken: token && token.join ? token[token.length - 1] : token};
|
||||
}
|
||||
|
||||
function indexOf(val, arr) {
|
||||
for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true;
|
||||
}
|
||||
|
||||
function indentFunction(states, meta) {
|
||||
return function(state, textAfter, line) {
|
||||
if (state.local && state.local.mode.indent)
|
||||
return state.local.mode.indent(state.localState, textAfter, line);
|
||||
if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1)
|
||||
return CodeMirror.Pass;
|
||||
|
||||
var pos = state.indent.length - 1, rules = states[state.state];
|
||||
scan: for (;;) {
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
var rule = rules[i];
|
||||
if (rule.data.dedent && rule.data.dedentIfLineStart !== false) {
|
||||
var m = rule.regex.exec(textAfter);
|
||||
if (m && m[0]) {
|
||||
pos--;
|
||||
if (rule.next || rule.push) rules = states[rule.next || rule.push];
|
||||
textAfter = textAfter.slice(m[0].length);
|
||||
continue scan;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return pos < 0 ? 0 : state.indent[pos];
|
||||
};
|
||||
}
|
||||
});
|
1
javascripts/lib/vue-split-pane.min.js
vendored
Normal file
1
javascripts/lib/vue-split-pane.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
javascripts/lib/vuedraggable.min.js
vendored
1
javascripts/lib/vuedraggable.min.js
vendored
File diff suppressed because one or more lines are too long
2
javascripts/lib/vuedraggable.umd.min.js
vendored
Normal file
2
javascripts/lib/vuedraggable.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
171
stylesheets/automator.css
Normal file
171
stylesheets/automator.css
Normal file
@ -0,0 +1,171 @@
|
||||
._-automator-split-pane-fix {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c-automator-tab {
|
||||
height: 35rem;
|
||||
width: 94rem;
|
||||
}
|
||||
|
||||
.c-automator-tab--full-screen {
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.l-automator-tab {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
.l-automator-tab--full-screen {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.l-automator-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.l-automator-pane__content {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.l-automator-pane__controls {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.c-automator__controls {
|
||||
background-color: #262626;
|
||||
}
|
||||
|
||||
.l-automator__controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
/* for corner buttons */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.l-automator__button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.3rem 0.8rem 0.3rem 0.8rem;
|
||||
}
|
||||
|
||||
.l-automator__button--corner {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.c-automator__button {
|
||||
margin: 0.4rem;
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
.c-automator__button-play--active {
|
||||
border-color: #45a047;
|
||||
color: #45a047;
|
||||
}
|
||||
|
||||
.c-automator__button--active {
|
||||
border-color: #45a047;
|
||||
color: #45a047;
|
||||
}
|
||||
|
||||
.l-automator__script-names {
|
||||
width: 15rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.l-automator__scripts-dropdown {
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
padding: 0.1rem;
|
||||
}
|
||||
|
||||
.l-automator__rename-input {
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
padding: 0.1rem;
|
||||
}
|
||||
|
||||
.c-automator-editor__active-line {
|
||||
background: rgb(25, 10, 44);
|
||||
}
|
||||
|
||||
.c-automator-editor__active-line-gutter {
|
||||
font-weight: bold;
|
||||
background: rgb(25, 10, 44);
|
||||
filter: brightness(200%);
|
||||
}
|
||||
|
||||
.c-automator-editor {
|
||||
font-family: Typewriter, serif;
|
||||
font-size: 1.4rem;
|
||||
text-align: left;
|
||||
border-bottom: 0.15rem solid #262626;
|
||||
}
|
||||
|
||||
.l-automator-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.l-automator-editor__codemirror-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.CodeMirror-hint {
|
||||
font-size: 1rem;
|
||||
font-family: Typewriter, serif;
|
||||
}
|
||||
|
||||
.cm-s-liquibyte.CodeMirror {
|
||||
font-size: 1.4rem;
|
||||
font-family: Typewriter, serif;
|
||||
line-height: 1.6rem;
|
||||
/* required for expanding into pane */
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.c-automator-docs {
|
||||
border-right: 0.15rem solid #262626;
|
||||
border-bottom: 0.15rem solid #262626;
|
||||
background-color: lightgray;
|
||||
padding-left: 1rem;
|
||||
font-size: 1.4rem;
|
||||
color: black;
|
||||
text-align: left;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.c-automator-docs-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.c-automator-docs-page__link {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.c-automator-docs-page__indented {
|
||||
margin-left: 4rem;
|
||||
}
|
346
stylesheets/codemirror/codemirror.css
Normal file
346
stylesheets/codemirror/codemirror.css
Normal file
@ -0,0 +1,346 @@
|
||||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: black;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
.cm-fat-cursor-mark {
|
||||
background-color: rgba(20, 255, 20, 0.5);
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
}
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: -20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -30px; margin-right: -30px;
|
||||
padding-bottom: 30px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 30px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -30px;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
73
stylesheets/codemirror/lint.css
Normal file
73
stylesheets/codemirror/lint.css
Normal file
@ -0,0 +1,73 @@
|
||||
/* The lint marker gutter */
|
||||
.CodeMirror-lint-markers {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-tooltip {
|
||||
background-color: #ffd;
|
||||
border: 1px solid black;
|
||||
border-radius: 4px 4px 4px 4px;
|
||||
color: black;
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
overflow: hidden;
|
||||
padding: 2px 5px;
|
||||
position: fixed;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
z-index: 100;
|
||||
max-width: 600px;
|
||||
opacity: 0;
|
||||
transition: opacity .4s;
|
||||
-moz-transition: opacity .4s;
|
||||
-webkit-transition: opacity .4s;
|
||||
-o-transition: opacity .4s;
|
||||
-ms-transition: opacity .4s;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {
|
||||
background-position: left bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-error {
|
||||
background-image:
|
||||
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==")
|
||||
;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
|
||||
padding-left: 18px;
|
||||
background-position: top left;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-multiple {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right bottom;
|
||||
width: 100%; height: 100%;
|
||||
}
|
95
stylesheets/codemirror/liquibyte.css
Normal file
95
stylesheets/codemirror/liquibyte.css
Normal file
@ -0,0 +1,95 @@
|
||||
.cm-s-liquibyte.CodeMirror {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
line-height: 1.2em;
|
||||
font-size: 1em;
|
||||
}
|
||||
.cm-s-liquibyte .CodeMirror-focused .cm-matchhighlight {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #0f0;
|
||||
text-decoration-style: wavy;
|
||||
}
|
||||
.cm-s-liquibyte .cm-trailingspace {
|
||||
text-decoration: line-through;
|
||||
text-decoration-color: #f00;
|
||||
text-decoration-style: dotted;
|
||||
}
|
||||
.cm-s-liquibyte .cm-tab {
|
||||
text-decoration: line-through;
|
||||
text-decoration-color: #404040;
|
||||
text-decoration-style: dotted;
|
||||
}
|
||||
.cm-s-liquibyte .CodeMirror-gutters { background-color: #262626; border-right: 1px solid #505050; padding-right: 0.8em; }
|
||||
.cm-s-liquibyte .CodeMirror-gutter-elt div { font-size: 1.2em; }
|
||||
.cm-s-liquibyte .CodeMirror-guttermarker { }
|
||||
.cm-s-liquibyte .CodeMirror-guttermarker-subtle { }
|
||||
.cm-s-liquibyte .CodeMirror-linenumber { color: #606060; padding-left: 0; }
|
||||
.cm-s-liquibyte .CodeMirror-cursor { border-left: 1px solid #eee; }
|
||||
|
||||
.cm-s-liquibyte span.cm-comment { color: #008000; }
|
||||
.cm-s-liquibyte span.cm-def { color: #ffaf40; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-keyword { color: #c080ff; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-builtin { color: #ffaf40; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-variable { color: #5967ff; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-string { color: #ff8000; }
|
||||
.cm-s-liquibyte span.cm-number { color: #0f0; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-atom { color: #bf3030; font-weight: bold; }
|
||||
|
||||
.cm-s-liquibyte span.cm-variable-2 { color: #007f7f; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-variable-3, .cm-s-liquibyte span.cm-type { color: #c080ff; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-property { color: #999; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-operator { color: #fff; }
|
||||
|
||||
.cm-s-liquibyte span.cm-meta { color: #0f0; }
|
||||
.cm-s-liquibyte span.cm-qualifier { color: #fff700; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-bracket { color: #cc7; }
|
||||
.cm-s-liquibyte span.cm-tag { color: #ff0; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-attribute { color: #c080ff; font-weight: bold; }
|
||||
.cm-s-liquibyte span.cm-error { color: #f00; }
|
||||
|
||||
.cm-s-liquibyte div.CodeMirror-selected { background-color: rgba(255, 0, 0, 0.25); }
|
||||
|
||||
.cm-s-liquibyte span.cm-compilation { background-color: rgba(255, 255, 255, 0.12); }
|
||||
|
||||
.cm-s-liquibyte .CodeMirror-activeline-background { background-color: rgba(0, 255, 0, 0.15); }
|
||||
|
||||
/* Default styles for common addons */
|
||||
.cm-s-liquibyte .CodeMirror span.CodeMirror-matchingbracket { color: #0f0; font-weight: bold; }
|
||||
.cm-s-liquibyte .CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00; font-weight: bold; }
|
||||
.CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3); }
|
||||
/* Scrollbars */
|
||||
/* Simple */
|
||||
.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div:hover, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div:hover {
|
||||
background-color: rgba(80, 80, 80, .7);
|
||||
}
|
||||
.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div {
|
||||
background-color: rgba(80, 80, 80, .3);
|
||||
border: 1px solid #404040;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.cm-s-liquibyte div.CodeMirror-simplescroll-vertical div {
|
||||
border-top: 1px solid #404040;
|
||||
border-bottom: 1px solid #404040;
|
||||
}
|
||||
.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div {
|
||||
border-left: 1px solid #404040;
|
||||
border-right: 1px solid #404040;
|
||||
}
|
||||
.cm-s-liquibyte div.CodeMirror-simplescroll-vertical {
|
||||
background-color: #262626;
|
||||
}
|
||||
.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal {
|
||||
background-color: #262626;
|
||||
border-top: 1px solid #404040;
|
||||
}
|
||||
/* Overlay */
|
||||
.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div, div.CodeMirror-overlayscroll-vertical div {
|
||||
background-color: #404040;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.cm-s-liquibyte div.CodeMirror-overlayscroll-vertical div {
|
||||
border: 1px solid #404040;
|
||||
}
|
||||
.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div {
|
||||
border: 1px solid #404040;
|
||||
}
|
85
stylesheets/codemirror/panda-syntax.css
Normal file
85
stylesheets/codemirror/panda-syntax.css
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
Name: Panda Syntax
|
||||
Author: Siamak Mokhtari (http://github.com/siamak/)
|
||||
CodeMirror template by Siamak Mokhtari (https://github.com/siamak/atom-panda-syntax)
|
||||
*/
|
||||
.cm-s-panda-syntax {
|
||||
background: #292A2B;
|
||||
color: #E6E6E6;
|
||||
line-height: 1.5;
|
||||
font-family: 'Operator Mono', 'Source Code Pro', Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
}
|
||||
.cm-s-panda-syntax .CodeMirror-cursor { border-color: #ff2c6d; }
|
||||
.cm-s-panda-syntax .CodeMirror-activeline-background {
|
||||
background: rgba(99, 123, 156, 0.1);
|
||||
}
|
||||
.cm-s-panda-syntax .CodeMirror-selected {
|
||||
background: #FFF;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-comment {
|
||||
font-style: italic;
|
||||
color: #676B79;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-operator {
|
||||
color: #f3f3f3;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-string {
|
||||
color: #19F9D8;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-string-2 {
|
||||
color: #FFB86C;
|
||||
}
|
||||
|
||||
.cm-s-panda-syntax .cm-tag {
|
||||
color: #ff2c6d;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-meta {
|
||||
color: #b084eb;
|
||||
}
|
||||
|
||||
.cm-s-panda-syntax .cm-number {
|
||||
color: #FFB86C;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-atom {
|
||||
color: #ff2c6d;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-keyword {
|
||||
color: #FF75B5;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-variable {
|
||||
color: #ffb86c;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-variable-2 {
|
||||
color: #ff9ac1;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-variable-3, .cm-s-panda-syntax .cm-type {
|
||||
color: #ff9ac1;
|
||||
}
|
||||
|
||||
.cm-s-panda-syntax .cm-def {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-property {
|
||||
color: #f3f3f3;
|
||||
}
|
||||
.cm-s-panda-syntax .cm-unit {
|
||||
color: #ffb86c;
|
||||
}
|
||||
|
||||
.cm-s-panda-syntax .cm-attribute {
|
||||
color: #ffb86c;
|
||||
}
|
||||
|
||||
.cm-s-panda-syntax .CodeMirror-matchingbracket {
|
||||
border-bottom: 1px dotted #19F9D8;
|
||||
padding-bottom: 2px;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.cm-s-panda-syntax .CodeMirror-gutters {
|
||||
background: #292a2b;
|
||||
border-right-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.cm-s-panda-syntax .CodeMirror-linenumber {
|
||||
color: #e6e6e6;
|
||||
opacity: 0.6;
|
||||
}
|
36
stylesheets/codemirror/show-hint.css
Normal file
36
stylesheets/codemirror/show-hint.css
Normal file
@ -0,0 +1,36 @@
|
||||
.CodeMirror-hints {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
list-style: none;
|
||||
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
|
||||
-webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
|
||||
box-shadow: 2px 3px 5px rgba(0,0,0,.2);
|
||||
border-radius: 3px;
|
||||
border: 1px solid silver;
|
||||
|
||||
background: white;
|
||||
font-size: 90%;
|
||||
font-family: monospace;
|
||||
|
||||
max-height: 20em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.CodeMirror-hint {
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
white-space: pre;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li.CodeMirror-hint-active {
|
||||
background: #08f;
|
||||
color: white;
|
||||
}
|
@ -487,16 +487,6 @@ button {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.automator-save-load-btn {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
font-size: 1.2rem;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
border-radius: .4rem;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.celestialtabbtn {
|
||||
background-color: #5151ec;
|
||||
border: 2px solid #d0d0d0;
|
||||
@ -598,13 +588,6 @@ button {
|
||||
|
||||
}
|
||||
|
||||
#automatorTreeCanvas {
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
z-index: -999;
|
||||
}
|
||||
|
||||
#loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@ -915,7 +898,6 @@ p,
|
||||
ul,
|
||||
ol,
|
||||
table,
|
||||
pre,
|
||||
dl {
|
||||
margin: 0 0 3px;
|
||||
}
|
||||
@ -973,21 +955,6 @@ blockquote {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
code,
|
||||
pre {
|
||||
font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, Consolas, Liberation Mono, DejaVu Sans Mono, Courier New, monospace;
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 8px 15px;
|
||||
background: #f8f8f8;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #e5e5e5;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0
|
||||
}
|
||||
@ -1130,113 +1097,6 @@ br {
|
||||
transform: translate(-50%,-50%);
|
||||
}
|
||||
|
||||
#automator {
|
||||
height: 300px;
|
||||
width: 400px;
|
||||
overflow-y: scroll;
|
||||
resize: none;
|
||||
font-family: 'PT Mono', monospace;
|
||||
color: #66ff66;
|
||||
background-color: black;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#automator::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
#automator::-webkit-scrollbar-track {
|
||||
box-shadow: inset 0 0 5px grey;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#automator::-webkit-scrollbar-thumb {
|
||||
background: #08450b;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#automator::selection {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.automatorinstruction.command {
|
||||
background-color: black;
|
||||
border: 2px solid #0b600e;
|
||||
border-radius: 5px;
|
||||
color: #d5ffd7;
|
||||
cursor: pointer;
|
||||
transition-duration: 0.12s;
|
||||
width: 150px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.automatorinstruction.target {
|
||||
background-color: black;
|
||||
border: 2px solid #2196F3;
|
||||
border-radius: 5px;
|
||||
color: #BBDEFB;
|
||||
cursor: pointer;
|
||||
transition-duration: 0.12s;
|
||||
width: 150px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.automatorinstruction.command:hover {
|
||||
color: black;
|
||||
background-color: #d5ffd7;
|
||||
}
|
||||
|
||||
.automatorinstruction.target:hover {
|
||||
color: black;
|
||||
background-color: #BBDEFB;
|
||||
}
|
||||
|
||||
.automatorinstructionbought.command {
|
||||
background-color: #0b600e;
|
||||
border: 2px solid #08450b;
|
||||
border-radius: 5px;
|
||||
color: #d5ffd7;
|
||||
cursor: default;
|
||||
transition-duration: 0.12s;
|
||||
width: 150px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.automatorinstructionbought.target {
|
||||
background-color: #2196F3;
|
||||
border: 2px solid #1976D2;
|
||||
border-radius: 5px;
|
||||
color: #BBDEFB;
|
||||
cursor: default;
|
||||
transition-duration: 0.12s;
|
||||
width: 150px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.automatorinstructionlocked.command {
|
||||
background-color: #656565;
|
||||
border: 2px solid #0b600e;
|
||||
border-radius: 5px;
|
||||
color: #d5ffd7;
|
||||
cursor: default;
|
||||
transition-duration: 0.12s;
|
||||
width: 150px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.automatorinstructionlocked.target {
|
||||
background-color: #656565;
|
||||
border: 2px solid #2196F3;
|
||||
border-radius: 5px;
|
||||
color: #BBDEFB;
|
||||
cursor: default;
|
||||
transition-duration: 0.12s;
|
||||
width: 150px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#perks {
|
||||
width: 900px;
|
||||
height: 500px;
|
||||
@ -1294,10 +1154,6 @@ screen and (max-width: 720px) {
|
||||
header p.view {
|
||||
position: static;
|
||||
}
|
||||
pre,
|
||||
code {
|
||||
word-wrap: normal;
|
||||
}
|
||||
}
|
||||
|
||||
@media print,
|
||||
@ -6312,3 +6168,78 @@ kbd {
|
||||
color: black;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.c-automator-blocks {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.o-automator-command {
|
||||
border: 1px solid gainsboro;
|
||||
padding: .5rem;
|
||||
display: inline-block;
|
||||
background: #de8787;
|
||||
border-radius: .5rem;
|
||||
width: 6rem;
|
||||
user-select: none;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.c-automator-command-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c-automator-block-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.c-automator-block-row > * {
|
||||
margin-right: .5rem
|
||||
}
|
||||
|
||||
.c-automator-block-editor {
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
border-top-left-radius: 1rem;
|
||||
border-bottom-left-radius: 1rem;
|
||||
box-sizing: border-box;
|
||||
padding: .5rem;
|
||||
tab-size: 1.5em;
|
||||
-moz-tab-size: 1.5em;
|
||||
}
|
||||
|
||||
.o-automator-block-input {
|
||||
border: 1px solid gainsboro;
|
||||
padding: .5rem;
|
||||
display: inline-block;
|
||||
background: burlywood;
|
||||
border-radius: .5rem;
|
||||
color: #4F5957;
|
||||
font-size: 1.1rem;
|
||||
font-family: Typewriter, serif;
|
||||
}
|
||||
|
||||
.o-automator-linenumber {
|
||||
color: #55ff55;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.l-automator-nested-block {
|
||||
width: fit-content;
|
||||
padding: 1rem;
|
||||
border: 2px dotted #55ff55;
|
||||
margin-left: 3rem;
|
||||
margin-top: .5rem;
|
||||
min-widtH: 30rem;
|
||||
}
|
||||
|
||||
.o-automator-block-delete {
|
||||
color: #ff3333;
|
||||
font-size: 1.7rem;
|
||||
cursor: pointer;
|
||||
}
|
@ -137,42 +137,6 @@ input.t-dark {
|
||||
color: #757575;
|
||||
}
|
||||
|
||||
.t-dark .automatorinstruction.command {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.t-dark .automatorinstruction.target {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.t-dark .automatorinstruction.command:hover {
|
||||
color: black;
|
||||
background-color: #d5ffd7;
|
||||
}
|
||||
|
||||
.t-dark .automatorinstruction.target:hover {
|
||||
color: black;
|
||||
background-color: #BBDEFB;
|
||||
}
|
||||
|
||||
.t-dark .automatorinstructionbought.command {
|
||||
background-color: #0b600e;
|
||||
border: 2px solid #08450b;
|
||||
}
|
||||
|
||||
.t-dark .automatorinstructionbought.target {
|
||||
background-color: #2196F3;
|
||||
border: 2px solid #1976D2;
|
||||
}
|
||||
|
||||
.t-dark .automatorinstructionlocked.command {
|
||||
background-color: #656565;
|
||||
}
|
||||
|
||||
.t-dark .automatorinstructionlocked.target {
|
||||
background-color: #656565;
|
||||
}
|
||||
|
||||
.t-dark #realityanimbg {
|
||||
filter: invert(1);
|
||||
}
|
@ -169,38 +169,6 @@ strong {
|
||||
text-shadow: 0 0 7px #0b600e;
|
||||
}
|
||||
|
||||
.t-s6 .automatorinstruction.command {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.t-s6 .automatorinstruction.target {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.t-s6 .automatorinstruction.command:hover {
|
||||
background-color: #d5ffd7;
|
||||
}
|
||||
|
||||
.t-s6 .automatorinstruction.target:hover {
|
||||
background-color: #BBDEFB;
|
||||
}
|
||||
|
||||
.t-s6 .automatorinstructionbought.command {
|
||||
background-color: #0b600e;
|
||||
}
|
||||
|
||||
.t-s6 .automatorinstructionbought.target {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
.t-s6 .automatorinstructionlocked.command {
|
||||
background-color: #656565;
|
||||
}
|
||||
|
||||
.t-s6 .automatorinstructionlocked.target {
|
||||
background-color: #656565;
|
||||
}
|
||||
|
||||
.t-s6 #realityanimbg {
|
||||
filter: invert(1);
|
||||
}
|
Loading…
Reference in New Issue
Block a user