public void Update(EnemyAIController enemyAI, float deltaTime) { if (character.Submarine != null) { if (targetCharacter != null && targetCharacter.Submarine != targetSubmarine || character.Submarine != null && targetSubmarine != null && targetCharacter == null) { DeattachFromBody(reset: true); return; } } if (IsAttached) { if (Math.Sign(attachLimb.Dir) != Math.Sign(jointDir)) { var attachJoint = AttachJoints[0]; if (attachJoint is WeldJoint weldJoint) { weldJoint.LocalAnchorA = new Vector2(-weldJoint.LocalAnchorA.X, weldJoint.LocalAnchorA.Y); weldJoint.ReferenceAngle = -weldJoint.ReferenceAngle; } else if (attachJoint is RevoluteJoint revoluteJoint) { revoluteJoint.LocalAnchorA = new Vector2(-revoluteJoint.LocalAnchorA.X, revoluteJoint.LocalAnchorA.Y); revoluteJoint.ReferenceAngle = -revoluteJoint.ReferenceAngle; } jointDir = attachLimb.Dir; } for (int i = 0; i < AttachJoints.Count; i++) { //something went wrong, limb body is very far from the joint anchor -> deattach if (Vector2.DistanceSquared(AttachJoints[i].WorldAnchorB, AttachJoints[i].BodyA.Position) > 10.0f * 10.0f) { #if DEBUG DebugConsole.Log("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach"); #endif DeattachFromBody(reset: true); return; } } if (targetCharacter != null) { if (enemyAI.AttackingLimb?.attack == null) { DeattachFromBody(reset: true, cooldown: 1); } else { float range = enemyAI.AttackingLimb.attack.DamageRange * 2f; if (Vector2.DistanceSquared(targetCharacter.WorldPosition, enemyAI.AttackingLimb.WorldPosition) > range * range) { DeattachFromBody(reset: true, cooldown: 1); } } } } if (attachCooldown > 0) { attachCooldown -= deltaTime; } if (deattachCheckTimer > 0) { deattachCheckTimer -= deltaTime; } if (targetCharacter != null) { // Own sim pos -> target where we are _attachPos = character.SimPosition; } Vector2 transformedAttachPos = _attachPos; if (character.Submarine == null && targetSubmarine != null) { transformedAttachPos += ConvertUnits.ToSimUnits(targetSubmarine.Position); } if (transformedAttachPos != Vector2.Zero) { AttachPos = transformedAttachPos; } switch (enemyAI.State) { case AIState.Idle: if (AttachToWalls && character.Submarine == null && Level.Loaded != null) { if (!IsAttached) { raycastTimer -= deltaTime; //check if there are any walls nearby the character could attach to if (raycastTimer < 0.0f) { _attachPos = Vector2.Zero; var cells = Level.Loaded.GetCells(character.WorldPosition, 1); if (cells.Count > 0) { float closestDist = float.PositiveInfinity; foreach (Voronoi2.VoronoiCell cell in cells) { foreach (Voronoi2.GraphEdge edge in cell.Edges) { if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection)) { Vector2 potentialAttachPos = ConvertUnits.ToSimUnits(intersection); float distSqr = Vector2.DistanceSquared(character.SimPosition, potentialAttachPos); if (distSqr < closestDist) { attachSurfaceNormal = edge.GetNormal(cell); targetBody = cell.Body; _attachPos = potentialAttachPos; closestDist = distSqr; } break; } } } } raycastTimer = RaycastInterval; } } } else { _attachPos = Vector2.Zero; } if (_attachPos == Vector2.Zero || targetBody == null) { DeattachFromBody(reset: false); } else { float squaredDistance = Vector2.DistanceSquared(character.SimPosition, _attachPos); float targetDistance = Math.Max(Math.Max(character.AnimController.Collider.radius, character.AnimController.Collider.width), character.AnimController.Collider.height) * 1.2f; if (squaredDistance < targetDistance * targetDistance) { //close enough to a wall -> attach AttachToBody(_attachPos); enemyAI.SteeringManager.Reset(); } else { //move closer to the wall DeattachFromBody(reset: false); enemyAI.SteeringManager.SteeringAvoid(deltaTime, 1.0f, 0.1f); enemyAI.SteeringManager.SteeringSeek(_attachPos); } } break; case AIState.Attack: case AIState.Aggressive: if (enemyAI.IsSteeringThroughGap) { break; } if (_attachPos == Vector2.Zero) { break; } if (!AttachToSub && !AttachToCharacters) { break; } if (enemyAI.AttackingLimb == null) { break; } if (targetBody == null) { break; } if (IsAttached && AttachJoints[0].BodyB == targetBody) { break; } Vector2 referencePos = targetCharacter != null ? targetCharacter.WorldPosition : ConvertUnits.ToDisplayUnits(transformedAttachPos); if (Vector2.DistanceSquared(referencePos, enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange) { AttachToBody(transformedAttachPos); } break; default: DeattachFromBody(reset: true); break; } if (IsAttached && targetBody != null && deattachCheckTimer <= 0.0f) { bool deattach = false; if (maxAttachDuration > 0) { deattach = true; attachCooldown = coolDown; } if (!deattach && targetWall != null && targetSubmarine != null) { // Deattach if the wall is broken enough where we are attached to int targetSection = targetWall.FindSectionIndex(attachLimb.WorldPosition, world: true, clamp: true); if (enemyAI.CanPassThroughHole(targetWall, targetSection)) { deattach = true; attachCooldown = coolDown; } if (!deattach) { // Deattach if the velocity is high float velocity = targetSubmarine.Velocity == Vector2.Zero ? 0.0f : targetSubmarine.Velocity.Length(); deattach = velocity > maxDeattachSpeed; if (!deattach) { if (velocity > minDeattachSpeed) { float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ? Math.Sign(Math.Abs(velocity) - minDeattachSpeed) : (Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed); if (Rand.Range(0.0f, 1.0f) < velocityFactor) { deattach = true; character.AddDamage(character.WorldPosition, new List <Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true); attachCooldown = Math.Max(detachStun * 2, coolDown); } } } } deattachCheckTimer = 5.0f; } if (deattach) { DeattachFromBody(reset: true); } } }
/// <summary> /// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken /// </summary> public static Dictionary <Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null) { List <Structure> structureList = new List <Structure>(); float dist = 600.0f; foreach (MapEntity entity in MapEntity.mapEntityList) { if (!(entity is Structure structure)) { continue; } if (structure.HasBody && !structure.IsPlatform && Vector2.Distance(structure.WorldPosition, worldPosition) < dist * 3.0f) { structureList.Add(structure); } } Dictionary <Structure, float> damagedStructures = new Dictionary <Structure, float>(); foreach (Structure structure in structureList) { for (int i = 0; i < structure.SectionCount; i++) { float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange); if (distFactor <= 0.0f) { continue; } structure.AddDamage(i, damage * distFactor, attacker); if (damagedStructures.ContainsKey(structure)) { damagedStructures[structure] += damage * distFactor; } else { damagedStructures.Add(structure, damage * distFactor); } } } if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f)) { for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--) { if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; } foreach (var cell in destructibleWall.Cells) { if (cell.IsPointInside(worldPosition)) { destructibleWall.AddDamage(levelWallDamage, worldPosition); continue; } foreach (var edge in cell.Edges) { if (!MathUtils.GetLineIntersection(worldPosition, cell.Center, edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, out Vector2 intersection)) { continue; } float wallDist = Vector2.DistanceSquared(worldPosition, intersection); if (wallDist < worldRange * worldRange) { destructibleWall.AddDamage(damage, worldPosition); break; } } } } } return(damagedStructures); }