From bec6d95198a6319044f0db7dda12a748debb1c34 Mon Sep 17 00:00:00 2001 From: Regalis Date: Sun, 14 Feb 2016 16:47:23 +0200 Subject: [PATCH] Server log, ai characters steer away from the abyss --- Subsurface/Barotrauma.csproj | 1 + .../Source/Characters/AI/EnemyAIController.cs | 11 ++- Subsurface/Source/Characters/Character.cs | 51 ++++++++++--- Subsurface/Source/Events/Quests/Quest.cs | 9 ++- .../Source/GameSession/GameModes/QuestMode.cs | 3 + Subsurface/Source/Items/CharacterInventory.cs | 14 +++- Subsurface/Source/Items/Components/Door.cs | 2 +- .../Items/Components/Holdable/Holdable.cs | 7 +- .../Items/Components/Holdable/Pickable.cs | 10 +-- .../Source/Items/Components/ItemComponent.cs | 2 +- .../Items/Components/Machines/Controller.cs | 30 ++++++-- .../Components/Machines/Deconstructor.cs | 2 +- .../Items/Components/Machines/Fabricator.cs | 2 +- .../Source/Items/Components/Machines/Pump.cs | 11 ++- .../Source/Items/Components/Machines/Radar.cs | 2 +- .../Items/Components/Machines/Reactor.cs | 15 +++- .../Items/Components/Machines/Steering.cs | 2 +- .../Items/Components/Power/PowerContainer.cs | 2 +- .../Items/Components/Signal/Connection.cs | 2 +- .../Components/Signal/ConnectionPanel.cs | 18 ++++- .../Source/Items/Components/Signal/Wire.cs | 2 +- Subsurface/Source/Items/Item.cs | 5 +- Subsurface/Source/Networking/GameServer.cs | 64 ++++++++++++++-- .../Source/Networking/GameServerSettings.cs | 39 ++++++---- Subsurface/Source/Networking/NetworkMember.cs | 2 + Subsurface/Source/Networking/ServerLog.cs | 69 ++++++++++++++++++ Subsurface_Solution.v12.suo | Bin 828928 -> 846336 bytes 27 files changed, 306 insertions(+), 71 deletions(-) create mode 100644 Subsurface/Source/Networking/ServerLog.cs diff --git a/Subsurface/Barotrauma.csproj b/Subsurface/Barotrauma.csproj index abdbe0bb7..61d392857 100644 --- a/Subsurface/Barotrauma.csproj +++ b/Subsurface/Barotrauma.csproj @@ -134,6 +134,7 @@ + diff --git a/Subsurface/Source/Characters/AI/EnemyAIController.cs b/Subsurface/Source/Characters/AI/EnemyAIController.cs index d79b4b2fc..0a14ac5bc 100644 --- a/Subsurface/Source/Characters/AI/EnemyAIController.cs +++ b/Subsurface/Source/Characters/AI/EnemyAIController.cs @@ -143,8 +143,15 @@ namespace Barotrauma private void UpdateNone(float deltaTime) { //wander around randomly - steeringManager.SteeringWander(0.8f); - steeringManager.SteeringAvoid(deltaTime, 1.0f); + if (Character.Submarine==null && SimPosition.Y < ConvertUnits.ToSimUnits(SubmarineBody.DamageDepth*0.5f)) + { + steeringManager.SteeringManual(deltaTime, Vector2.UnitY); + } + else + { + steeringManager.SteeringWander(0.8f); + steeringManager.SteeringAvoid(deltaTime, 1.0f); + } attackingLimb = null; attackTimer = 0.0f; diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index df18d9434..e4a8d8c8c 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -107,7 +107,11 @@ namespace Barotrauma //which AIstate each sound is for private AIController.AiState[] soundStates; - private Entity viewTarget; + public Entity ViewTarget + { + get; + private set; + } private CharacterInfo info; @@ -1085,7 +1089,17 @@ namespace Barotrauma public virtual AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false) { - return AddDamage(worldPosition, attack.DamageType, attack.GetDamage(deltaTime), attack.GetBleedingDamage(deltaTime), attack.Stun, playSound); + + + var attackResult = AddDamage(worldPosition, attack.DamageType, attack.GetDamage(deltaTime), attack.GetBleedingDamage(deltaTime), attack.Stun, playSound); + + var attackingCharacter = attacker as Character; + if (attackingCharacter != null && attackingCharacter.AIController == null) + { + GameServer.Log(Name + " attacked by " + attackingCharacter.Name+". Damage: "+attackResult.Damage+" Bleeding damage: "+attackResult.Bleeding); + } + + return attackResult; } public AttackResult AddDamage(Vector2 simPosition, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound) @@ -1245,6 +1259,8 @@ namespace Barotrauma } } + GameServer.Log(Name+" has died (cause of death: "+causeOfDeath+")"); + if (OnDeath != null) OnDeath(this, causeOfDeath); //CoroutineManager.StartCoroutine(DeathAnim(GameMain.GameScreen.Cam)); @@ -1384,19 +1400,19 @@ namespace Barotrauma { if (Character.controlled==this) { - viewTarget = Lights.LightManager.ViewTarget == null ? this : Lights.LightManager.ViewTarget; + ViewTarget = Lights.LightManager.ViewTarget == null ? this : Lights.LightManager.ViewTarget; } - if (viewTarget == null) viewTarget = this; + if (ViewTarget == null) ViewTarget = this; Vector2 relativeCursorPosition = cursorPosition; - relativeCursorPosition -= viewTarget.Position; + relativeCursorPosition -= ViewTarget.Position; if (relativeCursorPosition.Length()>500.0f) { relativeCursorPosition = Vector2.Normalize(relativeCursorPosition) * 495.0f; } - message.Write(viewTarget.ID); + message.Write(ViewTarget.ID); message.WriteRangedSingle(relativeCursorPosition.X, -500.0f, 500.0f, 8); message.WriteRangedSingle(relativeCursorPosition.Y, -500.0f, 500.0f, 8); @@ -1432,7 +1448,7 @@ namespace Barotrauma switch (type) { case NetworkEventType.PickItem: - System.Diagnostics.Debug.WriteLine("**************** PickItem networkevent received"); + ushort itemId = message.ReadUInt16(); @@ -1444,7 +1460,18 @@ namespace Barotrauma System.Diagnostics.Debug.WriteLine("item id: "+itemId); Item item = FindEntityByID(itemId) as Item; - if (item != null) item.Pick(this, false, pickHit, actionHit); + if (item != null) + { + if (item == selectedConstruction) + { + GameServer.Log(Name + " deselected " + item.Name); + } + else + { + GameServer.Log(Name + " selected " + item.Name); + } + item.Pick(this, false, pickHit, actionHit); + } return; case NetworkEventType.SelectCharacter: @@ -1565,7 +1592,7 @@ namespace Barotrauma Vector2 pos = Vector2.Zero; ushort viewTargetID = 0; - viewTarget = null; + ViewTarget = null; try { @@ -1609,10 +1636,10 @@ namespace Barotrauma { cursorPosition = MathUtils.IsValid(relativeCursorPos) ? relativeCursorPos : Vector2.Zero; - viewTarget = viewTargetID == 0 ? this : Entity.FindEntityByID(viewTargetID); - if (viewTarget == null) viewTarget = this; + ViewTarget = viewTargetID == 0 ? this : Entity.FindEntityByID(viewTargetID); + if (ViewTarget == null) ViewTarget = this; - cursorPosition += viewTarget.Position; + cursorPosition += ViewTarget.Position; } else { diff --git a/Subsurface/Source/Events/Quests/Quest.cs b/Subsurface/Source/Events/Quests/Quest.cs index fdd956b9a..2fe76a0b4 100644 --- a/Subsurface/Source/Events/Quests/Quest.cs +++ b/Subsurface/Source/Events/Quests/Quest.cs @@ -173,9 +173,12 @@ namespace Barotrauma { if (index >= headers.Count && index >= messages.Count) return; - new GUIMessageBox( - index < headers.Count ? headers[index] : "", - index < messages.Count ? messages[index] : ""); + string header = index < headers.Count ? headers[index] : ""; + string message = index < messages.Count ? messages[index] : ""; + + Barotrauma.Networking.GameServer.Log("Mission info: " + header + " - " + message); + + new GUIMessageBox(header, message); } /// diff --git a/Subsurface/Source/GameSession/GameModes/QuestMode.cs b/Subsurface/Source/GameSession/GameModes/QuestMode.cs index 1bfce67a2..80aaefdf3 100644 --- a/Subsurface/Source/GameSession/GameModes/QuestMode.cs +++ b/Subsurface/Source/GameSession/GameModes/QuestMode.cs @@ -38,6 +38,9 @@ namespace Barotrauma new GUIMessageBox(mission.Name, mission.Description, 400, 400); + Networking.GameServer.Log("Mission: " + mission.Name); + Networking.GameServer.Log(mission.Description); + //quest.Start(Level.Loaded); } diff --git a/Subsurface/Source/Items/CharacterInventory.cs b/Subsurface/Source/Items/CharacterInventory.cs index 6791f1133..7a5e62b20 100644 --- a/Subsurface/Source/Items/CharacterInventory.cs +++ b/Subsurface/Source/Items/CharacterInventory.cs @@ -100,15 +100,19 @@ namespace Barotrauma /// public override bool TryPutItem(Item item, List allowedSlots, bool createNetworkEvent = true) { + bool alreadyInInventory = Array.Find(Items, i => i == item)!=null; + //try to place the item in LimBlot.Any slot if that's allowed if (allowedSlots.Contains(LimbSlot.Any)) { for (int i = 0; i < capacity; i++) { if (Items[i] != null || limbSlots[i] != LimbSlot.Any) continue; + + GameServer.Log(character.Name + " picked up " + item.Name); PutItem(item, i, createNetworkEvent); item.Unequip(character); - return true; + return true; } } @@ -138,7 +142,11 @@ namespace Barotrauma } } - if (placed) return true; + if (placed) + { + if (!alreadyInInventory) GameServer.Log(character.Name + " picked up " + item.Name); + return true; + } } @@ -368,7 +376,7 @@ namespace Barotrauma for (int i = 0; i(); if (connectionPanel == null) return; @@ -173,7 +173,7 @@ namespace Barotrauma.Items.Components if (w == null) continue; w.Item.Drop(character); - w.Item.SetTransform(character.SimPosition, 0.0f); + w.Item.SetTransform(pos, 0.0f); } } } diff --git a/Subsurface/Source/Items/Components/ItemComponent.cs b/Subsurface/Source/Items/Components/ItemComponent.cs index cd6e4e2b4..18cf9fcc5 100644 --- a/Subsurface/Source/Items/Components/ItemComponent.cs +++ b/Subsurface/Source/Items/Components/ItemComponent.cs @@ -731,7 +731,7 @@ namespace Barotrauma.Items.Components return false; } - public virtual void ReadNetworkData(NetworkEventType type, NetBuffer message, float sendingTime) + public virtual void ReadNetworkData(NetworkEventType type, NetIncomingMessage message, float sendingTime) { } } diff --git a/Subsurface/Source/Items/Components/Machines/Controller.cs b/Subsurface/Source/Items/Components/Machines/Controller.cs index 646a15cbf..c9d9a6442 100644 --- a/Subsurface/Source/Items/Components/Machines/Controller.cs +++ b/Subsurface/Source/Items/Components/Machines/Controller.cs @@ -152,7 +152,10 @@ namespace Barotrauma.Items.Components return; } - if (character!=null) item.SendSignal(ToolBox.Vector2ToString(character.CursorWorldPosition), "position_out"); + Entity focusTarget = null; + + if (character == null) return; + foreach (Connection c in item.Connections) { @@ -162,17 +165,28 @@ namespace Barotrauma.Items.Components { if (c2 == null || c2.Item == null || !c2.Item.Prefab.FocusOnSelected) continue; - Vector2 centerPos = c2.Item.WorldPosition; - - if (character == Character.Controlled && cam != null) - { - Lights.LightManager.ViewTarget = c2.Item; - cam.TargetPos = c2.Item.WorldPosition; - } + focusTarget = c2.Item; break; } } + + if (focusTarget == null) + { + item.SendSignal(ToolBox.Vector2ToString(character.CursorWorldPosition), "position_out"); + return; + } + + if (character == Character.Controlled && cam != null) + { + Lights.LightManager.ViewTarget = focusTarget; + cam.TargetPos = focusTarget.WorldPosition; + } + + if (!character.IsNetworkPlayer || character.ViewTarget == focusTarget) + { + item.SendSignal(ToolBox.Vector2ToString(character.CursorWorldPosition), "position_out"); + } } public override bool Pick(Character picker) diff --git a/Subsurface/Source/Items/Components/Machines/Deconstructor.cs b/Subsurface/Source/Items/Components/Machines/Deconstructor.cs index 0b777ed80..674eb491b 100644 --- a/Subsurface/Source/Items/Components/Machines/Deconstructor.cs +++ b/Subsurface/Source/Items/Components/Machines/Deconstructor.cs @@ -138,7 +138,7 @@ namespace Barotrauma.Items.Components return true; } - public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetBuffer message, float sendingTime) + public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetIncomingMessage message, float sendingTime) { if (sendingTime < lastNetworkUpdate) return; diff --git a/Subsurface/Source/Items/Components/Machines/Fabricator.cs b/Subsurface/Source/Items/Components/Machines/Fabricator.cs index 136c49bc5..e4eae4bb7 100644 --- a/Subsurface/Source/Items/Components/Machines/Fabricator.cs +++ b/Subsurface/Source/Items/Components/Machines/Fabricator.cs @@ -309,7 +309,7 @@ namespace Barotrauma.Items.Components return true; } - public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetBuffer message, float sendingTime) + public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetIncomingMessage message, float sendingTime) { if (sendingTime < lastNetworkUpdate) return; diff --git a/Subsurface/Source/Items/Components/Machines/Pump.cs b/Subsurface/Source/Items/Components/Machines/Pump.cs index 473684e99..3920c3131 100644 --- a/Subsurface/Source/Items/Components/Machines/Pump.cs +++ b/Subsurface/Source/Items/Components/Machines/Pump.cs @@ -205,7 +205,7 @@ namespace Barotrauma.Items.Components return true; } - public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetBuffer message, float sendingTime) + public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetIncomingMessage message, float sendingTime) { float newFlow = 0.0f; bool newActive; @@ -230,6 +230,15 @@ namespace Barotrauma.Items.Components IsActive = newActive; lastUpdate = sendingTime; + + if (GameMain.Server == null) return; + + var sender = GameMain.Server.ConnectedClients.Find(c => c.Connection == message.SenderConnection); + if (sender != null) + { + Networking.GameServer.Log("Pump settings adjusted by " + sender.name); + Networking.GameServer.Log("Active: " + (IsActive ? "yes" : "no ") + " Pumping speed: " + (int)flowPercentage + " %"); + } } } } diff --git a/Subsurface/Source/Items/Components/Machines/Radar.cs b/Subsurface/Source/Items/Components/Machines/Radar.cs index e730bdc80..6ef73dd3c 100644 --- a/Subsurface/Source/Items/Components/Machines/Radar.cs +++ b/Subsurface/Source/Items/Components/Machines/Radar.cs @@ -327,7 +327,7 @@ namespace Barotrauma.Items.Components return true; } - public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetBuffer message, float sendingTime) + public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetIncomingMessage message, float sendingTime) { try { diff --git a/Subsurface/Source/Items/Components/Machines/Reactor.cs b/Subsurface/Source/Items/Components/Machines/Reactor.cs index e43a4a1dd..51f610e27 100644 --- a/Subsurface/Source/Items/Components/Machines/Reactor.cs +++ b/Subsurface/Source/Items/Components/Machines/Reactor.cs @@ -361,6 +361,8 @@ namespace Barotrauma.Items.Components private void MeltDown() { if (item.Condition <= 0.0f) return; + + GameServer.Log("Reactor meltdown!"); new RepairTask(item, 60.0f, "Reactor meltdown!"); item.Condition = 0.0f; @@ -539,7 +541,7 @@ namespace Barotrauma.Items.Components return true; } - public override void ReadNetworkData(NetworkEventType type, NetBuffer message, float sendingTime) + public override void ReadNetworkData(NetworkEventType type, NetIncomingMessage message, float sendingTime) { if (sendingTime < lastUpdate) return; @@ -574,6 +576,17 @@ namespace Barotrauma.Items.Components FissionRate = newFissionRate; lastUpdate = sendingTime; + + + if (GameMain.Server == null) return; + + var sender = GameMain.Server.ConnectedClients.Find(c => c.Connection == message.SenderConnection); + if (sender != null) + { + Networking.GameServer.Log("Reactor settings adjusted by " + sender.name); + Networking.GameServer.Log("Autotemp: " +(autoTemp ? "ON " : "OFF") + " Shutdown temp: "+shutDownTemp+" Cooling rate: "+coolingRate+" Fission rate: "+fissionRate); + } + } } } diff --git a/Subsurface/Source/Items/Components/Machines/Steering.cs b/Subsurface/Source/Items/Components/Machines/Steering.cs index 3f67f543d..5781a17db 100644 --- a/Subsurface/Source/Items/Components/Machines/Steering.cs +++ b/Subsurface/Source/Items/Components/Machines/Steering.cs @@ -313,7 +313,7 @@ namespace Barotrauma.Items.Components return true; } - public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetBuffer message, float sendingTime) + public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetIncomingMessage message, float sendingTime) { Vector2 newTargetVelocity = Vector2.Zero; bool newAutoPilot = false; diff --git a/Subsurface/Source/Items/Components/Power/PowerContainer.cs b/Subsurface/Source/Items/Components/Power/PowerContainer.cs index 48005e690..bf88d5be3 100644 --- a/Subsurface/Source/Items/Components/Power/PowerContainer.cs +++ b/Subsurface/Source/Items/Components/Power/PowerContainer.cs @@ -261,7 +261,7 @@ namespace Barotrauma.Items.Components return true; } - public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetBuffer message, float sendingTime) + public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetIncomingMessage message, float sendingTime) { float newRechargeSpeed = 0f; float newCharge = 0.0f; diff --git a/Subsurface/Source/Items/Components/Signal/Connection.cs b/Subsurface/Source/Items/Components/Signal/Connection.cs index cf16ab31d..e79e562b5 100644 --- a/Subsurface/Source/Items/Components/Signal/Connection.cs +++ b/Subsurface/Source/Items/Components/Signal/Connection.cs @@ -15,7 +15,7 @@ namespace Barotrauma.Items.Components private static Sprite wireCorner, wireVertical, wireHorizontal; //how many wires can be linked to a single connector - private const int MaxLinked = 5; + public const int MaxLinked = 5; public readonly string Name; diff --git a/Subsurface/Source/Items/Components/Signal/ConnectionPanel.cs b/Subsurface/Source/Items/Components/Signal/ConnectionPanel.cs index 4f1b81a86..5e0f15b23 100644 --- a/Subsurface/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Subsurface/Source/Items/Components/Signal/ConnectionPanel.cs @@ -129,7 +129,7 @@ namespace Barotrauma.Items.Components foreach (Connection c in Connections) { Wire[] wires = Array.FindAll(c.Wires, w => w != null); - message.Write((byte)wires.Length); + message.WriteRangedInteger(0, Connection.MaxLinked, wires.Length); for (int i = 0 ; i < wires.Length; i++) { message.Write(wires[i].Item.ID); @@ -139,15 +139,24 @@ namespace Barotrauma.Items.Components return true; } - public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetBuffer message, float sendingTime) + public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetIncomingMessage message, float sendingTime) { + if (GameMain.Server != null) + { + var sender = GameMain.Server.ConnectedClients.Find(c => c.Connection == message.SenderConnection); + if (sender != null) + { + Networking.GameServer.Log(item.Name + " rewired by " + sender.name); + } + } + System.Diagnostics.Debug.WriteLine("connectionpanel update"); foreach (Connection c in Connections) { //int wireCount = c.Wires.Length; c.ClearConnections(); - byte wireCount = message.ReadByte(); + int wireCount = message.ReadRangedInteger(0, Connection.MaxLinked); for (int i = 0; i < wireCount; i++) { @@ -161,6 +170,9 @@ namespace Barotrauma.Items.Components c.Wires[i] = wireComponent; wireComponent.Connect(c, false); + + var otherConnection = c.Wires[i].OtherConnection(c); + Networking.GameServer.Log(c.Name + " -> " + (otherConnection == null ? "none" : otherConnection.Name)); } c.UpdateRecipients(); } diff --git a/Subsurface/Source/Items/Components/Signal/Wire.cs b/Subsurface/Source/Items/Components/Signal/Wire.cs index 0b3150248..8f1821305 100644 --- a/Subsurface/Source/Items/Components/Signal/Wire.cs +++ b/Subsurface/Source/Items/Components/Signal/Wire.cs @@ -480,7 +480,7 @@ namespace Barotrauma.Items.Components return true; } - public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetBuffer message, float sendingTime) + public override void ReadNetworkData(Networking.NetworkEventType type, Lidgren.Network.NetIncomingMessage message, float sendingTime) { Nodes.Clear(); diff --git a/Subsurface/Source/Items/Item.cs b/Subsurface/Source/Items/Item.cs index a9d2bcc10..ebc309689 100644 --- a/Subsurface/Source/Items/Item.cs +++ b/Subsurface/Source/Items/Item.cs @@ -833,7 +833,7 @@ namespace Barotrauma public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { - if (condition<=0.0f) + if (condition <= 0.0f) { FixRequirement.DrawHud(spriteBatch, this, character); return; @@ -1123,6 +1123,9 @@ namespace Barotrauma { //if (dropper == Character.Controlled) // new NetworkEvent(NetworkEventType.DropItem, ID, true); + + + if (dropper != null) GameServer.Log(dropper.Name + " dropped " + Name); foreach (ItemComponent ic in components) ic.Drop(dropper); diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index c5cd7fc62..89982fd94 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -20,7 +20,7 @@ namespace Barotrauma.Networking private NetStats netStats; private int roundStartSeed; - + //is the server running private bool started; @@ -32,6 +32,8 @@ namespace Barotrauma.Networking private bool masterServerResponded; + private ServerLog log; + public TraitorManager TraitorManager; public GameServer(string name, int port, bool isPublic = false, string password = "", bool attemptUPnP = false, int maxPlayers = 10) @@ -41,6 +43,8 @@ namespace Barotrauma.Networking banList = new BanList(); + log = new ServerLog(name); + this.name = name; this.password = password; @@ -78,12 +82,14 @@ namespace Barotrauma.Networking { try { + Log("Starting the server..."); server = new NetServer(config); netPeer = server; server.Start(); } catch (Exception e) { + Log("Error while starting the server ("+e.Message+")"); DebugConsole.ThrowError("Couldn't start the server", e); } @@ -166,8 +172,8 @@ namespace Barotrauma.Networking request.AddParameter("action", "refreshserver"); request.AddParameter("gamestarted", gameStarted ? 1 : 0); request.AddParameter("playercount", PlayerCountToByte(ConnectedClients.Count, config.MaximumConnections)); - - System.Diagnostics.Debug.WriteLine("refreshing master"); + + Log("Refreshing connection with master server..."); var sw = new Stopwatch(); sw.Start(); @@ -182,6 +188,9 @@ namespace Barotrauma.Networking { restRequestHandle.Abort(); DebugConsole.NewMessage("Couldn't connect to master server (request timed out)", Color.Red); + + Log("Couldn't connect to master server (request timed out)"); + break; //registeredToMaster = false; } @@ -201,14 +210,18 @@ namespace Barotrauma.Networking if (response.ErrorException != null) { DebugConsole.NewMessage("Error while registering to master server (" + response.ErrorException + ")", Color.Red); + Log("Error while registering to master server (" + response.ErrorException + ")"); return; } if (response.StatusCode != System.Net.HttpStatusCode.OK) { DebugConsole.NewMessage("Error while reporting to master server (" + response.StatusCode + ": " + response.StatusDescription + ")", Color.Red); + Log("Error while reporting to master server (" + response.StatusCode + ": " + response.StatusDescription + ")"); return; } + + Log("Master server responded"); } public override void Update(float deltaTime) @@ -233,6 +246,15 @@ namespace Barotrauma.Networking || (endRoundAtLevelEnd && Submarine.Loaded!=null && Submarine.Loaded.AtEndPosition)) { + if (AutoRestart && isCrewDead) + { + Log("Ending round (entire crew dead)"); + } + else + { + Log("Ending round (submarine reached the end of the level)"); + } + EndButtonHit(null, null); UpdateNetLobby(null,null); return; @@ -474,10 +496,10 @@ namespace Barotrauma.Networking case (byte)PacketTypes.Vote: Voting.RegisterVote(inc, ConnectedClients); - if (Voting.AllowEndVoting && EndVoteMax > 0 && - + if (Voting.AllowEndVoting && EndVoteMax > 0 && ((float)EndVoteCount / (float)EndVoteMax) >= EndVoteRequiredRatio) { + Log("Ending round by votes ("+EndVoteCount+"/"+(EndVoteMax-EndVoteCount)+")"); EndButtonHit(null,null); } break; @@ -673,8 +695,10 @@ namespace Barotrauma.Networking { yield return new WaitForSeconds(3.0f); + //save all the current events to a list and clear them var existingEvents = NetworkEvent.Events; NetworkEvent.Events.Clear(); + foreach (Hull hull in Hull.hullList) { if (!hull.FireSources.Any() && hull.Volume < 0.01f) continue; @@ -703,13 +727,18 @@ namespace Barotrauma.Networking List syncMessages = new List(NetworkEvent.Events); while (syncMessages.Any()) { + //put 5 events in the message and send them to the spectator NetworkEvent.Events = syncMessages.GetRange(0, Math.Min(syncMessages.Count, 5)); SendNetworkEvents(new List() { sender }); syncMessages.RemoveRange(0, Math.Min(syncMessages.Count, 5)); + //restore "normal" events NetworkEvent.Events = existingEvents; yield return new WaitForSeconds(0.1f); + + //save "normal" events again + existingEvents = NetworkEvent.Events; } yield return CoroutineStatus.Success; @@ -762,6 +791,11 @@ namespace Barotrauma.Networking GameMain.GameSession = new GameSession(selectedSub, "", selectedMode); GameMain.GameSession.StartShift(GameMain.NetLobbyScreen.LevelSeed); + GameServer.Log("Starting a new round..."); + GameServer.Log("Submarine: " + selectedSub.Name); + GameServer.Log("Game mode: " + selectedMode.Name); + GameServer.Log("Level seed: " + GameMain.NetLobbyScreen.LevelSeed); + yield return CoroutineStatus.Running; List characterInfos = new List(); @@ -972,6 +1006,8 @@ namespace Barotrauma.Networking if (string.IsNullOrWhiteSpace(msg)) msg = client.name + " has left the server"; if (string.IsNullOrWhiteSpace(targetmsg)) targetmsg = "You have left the server"; + + Log(msg); NetOutgoingMessage outmsg = server.CreateMessage(); outmsg.Write((byte)PacketTypes.KickedOut); @@ -1079,6 +1115,8 @@ namespace Barotrauma.Networking return; } + Log(traitor.Info.Name + " is the traitor and the target is " + target.Info.Name); + Client traitorClient = null; foreach (Client c in ConnectedClients) { @@ -1196,8 +1234,6 @@ namespace Barotrauma.Networking banButton.OnClicked += GameMain.NetLobbyScreen.BanPlayer; } - - return true; } @@ -1392,6 +1428,13 @@ namespace Barotrauma.Networking return preferredClient; } + public static void Log(string line) + { + if (GameMain.Server == null || GameMain.Server.saveServerLogs) return; + + GameMain.Server.log.WriteLine(line); + } + /// /// sends some random data to the clients /// use for debugging purposes @@ -1432,6 +1475,13 @@ namespace Barotrauma.Networking public override void Disconnect() { banList.Save(); + + if (saveServerLogs) + { + Log("Shutting down server..."); + log.Save(); + } + server.Shutdown("The server has shut down"); } } diff --git a/Subsurface/Source/Networking/GameServerSettings.cs b/Subsurface/Source/Networking/GameServerSettings.cs index ea7640a3c..4a1af6c77 100644 --- a/Subsurface/Source/Networking/GameServerSettings.cs +++ b/Subsurface/Source/Networking/GameServerSettings.cs @@ -43,6 +43,8 @@ namespace Barotrauma.Networking private bool endRoundAtLevelEnd = true; + private bool saveServerLogs = true; + public bool AutoRestart { get { return (ConnectedClients.Count == 0) ? false : autoRestart; } @@ -97,7 +99,11 @@ namespace Barotrauma.Networking var randomizeLevelBox = new GUITickBox(new Rectangle(0, 30, 20, 20), "Randomize level seed between rounds", Alignment.Left, innerFrame); randomizeLevelBox.Selected = randomizeSeed; - randomizeLevelBox.OnSelected = ToggleRandomizeSeed; + randomizeLevelBox.OnSelected = (GUITickBox) => + { + randomizeSeed = GUITickBox.Selected; + return true; + }; var endBox = new GUITickBox(new Rectangle(0, 60, 20, 20), "End round when destination reached", Alignment.Left, innerFrame); endBox.Selected = endRoundAtLevelEnd; @@ -116,6 +122,7 @@ namespace Barotrauma.Networking var votesRequiredSlider = new GUIScrollBar(new Rectangle(150,115, 100, 10), GUI.Style, 0.1f, innerFrame); votesRequiredSlider.UserData = votesRequiredText; + votesRequiredSlider.BarScroll = (EndVoteRequiredRatio - 0.5f) * 2.0f; votesRequiredSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => { GUITextBlock voteText = scrollBar.UserData as GUITextBlock; @@ -125,7 +132,8 @@ namespace Barotrauma.Networking voteText.Text = "Votes required: " + (int)MathUtils.Round(EndVoteRequiredRatio * 100.0f, 10.0f) + " %"; return true; }; - + votesRequiredSlider.OnMoved(votesRequiredSlider, votesRequiredSlider.BarScroll); + new GUITextBlock(new Rectangle(0, 95+50, 100, 20), "Submarine selection:", GUI.Style, innerFrame); var selectionFrame = new GUIFrame(new Rectangle(0, 120 + 50, 300, 20), null, innerFrame); for (int i = 0; i<3; i++) @@ -147,8 +155,20 @@ namespace Barotrauma.Networking } var allowSpecBox = new GUITickBox(new Rectangle(0, 210 + 50, 20, 20), "Allow spectating", Alignment.Left, innerFrame); - allowSpecBox.Selected = true; - allowSpecBox.OnSelected = ToggleAllowSpectating; + allowSpecBox.Selected = allowSpectating; + allowSpecBox.OnSelected = (GUITickBox) => + { + allowSpectating = GUITickBox.Selected; + return true; + }; + + var saveLogsBox = new GUITickBox(new Rectangle(0, 240 + 50, 20, 20), "Save server logs", Alignment.Left, innerFrame); + saveLogsBox.Selected = saveServerLogs; + saveLogsBox.OnSelected = (GUITickBox) => + { + saveServerLogs = GUITickBox.Selected; + return true; + }; ; var closeButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Close", Alignment.BottomRight, GUI.Style, innerFrame); closeButton.OnClicked = ToggleSettingsFrame; @@ -194,17 +214,6 @@ namespace Barotrauma.Networking return true; } - private bool ToggleRandomizeSeed(GUITickBox tickBox) - { - randomizeSeed = tickBox.Selected; - return true; - } - - private bool ToggleAllowSpectating(GUITickBox tickBox) - { - allowSpectating = tickBox.Selected; - return true; - } public bool ToggleSettingsFrame(GUIButton button, object obj) { diff --git a/Subsurface/Source/Networking/NetworkMember.cs b/Subsurface/Source/Networking/NetworkMember.cs index 4690236a1..1c6916fe7 100644 --- a/Subsurface/Source/Networking/NetworkMember.cs +++ b/Subsurface/Source/Networking/NetworkMember.cs @@ -186,6 +186,8 @@ namespace Barotrauma.Networking { GameMain.NetLobbyScreen.NewChatMessage(message, messageColor[(int)messageType]); + GameServer.Log(message); + while (chatBox.CountChildren > 20) { chatBox.RemoveChild(chatBox.children[1]); diff --git a/Subsurface/Source/Networking/ServerLog.cs b/Subsurface/Source/Networking/ServerLog.cs new file mode 100644 index 000000000..7d1c5cb7b --- /dev/null +++ b/Subsurface/Source/Networking/ServerLog.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Barotrauma.Networking +{ + class ServerLog + { + const int LinesPerFile = 500; + + const string SavePath = "ServerLogs"; + + private string serverName; + + private Queue lines; + + public ServerLog(string serverName) + { + this.serverName = serverName; + + lines = new Queue(); + } + + public void WriteLine(string line) + { + string logLine = "[" + DateTime.Now.ToLongTimeString() + "] " + line; + + lines.Enqueue(logLine); + + if (lines.Count>=LinesPerFile) + { + Save(); + } + } + + public void Save() + { + if (!Directory.Exists(SavePath)) + { + try + { + Directory.CreateDirectory(SavePath); + } + catch (Exception e) + { + DebugConsole.ThrowError("Failed to create a folder for server logs", e); + return; + } + } + + string fileName = serverName+"_"+DateTime.Now.ToShortDateString()+"_"+DateTime.Now.ToShortTimeString()+".txt"; + + fileName = fileName.Replace(":", ""); + + string filePath = Path.Combine(SavePath, fileName); + + try + { + File.WriteAllLines(filePath, lines); + } + catch (Exception e) + { + DebugConsole.ThrowError("Saving the server log to " + filePath + " failed", e); + } + } + } +} diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index 8e982b8c9a525fc57ebcf5769ac01dab11dced8c..0bdc51fffdedab324a10066fb3b3604231c05ef2 100644 GIT binary patch delta 17610 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 delta 15059 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 BFE#)G