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:
garnet420 2019-06-29 23:04:57 -04:00 committed by GitHub
parent c5e23a6f58
commit 4fe1131e55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 14536 additions and 1033 deletions

View File

@ -108,6 +108,7 @@
"computed-property-spacing": "error",
"consistent-this": "error",
"func-call-spacing": "error",
"guard-for-in": "warn",
"id-blacklist": [
"error",
"ret",

View File

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

View File

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

View File

@ -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">

View File

@ -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:

View File

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

View File

@ -19,7 +19,7 @@ Vue.component("sidebar-ip", {
},
infinity() {
if (this.showCrunch) {
bigCrunchReset();
bigCrunchResetRequest();
}
}
},

View File

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

View File

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

View 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>`
});

View File

@ -0,0 +1,7 @@
"use strict";
Vue.component("automator-button", {
template: `
<button class="c-automator__button l-automator__button fas" @click="emitClick" />
`
});

View 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>`
});

View File

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

View 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>`
});

View File

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

View File

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

View File

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

View File

@ -38,6 +38,12 @@ let ui = {
subtab: "",
openGlyphWeights: false,
currentGlyphTooltip: -1,
automator: {
fullScreen: false,
editorScriptID: "",
// TODO: enum
mode: true
}
},
celestials: {
subtab: ""

View File

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

View File

@ -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)

View 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;
}
},
};

View 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: "}",
}
});
}());

View 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)),
},
];
})());

View 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;
}());

View 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,
};
})();

View 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,
};
}());

View File

@ -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) {

View File

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

View File

@ -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() {

View File

@ -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(),
});

View File

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

View File

@ -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",

View File

@ -244,3 +244,7 @@ Array.prototype.compact = function() {
};
Decimal.MAX_NUMBER = new Decimal(Number.MAX_VALUE);
String.isWhiteSpace = function(value) {
return value && !value.trim();
};

View File

@ -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() {

View File

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

View File

@ -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)) {

View File

@ -391,7 +391,6 @@ function completeReality(force, reset, auto = false) {
Tab.dimensions.normal.show();
}
AchievementTimers.marathon2.reset();
updateAutomatorRows();
drawPerkNetwork();
resetInfinityPoints();

View 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",
]
},
]
};

View File

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

View File

@ -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 = [

View File

@ -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()
}

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

252
javascripts/lib/lint.js Normal file
View 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);
});
});

View 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
View 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

171
stylesheets/automator.css Normal file
View 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;
}

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

View 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%;
}

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

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

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

View File

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

View File

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

View File

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