private void UpdateOutsideColliderPos(Hull hull) { outsideColliderNormal = null; outsideColliderPos = null; if (Submarine == null) { return; } Vector2 rayDir; if (IsHorizontal) { rayDir = new Vector2(Math.Sign(rect.Center.X - hull.Rect.Center.X), 0); } else { rayDir = new Vector2(0, Math.Sign((rect.Y - rect.Height / 2) - (hull.Rect.Y - hull.Rect.Height / 2))); } Vector2 rayStart = ConvertUnits.ToSimUnits(WorldPosition); Vector2 rayEnd = rayStart + rayDir * 500.0f; if (Submarine.CheckVisibility(rayStart, rayEnd) != null) { outsideColliderNormal = -rayDir; outsideColliderPos = Submarine.LastPickedPosition; } }
private void UpdateNone(float deltaTime) { attackingLimb = null; coolDownTimer -= deltaTime; if (Character.Submarine == null && SimPosition.Y < ConvertUnits.ToSimUnits(SubmarineBody.DamageDepth * 0.5f)) { //steer straight up if very deep steeringManager.SteeringManual(deltaTime, Vector2.UnitY); return; } if (attachToWalls && Character.Submarine == null && Level.Loaded != null) { raycastTimer -= deltaTime; //check if there are any walls nearby the character could attach to if (raycastTimer < 1.0f) { wallAttackPos = Vector2.Zero; var cells = Level.Loaded.GetCells(WorldPosition, 1); if (cells.Count > 0) { Body closestBody = Submarine.CheckVisibility(Character.SimPosition, ConvertUnits.ToSimUnits(cells[0].Center)); if (closestBody != null && closestBody.UserData is Voronoi2.VoronoiCell) { wallAttackPos = Submarine.LastPickedPosition; } } raycastTimer = RaycastInterval; } } else { wallAttackPos = Vector2.Zero; } if (wallAttackPos == Vector2.Zero) { //wander around randomly steeringManager.SteeringAvoid(deltaTime, 0.1f); steeringManager.SteeringWander(0.5f); return; } float dist = Vector2.Distance(SimPosition, wallAttackPos); if (dist < Math.Max(Math.Max(Character.AnimController.Collider.radius, Character.AnimController.Collider.width), Character.AnimController.Collider.height) * 1.2f) { //close enough to a wall -> attach Character.AnimController.Collider.MoveToPos(wallAttackPos, 1.0f); steeringManager.Reset(); } else { //move closer to the wall steeringManager.SteeringAvoid(deltaTime, 0.1f); steeringManager.SteeringSeek(wallAttackPos); } }
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float speed = 1.0f) { if (steering == Vector2.Zero || host.Steering == Vector2.Zero) { return(Vector2.Zero); } float maxDistance = 2.0f; Vector2 ahead = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance; if (rayCastTimer <= 0.0f) { rayCastTimer = RayCastInterval; Body closestBody = Submarine.CheckVisibility(host.SimPosition, ahead); if (closestBody == null) { avoidSteering = Vector2.Zero; return(Vector2.Zero); } else { if (closestBody.UserData is Structure closestStructure) { Vector2 obstaclePosition = Submarine.LastPickedPosition; if (closestStructure.IsHorizontal) { obstaclePosition.Y = closestStructure.SimPosition.Y; } else { obstaclePosition.X = closestStructure.SimPosition.X; } avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition); } else if (closestBody.UserData is Item item) { avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - item.SimPosition); } else { avoidSteering = Vector2.Normalize(host.SimPosition - Submarine.LastPickedPosition); } //failed to normalize (the obstacle to avoid is at the same position as the character?) // -> move to a random direction if (!MathUtils.IsValid(avoidSteering)) { avoidSteering = Rand.Vector(1.0f); } } } else { rayCastTimer -= deltaTime; } return(avoidSteering * speed); }
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float speed = 1.0f) { if (steering == Vector2.Zero || host.Steering == Vector2.Zero) { return(Vector2.Zero); } float maxDistance = 2.0f; Vector2 ahead = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance; if (rayCastTimer <= 0.0f) { rayCastTimer = RayCastInterval; Body closestBody = Submarine.CheckVisibility(host.SimPosition, ahead); if (closestBody == null) { avoidSteering = Vector2.Zero; return(Vector2.Zero); } else { Structure closestStructure = closestBody.UserData as Structure; if (closestStructure != null) { Vector2 obstaclePosition = Submarine.LastPickedPosition; if (closestStructure.IsHorizontal) { obstaclePosition.Y = closestStructure.SimPosition.Y; } else { obstaclePosition.X = closestStructure.SimPosition.X; } avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition); } else if (closestBody.UserData is Item) { Item item = (Item)closestBody.UserData; avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - item.SimPosition); } else { avoidSteering = Vector2.Normalize(host.SimPosition - Submarine.LastPickedPosition); } } } else { rayCastTimer -= deltaTime; } return(avoidSteering * speed); }
public static void ApplyExplosionForces(Vector2 worldPosition, float range, float force, float damage = 0.0f, float stun = 0.0f) { if (range <= 0.0f) { return; } foreach (Character c in Character.CharacterList) { Vector2 explosionPos = worldPosition; if (c.Submarine != null) { explosionPos -= c.Submarine.Position; } explosionPos = ConvertUnits.ToSimUnits(explosionPos); foreach (Limb limb in c.AnimController.Limbs) { float dist = Vector2.Distance(limb.WorldPosition, worldPosition); //calculate distance from the "outer surface" of the physics body //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose float limbRadius = Math.Max(Math.Max(limb.body.width * 0.5f, limb.body.height * 0.5f), limb.body.radius); dist = Math.Max(0.0f, dist - FarseerPhysics.ConvertUnits.ToDisplayUnits(limbRadius)); if (dist > range) { continue; } float distFactor = 1.0f - dist / range; //solid obstacles between the explosion and the limb reduce the effect of the explosion by 90% if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null) { distFactor *= 0.1f; } c.AddDamage(limb.WorldPosition, DamageType.None, damage / c.AnimController.Limbs.Length * distFactor, 0.0f, stun * distFactor, false); if (limb.WorldPosition == worldPosition) { continue; } if (force > 0.0f) { limb.body.ApplyLinearImpulse(Vector2.Normalize(limb.WorldPosition - worldPosition) * distFactor * force); } } } }
private void UpdateOutsideColliderPos(Hull hull) { if (Submarine == null || IsRoomToRoom || Level.Loaded == null) { return; } Vector2 rayDir; if (IsHorizontal) { rayDir = new Vector2(Math.Sign(rect.Center.X - hull.Rect.Center.X), 0); } else { rayDir = new Vector2(0, Math.Sign((rect.Y - rect.Height / 2) - (hull.Rect.Y - hull.Rect.Height / 2))); } Vector2 rayStart = ConvertUnits.ToSimUnits(WorldPosition); Vector2 rayEnd = rayStart + rayDir * 500.0f; var levelCells = Level.Loaded.GetCells(WorldPosition, searchDepth: 1); foreach (var cell in levelCells) { if (cell.IsPointInside(WorldPosition)) { outsideCollisionBlocker.Enabled = true; Vector2 colliderPos = rayStart - Submarine.SimPosition; float colliderRotation = MathUtils.VectorToAngle(rayDir) - MathHelper.PiOver2; outsideCollisionBlocker.SetTransformIgnoreContacts(ref colliderPos, colliderRotation); return; } } var blockingBody = Submarine.CheckVisibility(rayStart, rayEnd); if (blockingBody != null) { //if the ray hit the body of the submarine itself (for example, if there's 2 layers of walls) we can ignore it if (blockingBody.UserData == Submarine) { return; } outsideCollisionBlocker.Enabled = true; Vector2 colliderPos = Submarine.LastPickedPosition - Submarine.SimPosition; float colliderRotation = MathUtils.VectorToAngle(rayDir) - MathHelper.PiOver2; outsideCollisionBlocker.SetTransformIgnoreContacts(ref colliderPos, colliderRotation); } else { outsideCollisionBlocker.Enabled = false; } }
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float lookAheadDistance, float weight, Vector2?heading = null) { if (steering == Vector2.Zero || host.Steering == Vector2.Zero) { return(Vector2.Zero); } float maxDistance = lookAheadDistance; if (Timing.TotalTime >= lastRayCastTime + RayCastInterval) { avoidRayCastHit = false; AvoidLookAheadPos = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance; lastRayCastTime = (float)Timing.TotalTime; Body closestBody = Submarine.CheckVisibility(host.SimPosition, AvoidLookAheadPos); if (closestBody != null) { avoidRayCastHit = true; AvoidRayCastHitPosition = Submarine.LastPickedPosition; AvoidDir = Submarine.LastPickedNormal; //add a bit of randomness AvoidDir = MathUtils.RotatePoint(AvoidDir, Rand.Range(-0.15f, 0.15f)); //wait a bit longer for the next raycast lastRayCastTime += RayCastInterval; } } if (AvoidDir.LengthSquared() < 0.0001f) { return(Vector2.Zero); } //if raycast hit nothing, lerp avoid dir to zero if (!avoidRayCastHit) { AvoidDir -= Vector2.Normalize(AvoidDir) * deltaTime * 0.5f; } Vector2 diff = AvoidRayCastHitPosition - host.SimPosition; float dist = diff.Length(); //> 0 when heading in the same direction as the obstacle, < 0 when away from it float dot = MathHelper.Clamp(Vector2.Dot(diff / dist, host.Steering), 0.0f, 1.0f); if (dot < 0) { return(Vector2.Zero); } return(AvoidDir * dot * weight * MathHelper.Clamp(1.0f - dist / lookAheadDistance, 0.0f, 1.0f)); }
private WayPoint FindClosestOutside(IEnumerable <WayPoint> waypointList, float tolerance, Body ignoredBody = null, IEnumerable <WayPoint> ignored = null, Func <WayPoint, bool> filter = null) { float closestDist = 0; WayPoint closest = null; foreach (WayPoint wp in waypointList) { if (wp.SpawnType != SpawnType.Path || wp == this) { continue; } // Ignore if already linked if (linkedTo.Contains(wp)) { continue; } if (ignored != null && ignored.Contains(wp)) { continue; } if (filter != null && !filter(wp)) { continue; } float sqrDist = Vector2.DistanceSquared(Position, wp.Position); if (closest == null || sqrDist < closestDist) { var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false); if (body != null && body != ignoredBody && !(body.UserData is Submarine)) { if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } closestDist = sqrDist; closest = wp; } } return(closest); }
public SteeringPath FindPath(Vector2 start, Vector2 end) { System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); float closestDist = 0.0f; PathNode startNode = null; foreach (PathNode node in nodes) { Vector2 nodePos = node.Position; float dist = System.Math.Abs(start.X - nodePos.X) + System.Math.Abs(start.Y - nodePos.Y) * 10.0f; //higher cost for vertical movement //prefer nodes that are closer to the end position dist += Vector2.Distance(end, nodePos) / 10.0f; if (dist < closestDist || startNode == null) { //if searching for a path inside the sub, make sure the waypoint is visible if (insideSubmarine) { var body = Submarine.PickBody( start, node.Waypoint.SimPosition, null, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs | Physics.CollisionPlatform); if (body != null) { if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; } if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } } closestDist = dist; startNode = node; } } if (startNode == null) { DebugConsole.NewMessage("Pathfinding error, couldn't find a start node", Color.DarkRed); return(new SteeringPath()); } closestDist = 0.0f; PathNode endNode = null; foreach (PathNode node in nodes) { Vector2 nodePos = node.Position; float dist = Vector2.Distance(end, nodePos); if (dist < closestDist || endNode == null) { //if searching for a path inside the sub, make sure the waypoint is visible if (insideSubmarine) { var body = Submarine.CheckVisibility(end, node.Waypoint.SimPosition); if (body != null && body.UserData is Structure) { continue; } } closestDist = dist; endNode = node; } } if (endNode == null) { DebugConsole.NewMessage("Pathfinding error, couldn't find an end node", Color.DarkRed); return(new SteeringPath()); } var path = FindPath(startNode, endNode); sw.Stop(); System.Diagnostics.Debug.WriteLine("findpath: " + sw.ElapsedMilliseconds + " ms"); return(path); }
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null) { if (dir != -1 && dir != 1) { return(null); } float closestDist = 0.0f; WayPoint closest = null; foreach (WayPoint wp in WayPointList) { if (wp.SpawnType != SpawnType.Path || wp == this) { continue; } float diff = 0.0f; if (horizontalSearch) { if ((wp.Position.Y - Position.Y) < tolerance.X || (wp.Position.Y - Position.Y) > tolerance.Y) { continue; } diff = wp.Position.X - Position.X; } else { if ((wp.Position.X - Position.X) < tolerance.X || (wp.Position.X - Position.X) > tolerance.Y) { continue; } diff = wp.Position.Y - Position.Y; } if (Math.Sign(diff) != dir) { continue; } float dist = Vector2.Distance(wp.Position, Position); if (closest == null || dist < closestDist) { var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, true, true, false); if (body != null && body != ignoredBody && !(body.UserData is Submarine)) { if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } closestDist = dist; closest = wp; } } return(closest); }
public override void DragCharacter(Character target, float deltaTime) { if (target == null) { return; } Limb mouthLimb = GetLimb(LimbType.Head); if (mouthLimb == null) { return; } if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { //stop dragging if there's something between the pull limb and the target Vector2 sourceSimPos = SimplePhysicsEnabled ? character.SimPosition : mouthLimb.SimPosition; Vector2 targetSimPos = target.SimPosition; if (character.Submarine != null && character.SelectedCharacter.Submarine == null) { targetSimPos -= character.Submarine.SimPosition; } else if (character.Submarine == null && character.SelectedCharacter.Submarine != null) { sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition; } var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true); if (body != null) { character.DeselectCharacter(); return; } } float dmg = character.Params.EatingSpeed; float eatSpeed = dmg / ((float)Math.Sqrt(Math.Max(target.Mass, 1)) * 10); eatTimer += deltaTime * eatSpeed; Vector2 mouthPos = SimplePhysicsEnabled ? character.SimPosition : GetMouthPosition().Value; Vector2 attackSimPosition = character.Submarine == null?ConvertUnits.ToSimUnits(target.WorldPosition) : target.SimPosition; Vector2 limbDiff = attackSimPosition - mouthPos; float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 1); if (limbDiff.LengthSquared() < extent * extent) { //pull the target character to the position of the mouth //(+ make the force fluctuate to waggle the character a bit) float dragForce = MathHelper.Clamp(eatSpeed * 10, 0, 40); if (dragForce > 0.1f) { target.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + dragForce)); target.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation, dragForce * 2); target.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + dragForce)); } if (InWater) { //pull the character's mouth to the target character (again with a fluctuating force) float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f)); mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); } else { float force = (float)Math.Sin(eatTimer * 100) * mouthLimb.Mass; mouthLimb.body.ApplyLinearImpulse(Vector2.UnitY * force * 2, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); mouthLimb.body.ApplyTorque(-force * 50); } var jaw = GetLimb(LimbType.Jaw); if (jaw != null) { jaw.body.ApplyTorque(-(float)Math.Sin(eatTimer * 150) * jaw.Mass * 25); } character.ApplyStatusEffects(ActionType.OnEating, deltaTime); float particleFrequency = MathHelper.Clamp(eatSpeed / 2, 0.02f, 0.5f); if (Rand.Value() < particleFrequency / 6) { target.AnimController.MainLimb.AddDamage(target.SimPosition, dmg, 0, 0, false); } if (Rand.Value() < particleFrequency) { target.AnimController.MainLimb.AddDamage(target.SimPosition, 0, dmg, 0, false); } if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f) { bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered; //keep severing joints until there is only one limb left var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered); if (nonSeveredJoints.None()) { //only one limb left, the character is now full eaten Entity.Spawner?.AddToRemoveQueue(target); character.SelectedCharacter = null; } else //sever a random joint { target.AnimController.SeverLimbJoint(nonSeveredJoints.GetRandom()); } } } else { character.SelectedCharacter = null; } }
/// <summary> /// Control the Character according to player input /// </summary> public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true) { if (DisableControls) { foreach (Key key in keys) { if (key == null) { continue; } key.Reset(); } } else { for (int i = 0; i < keys.Length; i++) { keys[i].SetState(); } if (moveCam) { if (needsAir && pressureProtection < 80.0f && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 50.0f)) { float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure; cam.Zoom = MathHelper.Lerp(cam.Zoom, (pressure / 50.0f) * Rand.Range(1.0f, 1.05f), (pressure - 50.0f) / 50.0f); } if (IsHumanoid) { cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, 250.0f, deltaTime); } else { //increased visibility range when controlling large a non-humanoid cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, MathHelper.Clamp(Mass, 250.0f, 800.0f), deltaTime); } } cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition); if (AnimController.CurrentHull?.Submarine != null) { cursorPosition -= AnimController.CurrentHull.Submarine.Position; } Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition); if (GUI.PauseMenuOpen) { cam.OffsetAmount = 0.0f; } else if (SelectedConstruction != null && SelectedConstruction.Components.Any(ic => (ic.GuiFrame != null && GUI.IsMouseOn(ic.GuiFrame)))) { cam.OffsetAmount = 0.0f; } else if (Lights.LightManager.ViewTarget == this && Vector2.DistanceSquared(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f) { if (GUI.PauseMenuOpen || IsUnconscious) { if (deltaTime > 0.0f) { cam.OffsetAmount = 0.0f; } } else if (Lights.LightManager.ViewTarget == this && Vector2.DistanceSquared(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f) { Body body = Submarine.CheckVisibility(AnimController.Limbs[0].SimPosition, mouseSimPos); Structure structure = body == null ? null : body.UserData as Structure; float sightDist = Submarine.LastPickedFraction; if (body?.UserData is Structure && !((Structure)body.UserData).CastShadow) { sightDist = 1.0f; } cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, Math.Max(250.0f, sightDist * 500.0f), 0.05f); } } if (SelectedConstruction != null && SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect))) { if (GameMain.Client != null) { //emulate a Select input to get the character to deselect the item server-side keys[(int)InputType.Select].Hit = true; } //reset focus to prevent us from accidentally interacting with another entity focusedItem = null; focusedCharacter = null; findFocusedTimer = 0.2f; SelectedConstruction = null; } DoInteractionUpdate(deltaTime, mouseSimPos); } DisableControls = false; }
public SteeringPath FindPath(Vector2 start, Vector2 end, string errorMsgStr = null) { float closestDist = 0.0f; PathNode startNode = null; foreach (PathNode node in nodes) { Vector2 nodePos = node.Position; float xDiff = System.Math.Abs(start.X - nodePos.X); float yDiff = System.Math.Abs(start.Y - nodePos.Y); if (yDiff > 1.0f && node.Waypoint.Ladders == null && node.Waypoint.Stairs == null) { yDiff += 10.0f; } float dist = xDiff + (insideSubmarine ? yDiff * 10.0f : yDiff); //higher cost for vertical movement when inside the sub //prefer nodes that are closer to the end position dist += (Math.Abs(end.X - nodePos.X) + Math.Abs(end.Y - nodePos.Y)) / 2.0f; //much higher cost to waypoints that are outside if (node.Waypoint.CurrentHull == null && insideSubmarine) { dist *= 10.0f; } if (dist < closestDist || startNode == null) { //if searching for a path inside the sub, make sure the waypoint is visible if (insideSubmarine) { var body = Submarine.PickBody( start, node.Waypoint.SimPosition, null, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs | Physics.CollisionPlatform); if (body != null) { if (body.UserData is Submarine) { continue; } if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; } if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } } closestDist = dist; startNode = node; } } if (startNode == null) { DebugConsole.NewMessage("Pathfinding error, couldn't find a start node. " + errorMsgStr, Color.DarkRed); return(new SteeringPath(true)); } closestDist = 0.0f; PathNode endNode = null; foreach (PathNode node in nodes) { Vector2 nodePos = node.Position; float dist = Vector2.Distance(end, nodePos); if (insideSubmarine) { //much higher cost to waypoints that are outside if (node.Waypoint.CurrentHull == null) { dist *= 10.0f; } //avoid stopping at a doorway if (node.Waypoint.ConnectedDoor != null) { dist *= 10.0f; } } if (dist < closestDist || endNode == null) { //if searching for a path inside the sub, make sure the waypoint is visible if (insideSubmarine) { // TODO: for some reason fails to find the path when the sub is flooding. Disabling this check helps fixes it, but we can't disable it var body = Submarine.CheckVisibility(end, node.Waypoint.SimPosition); if (body != null && body.UserData is Structure) { continue; } } closestDist = dist; endNode = node; } } if (endNode == null) { DebugConsole.NewMessage("Pathfinding error, couldn't find an end node. " + errorMsgStr, Color.DarkRed); return(new SteeringPath(true)); } var path = FindPath(startNode, endNode); return(path); }
private void GetTargetEntity() { targetEntity = null; if (Character.AnimController.CurrentHull != null) { wallAttackPos = Vector2.Zero; return; } //check if there's a wall between the target and the Character Vector2 rayStart = Character.SimPosition; Vector2 rayEnd = selectedAiTarget.SimPosition; if (selectedAiTarget.Entity.Submarine != null && Character.Submarine == null) { rayStart -= ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position); } Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd); if (Submarine.LastPickedFraction == 1.0f || closestBody == null) { wallAttackPos = Vector2.Zero; return; } Structure wall = closestBody.UserData as Structure; if (wall == null) { wallAttackPos = Submarine.LastPickedPosition; if (selectedAiTarget.Entity.Submarine != null && Character.Submarine == null) { wallAttackPos -= ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position); } } else { int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition)); float sectionDamage = wall.SectionDamage(sectionIndex); for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++) { if (wall.SectionBodyDisabled(i)) { sectionIndex = i; break; } if (wall.SectionDamage(i) > sectionDamage) { sectionIndex = i; } } wallAttackPos = wall.SectionPosition(sectionIndex); //if (wall.Submarine != null) wallAttackPos += wall.Submarine.Position; wallAttackPos = ConvertUnits.ToSimUnits(wallAttackPos); } targetEntity = closestBody.UserData as IDamageable; }
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float lookAheadDistance, float weight) { if (steering == Vector2.Zero || host.Steering == Vector2.Zero) { return(Vector2.Zero); } float maxDistance = lookAheadDistance; if (rayCastTimer <= 0.0f) { Vector2 ahead = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance; rayCastTimer = RayCastInterval; Body closestBody = Submarine.CheckVisibility(host.SimPosition, ahead); if (closestBody == null) { avoidObstaclePos = null; return(Vector2.Zero); } else { if (closestBody.UserData is Structure closestStructure) { Vector2 obstaclePosition = Submarine.LastPickedPosition; if (closestStructure.IsHorizontal) { obstaclePosition.Y = closestStructure.SimPosition.Y; } else { obstaclePosition.X = closestStructure.SimPosition.X; } avoidObstaclePos = obstaclePosition; //avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition); } /*else if (closestBody.UserData is Item) * { * avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - item.SimPosition); * }*/ else { avoidObstaclePos = Submarine.LastPickedPosition; //avoidSteering = Vector2.Normalize(host.SimPosition - Submarine.LastPickedPosition); } } } else { rayCastTimer -= deltaTime; } if (!avoidObstaclePos.HasValue) { return(Vector2.Zero); } Vector2 diff = avoidObstaclePos.Value - host.SimPosition; float dist = diff.Length(); if (dist > maxDistance) { return(Vector2.Zero); } return(-diff * (1.0f - dist / maxDistance) * weight); }
/// <summary> /// Control the Character according to player input /// </summary> public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true) { if (!DisableControls) { for (int i = 0; i < keys.Length; i++) { keys[i].SetState(); } } else { foreach (Key key in keys) { if (key == null) { continue; } key.Reset(); } } if (moveCam) { if (needsAir && pressureProtection < 80.0f && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 50.0f)) { float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure; cam.Zoom = MathHelper.Lerp(cam.Zoom, (pressure / 50.0f) * Rand.Range(1.0f, 1.05f), (pressure - 50.0f) / 50.0f); } if (IsHumanoid) { cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, 250.0f, deltaTime); } else { //increased visibility range when controlling large a non-humanoid cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, MathHelper.Clamp(Mass, 250.0f, 800.0f), deltaTime); } } cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition); if (AnimController.CurrentHull != null && AnimController.CurrentHull.Submarine != null) { cursorPosition -= AnimController.CurrentHull.Submarine.Position; } Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition); if (moveCam) { if (DebugConsole.IsOpen || GUI.PauseMenuOpen || IsUnconscious || (GameMain.GameSession?.CrewManager?.CrewCommander != null && GameMain.GameSession.CrewManager.CrewCommander.IsOpen)) { if (deltaTime > 0.0f) { cam.OffsetAmount = 0.0f; } } else if (Lights.LightManager.ViewTarget == this && Vector2.DistanceSquared(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f) { Body body = Submarine.CheckVisibility(AnimController.Limbs[0].SimPosition, mouseSimPos); Structure structure = body == null ? null : body.UserData as Structure; float sightDist = Submarine.LastPickedFraction; if (body?.UserData is Structure && !((Structure)body.UserData).CastShadow) { sightDist = 1.0f; } cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, Math.Max(250.0f, sightDist * 500.0f), 0.05f); } } DoInteractionUpdate(deltaTime, mouseSimPos); DisableControls = false; }
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float lookAheadDistance, float weight, Vector2?heading = null) { if (steering == Vector2.Zero || host.Steering == Vector2.Zero) { return(Vector2.Zero); } float maxDistance = lookAheadDistance; if (rayCastTimer <= 0.0f) { Vector2 ahead = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance; rayCastTimer = RayCastInterval; Body closestBody = Submarine.CheckVisibility(host.SimPosition, ahead); if (closestBody == null) { avoidObstaclePos = null; return(Vector2.Zero); } else { // TODO: Doesn't take items into account (like turrets) if (closestBody.UserData is Structure closestStructure) { Vector2 obstaclePosition = Submarine.LastPickedPosition; if (closestStructure.IsHorizontal) { obstaclePosition.Y = closestStructure.SimPosition.Y; } else { obstaclePosition.X = closestStructure.SimPosition.X; } avoidObstaclePos = obstaclePosition; } else { avoidObstaclePos = Submarine.LastPickedPosition; } } } else { rayCastTimer -= deltaTime; } if (!avoidObstaclePos.HasValue) { return(Vector2.Zero); } Vector2 diff = avoidObstaclePos.Value - host.SimPosition; float dist = diff.Length(); if (dist > maxDistance) { return(Vector2.Zero); } if (heading.HasValue) { var f = heading ?? host.Steering; // Avoid to left or right depending on the current heading Vector2 relativeVector = Vector2.Normalize(diff) - Vector2.Normalize(f); var dir = relativeVector.X > 0 ? diff.Right() : diff.Left(); float factor = 1.0f - Math.Min(dist / maxDistance, 1); return(dir * factor * weight); } else { // Doesn't work right because it effectively just slows down or reverses the movement, where as we'd like to go right or left to avoid the target. // There's also another issue, which also affects going right or left: the raycast doesn't hit anything if we turn too much -> avoiding doesn't work well. // Could probably "remember" the avoidance a bit longer so that the avoid steering is not immedieately disgarded, but kept for a while and reduced gradually? return(-diff * (1.0f - dist / maxDistance) * weight); } }
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null, IEnumerable <WayPoint> ignored = null, Func <WayPoint, bool> filter = null) { if (dir != -1 && dir != 1) { return(null); } float closestDist = 0.0f; WayPoint closest = null; foreach (WayPoint wp in WayPointList) { if (wp.SpawnType != SpawnType.Path || wp == this) { continue; } float xDiff = wp.Position.X - Position.X; float yDiff = wp.Position.Y - Position.Y; float xDist = Math.Abs(xDiff); float yDist = Math.Abs(yDiff); if (tolerance.X < xDist) { continue; } if (tolerance.Y < yDist) { continue; } float dist = 0.0f; float diff = 0.0f; if (horizontalSearch) { diff = xDiff; dist = xDist + yDist / 5.0f; } else { diff = yDiff; dist = yDist + xDist / 5.0f; //prefer ladder waypoints when moving vertically if (wp.Ladders != null) { dist *= 0.5f; } } if (Math.Sign(diff) != dir) { continue; } // Ignore if already linked if (linkedTo.Contains(wp)) { continue; } if (ignored != null && ignored.Contains(wp)) { continue; } if (filter != null && !filter(wp)) { continue; } if (closest == null || dist < closestDist) { var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false); if (body != null && body != ignoredBody && !(body.UserData is Submarine)) { if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } closestDist = dist; closest = wp; } } return(closest); }
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker) { if (attack.Range <= 0.0f) { return; } //long range for the broad distance check, because large characters may still be in range even if their collider isn't float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f); foreach (Character c in Character.CharacterList) { if (!c.Enabled || Math.Abs(c.WorldPosition.X - worldPosition.X) > broadRange || Math.Abs(c.WorldPosition.Y - worldPosition.Y) > broadRange) { continue; } Vector2 explosionPos = worldPosition; if (c.Submarine != null) { explosionPos -= c.Submarine.Position; } Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false); bool underWater = hull == null || explosionPos.Y < hull.Surface; explosionPos = ConvertUnits.ToSimUnits(explosionPos); Dictionary <Limb, float> distFactors = new Dictionary <Limb, float>(); foreach (Limb limb in c.AnimController.Limbs) { float dist = Vector2.Distance(limb.WorldPosition, worldPosition); //calculate distance from the "outer surface" of the physics body //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose float limbRadius = Math.Max(Math.Max(limb.body.width * 0.5f, limb.body.height * 0.5f), limb.body.radius); dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius)); if (dist > attack.Range) { continue; } float distFactor = 1.0f - dist / attack.Range; //solid obstacles between the explosion and the limb reduce the effect of the explosion by 90% if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null) { distFactor *= 0.1f; } distFactors.Add(limb, distFactor); List <Affliction> modifiedAfflictions = new List <Affliction>(); foreach (Affliction affliction in attack.Afflictions.Keys) { modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / c.AnimController.Limbs.Length)); } c.LastDamageSource = damageSource; if (attacker == null) { if (damageSource is Item item) { attacker = item.GetComponent <Projectile>()?.User; if (attacker == null) { attacker = item.GetComponent <MeleeWeapon>()?.User; } } } //use a position slightly from the limb's position towards the explosion //ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods Vector2 hitPos = limb.WorldPosition + (worldPosition - limb.WorldPosition) / dist * 0.01f; c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker); if (attack.StatusEffects != null && attack.StatusEffects.Any()) { attack.SetUser(attacker); var statusEffectTargets = new List <ISerializableEntity>() { c, limb }; foreach (StatusEffect statusEffect in attack.StatusEffects) { statusEffect.Apply(ActionType.OnUse, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(ActionType.Always, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(underWater ? ActionType.InWater : ActionType.NotInWater, 1.0f, damageSource, statusEffectTargets); } } if (limb.WorldPosition != worldPosition && !MathUtils.NearlyEqual(force, 0.0f)) { Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition); if (!MathUtils.IsValid(limbDiff)) { limbDiff = Rand.Vector(1.0f); } Vector2 impulse = limbDiff * distFactor * force; Vector2 impulsePoint = limb.SimPosition - limbDiff * limbRadius; limb.body.ApplyLinearImpulse(impulse, impulsePoint, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); } } //sever joints if (c.IsDead && attack.SeverLimbsProbability > 0.0f) { foreach (Limb limb in c.AnimController.Limbs) { if (!distFactors.ContainsKey(limb)) { continue; } if (Rand.Range(0.0f, 1.0f) < attack.SeverLimbsProbability * distFactors[limb]) { c.TrySeverLimbJoints(limb, 1.0f); } } } } }
public override void DragCharacter(Character target, float deltaTime) { if (target == null) { return; } Limb mouthLimb = Array.Find(Limbs, l => l != null && l.MouthPos.HasValue); if (mouthLimb == null) { mouthLimb = GetLimb(LimbType.Head); } if (mouthLimb == null) { DebugConsole.ThrowError("Character \"" + character.SpeciesName + "\" failed to eat a target (a head or a limb with a mouthpos required)"); return; } if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { //stop dragging if there's something between the pull limb and the target Vector2 sourceSimPos = mouthLimb.SimPosition; Vector2 targetSimPos = target.SimPosition; if (character.Submarine != null && character.SelectedCharacter.Submarine == null) { targetSimPos -= character.Submarine.SimPosition; } else if (character.Submarine == null && character.SelectedCharacter.Submarine != null) { sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition; } var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true); if (body != null) { character.DeselectCharacter(); return; } } Character targetCharacter = target; float eatSpeed = character.Mass / targetCharacter.Mass * 0.1f; eatTimer += deltaTime * eatSpeed; Vector2 mouthPos = GetMouthPosition().Value; Vector2 attackSimPosition = character.Submarine == null?ConvertUnits.ToSimUnits(target.WorldPosition) : target.SimPosition; Vector2 limbDiff = attackSimPosition - mouthPos; float limbDist = limbDiff.Length(); if (limbDist < 1.0f) { //pull the target character to the position of the mouth //(+ make the force fluctuate to waggle the character a bit) if (CanDrag(target)) { targetCharacter.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); targetCharacter.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation, 20.0f); targetCharacter.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); } //pull the character's mouth to the target character (again with a fluctuating force) float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f)); mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); character.ApplyStatusEffects(ActionType.OnEating, deltaTime); if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f) { //apply damage to the target character to get some blood particles flying targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, 0.0f, 20.0f, 0.0f, false); //keep severing joints until there is only one limb left LimbJoint[] nonSeveredJoints = Array.FindAll(targetCharacter.AnimController.LimbJoints, l => !l.IsSevered && l.CanBeSevered && l.LimbA != null && !l.LimbA.IsSevered && l.LimbB != null && !l.LimbB.IsSevered); if (nonSeveredJoints.Length == 0) { //only one limb left, the character is now full eaten Entity.Spawner?.AddToRemoveQueue(targetCharacter); character.SelectedCharacter = null; } else //sever a random joint { targetCharacter.AnimController.SeverLimbJoint(nonSeveredJoints[Rand.Int(nonSeveredJoints.Length)]); } } } else { character.SelectedCharacter = null; } }
public static void Update(float deltaTime, Character character, Camera cam) { if (GUI.DisableHUD) { if (character.Inventory != null && !LockInventory(character)) { character.Inventory.UpdateSlotInput(); } return; } if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) { if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null) { bool mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && GUI.MouseOn == null; if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked()) { CharacterHealth.OpenHealthWindow = character.CharacterHealth; } } if (character.Inventory != null) { if (!LockInventory(character)) { character.Inventory.Update(deltaTime, cam); } for (int i = 0; i < character.Inventory.Items.Length - 1; i++) { var item = character.Inventory.Items[i]; if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; } foreach (ItemComponent ic in item.Components) { if (ic.DrawHudWhenEquipped) { ic.UpdateHUD(character, deltaTime, cam); } } } } if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) { if (character.SelectedCharacter.CanInventoryBeAccessed) { character.SelectedCharacter.Inventory.Update(deltaTime, cam); } character.SelectedCharacter.CharacterHealth.UpdateHUD(deltaTime); } Inventory.UpdateDragging(); } if (focusedItem != null) { if (character.FocusedItem != null) { focusedItemOverlayTimer = Math.Min(focusedItemOverlayTimer + deltaTime, ItemOverlayDelay + 1.0f); } else { focusedItemOverlayTimer = Math.Max(focusedItemOverlayTimer - deltaTime, 0.0f); if (focusedItemOverlayTimer <= 0.0f) { focusedItem = null; shouldRecreateHudTexts = true; } } } if (brokenItemsCheckTimer > 0.0f) { brokenItemsCheckTimer -= deltaTime; } else { brokenItems.Clear(); brokenItemsCheckTimer = 1.0f; foreach (Item item in Item.ItemList) { if (item.Submarine == null || item.Submarine.TeamID != character.TeamID || item.Submarine.Info.IsWreck) { continue; } if (!item.Repairables.Any(r => item.ConditionPercentage <= r.RepairIconThreshold)) { continue; } if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; } Vector2 diff = item.WorldPosition - character.WorldPosition; if (Submarine.CheckVisibility(character.SimPosition, character.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) { brokenItems.Add(item); } } } }
public static void Update(float deltaTime, Character character, Camera cam) { if (GUI.DisableHUD) { return; } if (!character.IsUnconscious && character.Stun <= 0.0f) { if (character.Inventory != null) { if (!character.LockHands && character.Stun < 0.1f) { character.Inventory.Update(deltaTime, cam); } for (int i = 0; i < character.Inventory.Items.Length - 1; i++) { var item = character.Inventory.Items[i]; if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; } foreach (ItemComponent ic in item.Components) { if (ic.DrawHudWhenEquipped) { ic.UpdateHUD(character, deltaTime, cam); } } } } if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) { if (character.SelectedCharacter.CanInventoryBeAccessed) { character.SelectedCharacter.Inventory.Update(deltaTime, cam); } character.SelectedCharacter.CharacterHealth.UpdateHUD(deltaTime); } Inventory.UpdateDragging(); } if (focusedItem != null) { if (character.FocusedItem != null) { focusedItemOverlayTimer = Math.Min(focusedItemOverlayTimer + deltaTime, ItemOverlayDelay + 1.0f); } else { focusedItemOverlayTimer = Math.Max(focusedItemOverlayTimer - deltaTime, 0.0f); if (focusedItemOverlayTimer <= 0.0f) { focusedItem = null; } } } if (brokenItemsCheckTimer > 0.0f) { brokenItemsCheckTimer -= deltaTime; } else { brokenItems.Clear(); brokenItemsCheckTimer = 1.0f; foreach (Item item in Item.ItemList) { if (!item.Repairables.Any(r => item.Condition < r.ShowRepairUIThreshold)) { continue; } if (!Submarine.VisibleEntities.Contains(item)) { continue; } Vector2 diff = item.WorldPosition - character.WorldPosition; if (Submarine.CheckVisibility(character.SimPosition, character.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) { brokenItems.Add(item); } } } }
//goes through all the AItargets, evaluates how preferable it is to attack the target, //whether the Character can see/hear the target and chooses the most preferable target within //sight/hearing range public void UpdateTargets(Character character) { var prevAiTarget = selectedAiTarget; selectedAiTarget = null; selectedTargetMemory = null; targetValue = 0.0f; UpdateTargetMemories(); foreach (AITarget target in AITarget.List) { if (Level.Loaded != null && target.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } float valueModifier = 0.0f; float dist = 0.0f; Character targetCharacter = target.Entity as Character; //ignore the aitarget if it is the Character itself if (targetCharacter == character) { continue; } if (targetCharacter != null) { if (targetCharacter.IsDead) { if (eatDeadPriority == 0.0f) { continue; } valueModifier = eatDeadPriority; } else if (targetCharacter.SpeciesName == "human") { if (attackHumans == 0.0f) { continue; } valueModifier = attackHumans; } else { EnemyAIController enemy = targetCharacter.AIController as EnemyAIController; if (enemy != null) { if (enemy.combatStrength > combatStrength) { valueModifier = attackStronger; } else if (enemy.combatStrength < combatStrength) { valueModifier = attackWeaker; } else { continue; } } } } else if (target.Entity != null && attackRooms != 0.0f) { IDamageable targetDamageable = target.Entity as IDamageable; if (targetDamageable != null && targetDamageable.Health <= 0.0f) { continue; } //skip the target if it's a room and the character is already inside a sub if (character.AnimController.CurrentHull != null && target.Entity is Hull) { continue; } valueModifier = attackRooms; } if (valueModifier == 0.0f) { continue; } dist = Vector2.Distance(character.WorldPosition, target.WorldPosition); //if the target has been within range earlier, the character will notice it more easily //(i.e. remember where the target was) if (targetMemories.ContainsKey(target)) { dist *= 0.5f; } //ignore target if it's too far to see or hear if (dist > target.SightRange * sight && dist > target.SoundRange * hearing) { continue; } AITargetMemory targetMemory = FindTargetMemory(target); valueModifier = valueModifier * targetMemory.Priority / dist; if (Math.Abs(valueModifier) > Math.Abs(targetValue)) { Vector2 rayStart = character.AnimController.Limbs[0].SimPosition; Vector2 rayEnd = target.SimPosition; if (target.Entity.Submarine != null && character.Submarine == null) { rayStart -= ConvertUnits.ToSimUnits(target.Entity.Submarine.Position); } Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd); Structure closestStructure = (closestBody == null) ? null : closestBody.UserData as Structure; if (selectedAiTarget == null || Math.Abs(valueModifier) > Math.Abs(targetValue)) { selectedAiTarget = target; selectedTargetMemory = targetMemory; targetValue = valueModifier; } } } if (selectedAiTarget != prevAiTarget) { wallAttackPos = Vector2.Zero; } }
public static void ApplyExplosionForces(Vector2 worldPosition, Attack attack, float force) { if (attack.Range <= 0.0f) { return; } foreach (Character c in Character.CharacterList) { Vector2 explosionPos = worldPosition; if (c.Submarine != null) { explosionPos -= c.Submarine.Position; } explosionPos = ConvertUnits.ToSimUnits(explosionPos); Dictionary <Limb, float> distFactors = new Dictionary <Limb, float>(); foreach (Limb limb in c.AnimController.Limbs) { float dist = Vector2.Distance(limb.WorldPosition, worldPosition); //calculate distance from the "outer surface" of the physics body //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose float limbRadius = Math.Max(Math.Max(limb.body.width * 0.5f, limb.body.height * 0.5f), limb.body.radius); dist = Math.Max(0.0f, dist - FarseerPhysics.ConvertUnits.ToDisplayUnits(limbRadius)); if (dist > attack.Range) { continue; } float distFactor = 1.0f - dist / attack.Range; //solid obstacles between the explosion and the limb reduce the effect of the explosion by 90% if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null) { distFactor *= 0.1f; } distFactors.Add(limb, distFactor); c.AddDamage(limb.WorldPosition, DamageType.None, attack.GetDamage(1.0f) / c.AnimController.Limbs.Length * distFactor, attack.GetBleedingDamage(1.0f) / c.AnimController.Limbs.Length * distFactor, attack.Stun * distFactor, false); if (limb.WorldPosition != worldPosition && force > 0.0f) { Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition); if (!MathUtils.IsValid(limbDiff)) { limbDiff = Rand.Vector(1.0f); } Vector2 impulsePoint = limb.SimPosition - limbDiff * limbRadius; limb.body.ApplyLinearImpulse(limbDiff * distFactor * force, impulsePoint); } } //sever joints if (c.IsDead && attack.SeverLimbsProbability > 0.0f) { foreach (Limb limb in c.AnimController.Limbs) { if (!distFactors.ContainsKey(limb)) { continue; } foreach (LimbJoint joint in c.AnimController.LimbJoints) { if (joint.IsSevered || (joint.LimbA != limb && joint.LimbB != limb)) { continue; } if (Rand.Range(0.0f, 1.0f) < attack.SeverLimbsProbability * distFactors[limb]) { c.AnimController.SeverLimbJoint(joint); } } } } } }