Fix for screen scrolling itself all the way around the world all the time


#1

Edit: The fix is live! Thanks @JayKyburz!

The game has a slideCenterPointInMap function that is supposed to move your screen so it’s centered on whatever it’s trying to show you. A lot of the time, it will decide that the best way to do this is to take you around the world in what seems like the wrong direction. There’s a fix you can paste in your javascript console when the game is your active window (Hit f12 or google it for instructions) below.

Here’s a video of before and after (sped up): https://www.youtube.com/watch?v=PHgc7_PRyhc

Here’s the fix:

function findWidgetWithProperty(parent, property) {
    if (parent && parent.children) {
        for (var i = 0, l = parent.children.length; i < l; ++i) {
            var child = parent.children[i];
            if (Object.prototype.hasOwnProperty.call(child, property)) {
                return child;
            } else {
                return findWidgetWithProperty(child, property);
            }
        }
    }
}
var gameWidget = findWidgetWithProperty(Crux.crux, 'slideCenterPointInMap');

gameWidget.slideCenterPointInMap = function (x, y, duration, doAfter) {
    void 0 === duration && (duration = 1e3),
    void 0 === doAfter && (doAfter = null),
    gameWidget.dustHex({
        x: x,
        y: y
    }, 1e3),
    x *= gameWidget.scale,
    y *= gameWidget.scale;

    var newX = Math.round(-x + gameWidget.viewportWidth / 2),
        newY = Math.round(-y + gameWidget.viewportHeight / 2);

    ///////////////////////////////////////////////////////////////////////////
    // See if adding or subtracting the hexMask width and height to our new x/y
    // yields a shorter distance from where we're starting and if so, do it.
    //
    if (Math.abs(newX - gameWidget.sx) > Math.abs(newX + gameWidget.hexMask.w - gameWidget.sx)) {
        newX += gameWidget.hexMask.w;
    } else if (Math.abs(newX - gameWidget.sx) > Math.abs(newX - gameWidget.hexMask.w - gameWidget.sx)) {
        newX -= gameWidget.hexMask.w;
    }
    if (Math.abs(newY - gameWidget.sy) > Math.abs(newY + gameWidget.hexMask.h - gameWidget.sy)) {
        newY += gameWidget.hexMask.h;
    } else if (Math.abs(newY - gameWidget.sy) > Math.abs(newY - gameWidget.hexMask.h - gameWidget.sy)) {
        newY -= gameWidget.hexMask.h;
    }

    Crux.createAnim(gameWidget, "sx", gameWidget.sx, newX, duration),
    Crux.createAnim(gameWidget, "sy", gameWidget.sy, newY, duration, {onComplete: doAfter})
}

#2

I’ll patch it in now. Thanks @dakotahawkins

I did have a todo in there you know :slight_smile:


#3

I’m not sure what uses it but you might want something similar in centerPointInMap. It has a different fixup to make sure the coords are within the hexMask bounds. I originally copied that, and it almost works, but y didn’t like wrapping from a large number to a small number (it would go the “wrong” way still).


#4

Yeah, I should look at it as well, but centerPointInMap snaps so in theory you should never notice if it does something weird.

Really appreciate your good work here! You rock Hawkins!


#5

I was playing with this a bit more, and I found I think it looks better if you don’t move to the exact center but just so that you’re within some distance of it. This cuts out a lot of smaller back-and-forth transitions and makes the replay feel a little smoother to me (because you don’t have to move as far, if you have to move at all). Only problem might be when you’re zoomed in all the way. Try it out and see what you think:

function findWidgetWithProperty(parent, property) {
    if (parent && parent.children) {
        for (var i = 0, l = parent.children.length; i < l; ++i) {
            var child = parent.children[i];
            if (Object.prototype.hasOwnProperty.call(child, property)) {
                return child;
            } else {
                return findWidgetWithProperty(child, property);
            }
        }
    }
}
var gameWidget = findWidgetWithProperty(Crux.crux, 'slideCenterPointInMap');

gameWidget.slideCenterPointInMap = function (x, y, duration, doAfter) {
    void 0 === duration && (duration = 1e3),
    void 0 === doAfter && (doAfter = null),
    gameWidget.dustHex({
        x: x,
        y: y
    }, 1e3),
    x *= gameWidget.scale,
    y *= gameWidget.scale;

    var newX = Math.round(-x + gameWidget.viewportWidth / 2),
        newY = Math.round(-y + gameWidget.viewportHeight / 2);

    ///////////////////////////////////////////////////////////////////////////
    // See if adding or subtracting the hexMask width and height to our new x/y
    // yields a shorter distance from where we're starting and if so, do it.
    //
    if (Math.abs(newX - gameWidget.sx) > Math.abs(newX + gameWidget.hexMask.w - gameWidget.sx)) {
        newX += gameWidget.hexMask.w;
    } else if (Math.abs(newX - gameWidget.sx) > Math.abs(newX - gameWidget.hexMask.w - gameWidget.sx)) {
        newX -= gameWidget.hexMask.w;
    }
    if (Math.abs(newY - gameWidget.sy) > Math.abs(newY + gameWidget.hexMask.h - gameWidget.sy)) {
        newY += gameWidget.hexMask.h;
    } else if (Math.abs(newY - gameWidget.sy) > Math.abs(newY - gameWidget.hexMask.h - gameWidget.sy)) {
        newY -= gameWidget.hexMask.h;
    }

    ///////////////////////////////////////////////////////////////////////////
    // Instead of moving the target x/y to the exact center, just move so that
    // it's within some max distance from the center. This makes the replay
    // seem smoother, and cuts out a lot of screen movement (e.g. two ships
    // fighting).
    //
    // Make the denominator below larger to move closer to the exact center.
    // The default 8 here keeps the action within the middle quarter of the
    // screen.
    //
    var maxDistFromCenterX = gameWidget.viewportWidth / 8;
    var maxDistFromCenterY = gameWidget.viewportHeight / 8;

    var newXdist = newX - gameWidget.sx;
    if (Math.abs(newXdist) < maxDistFromCenterX) {
        newX = gameWidget.sx;
    } else if (newXdist > 0) {
        newX -= maxDistFromCenterX;
    } else {
        newX += maxDistFromCenterX;
    }

    var newYdist = newY - gameWidget.sy;
    if (Math.abs(newYdist) < maxDistFromCenterY) {
        newY = gameWidget.sy;
        if (newX === gameWidget.sx) {
            return;
        }
    } else if (newYdist > 0) {
        newY -= maxDistFromCenterY;
    } else {
        newY += maxDistFromCenterY;
    }

    Crux.createAnim(gameWidget, "sx", gameWidget.sx, newX, duration),
    Crux.createAnim(gameWidget, "sy", gameWidget.sy, newY, duration, {onComplete: doAfter})
}

#6

Just tried it out. Love it, I’ll make it official! Thanks again!


#7

The only other thing I thought might be neat would be to make the animations loop over the pawns in coordinate order (sorted by x and if x is the same then by y), and reversing that order for each next animation.

I was thinking that way, you wouldn’t have it jump very far only to jump back again, it would just kind-of pan/scan back and forth.

I took a stab at it but it didn’t work, may be that the galaxy.pawnList doesn’t like being sorted and reversed :slight_smile:


#8

my solutions was to batch up the pawns in “sectors”. Grid sections of 6x6 hexes. I sort the pawnlist by sectors at the start of the animation playback.

Search for universe.sortPawnList in universe.js

I thought blobs of areas was better than all the way across the map and back.


#9

Ah, that’s what sectors are :slight_smile:

That’s probably fine then. I may try to see if it looks any different reversing that sort for each individual animation (or just starting each one from the “end” of the list that you’re closest to.)

And I was working from the minified javascript :’( Were you wondering why I changed all your variable names?

It doesn’t look like the “gameu” link works for BBB, which is understandable as it’s newer. Also I thought it was “gamen” anyway for some reason. I can never remember that :slight_smile:


#10

The url for the uncompressed scripts for this and blight is just u.
https://bbb.ironhelmet.com/u#/

We are using some ES6 features now and Typescript to compile to ES5 before uglifying.

Thats where those "void 0 === duration && (duration = 1e3)"
Thats an ES6 default value in a function parameter.


#11

Excellent! Thank you!