From 1f0f411d4f3ca78476f2004e27608b983319e666 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 27 Oct 2021 13:35:10 -0300 Subject: [PATCH] fix merge conflicts, warning: still broken --- .../ServerSource/Characters/Character.cs | 23 +- .../ServerSource/Networking/GameServer.cs | 137 +++--- .../Data/ContentPackages/Vanilla 0.9.xml | 53 ++- .../Characters/Health/CharacterHealth.cs | 284 ++++++++---- .../SharedSource/Items/Item.cs | 414 +++++++++++------- 5 files changed, 570 insertions(+), 341 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs index f605e430a..0ae36eb12 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs @@ -27,7 +27,16 @@ namespace Barotrauma GameServer.Log(GameServer.CharacterLogName(this) + " has died (Cause of death: " + causeOfDeath + ")", ServerLog.MessageType.Attack); } } - GameMain.Lua.hook.Call("characterDeath", new object[] { this,causeOfDeathAffliction }); + + if (HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter)) + { + var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this); + if (ownerClient != null) + { + (GameMain.GameSession?.GameMode as MultiPlayerCampaign)?.SaveExperiencePoints(ownerClient); + } + } + GameMain.Lua.hook.Call("characterDeath", new object[] { this, causeOfDeathAffliction }); healthUpdateTimer = 0.0f; if (CauseOfDeath.Killer != null && CauseOfDeath.Killer.IsTraitor && CauseOfDeath.Killer != this) @@ -35,10 +44,7 @@ namespace Barotrauma var owner = GameMain.Server.ConnectedClients.Find(c => c.Character == this); if (owner != null) { - if (!GameMain.Lua.game.overrideTraitors) - { - GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("KilledByTraitorNotification"), owner, ChatMessageType.ServerMessageBoxInGame); - } + GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("KilledByTraitorNotification"), owner, ChatMessageType.ServerMessageBoxInGame); } } foreach (Client client in GameMain.Server.ConnectedClients) @@ -49,5 +55,10 @@ namespace Barotrauma } } } + + partial void OnMoneyChanged(int prevAmount, int newAmount) + { + GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateMoney }); + } } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 51f6a23ac..1cef0cdb4 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -11,7 +11,6 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Xml.Linq; -using MoonSharp.Interpreter; namespace Barotrauma.Networking { @@ -197,15 +196,13 @@ namespace Barotrauma.Networking #endif } - GameMain.Lua.Initialize(); - TickRate = serverSettings.TickRate; Log("Server started", ServerLog.MessageType.ServerMessage); GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.RandomizeSettings(); - if (!string.IsNullOrEmpty(serverSettings.SelectedSubmarine)) + if (!string.IsNullOrEmpty(serverSettings.SelectedSubmarine)) { SubmarineInfo sub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == serverSettings.SelectedSubmarine); if (sub != null) { GameMain.NetLobbyScreen.SelectedSub = sub; } @@ -312,10 +309,7 @@ namespace Barotrauma.Networking SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient); } - GameMain.Lua.hook.Call("clientConnected", new object[] { newClient }); - - - SendChatMessage($"ServerMessage.JoinedServer~[client]={clName}", ChatMessageType.Server, null, changeType: PlayerConnectionChangeType.Joined); + SendChatMessage($"ServerMessage.JoinedServer~[client]={ClientLogName(newClient)}", ChatMessageType.Server, null, changeType: PlayerConnectionChangeType.Joined); serverSettings.ServerDetailsChanged = true; if (previousPlayer != null && previousPlayer.Name != newClient.Name) @@ -542,7 +536,7 @@ namespace Barotrauma.Networking initiatedStartGame = false; } } - else if (Screen.Selected == GameMain.NetLobbyScreen && !gameStarted && !initiatedStartGame && + else if (Screen.Selected == GameMain.NetLobbyScreen && !gameStarted && !initiatedStartGame && (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign || GameMain.GameSession?.GameMode is MultiPlayerCampaign)) { if (serverSettings.AutoRestart) @@ -693,7 +687,7 @@ namespace Barotrauma.Networking if (Timing.TotalTime > lastPingTime + 1.0) { lastPingData ??= new byte[64]; - for (int i=0;i x.Connection == inc.Sender); @@ -1327,11 +1322,11 @@ namespace Barotrauma.Networking Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); if (mpCampaign != null && Level.IsLoadedOutpost) { - mpCampaign.SaveInventories(); + mpCampaign.SavePlayers(); GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); SaveUtil.SaveGame(GameMain.GameSession.SavePath); } - EndGame(); + EndGame(); } } else @@ -1462,7 +1457,7 @@ namespace Barotrauma.Networking } break; case ClientPermissions.ManageCampaign: - (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.ServerRead(inc, sender); + (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.ServerRead(inc, sender); break; case ClientPermissions.ConsoleCommands: { @@ -1818,7 +1813,7 @@ namespace Barotrauma.Networking outmsg.Write(client.InGame); outmsg.Write(client.Permissions != ClientPermissions.None); outmsg.Write(client.Connection == OwnerConnection); - outmsg.Write(client.Connection != OwnerConnection && + outmsg.Write(client.Connection != OwnerConnection && !client.HasPermission(ClientPermissions.Ban) && !client.HasPermission(ClientPermissions.Kick) && !client.HasPermission(ClientPermissions.Unban)); //is kicking the player allowed @@ -1826,7 +1821,7 @@ namespace Barotrauma.Networking } } - public void ClientWriteLobby(Client c) + private void ClientWriteLobby(Client c) { bool isInitialUpdate = false; @@ -2198,8 +2193,8 @@ namespace Barotrauma.Networking bool isOutpost = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost; if (serverSettings.AllowRespawn && missionAllowRespawn) - { - respawnManager = new RespawnManager(this, serverSettings.UseRespawnShuttle && !isOutpost ? selectedShuttle : null); + { + respawnManager = new RespawnManager(this, serverSettings.UseRespawnShuttle && !isOutpost ? selectedShuttle : null); } if (campaign != null) { @@ -2291,7 +2286,7 @@ namespace Barotrauma.Networking } AssignBotJobs(bots, teamID); - if (campaign != null) + if (campaign != null) { foreach (CharacterInfo bot in bots) { @@ -2308,7 +2303,7 @@ namespace Barotrauma.Networking List spawnWaypoints = null; List mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]).ToList(); - if (Level.Loaded?.StartOutpost != null && + if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost && (Level.Loaded.StartOutpost.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false) && Level.Loaded.StartOutpost.GetConnectedSubs().Any(s => s.Info.Type == SubmarineType.Player)) @@ -2363,8 +2358,16 @@ namespace Barotrauma.Networking characterData.ApplyHealthData(spawnedCharacter); characterData.ApplyOrderData(spawnedCharacter); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); + spawnedCharacter.LoadTalents(); + characterData.HasSpawned = true; } + if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign && spawnedCharacter.Info != null) + { + spawnedCharacter.Info.SetExperience(Math.Max(spawnedCharacter.Info.ExperiencePoints, mpCampaign.GetSavedExperiencePoints(teamClients[i]))); + mpCampaign.ClearSavedExperiencePoints(teamClients[i]); + } + spawnedCharacter.OwnerClientEndPoint = teamClients[i].Connection.EndPointString; spawnedCharacter.OwnerClientName = teamClients[i].Name; } @@ -2375,6 +2378,8 @@ namespace Barotrauma.Networking spawnedCharacter.TeamID = teamID; spawnedCharacter.GiveJobItems(mainSubWaypoints[i]); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); + // talents are only avilable for players in online sessions, but modders or someone else might want to have them loaded anyway + spawnedCharacter.LoadTalents(); } } @@ -2414,11 +2419,8 @@ namespace Barotrauma.Networking { if (!(GameMain.GameSession?.GameMode is CampaignMode)) { - if (!GameMain.Lua.game.overrideTraitors) - { - TraitorManager = new TraitorManager(); - TraitorManager.Start(this); - } + TraitorManager = new TraitorManager(); + TraitorManager.Start(this); } } @@ -2435,7 +2437,6 @@ namespace Barotrauma.Networking Log("Round started.", ServerLog.MessageType.ServerMessage); - gameStarted = true; initiatedStartGame = false; GameMain.ResetFrameTime(); @@ -2444,9 +2445,7 @@ namespace Barotrauma.Networking roundStartTime = DateTime.Now; - GameMain.Lua.hook.Call("roundStart", new object[] { }); - - + startGameCoroutine = null; yield return CoroutineStatus.Success; } @@ -2471,6 +2470,7 @@ namespace Barotrauma.Networking msg.Write(serverSettings.AllowRespawn && missionAllowRespawn); msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowRewiring); + msg.Write(serverSettings.AllowFriendlyFire); msg.Write(serverSettings.LockAllDefaultWires); msg.Write(serverSettings.AllowRagdollButton); msg.Write(serverSettings.UseRespawnShuttle); @@ -2566,9 +2566,6 @@ namespace Barotrauma.Networking Log("Ending the round...", ServerLog.MessageType.ServerMessage); } - GameMain.Lua.hook.Call("roundEnd", new object[] { }); - - string endMessage = TextManager.FormatServerMessage("RoundSummaryRoundHasEnded"); var traitorResults = TraitorManager?.GetEndResults() ?? new List(); @@ -2638,8 +2635,8 @@ namespace Barotrauma.Networking } } - Submarine.Unload(); entityEventManager.Clear(); + Submarine.Unload(); GameMain.NetLobbyScreen.Select(); Log("Round ended.", ServerLog.MessageType.ServerMessage); @@ -2808,7 +2805,7 @@ namespace Barotrauma.Networking //reset karma to a neutral value, so if/when the ban is revoked the client wont get immediately punished by low karma again previousPlayer.Karma = Math.Max(previousPlayer.Karma, 50.0f); - + if (!string.IsNullOrEmpty(previousPlayer.EndPoint) && (previousPlayer.SteamID == 0 || range)) { string ip = previousPlayer.EndPoint; @@ -2862,8 +2859,6 @@ namespace Barotrauma.Networking { if (client == null) return; - GameMain.Lua.hook.Call("clientDisconnected", new object[] { client }); - if (gameStarted && client.Character != null) { client.Character.ClientDisconnected = true; @@ -3103,21 +3098,13 @@ namespace Barotrauma.Networking senderName = null; senderCharacter = null; } - else if (type == ChatMessageType.Radio && !GameMain.Lua.game.overrideSignalRadio) + else if (type == ChatMessageType.Radio) { //send to chat-linked wifi components Signal s = new Signal(message, sender: senderCharacter, source: senderRadio.Item); senderRadio.TransmitSignal(s, sentFromChat: true); } - var hookChatMsg = ChatMessage.Create(senderName, message, (ChatMessageType)type, senderCharacter, senderClient, changeType); - - var should = new LuaResult(GameMain.Lua.hook.Call("modifyChatMessage", new object[] { hookChatMsg, senderRadio })); - - if (should.Bool()) - return; - - //check which clients can receive the message and apply distance effects foreach (Client client in ConnectedClients) { @@ -3150,15 +3137,14 @@ namespace Barotrauma.Networking break; } - var chatMsg = ChatMessage.Create( senderName, modifiedMessage, (ChatMessageType)type, senderCharacter, - senderClient, + senderClient, changeType); - + SendDirectChatMessage(chatMsg, client); } @@ -3175,28 +3161,19 @@ namespace Barotrauma.Networking public void SendOrderChatMessage(OrderChatMessage message) { if (message.Sender == null || message.Sender.SpeechImpediment >= 100.0f) { return; } - //ChatMessageType messageType = ChatMessage.CanUseRadio(message.Sender) ? ChatMessageType.Radio : ChatMessageType.Default; - //check which clients can receive the message and apply distance effects foreach (Client client in ConnectedClients) { - string modifiedMessage = message.Text; - - if (message.Sender != null && - client.Character != null && !client.Character.IsDead) + if (message.Sender != null && client.Character != null && !client.Character.IsDead) { //too far to hear the msg -> don't send if (!client.Character.CanHearCharacter(message.Sender)) { continue; } } - SendDirectChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, message.TargetEntity, message.TargetCharacter, message.Sender), client); } - - string myReceivedMessage = message.Text; - - if (!string.IsNullOrWhiteSpace(myReceivedMessage)) + if (!string.IsNullOrWhiteSpace(message.Text)) { - AddChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, myReceivedMessage, message.TargetEntity, message.TargetCharacter, message.Sender)); + AddChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, message.Text, message.TargetEntity, message.TargetCharacter, message.Sender)); } } @@ -3458,7 +3435,7 @@ namespace Barotrauma.Networking { newCharacter.LastNetworkUpdateID = client.Character.LastNetworkUpdateID; } - + if (newCharacter.Info != null && newCharacter.Info.Character == null) { newCharacter.Info.Character = newCharacter; @@ -3492,15 +3469,15 @@ namespace Barotrauma.Networking } catch (Exception e) { - //gender = Gender.Male; - //race = Race.White; - //headSpriteId = 0; DebugConsole.Log("Received invalid characterinfo from \"" + sender.Name + "\"! { " + e.Message + " }"); } int hairIndex = message.ReadByte(); int beardIndex = message.ReadByte(); int moustacheIndex = message.ReadByte(); int faceAttachmentIndex = message.ReadByte(); + Color skinColor = message.ReadColorR8G8B8(); + Color hairColor = message.ReadColorR8G8B8(); + Color facialHairColor = message.ReadColorR8G8B8(); List> jobPreferences = new List>(); int count = message.ReadByte(); @@ -3517,6 +3494,9 @@ namespace Barotrauma.Networking sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, sender.Name); sender.CharacterInfo.RecreateHead(headSpriteId, race, gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); + sender.CharacterInfo.SkinColor = skinColor; + sender.CharacterInfo.HairColor = hairColor; + sender.CharacterInfo.FacialHairColor = facialHairColor; if (jobPreferences.Count > 0) { @@ -3709,7 +3689,7 @@ namespace Barotrauma.Networking { c.AssignedJob = preferredJob; assignedClientCount[preferredJob.First]++; - break; + break; } } else //none of the client's preferred jobs available, choose a random job @@ -3766,10 +3746,10 @@ namespace Barotrauma.Networking unassignedBots[0].Job = new Job(jobPrefab, variant); assignedPlayerCount[jobPrefab]++; unassignedBots.Remove(unassignedBots[0]); - canAssign = true; + canAssign = true; } } while (unassignedBots.Count > 0 && canAssign); - + //find a suitable job for the rest of the bots foreach (CharacterInfo c in unassignedBots) { @@ -3816,15 +3796,15 @@ namespace Barotrauma.Networking return preferredClient; } - public void UpdateMissionState(Mission mission, int state) + public void UpdateMissionState(Mission mission) { foreach (var client in connectedClients) { IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ServerPacketHeader.MISSION); int missionIndex = GameMain.GameSession.GetMissionIndex(mission); - msg.Write((byte)(missionIndex == -1 ? 255: missionIndex)); - msg.Write((ushort)state); + msg.Write((byte)(missionIndex == -1 ? 255 : missionIndex)); + mission?.ServerWrite(msg); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } } @@ -3840,9 +3820,6 @@ namespace Barotrauma.Networking { if (GameMain.Server == null || !GameMain.Server.ServerSettings.SaveServerLogs) { return; } - if(GameMain.Lua.hook != null) - GameMain.Lua.hook.Call("serverLog", new object[] { line, messageType }); - GameMain.Server.ServerSettings.ServerLog.WriteLine(line, messageType); foreach (Client client in GameMain.Server.ConnectedClients) @@ -3880,7 +3857,7 @@ namespace Barotrauma.Networking string submarinesString = string.Empty; for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++) { - submarinesString += GameMain.NetLobbyScreen.CampaignSubmarines[i].Name + ServerSettings.SubmarineSeparatorChar; + submarinesString += GameMain.NetLobbyScreen.CampaignSubmarines[i].Name + ServerSettings.SubmarineSeparatorChar; } submarinesString.Trim(ServerSettings.SubmarineSeparatorChar); serverSettings.CampaignSubmarines = submarinesString; diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 27133e747..f41bd4276 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -23,6 +23,7 @@ + @@ -63,6 +64,12 @@ + + + + + + @@ -73,6 +80,7 @@ + @@ -80,6 +88,8 @@ + + @@ -147,6 +157,13 @@ + + + + + + + @@ -155,6 +172,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -261,4 +305,11 @@ - + + + + + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index bfa7d0906..424a6f78c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -6,13 +6,13 @@ using System.Xml.Linq; using Barotrauma.Networking; using Barotrauma.Extensions; using System.Globalization; -using MoonSharp.Interpreter; +using Barotrauma.Abilities; namespace Barotrauma { partial class CharacterHealth { - public class LimbHealth + class LimbHealth { public Sprite IndicatorSprite; public Sprite HighlightSprite; @@ -20,7 +20,7 @@ namespace Barotrauma public Rectangle HighlightArea; public readonly string Name; - + public readonly List Afflictions = new List(); public readonly Dictionary VitalityMultipliers = new Dictionary(); @@ -76,7 +76,7 @@ namespace Barotrauma } } } - + public List GetActiveAfflictions(AfflictionPrefab prefab) { return Afflictions.FindAll(a => a.Prefab == prefab); @@ -85,7 +85,7 @@ namespace Barotrauma { return Afflictions.FindAll(a => a.Prefab.AfflictionType == afflictionType); } - } + } public const float InsufficientOxygenThreshold = 30.0f; public const float LowOxygenThreshold = 50.0f; @@ -117,15 +117,15 @@ namespace Barotrauma private set => Character.Params.Health.CrushDepth = value; } - private List limbHealths = new List(); + private readonly List limbHealths = new List(); //non-limb-specific afflictions - private List afflictions = new List(); + private readonly List afflictions = new List(); /// /// Note: returns only the non-limb-secific afflictions. Use GetAllAfflictions or some other method for getting also the limb-specific afflictions. /// public IEnumerable Afflictions => afflictions; - private HashSet irremovableAfflictions = new HashSet(); + private readonly HashSet irremovableAfflictions = new HashSet(); private Affliction bloodlossAffliction; private Affliction oxygenLowAffliction; private Affliction pressureAffliction; @@ -133,7 +133,7 @@ namespace Barotrauma public bool IsUnconscious { - get { return Vitality <= 0.0f || Character.IsDead; } + get { return (Vitality <= 0.0f || Character.IsDead) && !Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious); } } public float PressureKillDelay { get; private set; } = 5.0f; @@ -152,6 +152,7 @@ namespace Barotrauma max += Character.Info.Job.Prefab.VitalityModifier; } max *= Character.StaticHealthMultiplier; + max *= 1f + Character.GetStatValue(StatTypes.MaximumHealthMultiplier); return max * Character.HealthMultiplier; } } @@ -168,6 +169,20 @@ namespace Barotrauma } } + public Color DefaultFaceTint = Color.TransparentBlack; + + public Color FaceTint + { + get; + private set; + } + + public Color BodyTint + { + get; + private set; + } + public float OxygenAmount { get @@ -191,7 +206,11 @@ namespace Barotrauma public float Stun { get { return stunAffliction.Strength; } - set { stunAffliction.Strength = MathHelper.Clamp(value, 0.0f, stunAffliction.Prefab.MaxStrength); } + set + { + if (Character.GodMode) { return; } + stunAffliction.Strength = MathHelper.Clamp(value, 0.0f, stunAffliction.Prefab.MaxStrength); + } } public float StunTimer { get; private set; } @@ -223,7 +242,7 @@ namespace Barotrauma this.Character = character; InitIrremovableAfflictions(); - Vitality = maxVitality; + Vitality = maxVitality; minVitality = character.IsHuman ? -100.0f : 0.0f; @@ -266,6 +285,12 @@ namespace Barotrauma private LimbHealth GetMatchingLimbHealth(Limb limb) => limb == null ? null : limbHealths[limb.HealthIndex]; private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb, excludeSevered: false)); + /// + /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. + /// + private IEnumerable GetMatchingAfflictions(LimbHealth limb) + => limb.Afflictions.Union(afflictions.Where(a => GetMatchingLimbHealth(a) == limb)); + /// /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. /// @@ -298,7 +323,7 @@ namespace Barotrauma public Affliction GetAffliction(string identifier, bool allowLimbAfflictions = true) => GetAffliction(a => a.Prefab.Identifier == identifier, allowLimbAfflictions); - public Affliction GetAfflictionOfType(string afflictionType, bool allowLimbAfflictions = true) + public Affliction GetAfflictionOfType(string afflictionType, bool allowLimbAfflictions = true) => GetAffliction(a => a.Prefab.AfflictionType == afflictionType, allowLimbAfflictions); private Affliction GetAffliction(Func predicate, bool allowLimbAfflictions = true) @@ -402,62 +427,61 @@ namespace Barotrauma return strength; } - public void ApplyAffliction(Limb targetLimb, Affliction affliction) + public void ApplyAffliction(Limb targetLimb, Affliction affliction, bool allowStacking = true) { if (!affliction.Prefab.IsBuff && Unkillable || Character.GodMode) { return; } if (affliction.Prefab.LimbSpecific) { if (targetLimb == null) { - var should = new LuaResult(GameMain.Lua.hook.Call("afflictionApplied", new object[] { this, affliction })); - - if (should.Bool()) - return; - - //if a limb-specific affliction is applied to no specific limb, apply to all limbs foreach (LimbHealth limbHealth in limbHealths) { - AddLimbAffliction(limbHealth, affliction); + AddLimbAffliction(limbHealth, affliction, allowStacking: allowStacking); } - } else { - var should = new LuaResult(GameMain.Lua.hook.Call("afflictionApplied", new object[] { this, affliction, targetLimb })); - - if (should.Bool()) - return; - - AddLimbAffliction(targetLimb, affliction); + AddLimbAffliction(targetLimb, affliction, allowStacking: allowStacking); } } else { - var should = new LuaResult(GameMain.Lua.hook.Call("afflictionApplied", new object[] { this, affliction })); - - if (should.Bool()) - return; - - AddAffliction(affliction); + AddAffliction(affliction, allowStacking: allowStacking); } } - public float GetResistance(string resistanceId) + public float GetResistance(AfflictionPrefab affliction) { float resistance = 0.0f; for (int i = 0; i < afflictions.Count; i++) { - if (!afflictions[i].Prefab.IsBuff) continue; - float temp = afflictions[i].GetResistance(resistanceId); - if (temp > resistance) resistance = temp; + resistance += afflictions[i].GetResistance(affliction); } + return 1 - ((1 - resistance) * Character.GetAbilityResistance(affliction)); + } - return resistance; + public float GetStatValue(StatTypes statType) + { + float value = 0f; + for (int i = 0; i < afflictions.Count; i++) + { + value += afflictions[i].GetStatValue(statType); + } + return value; + } + + public bool HasFlag(AbilityFlags flagType) + { + for (int i = 0; i < afflictions.Count; i++) + { + if (afflictions[i].HasFlag(flagType)) { return true; } + } + return false; } private readonly List matchingAfflictions = new List(); - public void ReduceAffliction(Limb targetLimb, string affliction, float amount) + public void ReduceAffliction(Limb targetLimb, string affliction, float amount, ActionType? treatmentAction = null) { matchingAfflictions.Clear(); matchingAfflictions.AddRange(afflictions); @@ -486,6 +510,14 @@ namespace Barotrauma for (int i = matchingAfflictions.Count - 1; i >= 0; i--) { var matchingAffliction = matchingAfflictions[i]; + + // this logic runs very often, so culling unnecessary object creation and talent checking with this method + if (Character.HasTalents()) + { + var afflictionReduction = new AbilityValueAffliction(reduceAmount, matchingAffliction); + Character.CheckTalents(AbilityEffectType.OnReduceAffliction, afflictionReduction); + } + if (matchingAffliction.Strength < reduceAmount) { float surplus = reduceAmount - matchingAffliction.Strength; @@ -500,6 +532,17 @@ namespace Barotrauma { matchingAffliction.Strength -= reduceAmount; amount -= reduceAmount; + if (treatmentAction != null) + { + if (treatmentAction.Value == ActionType.OnUse) + { + matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime; + } + else if (treatmentAction.Value == ActionType.OnFailure) + { + matchingAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime; + } + } } } CalculateVitality(); @@ -515,11 +558,6 @@ namespace Barotrauma return; } - var should = new LuaResult(GameMain.Lua.hook.Call("afflictionApplied", new object[] { this, attackResult, hitLimb })); - - if (should.Bool()) - return; - foreach (Affliction newAffliction in attackResult.Afflictions) { if (newAffliction.Prefab.LimbSpecific) @@ -530,15 +568,15 @@ namespace Barotrauma { AddAffliction(newAffliction, allowStacking); } - } + } } - + public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount) { if (Unkillable || Character.GodMode) { return; } foreach (LimbHealth limbHealth in limbHealths) { - limbHealth.Afflictions.RemoveAll(a => + limbHealth.Afflictions.RemoveAll(a => a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType || a.Prefab.AfflictionType == AfflictionPrefab.Burn.AfflictionType || a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType); @@ -562,9 +600,9 @@ namespace Barotrauma else { // Instead of using the limbhealth count here, I think it's best to define the max vitality per limb roughly with a constant value. - // Therefore with e.g. 80 health, the max damage per limb would be 20. - // Having at least 20 damage on both legs would cause maximum limping. - float max = MaxVitality / 4; + // Therefore with e.g. 80 health, the max damage per limb would be 40. + // Having at least 40 damage on both legs would cause maximum limping. + float max = MaxVitality / 2; if (string.IsNullOrEmpty(afflictionType)) { float damage = GetAfflictionStrength("damage", limb, true); @@ -595,6 +633,22 @@ namespace Barotrauma CalculateVitality(); } + public void RemoveNegativeAfflictions() + { + // also don't remove genetic effects, even if they're negative + foreach (LimbHealth limbHealth in limbHealths) + { + limbHealth.Afflictions.RemoveAll(a => !a.Prefab.IsBuff && a.Prefab.AfflictionType != "geneticmaterialbuff" && a.Prefab.AfflictionType != "geneticmaterialdebuff"); + } + + afflictions.RemoveAll(a => !irremovableAfflictions.Contains(a) && !a.Prefab.IsBuff && a.Prefab.AfflictionType != "geneticmaterialbuff" && a.Prefab.AfflictionType != "geneticmaterialdebuff"); + foreach (Affliction affliction in irremovableAfflictions) + { + affliction.Strength = 0.0f; + } + CalculateVitality(); + } + private void AddLimbAffliction(Limb limb, Affliction newAffliction, bool allowStacking = true) { if (!newAffliction.Prefab.LimbSpecific || limb == null) { return; } @@ -616,7 +670,7 @@ namespace Barotrauma { if (newAffliction.Prefab == affliction.Prefab) { - float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier)); + float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab)); if (allowStacking) { // Add the existing strength @@ -638,10 +692,10 @@ namespace Barotrauma //create a new instance of the affliction to make sure we don't use the same instance for multiple characters //or modify the affliction instance of an Attack or a StatusEffect var copyAffliction = newAffliction.Prefab.Instantiate( - Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))), + Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))), newAffliction.Source); limbHealth.Afflictions.Add(copyAffliction); - + Character.HealthUpdateInterval = 0.0f; CalculateVitality(); @@ -660,6 +714,7 @@ namespace Barotrauma private void AddAffliction(Affliction newAffliction, bool allowStacking = true) { if (!DoesBleed && newAffliction is AfflictionBleeding) { return; } + if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; } if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; } if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab) { @@ -672,7 +727,7 @@ namespace Barotrauma { if (newAffliction.Prefab == affliction.Prefab) { - float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier)); + float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab)); if (allowStacking) { // Add the existing strength @@ -694,7 +749,7 @@ namespace Barotrauma //create a new instance of the affliction to make sure we don't use the same instance for multiple characters //or modify the affliction instance of an Attack or a StatusEffect afflictions.Add(newAffliction.Prefab.Instantiate( - Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))), + Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))), source: newAffliction.Source)); Character.HealthUpdateInterval = 0.0f; @@ -706,8 +761,6 @@ namespace Barotrauma } } - partial void UpdateProjSpecific(float deltaTime); - partial void UpdateLimbAfflictionOverlays(); public void Update(float deltaTime) @@ -716,6 +769,8 @@ namespace Barotrauma StunTimer = Stun > 0 ? StunTimer + deltaTime : 0; + if (Character.GodMode) { return; } + for (int i = 0; i < limbHealths.Count; i++) { for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) @@ -743,11 +798,11 @@ namespace Barotrauma Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } } - + for (int i = afflictions.Count - 1; i >= 0; i--) { var affliction = afflictions[i]; - if (irremovableAfflictions.Contains(affliction)) continue; + if (irremovableAfflictions.Contains(affliction)) { continue; } if (affliction.Strength <= 0.0f) { SteamAchievementManager.OnAfflictionRemoved(affliction, Character); @@ -761,9 +816,21 @@ namespace Barotrauma affliction.DamagePerSecondTimer += deltaTime; Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } - - UpdateLimbAfflictionOverlays(); + Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.MovementSpeed)); + + // maybe a bit of a hacky way to do this. should inquire if there is a better way. M61T + if (Character.InWater) + { + Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.SwimmingSpeed)); + } + else + { + Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.WalkingSpeed)); + } + + UpdateLimbAfflictionOverlays(); + UpdateSkinTint(); CalculateVitality(); if (Vitality <= MinVitality) @@ -772,6 +839,32 @@ namespace Barotrauma } } + private void UpdateSkinTint() + { + FaceTint = DefaultFaceTint; + BodyTint = Color.TransparentBlack; + + for (int i = 0; i < limbHealths.Count; i++) + { + for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) + { + var affliction = limbHealths[i].Afflictions[j]; + Color faceTint = affliction.GetFaceTint(); + if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } + Color bodyTint = affliction.GetBodyTint(); + if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } + } + } + for (int i = 0; i < afflictions.Count; i++) + { + var affliction = afflictions[i]; + Color faceTint = affliction.GetFaceTint(); + if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } + Color bodyTint = affliction.GetBodyTint(); + if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } + } + } + private void UpdateOxygen(float deltaTime) { if (!Character.NeedsOxygen) { return; } @@ -780,16 +873,21 @@ namespace Barotrauma if (IsUnconscious) { //the character dies of oxygen deprivation in 100 seconds after losing consciousness - OxygenAmount = MathHelper.Clamp(OxygenAmount - 1.0f * deltaTime, -100.0f, 100.0f); + OxygenAmount = MathHelper.Clamp(OxygenAmount - 1.0f * deltaTime, -100.0f, 100.0f); } else { - OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? -5.0f : 10.0f), -100.0f, 100.0f); + float decreaseSpeed = -5.0f; + float increaseSpeed = 10.0f; + float oxygenlowResistance = GetResistance(oxygenLowAffliction.Prefab); + decreaseSpeed *= (1f - oxygenlowResistance); + increaseSpeed *= (1f + oxygenlowResistance); + OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f); } UpdateOxygenProjSpecific(prevOxygen, deltaTime); } - + partial void UpdateOxygenProjSpecific(float prevOxygen, float deltaTime); partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime); @@ -805,8 +903,6 @@ namespace Barotrauma Vitality = MaxVitality; if (Unkillable || Character.GodMode) { return; } - float damageResistanceMultiplier = 1f - GetResistance("damage"); - foreach (LimbHealth limbHealth in limbHealths) { foreach (Affliction affliction in limbHealth.Afflictions) @@ -822,7 +918,6 @@ namespace Barotrauma { vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type]; } - vitalityDecrease *= damageResistanceMultiplier; Vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); } @@ -831,7 +926,6 @@ namespace Barotrauma foreach (Affliction affliction in afflictions) { float vitalityDecrease = affliction.GetVitalityDecrease(this); - vitalityDecrease *= damageResistanceMultiplier; Vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); } @@ -847,9 +941,10 @@ namespace Barotrauma private void Kill() { if (Unkillable || Character.GodMode) { return; } - - var causeOfDeath = GetCauseOfDeath(); - Character.Kill(causeOfDeath.First, causeOfDeath.Second); + + var (type, affliction) = GetCauseOfDeath(); + UpdateSkinTint(); + Character.Kill(type, affliction); #if CLIENT DisplayVitalityDelay = 0.0f; DisplayedVitality = Vitality; @@ -882,7 +977,7 @@ namespace Barotrauma } } - public Pair GetCauseOfDeath() + public (CauseOfDeathType type, Affliction affliction) GetCauseOfDeath() { List currentAfflictions = GetAllAfflictions(true); @@ -903,7 +998,7 @@ namespace Barotrauma causeOfDeath = Character.AnimController.InWater ? CauseOfDeathType.Drowning : CauseOfDeathType.Suffocation; } - return new Pair(causeOfDeath, strongestAffliction); + return (causeOfDeath, strongestAffliction); } // TODO: this method is called a lot (every half second) -> optimize, don't create new class instances and lists every time! @@ -949,15 +1044,16 @@ namespace Barotrauma /// A dictionary where the key is the identifier of the item and the value the suitability /// If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable. /// Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random) - public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, float randomization = 0.0f) + public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float randomization = 0.0f) { //key = item identifier //float = suitability treatmentSuitability.Clear(); float minSuitability = -10, maxSuitability = 10; - foreach (Affliction affliction in GetAllAfflictions()) + foreach (Affliction affliction in getAfflictions(limb)) { - if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; } + if (affliction.Strength <= affliction.Prefab.TreatmentThreshold) { continue; } + if (ignoreHiddenAfflictions && affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; } foreach (KeyValuePair treatment in affliction.Prefab.TreatmentSuitability) { if (!treatmentSuitability.ContainsKey(treatment.Key)) @@ -988,10 +1084,22 @@ namespace Barotrauma treatmentSuitability[treatment] += Rand.Range(-100.0f, 100.0f) * randomization; } } + + IEnumerable getAfflictions(Limb limb) + { + if (limb == null) + { + return GetAllAfflictions(); + } + else + { + return GetMatchingAfflictions(GetMatchingLimbHealth(limb)); + } + } } private readonly List activeAfflictions = new List(); - private readonly List> limbAfflictions = new List>(); + private readonly List<(LimbHealth limbHealth, Affliction affliction)> limbAfflictions = new List<(LimbHealth limbHealth, Affliction affliction)>(); public void ServerWrite(IWriteMessage msg) { activeAfflictions.Clear(); @@ -1007,7 +1115,7 @@ namespace Barotrauma { msg.Write(affliction.Prefab.UIntIdentifier); msg.WriteRangedSingle( - MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength), + MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength), 0.0f, affliction.Prefab.MaxStrength, 8); msg.Write((byte)affliction.Prefab.PeriodicEffects.Count()); foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects) @@ -1022,22 +1130,22 @@ namespace Barotrauma foreach (Affliction limbAffliction in limbHealth.Afflictions) { if (limbAffliction.Strength <= 0.0f || limbAffliction.Strength < limbAffliction.Prefab.ActivationThreshold) continue; - limbAfflictions.Add(new Pair(limbHealth, limbAffliction)); + limbAfflictions.Add((limbHealth, limbAffliction)); } } msg.Write((byte)limbAfflictions.Count); - foreach (var limbAffliction in limbAfflictions) + foreach (var (limbHealth, affliction) in limbAfflictions) { - msg.WriteRangedInteger(limbHealths.IndexOf(limbAffliction.First), 0, limbHealths.Count - 1); - msg.Write(limbAffliction.Second.Prefab.UIntIdentifier); + msg.WriteRangedInteger(limbHealths.IndexOf(limbHealth), 0, limbHealths.Count - 1); + msg.Write(affliction.Prefab.UIntIdentifier); msg.WriteRangedSingle( - MathHelper.Clamp(limbAffliction.Second.Strength, 0.0f, limbAffliction.Second.Prefab.MaxStrength), - 0.0f, limbAffliction.Second.Prefab.MaxStrength, 8); - msg.Write((byte)limbAffliction.Second.Prefab.PeriodicEffects.Count()); - foreach (AfflictionPrefab.PeriodicEffect periodicEffect in limbAffliction.Second.Prefab.PeriodicEffects) + MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength), + 0.0f, affliction.Prefab.MaxStrength, 8); + msg.Write((byte)affliction.Prefab.PeriodicEffects.Count()); + foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects) { - msg.WriteRangedSingle(limbAffliction.Second.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8); + msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8); } } } @@ -1053,7 +1161,7 @@ namespace Barotrauma /// Automatically filters out buffs. /// public static IEnumerable SortAfflictionsBySeverity(IEnumerable afflictions, bool excludeBuffs = true) => - afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength); + afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength / a.Prefab.MaxStrength); public void Save(XElement healthElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 2d6a462eb..7518e162d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -11,7 +11,7 @@ using System.Linq; using System.Xml.Linq; using Barotrauma.Extensions; using Barotrauma.MapCreatures.Behavior; -using MoonSharp.Interpreter; +using Barotrauma.Abilities; #if CLIENT using Microsoft.Xna.Framework.Graphics; @@ -25,7 +25,7 @@ namespace Barotrauma public ItemPrefab Prefab => prefab as ItemPrefab; public static bool ShowLinks = true; - + private HashSet tags; private bool isWire, isLogic; @@ -37,7 +37,6 @@ namespace Barotrauma set { currentHull = value; - ParentRuin = currentHull?.ParentRuin; } } @@ -98,14 +97,16 @@ namespace Barotrauma private Dictionary connections; - private List repairables; + private readonly List repairables; - private Queue impactQueue = new Queue(); + private Quality qualityComponent; + + private readonly Queue impactQueue = new Queue(); //a dictionary containing lists of the status effects in all the components of the item - private bool[] hasStatusEffectsOfType; - private Dictionary> statusEffectLists; - + private readonly bool[] hasStatusEffectsOfType; + private readonly Dictionary> statusEffectLists; + public Dictionary SerializableProperties { get; protected set; } private bool? hasInGameEditableProperties; @@ -170,7 +171,7 @@ namespace Barotrauma } } } - + public override string Name { get { return prefab.Name; } @@ -232,7 +233,6 @@ namespace Barotrauma { if (character != null && character.IsOnPlayerTeam) { - return IsPlayerTeamInteractable; } else @@ -254,6 +254,12 @@ namespace Barotrauma { if (!Prefab.AllowRotatingInEditor) { return; } rotationRad = MathHelper.ToRadians(value); +#if CLIENT + if (Screen.Selected == GameMain.SubEditorScreen) + { + SetContainedItemPositions(); + } +#endif } } @@ -261,7 +267,7 @@ namespace Barotrauma { get { return Prefab.ImpactTolerance; } } - + public float InteractDistance { get { return Prefab.InteractDistance; } @@ -344,7 +350,7 @@ namespace Barotrauma get; protected set; } - + [Editable, Serialize("1.0,1.0,1.0,1.0", true, description: "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true.")] public Color ContainerColor { @@ -360,9 +366,9 @@ namespace Barotrauma { get { - return - Container?.prefab.Identifier ?? - ParentInventory?.Owner?.ToString() ?? + return + Container?.prefab.Identifier ?? + ParentInventory?.Owner?.ToString() ?? ""; } set { /*do nothing*/ } @@ -449,42 +455,48 @@ namespace Barotrauma } public bool IsFullCondition => MathUtils.NearlyEqual(Condition, MaxCondition); - public float MaxCondition => Prefab.Health * healthMultiplier; + public float MaxCondition => Prefab.Health * healthMultiplier * maxRepairConditionMultiplier * (1.0f + GetQualityModifier(Items.Components.Quality.StatType.Condition)); public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition); private float offsetOnSelectedMultiplier = 1.0f; - + [Serialize(1.0f, false)] public float OffsetOnSelectedMultiplier { get => offsetOnSelectedMultiplier; set => offsetOnSelectedMultiplier = value; } - + private float healthMultiplier = 1.0f; [Serialize(1.0f, true, "Multiply the maximum condition by this value")] public float HealthMultiplier { get => healthMultiplier; - set - { - healthMultiplier = value; - } + set { healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); } } - + + private float maxRepairConditionMultiplier = 1.0f; + + [Serialize(1.0f, true)] + public float MaxRepairConditionMultiplier + { + get => maxRepairConditionMultiplier; + set { maxRepairConditionMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); } + } + //the default value should be Prefab.Health, but because we can't use it in the attribute, //we'll just use NaN (which does nothing) and set the default value in the constructor/load [Serialize(float.NaN, false), Editable] public float Condition { get { return condition; } - set + set { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } if (!MathUtils.IsValid(value)) { return; } if (Indestructible) { return; } - if (InvulnerableToDamage && value <= condition) { return;} + if (InvulnerableToDamage && value <= condition) { return; } float prev = condition; bool wasInFullCondition = IsFullCondition; @@ -501,7 +513,7 @@ namespace Barotrauma #endif ApplyStatusEffects(ActionType.OnBroken, 1.0f, null); } - + SetActiveSprite(); if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) @@ -541,6 +553,12 @@ namespace Barotrauma set => indestructible = value; } + public bool AllowDeconstruct + { + get; + set; + } + [Editable, Serialize(false, isSaveable: true, "When enabled will prevent the item from taking damage from all sources")] public bool InvulnerableToDamage { get; set; } @@ -614,9 +632,24 @@ namespace Barotrauma get { return Prefab.UseInHealthInterface; } } + public int Quality + { + get + { + return qualityComponent?.QualityLevel ?? 0; + } + set + { + if (qualityComponent != null) + { + qualityComponent.QualityLevel = value; + } + } + } + public bool InWater { - get + get { //if the item has an active physics body, inWater is updated in the Update method if (body != null && body.Enabled) { return inWater; } @@ -733,16 +766,16 @@ namespace Barotrauma { get { return allPropertyObjects; } } - + public bool IgnoreByAI(Character character) => HasTag("ignorebyai") || OrderedToBeIgnored && character.IsOnPlayerTeam; public bool OrderedToBeIgnored { get; set; } public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID) : this(new Rectangle( - (int)(position.X - itemPrefab.sprite.size.X / 2 * itemPrefab.Scale), - (int)(position.Y + itemPrefab.sprite.size.Y / 2 * itemPrefab.Scale), - (int)(itemPrefab.sprite.size.X * itemPrefab.Scale), - (int)(itemPrefab.sprite.size.Y * itemPrefab.Scale)), + (int)(position.X - itemPrefab.sprite.size.X / 2 * itemPrefab.Scale), + (int)(position.Y + itemPrefab.sprite.size.Y / 2 * itemPrefab.Scale), + (int)(itemPrefab.sprite.size.X * itemPrefab.Scale), + (int)(itemPrefab.sprite.size.Y * itemPrefab.Scale)), itemPrefab, submarine, id: id) { @@ -757,10 +790,10 @@ namespace Barotrauma { spriteColor = prefab.SpriteColor; - components = new List(); - drawableComponents = new List(); hasComponentsToDraw = false; - tags = new HashSet(); - repairables = new List(); + components = new List(); + drawableComponents = new List(); hasComponentsToDraw = false; + tags = new HashSet(); + repairables = new List(); defaultRect = newRect; rect = newRect; @@ -768,6 +801,8 @@ namespace Barotrauma condition = MaxCondition; lastSentCondition = condition; + AllowDeconstruct = itemPrefab.AllowDeconstruct; + allPropertyObjects.Add(this); XElement element = itemPrefab.ConfigElement; @@ -796,10 +831,10 @@ namespace Barotrauma body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile; } if (collisionCategory != null) - { + { if (!Physics.TryParseCollisionCategory(collisionCategory, out Category cat)) { - DebugConsole.ThrowError("Invalid collision category in item \"" + Name+"\" (" + collisionCategory + ")"); + DebugConsole.ThrowError("Invalid collision category in item \"" + Name + "\" (" + collisionCategory + ")"); } else { @@ -927,6 +962,8 @@ namespace Barotrauma ownInventory = itemContainer.Inventory; } + qualityComponent = GetComponent(); + InitProjSpecific(); if (callOnItemLoaded) @@ -944,6 +981,9 @@ namespace Barotrauma if (Components.Any(ic => ic is Wire) && Components.All(ic => ic is Wire || ic is Holdable)) { isWire = true; } if (HasTag("logic")) { isLogic = true; } + + ApplyStatusEffects(ActionType.OnSpawn, 1.0f); + Components.ForEach(c => c.ApplyStatusEffects(ActionType.OnSpawn, 1.0f)); } partial void InitProjSpecific(); @@ -990,7 +1030,7 @@ namespace Barotrauma continue; } - clone.components[i].requiredItems[kvp.Key][j].JoinedIdentifiers = + clone.components[i].requiredItems[kvp.Key][j].JoinedIdentifiers = kvp.Value[j].JoinedIdentifiers; } } @@ -998,7 +1038,7 @@ namespace Barotrauma if (FlippedX) clone.FlipX(false); if (FlippedY) clone.FlipY(false); - + foreach (ItemComponent component in clone.components) { component.OnItemLoaded(); @@ -1009,7 +1049,7 @@ namespace Barotrauma var containedClone = containedItem.Clone(); clone.ownInventory.TryPutItem(containedClone as Item, null); } - + return clone; } @@ -1023,7 +1063,7 @@ namespace Barotrauma updateableComponents.Add(component); } - component.OnActiveStateChanged += (bool isActive) => + component.OnActiveStateChanged += (bool isActive) => { bool hasSounds = false; #if CLIENT @@ -1031,7 +1071,7 @@ namespace Barotrauma #endif //component doesn't need to be updated if it isn't active, doesn't have a parent that could activate it, //nor status effects, sounds or conditionals that would need to run - if (!isActive && + if (!isActive && !hasSounds && component.Parent == null && (component.IsActiveConditionals == null || !component.IsActiveConditionals.Any()) && @@ -1041,8 +1081,8 @@ namespace Barotrauma } else { - if (!updateableComponents.Contains(component)) - { + if (!updateableComponents.Contains(component)) + { updateableComponents.Add(component); this.isActive = true; } @@ -1103,7 +1143,7 @@ namespace Barotrauma if (typeof(T) == typeof(ItemComponent)) { return (T)components.FirstOrDefault(); - } + } return default; } @@ -1116,12 +1156,17 @@ namespace Barotrauma if (!componentsByType.ContainsKey(typeof(T))) { return Enumerable.Empty(); } return components.Where(c => c is T).Cast(); } - + + public float GetQualityModifier(Quality.StatType statType) + { + return GetComponent()?.GetValue(statType) ?? 0.0f; + } + public void RemoveContained(Item contained) { ownInventory?.RemoveItem(contained); - contained.Container = null; + contained.Container = null; } public void SetTransform(Vector2 simPosition, float rotation, bool findNewHull = true, bool setPrevTransform = true) @@ -1263,7 +1308,7 @@ namespace Barotrauma { foreach (Item item in ItemList) item.FindHull(); } - + public Hull FindHull() { if (parentInventory != null && parentInventory.Owner != null) @@ -1278,7 +1323,7 @@ namespace Barotrauma } Submarine = parentInventory.Owner.Submarine; - if (body != null) body.Submarine = Submarine; + if (body != null) { body.Submarine = Submarine; } return CurrentHull; } @@ -1305,7 +1350,7 @@ namespace Barotrauma return rootContainer; } - + /// /// Should this item or any of its containers be ignored by the AI? /// @@ -1357,7 +1402,7 @@ namespace Barotrauma (component as ItemContainer)?.SetContainedItemPositions(); } } - + public void AddTag(string tag) { if (tags.Contains(tag)) { return; } @@ -1418,7 +1463,7 @@ namespace Barotrauma ApplyStatusEffect(effect, type, deltaTime, character, limb, useTarget, isNetworkEvent, false, worldPosition); } } - + readonly List targets = new List(); public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, bool checkCondition = true, Vector2? worldPosition = null) @@ -1428,11 +1473,11 @@ namespace Barotrauma if (condition == 0.0f && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { return; } } if (effect.type != type) { return; } - + bool hasTargets = effect.TargetIdentifiers == null; targets.Clear(); - + if (effect.HasTargetType(StatusEffect.TargetType.Contained)) { foreach (Item containedItem in ContainedItems) @@ -1444,6 +1489,11 @@ namespace Barotrauma continue; } + if (effect.TargetSlot > -1) + { + if (OwnInventory.FindIndex(containedItem) != effect.TargetSlot) { continue; } + } + hasTargets = true; targets.Add(containedItem); } @@ -1501,10 +1551,10 @@ namespace Barotrauma { targets.Add(limb); } - - if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) targets.Add(Container); - - effect.Apply(type, deltaTime, this, targets, worldPosition); + + if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) { targets.Add(Container); } + + effect.Apply(type, deltaTime, this, targets, worldPosition); } @@ -1526,7 +1576,7 @@ namespace Barotrauma private bool IsInWater() { if (CurrentHull == null) { return true; } - + float surfaceY = CurrentHull.Surface; return CurrentHull.WaterVolume > 0.0f && Position.Y < surfaceY; } @@ -1562,9 +1612,10 @@ namespace Barotrauma } } - aiTarget?.Update(deltaTime); - - GameMain.Lua.hook.Call("itemThink." + prefab.Identifier, new object[] { this, deltaTime }); + if (aiTarget != null) + { + aiTarget.Update(deltaTime); + } if (!isActive) { return; } @@ -1580,7 +1631,7 @@ namespace Barotrauma bool shouldBeActive = true; foreach (var conditional in ic.IsActiveConditionals) { - if (!ConditionalMatches(conditional)) + if (!ConditionalMatches(conditional)) { shouldBeActive = false; break; @@ -1678,14 +1729,22 @@ namespace Barotrauma } } - + public void UpdateTransform() { if (body == null) { return; } - Submarine prevSub = Submarine; - FindHull(); + var projectile = GetComponent(); + if (projectile?.StickTarget?.UserData is Limb limb) + { + Submarine = body.Submarine = limb.character?.Submarine; + currentHull = limb.character?.CurrentHull; + } + else + { + FindHull(); + } if (Submarine == null && prevSub != null) { @@ -1713,7 +1772,7 @@ namespace Barotrauma rect.X = (int)(displayPos.X - rect.Width / 2.0f); rect.Y = (int)(displayPos.Y + rect.Height / 2.0f); - if (Math.Abs(body.LinearVelocity.X) > NetConfig.MaxPhysicsBodyVelocity || + if (Math.Abs(body.LinearVelocity.X) > NetConfig.MaxPhysicsBodyVelocity || Math.Abs(body.LinearVelocity.Y) > NetConfig.MaxPhysicsBodyVelocity) { body.LinearVelocity = new Vector2( @@ -1752,17 +1811,23 @@ namespace Barotrauma Vector2 drag = body.LinearVelocity * volume; - body.ApplyForce((uplift - drag) * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + body.ApplyForce((uplift - drag) * 10.0f); //apply simple angular drag body.ApplyTorque(body.AngularVelocity * volume * -0.05f); - } + } private bool OnCollision(Fixture f1, Fixture f2, Contact contact) { if (transformDirty) { return false; } + var projectile = GetComponent(); + if (projectile?.IgnoredBodies != null) + { + if (projectile.IgnoredBodies.Contains(f2.Body)) { return false; } + } + contact.GetWorldManifold(out Vector2 normal, out _); if (contact.FixtureA.Body == f1.Body) { normal = -normal; } float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal); @@ -1791,7 +1856,7 @@ namespace Barotrauma foreach (Item contained in ContainedItems) { if (contained.body != null) { contained.HandleCollision(impact); } - } + } } } @@ -1802,10 +1867,10 @@ namespace Barotrauma //call the base method even if the item can't flip, to handle repositioning when flipping the whole sub base.FlipX(relativeToSub); - if (!Prefab.CanFlipX) + if (!Prefab.CanFlipX) { flippedX = false; - return; + return; } if (Prefab.AllowRotatingInEditor) @@ -1822,7 +1887,8 @@ namespace Barotrauma foreach (ItemComponent component in components) { component.FlipX(relativeToSub); - } + } + SetContainedItemPositions(); } public override void FlipY(bool relativeToSub) @@ -1847,6 +1913,7 @@ namespace Barotrauma { component.FlipY(relativeToSub); } + SetContainedItemPositions(); } /// @@ -1875,7 +1942,7 @@ namespace Barotrauma if (component != null && !connectedComponents.Contains(component)) { connectedComponents.Add(component); - } + } } } @@ -1906,28 +1973,28 @@ namespace Barotrauma return connectedComponents; } - - public static readonly Pair[] connectionPairs = new Pair[] + + public static readonly (string input, string output)[] connectionPairs = new (string input, string output)[] { - new Pair("power_in", "power_out"), - new Pair("signal_in1", "signal_out1"), - new Pair("signal_in2", "signal_out2"), - new Pair("signal_in3", "signal_out3"), - new Pair("signal_in4", "signal_out4"), - new Pair("signal_in", "signal_out"), - new Pair("signal_in1", "signal_out"), - new Pair("signal_in2", "signal_out") + ("power_in", "power_out"), + ("signal_in1", "signal_out1"), + ("signal_in2", "signal_out2"), + ("signal_in3", "signal_out3"), + ("signal_in4", "signal_out4"), + ("signal_in", "signal_out"), + ("signal_in1", "signal_out"), + ("signal_in2", "signal_out") }; private void GetConnectedComponentsRecursive(Connection c, HashSet alreadySearched, List connectedComponents) where T : ItemComponent { alreadySearched.Add(c); - + var recipients = c.Recipients; foreach (Connection recipient in recipients) { if (alreadySearched.Contains(recipient)) { continue; } - var component = recipient.Item.GetComponent(); + var component = recipient.Item.GetComponent(); if (component != null && !connectedComponents.Contains(component)) { connectedComponents.Add(component); @@ -1949,23 +2016,23 @@ namespace Barotrauma } } - recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents); + recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents); } - foreach (Pair connectionPair in connectionPairs) + foreach ((string input, string output) in connectionPairs) { - if (connectionPair.First == c.Name) + if (input == c.Name) { - var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == connectionPair.Second); + var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == output); if (pairedConnection != null) { if (alreadySearched.Contains(pairedConnection)) { continue; } GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents); } } - else if (connectionPair.Second == c.Name) + else if (output == c.Name) { - var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == connectionPair.First); + var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == input); if (pairedConnection != null) { if (alreadySearched.Contains(pairedConnection)) { continue; } @@ -1975,18 +2042,27 @@ namespace Barotrauma } } - public Controller FindController() + public Controller FindController(string[] tags = null) { //try finding the controller with the simpler non-recursive method first var controllers = GetConnectedComponents(); - if (controllers.None()) { controllers = GetConnectedComponents(recursive: true); } - return controllers.Count < 2 ? controllers.FirstOrDefault() : - (controllers.FirstOrDefault(c => c.GetFocusTarget() == this) ?? controllers.FirstOrDefault()); + bool needsTag = tags != null && tags.Length > 0; + if (controllers.None() || (needsTag && controllers.None(c => c.Item.HasTag(tags)))) + { + controllers = GetConnectedComponents(recursive: true); + } + if (needsTag) + { + controllers.RemoveAll(c => !c.Item.HasTag(tags)); + } + return controllers.Count < 2 ? + controllers.FirstOrDefault() : + controllers.FirstOrDefault(c => c.GetFocusTarget() == this) ?? controllers.FirstOrDefault(); } - public bool TryFindController(out Controller controller) + public bool TryFindController(out Controller controller, string[] tags = null) { - controller = FindController(); + controller = FindController(tags: tags); return controller != null; } @@ -2058,8 +2134,6 @@ namespace Barotrauma } while (CoroutineManager.DeltaTime <= 0.0f); delayedSignals.Remove((signal, connection)); - - signal.source = this; connection.SendSignal(signal); yield return CoroutineStatus.Success; @@ -2089,11 +2163,6 @@ namespace Barotrauma public bool TryInteract(Character picker, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceActionKey = false) { - var should = new LuaResult(GameMain.Lua.hook.Call("itemInteract", new object[] { this, picker, ignoreRequiredItems, forceSelectKey, forceActionKey })); - - if (!should.IsNull()) - return should.Bool(); - if (CampaignInteractionType != CampaignMode.InteractionType.None) { return false; } bool picked = false, selected = false; @@ -2151,7 +2220,7 @@ namespace Barotrauma } #endif if (!pickHit && !selectHit) { continue; } - + bool showUiMsg = false; #if CLIENT if (!ic.HasRequiredSkills(picker, out Skill tempRequiredSkill)) { hasRequiredSkills = false; skillMultiplier = ic.GetSkillMultiplier(); } @@ -2201,7 +2270,7 @@ namespace Barotrauma if (Container != null) Container.RemoveContained(this); - return true; + return true; } public float GetContainedItemConditionPercentage() @@ -2218,7 +2287,7 @@ namespace Barotrauma if (maxCondition > 0.0f) { return condition / maxCondition; - } + } return -1; } @@ -2232,11 +2301,6 @@ namespace Barotrauma if (condition == 0.0f) { return; } - var should = new LuaResult(GameMain.Lua.hook.Call("itemUse", new object[] { this, character, targetLimb })); - - if (should.Bool()) - return; - bool remove = false; foreach (ItemComponent ic in components) @@ -2253,7 +2317,7 @@ namespace Barotrauma #if CLIENT ic.PlaySound(ActionType.OnUse, character); #endif - + ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb); if (ic.DeleteOnUse) { remove = true; } @@ -2270,12 +2334,6 @@ namespace Barotrauma { if (condition == 0.0f) { return; } - var should = new LuaResult(GameMain.Lua.hook.Call("itemSecondaryUse", new object[] { this, character})); - - if (should.Bool()) - return; - - bool remove = false; foreach (ItemComponent ic in components) @@ -2307,14 +2365,9 @@ namespace Barotrauma public void ApplyTreatment(Character user, Character character, Limb targetLimb) { - var should = new LuaResult(GameMain.Lua.hook.Call("itemApplyTreatment", new object[] { this, user, character, targetLimb })); - - if (should.Bool()) - return; - //can't apply treatment to dead characters - if (character.IsDead) return; - if (!UseInHealthInterface) return; + if (character.IsDead) { return; } + if (!UseInHealthInterface) { return; } #if CLIENT if (GameMain.Client != null) @@ -2324,10 +2377,12 @@ namespace Barotrauma } #endif + float applyOnSelfFraction = user?.GetStatValue(StatTypes.ApplyTreatmentsOnSelfFraction) ?? 0.0f; + bool remove = false; foreach (ItemComponent ic in components) { - if (!ic.HasRequiredContainedItems(user, addMessage: user == Character.Controlled)) continue; + if (!ic.HasRequiredContainedItems(user, addMessage: user == Character.Controlled)) { continue; } bool success = Rand.Range(0.0f, 0.5f) < ic.DegreeOfSuccess(user); ActionType actionType = success ? ActionType.OnUse : ActionType.OnFailure; @@ -2336,7 +2391,19 @@ namespace Barotrauma ic.PlaySound(actionType, user); #endif ic.WasUsed = true; - ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user); + ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user, applyOnUserFraction: applyOnSelfFraction); + + if (applyOnSelfFraction > 0.0f) + { + //hacky af + ic.statusEffectLists.TryGetValue(actionType, out var effectList); + if (effectList != null) + { + effectList.ForEach(e => e.AfflictionMultiplier = applyOnSelfFraction); + ic.ApplyStatusEffects(actionType, 1.0f, user, targetLimb == null ? null : user.AnimController.GetLimb(targetLimb.type), user: user); + effectList.ForEach(e => e.AfflictionMultiplier = 1.0f); + } + } if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { @@ -2346,19 +2413,23 @@ namespace Barotrauma }); } - if (ic.DeleteOnUse) remove = true; + if (ic.DeleteOnUse) { remove = true; } } + if (user != null) + { + var abilityItem = new AbilityApplyTreatment(user, character, this); + user.CheckTalents(AbilityEffectType.OnApplyTreatment, abilityItem); + + } + + + if (remove) { Spawner?.AddToRemoveQueue(this); } } public bool Combine(Item item, Character user) { - var should = new LuaResult(GameMain.Lua.hook.Call("itemCombine", new object[] { this, item, user })); - - if (!should.IsNull()) - return should.Bool(); - if (item == this) { return false; } bool isCombined = false; foreach (ItemComponent ic in components) @@ -2373,11 +2444,6 @@ namespace Barotrauma public void Drop(Character dropper, bool createNetworkEvent = true) { - var should = new LuaResult(GameMain.Lua.hook.Call("itemDrop", new object[] { this, dropper})); - - if (should.Bool()) - return; - if (createNetworkEvent) { if (parentInventory != null && !parentInventory.Owner.Removed && !Removed && @@ -2411,28 +2477,25 @@ namespace Barotrauma } foreach (ItemComponent ic in components) { ic.Drop(dropper); } - + if (Container != null) { SetTransform(Container.SimPosition, 0.0f); Container.RemoveContained(this); Container = null; } - + if (parentInventory != null) { parentInventory.RemoveItem(this); parentInventory = null; } + + SetContainedItemPositions(); } public void Equip(Character character) { - var should = new LuaResult(GameMain.Lua.hook.Call("itemEquip", new object[] { this, character})); - - if (should.Bool()) - return; - if (Removed) { DebugConsole.ThrowError($"Tried to equip a removed item ({Name}).\n{Environment.StackTrace.CleanupStackTrace()}"); @@ -2444,11 +2507,6 @@ namespace Barotrauma public void Unequip(Character character) { - var should = new LuaResult(GameMain.Lua.hook.Call("itemUnequip", new object[] { this, character })); - - if (should.Bool()) - return; - foreach (ItemComponent ic in components) { ic.Unequip(character); } } @@ -2460,7 +2518,7 @@ namespace Barotrauma foreach (var itemProperty in itemProperties) { allProperties.Add(new Pair(this, itemProperty)); - } + } foreach (ItemComponent ic in components) { List componentProperties = SerializableProperty.GetProperties(ic); @@ -2542,6 +2600,14 @@ namespace Barotrauma { msg.Write((int)value); } + else if (value is string[] a) + { + msg.Write(a.Length); + for (int i = 0; i < a.Length; i++) + { + msg.Write(a[i] ?? ""); + } + } else { throw new NotImplementedException("Serializing item properties of the type \"" + value.GetType() + "\" not supported"); @@ -2590,8 +2656,8 @@ namespace Barotrauma { string val = msg.ReadString(); logValue = val; - if (allowEditing) - { + if (allowEditing) + { property.TrySetValue(parentObject, val); } } @@ -2649,13 +2715,26 @@ namespace Barotrauma logValue = XMLExtensions.RectToString(val); if (allowEditing) { property.TrySetValue(parentObject, val); } } + else if (type == typeof(string[])) + { + int arrayLength = msg.ReadInt32(); + string[] val = new string[arrayLength]; + for (int i = 0; i < arrayLength; i++) + { + val[i] = msg.ReadString(); + } + if (allowEditing) + { + property.TrySetValue(parentObject, val); + } + } else if (typeof(Enum).IsAssignableFrom(type)) { int intVal = msg.ReadInt32(); try { - if (allowEditing) - { + if (allowEditing) + { property.TrySetValue(parentObject, Enum.ToObject(type, intVal)); logValue = property.GetValue(parentObject).ToString(); } @@ -2684,10 +2763,9 @@ namespace Barotrauma { CoroutineManager.StopCoroutines(logPropertyChangeCoroutine); } - logPropertyChangeCoroutine = CoroutineManager.InvokeAfter(() => + logPropertyChangeCoroutine = CoroutineManager.Invoke(() => { - if(sender.Character != null) - GameServer.Log($"{sender.Character.Name} set the value \"{property.Name}\" of the item \"{Name}\" to \"{logValue}\".", ServerLog.MessageType.ItemInteraction); + GameServer.Log($"{sender.Character.Name} set the value \"{property.Name}\" of the item \"{Name}\" to \"{logValue}\".", ServerLog.MessageType.ItemInteraction); }, delay: 1.0f); } #endif @@ -2925,7 +3003,7 @@ namespace Barotrauma { component.OnItemLoaded(); } - + return item; } @@ -2975,7 +3053,7 @@ namespace Barotrauma (int)(rect.X - subPosition.X) + "," + (int)(rect.Y - subPosition.Y) + "," + width + "," + height)); - + if (linkedTo != null && linkedTo.Count > 0) { bool isOutpost = Submarine != null && Submarine.Info.IsOutpost; @@ -3025,7 +3103,7 @@ namespace Barotrauma ic.OnMapLoaded(); } } - + /// /// Remove the item so that it doesn't appear to exist in the game world (stop sounds, remove bodies etc) /// but don't reset anything that's required for cloning the item @@ -3033,7 +3111,7 @@ namespace Barotrauma public override void ShallowRemove() { base.ShallowRemove(); - + foreach (ItemComponent ic in components) { ic.ShallowRemove(); @@ -3055,7 +3133,7 @@ namespace Barotrauma return; } DebugConsole.Log("Removing item " + Name + " (ID: " + ID + ")"); - + base.Remove(); foreach (Character character in Character.CharacterList) @@ -3074,6 +3152,8 @@ namespace Barotrauma } } + connections?.Clear(); + if (parentInventory != null) { if (parentInventory is CharacterInventory characterInventory) @@ -3099,6 +3179,8 @@ namespace Barotrauma body = null; } + CurrentHull = null; + if (StaticFixtures != null) { foreach (Fixture fixture in StaticFixtures)