public static VertexPositionTexture[] GenerateWallShapes(List <VoronoiCell> cells, Level level) { float outWardThickness = 30.0f; List <VertexPositionTexture> verticeList = new List <VertexPositionTexture>(); foreach (VoronoiCell cell in cells) { CompareCCW compare = new CompareCCW(cell.Center); foreach (GraphEdge edge in cell.Edges) { if (edge.Cell1 != null && edge.Cell1.Body == null && edge.Cell1.CellType != CellType.Empty) { edge.Cell1 = null; } if (edge.Cell2 != null && edge.Cell2.Body == null && edge.Cell2.CellType != CellType.Empty) { edge.Cell2 = null; } if (compare.Compare(edge.Point1, edge.Point2) == -1) { var temp = edge.Point1; edge.Point1 = edge.Point2; edge.Point2 = temp; } } } foreach (VoronoiCell cell in cells) { foreach (GraphEdge edge in cell.Edges) { if (!edge.IsSolid) { continue; } GraphEdge leftEdge = cell.Edges.Find(e => e != edge && (edge.Point1 == e.Point1 || edge.Point1 == e.Point2)); GraphEdge rightEdge = cell.Edges.Find(e => e != edge && (edge.Point2 == e.Point1 || edge.Point2 == e.Point2)); Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero; float inwardThickness1 = 100; float inwardThickness2 = 100; if (leftEdge != null && !leftEdge.IsSolid) { leftNormal = edge.Point1 == leftEdge.Point1 ? Vector2.Normalize(leftEdge.Point2 - leftEdge.Point1) : Vector2.Normalize(leftEdge.Point1 - leftEdge.Point2); inwardThickness1 = Vector2.Distance(leftEdge.Point1, leftEdge.Point2) / 2; } else { leftNormal = Vector2.Normalize(cell.Center - edge.Point1); inwardThickness1 = Vector2.Distance(edge.Point1, cell.Center) / 2; } if (!MathUtils.IsValid(leftNormal)) { #if DEBUG DebugConsole.ThrowError("Invalid left normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidLeftNormal:" + level.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid left normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + leftNormal + ", seed: " + level.Seed + ")"); if (cell.Body != null) { GameMain.World.Remove(cell.Body); cell.Body = null; } leftNormal = Vector2.UnitX; break; } if (rightEdge != null && !rightEdge.IsSolid) { rightNormal = edge.Point2 == rightEdge.Point1 ? Vector2.Normalize(rightEdge.Point2 - rightEdge.Point1) : Vector2.Normalize(rightEdge.Point1 - rightEdge.Point2); inwardThickness2 = Vector2.Distance(rightEdge.Point1, rightEdge.Point2) / 2; } else { rightNormal = Vector2.Normalize(cell.Center - edge.Point2); inwardThickness2 = Vector2.Distance(edge.Point2, cell.Center) / 2; } if (!MathUtils.IsValid(rightNormal)) { #if DEBUG DebugConsole.ThrowError("Invalid right normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidRightNormal:" + level.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid right normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + rightNormal + ", seed: " + level.Seed + ")"); if (cell.Body != null) { GameMain.World.Remove(cell.Body); cell.Body = null; } rightNormal = Vector2.UnitX; break; } float point1UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point1 - cell.Center)); float point2UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point2 - cell.Center)); //handle wrapping around 0/360 if (point1UV - point2UV > MathHelper.Pi) { point2UV += MathHelper.TwoPi; } //the texture wraps around the cell 4 times //TODO: define the uv scale in level generation parameters? point1UV = point1UV / MathHelper.TwoPi * 4; point2UV = point2UV / MathHelper.TwoPi * 4; for (int i = 0; i < 2; i++) { Vector2[] verts = new Vector2[3]; VertexPositionTexture[] vertPos = new VertexPositionTexture[3]; if (i == 0) { verts[0] = edge.Point1 - leftNormal * outWardThickness; verts[1] = edge.Point2 - rightNormal * outWardThickness; verts[2] = edge.Point1 + leftNormal * inwardThickness1; vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), new Vector2(point1UV, 0.0f)); vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), new Vector2(point2UV, 0.0f)); vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(point1UV, 0.5f)); } else { verts[0] = edge.Point1 + leftNormal * inwardThickness1; verts[1] = edge.Point2 - rightNormal * outWardThickness; verts[2] = edge.Point2 + rightNormal * inwardThickness2; vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), new Vector2(point1UV, 0.5f)); vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), new Vector2(point2UV, 0.0f)); vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(point2UV, 0.5f)); } verticeList.AddRange(vertPos); } } } return(verticeList.ToArray()); }
private void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true) { if (Submarine != null && Submarine.GodMode || Indestructible) { return; } if (!Prefab.Body) { return; } if (!MathUtils.IsValid(damage)) { return; } damage = MathHelper.Clamp(damage, 0.0f, Prefab.Health); #if SERVER if (GameMain.Server != null && createNetworkEvent && damage != Sections[sectionIndex].damage) { GameMain.Server.CreateEntityEvent(this); } bool noGaps = true; for (int i = 0; i < Sections.Length; i++) { if (i != sectionIndex && SectionIsLeaking(i)) { noGaps = false; break; } } #endif if (damage < Prefab.Health * LeakThreshold) { if (Sections[sectionIndex].gap != null) { #if SERVER //the structure doesn't have any other gap, log the structure being fixed if (noGaps && attacker != null) { GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall repaired by " + attacker.Name, ServerLog.MessageType.ItemInteraction); } #endif DebugConsole.Log("Removing gap (ID " + Sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") from wall " + ID); //remove existing gap if damage is below leak threshold Sections[sectionIndex].gap.Open = 0.0f; Sections[sectionIndex].gap.Remove(); Sections[sectionIndex].gap = null; } } else { if (Sections[sectionIndex].gap == null) { Rectangle gapRect = Sections[sectionIndex].rect; float diffFromCenter; if (IsHorizontal) { diffFromCenter = (gapRect.Center.X - this.rect.Center.X) / (float)this.rect.Width * BodyWidth; if (BodyWidth > 0.0f) { gapRect.Width = (int)(BodyWidth * (gapRect.Width / (float)this.rect.Width)); } if (BodyHeight > 0.0f) { gapRect.Height = (int)BodyHeight; } } else { diffFromCenter = ((gapRect.Y - gapRect.Height / 2) - (this.rect.Y - this.rect.Height / 2)) / (float)this.rect.Height * BodyHeight; if (BodyWidth > 0.0f) { gapRect.Width = (int)BodyWidth; } if (BodyHeight > 0.0f) { gapRect.Height = (int)(BodyHeight * (gapRect.Height / (float)this.rect.Height)); } } if (FlippedX) { diffFromCenter = -diffFromCenter; } if (BodyRotation != 0.0f) { Vector2 structureCenter = Position; Vector2 gapPos = structureCenter + new Vector2( (float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation), (float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter; gapRect = new Rectangle((int)(gapPos.X - gapRect.Width / 2), (int)(gapPos.Y + gapRect.Height / 2), gapRect.Width, gapRect.Height); } gapRect.X -= 10; gapRect.Y += 10; gapRect.Width += 20; gapRect.Height += 20; bool horizontalGap = !IsHorizontal; if (Prefab.BodyRotation != 0.0f) { //rotation within a 90 deg sector (e.g. 100 -> 10, 190 -> 10, -10 -> 80) float sectorizedRotation = MathUtils.WrapAngleTwoPi(BodyRotation) % MathHelper.PiOver2; //diagonal if 30 < angle < 60 bool diagonal = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3; //gaps on the lower half of a diagonal wall are horizontal, ones on the upper half are vertical if (diagonal) { horizontalGap = gapRect.Y - gapRect.Height / 2 < Position.Y; } } Sections[sectionIndex].gap = new Gap(gapRect, horizontalGap, Submarine); //free the ID, because if we give gaps IDs we have to make sure they always match between the clients and the server and //that clients create them in the correct order along with every other entity created/removed during the round //which COULD be done via entityspawner, but it's unnecessary because we never access these gaps by ID Sections[sectionIndex].gap.FreeID(); Sections[sectionIndex].gap.ShouldBeSaved = false; Sections[sectionIndex].gap.ConnectedWall = this; DebugConsole.Log("Created gap (ID " + Sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") on wall " + ID); //AdjustKarma(attacker, 300); #if SERVER //the structure didn't have any other gaps yet, log the breach if (noGaps && attacker != null) { GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall breached by " + attacker.Name, ServerLog.MessageType.ItemInteraction); } #endif } float gapOpen = (damage / Prefab.Health - LeakThreshold) * (1.0f / (1.0f - LeakThreshold)); Sections[sectionIndex].gap.Open = gapOpen; } float damageDiff = damage - Sections[sectionIndex].damage; bool hadHole = SectionBodyDisabled(sectionIndex); Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, Prefab.Health); //otherwise it's possible to infinitely gain karma by welding fixed things if (attacker != null && damageDiff != 0.0f) { AdjustKarma(attacker, damageDiff); #if CLIENT if (GameMain.Client == null) { #endif if (damageDiff < 0.0f) { attacker.Info.IncreaseSkillLevel("mechanical", -damageDiff * SkillIncreaseMultiplier / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f), SectionPosition(sectionIndex, true)); } #if CLIENT } #endif } bool hasHole = SectionBodyDisabled(sectionIndex); if (hadHole == hasHole) { return; } UpdateSections(); }
partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos) { if (character != GameMain.Client.Character || !character.CanMove) { //remove states without a timestamp (there may still be ID-based states //in the list when the controlled character switches to timestamp-based interpolation) character.MemState.RemoveAll(m => m.Timestamp == 0.0f); //use simple interpolation for other players' characters and characters that can't move if (character.MemState.Count > 0) { CharacterStateInfo serverPos = character.MemState.Last(); if (!character.isSynced) { SetPosition(serverPos.Position, false); Collider.LinearVelocity = Vector2.Zero; character.MemLocalState.Clear(); character.LastNetworkUpdateID = serverPos.ID; character.isSynced = true; return; } if (character.MemState[0].SelectedCharacter == null || character.MemState[0].SelectedCharacter.Removed) { character.DeselectCharacter(); } else if (character.MemState[0].SelectedCharacter != null) { character.SelectCharacter(character.MemState[0].SelectedCharacter); } if (character.MemState[0].SelectedItem == null || character.MemState[0].SelectedItem.Removed) { character.SelectedConstruction = null; } else { if (character.SelectedConstruction != character.MemState[0].SelectedItem) { foreach (var ic in character.MemState[0].SelectedItem.Components) { if (ic.CanBeSelected) { ic.Select(character); } } } character.SelectedConstruction = character.MemState[0].SelectedItem; } if (character.MemState[0].Animation == AnimController.Animation.CPR) { character.AnimController.Anim = AnimController.Animation.CPR; } else if (character.AnimController.Anim == AnimController.Animation.CPR) { character.AnimController.Anim = AnimController.Animation.None; } Vector2 newVelocity = Collider.LinearVelocity; Vector2 newPosition = Collider.SimPosition; float newRotation = Collider.Rotation; float newAngularVelocity = Collider.AngularVelocity; Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity); newVelocity = newVelocity.ClampLength(100.0f); if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; } overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero; Collider.LinearVelocity = newVelocity; Collider.AngularVelocity = newAngularVelocity; float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); float errorTolerance = character.CanMove ? 0.01f : 0.2f; if (distSqrd > errorTolerance) { if (distSqrd > 10.0f || !character.CanMove) { Collider.TargetRotation = newRotation; SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false); } else { Collider.TargetRotation = newRotation; Collider.TargetPosition = newPosition; Collider.MoveToTargetPosition(true); } } //immobilized characters can't correct their position using AnimController movement // -> we need to correct it manually if (!character.CanMove) { float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition); float mainLimbErrorTolerance = 0.1f; //if the main limb is roughly at the correct position and the collider isn't moving (much at least), //don't attempt to correct the position. if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f) { MainLimb.PullJointWorldAnchorB = Collider.SimPosition; MainLimb.PullJointEnabled = true; } } } character.MemLocalState.Clear(); } else { //remove states with a timestamp (there may still timestamp-based states //in the list if the controlled character switches from timestamp-based interpolation to ID-based) character.MemState.RemoveAll(m => m.Timestamp > 0.0f); for (int i = 0; i < character.MemLocalState.Count; i++) { if (character.Submarine == null) { //transform in-sub coordinates to outside coordinates if (character.MemLocalState[i].Position.Y > lowestSubPos) { character.MemLocalState[i].TransformInToOutside(); } } else if (currentHull?.Submarine != null) { //transform outside coordinates to in-sub coordinates if (character.MemLocalState[i].Position.Y < lowestSubPos) { character.MemLocalState[i].TransformOutToInside(currentHull.Submarine); } } } if (character.MemState.Count < 1) { return; } overrideTargetMovement = Vector2.Zero; CharacterStateInfo serverPos = character.MemState.Last(); if (!character.isSynced) { SetPosition(serverPos.Position, false); Collider.LinearVelocity = Vector2.Zero; character.MemLocalState.Clear(); character.LastNetworkUpdateID = serverPos.ID; character.isSynced = true; return; } int localPosIndex = character.MemLocalState.FindIndex(m => m.ID == serverPos.ID); if (localPosIndex > -1) { CharacterStateInfo localPos = character.MemLocalState[localPosIndex]; //the entity we're interacting with doesn't match the server's if (localPos.SelectedCharacter != serverPos.SelectedCharacter) { if (serverPos.SelectedCharacter == null || serverPos.SelectedCharacter.Removed) { character.DeselectCharacter(); } else if (serverPos.SelectedCharacter != null) { character.SelectCharacter(serverPos.SelectedCharacter); } } if (localPos.SelectedItem != serverPos.SelectedItem) { if (serverPos.SelectedItem == null || serverPos.SelectedItem.Removed) { character.SelectedConstruction = null; } else if (serverPos.SelectedItem != null) { if (character.SelectedConstruction != serverPos.SelectedItem) { serverPos.SelectedItem.TryInteract(character, true, true); } character.SelectedConstruction = serverPos.SelectedItem; } } if (localPos.Animation != serverPos.Animation) { if (serverPos.Animation == AnimController.Animation.CPR) { character.AnimController.Anim = AnimController.Animation.CPR; } else if (character.AnimController.Anim == AnimController.Animation.CPR) { character.AnimController.Anim = AnimController.Animation.None; } } Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(serverPos.Position), character.CurrentHull, serverPos.Position.Y < lowestSubPos); Hull clientHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(localPos.Position), serverHull, localPos.Position.Y < lowestSubPos); if (serverHull != null && clientHull != null && serverHull.Submarine != clientHull.Submarine) { //hull subs don't match => teleport the camera to the other sub character.Submarine = serverHull.Submarine; character.CurrentHull = CurrentHull = serverHull; SetPosition(serverPos.Position); character.MemLocalState.Clear(); } else { Vector2 positionError = serverPos.Position - localPos.Position; float rotationError = serverPos.Rotation.HasValue && localPos.Rotation.HasValue ? serverPos.Rotation.Value - localPos.Rotation.Value : 0.0f; for (int i = localPosIndex; i < character.MemLocalState.Count; i++) { Hull pointHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(character.MemLocalState[i].Position), clientHull, character.MemLocalState[i].Position.Y < lowestSubPos); if (pointHull != clientHull && ((pointHull == null) || (clientHull == null) || (pointHull.Submarine == clientHull.Submarine))) { break; } character.MemLocalState[i].Translate(positionError, rotationError); } float errorMagnitude = positionError.Length(); if (errorMagnitude > 0.5f) { character.MemLocalState.Clear(); SetPosition(serverPos.Position, lerp: true, ignorePlatforms: false); } else if (errorMagnitude > 0.01f) { Collider.TargetPosition = Collider.SimPosition + positionError; Collider.TargetRotation = Collider.Rotation + rotationError; Collider.MoveToTargetPosition(lerp: true); } } } if (character.MemLocalState.Count > 120) { character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120); } character.MemState.Clear(); } }
private void ApplyImpact(float impact, Vector2 direction, Vector2 impactPos, bool applyDamage = true) { if (impact < MinCollisionImpact) { return; } Vector2 impulse = direction * impact * 0.5f; impulse = impulse.ClampLength(MaxCollisionImpact); if (!MathUtils.IsValid(impulse)) { string errorMsg = "Invalid impulse in SubmarineBody.ApplyImpact: " + impulse + ". Direction: " + direction + ", body position: " + Body.SimPosition + ", impact: " + impact + "."; if (GameMain.NetworkMember != null) { errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server."; } if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.ApplyImpact:InvalidImpulse", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } #if CLIENT if (Character.Controlled != null && Character.Controlled.Submarine == submarine && Character.Controlled.KnockbackCooldownTimer <= 0.0f) { GameMain.GameScreen.Cam.Shake = Math.Max(impact * 10.0f, GameMain.GameScreen.Cam.Shake); if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player)) { float angularVelocity = (impactPos.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y - (impactPos.Y - Body.SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X; GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f); } } #endif foreach (Character c in Character.CharacterList) { if (c.Submarine != submarine) { continue; } if (c.KnockbackCooldownTimer > 0.0f) { continue; } c.KnockbackCooldownTimer = Character.KnockbackCooldown; foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered) { continue; } limb.body.ApplyLinearImpulse(limb.Mass * impulse, 10.0f); } bool holdingOntoSomething = false; if (c.SelectedConstruction != null) { holdingOntoSomething = c.SelectedConstruction.GetComponent <Ladder>() != null || (c.SelectedConstruction.GetComponent <Controller>()?.LimbPositions.Any() ?? false); } if (!holdingOntoSomething) { c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f); //stun for up to 2 second if the impact equal or higher to the maximum impact if (impact >= MaxCollisionImpact) { c.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(3.0f).ToEnumerable(), stun: Math.Min(impulse.Length() * 0.2f, 2.0f), playSound: true); } } } foreach (Item item in Item.ItemList) { if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; } item.body.ApplyLinearImpulse(item.body.Mass * impulse, 10.0f); item.PositionUpdateInterval = 0.0f; } float dmg = applyDamage ? impact * ImpactDamageMultiplier : 0.0f; var damagedStructures = Explosion.RangedStructureDamage( ConvertUnits.ToDisplayUnits(impactPos), impact * 50.0f, dmg, dmg); #if CLIENT PlayDamageSounds(damagedStructures, impactPos, impact, "StructureBlunt"); #endif }
public override void Update(float deltaTime, Camera cam) { flowForce = Vector2.Zero; outsideColliderRaycastTimer -= deltaTime; if (open == 0.0f || linkedTo.Count == 0) { lerpedFlowForce = Vector2.Zero; return; } UpdateOxygen(); if (linkedTo.Count == 1) { //gap leading from a room to outside UpdateRoomToOut(deltaTime); } else { //gap leading from a room to another UpdateRoomToRoom(deltaTime); } flowForce.X = MathHelper.Clamp(flowForce.X, -MaxFlowForce, MaxFlowForce); flowForce.Y = MathHelper.Clamp(flowForce.Y, -MaxFlowForce, MaxFlowForce); lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime * 5.0f); EmitParticles(deltaTime); if (flowTargetHull != null && lerpedFlowForce.LengthSquared() > 0.0001f) { foreach (Character character in Character.CharacterList) { if (character.CurrentHull == null) { continue; } if (character.CurrentHull != linkedTo[0] as Hull && (linkedTo.Count < 2 || character.CurrentHull != linkedTo[1] as Hull)) { continue; } foreach (Limb limb in character.AnimController.Limbs) { if (!limb.inWater) { continue; } float dist = Vector2.Distance(limb.WorldPosition, WorldPosition); if (dist > lerpedFlowForce.Length()) { continue; } Vector2 force = lerpedFlowForce / (float)Math.Max(Math.Sqrt(dist), 20.0f) * 0.025f; //vertical gaps only apply forces if the character is roughly above/below the gap if (!IsHorizontal) { float xDist = Math.Abs(limb.WorldPosition.X - WorldPosition.X); if (xDist > rect.Width || rect.Width == 0) { break; } force *= 1.0f - xDist / rect.Width; } if (!MathUtils.IsValid(force)) { string errorMsg = "Attempted to apply invalid flow force to the character \"" + character.Name + "\", gap pos: " + WorldPosition + ", limb pos: " + limb.WorldPosition + ", flowforce: " + flowForce + ", lerpedFlowForce:" + lerpedFlowForce + ", dist: " + dist; DebugConsole.Log(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Gap.Update:InvalidFlowForce:" + character.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); continue; } character.AnimController.Collider.ApplyForce(force * limb.body.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); } } } }
void UpdateDying(float deltaTime) { if (deathAnimDuration <= 0.0f) { return; } float noise = (PerlinNoise.GetPerlin(WalkPos * 0.002f, WalkPos * 0.003f) - 0.5f) * 5.0f; float animStrength = (1.0f - deathAnimTimer / deathAnimDuration); Limb head = GetLimb(LimbType.Head); if (head != null && head.IsSevered) { return; } Limb tail = GetLimb(LimbType.Tail); if (head != null && !head.IsSevered) { head.body.ApplyTorque((float)(Math.Sqrt(head.Mass) * Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); } if (tail != null && !tail.IsSevered) { tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); } WalkPos += deltaTime * 10.0f * animStrength; Vector2 centerOfMass = GetCenterOfMass(); foreach (Limb limb in Limbs) { if (limb.IsSevered) { continue; } #if CLIENT if (limb.LightSource != null) { limb.LightSource.Color = Color.Lerp(limb.InitialLightSourceColor, Color.TransparentBlack, deathAnimTimer / deathAnimDuration); if (limb.InitialLightSpriteAlpha.HasValue) { limb.LightSource.OverrideLightSpriteAlpha = MathHelper.Lerp(limb.InitialLightSpriteAlpha.Value, 0.0f, deathAnimTimer / deathAnimDuration); } } #endif if (limb.type == LimbType.Head || limb.type == LimbType.Tail || limb.IsSevered || !limb.body.Enabled) { continue; } if (limb.Mass <= 0.0f) { string errorMsg = "Creature death animation error: invalid limb mass on character \"" + character.SpeciesName + "\" (type: " + limb.type + ", mass: " + limb.Mass + ")"; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidMass" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); deathAnimTimer = deathAnimDuration; return; } Vector2 diff = (centerOfMass - limb.SimPosition); if (!MathUtils.IsValid(diff)) { string errorMsg = "Creature death animation error: invalid diff (center of mass: " + centerOfMass + ", limb position: " + limb.SimPosition + ")"; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidDiff" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); deathAnimTimer = deathAnimDuration; return; } limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength, maxVelocity: 10.0f); } }
private void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true) { if (Submarine != null && Submarine.GodMode) { return; } if (!prefab.Body) { return; } if (!MathUtils.IsValid(damage)) { return; } damage = MathHelper.Clamp(damage, 0.0f, prefab.Health); if (GameMain.Server != null && createNetworkEvent && damage != sections[sectionIndex].damage) { GameMain.Server.CreateEntityEvent(this); } bool noGaps = true; for (int i = 0; i < sections.Length; i++) { if (i != sectionIndex && SectionIsLeaking(i)) { noGaps = false; break; } } if (damage < prefab.Health * LeakThreshold) { if (sections[sectionIndex].gap != null) { //the structure doesn't have any other gap, log the structure being fixed if (noGaps && attacker != null) { GameServer.Log((sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall repaired by " + attacker.Name, ServerLog.MessageType.ItemInteraction); } DebugConsole.Log("Removing gap (ID " + sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") from wall " + ID); //remove existing gap if damage is below leak threshold sections[sectionIndex].gap.Open = 0.0f; sections[sectionIndex].gap.Remove(); sections[sectionIndex].gap = null; #if CLIENT if (CastShadow) { GenerateConvexHull(); } #endif } } else { if (sections[sectionIndex].gap == null) { Rectangle gapRect = sections[sectionIndex].rect; gapRect.X -= 10; gapRect.Y += 10; gapRect.Width += 20; gapRect.Height += 20; sections[sectionIndex].gap = new Gap(gapRect, !isHorizontal, Submarine); //free the ID, because if we give gaps IDs we have to make sure they always match between the clients and the server and //that clients create them in the correct order along with every other entity created/removed during the round //which COULD be done via entityspawner, but it's unnecessary because we never access these gaps by ID sections[sectionIndex].gap.FreeID(); sections[sectionIndex].gap.ShouldBeSaved = false; sections[sectionIndex].gap.ConnectedWall = this; DebugConsole.Log("Created gap (ID " + sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") on wall " + ID); //AdjustKarma(attacker, 300); //the structure didn't have any other gaps yet, log the breach if (noGaps && attacker != null) { GameServer.Log((sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall breached by " + attacker.Name, ServerLog.MessageType.ItemInteraction); } #if CLIENT if (CastShadow) { GenerateConvexHull(); } #endif } float gapOpen = (damage / prefab.Health - LeakThreshold) * (1.0f / (1.0f - LeakThreshold)); sections[sectionIndex].gap.Open = gapOpen; } float damageDiff = damage - sections[sectionIndex].damage; bool hadHole = SectionBodyDisabled(sectionIndex); sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, prefab.Health); if (damageDiff != 0.0f) //otherwise it's possible to infinitely gain karma by welding fixed things { AdjustKarma(attacker, damageDiff); } bool hasHole = SectionBodyDisabled(sectionIndex); if (hadHole == hasHole) { return; } UpdateSections(); }
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); } } } } } }
private IEnumerable <object> UpdateTransitionCinematic(List <Submarine> subs, Camera cam, Vector2 targetPos) { if (!subs.Any()) { yield return(CoroutineStatus.Success); } Character.Controlled = null; cam.TargetPos = Vector2.Zero; #if CLIENT GameMain.LightManager.LosEnabled = false; #endif //Vector2 diff = targetPos - sub.Position; float targetSpeed = 10.0f; Level.Loaded.TopBarrier.Enabled = false; cam.TargetPos = Vector2.Zero; float timer = 0.0f; while (timer < duration) { if (Screen.Selected != GameMain.GameScreen) { yield return(new WaitForSeconds(0.1f)); #if CLIENT GUI.ScreenOverlayColor = Color.TransparentBlack; #endif Running = false; yield return(CoroutineStatus.Success); } cam.Zoom = Math.Max(0.2f, cam.Zoom - CoroutineManager.UnscaledDeltaTime * 0.1f); Vector2 cameraPos = subs.First().Position + Submarine.MainSub.HiddenSubPosition; cameraPos.Y = Math.Min(cameraPos.Y, ConvertUnits.ToDisplayUnits(Level.Loaded.TopBarrier.Position.Y) - cam.WorldView.Height / 2.0f); cam.Translate((cameraPos - cam.Position) * CoroutineManager.UnscaledDeltaTime * 10.0f); #if CLIENT GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, timer / duration); #endif foreach (Submarine sub in subs) { if (sub.Position == targetPos) { continue; } Vector2 dir = Vector2.Normalize(targetPos - sub.Position); if (!MathUtils.IsValid(dir)) { continue; } sub.ApplyForce((dir * targetSpeed - sub.Velocity) * 500.0f); } timer += CoroutineManager.UnscaledDeltaTime; yield return(CoroutineStatus.Running); } Running = false; yield return(new WaitForSeconds(0.1f)); #if CLIENT GUI.ScreenOverlayColor = Color.TransparentBlack; #endif yield return(CoroutineStatus.Success); }
private void ApplyImpact(float impact, Vector2 direction, Contact contact, bool applyDamage = true) { float minImpact = 3.0f; if (impact < minImpact) { return; } contact.GetWorldManifold(out Vector2 tempNormal, out FixedArray2 <Vector2> worldPoints); Vector2 lastContactPoint = worldPoints[0]; Vector2 impulse = direction * impact * 0.5f; impulse = impulse.ClampLength(5.0f); if (!MathUtils.IsValid(impulse)) { string errorMsg = "Invalid impulse in SubmarineBody.ApplyImpact: " + impulse + ". Direction: " + direction + ", body position: " + Body.SimPosition + ", impact: " + impact + "."; if (GameMain.NetworkMember != null) { errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server."; } if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.ApplyImpact:InvalidImpulse", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } #if CLIENT if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = impact * 2.0f; float angularVelocity = (lastContactPoint.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y - (lastContactPoint.Y - Body.SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X; GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f); } #endif foreach (Character c in Character.CharacterList) { if (c.Submarine != submarine) { continue; } if (impact > 2.0f) { c.SetStun((impact - 2.0f) * 0.1f); } foreach (Limb limb in c.AnimController.Limbs) { limb.body.ApplyLinearImpulse(limb.Mass * impulse, 20.0f); } c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 20.0f); } foreach (Item item in Item.ItemList) { if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; } item.body.ApplyLinearImpulse(item.body.Mass * impulse, 20.0f); } var damagedStructures = Explosion.RangedStructureDamage( ConvertUnits.ToDisplayUnits(lastContactPoint), impact * 50.0f, applyDamage ? impact * ImpactDamageMultiplier : 0.0f); #if CLIENT //play a damage sound for the structure that took the most damage float maxDamage = 0.0f; Structure maxDamageStructure = null; foreach (KeyValuePair <Structure, float> structureDamage in damagedStructures) { if (maxDamageStructure == null || structureDamage.Value > maxDamage) { maxDamage = structureDamage.Value; maxDamageStructure = structureDamage.Key; } } if (maxDamageStructure != null) { SoundPlayer.PlayDamageSound( "StructureBlunt", impact * 10.0f, ConvertUnits.ToDisplayUnits(lastContactPoint), MathHelper.Lerp(2000.0f, 10000.0f, (impact - minImpact) / 2.0f), maxDamageStructure.Tags); } #endif }
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource) { 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 - 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); List <Affliction> modifiedAfflictions = new List <Affliction>(); foreach (Affliction affliction in attack.Afflictions) { modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / c.AnimController.Limbs.Length)); } c.LastDamageSource = damageSource; Character attacker = null; if (damageSource is Item item) { attacker = item.GetComponent <Projectile>()?.User; if (attacker == null) { attacker = item.GetComponent <MeleeWeapon>()?.User; } } c.AddDamage(limb.WorldPosition, 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 && 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); } } } } } }
partial void ClientUpdatePosition(float deltaTime) { if (GameMain.Client == null) { return; } Body.CorrectPosition(positionBuffer, out Vector2 newPosition, out Vector2 newVelocity, out _, out _); Vector2 moveAmount = ConvertUnits.ToDisplayUnits(newPosition - Body.SimPosition); newVelocity = newVelocity.ClampLength(100.0f); if (!MathUtils.IsValid(newVelocity) || moveAmount.LengthSquared() < 0.0001f) { return; } List <Submarine> subsToMove = submarine.GetConnectedSubs(); foreach (Submarine dockedSub in subsToMove) { if (dockedSub == submarine) { continue; } //clear the position buffer of the docked subs to prevent unnecessary position corrections dockedSub.SubBody.positionBuffer.Clear(); } Submarine closestSub; if (Character.Controlled == null) { closestSub = Submarine.FindClosest(GameMain.GameScreen.Cam.Position); } else { closestSub = Character.Controlled.Submarine; } bool displace = moveAmount.LengthSquared() > 100.0f * 100.0f; foreach (Submarine sub in subsToMove) { sub.PhysicsBody.LinearVelocity = newVelocity; if (displace) { sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f); sub.SubBody.DisplaceCharacters(moveAmount); } else { sub.PhysicsBody.SetTransformIgnoreContacts(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f); } } if (closestSub != null && subsToMove.Contains(closestSub)) { GameMain.GameScreen.Cam.Position += moveAmount; if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) { GameMain.GameScreen.Cam.TargetPos += moveAmount; } if (Character.Controlled != null) { Character.Controlled.CursorPosition += moveAmount; } } }
void UpdateDying(float deltaTime) { if (deathAnimDuration <= 0.0f) { return; } float noise = (PerlinNoise.GetPerlin(WalkPos * 0.002f, WalkPos * 0.003f) - 0.5f) * 5.0f; float animStrength = (1.0f - deathAnimTimer / deathAnimDuration); Limb baseLimb = GetLimb(LimbType.Head); //if head is the main limb, it technically can't be severed - the rest of the limbs are considered severed if the head gets cut off if (baseLimb == MainLimb) { int connectedToHeadCount = GetConnectedLimbs(baseLimb).Count; //if there's nothing connected to the head, don't make it wiggle by itself if (connectedToHeadCount == 1) { baseLimb = null; } Limb torso = GetLimb(LimbType.Torso, excludeSevered: false); if (torso != null) { //if there are more limbs connected to the torso than to the head, make the torso wiggle instead int connectedToTorsoCount = GetConnectedLimbs(torso).Count; if (connectedToTorsoCount > connectedToHeadCount) { baseLimb = torso; } } } else if (baseLimb == null) { baseLimb = GetLimb(LimbType.Torso, excludeSevered: true); if (baseLimb == null) { return; } } var connectedToBaseLimb = GetConnectedLimbs(baseLimb); Limb tail = GetLimb(LimbType.Tail); if (baseLimb != null) { baseLimb.body.ApplyTorque((float)(Math.Sqrt(baseLimb.Mass) * Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); } if (tail != null && connectedToBaseLimb.Contains(tail)) { tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); } WalkPos += deltaTime * 10.0f * animStrength; Vector2 centerOfMass = GetCenterOfMass(); foreach (Limb limb in Limbs) { if (!connectedToBaseLimb.Contains(limb)) { continue; } #if CLIENT if (limb.LightSource != null) { limb.LightSource.Color = Color.Lerp(limb.InitialLightSourceColor, Color.TransparentBlack, deathAnimTimer / deathAnimDuration); if (limb.InitialLightSpriteAlpha.HasValue) { limb.LightSource.OverrideLightSpriteAlpha = MathHelper.Lerp(limb.InitialLightSpriteAlpha.Value, 0.0f, deathAnimTimer / deathAnimDuration); } } #endif if (limb.type == LimbType.Head || limb.type == LimbType.Tail || limb.IsSevered || !limb.body.Enabled) { continue; } if (limb.Mass <= 0.0f) { string errorMsg = "Creature death animation error: invalid limb mass on character \"" + character.SpeciesName + "\" (type: " + limb.type + ", mass: " + limb.Mass + ")"; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidMass" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); deathAnimTimer = deathAnimDuration; return; } Vector2 diff = (centerOfMass - limb.SimPosition); if (!MathUtils.IsValid(diff)) { string errorMsg = "Creature death animation error: invalid diff (center of mass: " + centerOfMass + ", limb position: " + limb.SimPosition + ")"; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidDiff" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); deathAnimTimer = deathAnimDuration; return; } limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength, maxVelocity: 10.0f); } }
public static VertexPositionTexture[] GenerateWallShapes(List <VoronoiCell> cells, Level level) { float inwardThickness = 500.0f, outWardThickness = 30.0f; List <VertexPositionTexture> verticeList = new List <VertexPositionTexture>(); foreach (VoronoiCell cell in cells) { //if (cell.body == null) continue; foreach (GraphEdge edge in cell.edges) { if (edge.cell1 != null && edge.cell1.body == null && edge.cell1.CellType != CellType.Empty) { edge.cell1 = null; } if (edge.cell2 != null && edge.cell2.body == null && edge.cell2.CellType != CellType.Empty) { edge.cell2 = null; } CompareCCW compare = new CompareCCW(cell.Center); if (compare.Compare(edge.point1, edge.point2) == -1) { var temp = edge.point1; edge.point1 = edge.point2; edge.point2 = temp; } } } foreach (VoronoiCell cell in cells) { //if (cell.body == null) continue; foreach (GraphEdge edge in cell.edges) { if (!edge.isSolid) { continue; } GraphEdge leftEdge = cell.edges.Find(e => e != edge && (edge.point1 == e.point1 || edge.point1 == e.point2)); GraphEdge rightEdge = cell.edges.Find(e => e != edge && (edge.point2 == e.point1 || edge.point2 == e.point2)); Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero; if (leftEdge == null) { leftNormal = GetEdgeNormal(edge, cell); } else { leftNormal = (leftEdge.isSolid) ? Vector2.Normalize(GetEdgeNormal(leftEdge) + GetEdgeNormal(edge, cell)) : Vector2.Normalize(leftEdge.Center - edge.point1); } if (!MathUtils.IsValid(leftNormal)) { #if DEBUG DebugConsole.ThrowError("Invalid left normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidLeftNormal:" + level.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid left normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + leftNormal + ", seed: " + level.Seed + ")"); if (cell.body != null) { GameMain.World.RemoveBody(cell.body); cell.body = null; } leftNormal = Vector2.UnitX; break; } if (rightEdge == null) { rightNormal = GetEdgeNormal(edge, cell); } else { rightNormal = (rightEdge.isSolid) ? Vector2.Normalize(GetEdgeNormal(rightEdge) + GetEdgeNormal(edge, cell)) : Vector2.Normalize(rightEdge.Center - edge.point2); } if (!MathUtils.IsValid(rightNormal)) { #if DEBUG DebugConsole.ThrowError("Invalid right normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidRightNormal:" + level.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid right normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + rightNormal + ", seed: " + level.Seed + ")"); if (cell.body != null) { GameMain.World.RemoveBody(cell.body); cell.body = null; } rightNormal = Vector2.UnitX; break; } for (int i = 0; i < 2; i++) { Vector2[] verts = new Vector2[3]; VertexPositionTexture[] vertPos = new VertexPositionTexture[3]; if (i == 0) { verts[0] = edge.point1 - leftNormal * outWardThickness; verts[1] = edge.point2 - rightNormal * outWardThickness; verts[2] = edge.point1 + leftNormal * inwardThickness; vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), Vector2.Zero); vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), Vector2.UnitX); vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(0, 0.5f)); } else { verts[0] = edge.point1 + leftNormal * inwardThickness; verts[1] = edge.point2 - rightNormal * outWardThickness; verts[2] = edge.point2 + rightNormal * inwardThickness; vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), new Vector2(0.0f, 0.5f)); vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), Vector2.UnitX); vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(1.0f, 0.5f)); } var comparer = new CompareCCW((verts[0] + verts[1] + verts[2]) / 3.0f); Array.Sort(verts, vertPos, comparer); for (int j = 0; j < 3; j++) { verticeList.Add(vertPos[j]); } } } } return(verticeList.ToArray()); }
public void ClientReadPosition(ServerNetObject type, NetBuffer msg, float sendingTime) { Vector2 newPosition = new Vector2(msg.ReadFloat(), msg.ReadFloat()); float newRotation = msg.ReadRangedSingle(0.0f, MathHelper.TwoPi, 7); bool awake = msg.ReadBoolean(); Vector2 newVelocity = Vector2.Zero; if (awake) { newVelocity = new Vector2( msg.ReadRangedSingle(-MaxVel, MaxVel, 12), msg.ReadRangedSingle(-MaxVel, MaxVel, 12)); } if (!MathUtils.IsValid(newPosition) || !MathUtils.IsValid(newRotation) || !MathUtils.IsValid(newVelocity)) { string errorMsg = "Received invalid position data for the item \"" + Name + "\" (position: " + newPosition + ", rotation: " + newRotation + ", velocity: " + newVelocity + ")"; #if DEBUG DebugConsole.ThrowError(errorMsg); #endif GameAnalyticsManager.AddErrorEventOnce("Item.ClientReadPosition:InvalidData" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } if (body == null) { DebugConsole.ThrowError("Received a position update for an item with no physics body (" + Name + ")"); return; } body.FarseerBody.Awake = awake; if (body.FarseerBody.Awake) { if ((newVelocity - body.LinearVelocity).LengthSquared() > 8.0f * 8.0f) { body.LinearVelocity = newVelocity; } } else { try { body.FarseerBody.Enabled = false; } catch (Exception e) { DebugConsole.ThrowError("Exception in PhysicsBody.Enabled = false (" + body.PhysEnabled + ")", e); if (body.UserData != null) { DebugConsole.NewMessage("PhysicsBody UserData: " + body.UserData.GetType().ToString(), Color.Red); } if (GameMain.World.ContactManager == null) { DebugConsole.NewMessage("ContactManager is null!", Color.Red); } else if (GameMain.World.ContactManager.BroadPhase == null) { DebugConsole.NewMessage("Broadphase is null!", Color.Red); } if (body.FarseerBody.FixtureList == null) { DebugConsole.NewMessage("FixtureList is null!", Color.Red); } } } if ((newPosition - SimPosition).Length() > body.LinearVelocity.Length() * 2.0f) { if (body.SetTransform(newPosition, newRotation)) { Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition); rect.X = (int)(displayPos.X - rect.Width / 2.0f); rect.Y = (int)(displayPos.Y + rect.Height / 2.0f); } } }
protected override void Draw(SpriteBatch spriteBatch) { if (!Visible) { return; } if (ProgressGetter != null) { float newSize = MathHelper.Clamp(ProgressGetter(), 0.0f, 1.0f); if (!MathUtils.IsValid(newSize)) { GameAnalyticsManager.AddErrorEventOnce( "GUIProgressBar.Draw:GetProgress", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "ProgressGetter of a GUIProgressBar (" + ProgressGetter.Target.ToString() + " - " + ProgressGetter.Method.ToString() + ") returned an invalid value (" + newSize + ")\n" + Environment.StackTrace); } else { BarSize = newSize; } } var sliderRect = GetSliderRect(barSize); slider.RectTransform.AbsoluteOffset = new Point((int)style.Padding.X, (int)style.Padding.Y); slider.RectTransform.MaxSize = new Point( (int)(Rect.Width - style.Padding.X + style.Padding.Z), (int)(Rect.Height - style.Padding.Y + style.Padding.W)); frame.Visible = showFrame; slider.Visible = BarSize > 0.0f; if (showFrame) { if (AutoDraw) { frame.DrawAuto(spriteBatch); } else { frame.DrawManually(spriteBatch); } } Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; if (BarSize <= 1.0f) { spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, sliderRect); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); } Color currColor = GetColor(State); slider.Color = currColor; if (AutoDraw) { slider.DrawAuto(spriteBatch); } else { slider.DrawManually(spriteBatch); } //hide the slider, we've already drawn it manually frame.Visible = false; slider.Visible = false; if (BarSize <= 1.0f) { spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; } }
public void Update(float deltaTime) { if (GameMain.Client != null) { if (memPos.Count == 0) { return; } Vector2 newVelocity = Body.LinearVelocity; Vector2 newPosition = Body.SimPosition; Body.CorrectPosition(memPos, deltaTime, out newVelocity, out newPosition); Vector2 moveAmount = ConvertUnits.ToDisplayUnits(newPosition - Body.SimPosition); newVelocity = newVelocity.ClampLength(100.0f); if (!MathUtils.IsValid(newVelocity)) { return; } List <Submarine> subsToMove = submarine.GetConnectedSubs(); foreach (Submarine dockedSub in subsToMove) { if (dockedSub == submarine) { continue; } //clear the position buffer of the docked subs to prevent unnecessary position corrections dockedSub.SubBody.memPos.Clear(); } Submarine closestSub = null; if (Character.Controlled == null) { closestSub = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter); } else { closestSub = Character.Controlled.Submarine; } bool displace = moveAmount.LengthSquared() > 100.0f * 100.0f; foreach (Submarine sub in subsToMove) { sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f); sub.PhysicsBody.LinearVelocity = newVelocity; if (displace) { sub.SubBody.DisplaceCharacters(moveAmount); } } if (closestSub != null && subsToMove.Contains(closestSub)) { GameMain.GameScreen.Cam.Position += moveAmount; if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) { GameMain.GameScreen.Cam.TargetPos += moveAmount; } if (Character.Controlled != null) { Character.Controlled.CursorPosition += moveAmount; } } return; } //if outside left or right edge of the level if (Position.X < 0 || Position.X > Level.Loaded.Size.X) { Rectangle worldBorders = Borders; worldBorders.Location += MathUtils.ToPoint(Position); //push the sub back below the upper "barrier" of the level if (worldBorders.Y > Level.Loaded.Size.Y) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Min(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.Size.Y - worldBorders.Y))); } else if (worldBorders.Y - worldBorders.Height < Level.Loaded.BottomPos) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height)))); } } //------------------------- Vector2 totalForce = CalculateBuoyancy(); if (Body.LinearVelocity.LengthSquared() > 0.0001f) { float dragCoefficient = 0.01f; float speedLength = (Body.LinearVelocity == Vector2.Zero) ? 0.0f : Body.LinearVelocity.Length(); float drag = speedLength * speedLength * dragCoefficient * Body.Mass; totalForce += -Vector2.Normalize(Body.LinearVelocity) * drag; } ApplyForce(totalForce); UpdateDepthDamage(deltaTime); }
private void SetDamage(int sectionIndex, float damage, Character attacker = null) { if (Submarine != null && Submarine.GodMode) { return; } if (!prefab.Body) { return; } if (!MathUtils.IsValid(damage)) { return; } if (GameMain.Server != null && damage != sections[sectionIndex].damage) { GameMain.Server.CreateEntityEvent(this); } bool noGaps = true; for (int i = 0; i < sections.Length; i++) { if (i != sectionIndex && SectionIsLeaking(i)) { noGaps = false; break; } } if (damage < prefab.Health * LeakThreshold) { if (sections[sectionIndex].gap != null) { //the structure doesn't have any other gap, log the structure being fixed if (noGaps && attacker != null) { GameServer.Log((sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall repaired by " + attacker.Name, ServerLog.MessageType.ItemInteraction); } //remove existing gap if damage is below 50% sections[sectionIndex].gap.Remove(); sections[sectionIndex].gap = null; #if CLIENT if (CastShadow) { GenerateConvexHull(); } #endif } } else { if (sections[sectionIndex].gap == null) { Rectangle gapRect = sections[sectionIndex].rect; gapRect.X -= 10; gapRect.Y += 10; gapRect.Width += 20; gapRect.Height += 20; sections[sectionIndex].gap = new Gap(gapRect, !isHorizontal, Submarine); sections[sectionIndex].gap.ConnectedWall = this; //AdjustKarma(attacker, 300); //the structure didn't have any other gaps yet, log the breach if (noGaps && attacker != null) { GameServer.Log((sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall breached by " + attacker.Name, ServerLog.MessageType.ItemInteraction); } #if CLIENT if (CastShadow) { GenerateConvexHull(); } #endif } float gapOpen = (damage / prefab.Health - LeakThreshold) * (1.0f / (1.0f - LeakThreshold)); sections[sectionIndex].gap.Open = gapOpen; } float damageDiff = damage - sections[sectionIndex].damage; bool hadHole = SectionBodyDisabled(sectionIndex); sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, prefab.Health); if (sections[sectionIndex].damage < prefab.Health) //otherwise it's possible to infinitely gain karma by welding fixed things { AdjustKarma(attacker, damageDiff); } bool hasHole = SectionBodyDisabled(sectionIndex); if (hadHole == hasHole) { return; } UpdateSections(); }
private void HandleLimbCollision(Contact contact, Limb limb) { if (limb.Mass > 100.0f) { Vector2 normal = Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ? Vector2.UnitY : Vector2.Normalize(Body.SimPosition - limb.SimPosition); float impact = Math.Min(Vector2.Dot(Velocity - limb.LinearVelocity, -normal), 50.0f) / 5.0f * Math.Min(limb.Mass / 200.0f, 1); ApplyImpact(impact, -normal, contact); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(impact, -normal, contact); } } //find all contacts between the limb and level walls List <Contact> levelContacts = new List <Contact>(); ContactEdge contactEdge = limb.body.FarseerBody.ContactList; while (contactEdge.Next != null) { if (contactEdge.Contact.Enabled && contactEdge.Other.UserData is VoronoiCell && contactEdge.Contact.IsTouching) { levelContacts.Add(contactEdge.Contact); } contactEdge = contactEdge.Next; } if (levelContacts.Count == 0) { return; } //if the limb is in contact with the level, apply an artifical impact to prevent the sub from bouncing on top of it //not a very realistic way to handle the collisions (makes it seem as if the characters were made of reinforced concrete), //but more realistic than bouncing and prevents using characters as "bumpers" that prevent all collision damage //TODO: apply impact damage and/or gib the character that got crushed between the sub and the level? Vector2 avgContactNormal = Vector2.Zero; foreach (Contact levelContact in levelContacts) { levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2 <Vector2> temp); //if the contact normal is pointing from the limb towards the level cell it's touching, flip the normal VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ? ((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData); var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.Center; if (Vector2.Dot(contactNormal, cellDiff) < 0) { contactNormal = -contactNormal; } avgContactNormal += contactNormal; //apply impacts at the positions where this sub is touching the limb ApplyImpact((Vector2.Dot(-Velocity, contactNormal) / 2.0f) / levelContacts.Count, contactNormal, levelContact); } avgContactNormal /= levelContacts.Count; float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); if (contactDot > 0.001f) { Vector2 velChange = Vector2.Normalize(Body.LinearVelocity) * contactDot; if (!MathUtils.IsValid(velChange)) { GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.HandleLimbCollision:" + submarine.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " + Body.LinearVelocity + ", avgContactNormal: " + avgContactNormal + ", contactDot: " + contactDot + ", velChange: " + velChange + ")"); return; } Body.LinearVelocity -= velChange; float damageAmount = contactDot * Body.Mass / limb.character.Mass; Vector2 n; FixedArray2 <Vector2> contactPos; contact.GetWorldManifold(out n, out contactPos); limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(contactPos[0]), limb, DamageType.Blunt, damageAmount, 0.0f, 0.0f, true, 0.0f); if (limb.character.IsDead) { foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints) { if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) { continue; } limb.character.AnimController.SeverLimbJoint(limbJoint); } } } }
private void SetDamage(int sectionIndex, float damage) { if (Submarine != null && Submarine.GodMode) { return; } if (!prefab.HasBody) { return; } if (!MathUtils.IsValid(damage)) { return; } if (GameMain.Server != null && damage != sections[sectionIndex].damage) { GameMain.Server.CreateEntityEvent(this); } if (damage < prefab.MaxHealth * 0.5f) { if (sections[sectionIndex].gap != null) { //remove existing gap if damage is below 50% sections[sectionIndex].gap.Remove(); sections[sectionIndex].gap = null; #if CLIENT if (CastShadow) { GenerateConvexHull(); } #endif } } else { if (sections[sectionIndex].gap == null) { Rectangle gapRect = sections[sectionIndex].rect; gapRect.X -= 10; gapRect.Y += 10; gapRect.Width += 20; gapRect.Height += 20; sections[sectionIndex].gap = new Gap(gapRect, !isHorizontal, Submarine); sections[sectionIndex].gap.ConnectedWall = this; #if CLIENT if (CastShadow) { GenerateConvexHull(); } #endif } sections[sectionIndex].gap.Open = (damage / prefab.MaxHealth - 0.5f) * 2.0f; } bool hadHole = SectionBodyDisabled(sectionIndex); sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, prefab.MaxHealth); bool hasHole = SectionBodyDisabled(sectionIndex); if (hadHole == hasHole) { return; } //if (hasHole) Explosion.ApplyExplosionForces(sections[sectionIndex].gap.WorldPosition, 500.0f, 5.0f, 0.0f, 0.0f); UpdateSections(); }
private void ApplyImpact(float impact, Vector2 direction, Vector2 impactPos, bool applyDamage = true) { if (impact < MinCollisionImpact) { return; } Vector2 impulse = direction * impact * 0.5f; impulse = impulse.ClampLength(MaxCollisionImpact); if (!MathUtils.IsValid(impulse)) { string errorMsg = "Invalid impulse in SubmarineBody.ApplyImpact: " + impulse + ". Direction: " + direction + ", body position: " + Body.SimPosition + ", impact: " + impact + "."; if (GameMain.NetworkMember != null) { errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server."; } if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.ApplyImpact:InvalidImpulse", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } #if CLIENT if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = impact * 2.0f; if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player)) { float angularVelocity = (impactPos.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y - (impactPos.Y - Body.SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X; GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f); } } #endif foreach (Character c in Character.CharacterList) { if (c.Submarine != submarine) { continue; } foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered) { continue; } limb.body.ApplyLinearImpulse(limb.Mass * impulse, 10.0f); } c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f); bool holdingOntoSomething = false; if (c.SelectedConstruction != null) { var controller = c.SelectedConstruction.GetComponent <Items.Components.Controller>(); holdingOntoSomething = controller != null && controller.LimbPositions.Any(); } //stun for up to 1 second if the impact equal or higher to the maximum impact if (impact >= MaxCollisionImpact && !holdingOntoSomething) { c.SetStun(Math.Min(impulse.Length() * 0.2f, 1.0f)); } } foreach (Item item in Item.ItemList) { if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; } item.body.ApplyLinearImpulse(item.body.Mass * impulse, 10.0f); } var damagedStructures = Explosion.RangedStructureDamage( ConvertUnits.ToDisplayUnits(impactPos), impact * 50.0f, applyDamage ? impact * ImpactDamageMultiplier : 0.0f); #if CLIENT //play a damage sound for the structure that took the most damage float maxDamage = 0.0f; Structure maxDamageStructure = null; foreach (KeyValuePair <Structure, float> structureDamage in damagedStructures) { if (maxDamageStructure == null || structureDamage.Value > maxDamage) { maxDamage = structureDamage.Value; maxDamageStructure = structureDamage.Key; } } if (maxDamageStructure != null) { SoundPlayer.PlayDamageSound( "StructureBlunt", impact * 10.0f, ConvertUnits.ToDisplayUnits(impactPos), MathHelper.Lerp(2000.0f, 10000.0f, (impact - MinCollisionImpact) / 2.0f), maxDamageStructure.Tags); } #endif }
private static void UpdateWaterAmbience(float ambienceVolume, float deltaTime) { if (GameMain.SoundManager.Disabled) { return; } //how fast the sub is moving, scaled to 0.0 -> 1.0 float movementSoundVolume = 0.0f; float insideSubFactor = 0.0f; foreach (Submarine sub in Submarine.Loaded) { float movementFactor = (sub.Velocity == Vector2.Zero) ? 0.0f : sub.Velocity.Length() / 10.0f; movementFactor = MathHelper.Clamp(movementFactor, 0.0f, 1.0f); if (Character.Controlled == null || Character.Controlled.Submarine != sub) { float dist = Vector2.Distance(GameMain.GameScreen.Cam.WorldViewCenter, sub.WorldPosition); movementFactor /= Math.Max(dist / 1000.0f, 1.0f); insideSubFactor = Math.Max(1.0f / Math.Max(dist / 1000.0f, 1.0f), insideSubFactor); } else { insideSubFactor = 1.0f; } movementSoundVolume = Math.Max(movementSoundVolume, movementFactor); if (!MathUtils.IsValid(movementSoundVolume)) { string errorMsg = "Failed to update water ambience volume - submarine's movement value invalid (" + movementSoundVolume + ", sub velocity: " + sub.Velocity + ")"; DebugConsole.Log(errorMsg); GameAnalyticsManager.AddErrorEventOnce("SoundPlayer.UpdateWaterAmbience:InvalidVolume", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); movementSoundVolume = 0.0f; } } for (int i = 0; i < 3; i++) { float volume = 0.0f; Sound sound = null; switch (i) { case 0: volume = ambienceVolume * (1.0f - movementSoundVolume) * insideSubFactor; sound = waterAmbienceIn; break; case 1: volume = ambienceVolume * movementSoundVolume * insideSubFactor; sound = waterAmbienceMoving; break; case 2: volume = 1.0f - insideSubFactor; sound = waterAmbienceOut; break; } if ((waterAmbienceChannels[i] == null || !waterAmbienceChannels[i].IsPlaying) && volume > 0.01f) { waterAmbienceChannels[i] = sound.Play(volume, "waterambience"); waterAmbienceChannels[i].Looping = true; } else if (waterAmbienceChannels[i] != null) { waterAmbienceChannels[i].Gain += deltaTime * Math.Sign(volume - waterAmbienceChannels[i].Gain); if (waterAmbienceChannels[i].Gain < 0.01f) { waterAmbienceChannels[i].FadeOutAndDispose(); } } } }
private 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; } if (onlyInside && c.Submarine == null) { continue; } else if (onlyOutside && c.Submarine != null) { continue; } Vector2 explosionPos = worldPosition; if (c.Submarine != null) { explosionPos -= c.Submarine.Position; } Hull hull = Hull.FindHull(explosionPos, null, false); bool underWater = hull == null || explosionPos.Y < hull.Surface; explosionPos = ConvertUnits.ToSimUnits(explosionPos); Dictionary <Limb, float> distFactors = new Dictionary <Limb, float>(); Dictionary <Limb, float> damages = new Dictionary <Limb, float>(); List <Affliction> modifiedAfflictions = new List <Affliction>(); foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered || limb.IgnoreCollisions || !limb.body.Enabled) { continue; } 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 = limb.body.GetMaxExtent(); 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 if (!ignoreCover) { distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition); } distFactors.Add(limb, distFactor); modifiedAfflictions.Clear(); foreach (Affliction affliction in attack.Afflictions.Keys) { //previously the damage would be divided by the number of limbs (the intention was to prevent characters with more limbs taking more damage from explosions) //that didn't work well on large characters like molochs and endworms: the explosions tend to only damage one or two of their limbs, and since the characters //have lots of limbs, they tended to only take a fraction of the damage they should //now we just divide by 10, which keeps the damage to normal-sized characters roughly the same as before and fixes the large characters modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / 10)); } 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 dir = worldPosition - limb.WorldPosition; Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f; AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker); damages.Add(limb, attackResult.Damage); 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 * 0.2f); } } if (c == Character.Controlled && !c.IsDead && playTinnitus) { Limb head = c.AnimController.GetLimb(LimbType.Head); if (head != null && damages.TryGetValue(head, out float headDamage) && headDamage > 0.0f && distFactors.TryGetValue(head, out float headFactor)) { PlayTinnitusProjSpecific(headFactor); } } //sever joints if (attack.SeverLimbsProbability > 0.0f) { foreach (Limb limb in c.AnimController.Limbs) { if (limb.character.Removed || limb.Removed) { continue; } if (limb.IsSevered) { continue; } if (!c.IsDead && !limb.CanBeSeveredAlive) { continue; } if (distFactors.TryGetValue(limb, out float distFactor)) { if (damages.TryGetValue(limb, out float damage)) { c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage, allowBeheading: true); } } } } } }
private void HandleLimbCollision(Impact collision, Limb limb) { if (limb?.body?.FarseerBody == null || limb.character == null) { return; } float impactMass = limb.Mass; var enemyAI = limb.character.AIController as EnemyAIController; float attackMultiplier = 1.0f; if (enemyAI?.ActiveAttack != null) { impactMass = Math.Max(Math.Max(limb.Mass, limb.character.AnimController.MainLimb.Mass), limb.character.AnimController.Collider.Mass); attackMultiplier = enemyAI.ActiveAttack.SubmarineImpactMultiplier; } if (impactMass * attackMultiplier > MinImpactLimbMass) { Vector2 normal = Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ? Vector2.UnitY : Vector2.Normalize(Body.SimPosition - limb.SimPosition); float impact = Math.Min(Vector2.Dot(collision.Velocity, -normal), 50.0f) * Math.Min(impactMass / 300.0f, 1); impact *= attackMultiplier; ApplyImpact(impact, normal, collision.ImpactPos, applyDamage: false); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(impact, normal, collision.ImpactPos, applyDamage: false); } } //find all contacts between the limb and level walls List <Contact> levelContacts = new List <Contact>(); ContactEdge contactEdge = limb.body.FarseerBody.ContactList; while (contactEdge?.Contact != null) { if (contactEdge.Contact.Enabled && contactEdge.Contact.IsTouching && contactEdge.Other?.UserData is VoronoiCell) { levelContacts.Add(contactEdge.Contact); } contactEdge = contactEdge.Next; } if (levelContacts.Count == 0) { return; } //if the limb is in contact with the level, apply an artifical impact to prevent the sub from bouncing on top of it //not a very realistic way to handle the collisions (makes it seem as if the characters were made of reinforced concrete), //but more realistic than bouncing and prevents using characters as "bumpers" that prevent all collision damage Vector2 avgContactNormal = Vector2.Zero; foreach (Contact levelContact in levelContacts) { levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2 <Vector2> temp); //if the contact normal is pointing from the limb towards the level cell it's touching, flip the normal VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ? ((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData); var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.Center; if (Vector2.Dot(contactNormal, cellDiff) < 0) { contactNormal = -contactNormal; } avgContactNormal += contactNormal; //apply impacts at the positions where this sub is touching the limb ApplyImpact((Vector2.Dot(-collision.Velocity, contactNormal) / 2.0f) / levelContacts.Count, contactNormal, collision.ImpactPos, applyDamage: false); } avgContactNormal /= levelContacts.Count; float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); if (contactDot > 0.001f) { Vector2 velChange = Vector2.Normalize(Body.LinearVelocity) * contactDot; if (!MathUtils.IsValid(velChange)) { GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.HandleLimbCollision:" + submarine.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " + Body.LinearVelocity + ", avgContactNormal: " + avgContactNormal + ", contactDot: " + contactDot + ", velChange: " + velChange + ")"); return; } Body.LinearVelocity -= velChange; if (contactDot > 0.1f) { float damageAmount = contactDot * Body.Mass / limb.character.Mass; limb.character.LastDamageSource = submarine; limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb, AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f); if (limb.character.IsDead) { foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints) { if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) { continue; } limb.character.AnimController.SeverLimbJoint(limbJoint); } } } } }
public static List <VertexPositionTexture> GenerateWallEdgeVertices(List <VoronoiCell> cells, Level level, float zCoord) { float outWardThickness = level.GenerationParams.WallEdgeExpandOutwardsAmount; List <VertexPositionTexture> vertices = new List <VertexPositionTexture>(); foreach (VoronoiCell cell in cells) { Vector2 minVert = cell.Edges[0].Point1; Vector2 maxVert = cell.Edges[0].Point1; float circumference = 0.0f; foreach (GraphEdge edge in cell.Edges) { circumference += Vector2.Distance(edge.Point1, edge.Point2); minVert = new Vector2( Math.Min(minVert.X, edge.Point1.X), Math.Min(minVert.Y, edge.Point1.Y)); maxVert = new Vector2( Math.Max(maxVert.X, edge.Point1.X), Math.Max(maxVert.Y, edge.Point1.Y)); } Vector2 center = (minVert + maxVert) / 2; foreach (GraphEdge edge in cell.Edges) { if (!edge.IsSolid) { continue; } GraphEdge leftEdge = cell.Edges.Find(e => e != edge && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2))); var leftAdjacentCell = leftEdge?.AdjacentCell(cell); if (leftAdjacentCell != null) { var adjEdge = leftAdjacentCell.Edges.Find(e => e != leftEdge && e.IsSolid && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2))); if (adjEdge != null) { leftEdge = adjEdge; } } GraphEdge rightEdge = cell.Edges.Find(e => e != edge && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2))); var rightAdjacentCell = rightEdge?.AdjacentCell(cell); if (rightAdjacentCell != null) { var adjEdge = rightAdjacentCell.Edges.Find(e => e != rightEdge && e.IsSolid && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2))); if (adjEdge != null) { rightEdge = adjEdge; } } Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero; float inwardThickness1 = level.GenerationParams.WallEdgeExpandInwardsAmount; float inwardThickness2 = level.GenerationParams.WallEdgeExpandInwardsAmount; if (leftEdge != null && !leftEdge.IsSolid) { leftNormal = edge.Point1.NearlyEquals(leftEdge.Point1) ? Vector2.Normalize(leftEdge.Point2 - leftEdge.Point1) : Vector2.Normalize(leftEdge.Point1 - leftEdge.Point2); } else if (leftEdge != null) { leftNormal = -Vector2.Normalize(edge.GetNormal(cell) + leftEdge.GetNormal(leftAdjacentCell ?? cell)); if (!MathUtils.IsValid(leftNormal)) { leftNormal = -edge.GetNormal(cell); } } else { leftNormal = Vector2.Normalize(cell.Center - edge.Point1); } inwardThickness1 = Math.Min(Vector2.Distance(edge.Point1, cell.Center), inwardThickness1); if (!MathUtils.IsValid(leftNormal)) { #if DEBUG DebugConsole.ThrowError("Invalid left normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidLeftNormal:" + level.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid left normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + leftNormal + ", seed: " + level.Seed + ")"); if (cell.Body != null) { if (GameMain.World.BodyList.Contains(cell.Body)) { GameMain.World.Remove(cell.Body); } cell.Body = null; } leftNormal = Vector2.UnitX; break; } if (rightEdge != null && !rightEdge.IsSolid) { rightNormal = edge.Point2.NearlyEquals(rightEdge.Point1) ? Vector2.Normalize(rightEdge.Point2 - rightEdge.Point1) : Vector2.Normalize(rightEdge.Point1 - rightEdge.Point2); } else if (rightEdge != null) { rightNormal = -Vector2.Normalize(edge.GetNormal(cell) + rightEdge.GetNormal(rightAdjacentCell ?? cell)); if (!MathUtils.IsValid(rightNormal)) { rightNormal = -edge.GetNormal(cell); } } else { rightNormal = Vector2.Normalize(cell.Center - edge.Point2); } inwardThickness2 = Math.Min(Vector2.Distance(edge.Point2, cell.Center), inwardThickness2); if (!MathUtils.IsValid(rightNormal)) { #if DEBUG DebugConsole.ThrowError("Invalid right normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidRightNormal:" + level.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid right normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + rightNormal + ", seed: " + level.Seed + ")"); if (cell.Body != null) { if (GameMain.World.BodyList.Contains(cell.Body)) { GameMain.World.Remove(cell.Body); } cell.Body = null; } rightNormal = Vector2.UnitX; break; } float point1UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point1 - center)); float point2UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point2 - center)); //handle wrapping around 0/360 if (point1UV - point2UV > MathHelper.Pi) { point1UV -= MathHelper.TwoPi; } int textureRepeatCount = (int)Math.Max(circumference / 2 / level.GenerationParams.WallEdgeTextureWidth, 1); point1UV = point1UV / MathHelper.TwoPi * textureRepeatCount; point2UV = point2UV / MathHelper.TwoPi * textureRepeatCount; for (int i = 0; i < 2; i++) { Vector2[] verts = new Vector2[3]; VertexPositionTexture[] vertPos = new VertexPositionTexture[3]; if (i == 0) { verts[0] = edge.Point1 - leftNormal * outWardThickness; verts[1] = edge.Point2 - rightNormal * outWardThickness; verts[2] = edge.Point1 + leftNormal * inwardThickness1; vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 0.0f)); vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f)); vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point1UV, 1.0f)); } else { verts[0] = edge.Point1 + leftNormal * inwardThickness1; verts[1] = edge.Point2 - rightNormal * outWardThickness; verts[2] = edge.Point2 + rightNormal * inwardThickness2; vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 1.0f)); vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f)); vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point2UV, 1.0f)); } vertices.AddRange(vertPos); } } } return(vertices); }
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(explosionPos, null, false); bool underWater = hull == null || explosionPos.Y < hull.Surface; explosionPos = ConvertUnits.ToSimUnits(explosionPos); Dictionary <Limb, float> distFactors = new Dictionary <Limb, float>(); Dictionary <Limb, float> damages = 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 = limb.body.GetMaxExtent(); 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>(); int limbCount = c.AnimController.Limbs.Count(l => !l.IsSevered && !l.ignoreCollisions); foreach (Affliction affliction in attack.Afflictions.Keys) { modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / limbCount)); } 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; AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker); damages.Add(limb, attackResult.Damage); 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 * 0.2f); } } //sever joints if (attack.SeverLimbsProbability > 0.0f) { foreach (Limb limb in c.AnimController.Limbs) { if (limb.character.Removed || limb.Removed) { continue; } if (limb.IsSevered) { continue; } if (!c.IsDead && !limb.CanBeSeveredAlive) { continue; } if (distFactors.TryGetValue(limb, out float distFactor)) { if (damages.TryGetValue(limb, out float damage)) { c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage, allowBeheading: true); } } } } } }
protected override void Draw(SpriteBatch spriteBatch) { if (!Visible) { return; } if (ProgressGetter != null) { float newSize = MathHelper.Clamp(ProgressGetter(), 0.0f, 1.0f); if (!MathUtils.IsValid(newSize)) { GameAnalyticsManager.AddErrorEventOnce( "GUIProgressBar.Draw:GetProgress", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "ProgressGetter of a GUIProgressBar (" + ProgressGetter.Target.ToString() + " - " + ProgressGetter.Method.ToString() + ") returned an invalid value (" + newSize + ")\n" + Environment.StackTrace); } else { BarSize = newSize; } } Rectangle sliderRect = new Rectangle( frame.Rect.X, (int)(frame.Rect.Y + (isHorizontal ? 0 : frame.Rect.Height * (1.0f - barSize))), isHorizontal ? (int)((frame.Rect.Width) * barSize) : frame.Rect.Width, isHorizontal ? (int)(frame.Rect.Height) : (int)(frame.Rect.Height * barSize)); frame.Visible = true; slider.Visible = true; if (AutoDraw) { frame.DrawAuto(spriteBatch); } else { frame.DrawManually(spriteBatch); } Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; if (BarSize <= 1.0f) { spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, sliderRect); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); } Color currColor = GetCurrentColor(state); slider.Color = currColor; if (AutoDraw) { slider.DrawAuto(spriteBatch); } else { slider.DrawManually(spriteBatch); } //hide the slider, we've already drawn it manually frame.Visible = false; slider.Visible = false; if (BarSize <= 1.0f) { spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; } }