User Script Extension: "ChainRuler" -- Use the ruler on more than 2 stars!


#1

Tired of using the ruler two stars at a time and manually adding up your ETAs? ME TOO!

The following is a script I started recently. I got some awesome help today from @Qwerty! It overrides ruler functions so that instead of measuring between two stars you can continue clicking stars and measure a more complicated route. I’m sure there are some problems with it, and there are still some things I’d like it to do, so I’ll provide the “unminified” code here so that others can collaborate.

Here’s a couple of screenshots:

No warpgates:

Through warpgates, the total “Warp Gate Flight Time” will be correct:

Now here’s the code. In chrome, you can paste this into the dev console and it should work:

//------------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2014 Dakota Hawkins and Vítězslav Ferko
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//------------------------------------------------------------------------------
var universe = NeptunesPride.universe;

// Override initRuler() to use an array of stars rather than starA/starB
universe.initRuler = function () {
    universe.ruler = {};
    universe.ruler.stars = [];
    universe.ruler.eta = 0;
    universe.ruler.baseEta = 0;
    universe.ruler.gateEta = 0;
    universe.ruler.gate = true;
    universe.ruler.totalDist = 0;
    universe.ruler.ly = "0.0";
    universe.ruler.hsRequired = 0;
};

// Provide a helper function to determine if two items represent a gated flight between a
// fleet and a star.
universe.isGatedFlight = function (fleet, star) {
    if (fleet.kind !== "fleet" || star.kind !== "star") return false;
    return (fleet.warpSpeed === 1 && fleet.path.length > 0 && fleet.path[0].uid === star.uid);
};

// Override updateRuler() to use an array of stars rather than starA/starB
universe.updateRuler = function (star) {
    // If the "star" is a fleet and it is orbiting a star, use the orbited star.
    if (star.kind === "fleet" && star.orbiting) {
        universe.ruler.stars.push(star.orbiting);
    } else {
        universe.ruler.stars.push(star);
    }

    var numStars = universe.ruler.stars.length;
    if (numStars < 2) return;

    var starA = universe.ruler.stars[numStars - 2];
    var starB = universe.ruler.stars[numStars - 1];

    var dist = universe.distance(starA.x, starA.y, starB.x, starB.y);
    var speed = universe.galaxy.fleet_speed;

    var baseEta = Math.floor(dist / speed) + 1;
    var gateEta = Math.floor(dist / (3 * speed)) + 1;

    universe.ruler.baseEta += baseEta;

    // Check whether the distance should be gated. This is the case if starA and starB are gated
    // or if one of the stars is a fleet traveling at warp to the other star.
    var eta = baseEta;
    var gated = false;
    if (universe.starsGated(starA, starB) || universe.isGatedFlight(starA, starB) || universe.isGatedFlight(starB, starA)) {
        gated = true;
        eta = gateEta;
    }

    universe.ruler.eta += eta;

    // Add up the gatedEta. This will now always represent the time if the stars are gated
    // regardless of whether they actually are. The only caviat is if one of the stars is a
    // fleet and we haven't decided it's a gated case. In that case, we have to add the ungated
    // eta, since you can't build a warp gate on a carrier.
    if (!gated && (starA.kind === "fleet" || starB.kind === "fleet")) {
        universe.ruler.gateEta += eta;
    } else {
        universe.ruler.gateEta += gateEta;
    }

    universe.ruler.totalDist += dist;

    var ly = (8 * universe.ruler.totalDist);
    universe.ruler.ly = (Math.round(ly * 10) / 10).toFixed(1);

    // Hyperspace required will be the max hyperspace required along the entire route.
    universe.ruler.hsRequired = Math.max(universe.ruler.hsRequired, Math.floor(8 * dist) - 2, 1);
};

var map = NeptunesPride.npui.map;

// Override drawRuler() to use an array of stars rather than starA/starB
map.drawRuler = function () {
    var numStars = universe.ruler.stars.length;
    if (numStars < 2) return;

    var minAlpha = 0.3, maxAlpha = 0.6, alphaStep = 0.05;

    // Draw backwards to make it easier to step off the alpha
    var alpha = maxAlpha;
    for (var i = numStars - 1; i > 0; i--) {
        map.context.strokeStyle = "rgba(255, 255, 255, " + alpha + ")";
        map.context.lineWidth = 8 * map.pixelRatio;
        map.context.beginPath();
        var x1 = map.worldToScreenX(universe.ruler.stars[i].x);
        var y1 = map.worldToScreenY(universe.ruler.stars[i].y);
        var x2 = map.worldToScreenX(universe.ruler.stars[i - 1].x);
        var y2 = map.worldToScreenY(universe.ruler.stars[i - 1].y);
        map.context.moveTo(x1, y1);
        map.context.lineTo(x2, y2);
        map.context.stroke();

        alpha = Math.max(minAlpha, alpha - alphaStep);
    }
};

var np = NeptunesPride.np;

// Override onStartRuler() to clear out the ruler.
np.onStartRuler = function () {
    universe.initRuler();

    if (universe.editMode === "ruler") {
        np.onEndRuler();
        return;
    }

    // Change to allow the ruler to start on a selected star OR fleet.
    if (universe.selectedStar) {
        universe.updateRuler(universe.selectedStar);
    } else if (universe.selectedFleet) {
        universe.updateRuler(universe.selectedFleet);
    }

    np.onResetEditMode();
    universe.editMode = "ruler";
    universe.interfaceSettings.showRuler = true;
    np.trigger("show_ruler_toolbar");
    np.trigger("map_refresh");
    np.trigger("hide_screen");
    np.trigger("hide_side_menu");
    np.trigger("hide_selection_menu");
    np.trigger("play_sound", "alt_open");
};

// Bind (or re-bind)? the v key to the ruler.
Mousetrap.bind(["v", "V"], function () { Crux.crux.trigger("start_ruler"); });

// For some reason this line must be doubled. WTF.
Crux.crux.on("start_ruler", np.onStartRuler);
Crux.crux.on("start_ruler", np.onStartRuler);

universe.initRuler();

Hopefully somebody can help minify it and make it in to a bookmarklet, I took a couple of stabs at encoding it to base64 but couldn’t get it to work. Of course, what would be REALLY swell is if @JayKyburz put this behavior in the game!

Update

Here is a bookmarklet that should work. Don’t forget to remove the leading +

+javascript:(function(){var n=NeptunesPride.universe,t,i;n.initRuler=function(){n.ruler={};n.ruler.stars=[];n.ruler.eta=0;n.ruler.baseEta=0;n.ruler.gateEta=0;n.ruler.gate=!0;n.ruler.totalDist=0;n.ruler.ly="0.0";n.ruler.hsRequired=0};n.isGatedFlight=function(n,t){return n.kind!=="fleet"||t.kind!=="star"?!1:n.warpSpeed===1&&n.path.length>0&&n.path[0].uid===t.uid};n.updateRuler=function(t){var u,e,o,l;if(t.kind==="fleet"&&t.orbiting?n.ruler.stars.push(t.orbiting):n.ruler.stars.push(t),u=n.ruler.stars.length,!(u<2)){var i=n.ruler.stars[u-2],r=n.ruler.stars[u-1],f=n.distance(i.x,i.y,r.x,r.y),s=n.galaxy.fleet_speed,h=Math.floor(f/s)+1,c=Math.floor(f/(3*s))+1;n.ruler.baseEta+=h;e=h;o=!1;(n.starsGated(i,r)||n.isGatedFlight(i,r)||n.isGatedFlight(r,i))&&(o=!0,e=c);n.ruler.eta+=e;n.ruler.gateEta+=o||i.kind!=="fleet"&&r.kind!=="fleet"?c:e;n.ruler.totalDist+=f;l=8*n.ruler.totalDist;n.ruler.ly=(Math.round(l*10)/10).toFixed(1);n.ruler.hsRequired=Math.max(n.ruler.hsRequired,Math.floor(8*f)-2,1)}};t=NeptunesPride.npui.map;t.drawRuler=function(){var u=n.ruler.stars.length,i;if(!(u<2)){var r=.6;for(i=u-1;i>0;i--){t.context.strokeStyle="rgba(255, 255, 255, "+r+")";t.context.lineWidth=8*t.pixelRatio;t.context.beginPath();var f=t.worldToScreenX(n.ruler.stars[i].x),e=t.worldToScreenY(n.ruler.stars[i].y),o=t.worldToScreenX(n.ruler.stars[i-1].x),s=t.worldToScreenY(n.ruler.stars[i-1].y);t.context.moveTo(f,e);t.context.lineTo(o,s);t.context.stroke();r=Math.max(.3,r-.05)}}};i=NeptunesPride.np;i.onStartRuler=function(){if(n.initRuler(),n.editMode==="ruler"){i.onEndRuler();return}n.selectedStar?n.updateRuler(n.selectedStar):n.selectedFleet&&n.updateRuler(n.selectedFleet);i.onResetEditMode();n.editMode="ruler";n.interfaceSettings.showRuler=!0;i.trigger("show_ruler_toolbar");i.trigger("map_refresh");i.trigger("hide_screen");i.trigger("hide_side_menu");i.trigger("hide_selection_menu");i.trigger("play_sound","alt_open")};Mousetrap.bind(["v","V"],function(){Crux.crux.trigger("start_ruler")});Crux.crux.on("start_ruler",i.onStartRuler);Crux.crux.on("start_ruler",i.onStartRuler);n.initRuler()})();

Update

Switched to the MIT license.

Update

If you start the ruler from a carrier and measure to it’s immediate destination, the ruler will use the carrier’s ETA rather than calculating it. This is to account for a carrier travelling between two gates:

Previously, that ETA would have been 3 hours since it would have been calculated off of the carrier distance.

Use the stuff @JayKyburz exposed for us rather than searching for it.

Update

Per discussion with @JayKyburz:

  • Ruler is always “gated” meaning it always displays both the eta and the gated eta. Now the eta should be correct and the gated eta should represent the eta if all the stars are gated. The only time that’s not true is some logic to account for the fact that you can’t build gates on carriers.
  • Ruler will display the total distance of the chain instead of the last link’s distance.
  • Will display the maximum hyperspace required for any link in the ruler chain.

Update

Tweaked the drawing code slightly to make the ruler more transparent (to a point) the farther you go. In other words, as you add links, the older ones will be more transparent and the most recent will be the least transparent.

Update

Fixed the global loop variable.

Update

It got put in the game! Best ruler ever!


User Script Extensions
#2

Great stuff Dakota.
As usual Qwerty’s awesomeness shines.
Good to see some collaboration going on.


#3

Super-Duper Awesome … both of you guys deserve a Wizard badge … and ditto what Infin8eye said that this should be incorporated into the game.


#4

do i get a beer to.


#5

Beers for everybody!


#6

Hey guys, it’s really dakota’s work. I feel ashamed of how excellent this script is. I gotta make something awesome too! Oh wait…


#7

Updated main post to include bookmarklet.


#8

I will expose the inbox, ui and main game object np so you don’t search through Crux’s children.

var np = Crux.crux.children.filter(
function (widget) {
    return widget.hasOwnProperty("onStartRuler");})[0];

will become

var np = NeptunesPride.np

Although I think using the filter and hasOwnProperty is a good way to find those objects!


#9

What about the map, @JayKyburz?

Updated to use the MIT license.


#10

map should be NeptunesPride.npui.map

Also, sorry about some of the weird names. I never expected anybody to be reading the code but me.


#11

LOL, I wrote almost the whole thing before realizing i could get uncompressed scripts from /gameu/ :frowning: @Qwerty told me about it.

I think it’s awesome that you let us see the client code! I love the game and I just want it to be as good as it can be :smile:


#12

Updated to use the stuff @JayKyburz exposed and to use carrier ETA where appropriate (see update on original post).


#13

Updated with some changes based on discussion with @JayKyburz, see the main post for details.


#14

Updated the ruler drawing code to adjust transparency to indicate “direction” you’re drawing the ruler. See main post for code/details.


#15

I like how you use Math.max I dont really a lot of other peoples code but I should!


#16

It bothers me a little that it will still do the max comparison after it’s no longer necessary :slight_smile: As a C++ developer, JavaScript is weird and scary to me. This is me asking stupid questions of my old friend the actual web developer, lol:


#17

haha, well I’ve only been programming a little while so still learning. C++ is too scary for me.

you created a global i in drawRuler but i fixed it.

for (i = numStars - 1; i > 0; i--) {

#18

Global i is all-knowing, all-seeing, all-incrementing.

And now fixed.


#19

Looks like the ChainRuler made it in to the game, plus an improvement to display your real ETA, warp ETA, non-warp ETA, and a reset button! Thanks @JayKyburz!


Combat Corner Case - arrive same time from same distance?
#20

Congratulations, @dakotahawkins!