rts-commander / docs /FIXES_IMPLEMENTATION.md
Luigi's picture
deploy(web): full clean snapshot with app code and assets
12d64f8
|
raw
history blame
10.8 kB

๐Ÿ”ง Quick Fixes for Critical Gameplay Issues

Fix 1: Attack System Implementation

Backend (app.py)

Add after line 420:

# Add to handle_command method
elif cmd_type == "attack_unit":
    attacker_ids = command.get("attacker_ids", [])
    target_id = command.get("target_id")
    
    if target_id in self.game_state.units:
        target = self.game_state.units[target_id]
        for uid in attacker_ids:
            if uid in self.game_state.units:
                attacker = self.game_state.units[uid]
                # Set target for attack
                attacker.target = Position(target.position.x, target.position.y)
                attacker.target_unit_id = target_id  # Add this field to Unit class

# Add combat logic to game_loop (after line 348)
def update_combat(self):
    """Handle unit combat"""
    for unit in list(self.game_state.units.values()):
        if hasattr(unit, 'target_unit_id') and unit.target_unit_id:
            target_id = unit.target_unit_id
            if target_id in self.game_state.units:
                target = self.game_state.units[target_id]
                
                # Check range
                distance = unit.position.distance_to(target.position)
                
                if distance <= unit.range:
                    # In range - attack!
                    unit.target = None  # Stop moving
                    
                    # Apply damage (simplified - no cooldown for now)
                    target.health -= unit.damage / 20  # Damage over time
                    
                    if target.health <= 0:
                        # Target destroyed
                        del self.game_state.units[target_id]
                        unit.target_unit_id = None
                else:
                    # Move closer
                    unit.target = Position(target.position.x, target.position.y)
            else:
                # Target no longer exists
                unit.target_unit_id = None

Frontend (static/game.js)

Replace onRightClick method around line 220:

onRightClick(e) {
    e.preventDefault();
    
    if (this.selectedUnits.size === 0) return;
    
    const rect = this.canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    
    // Convert to world coordinates
    const worldX = (x / this.camera.zoom) + this.camera.x;
    const worldY = (y / this.camera.zoom) + this.camera.y;
    
    // Check if clicking on an enemy unit
    const clickedUnit = this.getUnitAtPosition(worldX, worldY);
    
    if (clickedUnit && clickedUnit.player_id !== 0) {
        // ATTACK ENEMY UNIT
        this.attackUnit(clickedUnit.id);
        this.showNotification(`๐ŸŽฏ Attacking enemy ${clickedUnit.type}!`, 'warning');
    } else {
        // MOVE TO POSITION
        this.moveSelectedUnits(worldX, worldY);
    }
}

// Add new methods
getUnitAtPosition(worldX, worldY) {
    if (!this.gameState || !this.gameState.units) return null;
    
    const CLICK_TOLERANCE = CONFIG.TILE_SIZE;
    
    for (const [id, unit] of Object.entries(this.gameState.units)) {
        const dx = worldX - (unit.position.x + CONFIG.TILE_SIZE / 2);
        const dy = worldY - (unit.position.y + CONFIG.TILE_SIZE / 2);
        const distance = Math.sqrt(dx * dx + dy * dy);
        
        if (distance < CLICK_TOLERANCE) {
            return { id, ...unit };
        }
    }
    return null;
}

attackUnit(targetId) {
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
    
    this.ws.send(JSON.stringify({
        type: 'attack_unit',
        attacker_ids: Array.from(this.selectedUnits),
        target_id: targetId
    }));
}

Fix 2: Production Requirements

Backend (app.py)

Add near top of file after imports:

# Production requirements mapping
PRODUCTION_REQUIREMENTS = {
    UnitType.INFANTRY: BuildingType.BARRACKS,
    UnitType.TANK: BuildingType.WAR_FACTORY,
    UnitType.ARTILLERY: BuildingType.WAR_FACTORY,
    UnitType.HELICOPTER: BuildingType.WAR_FACTORY,
    UnitType.HARVESTER: BuildingType.HQ  # โ† CRITICAL: Harvester needs HQ!
}

Replace build_unit handler in handle_command:

elif cmd_type == "build_unit":
    unit_type_str = command.get("unit_type")
    player_id = command.get("player_id", 0)
    
    if not unit_type_str:
        return
    
    try:
        unit_type = UnitType(unit_type_str)
    except ValueError:
        return
    
    # Find required building type
    required_building = PRODUCTION_REQUIREMENTS.get(unit_type)
    
    if not required_building:
        return
    
    # Find a suitable building owned by player
    suitable_building = None
    for building in self.game_state.buildings.values():
        if (building.player_id == player_id and 
            building.type == required_building):
            suitable_building = building
            break
    
    if suitable_building:
        # Add to production queue
        suitable_building.production_queue.append(unit_type_str)
        
        # Notify client
        await self.broadcast({
            "type": "notification",
            "message": f"Training {unit_type_str} at {required_building.value}",
            "level": "success"
        })
    else:
        # Send error
        await self.broadcast({
            "type": "notification",
            "message": f"โš ๏ธ Requires {required_building.value} to train {unit_type_str}!",
            "level": "error"
        })

Frontend (static/game.js)

Update train unit methods around line 540:

trainUnit(unitType) {
    // Check requirements
    const requirements = {
        'infantry': 'barracks',
        'tank': 'war_factory',
        'artillery': 'war_factory',
        'helicopter': 'war_factory',
        'harvester': 'hq'  // โ† CRITICAL!
    };
    
    const requiredBuilding = requirements[unitType];
    
    if (!this.hasBuilding(requiredBuilding)) {
        this.showNotification(
            `โš ๏ธ Need ${requiredBuilding.replace('_', ' ').toUpperCase()} to train ${unitType}!`,
            'error'
        );
        return;
    }
    
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
    
    this.ws.send(JSON.stringify({
        type: 'build_unit',
        unit_type: unitType,
        player_id: 0
    }));
}

hasBuilding(buildingType) {
    if (!this.gameState || !this.gameState.buildings) return false;
    
    for (const building of Object.values(this.gameState.buildings)) {
        if (building.player_id === 0 && building.type === buildingType) {
            return true;
        }
    }
    return false;
}

// Update setupBuildMenu to show requirements
setupBuildMenu() {
    document.getElementById('train-infantry').addEventListener('click', () => {
        this.trainUnit('infantry');
    });
    document.getElementById('train-tank').addEventListener('click', () => {
        this.trainUnit('tank');
    });
    document.getElementById('train-harvester').addEventListener('click', () => {
        this.trainUnit('harvester');
    });
    document.getElementById('train-helicopter').addEventListener('click', () => {
        this.trainUnit('helicopter');
    });
    document.getElementById('train-artillery').addEventListener('click', () => {
        this.trainUnit('artillery');
    });
    
    // Add tooltips
    document.getElementById('train-infantry').title = 'Requires: Barracks';
    document.getElementById('train-tank').title = 'Requires: War Factory';
    document.getElementById('train-harvester').title = 'Requires: HQ (Command Center)';
    document.getElementById('train-helicopter').title = 'Requires: War Factory';
    document.getElementById('train-artillery').title = 'Requires: War Factory';
}

Fix 3: Add Unit Range Field

Backend (app.py)

Update Unit dataclass around line 68:

@dataclass
class Unit:
    id: str
    type: UnitType
    player_id: int
    position: Position
    health: int
    max_health: int
    speed: float
    damage: int
    range: float = 100.0  # Add range field
    target_unit_id: Optional[str] = None  # Add target tracking
    
    # ... rest of methods

Update create_unit method around line 185 to set range:

def create_unit(self, unit_type: UnitType, player_id: int, position: Position) -> Unit:
    """Create a new unit"""
    unit_stats = {
        UnitType.INFANTRY: {"health": 100, "speed": 2.0, "damage": 10, "range": 80},
        UnitType.TANK: {"health": 200, "speed": 1.5, "damage": 30, "range": 120},
        UnitType.HARVESTER: {"health": 150, "speed": 1.0, "damage": 0, "range": 0},
        UnitType.HELICOPTER: {"health": 120, "speed": 3.0, "damage": 25, "range": 150},
        UnitType.ARTILLERY: {"health": 100, "speed": 1.0, "damage": 50, "range": 200},
    }
    
    stats = unit_stats[unit_type]
    unit_id = str(uuid.uuid4())
    unit = Unit(
        id=unit_id,
        type=unit_type,
        player_id=player_id,
        position=position,
        health=stats["health"],
        max_health=stats["health"],
        speed=stats["speed"],
        damage=stats["damage"],
        range=stats["range"],  # Add range
        target=None
    )
    self.units[unit_id] = unit
    return unit

Testing Checklist

After applying fixes:

  1. Test Attack:

    • Select friendly unit
    • Right-click on enemy unit
    • Unit should move toward enemy and attack when in range
    • Enemy health should decrease
    • Enemy should be destroyed when health reaches 0
  2. Test Production:

    • Try to train Harvester WITHOUT HQ โ†’ Should show error
    • Build HQ
    • Try to train Harvester WITH HQ โ†’ Should work
    • Try to train Infantry without Barracks โ†’ Should show error
    • Build Barracks
    • Train Infantry โ†’ Should work
  3. Test Requirements:

    • Hover over unit buttons โ†’ Should show tooltip with requirements
    • Click button without building โ†’ Should show error notification

Quick Apply Commands

# Backup current files
cd /home/luigi/rts/web
cp app.py app.py.backup
cp static/game.js static/game.js.backup

# Apply fixes manually or use sed/patch
# Then rebuild Docker:
docker stop rts-game
docker rm rts-game
docker build -t rts-game-web .
docker run -d --name rts-game -p 7860:7860 rts-game-web

Summary

These fixes add:

  1. โœ… Attack System - Right-click enemies to attack
  2. โœ… Production Requirements - Harvester needs HQ (not Refinery!)
  3. โœ… Error Messages - Clear feedback when requirements not met
  4. โœ… Tooltips - Shows what building is required

Impact: Game becomes playable and faithful to original mechanics!