/// <summary> /// Handles the case where the current visual threat is a Visual_light. /// </summary> /// <returns>A new state or NONE if nothing needed to be handled</returns> private AiStateType HandleVisualLightThreats() { AiStateType state = AiStateType.None; AiThreatManager manager = zombieStateMachine.ThreatManager; AiTarget visualThreat = zombieStateMachine.ThreatManager.CurrentVisualThreat; // and we currently have a lower priority target then drop into alerted // mode and try to find source of light if (manager.IsTargeting(AiTargetType.Audio) || manager.IsTargeting(AiTargetType.Visual_Food)) { state = AiStateType.Alerted; } else if (manager.IsTargeting(AiTargetType.Visual_Light)) { // Get unique ID of the collider of our target int currentID = zombieStateMachine.ThreatManager.CurrentTarget.GetColliderID(); // If this is the same light if (currentID == visualThreat.GetColliderID()) { RepathToThreatIfNecessary(visualThreat); state = AiStateType.Pursuit; } else { state = AiStateType.Alerted; } } manager.TrackTarget(visualThreat); return(state); }
/// <summary> /// Handles the case where the current Audio AiTarget contains a current Audio Threat. /// </summary> /// <returns>A new state or NONE if nothing needed to be handled</returns> private AiStateType HandleAudioThreat() { AiStateType state = AiStateType.None; AiThreatManager manager = zombieStateMachine.ThreatManager; AiTarget audioThreat = zombieStateMachine.ThreatManager.CurrentAudioThreat; if (manager.IsTargeting(AiTargetType.Visual_Food)) { state = AiStateType.Alerted; } else if (manager.IsTargeting(AiTargetType.Audio)) { // Get unique ID of the collider of our target int currentID = zombieStateMachine.ThreatManager.CurrentTarget.GetColliderID(); // If this is the same light if (currentID == zombieStateMachine.ThreatManager.CurrentAudioThreat.GetColliderID()) { RepathToThreatIfNecessary(audioThreat); state = AiStateType.Pursuit; } else { state = AiStateType.Alerted; } } manager.TrackTarget(audioThreat); return(state); }
/// <summary> /// Indicates whether or not to perform the expensive repath to the target. We want to repath less /// frequently as we get closer to the target. Thus, need to calculate distance to the target and /// clamp it to our min/max times. /// </summary> /// <returns>true if we should repath to the target's location</returns> private bool ShouldRepath(AiTarget threat) { return(Mathf.Clamp( threat.Distance * this.repathDistanceMultiplier, this.repathVisualMinDuration, this.repathVisualMaxDuration ) < this.repathTimer); }
/// <summary> /// Indicates whether or not the zombie is hungry enough and can satisfy that hunger because the given target is within range. /// </summary> /// <param name="target">Should represent a visual food target.</param> /// <returns>True if the zombie is hungry enough and can reach the target.</returns> public bool CanHungerBeSatisfied(AiTarget target) { if (target.Type != AiTargetType.Visual_Food) { Debug.LogWarning("Received invalid target when checking to see if hunger could be satisfied: " + target.ToString()); return(false); } return((MAX_SATISFACTION - Satisfaction) > (target.Distance / Sensor.WorldRadius)); }
/// <summary> /// Performs the repath to the given threat if necessary. /// </summary> /// <param name="threat">The threat to repath to.</param> private void RepathToThreatIfNecessary(AiTarget threat) { // The position must be different different - maybe same threat but it has moved so repath periodically // and our repath timer must be ready to repath so we can save on cpu cycles if (zombieStateMachine.ThreatManager.CurrentTarget.Position != threat.Position && ShouldRepath(threat)) { zombieStateMachine.NavAgent.SetDestination(threat.Position); ResetRepathTimer(); } }
/// <summary> /// Called by the state machine each frame. /// </summary> /// <returns>Either the current state or a new state.</returns> public override AiStateType OnUpdate() { UpdateTimers(); if (HasReachedMaxTime()) { // reset the waypoint but stay in the alerted state with a fresh timer zombieStateMachine.WaypointManager.TrackWayPoint(); ResetMaxDurationTimer(); } AiTarget? potentialThreat = zombieStateMachine.ThreatManager.DeterminePotentialThreat(); AiStateType state = zombieStateMachine.ThreatManager.DetermineNextPotentialThreatState(potentialThreat); if (state == AiStateType.None) { state = GetDefaultStateType(); } else { AiTarget newThreat = (AiTarget)potentialThreat; if (newThreat.Type == AiTargetType.Visual_Player) { zombieStateMachine.ThreatManager.TrackTarget(newThreat); } else { if (newThreat.Type == AiTargetType.Audio || newThreat.Type == AiTargetType.Visual_Light) { zombieStateMachine.ThreatManager.TrackTarget(newThreat); ResetMaxDurationTimer(); } if (newThreat.Type == AiTargetType.Visual_Food) { if (zombieStateMachine.ThreatManager.DoesTargetExist()) { state = GetDefaultStateType(); // food is less of a priority, so reset back to alerted state (audio or light) } else { zombieStateMachine.ThreatManager.TrackTarget(newThreat); } } } } if (state == GetDefaultStateType()) { state = HandleDefaultState(); } return(state); }
private void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Effect damageEffect = null) { if (prefab.sprite == null) { return; } if (editing) { if (!HasBody && !ShowStructures) { return; } if (HasBody && !ShowWalls) { return; } } Color color = IsHighlighted ? Color.Orange : spriteColor; if (IsSelected && editing) { //color = Color.Lerp(color, Color.Gold, 0.5f); color = spriteColor; Vector2 rectSize = rect.Size.ToVector2(); if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; } if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; } Vector2 bodyPos = WorldPosition + BodyOffset; GUI.DrawRectangle(spriteBatch, new Vector2(bodyPos.X, -bodyPos.Y), rectSize.X, rectSize.Y, BodyRotation, Color.White, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom))); } Vector2 drawOffset = Submarine == null ? Vector2.Zero : Submarine.DrawPosition; float depth = SpriteDepthOverrideIsSet ? SpriteOverrideDepth : prefab.sprite.Depth; depth -= (ID % 255) * 0.000001f; Vector2 textureOffset = this.textureOffset; if (FlippedX) { textureOffset.X = -textureOffset.X; } if (FlippedY) { textureOffset.Y = -textureOffset.Y; } if (back && damageEffect == null) { if (Prefab.BackgroundSprite != null) { Vector2 dropShadowOffset = Vector2.Zero; if (UseDropShadow) { dropShadowOffset = DropShadowOffset; if (dropShadowOffset == Vector2.Zero) { if (Submarine == null) { dropShadowOffset = Vector2.UnitY * 10.0f; } else { dropShadowOffset = IsHorizontal ? new Vector2(0.0f, Math.Sign(Submarine.HiddenSubPosition.Y - Position.Y) * 10.0f) : new Vector2(Math.Sign(Submarine.HiddenSubPosition.X - Position.X) * 10.0f, 0.0f); } } dropShadowOffset.Y = -dropShadowOffset.Y; } SpriteEffects oldEffects = Prefab.BackgroundSprite.effects; Prefab.BackgroundSprite.effects ^= SpriteEffects; Point backGroundOffset = new Point( MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width), MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height)); Prefab.BackgroundSprite.DrawTiled( spriteBatch, new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)), new Vector2(rect.Width, rect.Height), color: color, textureScale: TextureScale * Scale, startOffset: backGroundOffset, depth: Math.Max(Prefab.BackgroundSprite.Depth, depth + 0.000001f)); if (UseDropShadow) { Prefab.BackgroundSprite.DrawTiled( spriteBatch, new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)) + dropShadowOffset, new Vector2(rect.Width, rect.Height), color: Color.Black * 0.5f, textureScale: TextureScale * Scale, startOffset: backGroundOffset, depth: (depth + Prefab.BackgroundSprite.Depth) / 2.0f); } Prefab.BackgroundSprite.effects = oldEffects; } } if (back == depth > 0.5f) { SpriteEffects oldEffects = prefab.sprite.effects; prefab.sprite.effects ^= SpriteEffects; for (int i = 0; i < Sections.Length; i++) { if (damageEffect != null) { float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / Prefab.Health); if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor) { damageEffect.Parameters["aCutoff"].SetValue(newCutoff); damageEffect.Parameters["cCutoff"].SetValue(newCutoff * 1.2f); damageEffect.Parameters["inColor"].SetValue(color.ToVector4()); damageEffect.CurrentTechnique.Passes[0].Apply(); Submarine.DamageEffectCutoff = newCutoff; Submarine.DamageEffectColor = color; } } Point sectionOffset = new Point( Math.Abs(rect.Location.X - Sections[i].rect.Location.X), Math.Abs(rect.Location.Y - Sections[i].rect.Location.Y)); if (FlippedX && IsHorizontal) { sectionOffset.X = Sections[i].rect.Right - rect.Right; } if (FlippedY && !IsHorizontal) { sectionOffset.Y = (rect.Y - rect.Height) - (Sections[i].rect.Y - Sections[i].rect.Height); } sectionOffset.X += MathUtils.PositiveModulo((int)-textureOffset.X, prefab.sprite.SourceRect.Width); sectionOffset.Y += MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.sprite.SourceRect.Height); prefab.sprite.DrawTiled( spriteBatch, new Vector2(Sections[i].rect.X + drawOffset.X, -(Sections[i].rect.Y + drawOffset.Y)), new Vector2(Sections[i].rect.Width, Sections[i].rect.Height), color: color, startOffset: sectionOffset, depth: depth, textureScale: TextureScale * Scale); } prefab.sprite.effects = oldEffects; } if (GameMain.DebugDraw) { if (Bodies != null) { for (int i = 0; i < Bodies.Count; i++) { Vector2 pos = FarseerPhysics.ConvertUnits.ToDisplayUnits(Bodies[i].Position); if (Submarine != null) { pos += Submarine.Position; } pos.Y = -pos.Y; GUI.DrawRectangle(spriteBatch, pos, FarseerPhysics.ConvertUnits.ToDisplayUnits(bodyDebugDimensions[i].X), FarseerPhysics.ConvertUnits.ToDisplayUnits(bodyDebugDimensions[i].Y), -Bodies[i].Rotation, Color.White); } } if (SectionCount > 0 && HasBody) { for (int i = 0; i < SectionCount; i++) { if (GetSection(i).damage > 0) { var textPos = SectionPosition(i, true); textPos.Y = -textPos.Y; GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / Health) * 100f) + "%", Color.Yellow); } } } AiTarget?.Draw(spriteBatch); } }
/// <summary> /// Indicates whether or not the distance to the given threat is smaller then anything we may have /// previously stored /// </summary> /// <param name="threat">The threat to test</param> /// <param name="distanceToThreat">The pre-computed distance to the given threat</param> /// <returns>true if a previous threat existed and zombie is now closer to it.</returns> private bool IsCloserThanLastThreat(AiTarget threat, float distanceToThreat) { AiThreatManager manager = zombieStateMachine.ThreatManager; return(threat.Type == AiTargetType.None || distanceToThreat < threat.Distance); }
/// <summary> /// Tracks the target using the values of the given target. /// </summary> public void TrackTarget(AiTarget target) { this.TrackTarget(target.Type, target.Collider, target.Position, target.Distance, this.stateMachine.StoppingDistance); }