mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 04:03:49 +00:00
Add progress bars to show loading time and healthbars (#1107)
## Description: Add progress bars to show construction time, loading time and health bars in the UI layer The progress bars always show at least one pixel of progression (better visuals)    ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: Vivacious Box --------- Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
export class ProgressBar {
|
||||
private static readonly CLEAR_PADDING = 2;
|
||||
constructor(
|
||||
private colors: string[] = [],
|
||||
private ctx: CanvasRenderingContext2D,
|
||||
private x: number,
|
||||
private y: number,
|
||||
private w: number,
|
||||
private h: number,
|
||||
private progress: number = 0, // Progress from 0 to 1
|
||||
) {
|
||||
this.setProgress(progress);
|
||||
}
|
||||
|
||||
setProgress(progress: number): void {
|
||||
progress = Math.max(0, Math.min(1, progress));
|
||||
this.clear();
|
||||
// Draw the loading bar background
|
||||
this.ctx.fillStyle = "rgba(0, 0, 0, 1)";
|
||||
this.ctx.fillRect(this.x - 1, this.y - 1, this.w, this.h);
|
||||
|
||||
// Draw the loading progress
|
||||
if (this.colors.length === 0) {
|
||||
this.ctx.fillStyle = "#808080"; // default gray
|
||||
} else {
|
||||
const idx = Math.min(
|
||||
this.colors.length - 1,
|
||||
Math.floor(progress * this.colors.length),
|
||||
);
|
||||
this.ctx.fillStyle = this.colors[idx];
|
||||
}
|
||||
this.ctx.fillRect(
|
||||
this.x,
|
||||
this.y,
|
||||
Math.max(1, Math.floor(progress * (this.w - 2))),
|
||||
this.h - 2,
|
||||
);
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.ctx.clearRect(
|
||||
this.x - ProgressBar.CLEAR_PADDING,
|
||||
this.y - ProgressBar.CLEAR_PADDING,
|
||||
this.w + ProgressBar.CLEAR_PADDING,
|
||||
this.h + ProgressBar.CLEAR_PADDING,
|
||||
);
|
||||
}
|
||||
|
||||
getX(): number {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
getY(): number {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
getProgress(): number {
|
||||
return this.progress;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,24 @@
|
||||
import { Colord } from "colord";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { Theme } from "../../../core/configuration/Config";
|
||||
import { UnitType } from "../../../core/game/Game";
|
||||
import { Tick, UnitType } from "../../../core/game/Game";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { GameView, UnitView } from "../../../core/game/GameView";
|
||||
import { UnitSelectionEvent } from "../../InputHandler";
|
||||
import { ProgressBar } from "../ProgressBar";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
const COLOR_PROGRESSION = [
|
||||
"rgb(232, 25, 25)",
|
||||
"rgb(240, 122, 25)",
|
||||
"rgb(202, 231, 15)",
|
||||
"rgb(44, 239, 18)",
|
||||
];
|
||||
const HEALTHBAR_WIDTH = 11; // Width of the health bar
|
||||
const LOADINGBAR_WIDTH = 18; // Width of the loading bar
|
||||
const PROGRESSBAR_HEIGHT = 3; // Height of a bar
|
||||
|
||||
/**
|
||||
* Layer responsible for drawing UI elements that overlay the game
|
||||
* such as selection boxes, health bars, etc.
|
||||
@@ -17,7 +29,11 @@ export class UILayer implements Layer {
|
||||
|
||||
private theme: Theme | null = null;
|
||||
private selectionAnimTime = 0;
|
||||
|
||||
private allProgressBars: Map<
|
||||
number,
|
||||
{ unit: UnitView; startTick: Tick; endTick: Tick; progressBar: ProgressBar }
|
||||
> = new Map();
|
||||
private allHealthBars: Map<number, ProgressBar> = new Map();
|
||||
// Keep track of currently selected unit
|
||||
private selectedUnit: UnitView | null = null;
|
||||
|
||||
@@ -51,6 +67,16 @@ export class UILayer implements Layer {
|
||||
if (this.selectedUnit && this.selectedUnit.type() === UnitType.Warship) {
|
||||
this.drawSelectionBox(this.selectedUnit);
|
||||
}
|
||||
|
||||
this.game
|
||||
.updatesSinceLastTick()
|
||||
?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id))
|
||||
?.forEach((unitView) => {
|
||||
if (unitView === undefined) return;
|
||||
this.onUnitEvent(unitView);
|
||||
});
|
||||
|
||||
this.updateProgressBars();
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -76,6 +102,42 @@ export class UILayer implements Layer {
|
||||
this.canvas.height = this.game.height();
|
||||
}
|
||||
|
||||
onUnitEvent(unit: UnitView) {
|
||||
switch (unit.type()) {
|
||||
case UnitType.Construction: {
|
||||
const playerId = this.game.myPlayer()?.id();
|
||||
if (
|
||||
unit.isActive() &&
|
||||
playerId !== undefined &&
|
||||
unit.owner().id() === playerId
|
||||
) {
|
||||
const constructionType = unit.constructionType();
|
||||
if (constructionType === undefined) {
|
||||
// Skip units without construction type
|
||||
return;
|
||||
}
|
||||
const endTick =
|
||||
this.game.unitInfo(constructionType).constructionDuration || 0;
|
||||
this.drawLoadingBar(unit, endTick);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UnitType.Warship: {
|
||||
this.drawHealthBar(unit);
|
||||
break;
|
||||
}
|
||||
case UnitType.SAMLauncher:
|
||||
case UnitType.MissileSilo:
|
||||
if (unit.isActive() && unit.isCooldown()) {
|
||||
const endTick = unit.ticksLeftInCooldown() || 0;
|
||||
this.drawLoadingBar(unit, endTick);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the unit selection event
|
||||
*/
|
||||
@@ -187,11 +249,71 @@ export class UILayer implements Layer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw health bar for a unit (placeholder for future implementation)
|
||||
* Draw health bar for a unit
|
||||
*/
|
||||
public drawHealthBar(unit: UnitView) {
|
||||
// This is a placeholder for future health bar implementation
|
||||
// It would draw a health bar above units that have health
|
||||
const maxHealth = this.game.unitInfo(unit.type()).maxHealth;
|
||||
if (maxHealth === undefined || this.context === null) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.allHealthBars.has(unit.id()) &&
|
||||
(unit.health() >= maxHealth || unit.health() <= 0 || !unit.isActive())
|
||||
) {
|
||||
// full hp/dead warships dont need a hp bar
|
||||
this.allHealthBars.get(unit.id())?.clear();
|
||||
this.allHealthBars.delete(unit.id());
|
||||
} else if (unit.health() < maxHealth && unit.health() > 0) {
|
||||
this.allHealthBars.get(unit.id())?.clear();
|
||||
const healthBar = new ProgressBar(
|
||||
COLOR_PROGRESSION,
|
||||
this.context,
|
||||
this.game.x(unit.tile()) - 4,
|
||||
this.game.y(unit.tile()) - 6,
|
||||
HEALTHBAR_WIDTH,
|
||||
PROGRESSBAR_HEIGHT,
|
||||
unit.health() / maxHealth,
|
||||
);
|
||||
// keep track of units that have health bars for clearing purposes
|
||||
this.allHealthBars.set(unit.id(), healthBar);
|
||||
}
|
||||
}
|
||||
|
||||
private updateProgressBars() {
|
||||
const currentTick = this.game.ticks();
|
||||
this.allProgressBars.forEach((progressBarInfo, unitId) => {
|
||||
const progress =
|
||||
(currentTick - progressBarInfo.startTick) / progressBarInfo.endTick;
|
||||
if (progress >= 1 || !progressBarInfo.unit.isActive()) {
|
||||
this.allProgressBars.get(unitId)?.progressBar.clear();
|
||||
this.allProgressBars.delete(unitId);
|
||||
return;
|
||||
}
|
||||
progressBarInfo.progressBar.setProgress(progress);
|
||||
});
|
||||
}
|
||||
|
||||
public drawLoadingBar(unit: UnitView, endTick: Tick) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
if (!this.allProgressBars.has(unit.id())) {
|
||||
const progressBar = new ProgressBar(
|
||||
COLOR_PROGRESSION,
|
||||
this.context,
|
||||
this.game.x(unit.tile()) - 8,
|
||||
this.game.y(unit.tile()) - 10,
|
||||
LOADINGBAR_WIDTH,
|
||||
PROGRESSBAR_HEIGHT,
|
||||
0,
|
||||
);
|
||||
this.allProgressBars.set(unit.id(), {
|
||||
unit,
|
||||
startTick: this.game.ticks(),
|
||||
endTick,
|
||||
progressBar,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
paintCell(x: number, y: number, color: Colord, alpha: number) {
|
||||
|
||||
Reference in New Issue
Block a user