Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added proprietary/images/doomsdayicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 6 additions & 5 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@
"factory": "Factory",
"airfield": "Airfield",
"air_field": "Airfield",
"fighter_jet": "Fighter Jet"
"fighter_jet": "Fighter Jet",
"doomsday_device": "Doomsday Device"
},
"user_setting": {
"title": "User Settings",
Expand Down Expand Up @@ -487,7 +488,8 @@
"hospital": "Lowers troop casualties from combat",
"academy": "Increases troop speed and enemy losses in combat",
"airfield": "Send bombers, fighterjets and paratroopers",
"fighter_jet": "Destroys bombers and fighters jets"
"fighter_jet": "Destroys bombers and fighters jets",
"doomsday_device": "A mysterious device with unknown effects"
},
"not_enough_money": "Not enough money"
},
Expand Down Expand Up @@ -554,9 +556,8 @@
"reject_alliance": "Reject",
"alliance_renewed": "Your alliance with {name} has been renewed",
"ignore": "Ignore",
"paratrooper_sent": "Paratrooper"
},
"game_messages": {
"paratrooper_sent": "Paratrooper",
"doomsday_triggered": "{player}'s Doomsday Device has been Triggered!",
"max_paratrooper_units_reached": "Maximum number of paratrooper planes reached.",
"incoming_paratrooper_attack": "Incoming Paratrooper Attack from {attackerName}"
},
Expand Down
4 changes: 1 addition & 3 deletions resources/lang/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,7 @@
"reject_alliance": "Reddet",
"alliance_renewed": "{name} ile ittifakınız yenilendi",
"ignore": "Yoksay",
"paratrooper_sent": "Paraşütçü"
},
"game_messages": {
"paratrooper_sent": "Paraşütçü",
"max_paratrooper_units_reached": "Maksimum paraşütçü uçağı sayısına ulaşıldı.",
"incoming_paratrooper_attack": "{attackerName} tarafından Gelen Paraşütçü Saldırısı"
},
Expand Down
1 change: 1 addition & 0 deletions src/client/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export function getMessageTypeClasses(type: MessageType): string {
case MessageType.WARN:
case MessageType.PEACE_TIMER_BLOCKED:
case MessageType.TRADE_SHIP_TURNED_AROUND:
case MessageType.DOOMSDAY_DEVICE_ACTIVATED:
return severityColors["warn"];
case MessageType.WAR_DECLARED:
return severityColors["warn"]; // war start: highlight prominently
Expand Down
87 changes: 87 additions & 0 deletions src/client/graphics/fx/NukeFx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,90 @@ export function nukeFxFactory(
}
return nukeFx;
}

/**
* Slower, larger, more lingering FX for Doomsday device trigger.
* - Larger shockwave (longer duration)
* - Higher density lingering smoke/fire with longer fade
*/
export function doomsdayFxFactory(
animatedSpriteLoader: AnimatedSpriteLoader,
x: number,
y: number,
radius: number,
game: GameView,
scale: number = 1.2,
): Fx[] {
const fx: Fx[] = [];
// Central sustained explosion sprite (scaled up)
fx.push(
new SpriteFx(
animatedSpriteLoader,
x,
y,
FxType.Nuke,
undefined,
undefined,
undefined,
scale,
),
);
// Slower shockwave (duration 6000ms, larger reach)
fx.push(new ShockwaveFx(x, y, 6000, radius * 2));
// Lingering debris plan (higher density, longer fade via FadeFx wrapper)
const debrisPlan: Array<{
type: FxType;
radiusFactor: number;
density: number;
fadeIn: number;
fadeOut: number;
}> = [
{
type: FxType.MiniFire,
radiusFactor: 1.3,
density: 1 / 18,
fadeIn: 0.2,
fadeOut: 1.5,
},
{
type: FxType.MiniSmoke,
radiusFactor: 1.4,
density: 1 / 20,
fadeIn: 0.2,
fadeOut: 1.8,
},
{
type: FxType.MiniBigSmoke,
radiusFactor: 1.2,
density: 1 / 50,
fadeIn: 0.2,
fadeOut: 2.0,
},
{
type: FxType.MiniSmokeAndFire,
radiusFactor: 1.1,
density: 1 / 55,
fadeIn: 0.2,
fadeOut: 2.2,
},
];
for (const { type, radiusFactor, density, fadeIn, fadeOut } of debrisPlan) {
const count = Math.max(0, Math.floor(radius * density));
for (let i = 0; i < count; i++) {
const angle = Math.random() * 2 * Math.PI;
const distance = Math.random() * (radius * radiusFactor);
const sx = Math.floor(x + Math.cos(angle) * distance * 0.5);
const sy = Math.floor(y + Math.sin(angle) * distance * 0.5);
if (game.isValidCoord(sx, sy) && game.isLand(game.ref(sx, sy))) {
fx.push(
new FadeFx(
new SpriteFx(animatedSpriteLoader, sx, sy, type),
fadeIn,
fadeOut,
) as Fx,
);
}
}
}
return fx;
}
8 changes: 8 additions & 0 deletions src/client/graphics/layers/BuildMenu.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import doomsdayDeviceIcon from "../../../../proprietary/images/doomsdayicon.png";
import researchLabIcon from "../../../../proprietary/images/researchlab.png";
import airfieldIcon from "../../../../resources/images/AirfieldIcon.svg";
import warshipIcon from "../../../../resources/images/BattleshipIconWhite.svg";
Expand Down Expand Up @@ -154,6 +155,13 @@ const buildTable: BuildItemDisplay[][] = [
key: "unit_type.defense_post",
countable: true,
},
{
unitType: UnitType.DoomsdayDevice,
icon: doomsdayDeviceIcon,
description: "build_menu.desc.doomsday_device",
key: "unit_type.doomsday_device",
countable: true,
},
],
];

Expand Down
4 changes: 4 additions & 0 deletions src/client/graphics/layers/ControlPanel2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"SAM Launcher": "/images/SamLauncherIconWhite.svg",
"Air Field": "/images/AirfieldIcon.svg",
"Defense Post": "/images/ShieldIconWhite.svg",
"Doomsday Device": "/images/Doomsdayicon.png",
};

// Per-unit icon scale used for small inline icons in this panel
Expand All @@ -159,6 +160,7 @@
[UnitType.SAMLauncher]: 1,
[UnitType.Airfield]: 1,
[UnitType.DefensePost]: 1,
[UnitType.DoomsdayDevice]: 1,
};

private iconPixelSize(t: UnitType | null, base = 16): number {
Expand Down Expand Up @@ -198,6 +200,7 @@
UnitType.Academy,
UnitType.Factory,
UnitType.City,
UnitType.DoomsdayDevice,
];

private readonly investmentRequestHandler = (event: Event) => {
Expand Down Expand Up @@ -957,7 +960,7 @@

private _openUnitUpgradeSettings() {
const modal =
(document.querySelector("unit-upgrade-settings-modal") as any) ||

Check warning on line 963 in src/client/graphics/layers/ControlPanel2.ts

View workflow job for this annotation

GitHub Actions / 🔍 ESLint

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator. (@typescript-eslint/prefer-nullish-coalescing)
this._ensureUnitUpgradeSettingsModal();
if (!modal) {
console.warn(
Expand Down Expand Up @@ -1000,7 +1003,7 @@

private _openStatistics() {
const modal =
(document.querySelector("statistics-modal") as any) ||

Check warning on line 1006 in src/client/graphics/layers/ControlPanel2.ts

View workflow job for this annotation

GitHub Actions / 🔍 ESLint

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator. (@typescript-eslint/prefer-nullish-coalescing)
this._ensureStatisticsModal();
if (!modal) {
console.warn("StatisticsModal element not found or failed to create");
Expand Down Expand Up @@ -1364,6 +1367,7 @@
UnitType.Academy,
UnitType.ResearchLab,
UnitType.Factory,
UnitType.DoomsdayDevice,
].map((s) => {
return html`
<label
Expand Down
14 changes: 1 addition & 13 deletions src/client/graphics/layers/EventsDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -729,19 +729,7 @@ export class EventsDisplay extends LitElement implements Layer {

const unitView = this.game.unit(event.unitID);

let translatedDescription = event.message;

if (event.messageType === MessageType.PARATROOPER_INBOUND) {
const match = event.message.match(/from (.*)/);
const attackerName = match ? match[1] : "Unknown Attacker";

translatedDescription = translateText(
"game_messages.incoming_paratrooper_attack",
{
attackerName: attackerName,
},
);
}
const translatedDescription = event.message;

this.addEvent({
description: translatedDescription,
Expand Down
18 changes: 17 additions & 1 deletion src/client/graphics/layers/FxLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { Fx, FxType } from "../fx/Fx";
import { nukeFxFactory, ShockwaveFx } from "../fx/NukeFx";
import { doomsdayFxFactory, nukeFxFactory, ShockwaveFx } from "../fx/NukeFx";
import { SpriteFx } from "../fx/SpriteFx";
import { UnitExplosionFx } from "../fx/UnitExplosionFx";
import { Layer } from "./Layer";
Expand Down Expand Up @@ -57,6 +57,22 @@ export class FxLayer implements Layer {
this.allFx.push(fx);
}
});

this.game
.updatesSinceLastTick()
?.[GameUpdateType.DoomsdayExplosion]?.forEach((update) => {
const { x, y, radius } = update;
const doomFx = doomsdayFxFactory(
this.animatedSpriteLoader,
x,
y,
radius,
this.game,
);
for (const fx of doomFx) {
this.allFx.push(fx);
}
});
}

onUnitEvent(unit: UnitView) {
Expand Down
5 changes: 5 additions & 0 deletions src/client/graphics/layers/StructureLayer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { colord } from "colord";
import * as PIXI from "pixi.js";
import airfieldIcon from "../../../../proprietary/images/airfieldicon2.png";
import doomsdayDeviceIcon from "../../../../proprietary/images/doomsdayicon.png";
import researchLabIcon from "../../../../proprietary/images/researchlab.png";
import anchorIcon from "../../../../resources/images/AnchorIcon.png";
import academyIcon from "../../../../resources/images/buildings/academy_icon.png";
Expand Down Expand Up @@ -65,6 +66,7 @@ const STRUCTURE_BG_SHAPES: Partial<Record<UnitType, BgShape>> = {
[UnitType.ResearchLab]: "square",
[UnitType.Academy]: "square",
[UnitType.Factory]: "circle",
[UnitType.DoomsdayDevice]: "square",
};

export class StructureLayer implements Layer {
Expand Down Expand Up @@ -114,6 +116,7 @@ export class StructureLayer implements Layer {
[UnitType.Port, { iconPath: anchorIcon, image: null }],
[UnitType.MissileSilo, { iconPath: missileSiloIcon, image: null }],
[UnitType.SAMLauncher, { iconPath: SAMMissileIcon, image: null }],
[UnitType.DoomsdayDevice, { iconPath: doomsdayDeviceIcon, image: null }],
]);

// Per-structure icon scale factor (1 = default size)
Expand All @@ -128,6 +131,7 @@ export class StructureLayer implements Layer {
[UnitType.Port]: 1,
[UnitType.MissileSilo]: 1,
[UnitType.SAMLauncher]: 1,
[UnitType.DoomsdayDevice]: 1.4,
};

constructor(
Expand Down Expand Up @@ -727,6 +731,7 @@ export class StructureLayer implements Layer {
UnitType.Academy,
UnitType.ResearchLab,
UnitType.Factory,
UnitType.DoomsdayDevice,
]);
if (centerScaledTypes.has(structureType as UnitType)) {
const padded = 4;
Expand Down
12 changes: 11 additions & 1 deletion src/core/configuration/DefaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ export class DefaultConfig implements Config {
),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 20,
constructionDuration: this.instantBuild() ? 0 : 10 * 10,
maxHealth: 1000,
};
case UnitType.CargoPlane:
Expand Down Expand Up @@ -851,6 +851,16 @@ export class DefaultConfig implements Config {
cost: () => 0n,
territoryBound: false,
};
case UnitType.DoomsdayDevice:
return {
cost: (p: Player) =>
p.type() === PlayerType.Human && this.infiniteGold()
? 0n
: 50_000_000n,
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 10 * 10,
maxHealth: 3000,
};
default:
assertNever(type);
}
Expand Down
6 changes: 6 additions & 0 deletions src/core/execution/AirfieldExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ export class AirfieldExecution implements Execution {
UnitType.City,
UnitType.Academy,
UnitType.Hospital,
UnitType.DoomsdayDevice,
UnitType.Factory,
UnitType.ResearchLab,
])
.filter(({ unit }) => {
const o = mg.owner(unit.tile());
Expand All @@ -174,6 +177,9 @@ export class AirfieldExecution implements Execution {
UnitType.City,
UnitType.Academy,
UnitType.Hospital,
UnitType.DoomsdayDevice,
UnitType.Factory,
UnitType.ResearchLab,
];

const sortedEnemies = enemies.sort((a, b) => {
Expand Down
4 changes: 4 additions & 0 deletions src/core/execution/ConstructionExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from "../game/Upgradeables";
import { AirfieldExecution } from "./AirfieldExecution";
import { DefensePostExecution } from "./DefensePostExecution";
import { DoomsdayDeviceExecution } from "./DoomsdayDeviceExecution";
import { FighterJetExecution } from "./FighterJetExecution";
import { MirvExecution } from "./MIRVExecution";
import { MissileSiloExecution } from "./MissileSiloExecution";
Expand Down Expand Up @@ -263,6 +264,9 @@ export class ConstructionExecution implements Execution {
this.applyUpgradesIfNeeded(built, this.desiredLevel);
}
break;
case UnitType.DoomsdayDevice:
this.mg.addExecution(new DoomsdayDeviceExecution(player, this.tile));
break;
case UnitType.Airfield:
this.mg.addExecution(new AirfieldExecution(player, this.tile));
break;
Expand Down
Loading
Loading