From 8c559f716f1a1610f6a3e8fb214d26ecb886b45f Mon Sep 17 00:00:00 2001 From: Regalis Date: Thu, 20 Aug 2015 00:42:24 +0300 Subject: [PATCH] Progress on tutorial, misc bugfixes --- .../Source/Characters/AI/AIController.cs | 2 +- .../Source/Characters/AI/EnemyAIController.cs | 7 +- .../Source/Characters/AnimController.cs | 6 +- Subsurface/Source/Characters/Character.cs | 27 +++- .../Characters/HumanoidAnimController.cs | 10 +- Subsurface/Source/Characters/Ragdoll.cs | 6 +- .../GameSession/GameModes/TutorialMode.cs | 151 ++++++++++++++++-- Subsurface/Source/Items/Components/Label.cs | 5 +- .../Source/Items/Components/Machines/Pump.cs | 25 +-- .../Items/Components/Machines/Reactor.cs | 49 ++++-- .../Items/Components/Machines/Steering.cs | 5 +- Subsurface/Source/Map/Entity.cs | 5 + Subsurface/Source/Map/Submarine.cs | 7 +- Subsurface/Source/Networking/GameClient.cs | 29 +++- Subsurface/Source/Networking/GameServer.cs | 41 ++++- Subsurface/Source/Networking/NetworkEvent.cs | 5 +- Subsurface/Source/Physics/PhysicsBody.cs | 49 ++++-- Subsurface/Source/Utils/MathUtils.cs | 12 ++ Subsurface_Solution.v12.suo | Bin 648192 -> 655872 bytes 19 files changed, 356 insertions(+), 85 deletions(-) diff --git a/Subsurface/Source/Characters/AI/AIController.cs b/Subsurface/Source/Characters/AI/AIController.cs index f399f170f..53aed1da8 100644 --- a/Subsurface/Source/Characters/AI/AIController.cs +++ b/Subsurface/Source/Characters/AI/AIController.cs @@ -43,7 +43,7 @@ namespace Subsurface steeringManager = new SteeringManager(this); } - public virtual void SelectTarget(IDamageable target) { } + public virtual void SelectTarget(AITarget target) { } public virtual void Update(float deltaTime) { } diff --git a/Subsurface/Source/Characters/AI/EnemyAIController.cs b/Subsurface/Source/Characters/AI/EnemyAIController.cs index d0604970f..4eda0150f 100644 --- a/Subsurface/Source/Characters/AI/EnemyAIController.cs +++ b/Subsurface/Source/Characters/AI/EnemyAIController.cs @@ -82,11 +82,10 @@ namespace Subsurface state = AiState.None; } - public override void SelectTarget(IDamageable target) + public override void SelectTarget(AITarget target) { - targetEntity = target; - selectedAiTarget = target.AiTarget; - selectedTargetMemory = FindTargetMemory(target.AiTarget); + selectedAiTarget = target; + selectedTargetMemory = FindTargetMemory(target); targetValue = 100.0f; } diff --git a/Subsurface/Source/Characters/AnimController.cs b/Subsurface/Source/Characters/AnimController.cs index 2dc202f8d..3ed46bc4e 100644 --- a/Subsurface/Source/Characters/AnimController.cs +++ b/Subsurface/Source/Characters/AnimController.cs @@ -31,7 +31,11 @@ namespace Subsurface public float StunTimer { get { return stunTimer; } - set { stunTimer = value; } + set + { + if (float.IsNaN(value) || float.IsInfinity(value)) return; + stunTimer = value; + } } public AnimController(Character character, XElement element) diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index 37462d66e..0760b87c1 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -1024,6 +1024,10 @@ namespace Subsurface { return; } + else if (type== NetworkEventType.NotMoving) + { + return; + } //if (type == Networking.NetworkEventType.KeyHit) @@ -1057,11 +1061,11 @@ namespace Subsurface message.Write(limb.body.Position.X); message.Write(limb.body.Position.Y); - message.Write(limb.body.LinearVelocity.X); - message.Write(limb.body.LinearVelocity.Y); + //message.Write(limb.body.LinearVelocity.X); + //message.Write(limb.body.LinearVelocity.Y); message.Write(limb.body.Rotation); - message.Write(limb.body.AngularVelocity); + //message.WriteRangedSingle(MathHelper.Clamp(limb.body.AngularVelocity, -10.0f, 10.0f), -10.0f, 10.0f, 8); i++; } @@ -1118,6 +1122,13 @@ namespace Subsurface } return; } + else if (type == NetworkEventType.NotMoving) + { + AnimController.TargetMovement = Vector2.Zero; + actionKeyDown.State = false; + secondaryKeyDown.State = false; + return; + } bool actionKeyState = false; bool secondaryKeyState = false; @@ -1176,11 +1187,11 @@ namespace Subsurface pos.X = message.ReadFloat(); pos.Y = message.ReadFloat(); - vel.X = message.ReadFloat(); - vel.Y = message.ReadFloat(); + //vel.X = message.ReadFloat(); + //vel.Y = message.ReadFloat(); rotation = message.ReadFloat(); - angularVel = message.ReadFloat(); + //angularVel = message.ReadFloat(); } catch { @@ -1189,10 +1200,10 @@ namespace Subsurface if (limb.body != null) { - limb.body.TargetVelocity = vel; + limb.body.TargetVelocity = limb.body.LinearVelocity; limb.body.TargetPosition = pos;// +vel * (float)(deltaTime / 60.0); limb.body.TargetRotation = rotation;// +angularVel * (float)(deltaTime / 60.0); - limb.body.TargetAngularVelocity = angularVel; + limb.body.TargetAngularVelocity = limb.body.AngularVelocity; } } diff --git a/Subsurface/Source/Characters/HumanoidAnimController.cs b/Subsurface/Source/Characters/HumanoidAnimController.cs index a887f35e3..93f618a1c 100644 --- a/Subsurface/Source/Characters/HumanoidAnimController.cs +++ b/Subsurface/Source/Characters/HumanoidAnimController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Xml.Linq; using FarseerPhysics; using Microsoft.Xna.Framework; +using Subsurface.Items.Components; namespace Subsurface { @@ -542,7 +543,7 @@ namespace Subsurface void UpdateClimbing() { - if (character.SelectedConstruction == null) + if (character.SelectedConstruction == null || character.SelectedConstruction.GetComponent()==null) { Anim = Animation.None; return; @@ -623,7 +624,12 @@ namespace Subsurface torso.body.ApplyForce(climbForce * 40.0f * torso.Mass); head.body.SmoothRotate(0.0f); - Rectangle trigger = character.SelectedConstruction.Prefab.Triggers.First(); + Rectangle trigger = character.SelectedConstruction.Prefab.Triggers.FirstOrDefault(); + if (trigger == null) + { + character.SelectedConstruction = null; + return; + } trigger = character.SelectedConstruction.TransformTrigger(trigger); //stop climbing if: diff --git a/Subsurface/Source/Characters/Ragdoll.cs b/Subsurface/Source/Characters/Ragdoll.cs index 0efc2b6b3..3187ed605 100644 --- a/Subsurface/Source/Characters/Ragdoll.cs +++ b/Subsurface/Source/Characters/Ragdoll.cs @@ -472,7 +472,7 @@ namespace Subsurface inWater = false; headInWater = false; - if (currentHull.Volume>currentHull.FullVolume*0.95f || ConvertUnits.ToSimUnits(currentHull.Surface)-floorY> HeadPosition*0.95f) + if (currentHull.Volume > currentHull.FullVolume * 0.95f || ConvertUnits.ToSimUnits(currentHull.Surface) - floorY > HeadPosition * 0.95f) inWater = true; } @@ -562,7 +562,7 @@ namespace Subsurface private void UpdateNetplayerPosition() { Limb refLimb = GetLimb(LimbType.Torso); - if (refLimb== null) refLimb = GetLimb(LimbType.Head); + if (refLimb == null) refLimb = GetLimb(LimbType.Head); if (refLimb.body.TargetPosition == Vector2.Zero) return; @@ -603,7 +603,7 @@ namespace Subsurface if (resetAll) { - System.Diagnostics.Debug.WriteLine("resetall"); + System.Diagnostics.Debug.WriteLine("reset ragdoll limb positions"); foreach (Limb limb in limbs) { diff --git a/Subsurface/Source/GameSession/GameModes/TutorialMode.cs b/Subsurface/Source/GameSession/GameModes/TutorialMode.cs index 7a83faa6f..dd82ff8d9 100644 --- a/Subsurface/Source/GameSession/GameModes/TutorialMode.cs +++ b/Subsurface/Source/GameSession/GameModes/TutorialMode.cs @@ -22,6 +22,8 @@ namespace Subsurface Game1.GameSession.StartShift(TimeSpan.Zero, "tutorial"); + Game1.GameSession.taskManager.Tasks.Clear(); + Game1.GameScreen.Select(); } @@ -184,7 +186,7 @@ namespace Subsurface + " going into the to the power connection - that's why the monitor isn't working." + " You should find a piece of wire to connect it. Try searching some of the cabinets scattered around the sub."); - while (Character.Controlled.Inventory.items.FirstOrDefault(i => i!=null && i.GetComponent()!=null)==null) + while (!HasItem("Wire")) { yield return Status.Running; } @@ -280,32 +282,159 @@ namespace Subsurface var moloch = new Character("Content/Characters/Moloch/moloch.xml", steering.Item.SimPosition + Vector2.UnitX * 15.0f); moloch.PlaySound(AIController.AiState.Attack); - //moloch.AIController. - infoBox = CreateInfoFrame("Uh-oh... Something enormous just appeared on the radar."); - Structure window = null; + List windows = new List(); foreach (Structure s in Structure.wallList) { - if (s.CastShadow) continue; + if (s.CastShadow || !s.HasBody) continue; - if (window == null || s.Rect.Right > window.Rect.Right) window = s; + if (s.Rect.Right > steering.Item.Position.X) windows.Add(s); } bool broken = false; do { - moloch.AIController.SelectTarget(steering.Item); - for (int i = 0; i < window.SectionCount; i++) + moloch.AIController.SelectTarget(steering.Item.CurrentHull.AiTarget); + Vector2 steeringDir = windows[0].Position - moloch.Position; + if (steeringDir != Vector2.Zero) steeringDir = Vector2.Normalize(steeringDir); + + foreach (Limb limb in moloch.AnimController.limbs) { - if (!window.SectionHasHole(i)) continue; - broken = true; - break; + limb.body.LinearVelocity = new Vector2(limb.LinearVelocity.X, limb.LinearVelocity.Y + steeringDir.Y*0.01f); } + moloch.AIController.Steering = steeringDir; + + foreach (Structure window in windows) + { + for (int i = 0; i < window.SectionCount; i++) + { + if (!window.SectionHasHole(i)) continue; + broken = true; + break; + } + if (broken) break; + } + + yield return new WaitForSeconds(1.0f); } while (!broken); + yield return new WaitForSeconds(1.0f); + + + var capacitor1 = Item.itemList.Find(i => i.HasTag("capacitor1")).GetComponent(); + var capacitor2 = Item.itemList.Find(i => i.HasTag("capacitor1")).GetComponent(); + CoroutineManager.StartCoroutine(KeepEnemyAway(moloch, new PowerContainer[] { capacitor1, capacitor2 })); + + + infoBox = CreateInfoFrame("The hull has been breached! Close all the doors to the command room to stop the water from flooding the entire sub!"); + + + Door commandDoor1 = Item.itemList.Find(i => i.HasTag("commanddoor1")).GetComponent(); + Door commandDoor2 = Item.itemList.Find(i => i.HasTag("commanddoor2")).GetComponent(); + Door commandDoor3 = Item.itemList.Find(i => i.HasTag("commanddoor3")).GetComponent(); + + while (commandDoor1.IsOpen && (commandDoor2.IsOpen || commandDoor3.IsOpen)) + { + yield return Status.Running; + } + + infoBox = CreateInfoFrame("Great! You should find yourself an diving mask or a diving suit, in case the creature causes more damage. "+ + "There are some in the room next to the airlock."); + + while (!HasItem("Diving Mask") && !HasItem("Diving Suit")) + { + yield return Status.Running; + } + + if (HasItem("Diving Mask")) + { + infoBox = CreateInfoFrame("The diving mask will let you breathe underwater, but it won't protect from the water pressure outside the sub. "+ + "It should be fine for the situation at hand, but you still need to find an oxygen tank and drag it into the same slot as the mask." + + "You should grab one or two."); + } + else if (HasItem("Diving Suit")) + { + infoBox = CreateInfoFrame("In addition to letting you breathe underwater, the suit will protect you from the water pressure outside the sub " + + "(unlike the diving mask). However, you still need to drag an oxygen tank into the same slot as the suit to supply oxygen. "+ + "You should grab one or two."); + } + + while (!HasItem("Oxygen Tank")) + { + yield return Status.Running; + } + + yield return new WaitForSeconds(5.0f); + + infoBox = CreateInfoFrame("Now it's time to stop the creature attacking the submarine. Head to the railgun room at the upper right corner of the sub."); + + var railGun = Item.itemList.Find(i => i.GetComponent()!=null); + + while (Vector2.Distance(Character.Controlled.Position, railGun.Position)>500) + { + yield return new WaitForSeconds(1.0f); + } + + infoBox = CreateInfoFrame("The railgun requires a large power surge to fire. The reactor can't provide a surge large enough, so we need to use the " + +" supercapacitors in the railgun room. The capacitors need to be charged first; select them and crank up the recharge rate."); + + while (capacitor1.RechargeSpeed<0.5f && capacitor2.RechargeSpeed<0.5f) + { + yield return new WaitForSeconds(1.0f); + } + + infoBox = CreateInfoFrame("The capacitors consume large amounts of power when they're being charged at a high rate. "+ + "Be cautious to overload the electrical grid or the reactor. They also take some time to recharge, so now is a good "+ + "time to head to the room below to load some shells into the railgun."); + + + var loader = Item.itemList.Find(i => i.Name == "Railgun Loader").GetComponent(); + + while (Math.Abs(Character.Controlled.Position.Y - loader.Item.Position.Y)>50) + { + yield return Status.Running; + } + + infoBox = CreateInfoFrame("Grab one of the shells. You can load it by selecting the railgun loader and dragging the shell to. " + +"one of the free slots."); + + while (loader.Item.ContainedItems.FirstOrDefault(i => i != null) != null) + { + capacitor1.Charge += 1.0f; + capacitor2.Charge += 1.0f; + yield return Status.Running; + } + + + yield return Status.Success; + } + + private bool HasItem(string itemName) + { + if (Character.Controlled == null) return false; + return Character.Controlled.Inventory.items.FirstOrDefault(i => i != null && i.Name == itemName)!=null; + } + + /// + /// keeps the enemy away from the sub until the capacitors are loaded + /// + private IEnumerable KeepEnemyAway(Character enemy, PowerContainer[] capacitors) + { + do + { + Vector2 targetPos = Character.Controlled.Position + new Vector2(0.0f, 3000.0f); + + Vector2 steering = targetPos - enemy.Position; + if (steering != Vector2.Zero) steering = Vector2.Normalize(steering); + + enemy.AIController.Steering = steering*2.0f; + + yield return Status.Running; + } while (capacitors.FirstOrDefault(c => c.Charge > 0.4f) == null); + yield return Status.Success; } diff --git a/Subsurface/Source/Items/Components/Label.cs b/Subsurface/Source/Items/Components/Label.cs index 411fbdeee..dbc63f08b 100644 --- a/Subsurface/Source/Items/Components/Label.cs +++ b/Subsurface/Source/Items/Components/Label.cs @@ -92,8 +92,11 @@ namespace Subsurface.Items.Components newText = message.ReadString(); } - catch + catch (Exception e) { +#if DEBUG + DebugConsole.ThrowError("invalid network message", e); +#endif return; } diff --git a/Subsurface/Source/Items/Components/Machines/Pump.cs b/Subsurface/Source/Items/Components/Machines/Pump.cs index d162e1e42..c320f0944 100644 --- a/Subsurface/Source/Items/Components/Machines/Pump.cs +++ b/Subsurface/Source/Items/Components/Machines/Pump.cs @@ -22,8 +22,9 @@ namespace Subsurface.Items.Components get { return flowPercentage; } set { - if (float.IsNaN(flowPercentage)) return; - flowPercentage = MathHelper.Clamp(value,-100.0f,100.0f); + if (!MathUtils.IsValid(flowPercentage)) return; + flowPercentage = MathHelper.Clamp(value,-100.0f,100.0f); + flowPercentage = MathUtils.Round(flowPercentage, 1.0f); } } @@ -117,14 +118,14 @@ namespace Subsurface.Items.Components spriteBatch.DrawString(GUI.Font, "Flow percentage: " + (int)flowPercentage + " %", new Vector2(x + 20, y + 80), Color.White); - if (GUI.DrawButton(spriteBatch, new Rectangle(x + 200, y + 70, 40, 40), "+", true)) + if (GUI.DrawButton(spriteBatch, new Rectangle(x + 200, y + 70, 40, 40), "+", false)) { - FlowPercentage += 1.0f; + FlowPercentage += 10.0f; item.NewComponentEvent(this, true); } - if (GUI.DrawButton(spriteBatch, new Rectangle(x + 250, y + 70, 40, 40), "-", true)) + if (GUI.DrawButton(spriteBatch, new Rectangle(x + 250, y + 70, 40, 40), "-", false)) { - FlowPercentage -= 1.0f; + FlowPercentage -= 10.0f; item.NewComponentEvent(this, true); } @@ -166,7 +167,7 @@ namespace Subsurface.Items.Components public override void FillNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetOutgoingMessage message) { - message.Write(flowPercentage); + message.Write(Convert.ToByte(flowPercentage+100)); message.Write(isActive); } @@ -177,11 +178,17 @@ namespace Subsurface.Items.Components try { - newFlow = message.ReadFloat(); + newFlow = (float)(message.ReadByte()-100); newActive = message.ReadBoolean(); } - catch { return; } + catch (Exception e) + { +#if DEBUG + DebugConsole.ThrowError("invalid network message", e); +#endif + return; + } FlowPercentage = newFlow; isActive = newActive; diff --git a/Subsurface/Source/Items/Components/Machines/Reactor.cs b/Subsurface/Source/Items/Components/Machines/Reactor.cs index a2351be9d..b5bc0a300 100644 --- a/Subsurface/Source/Items/Components/Machines/Reactor.cs +++ b/Subsurface/Source/Items/Components/Machines/Reactor.cs @@ -68,19 +68,31 @@ namespace Subsurface.Items.Components public float FissionRate { get { return fissionRate; } - set { fissionRate = MathHelper.Clamp(value, 0.0f, 100.0f); } + set + { + if (!MathUtils.IsValid(value)) return; + fissionRate = MathHelper.Clamp(value, 0.0f, 100.0f); + } } public float CoolingRate { get { return coolingRate; } - set { coolingRate = MathHelper.Clamp(value, 0.0f, 100.0f); } + set + { + if (!MathUtils.IsValid(value)) return; + coolingRate = MathHelper.Clamp(value, 0.0f, 100.0f); + } } public float Temperature { get { return temperature; } - set { temperature = MathHelper.Clamp(value, 0.0f, 10000.0f); } + set + { + if (!MathUtils.IsValid(value)) return; + temperature = MathHelper.Clamp(value, 0.0f, 10000.0f); + } } public bool IsRunning() @@ -100,6 +112,7 @@ namespace Subsurface.Items.Components public float ShutDownTemp { get { return shutDownTemp; } + private set { shutDownTemp = MathHelper.Clamp(value, 0.0f, 10000.0f); } } public Reactor(Item item, XElement element) @@ -334,12 +347,12 @@ namespace Subsurface.Items.Components if (GUI.DrawButton(spriteBatch, new Rectangle(x + 400, y + 180, 40, 40), "+", true)) { valueChanged = true; - shutDownTemp += 100.0f; + ShutDownTemp += 100.0f; } if (GUI.DrawButton(spriteBatch, new Rectangle(x + 450, y + 180, 40, 40), "-", true)) { valueChanged = true; - shutDownTemp -= 100.0f; + ShutDownTemp -= 100.0f; } if (valueChanged) @@ -392,11 +405,11 @@ namespace Subsurface.Items.Components public override void FillNetworkData(NetworkEventType type, NetOutgoingMessage message) { message.Write(autoTemp); - message.Write(temperature); - message.Write(shutDownTemp); + message.WriteRangedSingle(temperature, 0.0f, 10000.0f, 16); + message.WriteRangedSingle(shutDownTemp, 0.0f, 10000.0f, 16); - message.Write(coolingRate); - message.Write(fissionRate); + message.WriteRangedSingle(coolingRate, 0.0f, 100.0f, 8); + message.WriteRangedSingle(fissionRate, 0.0f, 100.0f, 8); } public override void ReadNetworkData(NetworkEventType type, NetIncomingMessage message) @@ -408,18 +421,24 @@ namespace Subsurface.Items.Components try { newAutoTemp = message.ReadBoolean(); - newTemperature = message.ReadFloat(); - newShutDownTemp = message.ReadFloat(); + newTemperature = message.ReadRangedSingle(0.0f, 10000.0f, 16); + newShutDownTemp = message.ReadRangedSingle(0.0f, 10000.0f, 16); - newCoolingRate = message.ReadFloat(); - newFissionRate = message.ReadFloat(); + newCoolingRate = message.ReadRangedSingle(0.0f, 100.0f, 8); + newFissionRate = message.ReadRangedSingle(0.0f, 100.0f, 8); } - catch { return; } + catch (Exception e) + { +#if DEBUG + DebugConsole.ThrowError("invalid network message", e); +#endif + return; + } autoTemp = newAutoTemp; Temperature = newTemperature; - shutDownTemp = newShutDownTemp; + ShutDownTemp = newShutDownTemp; CoolingRate = newCoolingRate; FissionRate = newFissionRate; diff --git a/Subsurface/Source/Items/Components/Machines/Steering.cs b/Subsurface/Source/Items/Components/Machines/Steering.cs index 0caf77329..5bb97e7c8 100644 --- a/Subsurface/Source/Items/Components/Machines/Steering.cs +++ b/Subsurface/Source/Items/Components/Machines/Steering.cs @@ -48,10 +48,7 @@ namespace Subsurface.Items.Components get { return targetVelocity;} set { - if (float.IsNaN(value.X) || float.IsNaN(value.Y)) - { - return; - } + if (!MathUtils.IsValid(value)) return; targetVelocity.X = MathHelper.Clamp(value.X, -100.0f, 100.0f); targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f); } diff --git a/Subsurface/Source/Map/Entity.cs b/Subsurface/Source/Map/Entity.cs index bd2dc940d..375f796fd 100644 --- a/Subsurface/Source/Map/Entity.cs +++ b/Subsurface/Source/Map/Entity.cs @@ -45,6 +45,11 @@ namespace Subsurface get { return Vector2.Zero; } } + public AITarget AiTarget + { + get { return aiTarget; } + } + public Entity() { //give an unique ID diff --git a/Subsurface/Source/Map/Submarine.cs b/Subsurface/Source/Map/Submarine.cs index 4d09ddcb1..e520bc943 100644 --- a/Subsurface/Source/Map/Submarine.cs +++ b/Subsurface/Source/Map/Submarine.cs @@ -505,7 +505,7 @@ namespace Subsurface private void Translate(Vector2 amount) { - if (amount == Vector2.Zero || ! amount.IsValid()) return; + if (amount == Vector2.Zero || !amount.IsValid()) return; Level.Loaded.Move(-amount); } @@ -587,8 +587,11 @@ namespace Subsurface newSpeed = new Vector2(message.ReadFloat(), message.ReadFloat()); } - catch + catch (Exception e) { +#if DEBUG + DebugConsole.ThrowError("invalid network message", e); +#endif return; } diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 7e9b882ce..b1f1d0936 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -72,13 +72,18 @@ namespace Subsurface.Networking myCharacter = Character.Controlled; // Create new instance of configs. Parameter is "application Id". It has to be same on client and server. - NetPeerConfiguration Config = new NetPeerConfiguration("subsurface"); - - //Config.SimulatedLoss = 0.2f; - //Config.SimulatedMinimumLatency = 0.5f; + NetPeerConfiguration config = new NetPeerConfiguration("subsurface"); + +#if DEBUG + config.SimulatedLoss = 0.2f; + config.SimulatedMinimumLatency = 0.3f; +#endif + + config.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt + | NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error); // Create new client, with previously created configs - client = new NetClient(Config); + client = new NetClient(config); NetOutgoingMessage outmsg = client.CreateMessage(); client.Start(); @@ -267,9 +272,19 @@ namespace Subsurface.Networking Character.Controlled = null; Game1.GameScreen.Cam.TargetPos = Vector2.Zero; } - else + else if (gameStarted) { - if (gameStarted) new NetworkEvent(myCharacter.ID, true); + Vector2 charMovement = myCharacter.AnimController.TargetMovement; + if ((charMovement==Vector2.Zero || charMovement.Length()<0.001f) && + !myCharacter.ActionKeyDown.State && !myCharacter.SecondaryKeyDown.State) + { + new NetworkEvent(NetworkEventType.NotMoving, myCharacter.ID, true); + + } + else + { + new NetworkEvent(myCharacter.ID, true); + } } } diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index 4472cbf9a..65d548fd3 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -30,7 +30,7 @@ namespace Subsurface.Networking private Client myClient; - public GameServer(string name, int port, bool isPublic = false, string password="", bool attemptUPnP = false, int maxPlayers = 10) + public GameServer(string name, int port, bool isPublic = false, string password = "", bool attemptUPnP = false, int maxPlayers = 10) { var endRoundButton = new GUIButton(new Rectangle(Game1.GraphicsWidth - 290, 20, 150, 25), "End round", Alignment.TopLeft, GUI.style, inGameHUD); endRoundButton.OnClicked = EndButtonHit; @@ -41,8 +41,10 @@ namespace Subsurface.Networking config = new NetPeerConfiguration("subsurface"); - //config.SimulatedLoss = 0.2f; - //config.SimulatedMinimumLatency = 0.5f; +#if DEBUG + config.SimulatedLoss = 0.2f; + config.SimulatedMinimumLatency = 0.3f; +#endif config.Port = port; Port = port; @@ -53,6 +55,9 @@ namespace Subsurface.Networking } config.MaximumConnections = maxPlayers; + + config.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt + | NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error); config.EnableMessageType(NetIncomingMessageType.ConnectionApproval); @@ -457,6 +462,31 @@ namespace Subsurface.Networking { //System.Diagnostics.Debug.WriteLine("networkevent "+networkEvent.ID); + List recipients = new List(); + + if (!networkEvent.IsImportant) + { + Entity e = Entity.FindEntityByID(networkEvent.ID); + foreach (Client c in connectedClients) + { + if (c.character==null) continue; + if (Vector2.Distance(e.SimPosition, c.character.SimPosition) > 2000.0f) continue; + + recipients.Add(c.Connection); + } + } + else + { + foreach (Client c in connectedClients) + { + if (c.character == null) continue; + + recipients.Add(c.Connection); + } + } + + if (recipients.Count == 0) return; + NetOutgoingMessage message = server.CreateMessage(); message.Write((byte)PacketTypes.NetworkEvent); //if (!networkEvent.IsClient) continue; @@ -467,7 +497,7 @@ namespace Subsurface.Networking if (server.ConnectionsCount>0) { - server.SendMessage(message, server.Connections, + server.SendMessage(message, recipients, (networkEvent.IsImportant) ? NetDeliveryMethod.Unreliable : NetDeliveryMethod.ReliableUnordered, 0); } @@ -703,8 +733,7 @@ namespace Subsurface.Networking spriteBatch.DrawString(GUI.SmallFont, "Sent bytes: " + server.Statistics.SentBytes, new Vector2(x + 10, y + 75), Color.White); spriteBatch.DrawString(GUI.SmallFont, "Sent packets: " + server.Statistics.SentPackets, new Vector2(x + 10, y + 90), Color.White); - - + y += 110; foreach (Client c in connectedClients) { diff --git a/Subsurface/Source/Networking/NetworkEvent.cs b/Subsurface/Source/Networking/NetworkEvent.cs index 47daaed52..37e69e849 100644 --- a/Subsurface/Source/Networking/NetworkEvent.cs +++ b/Subsurface/Source/Networking/NetworkEvent.cs @@ -11,14 +11,15 @@ namespace Subsurface.Networking DropItem = 3, InventoryUpdate = 4, PickItem = 5, - UpdateProperty = 6 + UpdateProperty = 6, + NotMoving = 7 } class NetworkEvent { public static List events = new List(); - private static bool[] isImportant = { false, true, false, true, true, true }; + private static bool[] isImportant = { false, true, false, true, true, true, true, false }; private int id; diff --git a/Subsurface/Source/Physics/PhysicsBody.cs b/Subsurface/Source/Physics/PhysicsBody.cs index 8af21e7f0..562397974 100644 --- a/Subsurface/Source/Physics/PhysicsBody.cs +++ b/Subsurface/Source/Physics/PhysicsBody.cs @@ -7,6 +7,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Subsurface.Networking; using System.Collections.Generic; +using System; namespace Subsurface { @@ -48,6 +49,7 @@ namespace Subsurface get { return targetPosition; } set { + if (float.IsNaN(value.X) || float.IsNaN(value.Y)) return; targetPosition.X = MathHelper.Clamp(value.X, -10000.0f, 10000.0f); targetPosition.Y = MathHelper.Clamp(value.Y, -10000.0f, 10000.0f); } @@ -57,7 +59,8 @@ namespace Subsurface { get { return targetVelocity; } set - { + { + if (float.IsNaN(value.X) || float.IsNaN(value.Y)) return; targetVelocity.X = MathHelper.Clamp(value.X, -100.0f, 100.0f); targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f); } @@ -68,7 +71,7 @@ namespace Subsurface get { return targetRotation; } set { - if (float.IsNaN(value) || float.IsInfinity(value) || float.IsNegativeInfinity(value)) return; + if (float.IsNaN(value) || float.IsInfinity(value)) return; targetRotation = value; } } @@ -76,7 +79,11 @@ namespace Subsurface public float TargetAngularVelocity { get { return targetAngularVelocity; } - set { targetAngularVelocity = value; } + set + { + if (float.IsNaN(value) || float.IsInfinity(value)) return; + targetAngularVelocity = value; + } } public Vector2 DrawPosition @@ -356,13 +363,37 @@ namespace Subsurface public void ReadNetworkData(NetworkEventType type, NetIncomingMessage message) { - targetPosition.X = message.ReadFloat(); - targetPosition.Y = message.ReadFloat(); - targetVelocity.X = message.ReadFloat(); - targetVelocity.Y = message.ReadFloat(); + Vector2 newTargetPos = Vector2.Zero; + Vector2 newTargetVel = Vector2.Zero; + + float newTargetRotation = 0.0f, newTargetAngularVel = 0.0f; + try + { + newTargetPos = new Vector2(message.ReadFloat(),message.ReadFloat()); + newTargetVel = new Vector2(message.ReadFloat(),message.ReadFloat()); + + newTargetRotation = message.ReadFloat(); + newTargetAngularVel = message.ReadFloat(); + } + + catch (Exception e) + { +#if DEBUG + DebugConsole.ThrowError("invalid network message", e); +#endif + return; + } + + if (!MathUtils.IsValid(newTargetPos) || !MathUtils.IsValid(newTargetVel) || + !MathUtils.IsValid(newTargetRotation) || !MathUtils.IsValid(newTargetAngularVel)) return; + + targetPosition = newTargetPos; + targetVelocity = newTargetVel; + + targetRotation = newTargetRotation; + targetAngularVelocity = newTargetAngularVel; + - targetRotation = message.ReadFloat(); - targetAngularVelocity = message.ReadFloat(); } } } diff --git a/Subsurface/Source/Utils/MathUtils.cs b/Subsurface/Source/Utils/MathUtils.cs index 1a0d088e5..e16b13542 100644 --- a/Subsurface/Source/Utils/MathUtils.cs +++ b/Subsurface/Source/Utils/MathUtils.cs @@ -27,6 +27,18 @@ namespace Subsurface return (float)Math.Atan2(vector.Y, vector.X); } + public static bool IsValid(float value) + { + return (!float.IsInfinity(value) && !float.IsInfinity(value)); + } + + public static bool IsValid(Vector2 vector) + { + return (!float.IsInfinity(vector.X) && !float.IsInfinity(vector.Y) && !float.IsNaN(vector.X) && !float.IsNaN(vector.Y)); + } + + + public static float CurveAngle(float from, float to, float step) { diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index 12c9edcda02befc212f622bc558ef49b22379ef3..24e07d1335d7d2da193b848b3100e1c7fffdfe57 100644 GIT binary patch delta 13120 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 W$AyxaPd`jB8&|(F{iRfV_5RMC@QRL`Z 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 GhW`dKQHu!x