443 lines
13 KiB
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
|
|
spellData := luaSpell.Spell.GetSpellData()
|
|
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
|
|
}
|