Spaces:
Sleeping
Sleeping
| # ๐ง Quick Fixes for Critical Gameplay Issues | |
| ## Fix 1: Attack System Implementation | |
| ### Backend (app.py) | |
| Add after line 420: | |
| ```python | |
| # 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: | |
| ```javascript | |
| 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: | |
| ```python | |
| # 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`: | |
| ```python | |
| 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: | |
| ```javascript | |
| 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: | |
| ```python | |
| @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: | |
| ```python | |
| 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 | |
| ```bash | |
| # 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! | |