426 lines
11 KiB
Markdown
426 lines
11 KiB
Markdown
# ClassAI Code Duplication Analysis
|
||||
|
|
|
|||
|
|
## Executive Summary
|
|||
|
|
**Critical Code Duplication Detected:** 1,740 occurrences of common methods across 200 files.
|
|||
|
|
|
|||
|
|
### Impact
|
|||
|
|
- **Maintenance Burden:** Bug fixes require changes in 13+ places
|
|||
|
|
- **Linker Warnings:** Multiple definition warnings for identical methods
|
|||
|
|
- **Memory Waste:** Duplicated code increases binary size unnecessarily
|
|||
|
|
- **Inconsistency Risk:** Same bugs exist in multiple classes
|
|||
|
|
|
|||
|
|
## Duplicate Code Patterns Identified
|
|||
|
|
|
|||
|
|
### Pattern 1: Cooldown Management (EVERY SPECIALIZATION)
|
|||
|
|
**Duplicated in:** 50+ files
|
|||
|
|
```cpp
|
|||
|
|
void UpdateCooldowns(uint32 diff)
|
|||
|
|
{
|
|||
|
|
for (auto& cooldown : _cooldowns)
|
|||
|
|
if (cooldown.second > diff)
|
|||
|
|
cooldown.second -= diff;
|
|||
|
|
else
|
|||
|
|
cooldown.second = 0;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pattern 2: Ability Validation (EVERY SPECIALIZATION)
|
|||
|
|
**Duplicated in:** 50+ files
|
|||
|
|
```cpp
|
|||
|
|
bool CanUseAbility(uint32 spellId)
|
|||
|
|
{
|
|||
|
|
auto it = _cooldowns.find(spellId);
|
|||
|
|
if (it != _cooldowns.end() && it->second > 0)
|
|||
|
|
return false;
|
|||
|
|
return HasEnoughResource(spellId);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pattern 3: Combat State Management (EVERY SPECIALIZATION)
|
|||
|
|
**Duplicated in:** 50+ files
|
|||
|
|
```cpp
|
|||
|
|
void OnCombatStart(::Unit* target)
|
|||
|
|
{
|
|||
|
|
Player* bot = GetBot();
|
|||
|
|
if (!bot)
|
|||
|
|
return;
|
|||
|
|
// Identical logic across all specs
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void OnCombatEnd()
|
|||
|
|
{
|
|||
|
|
// Identical cleanup logic
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pattern 4: Range Calculation (EVERY SPECIALIZATION)
|
|||
|
|
**Duplicated in:** 50+ files
|
|||
|
|
```cpp
|
|||
|
|
float GetOptimalRange(::Unit* target)
|
|||
|
|
{
|
|||
|
|
// Melee: return 5.0f
|
|||
|
|
// Ranged DPS: return 25.0f
|
|||
|
|
// Healers: return 30.0f
|
|||
|
|
// Only difference is the return value
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pattern 5: Resource Management (MANY SPECIALIZATIONS)
|
|||
|
|
**Duplicated in:** 30+ files
|
|||
|
|
```cpp
|
|||
|
|
bool HasEnoughResource(uint32 spellId)
|
|||
|
|
{
|
|||
|
|
// Check mana/rage/energy/etc
|
|||
|
|
// Logic varies slightly by resource type
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void ConsumeResource(uint32 spellId)
|
|||
|
|
{
|
|||
|
|
// Deduct resource cost
|
|||
|
|
// Logic varies slightly by resource type
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Files with Duplicate Definitions (Linker Warnings)
|
|||
|
|
|
|||
|
|
### FrostSpecialization (Death Knight)
|
|||
|
|
```
|
|||
|
|
warning LNK4006: "GetOptimalRange" bereits definiert
|
|||
|
|
warning LNK4006: "GetOptimalPosition" bereits definiert
|
|||
|
|
warning LNK4006: "ConsumeResource" bereits definiert
|
|||
|
|
warning LNK4006: "HasEnoughResource" bereits definiert
|
|||
|
|
warning LNK4006: "OnCombatEnd" bereits definiert
|
|||
|
|
warning LNK4006: "OnCombatStart" bereits definiert
|
|||
|
|
warning LNK4006: "CanUseAbility" bereits definiert
|
|||
|
|
warning LNK4006: "UpdateCooldowns" bereits definiert
|
|||
|
|
warning LNK4006: "UpdateBuffs" bereits definiert
|
|||
|
|
warning LNK4006: "UpdateRotation" bereits definiert
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Current Class Hierarchy
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ClassAI (base)
|
|||
|
|
├── DeathKnightAI
|
|||
|
|
│ ├── BloodSpecialization (862 lines)
|
|||
|
|
│ ├── FrostSpecialization
|
|||
|
|
│ └── UnholySpecialization
|
|||
|
|
├── PaladinAI
|
|||
|
|
│ ├── HolySpecialization
|
|||
|
|
│ ├── ProtectionSpecialization
|
|||
|
|
│ └── RetributionSpecialization (433 lines)
|
|||
|
|
├── WarriorAI
|
|||
|
|
│ ├── ArmsSpecialization (620 lines)
|
|||
|
|
│ ├── FurySpecialization
|
|||
|
|
│ └── ProtectionSpecialization
|
|||
|
|
├── MageAI
|
|||
|
|
│ ├── ArcaneSpecialization
|
|||
|
|
│ ├── FireSpecialization (622 lines)
|
|||
|
|
│ └── FrostSpecialization
|
|||
|
|
├── (9 more classes with 3+ specs each)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Total Specs:** ~40 specializations
|
|||
|
|
**Estimated Duplicated Code:** 60-70% of each spec is duplicated boilerplate
|
|||
|
|
|
|||
|
|
## Proposed Refactoring Strategy
|
|||
|
|
|
|||
|
|
### Phase 1: Create Base Template System
|
|||
|
|
|
|||
|
|
#### New Base Class: `CombatSpecializationBase`
|
|||
|
|
```cpp
|
|||
|
|
template<typename ResourceType>
|
|||
|
|
class CombatSpecializationBase : public ClassAI
|
|||
|
|
{
|
|||
|
|
protected:
|
|||
|
|
// ===== COMMON COOLDOWN MANAGEMENT =====
|
|||
|
|
std::unordered_map<uint32, uint32> _cooldowns;
|
|||
|
|
|
|||
|
|
virtual void UpdateCooldowns(uint32 diff) final
|
|||
|
|
{
|
|||
|
|
for (auto& cooldown : _cooldowns)
|
|||
|
|
if (cooldown.second > diff)
|
|||
|
|
cooldown.second -= diff;
|
|||
|
|
else
|
|||
|
|
cooldown.second = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool IsCooldownReady(uint32 spellId) const
|
|||
|
|
{
|
|||
|
|
auto it = _cooldowns.find(spellId);
|
|||
|
|
return it == _cooldowns.end() || it->second == 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void StartCooldown(uint32 spellId, uint32 duration)
|
|||
|
|
{
|
|||
|
|
_cooldowns[spellId] = duration;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== COMMON RESOURCE MANAGEMENT =====
|
|||
|
|
ResourceType _currentResource;
|
|||
|
|
ResourceType _maxResource;
|
|||
|
|
|
|||
|
|
virtual bool HasEnoughResource(uint32 spellId)
|
|||
|
|
{
|
|||
|
|
ResourceType cost = GetResourceCost(spellId);
|
|||
|
|
return _currentResource >= cost;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
virtual void ConsumeResource(uint32 spellId)
|
|||
|
|
{
|
|||
|
|
ResourceType cost = GetResourceCost(spellId);
|
|||
|
|
_currentResource -= cost;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== COMMON ABILITY VALIDATION =====
|
|||
|
|
virtual bool CanUseAbility(uint32 spellId) final
|
|||
|
|
{
|
|||
|
|
return IsCooldownReady(spellId) &&
|
|||
|
|
HasEnoughResource(spellId) &&
|
|||
|
|
IsSpellUsable(spellId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== COMMON COMBAT STATE =====
|
|||
|
|
virtual void OnCombatStart(::Unit* target) override
|
|||
|
|
{
|
|||
|
|
_inCombat = true;
|
|||
|
|
_combatTarget = target;
|
|||
|
|
OnSpecializationCombatStart(target);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
virtual void OnCombatEnd() override
|
|||
|
|
{
|
|||
|
|
_inCombat = false;
|
|||
|
|
_combatTarget = nullptr;
|
|||
|
|
OnSpecializationCombatEnd();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== HOOKS FOR SPECIALIZATION-SPECIFIC LOGIC =====
|
|||
|
|
virtual void OnSpecializationCombatStart(::Unit* target) {}
|
|||
|
|
virtual void OnSpecializationCombatEnd() {}
|
|||
|
|
virtual ResourceType GetResourceCost(uint32 spellId) = 0;
|
|||
|
|
virtual void UpdateRotation(::Unit* target) = 0;
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Specialized Resource Types
|
|||
|
|
```cpp
|
|||
|
|
// For Mana-based classes (Mage, Priest, Paladin, etc)
|
|||
|
|
using ManaSpecialization = CombatSpecializationBase<uint32>;
|
|||
|
|
|
|||
|
|
// For Rage-based classes (Warrior)
|
|||
|
|
using RageSpecialization = CombatSpecializationBase<uint32>;
|
|||
|
|
|
|||
|
|
// For Energy-based classes (Rogue, Monk)
|
|||
|
|
using EnergySpecialization = CombatSpecializationBase<uint32>;
|
|||
|
|
|
|||
|
|
// For Rune-based classes (Death Knight)
|
|||
|
|
struct RuneResource { /* complex rune management */ };
|
|||
|
|
using RuneSpecialization = CombatSpecializationBase<RuneResource>;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Role-Based Templates
|
|||
|
|
```cpp
|
|||
|
|
// Melee DPS template
|
|||
|
|
template<typename ResourceType>
|
|||
|
|
class MeleeDpsSpecialization : public CombatSpecializationBase<ResourceType>
|
|||
|
|
{
|
|||
|
|
public:
|
|||
|
|
float GetOptimalRange(::Unit* target) override { return 5.0f; }
|
|||
|
|
|
|||
|
|
protected:
|
|||
|
|
bool IsInMeleeRange(::Unit* target) const
|
|||
|
|
{
|
|||
|
|
return GetBot()->GetDistance(target) <= 5.0f;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Ranged DPS template
|
|||
|
|
template<typename ResourceType>
|
|||
|
|
class RangedDpsSpecialization : public CombatSpecializationBase<ResourceType>
|
|||
|
|
{
|
|||
|
|
public:
|
|||
|
|
float GetOptimalRange(::Unit* target) override { return 25.0f; }
|
|||
|
|
|
|||
|
|
protected:
|
|||
|
|
bool ShouldMaintainDistance(::Unit* target) const
|
|||
|
|
{
|
|||
|
|
return GetBot()->GetDistance(target) < 15.0f;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Healer template
|
|||
|
|
template<typename ResourceType>
|
|||
|
|
class HealerSpecialization : public CombatSpecializationBase<ResourceType>
|
|||
|
|
{
|
|||
|
|
public:
|
|||
|
|
float GetOptimalRange(::Unit* target) override { return 30.0f; }
|
|||
|
|
|
|||
|
|
protected:
|
|||
|
|
::Unit* GetBestHealTarget()
|
|||
|
|
{
|
|||
|
|
// Common heal target selection logic
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Tank template
|
|||
|
|
template<typename ResourceType>
|
|||
|
|
class TankSpecialization : public CombatSpecializationBase<ResourceType>
|
|||
|
|
{
|
|||
|
|
public:
|
|||
|
|
float GetOptimalRange(::Unit* target) override { return 5.0f; }
|
|||
|
|
|
|||
|
|
protected:
|
|||
|
|
void ManageThreat(::Unit* target)
|
|||
|
|
{
|
|||
|
|
// Common threat management
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 2: Refactor Each Specialization
|
|||
|
|
|
|||
|
|
#### Before (433 lines - RetributionSpecialization)
|
|||
|
|
```cpp
|
|||
|
|
class RetributionSpecialization : public PaladinSpecialization
|
|||
|
|
{
|
|||
|
|
public:
|
|||
|
|
void UpdateCooldowns(uint32 diff) { /* 10 lines */ }
|
|||
|
|
bool CanUseAbility(uint32 spellId) { /* 8 lines */ }
|
|||
|
|
void OnCombatStart(::Unit* target) { /* 12 lines */ }
|
|||
|
|
void OnCombatEnd() { /* 8 lines */ }
|
|||
|
|
float GetOptimalRange(::Unit* target) { return 5.0f; }
|
|||
|
|
// ... 390 more lines of actual rotation logic
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### After (~250 lines - 42% reduction)
|
|||
|
|
```cpp
|
|||
|
|
class RetributionSpecialization : public MeleeDpsSpecialization<uint32>
|
|||
|
|
{
|
|||
|
|
public:
|
|||
|
|
RetributionSpecialization(Player* bot)
|
|||
|
|
: MeleeDpsSpecialization(bot)
|
|||
|
|
{
|
|||
|
|
// Specialization-specific initialization
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void UpdateRotation(::Unit* target) override
|
|||
|
|
{
|
|||
|
|
// ONLY the unique Retribution logic
|
|||
|
|
if (ShouldCastTemplarsVerdict(target))
|
|||
|
|
CastTemplarsVerdict(target);
|
|||
|
|
else if (ShouldCastCrusaderStrike(target))
|
|||
|
|
CastCrusaderStrike(target);
|
|||
|
|
// ... only rotation logic, no boilerplate
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected:
|
|||
|
|
uint32 GetResourceCost(uint32 spellId) override
|
|||
|
|
{
|
|||
|
|
// Mana cost calculation
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
// ONLY Retribution-specific state
|
|||
|
|
uint8 _holyPower;
|
|||
|
|
bool _hasArtOfWar;
|
|||
|
|
|
|||
|
|
// ONLY Retribution-specific methods
|
|||
|
|
bool ShouldCastTemplarsVerdict(::Unit* target);
|
|||
|
|
void CastTemplarsVerdict(::Unit* target);
|
|||
|
|
bool ShouldCastCrusaderStrike(::Unit* target);
|
|||
|
|
void CastCrusaderStrike(::Unit* target);
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Expected Benefits
|
|||
|
|
|
|||
|
|
### Code Reduction
|
|||
|
|
- **Before:** ~40 specs × 400 lines avg = 16,000 lines
|
|||
|
|
- **After:** 40 specs × 200 lines avg = 8,000 lines
|
|||
|
|
- **Reduction:** 50% less code to maintain
|
|||
|
|
|
|||
|
|
### Eliminated Duplication
|
|||
|
|
- Remove 1,740 duplicate method implementations
|
|||
|
|
- Eliminate all linker warnings
|
|||
|
|
- Single source of truth for common logic
|
|||
|
|
|
|||
|
|
### Maintenance Improvements
|
|||
|
|
- Fix bugs in ONE place instead of 40+
|
|||
|
|
- Add features to base class, all specs benefit
|
|||
|
|
- Consistent behavior across all classes
|
|||
|
|
|
|||
|
|
### Performance Improvements
|
|||
|
|
- Smaller binary size
|
|||
|
|
- Better CPU cache utilization
|
|||
|
|
- Reduced memory footprint
|
|||
|
|
|
|||
|
|
## Implementation Plan
|
|||
|
|
|
|||
|
|
### Week 1: Foundation
|
|||
|
|
- **Day 1-2:** Create `CombatSpecializationBase` template
|
|||
|
|
- **Day 3:** Create role-based templates (Melee, Ranged, Healer, Tank)
|
|||
|
|
- **Day 4:** Create resource-type specializations
|
|||
|
|
- **Day 5:** Test compilation, ensure no regressions
|
|||
|
|
|
|||
|
|
### Week 2: First Wave (Simpler Classes)
|
|||
|
|
- **Day 1:** Refactor Warriors (3 specs)
|
|||
|
|
- **Day 2:** Refactor Paladins (3 specs)
|
|||
|
|
- **Day 3:** Refactor Hunters (3 specs)
|
|||
|
|
- **Day 4:** Refactor Rogues (3 specs)
|
|||
|
|
- **Day 5:** Test all 12 specs in combat
|
|||
|
|
|
|||
|
|
### Week 3: Second Wave (Complex Classes)
|
|||
|
|
- **Day 1:** Refactor Death Knights (3 specs)
|
|||
|
|
- **Day 2:** Refactor Mages (3 specs)
|
|||
|
|
- **Day 3:** Refactor Priests (3 specs)
|
|||
|
|
- **Day 4:** Refactor Druids (4 specs)
|
|||
|
|
- **Day 5:** Test all 13 specs in combat
|
|||
|
|
|
|||
|
|
### Week 4: Final Wave + Testing
|
|||
|
|
- **Day 1:** Refactor Warlocks (3 specs)
|
|||
|
|
- **Day 2:** Refactor Shamans (3 specs)
|
|||
|
|
- **Day 3:** Refactor Monks (3 specs)
|
|||
|
|
- **Day 4:** Refactor Demon Hunters + Evokers
|
|||
|
|
- **Day 5:** Full regression testing with all 40 specs
|
|||
|
|
|
|||
|
|
## Risk Mitigation
|
|||
|
|
|
|||
|
|
### Backup Strategy
|
|||
|
|
- Keep original files as `.cpp.backup`
|
|||
|
|
- Branch: `refactor/classai-templates`
|
|||
|
|
- Commit after each class refactored
|
|||
|
|
|
|||
|
|
### Testing Strategy
|
|||
|
|
- Test EACH class after refactoring
|
|||
|
|
- Verify combat works for all 3 specs
|
|||
|
|
- Check memory usage hasn't increased
|
|||
|
|
- Confirm no new crashes
|
|||
|
|
|
|||
|
|
### Rollback Plan
|
|||
|
|
If refactoring causes issues:
|
|||
|
|
1. Revert to backup files
|
|||
|
|
2. Cherry-pick working classes
|
|||
|
|
3. Leave problematic classes unreffactored temporarily
|
|||
|
|
|
|||
|
|
## Success Criteria
|
|||
|
|
|
|||
|
|
✅ All linker warnings eliminated
|
|||
|
|
✅ 50%+ code reduction
|
|||
|
|
✅ All 40 specs compile cleanly
|
|||
|
|
✅ All specs function in combat
|
|||
|
|
✅ No performance regression
|
|||
|
|
✅ No memory increase
|
|||
|
|
|
|||
|
|
## Conclusion
|
|||
|
|
|
|||
|
|
This refactoring will:
|
|||
|
|
- Cut maintenance burden in HALF
|
|||
|
|
- Eliminate technical debt
|
|||
|
|
- Make future features easier to add
|
|||
|
|
- Improve code quality significantly
|
|||
|
|
- Pay dividends for ALL future development
|
|||
|
|
|
|||
|
|
**Recommendation:** Proceed with refactoring immediately before adding more features.
|