package spells import ( "fmt" "sync" ) // SpellTargeting handles spell target selection and validation type SpellTargeting struct { // TODO: Add references to zone and entity systems when available // zoneServer *ZoneServer // entityManager *EntityManager mutex sync.RWMutex } // TargetingResult represents the result of spell targeting type TargetingResult struct { ValidTargets []int32 // List of valid target IDs InvalidTargets []int32 // List of invalid target IDs ErrorCode int32 // Error code if targeting failed ErrorMessage string // Human readable error message } // TargetingOptions contains options for target selection type TargetingOptions struct { BypassSpellChecks bool // Skip spell-specific target validation BypassRangeChecks bool // Skip range validation MaxRange float32 // Override maximum range IgnoreLineOfSight bool // Ignore line of sight requirements } // NewSpellTargeting creates a new spell targeting system func NewSpellTargeting() *SpellTargeting { return &SpellTargeting{} } // GetSpellTargets finds all valid targets for a spell and adds them to the LuaSpell // This is the main targeting function converted from C++ SpellProcess::GetSpellTargets func (st *SpellTargeting) GetSpellTargets(luaSpell *LuaSpell, options *TargetingOptions) *TargetingResult { if luaSpell == nil || luaSpell.Spell == nil { return &TargetingResult{ ValidTargets: make([]int32, 0), InvalidTargets: make([]int32, 0), ErrorCode: FailureReasonInvalidTarget, ErrorMessage: "Invalid spell or LuaSpell", } } spell := luaSpell.Spell spellData := spell.GetSpellData() if spellData == nil { return &TargetingResult{ ValidTargets: make([]int32, 0), InvalidTargets: make([]int32, 0), ErrorCode: FailureReasonInvalidTarget, ErrorMessage: "Invalid spell data", } } // Default options if none provided if options == nil { options = &TargetingOptions{} } result := &TargetingResult{ ValidTargets: make([]int32, 0), InvalidTargets: make([]int32, 0), ErrorCode: 0, ErrorMessage: "", } // Determine targeting method based on spell target type switch spellData.TargetType { case TargetTypeSelf: st.getSelfTargets(luaSpell, result, options) case TargetTypeSingle: st.getSingleTarget(luaSpell, result, options) case TargetTypeGroup: st.getGroupTargets(luaSpell, result, options) case TargetTypeGroupAE: st.getGroupAETargets(luaSpell, result, options) case TargetTypeAE: st.getAETargets(luaSpell, result, options) case TargetTypePBAE: st.getPBAETargets(luaSpell, result, options) default: result.ErrorCode = FailureReasonInvalidTarget result.ErrorMessage = fmt.Sprintf("Unsupported target type: %d", spellData.TargetType) } return result } // getSelfTargets handles self-targeting spells func (st *SpellTargeting) getSelfTargets(luaSpell *LuaSpell, result *TargetingResult, options *TargetingOptions) { if luaSpell.CasterID != 0 { result.ValidTargets = append(result.ValidTargets, luaSpell.CasterID) luaSpell.AddTarget(luaSpell.CasterID) } else { result.ErrorCode = FailureReasonInvalidTarget result.ErrorMessage = "No valid caster for self-targeting spell" } } // getSingleTarget handles single-target spells func (st *SpellTargeting) getSingleTarget(luaSpell *LuaSpell, result *TargetingResult, options *TargetingOptions) { targetID := luaSpell.InitialTarget if targetID == 0 { targetID = luaSpell.CasterID // Default to self if no target } if st.isValidTarget(luaSpell, targetID, options) { result.ValidTargets = append(result.ValidTargets, targetID) luaSpell.AddTarget(targetID) } else { result.InvalidTargets = append(result.InvalidTargets, targetID) result.ErrorCode = FailureReasonInvalidTarget result.ErrorMessage = "Target is not valid for this spell" } } // getGroupTargets handles group-targeting spells func (st *SpellTargeting) getGroupTargets(luaSpell *LuaSpell, result *TargetingResult, options *TargetingOptions) { // TODO: Implement group targeting when group system is available // This would: // 1. Get the caster's group // 2. Iterate through group members // 3. Validate each member as a target // 4. Add valid members to the target list // For now, just target self as placeholder if luaSpell.CasterID != 0 { result.ValidTargets = append(result.ValidTargets, luaSpell.CasterID) luaSpell.AddTarget(luaSpell.CasterID) } } // getGroupAETargets handles group area effect spells func (st *SpellTargeting) getGroupAETargets(luaSpell *LuaSpell, result *TargetingResult, options *TargetingOptions) { // TODO: Implement group AE targeting // This is similar to group targeting but may include pets and other considerations st.getGroupTargets(luaSpell, result, options) } // getAETargets handles area effect spells (true AOE) func (st *SpellTargeting) getAETargets(luaSpell *LuaSpell, result *TargetingResult, options *TargetingOptions) { // TODO: Implement AOE targeting when zone system is available // This would: // 1. Get the spell's radius from spell data // 2. Find all entities within radius of the target location // 3. Filter based on spell criteria (friendly/hostile, etc.) // 4. Apply maximum target limits // 5. Add valid targets to the list // For now, implement basic logic _ = luaSpell.Spell.GetSpellData() // TODO: Use spell data when needed maxTargets := int32(10) // TODO: Use spellData.AOENodeNumber when field exists if maxTargets <= 0 { maxTargets = 10 // Default limit } // Placeholder: just target the initial target for now if luaSpell.InitialTarget != 0 && st.isValidTarget(luaSpell, luaSpell.InitialTarget, options) { result.ValidTargets = append(result.ValidTargets, luaSpell.InitialTarget) luaSpell.AddTarget(luaSpell.InitialTarget) } // TODO: Use actual AOE node number when SpellData is updated _ = maxTargets // Suppress unused variable warning } // getPBAETargets handles point-blank area effect spells (centered on caster) func (st *SpellTargeting) getPBAETargets(luaSpell *LuaSpell, result *TargetingResult, options *TargetingOptions) { // TODO: Implement PBAE targeting when zone system is available // This is similar to AE but centered on the caster instead of a target location // For now, just target self if luaSpell.CasterID != 0 { result.ValidTargets = append(result.ValidTargets, luaSpell.CasterID) luaSpell.AddTarget(luaSpell.CasterID) } } // isValidTarget validates whether a target is valid for a spell func (st *SpellTargeting) isValidTarget(luaSpell *LuaSpell, targetID int32, options *TargetingOptions) bool { if targetID == 0 { return false } if options.BypassSpellChecks { return true } // TODO: Implement comprehensive target validation when entity system is available // This would check: // 1. Target exists and is alive/dead as required // 2. Target is within range // 3. Line of sight if required // 4. Spell can affect target type (player/NPC/etc.) // 5. Faction/relationship requirements // 6. Target is not immune to spell effects // 7. Spell-specific requirements return true // Placeholder - accept all targets for now } // ValidateRange checks if a target is within spell range func (st *SpellTargeting) ValidateRange(casterID, targetID int32, spell *Spell, options *TargetingOptions) bool { if options != nil && options.BypassRangeChecks { return true } // TODO: Implement range checking when position system is available // This would: // 1. Get caster position // 2. Get target position // 3. Calculate distance // 4. Compare to spell range return true // Placeholder - all targets in range for now } // ValidateLineOfSight checks if caster has line of sight to target func (st *SpellTargeting) ValidateLineOfSight(casterID, targetID int32, options *TargetingOptions) bool { if options != nil && options.IgnoreLineOfSight { return true } // TODO: Implement line of sight checking when zone geometry is available // This would use ray tracing to check for obstacles between caster and target return true // Placeholder - always have LOS for now } // GetPlayerGroupTargets gets valid group member targets for a spell // This is converted from C++ SpellProcess::GetPlayerGroupTargets func (st *SpellTargeting) GetPlayerGroupTargets(targetPlayerID, casterID int32, luaSpell *LuaSpell, options *TargetingOptions) bool { // TODO: Implement when group system is available // This would: // 1. Get the target player's group // 2. Validate caster can target this group // 3. Add all valid group members as targets // 4. Handle pets if included in group targeting // For now, just add the target player if st.isValidTarget(luaSpell, targetPlayerID, options) { luaSpell.AddTarget(targetPlayerID) return true } return false } // AddSelfAndPet adds caster and their pet to spell targets func (st *SpellTargeting) AddSelfAndPet(luaSpell *LuaSpell, casterID int32, onlyPet bool) { if !onlyPet && casterID != 0 { luaSpell.AddTarget(casterID) } // TODO: Add pet targeting when pet system is available // This would: // 1. Get caster's active pet // 2. Validate pet as target // 3. Add pet to target list } // AddNPCGroupOrSelfTarget adds NPC group members or self to targets func (st *SpellTargeting) AddNPCGroupOrSelfTarget(luaSpell *LuaSpell, targetID int32) { // TODO: Implement NPC group targeting when NPC AI system is available // NPCs may have different grouping mechanics than players // For now, just add the target if targetID != 0 { luaSpell.AddTarget(targetID) } } // CalculateDistance calculates distance between two entities func (st *SpellTargeting) CalculateDistance(entity1ID, entity2ID int32) float32 { // TODO: Implement when position system is available // This would get positions and calculate 3D distance return 0.0 // Placeholder } // IsInRange checks if target is within spell range func (st *SpellTargeting) IsInRange(spell *Spell, distance float32) bool { if spell == nil { return false } spellData := spell.GetSpellData() if spellData == nil { return false } maxRange := spellData.Range if maxRange <= 0 { return true // No range limit } return distance <= maxRange } // GetSpellRange returns the maximum range for a spell func (st *SpellTargeting) GetSpellRange(spell *Spell) float32 { if spell == nil { return 0.0 } spellData := spell.GetSpellData() if spellData == nil { return 0.0 } return spellData.Range } // GetSpellRadius returns the area effect radius for a spell func (st *SpellTargeting) GetSpellRadius(spell *Spell) float32 { if spell == nil { return 0.0 } spellData := spell.GetSpellData() if spellData == nil { return 0.0 } return spellData.Radius } // GetMaxTargets returns the maximum number of targets for an AOE spell func (st *SpellTargeting) GetMaxTargets(spell *Spell) int32 { if spell == nil { return 1 } spellData := spell.GetSpellData() if spellData == nil { return 1 } // TODO: Use spellData.AOENodeNumber when field exists maxTargets := int32(1) // Default to single target return maxTargets } // IsHostileSpell determines if a spell is hostile/aggressive func (st *SpellTargeting) IsHostileSpell(spell *Spell) bool { if spell == nil { return false } // Check if spell is classified as offensive return spell.IsOffenseSpell() } // IsFriendlySpell determines if a spell is beneficial/friendly func (st *SpellTargeting) IsFriendlySpell(spell *Spell) bool { if spell == nil { return false } // Check if spell is classified as beneficial return spell.IsBuffSpell() || spell.IsHealSpell() } // CanTargetSelf checks if spell can target the caster func (st *SpellTargeting) CanTargetSelf(spell *Spell) bool { if spell == nil { return false } spellData := spell.GetSpellData() if spellData == nil { return false } // Check target type and spell flags return spellData.TargetType == TargetTypeSelf || spellData.TargetType == TargetTypeGroup || spellData.TargetType == TargetTypePBAE } // CanTargetOthers checks if spell can target entities other than caster func (st *SpellTargeting) CanTargetOthers(spell *Spell) bool { if spell == nil { return false } spellData := spell.GetSpellData() if spellData == nil { return false } // Most target types can target others, self-only spells cannot return spellData.TargetType != TargetTypeSelf } // RequiresTarget checks if spell requires an explicit target selection func (st *SpellTargeting) RequiresTarget(spell *Spell) bool { if spell == nil { return false } spellData := spell.GetSpellData() if spellData == nil { return false } // Single target spells typically require explicit targeting return spellData.TargetType == TargetTypeSingle } // GetTargetingInfo returns information about spell targeting requirements func (st *SpellTargeting) GetTargetingInfo(spell *Spell) map[string]any { info := make(map[string]any) if spell == nil { return info } spellData := spell.GetSpellData() if spellData == nil { return info } info["target_type"] = spellData.TargetType info["range"] = spellData.Range info["radius"] = spellData.Radius info["max_targets"] = int32(1) // TODO: Use spellData.AOENodeNumber when field exists info["can_target_self"] = st.CanTargetSelf(spell) info["can_target_others"] = st.CanTargetOthers(spell) info["requires_target"] = st.RequiresTarget(spell) info["is_hostile"] = st.IsHostileSpell(spell) info["is_friendly"] = st.IsFriendlySpell(spell) return info }