File size: 10,775 Bytes
12d64f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# ๐Ÿ”ง 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!