127 lines
4.7 KiB
JavaScript
127 lines
4.7 KiB
JavaScript
// The city training modal: choose a unit to build and manage the city's queue.
|
||
|
||
import { groupDigits, hours } from "../../../shared/text_format.js";
|
||
import { ECONOMY } from "../../../shared/data.js";
|
||
|
||
export class TrainModal {
|
||
constructor() {
|
||
this.$modal = $("#modal-train");
|
||
this.$title = $("#train-title");
|
||
this.$list = $("#train-list");
|
||
this.$status = $("#train-status");
|
||
this.$queue = $("#train-queue");
|
||
this.$label = $("#train-label");
|
||
this.$progress = $("#train-progress");
|
||
this.cityId = 0;
|
||
this.gdp = 0;
|
||
this.protoNames = new Map();
|
||
this.onTrain = () => {};
|
||
this.onCancel = () => {};
|
||
this.$modal.find(".modal-close").on("click", () => this.close());
|
||
}
|
||
|
||
isShowing(cityId) {
|
||
return !this.$modal.hasClass("hidden") && this.cityId === cityId;
|
||
}
|
||
|
||
show(city, gdp, trainableUnits, queue = []) {
|
||
this.cityId = city.id;
|
||
this.gdp = gdp;
|
||
this.protoNames = new Map();
|
||
this.$title.text(`${city.name} — Train`);
|
||
this.$list.empty();
|
||
this.$status.addClass("hidden");
|
||
for (let index = 0; index < trainableUnits.length; index++) {
|
||
const proto = trainableUnits[index];
|
||
if (!proto.cost && proto.cost !== 0) continue;
|
||
const button = $("<button class='aero'></button>");
|
||
const duration = ECONOMY.trainingHours(gdp, proto.cost);
|
||
button.text(`${proto.name} — Cost ${groupDigits(proto.cost)} — ${hours(duration)}`);
|
||
const serverIndex = proto.serverIndex !== undefined ? proto.serverIndex : index;
|
||
this.protoNames.set(serverIndex, proto.name);
|
||
button.on("click", () => this.onTrain(city.id, serverIndex));
|
||
this.$list.append(button);
|
||
}
|
||
this.renderQueue(queue);
|
||
this.$modal.removeClass("hidden");
|
||
}
|
||
|
||
// Draws the city's training queue: one row per run of contiguous identical
|
||
// units, with the cumulated hours until each run is ready and a button to
|
||
// cancel its last unit. Cancelling refunds nothing.
|
||
renderQueue(queue) {
|
||
if (!this.$queue || !this.$queue.length) return;
|
||
this.$queue.empty();
|
||
if (!queue || queue.length === 0) {
|
||
this.$queue.append(
|
||
$("<div class='desc queue-empty'></div>").text("No units queued.")
|
||
);
|
||
return;
|
||
}
|
||
let cumulative = 0;
|
||
groupQueue(queue).forEach((group, groupIndex) => {
|
||
let firstReady = 0;
|
||
for (const index of group.indices) {
|
||
const entry = queue[index];
|
||
const elapsed = index === 0 ? (entry.elapsedHours || 0) : 0;
|
||
cumulative += Math.max(entry.totalHours - elapsed, 0);
|
||
if (index === group.indices[0]) firstReady = cumulative;
|
||
}
|
||
const count = group.indices.length;
|
||
const name = this.protoNames.get(group.protoIndex) || "Unit";
|
||
const label = count > 1 ? `${name} ×${count}` : name;
|
||
const $row = $("<div class='row-card queue-row'></div>");
|
||
const $info = $("<div class='info'></div>");
|
||
$info.append($("<div class='name'></div>").text(`${groupIndex + 1}. ${label}`));
|
||
$info.append(
|
||
$("<div class='desc'></div>").text(
|
||
count > 1
|
||
? `Next in ${hours(firstReady)} · all ready in ${hours(cumulative)}`
|
||
: `Ready in ${hours(cumulative)}`
|
||
)
|
||
);
|
||
const $actions = $("<div class='actions'></div>");
|
||
const $cancel = $("<button class='aero'></button>").text(count > 1 ? "Cancel last" : "Cancel");
|
||
$cancel.on("click", () => this.onCancel(this.cityId, group.indices[group.indices.length - 1]));
|
||
$actions.append($cancel);
|
||
$row.append($info, $actions);
|
||
this.$queue.append($row);
|
||
});
|
||
}
|
||
|
||
beginTraining(name, totalHours, elapsedHours = 0) {
|
||
this.$label.text(`Training ${name}…`);
|
||
this.$progress.css("width", `${Math.min(100, (elapsedHours / Math.max(totalHours, 0.0001)) * 100)}%`);
|
||
this.$status.removeClass("hidden");
|
||
}
|
||
|
||
setTrainingProgress(name, elapsedHours, totalHours) {
|
||
const remaining = Math.max(0, Math.ceil(totalHours - elapsedHours));
|
||
this.$label.text(`Training ${name}… (${remaining} h left)`);
|
||
this.$progress.css("width", `${Math.min(100, (elapsedHours / Math.max(totalHours, 0.0001)) * 100)}%`);
|
||
}
|
||
|
||
endTraining() {
|
||
this.$label.text("Training complete");
|
||
this.$progress.css("width", "100%");
|
||
}
|
||
|
||
close() {
|
||
this.$modal.addClass("hidden");
|
||
this.cityId = 0;
|
||
}
|
||
}
|
||
|
||
// Runs of contiguous queue entries with the same unit type, as lists of queue
|
||
// indices. Non-adjacent entries of the same type stay separate so a row only
|
||
// ever represents the units sitting together.
|
||
function groupQueue(queue) {
|
||
const groups = [];
|
||
queue.forEach((entry, index) => {
|
||
const last = groups[groups.length - 1];
|
||
if (last && last.protoIndex === entry.protoIndex) last.indices.push(index);
|
||
else groups.push({ protoIndex: entry.protoIndex, indices: [index] });
|
||
});
|
||
return groups;
|
||
}
|