Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5cfe865
Refactor Research Tree: Remove Economy, update UI with icons and toolโ€ฆ
El-Magico777 Dec 12, 2025
c859e43
chore: refine tech tooltips and effects
El-Magico777 Dec 13, 2025
2b51407
Clarify tech descriptions and tooltips
El-Magico777 Dec 13, 2025
66a040a
feat: implement structure stacking system with functional multipliers
El-Magico777 Dec 13, 2025
cff23ef
feat: Complete structure stacking system with proper counts and unlimโ€ฆ
El-Magico777 Dec 13, 2025
604033e
refactor: Shorten submarine level 1 name in build menu to 'Diesel Sub'
El-Magico777 Dec 13, 2025
c064032
fix: gate diesel subs behind Sea-1 and allow research labs by default
El-Magico777 Dec 13, 2025
755b029
chore: show tech short description in unlock toast
El-Magico777 Dec 13, 2025
f0cf9d6
ui: move stack chip and swap stack controls
El-Magico777 Dec 13, 2025
4da71bd
feat: cap structure stacking at 25 across client+server\n\n- Add MAX_โ€ฆ
El-Magico777 Dec 13, 2025
20d802a
feat: add multi-priority research system with category-level controls
El-Magico777 Dec 13, 2025
b2a7f92
Update tests for new tech tree structure (Land/Sea/Air/Nuclear)
El-Magico777 Dec 13, 2025
41efa56
Remove dead code: policy directives, insurance, and scorched earth
El-Magico777 Dec 14, 2025
44ddd80
chore: remove economy category from HelpModal tech tree
El-Magico777 Dec 14, 2025
4d7d16a
fix: paratroopers not showing in radial menu after researching Air-1
El-Magico777 Dec 14, 2025
9074d01
feat: replace Bombers tab with radial menu control and enable auto-boโ€ฆ
El-Magico777 Dec 14, 2025
6761394
feat: make disabled radial menu options visible with color-coded states
El-Magico777 Dec 14, 2025
571bf4c
UI: Diplomacy relation icons
El-Magico777 Dec 14, 2025
7e80e55
UI: Remove Diplomacy tab from ControlPanel2
El-Magico777 Dec 14, 2025
925f5da
Fix: Diplomacy icon sizing and filtering
El-Magico777 Dec 14, 2025
7a7079b
feat(ui): Remove Trade tab and migrate Trade Demand to Build tab toolbar
El-Magico777 Dec 14, 2025
59321e4
fix: Remove viewport clipping from TerritoryLayer putImageData to fixโ€ฆ
El-Magico777 Dec 14, 2025
a341f91
feat(artillery): implement land-based Artillery unit system with GPU โ€ฆ
El-Magico777 Dec 15, 2025
04b536e
feat(artillery): integrate artillery into tech tree with auto-upgradeโ€ฆ
El-Magico777 Dec 18, 2025
f6731fb
feat(ui): add artillery to player info overlay
El-Magico777 Dec 18, 2025
93577e8
feat(hotkeys): add artillery hotkey with default key '4'
El-Magico777 Dec 18, 2025
f719dde
docs(help): add artillery to help modal units tab
El-Magico777 Dec 18, 2025
c7c7ffc
feat(combat): add warship targeting for coastal artillery
El-Magico777 Dec 18, 2025
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/artillery-battery.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/research/air.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/research/land.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/research/nuclear.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/research/sea.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 18 additions & 1 deletion resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
"build_port_desc": "Can only be built near water. Allows building Cruisers. Automatically sends cargo ships between ports of your country and other countries (except when trade is stopped), giving gold to both sides. Trade stops automatically when you attack or are attacked by a player. It resumes after 5 minutes or if you become allies. You can manually toggle trading with \"Stop trading\" or \"Start trading\".",
"build_warship": "Cruiser",
"build_warship_desc": "Patrols in an area, capturing enemy cargo ships and destroying their Boats (transport ships) and Cruisers. Spawns from the nearest Port and patrols the area you first clicked to build it. You can control Cruisers by attack-clicking on them (see action Attack under Hotkeys) and then attack-clicking the new area you want them to move to.",
"build_artillery": "Artillery",
"build_artillery_desc": "Land-based heavy artillery that patrols an area and bombards enemy structures within range. Spawns from the nearest Factory and patrols the area you first clicked to build it. You can redirect Artillery by attack-clicking on them and then attack-clicking the new area you want them to move to.",
"build_silo": "Missile Silo",
"build_silo_desc": "Allows launching missiles.",
"build_sam": "SAM Launcher",
Expand Down Expand Up @@ -291,6 +293,7 @@
"fighter_jet": "Fast air unit for intercepting bombers and scouting.",
"warship": "Patrols in an area, capturing enemy cargo ships and destroying their Boats and Cruisers.",
"submarine": "Stealthy naval unit. Can launch nukes if researched. Invisible to enemies unless they have detection.",
"artillery": "Land-based heavy artillery that patrols an area and bombards enemy structures within range. Spawns from the nearest Factory.",
"city": "Increases your max population. Useful when you can't expand your territory.",
"port": "Allows building Cruisers and Submarines. Automatically sends cargo ships to trade with other players.",
"airfield": "Automatically deploys bombers that fly out to strike enemy structures then return to base.",
Expand All @@ -310,6 +313,7 @@
"defense_post": "Defense Post",
"port": "Port",
"warship": "Cruiser",
"artillery": "Artillery",
"submarine": "Submarine",
"missile_silo": "Missile Silo",
"sam_launcher": "SAM Launcher",
Expand Down Expand Up @@ -430,6 +434,8 @@
"build_warship_desc": "Set the hotkey to build a Cruiser.",
"build_submarine": "Build Submarine",
"build_submarine_desc": "Set the hotkey to build a Submarine.",
"build_artillery": "Build Artillery",
"build_artillery_desc": "Set the hotkey to build Artillery.",
"reset": "Reset",
"unbind": "Unbind",
"on": "On",
Expand Down Expand Up @@ -571,6 +577,7 @@
"missile_launchers": "Missile launchers",
"sams": "SAMs",
"warships": "Cruisers",
"artillery": "Artillery",
"fighter_jets": "Fighter Jets",
"health": "Health",
"attitude": "Attitude",
Expand Down Expand Up @@ -699,7 +706,10 @@
"international_trade_origin": "Your cargo truck successfully delivered goods to {destinationName}. You received {goldAmount} gold.",
"international_trade_destination": "A cargo truck from {originName} arrived. You received {goldAmount} gold.",
"insurance_refund": "Insurance refund {amount} gold.",
"insurance_refund_conquest": "Insurance refund {amount} gold for conquered structure."
"insurance_refund_conquest": "Insurance refund {amount} gold for conquered structure.",
"artillery_out_of_range_1": "Artillery is out of range (max 60 tiles for level 1)",
"artillery_out_of_range_2": "Artillery is out of range (max 75 tiles for level 2)",
"artillery_out_of_range_3": "Artillery is out of range (max 90 tiles for level 3)"
},
"research_tree": {
"title": "Research Tree",
Expand Down Expand Up @@ -1023,6 +1033,13 @@
"desc": "'<strong>'Requirement:'</strong>' Port<br />Generates gold by trading between your ports and other players' ports (not between your own ports). Passive income source. '<strong>'Tip:'</strong>' Protect trade routes from pirates and enemy submarines."
}
},
"land": {
"title": "Land Units",
"artillery": {
"name": "Artillery",
"desc": "'<strong>'Requirement:'</strong>' Factory<br />Land-based heavy artillery that patrols territory and bombards enemy structures within range. Spawns from nearest Factory. '<strong>'Range:'</strong>' 60/75/90 tiles (levels 1/2/3). '<strong>'Upgrades:'</strong>' Higher levels increase range, damage, and durability. '<strong>'Tip:'</strong>' Excellent for softening enemy defenses before ground assault."
}
},
"air": {
"title": "Air Units",
"bomber": {
Expand Down
38 changes: 28 additions & 10 deletions src/client/HelpModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,13 +390,6 @@ export class HelpModal extends LitElement {
altKey: "ui_guide.command_center_economy_alt",
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>`,
},
{
titleKey: "ui_guide.command_center_trade_title",
descKey: "ui_guide.command_center_trade_desc",
img: "/images/HelpModalScreenshots/CC-Trade.png",
altKey: "ui_guide.command_center_trade_alt",
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 21c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1 .6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/><path d="M19.38 20A11.6 11.6 0 0 0 21 14l-9-4-9 4c0 2.9.9 5.8 2.5 8"/><path d="M12 10V4"/><path d="M8 8v2"/><path d="M16 8v2"/></svg>`,
},
{
titleKey: "ui_guide.command_center_diplomacy_title",
descKey: "ui_guide.command_center_diplomacy_desc",
Expand Down Expand Up @@ -889,6 +882,15 @@ export class HelpModal extends LitElement {
},
];

const landUnits = [
{
nameKey: "units.land.artillery.name",
iconClass: "artillery-icon",
hotkey: "4",
descKey: "units.land.artillery.desc",
},
];

const airUnits = [
{
nameKey: "units.air.bomber.name",
Expand Down Expand Up @@ -978,6 +980,25 @@ export class HelpModal extends LitElement {
</table>
</div>

<div class="help-subsection">
<div class="text-xl font-bold mb-3 mt-6">
${this.t("units.land.title")}
</div>
<table class="help-table">
<thead>
<tr>
<th>${this.t("labels.name")}</th>
<th class="icon-col">${this.t("labels.icon")}</th>
<th>${this.t("labels.hotkey")}</th>
<th>${this.t("labels.description")}</th>
</tr>
</thead>
<tbody class="text-left">
${renderUnitRows(landUnits)}
</tbody>
</table>
</div>

<div class="help-subsection">
<div class="text-xl font-bold mb-3 mt-6">
${this.t("units.air.title")}
Expand Down Expand Up @@ -1153,7 +1174,6 @@ export class HelpModal extends LitElement {
Sea: [],
Air: [],
Nuclear: [],
Economy: [],
};

for (const node of nodes) {
Expand Down Expand Up @@ -1188,15 +1208,13 @@ export class HelpModal extends LitElement {
Sea: this.t("tech_tree.categories.sea"),
Air: this.t("tech_tree.categories.air"),
Nuclear: this.t("tech_tree.categories.nuclear"),
Economy: this.t("tech_tree.categories.economy"),
};

const categoryDescriptions: Record<string, string> = {
Land: this.t("tech_tree.categories.land_desc"),
Sea: this.t("tech_tree.categories.sea_desc"),
Air: this.t("tech_tree.categories.air_desc"),
Nuclear: this.t("tech_tree.categories.nuclear_desc"),
Economy: this.t("tech_tree.categories.economy_desc"),
};

return html`
Expand Down
90 changes: 89 additions & 1 deletion src/client/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,18 @@ export class CenterCameraEvent implements GameEvent {

import { UnitType } from "../core/game/Game";
import { GameView } from "../core/game/GameView";
import { getArtilleryMaxDistance } from "../core/game/UnitUpgrades";
import {
isUpgradeableUnit,
maxStructureLevel,
maxUnitLevel,
playerMaxUnitLevel,
} from "../core/game/Upgradeables";
import { ToggleBomberUpgradeModeEvent } from "./events/ToggleBomberUpgradeModeEvent";
import { ToggleUpgradeModeEvent } from "./events/ToggleUpgradeModeEvent";
import { TransformHandler } from "./graphics/TransformHandler";
import { UIState } from "./graphics/UIState";
import { BuildUnitIntentEvent } from "./Transport";
import { ArtilleryOutOfRangeEvent, BuildUnitIntentEvent } from "./Transport";

// Post-impact nuke halo event (world tile coordinates)
export class NukeImpactEvent implements GameEvent {
Expand Down Expand Up @@ -187,6 +194,7 @@ export class InputHandler {
buildFighterJet: "Digit8",
buildWarship: "Digit9",
buildSubmarine: "Digit0",
buildArtillery: "Digit4",
buildCity: "KeyY",
buildPort: "KeyU",
buildAirfield: "KeyI",
Expand Down Expand Up @@ -379,6 +387,7 @@ export class InputHandler {
[this.keybinds.buildFighterJet]: UnitType.FighterJet,
[this.keybinds.buildWarship]: UnitType.Warship,
[this.keybinds.buildSubmarine]: UnitType.Submarine,
[this.keybinds.buildArtillery]: UnitType.Artillery,
[this.keybinds.buildCity]: UnitType.City,
[this.keybinds.buildPort]: UnitType.Port,
[this.keybinds.buildAirfield]: UnitType.Airfield,
Expand Down Expand Up @@ -410,6 +419,14 @@ export class InputHandler {

if (this.game.isValidCoord(cell.x, cell.y)) {
const tile = this.game.ref(cell.x, cell.y);

if (
unitType === UnitType.Artillery &&
!this.validateArtilleryBuildDistance(tile)
) {
return;
}

this.eventBus.emit(new BuildUnitIntentEvent(unitType, tile));
}
}
Expand Down Expand Up @@ -499,6 +516,14 @@ export class InputHandler {
this.uiState.bomberUpgradeMode = false;
this.eventBus.emit(new ToggleBomberUpgradeModeEvent(false));
}

if (
this.uiState.pendingBuildUnitType === UnitType.Artillery &&
!this.validateArtilleryBuildDistance(tile, true)
) {
return;
}

this.eventBus.emit(
new BuildUnitIntentEvent(this.uiState.pendingBuildUnitType, tile),
);
Expand Down Expand Up @@ -635,6 +660,69 @@ export class InputHandler {
};
}

private readArtilleryTargetLevel(): number {
const myPlayer = this.game.myPlayer();
if (!myPlayer) return 1;

// Default to player's max unlocked level
let targetLevel = playerMaxUnitLevel(myPlayer, UnitType.Artillery);

try {
if (isUpgradeableUnit(UnitType.Artillery)) {
const rawUnits = localStorage.getItem("unitUpgradeSettings.levels");
if (rawUnits) {
const obj = JSON.parse(rawUnits) as Record<string, number>;
const val = obj?.[String(UnitType.Artillery)];
if (typeof val === "number") {
targetLevel = Math.min(maxUnitLevel(UnitType.Artillery), val);
}
}
} else {
const rawStruct = localStorage.getItem("buildSettings.levels");
if (rawStruct) {
const obj = JSON.parse(rawStruct) as Record<string, number>;
const val = obj?.[String(UnitType.Artillery)];
if (typeof val === "number") {
targetLevel = Math.min(maxStructureLevel(UnitType.Artillery), val);
}
}
}
} catch {
// Keep default (player's max unlocked level)
}
return targetLevel;
}

private validateArtilleryBuildDistance(
tile: TileRef,
clearPendingIfInvalid: boolean = false,
): boolean {
const myPlayer = this.game.myPlayer();
if (!myPlayer) return true;

const factories = myPlayer.units(UnitType.Factory);
if (factories.length === 0) return true;

let minDistSq = Infinity;
for (const factory of factories) {
const distSq = this.game.euclideanDistSquared(factory.tile(), tile);
if (distSq < minDistSq) minDistSq = distSq;
}

const targetLevel = this.readArtilleryTargetLevel();
const maxDist = getArtilleryMaxDistance(targetLevel);

if (minDistSq > maxDist * maxDist) {
this.eventBus.emit(new ArtilleryOutOfRangeEvent(targetLevel, maxDist));
if (clearPendingIfInvalid && !this.uiState.multibuildEnabled) {
this.uiState.pendingBuildUnitType = null;
}
return false;
}

return true;
}

destroy() {
if (this.moveInterval !== null) {
clearInterval(this.moveInterval);
Expand Down
3 changes: 3 additions & 0 deletions src/client/LocalPersistantStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export function startGame(id: GameID, lobby: Partial<GameConfig>) {
return;
}

// Clear stack count settings so each game starts fresh
localStorage.removeItem("buildSettings.stackCount");

_startTime = Date.now();
const stats = getStats();
stats[id] = { lobby };
Expand Down
Loading
Loading