private void FiringUpdate() { // If SpellTarget is out of range - switch to abort state if (Spell.RangeBehaviour == Spell.TargetRangeBehaviour.AbortWhenOutOfRange && !TargetUtility.IsInRange(Source, CastTarget, _minRange, _maxRange)) { Abort(); return; } var canStopFiring = true; for (var i = _children.Count - 1; i >= 0; i--) { var subProcessor = _children[i]; subProcessor.Update(); // Remove inactive SubSpells if (!subProcessor.IsActive) { _children.RemoveAt(i); } else if (subProcessor.SubSpell.SpellShouldWaitUntilEnd) { canStopFiring = false; } } if (canStopFiring) { _state = SpellState.Finilizing; Event?.Invoke(this, SpellEvent.FinishedFire, null); } }
// Proxy targeting for special range handling private Vector3?GetTargetLocation() { // If target is invalid, we can't update proxy if (!_originalTarget.IsValid || !_originalTarget.HasPosition) { return(null); } // If source is invalid, we can't update proxy if (!Source.IsValid || !Source.HasPosition) { return(null); } var desired = _originalTarget.Position; var source = Source.Position; var dir = new Vector3(desired.x - source.x, 0, desired.z - source.z); var distance = dir.magnitude; dir.Normalize(); switch (Spell.RangeBehaviour) { case Spell.TargetRangeBehaviour.RetargetClampToRange: desired = source + dir * Mathf.Clamp(distance, _minRange, _maxRange); break; case Spell.TargetRangeBehaviour.RetargetSetMaxRange: desired = source + dir * _maxRange; break; } // Stick to floor return(TargetUtility.AboveGround(desired)); }
public SpellHandler(Spell spell, Target source, Target castTarget, int stacks) { Assert.IsNotNull(spell, "spell != null"); Assert.IsTrue(source.IsValid, "source.IsValid"); Assert.IsTrue(castTarget.IsValid, "castTarget.IsValid"); Assert.IsTrue(castTarget.Type == spell.TargetType, "castTarget.Type == spell.TargetType"); Spell = spell; Source = source; _originalTarget = castTarget; _minRange = spell.MinRange.GetValue(Stacks); _maxRange = spell.MaxRange.GetValue(Stacks); #if DEBUG // Draw debug info if (spell.TargetType != TargetType.None) { Debugger.Default.DrawCircle(source.OffsettedPosition, Vector3.up, _minRange, Color.yellow, 1f); Debugger.Default.DrawCircle(source.OffsettedPosition, Vector3.up, _maxRange, Color.yellow, 1f); } TargetUtility.DebugDrawSourceAndTarget(source, castTarget); #endif // Create target proxy if we need to retarget if (spell.RangeBehaviour == Spell.TargetRangeBehaviour.RetargetClampToRange || spell.RangeBehaviour == Spell.TargetRangeBehaviour.RetargetSetMaxRange) { if (castTarget.Type == TargetType.Location || castTarget.Type == TargetType.LocationProvider) { CastTarget = new Target(_locationTargetProxy); } else { Debug.LogWarning($"Can't create proxy target for target of type {castTarget.Type}"); } } else { CastTarget = castTarget; } Stacks = stacks; _state = SpellState.Started; }
public ISpellHandler Cast(Spell spell, CharacterState caster, Target target, int stacks) { if (!target.IsValid) { return(null); } if (caster == null || !caster.IsAlive) { return(null); } Assert.IsNotNull(spell); if (target.Type != spell.TargetType) { Debug.LogWarning($"Can't cast {spell.name}. Invalid target, required type: {spell.TargetType}, got {target.Type}", this); return(null); } var source = new Target(caster); var minRange = spell.MinRange.GetValue(stacks); var maxRange = spell.MaxRange.GetValue(stacks); if (spell.CheckRangeOnCast && !TargetUtility.IsInRange(source, target, minRange, maxRange)) { Debug.LogWarning($"Can't cast {spell.name}. Out of range", this); return(null); } if (target.Type == TargetType.Character && !TargetUtility.IsValidTeam(caster, target.Character, spell.AffectsTeam)) { Debug.LogWarning($"Can't cast {spell.name}. Invalid team", this); return(null); } var state = new SpellHandler(spell, source, target, stacks); _activeSpellStates.Add(state); return(state); }
public SubSpellHandler( SpellHandler spellHandler, SubSpell subSpell, Target source, Target target) { Assert.IsTrue(source.IsValid); Assert.IsTrue(target.IsValid); Assert.IsNotNull(subSpell); Assert.IsNotNull(spellHandler); SubSpell = subSpell; _spellHandler = spellHandler; Source = source; Target = target; _state = SubSpellState.Started; #if DEBUG TargetUtility.DebugDrawSourceAndTarget(source, target); #endif }
private void QueryTargets(List <Target> queried, Query query, Target defaultOrigin) { queried.Clear(); var origin = ResolveTarget(query.Origin, defaultOrigin); if (!origin.IsValid) { return; } switch (query.NewTargetsQueryType) { case Query.QueryType.None: return; case Query.QueryType.OriginAsTarget: queried.Add(origin); return; case Query.QueryType.FillAoE: FillTargetsInAoe(queried, query.Area, origin); return; case Query.QueryType.RandomLocationInAoe: var randomLoc = RandomLocationInAoe(query.Area, origin); queried.Add(new Target(randomLoc, origin.Forward)); return; default: break; } // Find all characters inside AoE and put them inside buffer CharactersInAoe(QueriedCharactersBuffer, query.Area, origin); // Filter characters for (var i = QueriedCharactersBuffer.Count - 1; i >= 0; i--) { var c = QueriedCharactersBuffer[i]; // Team filtering if (!TargetUtility.IsValidTeam(SpellHandler.Source.Character, c, query.AffectsTeam)) { QueriedCharactersBuffer.RemoveAt(i); continue; } // Filter affected if (query.ExcludeAlreadyAffected && _spellHandler.AffectedCharacters != null && _spellHandler.AffectedCharacters.Contains(c)) { QueriedCharactersBuffer.RemoveAt(i); } } // If we are empty after filtering if (QueriedCharactersBuffer.Count == 0) { return; } if (query.NewTargetsQueryType == Query.QueryType.RandomTargetInAoe) { queried.Add(new Target(RandomUtils.Choice(QueriedCharactersBuffer))); return; } if (query.NewTargetsQueryType == Query.QueryType.AllTargetsInAoe) { for (var i = 0; i < QueriedCharactersBuffer.Count; i++) { queried.Add(new Target(QueriedCharactersBuffer[i])); } return; } if (query.NewTargetsQueryType == Query.QueryType.ClosestToOriginInAoe) { var minDistance = 1e8f; var minIndex = 0; var originPos = origin.Position; for (var i = 1; i < QueriedCharactersBuffer.Count; i++) { var c = QueriedCharactersBuffer[i]; var distance = Vector3.Distance(originPos, c.transform.position); if (distance < minDistance) { minIndex = i; minDistance = distance; } } queried.Add(new Target(QueriedCharactersBuffer[minIndex])); return; } }
private void FireEvent(SubSpellEvent eventType, Target defaultOrigin) { // Send non-targeted event to effect SubSpell.EffectHandler?.OnEvent(new SubSpellEventArgs(this, eventType)); for (var eventIndex = 0; eventIndex < SubSpell.FireSubSpellEvents.Length; eventIndex++) { // Filter SubSpell events var e = SubSpell.FireSubSpellEvents[eventIndex]; if (e.Type != eventType) { continue; } QueryTargets(Queried, e.Query, defaultOrigin); // Targeted event SubSpell.EffectHandler?.OnEvent(new SubSpellEventArgs(this, eventType, e.Query, Queried)); // New source of SubSpell var newSsSource = ResolveTarget(e.SubSpellSource, ResolveTarget(e.Query.Origin, defaultOrigin)); foreach (var target in Queried) { #if DEBUG TargetUtility.DebugDraw(target, Color.yellow); #endif if (target.Type == TargetType.Character) { if (e.Query.ExcludeAlreadyAffected && _spellHandler.AffectedCharacters.Contains(target.Character)) { continue; } if (e.TrackAffectedCharacters) { _spellHandler.AffectedCharacters.Add(target.Character); } if (e.ApplyBuffToTarget != null) { target.Character.ApplyBuff( e.ApplyBuffToTarget, SpellHandler.Source.Character, Stacks, this); } } // SubSpell firing if (e.FireSubSpell != null) { // Before adding SubSpell we first need to figure out what // new sources and targets will be. // Because some SubSpells overrides targeting var newTarget = ResolveTarget(e.SubSpellTarget, target); // Fire child sub spell // Queue it to the spell processor _spellHandler.CastSubSpell(e.FireSubSpell, newSsSource, newTarget); } } } }
private void Update() { if (!IsActive) { return; } // Lifetime check if (_timer > 0) { _timer -= Time.deltaTime; if (_timer <= 0) { HandleEvent(ProjectileEvents.TimeExpired, new Target(transform.position)); } } // If target become invalid (i.e. character died) - retarget to its last location if (!_target.IsValid) { _target = new Target(_lastValidTargetPosition); } // Sample new target since the target can move (i.e. character) _lastValidTargetPosition = _target.Position; var currentPosition = transform.position; // If we have reached destination than no additional movement required if (TargetUtility.XZDistance(currentPosition, _lastValidTargetPosition) < DestinationThreshold) { return; } // Calculate direction and distance var xzDir = _lastValidTargetPosition - currentPosition; xzDir.y = 0; var xzDistance = xzDir.magnitude; // XZ direction only xzDir.Normalize(); // Calculate desired position var nextPosition = currentPosition + _speed * Time.deltaTime * xzDir; _distanceTraveled += _speed * Time.deltaTime * xzDir.magnitude; // Update height by calculating relative progress using traveled and remaining distance var progress = Mathf.Clamp01(_distanceTraveled / (_distanceTraveled + xzDistance)); // And sampling height from Height curve float baseY; if (_projectile.HoverGround) { baseY = TargetUtility.AboveGround(nextPosition).y; } else { baseY = Mathf.Lerp(_spawnPoint.y, _lastValidTargetPosition.y, progress); } nextPosition.y = baseY + _projectile.HeightProfile.Evaluate(progress); // If projectile have reached the destination var targetDir = nextPosition - _lastValidTargetPosition; // Update position and rotation transform.position = nextPosition; transform.rotation = Quaternion.LookRotation(targetDir.normalized); if (TargetUtility.XZDistance(nextPosition, _lastValidTargetPosition) < DestinationThreshold) { var eventTarget = _handler.Target; // If eventTarget (destination) is not valid // Raise event with repositioned target if (!eventTarget.IsValid) { eventTarget = _target; } HandleEvent(ProjectileEvents.ReachedDestination, eventTarget); } // Distance check if (_maxDistance > 0 && _distanceTraveled > _maxDistance) { HandleEvent(ProjectileEvents.ReachedMaxDistance, new Target(transform.position)); } }
public void Initialize(SubSpellHandler handler, ProjectileData projectileData) { _projectile = projectileData; _handler = handler; // Setup RigidBody to handle collisions var body = GetComponentInChildren <Rigidbody>(); if (body == null) { body = gameObject.AddComponent <Rigidbody>(); } body.isKinematic = false; body.useGravity = false; // Bake source point if (handler.Source.Type == TargetType.Character) { _spawnPoint = handler.Source.Character.GetNodeTransform(_projectile.SpawnNode).position; } else { _spawnPoint = handler.Source.Transform.position; } if (_projectile.Type == ProjectileType.Targeted && handler.Target.IsValid) { TargetUtility.DebugDraw(handler.Target, Color.blue); // ReTargeting to specific transform of character if (handler.Target.Type == TargetType.Character) { _target = new Target(handler.Target.Character.GetNodeTransform(_projectile.TargetNode)); } else { _target = handler.Target; } } // Get initial direction if (_projectile.Type == ProjectileType.Directional) { var direction = handler.Source.Forward; // If target is specified, use it if (handler.Target.HasPosition) { direction = handler.Target.Position - _spawnPoint; direction.y = 0; direction.Normalize(); } NavMesh.Raycast(_spawnPoint, _spawnPoint + direction * 100f, out var hit, NavMesh.AllAreas); _target = new Target(hit.position); } // Resolve stacked properties _timer = projectileData.TimeToLive.GetValue(handler.Stacks); _speed = projectileData.Speed.GetValue(handler.Stacks); _maxDistance = projectileData.MaxDistance.GetValue(handler.Stacks); // Initial positioning transform.position = _spawnPoint; //transform.rotation = Quaternion.LookRotation(_direction); IsActive = true; }
private void OnTriggerEnter(Collider other) { if (!IsActive) { return; } Target eventTarget; switch (_projectile.CollisionTarget) { case ProjectileData.EventTarget.CollisionPosition: eventTarget = new Target(transform.position); break; case ProjectileData.EventTarget.OtherColliderTransform: eventTarget = new Target(other.transform); break; case ProjectileData.EventTarget.OtherColliderPosition: eventTarget = new Target(other.transform.position); break; case ProjectileData.EventTarget.OtherColliderCharacter: var otherChar = other.GetComponent <CharacterState>(); if (otherChar != null) { eventTarget = new Target(otherChar); } else { eventTarget = new Target(other.transform); } break; case ProjectileData.EventTarget.SelfPosition: eventTarget = new Target(transform.position); break; default: case ProjectileData.EventTarget.SelfTransform: eventTarget = new Target(transform); break; } var character = other.GetComponent <CharacterState>(); if (character == null) { // Collision with non-character object. HandleEvent(ProjectileEvents.CollisionWithWorld, eventTarget); } else if (character.Equals(_handler.Target.Character)) { if (TargetUtility.IsValidTeam(_handler.SpellHandler.Source.Character, character, _projectile.Affects)) { // Collision with target character object HandleEvent(ProjectileEvents.CollisionWithTarget, eventTarget); } } else { if (TargetUtility.IsValidTeam(_handler.SpellHandler.Source.Character, character, _projectile.Affects)) { // Collision with non-target character object HandleEvent(ProjectileEvents.CollisionWithOtherTargets, eventTarget); } } }