feat: add Tech Unlocked mode to public lobby rotation#140
Closed
El-Magico777 wants to merge 1 commit intov0.2.0from
Closed
feat: add Tech Unlocked mode to public lobby rotation#140El-Magico777 wants to merge 1 commit intov0.2.0from
El-Magico777 wants to merge 1 commit intov0.2.0from
Conversation
- Add 50/50 rotation between normal and Tech Unlocked games in MapPlaylist - Tech Unlocked games have all technologies researched from start (researchAllTechs: true) - Display ' Tech Unlocked' badge in PublicLobby UI when game has all techs unlocked - Badge uses yellow highlight with tooltip explaining the mode - Add translation strings for tech_unlocked and tech_unlocked_tooltip Files changed: - src/server/MapPlaylist.ts: Add fastModeCounter for 50/50 rotation - src/client/PublicLobby.ts: Add Tech Unlocked badge display - resources/lang/en.json: Add translation strings
El-Magico777
added a commit
that referenced
this pull request
Dec 18, 2025
1brucben
pushed a commit
that referenced
this pull request
Dec 19, 2025
* Refactor Research Tree: Remove Economy, update UI with icons and tooltips
* chore: refine tech tooltips and effects
- Tweaked research tree UI text and tooltip content (short vs detailed)
- Updated tech tooltips with clearer unlock summaries and numbers
- Refactored tech effects unlock logic and cleaned definitions
* Clarify tech descriptions and tooltips
* feat: implement structure stacking system with functional multipliers
Adds a complete structure stacking system that allows players to build
multiple instances of structures in a single tile. Stacking provides:
- Increased HP (bonus per stacked instance)
- Multiplied functionality (e.g., ×2 city = 2× population cap)
STACKING MECHANICS:
- Stack count is user-controlled (1-99) via +/- buttons in build menu
- Each game starts fresh with stack count reset to 1
- Uses localStorage for in-game UI communication only (cleared on game start)
- Stack count shown as ×N prefix in build menu display
STACKABLE STRUCTURES (9 total):
- City: ×N population cap increase
- Port: ×N trade ship supply
- Airfield: spawns N bombers
- Hospital: ×N healing effect
- Academy: ×N combat bonus
- Research Lab: ×N research boost
- Factory: ×N gold production bonus
- Missile Silo: can launch N nukes before cooldown
- SAM Launcher: fires N missiles per interception
DEFENSE POST: Stacking intentionally disabled (single instance only)
IMPLEMENTATION DETAILS:
Core data model:
- Added _stackCount field to UnitImpl for tracking stacked instances
- Added _launchesRemaining field for missile silo multi-launch tracking
- Added stackCount() and launchesRemaining() to Unit interface
- Added fields to GameUpdates and GameView for client sync
Stackable structure definitions (Upgradeables.ts):
- Added STACKABLE_STRUCTURES set (9 structure types)
- Added TECH_UPGRADEABLE_STRUCTURES set (SAM, Airfield only)
- Added helper functions: isStackableStructure(), maxStackCount(),
isTechUpgradeableStructure(), maxStructureTechLevel()
Construction execution:
- ConstructionExecution now separates stackCount from techLevel
- Added computeStackCount() and computeTechLevel() methods
- Added applyStackingIfNeeded() to set stack count and HP bonuses
Structure-specific execution updates:
- AirfieldExecution: spawns stackCount bombers, tracks upgrades
- SAMLauncherExecution: fires stackCount missiles per interception
- MissileSiloExecution: accepts stackCount param, allows N launches
before cooldown via launchesRemaining tracking
- DefensePostExecution: removed stacking support
Client UI:
- BuildMenu: shows ×N prefix for stacked structures
- ControlPanel2: Stack ×N UI with +/- adjustment buttons
- Transport: reads stack count and sends with build intent
- LocalPersistantStats: clears stack count settings on game start
* feat: Complete structure stacking system with proper counts and unlimited SAM/Silo stacking
- Fix structure stacking system to properly increment stackCount via setStackCount()
- Update playerMaxStructureLevel() to return 99 for all stackable structures (SAM, Silo, Airfield)
- Remove legacy cap of 3 from maxStructureLevel() for SAM and Silo
- Implement launchesRemaining system for SAM (fire stackCount times before cooldown)
- Fix SAM/Silo cooldown calculation to use effectiveMaxHealth() instead of base health
- SAM range now uses playerMaxStructureTechLevel() (tech tier 1-3), not stack count
- Display stack count instead of level for structure badges
- Add SAM tech level as secondary indicator (similar to Airfield bomber level)
- PlayerInfoOverlay and BuildMenu now show correct counts including stacked structures
- Update unitsOwned() to use stackCount() for all stackable structures
- Rename 'Upgrade Structures' button to 'Stack Structures'
* refactor: Shorten submarine level 1 name in build menu to 'Diesel Sub'
* fix: gate diesel subs behind Sea-1 and allow research labs by default
* chore: show tech short description in unlock toast
* ui: move stack chip and swap stack controls
* feat: cap structure stacking at 25 across client+server\n\n- Add MAX_STACK_COUNT=25 and clamp in helpers (maxStackCount, maxStructureLevel, playerMaxStructureLevel)\n- Clamp ConstructionExecution desired stack, UnitImpl.setStackCount\n- Limit client intent payload and schema to 25 (Transport, Schemas)\n- Keeps UI controls honoring cap via shared helpers
* feat: add multi-priority research system with category-level controls
- Support multiple simultaneous research priorities (Set-based)
- Add category 'Prioritize' button to set all techs in category
- Cross-category prioritization: selecting individual tech clears same-level techs from other categories
- Category prioritization clears other categories but preserves existing priorities in selected category
- Locked prioritized techs show gold background with lock icon
- Research allocation: 60% split among available priorities, 40% to others
- Priority prerequisites get 60% when priorities locked
- Update PlayerView to expose full priorities Set
* Update tests for new tech tree structure (Land/Sea/Air/Nuclear)
* Remove dead code: policy directives, insurance, and scorched earth
- Remove ScorchedEarth upgrade and all related code
- Delete ScorchedEarthExecution and integration tests
- Remove SCORCHED_EARTH from UpgradeType enum
- Remove scorchedEarthActivationCost from Config
- Remove Insurance system
- Remove INSURANCE_REFUND message type
- Remove insurance refund logic from UnitImpl
- Remove structureInsuranceRefundNum/Den from Config
- Remove ECONOMY_INSURANCE tech ID
- Remove policy directive system (referenced non-existent Economy-3 tech)
- Delete PolicyDirectiveSelectExecution, MarkPolicyDirectivesSeenExecution
- Delete PolicyDirectives.ts definition file
- Remove policy schemas, intents, handlers from Transport/ExecutionManager
- Remove policy methods from Player interface and PlayerImpl
- Remove policy logic from all TechEffects modifier functions
- Remove policy notification from ResearchToggleButton
- Move InternationalTrade upgrade to Land-1 tech (Road Network)
- Remove unused tech IDs: ECONOMY_INTERNATIONAL_TRADE, ECONOMY_TBD_LEVEL4, TRADE_POLICY_FRAMEWORK
- Remove unused upgrade types: WaterUpgrade1/2/3, AirUpgrade3, EconomyUpgrade1/2/3
All tests passing (46 suites, 276 tests)
* chore: remove economy category from HelpModal tech tree
* fix: paratroopers not showing in radial menu after researching Air-1
The paratrooper option was not appearing in the radial menu even when Air-1 tech was researched and airfield was in range.
Root cause: Both RadialMenu.shouldShowAirAttack() and ParatrooperAttackExecution.init() were checking for obsolete UpgradeType.AirUpgrade1 instead of UpgradeType.JetEngines.
The Air-1 Early Air Power tech grants UpgradeType.JetEngines, not AirUpgrade1, so the check was always failing.
Changed both files to check for JetEngines upgrade, which is the correct requirement for paratroopers.
All tests pass (46 suites, 276 tests).
* feat: replace Bombers tab with radial menu control and enable auto-bombing by default
BREAKING CHANGE: Auto-bombing is now enabled by default for all players
Changes:
- PlayerImpl: Changed _autoBombingEnabled default from false to true
* All players now have auto-bombing active by default
* Can still be overridden via radial menu manual targeting
- RadialMenu: Added bomber targeting action (Slot.Bomber)
* Shows airfield icon when right-clicking enemy land tiles
* Gating: requires airfield, land tile, enemy ownership, war status
* Range validation: checks bomber level and config range from all airfields
* Targets all structure types (City, DefensePost, SAMLauncher, etc.)
* Uses preferClosest=true for optimal targeting priority
* Icon: airfield icon (matches build menu consistency)
- BomberExecution: Fixed manual to auto fallback logic
* When manual targets exhausted, now clears bomberIntent via setBomberIntent(null)
* Ensures seamless switch back to auto-bombing mode
* Previously would get stuck when findTargetFromQueue returned null
- ControlPanel2: Completely removed Bombers tab
* Deleted tab UI rendering (button and content panel ~200 lines)
* Removed all bomber-specific state variables
* Removed bomber methods: _startAutoBombing, _stopAutoBombing,
_handleBomberTargetChange, populateBomberForm, _refreshBomberPlayerLists,
_getPlayersInAirfieldRange
* Removed bomber-related updated() hook logic
* Removed Bombers from activeTab type union and _changeTab signature
* Cleaned up residual references (comments, dead code)
* Kept sendBomberIntent() for radial integration
- UI/UX Improvements:
* Unified bomber control via radial menu (right-click context)
* Removed toggle complexity from control panel
* Airfield icon provides visual consistency with build menu
* Auto-bombing ensures bombers always active when at war
All tests pass (46 suites, 276 tests). TypeScript and ESLint clean.
* feat: make disabled radial menu options visible with color-coded states
Previously, disabled radial menu options were completely greyed out with a generic disabled icon, making it impossible to understand what options were available or why they were unavailable.
Now there are three distinct visual states:
1. Active (unlocked and in range): Full color, full opacity, clickable
2. Out of range (unlocked but too far): Darkened version of the feature's color (preserves hue), 0.7 opacity
3. Not unlocked (feature not researched): Black and white (no color set), 0.7 opacity
Changes:
- Replaced swap-to-grey-icon with darkenColor() helper that preserves original hue when a color is set
- When no color is set (not unlocked), uses dark grey fallback for black-and-white appearance
- Increased disabled opacity from 0.5 to 0.7 for better icon visibility
- Icons always remain visible (boat, airAttack, bomber, info, ally, peace icons set as defaults)
- Removed unused disabledIcon import
Result: Players can now see all radial menu options and understand their status at a glance - full color means ready to use, colored but dark means out of range, black and white means not yet unlocked.
* UI: Diplomacy relation icons
- NameLayer: add war (sword) and neutral (dove) icons above player names, reusing diplomacy assets
- Color: render war icon via CSS mask using SwordIcon.svg with exact warColor (#8B0000) to match radial menu; neutral uses existing dove image
- Self-filter: hide alliance/war/neutral icons for the local player’s own name
- Overlay: remove relation icons from PlayerInfoOverlay; show text-only relation (Allied/Neutral/Hostile) for clarity
Rationale:
- Provide consistent visual indicators for neutral and at-war states where only allied icon existed
- Ensure color parity with radial menu (darker red for war) and avoid yellow tint
- Reduce clutter by not showing diplomacy icons for self and keeping the overlay textual
Notes:
- Assets used: resources/images/SwordIcon.svg (masked to #8B0000), proprietary/images/dove.png; alliance remains resources/images/AllianceIcon.svg
- No server or core changes; client-only UI update
* UI: Remove Diplomacy tab from ControlPanel2
- Remove Diplomacy from tab list and type definitions
- Stub out renderDiplomacyTab method (now returns empty template)
- Remove all Diplomacy tab UI code and event handlers
Rationale:
- Diplomacy relations are now displayed via NameLayer icons (allied/war/neutral)
- No need for separate tab UI when visual indicators are on the map
- Simplifies control panel and reduces clutter
Changes:
- Removed 'Diplomacy' from activeTab type union
- Removed Diplomacy tab button from UI
- Removed 180+ lines of diplomacy tab render logic
- Kept method signature for compatibility but returns empty html
* Fix: Diplomacy icon sizing and filtering
- War icon now 20% smaller for better visual balance
- Use exact #8B0000 color via CSS mask (matches radial menu war button)
- Only show diplomacy icons for Human/FakeHuman players (not regular bots)
- Removed icon resize loop that caused flickering
Changes:
- NameLayer: war icon sized at 0.8x, uses CSS mask with #8B0000
- Added PlayerType check: isHumanOrFakeHuman filter for alliance/war/neutral icons
- Import PlayerType from Game.ts for type checking
- Simplified icon creation - stable sizing without constant updates
* perf: Optimize cluster calculation with DFS and zero-allocation patterns
Replace BFS with DFS and eliminate GC pressure in bot annexing cluster calculations:
**Algorithm Optimization:**
- Switch from O(N) queue.shift() to O(1) stack.pop() operations in cluster traversal
- Replace BFS queue-based approach with DFS stack-based approach for better performance
**Memory Optimization:**
- Replace Set.has()/Set.add() with Uint8Array bitfield for visited tile tracking
- Add generation counter to avoid clearing bitfield array on each calculation
- Implement WeakMap-based reusable buffer management per game instance
- Eliminates repeated allocations across multiple cluster calculations
**Zero-Allocation Neighbor Iteration:**
- Add forEachNeighbor() and forEachNeighborWithDiag() to Game interface
- Implement callback-based neighbor iteration in GameImpl
- Update surroundedBySamePlayer(), isSurrounded(), and getCapturingPlayer() to use callbacks
- Eliminates intermediate array allocations from neighbor() calls
**Impact:**
This optimization runs every 20 ticks for each bot player during the encirclement
annexing mechanic. With multiple bots and large territories, the reduction in GC
pressure and algorithmic efficiency significantly improves performance in long-running
games, especially when cluster calculations exceed 1000ms (as logged in the code).
**Testing:**
All existing tests pass, confirming identical functionality with improved performance.
Bot-only annexing mechanic behavior remains unchanged.
* feat: Add lobby chat with vertical side panel layout and profanity filter
## Overview
Implements a lobby chat feature for private lobbies, allowing players to communicate before the game starts. This feature is based on OpenFrontIO PR #2514 but adapted to Terratomic's architecture using a polling-based REST API instead of WebSocket. Includes automatic profanity filtering for content moderation.
## Why Polling Instead of WebSocket
Terratomic's architecture only establishes WebSocket connections when the game starts, not during the lobby phase. To avoid significant architectural changes, this implementation uses a REST API with 1-second polling interval via the existing lobby polling mechanism.
## Features Added
### Backend Changes
- **LobbyMessage Interface** (Schemas.ts):
- clientID: string
- username: string
- isHost: boolean
- text: string (max 300 chars)
- timestamp: number
- **GameServer Updates**:
- Added lobbyMessages array to store up to 100 messages per lobby
- Added addLobbyMessage(clientID, username, text) method with validation
- Only allows messages before game starts and when chatEnabled is true
- Added chatEnabled to updateGameConfig() to persist toggle state
- Included messages in gameInfo() response for polling
- **Profanity Filter**: Integrated bad-words package to filter profanity server-side
- **Worker REST API**:
- POST /api/lobby/:id/messages endpoint
- Validates clientID, username (1-50 chars), and text (max 300 chars)
- Routes to correct worker using worker path prefix (e.g., /w1/api/lobby/...)
- Returns 404 if game not found, 400 for invalid input
- **GameConfig Schema**:
- Added chatEnabled: boolean field (default: false)
- Updated all game creation paths to include chatEnabled
### Frontend Changes
- **LobbyChatPanel Component** (new):
- Lit-based web component using light DOM
- Displays messages with sender name and (Host) indicator
- Auto-scrolls to bottom on new messages
- Input field with 300 character limit
- Send button and Enter key support
- Styles: local messages (right, blue), remote messages (left, dark)
- POST to /${workerPath}/api/lobby/${gameID}/messages
- **HostLobbyModal**:
- Added "Enable Lobby Chat" toggle in cheat codes section
- Vertical side panel layout (280px width) when chat enabled
- CSS grid: 2 columns (1fr 1fr) → 3 columns (1fr 1fr 280px) with .with-chat class
- Chat panel has max-height: 75vh with scrollable messages area
- Polls lobbyMessages and passes to LobbyChatPanel component
- Reverted sp-player-area max-height to 45% (chat no longer inside)
- **JoinPrivateLobbyModal**:
- Same vertical side panel approach (260px width, max-height: 500px)
- Flex layout: .join-lobby-layout.with-chat uses flexbox with align-items: stretch
- Added matching background/border-radius to team assignment panel
- Chat and team panels aligned at top with consistent styling
- Stores joinedClientID and joinedUsername on successful join
- **Translation**:
- Added "enable_chat" key to en.json
### Layout Design
- **Vertical Side Panel**: Chat appears as a dedicated column on the right side instead of inline with team assignments
- **Responsive**: Side panel only shows when chatEnabled is true
- **Alignment**: Team assignment and chat panels have matching heights and top alignment
- **Scrolling**: Messages area scrolls independently while input stays at bottom
### Content Moderation
- **Profanity Filter** (bad-words package):
- Automatically filters profanity from all messages server-side
- Replaces filtered words with asterisks (e.g., "f***")
- Uses default English profanity list (~400 words + variations)
- Applied before message storage - no raw profanity persisted
- Server-side filtering prevents client-side bypass
- Can be customized via addWords/removeWords if needed
## Testing Updates
Updated test configurations to include chatEnabled: false:
- GameServerAuthorization.test.ts
- ReplayCodec.test.ts
- tests/util/Setup.ts
## Technical Details
- **Message Limit**: 100 messages per lobby (server-side)
- **Character Limit**: 300 characters per message (enforced client and server)
- **Username Limit**: 50 characters max
- **Polling Interval**: 1 second (reuses existing lobby polling)
- **Security**: Server validates chat is enabled before accepting messages
- **Lifecycle**: Messages only allowed before game starts
- **Package Added**: bad-words (~50KB, 800k+ weekly downloads)
## Future Improvements
- Message timestamps display
- Typing indicators
- Message persistence/archival
- Custom profanity list configuration
* feat(ui): Remove Trade tab and migrate Trade Demand to Build tab toolbar
**Changes:**
1. **Removed Trade Tab**
- Eliminated separate Trade tab from Command Center UI
- Removed trade ship listing, construction queue, and embargo management UI
- Removed _renderTradeTab(), _computeTradeShipStatus(), embargo handlers
- Cleaned up associated Help Modal documentation
2. **Trade Demand Indicator Migration**
- Moved Trade Demand indicator to Build tab horizontal toolbar
- Now displays alongside Multi-Build and Stack Mode controls
- Compact design: ship icon + label + demand level (Very High/High/Medium/Low/Very Low)
- Hidden when player has no ports (not yet trading-enabled)
- Shows 'No Ships' state when no trade ships exist
3. **Trade Demand Calculation Fix**
- **Before:** Compared player's queue vs ALL players' ships globally (always showed 'Low')
- **After:** Compares player's queue vs ONLY their own ships (actually meaningful)
- New thresholds:
- Very High (red): queue > 2× ship count need ships urgently
- High (orange): queue > ship count could use more ships
- Medium (white): balanced capacity
- Low (green): 50%+ ships idle surplus capacity
- Very Low (blue): no routes waiting, ships available oversupply
4. **Tooltip Flicker Prevention**
- Added 2-second tooltip cache to prevent rapid re-renders
- Tooltip shows detailed stats: '15 routes waiting, 3/10 ships available'
- Cache only updates when actual values change (queue length, ship counts)
- Static tooltip positioning with pointer-events stabilization
5. **Build Menu Grid Redesign**
- Converted from flex rows to CSS Grid (4 columns × 3 rows)
- Reduced button height to 44px (from 50px) for denser layout
- Eliminated scrollbars - all 11 items fit in viewport
- Stack badges repositioned to top-right corner (blue badge: ×2, ×3, etc.)
- Icon sizing standardized to 24×24px
- Count badges moved to right side inline with layout
6. **Build Tab Toolbar Enhancement**
- Horizontal toolbar with consistent styling across Build/Attack tabs
- Multi-Build button: toggle mass-placement mode
- Stack Mode button: toggle structure stacking mode
- Stack control widget: compact +/- buttons with numeric display
- Trade Demand indicator: right-aligned in toolbar with spacer
- All controls use unified toolbar button styling (11px font, 4px padding)
7. **Architecture Notes**
- Trade Demand driven by cities (max population boost), territory, InternationalTrade upgrade
- Factories do NOT affect trade demand (only industrial production)
- Stacked cities count via effectiveUnits() (level 3 city = 3× effective units)
- Trade queue filled via gravity model: K × IP_A × IP_B / distance / world_IP
**Why:**
- Simplified UI - removed underutilized Trade tab
- Trade Demand now visible during building phase (Build tab) when most relevant
- Fixed misleading indicator that compared against global ship pool
- Players get actionable feedback: 'High' demand = build more ships
- Consistent toolbar UI pattern across all Command Center tabs
- Denser, scrollbar-free Build menu improves UX
* fix: Remove viewport clipping from TerritoryLayer putImageData to fix missing territory colors on zoom out
## Problem
Territory colors were not rendering correctly when zooming out. Areas of the map that were outside the initial viewport would appear blank/transparent even though they had proper ownership data. This issue became more frequent and noticeable as users zoomed out to view larger portions of the map.
## Root Cause
Introduced in commit 8361640 (Dec 9, 2025) as part of a dirty rect optimization. The renderLayer method was clipping the putImageData call to only update the intersection of:
- The dirty rect (tiles that changed)
- The visible viewport (what's currently on screen)
This optimization prevented off-screen areas from being written to the canvas. While the ImageData buffer contained correct color information for all tiles, the canvas element only received updates for tiles within the viewport at the time they were painted.
When users zoomed out, previously off-screen areas would become visible, but since putImageData was never called for those regions, the canvas still had blank/uninitialized data there.
## Solution
Removed the viewport intersection logic from the putImageData call. Now the full dirty rect is applied to the canvas without viewport clipping:
Before:
\\\ ypescript
// Intersect dirty rect with visible viewport
const vx0 = Math.max(0, topLeft.x, this.dirtyRect.x0);
const vy0 = Math.max(0, topLeft.y, this.dirtyRect.y0);
\\\
After:
\\\ ypescript
// Apply the dirty rect directly without viewport clipping
const x0 = Math.max(0, this.dirtyRect.x0);
const y0 = Math.max(0, this.dirtyRect.y0);
\\\
This ensures the canvas stays in sync with ImageData for all painted tiles, regardless of viewport position. The performance impact is minimal because:
- We still use dirty rect tracking (only updating changed areas)
- Modern browsers optimize putImageData efficiently
- The final drawImage call already renders the full canvas
## Testing
Verify by:
1. Starting a new game
2. Zooming out to view large portions of the map
3. All territory colors should now render correctly without blank areas
* Fix infinite gold not applying to fighter jets and airfield bomber upgrades
When infinite gold is enabled in single player lobby, the base cost of units
correctly becomes 0, but upgradeable units (Fighter jets) and airfield bomber
upgrades were still using hardcoded costs from UnitUpgrades.ts.
This fix adds checks in aggregateStructureBuildCost() and
computeBomberUpgradeCost() to detect when base cost is 0 (indicating infinite
gold is enabled) and returns 0 for upgrade costs as well.
Fixes issue where airfields and fighters don't cost 0 when unlimited gold is
selected in single player lobby.
* Improve unit selection grid layout in singleplayer lobby
Reduced unit selection card width from 140px to 115px in the Extra Settings section to optimize the grid layout. This change allows 6 unit cards to fit per row instead of 5, reducing the total from 3 rows to 2 rows for the 11 available structure types.
Benefits:
- Cleaner, more compact UI layout
- Better visual density without overcrowding
- Improved consistency with modern grid patterns
Affected file:
- src/client/utilities/RenderUnitTypeOptions.ts
* feat(artillery): implement land-based Artillery unit system with GPU rendering and advanced targeting
Implements a new Artillery unit type as a land-based equivalent to naval Warships, providing ground forces with long-range bombardment capabilities with autonomous patrol, intelligent targeting, and GPU-accelerated rendering.
Core Gameplay Mechanics
Unit Specifications (Level-Based)
- **Cost**: 500k/600k/700k gold for levels 1/2/3
- **Health**: 1000/1250/1500 HP across 3 upgrade tiers
- **Damage**: 12-22 / 17-27 / 22-32 per shell
- **Targeting Range**: 40/50/60 tiles by level
- **Max Distance from Factory/Redirecting**: 60/75/90 tiles by level
- **Attack Rate**: 1 shell every 2 ticks when target locked
- **Movement Speed**: Every 4/3/2 ticks by level
- **Patrol Range**: 35 tiles
Autonomous Behavior
- Spawns from owned Factories when constructed
- Patrols random positions within 35-tile radius using friendly-territory A* pathfinding
- Halts movement while actively firing at targets
- Targets enemy structures with intelligent priority system:
1. Enemy artillery (highest priority, direct counter)
2. Defense posts
3. Other structures
- Does not target neutral players without war declaration
- Fires shells using existing ShellExecution system (1 shell every 2 ticks)
- Heals +1 HP per tick if owning factory exists
- Respects peace timer (no attacks during peace)
- Destroyed automatically when tile is conquered by enemy
Movement and Pathfinding
- **Territory Restriction**: Can only traverse owned + allied territory (friendly-land pathfinding)
- **A* Pathfinding**: Uses MiniAStar with 5k expansion cap for performance
- **Path Caching**: Full patrol paths cached and reused until target changes or path becomes invalid
- **Water Handling**: Can traverse water/shore tiles (mirrors RoadManager logic)
- **Distance Validation**:
- Server validates spawn distance from nearest factory
- Server validates redirect distance from current position
- Client validates before sending commands (prevents unnecessary network calls)
- Out-of-range commands rejected with localized error notifications
Player Control
- Click to select artillery (shows pulsating colored selection box)
- Click land tile while selected to redirect patrol zone
- Visual selection feedback matches warship/fighter jet UX
- MoveArtilleryIntent system for client-server communication
- Client-side validation reads intended upgrade level from localStorage
Air Unit Integration
- Bombers target artillery as highest priority (above SAM launchers)
- Fighters can target artillery units (priority 5, after air units)
- Artillery becomes valid target for both bomber and fighter AI
Rendering System
GPU-Accelerated PIXI Layer
- Dedicated ArtilleryLayer using PIXI WebGL (shouldTransform=false) for crisp rendering
- Artillery renders 20% smaller than structures with sharp edges at all zoom levels
- Registered in GameRenderer after StructureLayer for proper z-ordering
Visual Features
- **Icon**: 1024x1024 PNG white artillery battery icon
- **Background**: Player-colored territory fill with 17% darkened border
- **Firing State**: Muted red background flash when firing shells
- **Health Bars**:
- Dynamic PIXI.Graphics positioned above icon
- Scales with zoom and icon size
- Color-coded: red/orange/yellow/green based on health percentage
- Auto-hidden at full health or death
- Updates every frame for smooth real-time display
- **Level Indicators**: Bronze stars in top-left corner (1/2/3 stars for levels)
- Tight spacing (0.3px between stars)
- Positioned 1px from edge
- Appear on both normal and firing states
- **Texture Caching**: Separate cached textures per level and firing state
Implementation Details
New Files
- `src/core/execution/ArtilleryExecution.ts`: Autonomous patrol, targeting, combat, path caching
- `src/core/execution/MoveArtilleryExecution.ts`: Player redirection with distance validation
- `src/client/graphics/layers/ArtilleryLayer.ts`: GPU-accelerated rendering with health bars
- `proprietary/images/artillery-battery.png`: 1024x1024 white icon asset
- `tests/core/execution/ArtilleryExecution.test.ts`: Comprehensive test suite (10 tests)
Modified Systems
**Game Logic**:
- Added Artillery to UnitType enum
- ARTILLERY_UPGRADES configuration with level-based stats
- artillerySpawn() method in PlayerImpl
- Artillery cases in ExecutionManager, ConstructionExecution, ShellExecution
- AI spawning support in UnitCreationHelper
- BomberExecution and FighterJetExecution targeting updates
- Unlimited gold mode support in Costs.ts
**Client Rendering**:
- Artillery layer registration in GameRenderer
- Selection box indicator in UILayer (pulsating colored square)
- Click handling for selection and redirection in UnitLayer
- InputHandler validation with ArtilleryOutOfRangeEvent emission
- EventsDisplay notifications for rejected commands
**Build System**:
- Build menu integration with factory prerequisite check
- Single player lobby unit toggle option
- Sprite loading in SpriteLoader
- Player info overlay icon path
- Unit upgrade settings integration
**Translations**:
- English: "Artillery", description, unit type label
- messages.artillery_out_of_range_{1|2|3} for distance validation errors
**Utilities**:
- getArtilleryMaxDistance() helper for centralized distance caps
- readArtilleryTargetLevel() helper for localStorage reads
- validateArtilleryBuildDistance() for range validation from factories
- setUnitLevel() test helper for safe level assignment
Performance Optimizations
Path Caching System
Instead of computing A* pathfinding every 2-4 ticks per artillery unit:
- Cache full patrol path on first computation
- Reuse cached path until invalidated
- Invalidate when:
- Target tile changes
- Current tile not on expected path (terrain changed)
- Path exhausted
- **Impact**: Reduces A* calls by ~10-30x for typical patrol patterns
- **Benefit**: Significantly lower CPU usage with multiple artillery units at high upgrade levels
Rendering Optimizations
- Removed redundant shell detection loop from ArtilleryLayer.renderLayer()
- Consolidated texture creation code to eliminate duplication
- Texture caching with level-specific keys
Code Quality
Refactoring
- Fixed filename typo: artillery-battery.png (consistent spelling)
- Removed unused artillery-icon.svg asset
- Removed all debug console.log/warn statements
- Removed unused artilleryTargettingRange() from Config interface
- Eliminated ~90 lines of duplicate validation code
- Centralized distance and level helpers
Testing
- 10 comprehensive tests covering:
- Target priority (artillery > defense > other)
- Neutral player handling
- Conquest deletion
- Distance validation for all 3 levels
- Redirect behavior
- All 299 tests passing
- Lint and tsc --noEmit clean
Technical Notes
- Uses MiniAStar with 5k iteration cap for friendly-territory pathfinding
- Reuses ShellExecution for projectile mechanics
- Distance measured via euclideanDistSquared for performance
- Client mirrors server validation to prevent unnecessary network round-trips
- Compatible with existing upgrade system (3 levels)
- Follows execution pattern with init() and tick() lifecycle
- WebSocket intent system for client-server coordination
- Health bar graphics properly destroyed when artillery removed
* feat(artillery): integrate artillery into tech tree with auto-upgrade system
Artillery feature complete with tech tree integration and bug fixes:
Tech Tree Integration:
- Land-2 (Ground Air Defense): Unlocks Artillery Level 1 (60 tile range, 1000 HP, 12-22 damage)
- Land-3 (Modern Air Defense): Upgrades to Artillery Level 2 (75 tile range, 1250 HP, 17-27 damage)
- Land-4 (Military Academy): Upgrades to Artillery Level 3 (90 tile range, 1500 HP, 20-30 damage)
- Added ArtilleryResearch upgrade type to gate availability behind Land-2 tech
- All existing artillery units auto-upgrade when player unlocks new tech levels
UI/UX Improvements:
- Added build menu description tooltip for artillery units
- Updated tech tooltips to display artillery stats and unlock info
- Shortened tech shortDescription to prevent overflow (e.g., 'City AA, SAM+, Artillery')
- Fixed out-of-range notifications to show correct artillery level (not always level 1)
- Artillery now only appears in build menu after Land-2 tech is researched
Bug Fixes:
- Fixed water pathfinding: artillery can no longer traverse ocean/water tiles
* Changed traversal logic from allowing water/shore to blocking ocean/water
* Prevents artillery from drowning when redirected near coastlines
- Fixed readArtilleryTargetLevel() to use player's actual unlocked level as default
* Now uses playerMaxUnitLevel() instead of hardcoded level 1
* Properly detects when player has upgraded artillery via tech tree
- Removed debug console.log statements from artillerySpawn()
Auto-Upgrade Implementation:
- PlayerImpl.addUpgrade() triggers upgradeCombatUnits() for ArtilleryLevel2/3
- PlayerImpl.upgradeCombatUnits() includes Artillery in desiredMaxHealth calculation
- Upgradeables.playerMaxUnitLevel() returns 0/1/2/3 based on unlocked artillery techs
- BuildMenu.canBuild() checks both Factory ownership and ArtilleryResearch unlock
Translation Updates:
- Added 'build_menu.desc.artillery' to en.json for tooltip display
Tech progression now matches other auto-upgrade units (warships/submarines/fighters).
Artillery spawns at level 1, auto-upgrades with tech unlocks, and stays on land.
Tested: TypeScript compilation clean, all 286 tests passing, lint clean
* feat(ui): add artillery to player info overlay
- Added Artillery icon and count to PlayerInfoOverlay unit grid
- Expanded grid from 12 to 13 columns to accommodate artillery
- Fixed artillery icon path typo (artillary -> artillery)
- Added artillery translation to player_info_overlay section in en.json
Artillery count now displays alongside other military units when hovering
over player territories, positioned between Warships and Missile Silos.
* feat(hotkeys): add artillery hotkey with default key '4'
- Added buildArtillery keybind with default 'Digit4' to InputHandler
- Added artillery hotkey mapping to build menu hotkey display
- Added artillery keybind setting to UserSettingModal in Units section
- Added build_artillery and build_artillery_desc translations to en.json
Players can now press '4' to build artillery or customize the key in settings.
* docs(help): add artillery to help modal units tab
- Added new 'Land Units' section to Units tab in HelpModal
- Added Artillery entry with icon, hotkey (4), and detailed description
- Added artillery-icon CSS styling with proper mask and background
- Added land.artillery translations to en.json with requirements, range info, and tips
- Positioned Land Units section between Naval and Air units
Artillery now appears in the help documentation with full details about
requirements (Factory), range by level (60/75/90 tiles), upgrades, and
strategic usage tips.
* feat(combat): add warship targeting for coastal artillery
- Added Artillery to warship's nearbyUnits search (UnitType.Artillery)
- Integrated artillery into warship targeting priority system
- Priority order: Submarines > Artillery > Warships > Transport Ships > Trade Ships
- Artillery follows standard war declaration rules (requires active war state)
- Added test: 'Warship targets coastal artillery when in range'
Warships can now bombard coastal artillery within their 130-pixel targeting range,
creating strategic coastal defense considerations. Artillery positioned near water
is vulnerable to naval bombardment, forcing players to balance offensive positioning
with defensive survivability.
* Merge PR #140: add Tech Unlocked mode to public lobby rotation
Collaborator
Author
|
merged in #169 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description:
Files changed:
Please complete the following:
Please put your Discord username so you can be contacted if a bug or regression is found:
el_magico777