User Script Extension: "Calculon" Enhanced Combat Calculator


#1

This modifies the combat calculator to take into account.

  • Attacker’s ETA
  • Defender’s industry, manufacturing, and new ships created

Basically given a time until combat (ETA) it will add new ships created by the defender during that time:

The problem with this before was no access to the decimal number of partially constructed ships on a star. Now, it appears that information is exposed.

Let me know what you think!

Bookmarklet (remove leading ‘+’):

+javascript:(function(){var e=NeptunesPride.universe,t=NeptunesPride.npui;t.CombatCalc=function(){var r=10,a=0,c=1,o=1,d=0,n=0,s=NeptunesPride.gameConfig.productionTicks;e.selectedSpaceObject&&(r=e.selectedSpaceObject.st,"star"===e.selectedSpaceObject.kind&&(r=e.selectedSpaceObject.totalDefenses,a=e.selectedSpaceObject.c,d=e.selectedSpaceObject.i),e.selectedSpaceObject.player&&(c=e.selectedSpaceObject.player.tech.weapons.value,o=e.selectedSpaceObject.player.tech.manufacturing.value));var i=t.Screen("combat_calculator"),u=Crux.Widget("rel").roost(i),x=0,p=30,l=20,g=10,T=3,_=.5,f=1.5,C=3;Crux.Text("defender_weapon_tech","pad12 col_accent").grid(x,C,p,T).roost(i),i.defenderWeaponsUI=Crux.TextInput(!0,!0).setText(c).grid(l,C,g,T).roost(i),C+=T,Crux.Text("defender_ships","pad12 col_accent").grid(x,C,p,T).roost(i),i.defenderShipsUI=Crux.TextInput(!0,!0).setText(r).grid(l,C,g,T).roost(i),C+=T,Crux.Text("","pad12 col_accent").grid(x,C,p,T).rawHTML("Defender Industry").roost(i),i.defenderIndustryUI=Crux.TextInput(!0,!0).setText(d).grid(l,C,g,T).roost(i),C+=T,Crux.Text("","pad12 col_accent").grid(x,C,p,T).rawHTML("Defender Manufacturing").roost(i),i.defenderManufacturingUI=Crux.TextInput(!0,!0).setText(o).grid(l,C,g,T).roost(i),C+=T,Crux.Text("defender_ships_bonus","pad12 col_accent").grid(x,C,p,T).roost(i),Crux.Text("","pad4 col_base rad4 txt_center").grid(l,C,g,T).inset(8).rawHTML("+1 Weapons").roost(i),C+=T,Crux.Widget("col_black").grid(x,C,p,_).roost(i),C+=f,Crux.Text("attacker_weapon_tech","pad12 col_accent").grid(x,C,p,T).roost(i),i.attackerWeaponsUI=Crux.TextInput(!0,!0).setText(c).grid(l,C,g,T).roost(i),C+=T,Crux.Text("attacker_ships","pad12 col_accent").grid(x,C,p,T).roost(i),i.attackerShipsUI=Crux.TextInput(!0,!0).setText(r).grid(l,C,g,T).roost(i),C+=T,Crux.Text("eta","pad12 col_accent").grid(x,C,p,T).roost(i),i.attackerEtaUI=Crux.TextInput(!0,!0).setText(n).grid(l,C,g,T).roost(i),C+=T,Crux.Widget("col_black").grid(x,C,p,_).roost(i),C+=f,Crux.Button("fight","pre_calculate_combat").grid(l,C,g,T).roost(i),C+=T+1,i.result=Crux.Text("","pad12 col_accent txt_center").grid(x,C,p,T).rawHTML("").roost(i);var I=T+1;C+=I;var b=I*Crux.gridSize,S=p*Crux.gridSize,h=C*Crux.gridSize,k=48;return i.size(S,h-b),u.size(S,h-k),i.onPreCalcCombat=function(){var e=i.defenderShipsUI.getText(),t=1+i.defenderWeaponsUI.getText(),r=i.defenderIndustryUI.getText(),c=i.defenderManufacturingUI.getText(),o=i.attackerEtaUI.getText();o>0&&--o;var d=r*(c+5)/s,n=d*o;e=Math.floor(e+a+n);var u=i.attackerWeaponsUI.getText(),x=i.attackerShipsUI.getText();if(i.defenderShipsUI.setText(e),i.attackerEtaUI.setText(0),0===e||0===x)return void i.result.update("combat_calc_no_combat");for(var p="";!p;){if(x-=t,0>=x){p="combat_calc_win_defend";break}if(e-=u,0>=e){p="combat_calc_win_attack";break}}var l={};l.as=x,l.ds=e,i.result.updateFormat(p,l),i.h<h&&i.size(i.w,h)},i.on("pre_calculate_combat",i.onPreCalcCombat),i},NeptunesPride.np.trigger("map_refresh")})();

See the top of this post for information on how to create the bookmarklet.


Un-minified code:

// MIT License
//
// Copyright (c) 2016 Jay Kyburz, Dakota Hawkins
//
// 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.

(function () {
    var universe = NeptunesPride.universe;
    var npui = NeptunesPride.npui;

    npui.CombatCalc = function () {
        var defenderShips = 10;
        var defenderPartialShips = 0;
        var defenderWeapons = 1;
        var defenderManufacturing = 1;
        var defenderIndustry = 0;
        var attackerEta = 0;
        var productionTicks = NeptunesPride.gameConfig.productionTicks;

        if (universe.selectedSpaceObject) {
            defenderShips = universe.selectedSpaceObject.st;
            if (universe.selectedSpaceObject.kind === "star") {
                defenderShips = universe.selectedSpaceObject.totalDefenses;
                defenderPartialShips = universe.selectedSpaceObject.c;
                defenderIndustry = universe.selectedSpaceObject.i;
            }

            if (universe.selectedSpaceObject.player) {
                defenderWeapons = universe.selectedSpaceObject.player.tech.weapons.value;
                defenderManufacturing = universe.selectedSpaceObject.player.tech.manufacturing.value;
            }
        }

        var combatCalcWindow = npui.Screen("combat_calculator");
        var relativeUIContainer = Crux.Widget("rel").roost(combatCalcWindow);

        var col1_x = 0;
        var col1_width = 30;
        var col2_x = 20;
        var col2_width = 10;
        var row_height = 3;
        var pad_height = .5;
        var post_pad_y_offset = 1.5;

        var totalUIHeight = 3;
        Crux.Text("defender_weapon_tech", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
        combatCalcWindow.defenderWeaponsUI = Crux.TextInput(true, true).setText(defenderWeapons).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);

        totalUIHeight += row_height;
        Crux.Text("defender_ships", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
        combatCalcWindow.defenderShipsUI = Crux.TextInput(true, true).setText(defenderShips).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);

        totalUIHeight += row_height;
        Crux.Text("", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).rawHTML("Defender Industry").roost(combatCalcWindow);
        combatCalcWindow.defenderIndustryUI = Crux.TextInput(true, true).setText(defenderIndustry).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);

        totalUIHeight += row_height;
        Crux.Text("", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).rawHTML("Defender Manufacturing").roost(combatCalcWindow);
        combatCalcWindow.defenderManufacturingUI = Crux.TextInput(true, true).setText(defenderManufacturing).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);

        totalUIHeight += row_height;
        Crux.Text("defender_ships_bonus", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
        Crux.Text("", "pad4 col_base rad4 txt_center").grid(col2_x, totalUIHeight, col2_width, row_height).inset(8).rawHTML("+1 Weapons").roost(combatCalcWindow);

        totalUIHeight += row_height;
        Crux.Widget("col_black").grid(col1_x, totalUIHeight, col1_width, pad_height).roost(combatCalcWindow);

        totalUIHeight += post_pad_y_offset;
        Crux.Text("attacker_weapon_tech", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
        combatCalcWindow.attackerWeaponsUI = Crux.TextInput(true, true).setText(defenderWeapons).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);

        totalUIHeight += row_height;
        Crux.Text("attacker_ships", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
        combatCalcWindow.attackerShipsUI = Crux.TextInput(true, true).setText(defenderShips).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);

        totalUIHeight += row_height;
        Crux.Text("eta", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
        combatCalcWindow.attackerEtaUI = Crux.TextInput(true, true).setText(attackerEta).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);

        totalUIHeight += row_height;
        Crux.Widget("col_black").grid(col1_x, totalUIHeight, col1_width, pad_height).roost(combatCalcWindow);

        totalUIHeight += post_pad_y_offset;
        Crux.Button("fight", "pre_calculate_combat").grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);
        totalUIHeight += row_height + 1;

        combatCalcWindow.result = Crux.Text("", "pad12 col_accent txt_center").grid(col1_x, totalUIHeight, col1_width, row_height).rawHTML("").roost(combatCalcWindow);
        var resultsUIHeight = row_height + 1;
        totalUIHeight += resultsUIHeight;

        var resultsUIPixelHeight = resultsUIHeight * Crux.gridSize;
        var totalUIPixelWidth = col1_width * Crux.gridSize;
        var totalUIPixelHeight = totalUIHeight * Crux.gridSize;
        var windowTitleHeight = 48;

        combatCalcWindow.size(totalUIPixelWidth, totalUIPixelHeight - resultsUIPixelHeight);
        relativeUIContainer.size(totalUIPixelWidth, totalUIPixelHeight - windowTitleHeight);

        combatCalcWindow.onPreCalcCombat = function () {
            var updatedDefenderShips = combatCalcWindow.defenderShipsUI.getText();
            var updatedDefenderWeapons = 1 + combatCalcWindow.defenderWeaponsUI.getText();
            var updatedDefenderIndustry = combatCalcWindow.defenderIndustryUI.getText();
            var updatedDefenderManufacturing = combatCalcWindow.defenderManufacturingUI.getText();

            var updatedAttackerETA = combatCalcWindow.attackerEtaUI.getText();
            if (updatedAttackerETA > 0) {
                // Industry builds ships after combat, so subtract 1 from the attacker's ETA.
                --updatedAttackerETA;
            }

            var defenderShipsPerTick = (updatedDefenderIndustry * (updatedDefenderManufacturing + 5)) / productionTicks;
            var defenderNewShips = defenderShipsPerTick * updatedAttackerETA;
            updatedDefenderShips = Math.floor(
                updatedDefenderShips +
                defenderPartialShips +
                defenderNewShips
            );

            if (false) {
                console.log("updatedAttackerETA: " + updatedAttackerETA);
                console.log("defenderShipsPerTick: " + defenderShipsPerTick);
                console.log("defenderPartialShips: " + defenderPartialShips);
                console.log("defenderNewShips: " + defenderNewShips);
                console.log("updatedDefenderShips: " + updatedDefenderShips);
            }

            var updatedAttackerWeapons = combatCalcWindow.attackerWeaponsUI.getText();
            var updatedAttackerShips = combatCalcWindow.attackerShipsUI.getText();

            combatCalcWindow.defenderShipsUI.setText(updatedDefenderShips);
            combatCalcWindow.attackerEtaUI.setText(0);
            if (updatedDefenderShips === 0 || updatedAttackerShips === 0) {
                combatCalcWindow.result.update("combat_calc_no_combat");
                return;
            }

            var winner = "";
            while (!winner) {
                updatedAttackerShips -= updatedDefenderWeapons;
                if (updatedAttackerShips <= 0) {
                    winner = "combat_calc_win_defend";
                    break;
                }
                updatedDefenderShips -= updatedAttackerWeapons;
                if (updatedDefenderShips <= 0) {
                    winner = "combat_calc_win_attack";
                    break;
                }
            }

            var td = {};
            td.as = updatedAttackerShips;
            td.ds = updatedDefenderShips;
            combatCalcWindow.result.updateFormat(winner, td);

            // Show the results
            if (combatCalcWindow.h < totalUIPixelHeight) {
                combatCalcWindow.size(combatCalcWindow.w, totalUIPixelHeight);
            }
        };

        combatCalcWindow.on("pre_calculate_combat", combatCalcWindow.onPreCalcCombat);
        return combatCalcWindow;
    };

    NeptunesPride.np.trigger("map_refresh");
})();

Calculator Improvements
#2

awesome. I’ve usually been doing those calculations off to the side and punching in the true numbers for when I land. this is great. thanks.


#3

Let me know if you have any issues with it!

I’m aware of the following things:

  • The reported ships remaining will be less than what you actually see on the star after real combat
  • This is because combat happens before the star builds ships. So it should report the same thing you’d see in the event log, but right after combat the star builds more ships, so there will be one tick’s additional ships actually on the star.
  • We could figure this out and just add it to the result, but in the case the attacker wins we’d need a field for the attacker’s manufacturing I think. This is assuming when an attacker wins, they get immediate production – can anybody confirm whether that’s the case?
  • For now, just be careful if you paste the result into “defender ships” and run another calc.
  • It doesn’t do anything “special” with the partially constructed ships after it runs, it leaves them alone
  • This would matter if you ran a calc, changed the ETA from 0 and ran another one.
  • If the defender keeps winning, we could keep figuring out new values for the remaining ships, and if the attacker wins we could zero it out. Edit: it appears even if the attacker wins the star keeps its partially constructed ships.
  • We could just zero it out regardless, after the first calc.
  • Since we don’t display the number, and the UI doesn’t like fields with decimal values you can change, I’m a little uncomfortable “guessing” the right thing to do and doing something in the background.
  • For now, be careful if you run a calc, add more ETA and then run another calc. It will add the original partially constructed ships to the new ships produced during the second ETA.

It would be great to handle those things without adding much to the UI. The window needs to be small and simple since it has to work on phones and etc. Anybody have any suggestions?


#4

This is great. As Golden_Ace says, I’ve been doing this by hand; kcalc on-screen anytime I’m logged in to NP.

If you want a suggestion (and this could apply to the normal calculator too), how about a button that sets the final state as a new initial state? So you can plan serial combats. Whoever has ships remaining becomes the defender, with their ship count (plus build) and techs pre-set, and the attacker gets the loser’s weapons tech.


#5

Yeah I was trying to think of a way to show that to the user without getting too complicated. Since that affects partial ships and etc.

Maybe just have a time field that shows it’s now + N ticks, along with a reset button.

That, and have it do a better job of filling out the attacker’s info (could look at closest enemy carrier on the way or closest enemy star) and displaying who “owns” the star based on that.


#6

I’ve never used one of these before, but I made the bookmarklet as per your instructions in the other post and when I use it the calculator will just no longer show up. Any ideas why?


#7

I think an update broke it, and I haven’t looked at it since. I did get a heads up that there were going to be code changes that would probably break some of these scripts.


#8

Quick fix to this script. All I did was replace Crux.TextInput(true,true) with Crux.TextInput(“single”, “”, “number”)

// MIT License
//
// Copyright © 2016 Jay Kyburz, Dakota Hawkins
//
// 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.

(function () {
var universe = NeptunesPride.universe;
var npui = NeptunesPride.npui;

npui.CombatCalc = function () {
    var defenderShips = 10;
    var defenderPartialShips = 0;
    var defenderWeapons = 1;
    var defenderManufacturing = 1;
    var defenderIndustry = 0;
    var attackerEta = 0;
    var productionTicks = NeptunesPride.gameConfig.productionTicks;
    if (universe.selectedSpaceObject) {
        defenderShips = universe.selectedSpaceObject.st;
        if (universe.selectedSpaceObject.kind === "star") {
            defenderShips = universe.selectedSpaceObject.totalDefenses;
            defenderPartialShips = universe.selectedSpaceObject.c;
            defenderIndustry = universe.selectedSpaceObject.i;
        }
        if (universe.selectedSpaceObject.player) {
            defenderWeapons = universe.selectedSpaceObject.player.tech.weapons.value;
            defenderManufacturing = universe.selectedSpaceObject.player.tech.manufacturing.value;
        }
    }
    var combatCalcWindow = npui.Screen("combat_calculator");
    var relativeUIContainer = Crux.Widget("rel").roost(combatCalcWindow);
    var col1_x = 0;
    var col1_width = 30;
    var col2_x = 20;
    var col2_width = 10;
    var row_height = 3;
    var pad_height = .5;
    var post_pad_y_offset = 1.5;
    var totalUIHeight = 3;
    Crux.Text("defender_weapon_tech", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
    combatCalcWindow.defenderWeaponsUI = Crux.TextInput("single", "", "number").setText(defenderWeapons).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);
    totalUIHeight += row_height;
    Crux.Text("defender_ships", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
    combatCalcWindow.defenderShipsUI = Crux.TextInput("single", "", "number").setText(defenderShips).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);
    totalUIHeight += row_height;
    Crux.Text("", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).rawHTML("Defender Industry").roost(combatCalcWindow);
    combatCalcWindow.defenderIndustryUI = Crux.TextInput("single", "", "number").setText(defenderIndustry).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);
    totalUIHeight += row_height;
    Crux.Text("", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).rawHTML("Defender Manufacturing").roost(combatCalcWindow);
    combatCalcWindow.defenderManufacturingUI = Crux.TextInput("single", "", "number").setText(defenderManufacturing).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);
    totalUIHeight += row_height;
    Crux.Text("defender_ships_bonus", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
    Crux.Text("", "pad4 col_base rad4 txt_center").grid(col2_x, totalUIHeight, col2_width, row_height).inset(8).rawHTML("+1 Weapons").roost(combatCalcWindow);
    totalUIHeight += row_height;
    Crux.Widget("col_black").grid(col1_x, totalUIHeight, col1_width, pad_height).roost(combatCalcWindow);
    totalUIHeight += post_pad_y_offset;
    Crux.Text("attacker_weapon_tech", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
    combatCalcWindow.attackerWeaponsUI = Crux.TextInput("single", "", "number").setText(defenderWeapons).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);
    totalUIHeight += row_height;
    Crux.Text("attacker_ships", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
    combatCalcWindow.attackerShipsUI = Crux.TextInput("single", "", "number").setText(defenderShips).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);
    totalUIHeight += row_height;
    Crux.Text("eta", "pad12 col_accent").grid(col1_x, totalUIHeight, col1_width, row_height).roost(combatCalcWindow);
    combatCalcWindow.attackerEtaUI = Crux.TextInput("single", "", "number").setText(attackerEta).grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);
    totalUIHeight += row_height;
    Crux.Widget("col_black").grid(col1_x, totalUIHeight, col1_width, pad_height).roost(combatCalcWindow);
    totalUIHeight += post_pad_y_offset;
    Crux.Button("fight", "pre_calculate_combat").grid(col2_x, totalUIHeight, col2_width, row_height).roost(combatCalcWindow);
    totalUIHeight += row_height + 1;
    combatCalcWindow.result = Crux.Text("", "pad12 col_accent txt_center").grid(col1_x, totalUIHeight, col1_width, row_height).rawHTML("").roost(combatCalcWindow);
    var resultsUIHeight = row_height + 1;
    totalUIHeight += resultsUIHeight;
    var resultsUIPixelHeight = resultsUIHeight * Crux.gridSize;
    var totalUIPixelWidth = col1_width * Crux.gridSize;
    var totalUIPixelHeight = totalUIHeight * Crux.gridSize;
    var windowTitleHeight = 48;
    combatCalcWindow.size(totalUIPixelWidth, totalUIPixelHeight - resultsUIPixelHeight);
    relativeUIContainer.size(totalUIPixelWidth, totalUIPixelHeight - windowTitleHeight);
    combatCalcWindow.onPreCalcCombat = function () {
        var updatedDefenderShips = combatCalcWindow.defenderShipsUI.getText();
        var updatedDefenderWeapons = 1 + combatCalcWindow.defenderWeaponsUI.getText();
        var updatedDefenderIndustry = combatCalcWindow.defenderIndustryUI.getText();
        var updatedDefenderManufacturing = combatCalcWindow.defenderManufacturingUI.getText();
        var updatedAttackerETA = combatCalcWindow.attackerEtaUI.getText();
        if (updatedAttackerETA > 0) {
            // Industry builds ships after combat, so subtract 1 from the attacker's ETA.
            --updatedAttackerETA;
        }
        var defenderShipsPerTick = (updatedDefenderIndustry * (updatedDefenderManufacturing + 5)) / productionTicks;
        var defenderNewShips = defenderShipsPerTick * updatedAttackerETA;
        updatedDefenderShips = Math.floor(
            updatedDefenderShips +
            defenderPartialShips +
            defenderNewShips
        );
        if (false) {
            console.log("updatedAttackerETA: " + updatedAttackerETA);
            console.log("defenderShipsPerTick: " + defenderShipsPerTick);
            console.log("defenderPartialShips: " + defenderPartialShips);
            console.log("defenderNewShips: " + defenderNewShips);
            console.log("updatedDefenderShips: " + updatedDefenderShips);
        }
        var updatedAttackerWeapons = combatCalcWindow.attackerWeaponsUI.getText();
        var updatedAttackerShips = combatCalcWindow.attackerShipsUI.getText();
        combatCalcWindow.defenderShipsUI.setText(updatedDefenderShips);
        combatCalcWindow.attackerEtaUI.setText(0);
        if (updatedDefenderShips === 0 || updatedAttackerShips === 0) {
            combatCalcWindow.result.update("combat_calc_no_combat");
            return;
        }
        var winner = "";
        while (!winner) {
            updatedAttackerShips -= updatedDefenderWeapons;
            if (updatedAttackerShips <= 0) {
                winner = "combat_calc_win_defend";
                break;
            }
            updatedDefenderShips -= updatedAttackerWeapons;
            if (updatedDefenderShips <= 0) {
                winner = "combat_calc_win_attack";
                break;
            }
        }
        var td = {};
        td.as = updatedAttackerShips;
        td.ds = updatedDefenderShips;
        combatCalcWindow.result.updateFormat(winner, td);
        // Show the results
        if (combatCalcWindow.h < totalUIPixelHeight) {
            combatCalcWindow.size(combatCalcWindow.w, totalUIPixelHeight);
        }
    };
    combatCalcWindow.on("pre_calculate_combat", combatCalcWindow.onPreCalcCombat);
    return combatCalcWindow;
};
NeptunesPride.np.trigger("map_refresh");

})();