eq2go/internal/spells/spell_targeting.go

443 lines
13 KiB
Go

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]interface{} {
info := make(map[string]interface{})
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
}