fix merge conflicts, warning: still broken

This commit is contained in:
Evil Factory
2021-10-27 13:35:10 -03:00
parent 9d80de3ecf
commit 1f0f411d4f
5 changed files with 570 additions and 341 deletions
@@ -27,7 +27,16 @@ namespace Barotrauma
GameServer.Log(GameServer.CharacterLogName(this) + " has died (Cause of death: " + causeOfDeath + ")", ServerLog.MessageType.Attack); 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; healthUpdateTimer = 0.0f;
if (CauseOfDeath.Killer != null && CauseOfDeath.Killer.IsTraitor && CauseOfDeath.Killer != this) 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); var owner = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
if (owner != null) 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) 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 });
}
} }
} }
@@ -11,7 +11,6 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Xml.Linq; using System.Xml.Linq;
using MoonSharp.Interpreter;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -197,15 +196,13 @@ namespace Barotrauma.Networking
#endif #endif
} }
GameMain.Lua.Initialize();
TickRate = serverSettings.TickRate; TickRate = serverSettings.TickRate;
Log("Server started", ServerLog.MessageType.ServerMessage); Log("Server started", ServerLog.MessageType.ServerMessage);
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
GameMain.NetLobbyScreen.RandomizeSettings(); GameMain.NetLobbyScreen.RandomizeSettings();
if (!string.IsNullOrEmpty(serverSettings.SelectedSubmarine)) if (!string.IsNullOrEmpty(serverSettings.SelectedSubmarine))
{ {
SubmarineInfo sub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == serverSettings.SelectedSubmarine); SubmarineInfo sub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == serverSettings.SelectedSubmarine);
if (sub != null) { GameMain.NetLobbyScreen.SelectedSub = sub; } if (sub != null) { GameMain.NetLobbyScreen.SelectedSub = sub; }
@@ -312,10 +309,7 @@ namespace Barotrauma.Networking
SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient); SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient);
} }
GameMain.Lua.hook.Call("clientConnected", new object[] { newClient }); SendChatMessage($"ServerMessage.JoinedServer~[client]={ClientLogName(newClient)}", ChatMessageType.Server, null, changeType: PlayerConnectionChangeType.Joined);
SendChatMessage($"ServerMessage.JoinedServer~[client]={clName}", ChatMessageType.Server, null, changeType: PlayerConnectionChangeType.Joined);
serverSettings.ServerDetailsChanged = true; serverSettings.ServerDetailsChanged = true;
if (previousPlayer != null && previousPlayer.Name != newClient.Name) if (previousPlayer != null && previousPlayer.Name != newClient.Name)
@@ -542,7 +536,7 @@ namespace Barotrauma.Networking
initiatedStartGame = false; 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)) (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign || GameMain.GameSession?.GameMode is MultiPlayerCampaign))
{ {
if (serverSettings.AutoRestart) if (serverSettings.AutoRestart)
@@ -693,7 +687,7 @@ namespace Barotrauma.Networking
if (Timing.TotalTime > lastPingTime + 1.0) if (Timing.TotalTime > lastPingTime + 1.0)
{ {
lastPingData ??= new byte[64]; lastPingData ??= new byte[64];
for (int i=0;i<lastPingData.Length;i++) for (int i = 0; i < lastPingData.Length; i++)
{ {
lastPingData[i] = (byte)Rand.Range(33, 126); lastPingData[i] = (byte)Rand.Range(33, 126);
} }
@@ -849,9 +843,6 @@ namespace Barotrauma.Networking
case ClientPacketHeader.ERROR: case ClientPacketHeader.ERROR:
HandleClientError(inc, connectedClient); HandleClientError(inc, connectedClient);
break; break;
case ClientPacketHeader.LUA_NET_MESSAGE:
GameMain.Lua.networking.NetMessageReceived(inc, connectedClient);
break;
} }
} }
@@ -979,9 +970,9 @@ namespace Barotrauma.Networking
{ {
var spawnData = entityEvent.Data[0] as EntitySpawner.SpawnOrRemove; var spawnData = entityEvent.Data[0] as EntitySpawner.SpawnOrRemove;
errorLines.Add( errorLines.Add(
entityEvent.ID + ": " + entityEvent.ID + ": " +
(spawnData.Remove ? "Remove " : "Create ") + (spawnData.Remove ? "Remove " : "Create ") +
spawnData.Entity.ToString() + spawnData.Entity.ToString() +
" (" + spawnData.OriginalID + ", " + spawnData.Entity.ID + ")"); " (" + spawnData.OriginalID + ", " + spawnData.Entity.ID + ")");
} }
} }
@@ -1190,6 +1181,10 @@ namespace Barotrauma.Networking
{ {
c.Character.ServerRead(objHeader, inc, c); c.Character.ServerRead(objHeader, inc, c);
} }
else
{
DebugConsole.AddWarning($"Received character inputs from a client who's not controlling a character ({c.Name}).");
}
break; break;
case ClientNetObject.ENTITY_STATE: case ClientNetObject.ENTITY_STATE:
entityEventManager.Read(inc, c); entityEventManager.Read(inc, c);
@@ -1226,7 +1221,7 @@ namespace Barotrauma.Networking
sender.WaitForNextRoundRespawn = null; sender.WaitForNextRoundRespawn = null;
} }
} }
private void ClientReadServerCommand(IReadMessage inc) private void ClientReadServerCommand(IReadMessage inc)
{ {
Client sender = ConnectedClients.Find(x => x.Connection == inc.Sender); Client sender = ConnectedClients.Find(x => x.Connection == inc.Sender);
@@ -1327,11 +1322,11 @@ namespace Barotrauma.Networking
Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage);
if (mpCampaign != null && Level.IsLoadedOutpost) if (mpCampaign != null && Level.IsLoadedOutpost)
{ {
mpCampaign.SaveInventories(); mpCampaign.SavePlayers();
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
} }
EndGame(); EndGame();
} }
} }
else else
@@ -1462,7 +1457,7 @@ namespace Barotrauma.Networking
} }
break; break;
case ClientPermissions.ManageCampaign: case ClientPermissions.ManageCampaign:
(GameMain.GameSession.GameMode as MultiPlayerCampaign)?.ServerRead(inc, sender); (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.ServerRead(inc, sender);
break; break;
case ClientPermissions.ConsoleCommands: case ClientPermissions.ConsoleCommands:
{ {
@@ -1818,7 +1813,7 @@ namespace Barotrauma.Networking
outmsg.Write(client.InGame); outmsg.Write(client.InGame);
outmsg.Write(client.Permissions != ClientPermissions.None); outmsg.Write(client.Permissions != ClientPermissions.None);
outmsg.Write(client.Connection == OwnerConnection); outmsg.Write(client.Connection == OwnerConnection);
outmsg.Write(client.Connection != OwnerConnection && outmsg.Write(client.Connection != OwnerConnection &&
!client.HasPermission(ClientPermissions.Ban) && !client.HasPermission(ClientPermissions.Ban) &&
!client.HasPermission(ClientPermissions.Kick) && !client.HasPermission(ClientPermissions.Kick) &&
!client.HasPermission(ClientPermissions.Unban)); //is kicking the player allowed !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; bool isInitialUpdate = false;
@@ -2198,8 +2193,8 @@ namespace Barotrauma.Networking
bool isOutpost = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost; bool isOutpost = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost;
if (serverSettings.AllowRespawn && missionAllowRespawn) 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) if (campaign != null)
{ {
@@ -2291,7 +2286,7 @@ namespace Barotrauma.Networking
} }
AssignBotJobs(bots, teamID); AssignBotJobs(bots, teamID);
if (campaign != null) if (campaign != null)
{ {
foreach (CharacterInfo bot in bots) foreach (CharacterInfo bot in bots)
{ {
@@ -2308,7 +2303,7 @@ namespace Barotrauma.Networking
List<WayPoint> spawnWaypoints = null; List<WayPoint> spawnWaypoints = null;
List<WayPoint> mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]).ToList(); List<WayPoint> 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.Type == LevelData.LevelType.Outpost &&
(Level.Loaded.StartOutpost.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false) && (Level.Loaded.StartOutpost.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false) &&
Level.Loaded.StartOutpost.GetConnectedSubs().Any(s => s.Info.Type == SubmarineType.Player)) Level.Loaded.StartOutpost.GetConnectedSubs().Any(s => s.Info.Type == SubmarineType.Player))
@@ -2363,8 +2358,16 @@ namespace Barotrauma.Networking
characterData.ApplyHealthData(spawnedCharacter); characterData.ApplyHealthData(spawnedCharacter);
characterData.ApplyOrderData(spawnedCharacter); characterData.ApplyOrderData(spawnedCharacter);
spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]);
spawnedCharacter.LoadTalents();
characterData.HasSpawned = true; 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.OwnerClientEndPoint = teamClients[i].Connection.EndPointString;
spawnedCharacter.OwnerClientName = teamClients[i].Name; spawnedCharacter.OwnerClientName = teamClients[i].Name;
} }
@@ -2375,6 +2378,8 @@ namespace Barotrauma.Networking
spawnedCharacter.TeamID = teamID; spawnedCharacter.TeamID = teamID;
spawnedCharacter.GiveJobItems(mainSubWaypoints[i]); spawnedCharacter.GiveJobItems(mainSubWaypoints[i]);
spawnedCharacter.GiveIdCardTags(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.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); Log("Round started.", ServerLog.MessageType.ServerMessage);
gameStarted = true; gameStarted = true;
initiatedStartGame = false; initiatedStartGame = false;
GameMain.ResetFrameTime(); GameMain.ResetFrameTime();
@@ -2444,9 +2445,7 @@ namespace Barotrauma.Networking
roundStartTime = DateTime.Now; roundStartTime = DateTime.Now;
GameMain.Lua.hook.Call("roundStart", new object[] { }); startGameCoroutine = null;
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -2471,6 +2470,7 @@ namespace Barotrauma.Networking
msg.Write(serverSettings.AllowRespawn && missionAllowRespawn); msg.Write(serverSettings.AllowRespawn && missionAllowRespawn);
msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowDisguises);
msg.Write(serverSettings.AllowRewiring); msg.Write(serverSettings.AllowRewiring);
msg.Write(serverSettings.AllowFriendlyFire);
msg.Write(serverSettings.LockAllDefaultWires); msg.Write(serverSettings.LockAllDefaultWires);
msg.Write(serverSettings.AllowRagdollButton); msg.Write(serverSettings.AllowRagdollButton);
msg.Write(serverSettings.UseRespawnShuttle); msg.Write(serverSettings.UseRespawnShuttle);
@@ -2566,9 +2566,6 @@ namespace Barotrauma.Networking
Log("Ending the round...", ServerLog.MessageType.ServerMessage); Log("Ending the round...", ServerLog.MessageType.ServerMessage);
} }
GameMain.Lua.hook.Call("roundEnd", new object[] { });
string endMessage = TextManager.FormatServerMessage("RoundSummaryRoundHasEnded"); string endMessage = TextManager.FormatServerMessage("RoundSummaryRoundHasEnded");
var traitorResults = TraitorManager?.GetEndResults() ?? new List<TraitorMissionResult>(); var traitorResults = TraitorManager?.GetEndResults() ?? new List<TraitorMissionResult>();
@@ -2638,8 +2635,8 @@ namespace Barotrauma.Networking
} }
} }
Submarine.Unload();
entityEventManager.Clear(); entityEventManager.Clear();
Submarine.Unload();
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
Log("Round ended.", ServerLog.MessageType.ServerMessage); 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 //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); previousPlayer.Karma = Math.Max(previousPlayer.Karma, 50.0f);
if (!string.IsNullOrEmpty(previousPlayer.EndPoint) && (previousPlayer.SteamID == 0 || range)) if (!string.IsNullOrEmpty(previousPlayer.EndPoint) && (previousPlayer.SteamID == 0 || range))
{ {
string ip = previousPlayer.EndPoint; string ip = previousPlayer.EndPoint;
@@ -2862,8 +2859,6 @@ namespace Barotrauma.Networking
{ {
if (client == null) return; if (client == null) return;
GameMain.Lua.hook.Call("clientDisconnected", new object[] { client });
if (gameStarted && client.Character != null) if (gameStarted && client.Character != null)
{ {
client.Character.ClientDisconnected = true; client.Character.ClientDisconnected = true;
@@ -3103,21 +3098,13 @@ namespace Barotrauma.Networking
senderName = null; senderName = null;
senderCharacter = null; senderCharacter = null;
} }
else if (type == ChatMessageType.Radio && !GameMain.Lua.game.overrideSignalRadio) else if (type == ChatMessageType.Radio)
{ {
//send to chat-linked wifi components //send to chat-linked wifi components
Signal s = new Signal(message, sender: senderCharacter, source: senderRadio.Item); Signal s = new Signal(message, sender: senderCharacter, source: senderRadio.Item);
senderRadio.TransmitSignal(s, sentFromChat: true); 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 //check which clients can receive the message and apply distance effects
foreach (Client client in ConnectedClients) foreach (Client client in ConnectedClients)
{ {
@@ -3150,15 +3137,14 @@ namespace Barotrauma.Networking
break; break;
} }
var chatMsg = ChatMessage.Create( var chatMsg = ChatMessage.Create(
senderName, senderName,
modifiedMessage, modifiedMessage,
(ChatMessageType)type, (ChatMessageType)type,
senderCharacter, senderCharacter,
senderClient, senderClient,
changeType); changeType);
SendDirectChatMessage(chatMsg, client); SendDirectChatMessage(chatMsg, client);
} }
@@ -3175,28 +3161,19 @@ namespace Barotrauma.Networking
public void SendOrderChatMessage(OrderChatMessage message) public void SendOrderChatMessage(OrderChatMessage message)
{ {
if (message.Sender == null || message.Sender.SpeechImpediment >= 100.0f) { return; } 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 //check which clients can receive the message and apply distance effects
foreach (Client client in ConnectedClients) 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 //too far to hear the msg -> don't send
if (!client.Character.CanHearCharacter(message.Sender)) { continue; } if (!client.Character.CanHearCharacter(message.Sender)) { continue; }
} }
SendDirectChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, message.TargetEntity, message.TargetCharacter, message.Sender), client); SendDirectChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, message.TargetEntity, message.TargetCharacter, message.Sender), client);
} }
if (!string.IsNullOrWhiteSpace(message.Text))
string myReceivedMessage = message.Text;
if (!string.IsNullOrWhiteSpace(myReceivedMessage))
{ {
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; newCharacter.LastNetworkUpdateID = client.Character.LastNetworkUpdateID;
} }
if (newCharacter.Info != null && newCharacter.Info.Character == null) if (newCharacter.Info != null && newCharacter.Info.Character == null)
{ {
newCharacter.Info.Character = newCharacter; newCharacter.Info.Character = newCharacter;
@@ -3492,15 +3469,15 @@ namespace Barotrauma.Networking
} }
catch (Exception e) catch (Exception e)
{ {
//gender = Gender.Male;
//race = Race.White;
//headSpriteId = 0;
DebugConsole.Log("Received invalid characterinfo from \"" + sender.Name + "\"! { " + e.Message + " }"); DebugConsole.Log("Received invalid characterinfo from \"" + sender.Name + "\"! { " + e.Message + " }");
} }
int hairIndex = message.ReadByte(); int hairIndex = message.ReadByte();
int beardIndex = message.ReadByte(); int beardIndex = message.ReadByte();
int moustacheIndex = message.ReadByte(); int moustacheIndex = message.ReadByte();
int faceAttachmentIndex = message.ReadByte(); int faceAttachmentIndex = message.ReadByte();
Color skinColor = message.ReadColorR8G8B8();
Color hairColor = message.ReadColorR8G8B8();
Color facialHairColor = message.ReadColorR8G8B8();
List<Pair<JobPrefab, int>> jobPreferences = new List<Pair<JobPrefab, int>>(); List<Pair<JobPrefab, int>> jobPreferences = new List<Pair<JobPrefab, int>>();
int count = message.ReadByte(); int count = message.ReadByte();
@@ -3517,6 +3494,9 @@ namespace Barotrauma.Networking
sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, sender.Name); sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, sender.Name);
sender.CharacterInfo.RecreateHead(headSpriteId, race, gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); 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) if (jobPreferences.Count > 0)
{ {
@@ -3709,7 +3689,7 @@ namespace Barotrauma.Networking
{ {
c.AssignedJob = preferredJob; c.AssignedJob = preferredJob;
assignedClientCount[preferredJob.First]++; assignedClientCount[preferredJob.First]++;
break; break;
} }
} }
else //none of the client's preferred jobs available, choose a random job 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); unassignedBots[0].Job = new Job(jobPrefab, variant);
assignedPlayerCount[jobPrefab]++; assignedPlayerCount[jobPrefab]++;
unassignedBots.Remove(unassignedBots[0]); unassignedBots.Remove(unassignedBots[0]);
canAssign = true; canAssign = true;
} }
} while (unassignedBots.Count > 0 && canAssign); } while (unassignedBots.Count > 0 && canAssign);
//find a suitable job for the rest of the bots //find a suitable job for the rest of the bots
foreach (CharacterInfo c in unassignedBots) foreach (CharacterInfo c in unassignedBots)
{ {
@@ -3816,15 +3796,15 @@ namespace Barotrauma.Networking
return preferredClient; return preferredClient;
} }
public void UpdateMissionState(Mission mission, int state) public void UpdateMissionState(Mission mission)
{ {
foreach (var client in connectedClients) foreach (var client in connectedClients)
{ {
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.MISSION); msg.Write((byte)ServerPacketHeader.MISSION);
int missionIndex = GameMain.GameSession.GetMissionIndex(mission); int missionIndex = GameMain.GameSession.GetMissionIndex(mission);
msg.Write((byte)(missionIndex == -1 ? 255: missionIndex)); msg.Write((byte)(missionIndex == -1 ? 255 : missionIndex));
msg.Write((ushort)state); mission?.ServerWrite(msg);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); 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.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); GameMain.Server.ServerSettings.ServerLog.WriteLine(line, messageType);
foreach (Client client in GameMain.Server.ConnectedClients) foreach (Client client in GameMain.Server.ConnectedClients)
@@ -3880,7 +3857,7 @@ namespace Barotrauma.Networking
string submarinesString = string.Empty; string submarinesString = string.Empty;
for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++) 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); submarinesString.Trim(ServerSettings.SubmarineSeparatorChar);
serverSettings.CampaignSubmarines = submarinesString; serverSettings.CampaignSubmarines = submarinesString;
@@ -23,6 +23,7 @@
<Item file="Content/Items/Electricity/signalitems.xml" /> <Item file="Content/Items/Electricity/signalitems.xml" />
<Item file="Content/Items/Engine/engine.xml" /> <Item file="Content/Items/Engine/engine.xml" />
<Item file="Content/Items/Fabricators/fabricators.xml" /> <Item file="Content/Items/Fabricators/fabricators.xml" />
<Item file="Content/Items/Genetic/genetic.xml" />
<Item file="Content/Items/Labels/labels.xml" /> <Item file="Content/Items/Labels/labels.xml" />
<Item file="Content/Items/Ladder/ladder.xml" /> <Item file="Content/Items/Ladder/ladder.xml" />
<Item file="Content/Items/Materials/materials.xml" /> <Item file="Content/Items/Materials/materials.xml" />
@@ -63,6 +64,12 @@
<Item file="Content/Items/Gardening/gardeningtools.xml" /> <Item file="Content/Items/Gardening/gardeningtools.xml" />
<Item file="Content/Items/Gardening/plantproducts.xml" /> <Item file="Content/Items/Gardening/plantproducts.xml" />
<Item file="Content/Map/Pirates/PirateItems.xml" /> <Item file="Content/Map/Pirates/PirateItems.xml" />
<Item file="Content/Items/Jobgear/Assistant/assistant_talent_items.xml" />
<Item file="Content/Items/Jobgear/Mechanic/mechanic_talent_items.xml" />
<Item file="Content/Items/Jobgear/Security/securityofficer_talent_items.xml" />
<Item file="Content/Items/Jobgear/Engineer/engineer_talent_items.xml" />
<Item file="Content/Items/Jobgear/Medic/medic_talent_items.xml" />
<Item file="Content/Items/Jobgear/Captain/captain_talent_items.xml" />
<Character file="Content/Characters/Balloon/Balloon.xml" /> <Character file="Content/Characters/Balloon/Balloon.xml" />
<Character file="Content/Characters/Carrier/Carrier.xml" /> <Character file="Content/Characters/Carrier/Carrier.xml" />
<Character file="Content/Characters/Charybdis/Charybdis.xml" /> <Character file="Content/Characters/Charybdis/Charybdis.xml" />
@@ -73,6 +80,7 @@
<Character file="Content/Characters/Endworm/Endworm.xml" /> <Character file="Content/Characters/Endworm/Endworm.xml" />
<Character file="Content/Characters/Fractalguardian/Fractalguardian.xml" /> <Character file="Content/Characters/Fractalguardian/Fractalguardian.xml" />
<Character file="Content/Characters/Fractalguardian2/Fractalguardian2.xml" /> <Character file="Content/Characters/Fractalguardian2/Fractalguardian2.xml" />
<Character file="Content/Characters/Swarmfeeder/Swarmfeeder.xml" />
<Character file="Content/Characters/Hammerhead/Hammerhead.xml" /> <Character file="Content/Characters/Hammerhead/Hammerhead.xml" />
<Character file="Content/Characters/Hammerheadgold/Hammerheadgold.xml" /> <Character file="Content/Characters/Hammerheadgold/Hammerheadgold.xml" />
<Character file="Content/Characters/Hammerheadmatriarch/Hammerheadmatriarch.xml" /> <Character file="Content/Characters/Hammerheadmatriarch/Hammerheadmatriarch.xml" />
@@ -80,6 +88,8 @@
<Character file="Content/Characters/Human/Human.xml" /> <Character file="Content/Characters/Human/Human.xml" />
<Character file="Content/Characters/Husk/Husk.xml" /> <Character file="Content/Characters/Husk/Husk.xml" />
<Character file="Content/Characters/Humanhusk/Humanhusk.xml" /> <Character file="Content/Characters/Humanhusk/Humanhusk.xml" />
<Character file="Content/Characters/Legacyfractalguardian/Legacyfractalguardian.xml" />
<Character file="Content/Characters/Legacyfractalguardian2/Legacyfractalguardian2.xml" />
<Character file="Content/Characters/Legacyhusk/Legacyhusk.xml" /> <Character file="Content/Characters/Legacyhusk/Legacyhusk.xml" />
<Character file="Content/Characters/Legacycharybdis/Legacycharybdis.xml" /> <Character file="Content/Characters/Legacycharybdis/Legacycharybdis.xml" />
<Character file="Content/Characters/Legacycrawler/Legacycrawler.xml" /> <Character file="Content/Characters/Legacycrawler/Legacycrawler.xml" />
@@ -147,6 +157,13 @@
<Text file="Content/Texts/Korean/KoreanVanilla.xml" /> <Text file="Content/Texts/Korean/KoreanVanilla.xml" />
<UIStyle file="Content/UI/style.xml" /> <UIStyle file="Content/UI/style.xml" />
<Afflictions file="Content/Afflictions.xml" /> <Afflictions file="Content/Afflictions.xml" />
<Afflictions file="Content/AfflictionsGeneticMaterial.xml" />
<Afflictions file="Content/Talents/Assistant/AfflictionsAssistant.xml" />
<Afflictions file="Content/Talents/Captain/AfflictionsCaptain.xml" />
<Afflictions file="Content/Talents/Doctor/AfflictionsDoctor.xml" />
<Afflictions file="Content/Talents/Engineer/AfflictionsEngineer.xml" />
<Afflictions file="Content/Talents/Mechanic/AfflictionsMechanic.xml" />
<Afflictions file="Content/Talents/Security/AfflictionsSecurity.xml" />
<Structure file="Content/Map/StructurePrefabs.xml" /> <Structure file="Content/Map/StructurePrefabs.xml" />
<Structure file="Content/Map/Outposts/OutpostStructurePrefabs.xml" /> <Structure file="Content/Map/Outposts/OutpostStructurePrefabs.xml" />
<Structure file="Content/Map/Pirates/PirateStructures.xml" /> <Structure file="Content/Map/Pirates/PirateStructures.xml" />
@@ -155,6 +172,33 @@
<Structure file="Content/Map/Thalamus/thalamusstructures.xml" /> <Structure file="Content/Map/Thalamus/thalamusstructures.xml" />
<UpgradeModules file="Content/Upgrades/UpgradeModules.xml" /> <UpgradeModules file="Content/Upgrades/UpgradeModules.xml" />
<RuinConfig file="Content/Map/RuinConfig.xml" /> <RuinConfig file="Content/Map/RuinConfig.xml" />
<OutpostModule file="Content/Map/RuinModules/Alien_Chambers4Way.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Chasm.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_DeadEndL.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_DeadEndR.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_HallwayHorizontal.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_HallwayVertical.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_HallwaySphere.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_MaintenancePassages.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_MaintenancePassages2.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_SecurityModule.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_VaultModule1.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_VaultModule2.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Chambers1.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Chambers2.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_MaintenanceTunnels1.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_MaintenanceTunnels2R.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_MaintenanceTunnels2L.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Entrance1.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_GeneticStorage1L.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_GeneticStorage1R.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Chasm2.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Chasm3.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Entrance2.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Entrance3.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Storage1R.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Storage1L.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Offices1.sub" />
<WreckAIConfig file="Content/Map/Thalamus/ThalamusAI.xml" /> <WreckAIConfig file="Content/Map/Thalamus/ThalamusAI.xml" />
<BackgroundCreaturePrefabs file="Content/BackgroundCreatures/BackgroundCreaturePrefabs.xml" /> <BackgroundCreaturePrefabs file="Content/BackgroundCreatures/BackgroundCreaturePrefabs.xml" />
<LevelObjectPrefabs file="Content/Map/Biomes/Common/CommonLevelObjects.xml" /> <LevelObjectPrefabs file="Content/Map/Biomes/Common/CommonLevelObjects.xml" />
@@ -261,4 +305,11 @@
<EnemySubmarine file="Content/Map/EnemySubmarines/DugongPirate.sub"/> <EnemySubmarine file="Content/Map/EnemySubmarines/DugongPirate.sub"/>
<EnemySubmarine file="Content/Map/EnemySubmarines/HumpbackPirate.sub"/> <EnemySubmarine file="Content/Map/EnemySubmarines/HumpbackPirate.sub"/>
<EnemySubmarine file="Content/Map/EnemySubmarines/Typhon2Pirate.sub"/> <EnemySubmarine file="Content/Map/EnemySubmarines/Typhon2Pirate.sub"/>
</contentpackage> <Talents file="Content/Talents/Assistant/TalentsAssistant.xml"/>
<Talents file="Content/Talents/Security/TalentsSecurity.xml"/>
<Talents file="Content/Talents/Engineer/TalentsEngineer.xml"/>
<Talents file="Content/Talents/Mechanic/TalentsMechanic.xml"/>
<Talents file="Content/Talents/Captain/TalentsCaptain.xml"/>
<Talents file="Content/Talents/Doctor/TalentsDoctor.xml"/>
<TalentTrees file="Content/Talents/TalentTrees.xml"/>
</contentpackage>
@@ -6,13 +6,13 @@ using System.Xml.Linq;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using System.Globalization; using System.Globalization;
using MoonSharp.Interpreter; using Barotrauma.Abilities;
namespace Barotrauma namespace Barotrauma
{ {
partial class CharacterHealth partial class CharacterHealth
{ {
public class LimbHealth class LimbHealth
{ {
public Sprite IndicatorSprite; public Sprite IndicatorSprite;
public Sprite HighlightSprite; public Sprite HighlightSprite;
@@ -20,7 +20,7 @@ namespace Barotrauma
public Rectangle HighlightArea; public Rectangle HighlightArea;
public readonly string Name; public readonly string Name;
public readonly List<Affliction> Afflictions = new List<Affliction>(); public readonly List<Affliction> Afflictions = new List<Affliction>();
public readonly Dictionary<string, float> VitalityMultipliers = new Dictionary<string, float>(); public readonly Dictionary<string, float> VitalityMultipliers = new Dictionary<string, float>();
@@ -76,7 +76,7 @@ namespace Barotrauma
} }
} }
} }
public List<Affliction> GetActiveAfflictions(AfflictionPrefab prefab) public List<Affliction> GetActiveAfflictions(AfflictionPrefab prefab)
{ {
return Afflictions.FindAll(a => a.Prefab == prefab); return Afflictions.FindAll(a => a.Prefab == prefab);
@@ -85,7 +85,7 @@ namespace Barotrauma
{ {
return Afflictions.FindAll(a => a.Prefab.AfflictionType == afflictionType); return Afflictions.FindAll(a => a.Prefab.AfflictionType == afflictionType);
} }
} }
public const float InsufficientOxygenThreshold = 30.0f; public const float InsufficientOxygenThreshold = 30.0f;
public const float LowOxygenThreshold = 50.0f; public const float LowOxygenThreshold = 50.0f;
@@ -117,15 +117,15 @@ namespace Barotrauma
private set => Character.Params.Health.CrushDepth = value; private set => Character.Params.Health.CrushDepth = value;
} }
private List<LimbHealth> limbHealths = new List<LimbHealth>(); private readonly List<LimbHealth> limbHealths = new List<LimbHealth>();
//non-limb-specific afflictions //non-limb-specific afflictions
private List<Affliction> afflictions = new List<Affliction>(); private readonly List<Affliction> afflictions = new List<Affliction>();
/// <summary> /// <summary>
/// Note: returns only the non-limb-secific afflictions. Use GetAllAfflictions or some other method for getting also the limb-specific afflictions. /// Note: returns only the non-limb-secific afflictions. Use GetAllAfflictions or some other method for getting also the limb-specific afflictions.
/// </summary> /// </summary>
public IEnumerable<Affliction> Afflictions => afflictions; public IEnumerable<Affliction> Afflictions => afflictions;
private HashSet<Affliction> irremovableAfflictions = new HashSet<Affliction>(); private readonly HashSet<Affliction> irremovableAfflictions = new HashSet<Affliction>();
private Affliction bloodlossAffliction; private Affliction bloodlossAffliction;
private Affliction oxygenLowAffliction; private Affliction oxygenLowAffliction;
private Affliction pressureAffliction; private Affliction pressureAffliction;
@@ -133,7 +133,7 @@ namespace Barotrauma
public bool IsUnconscious 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; public float PressureKillDelay { get; private set; } = 5.0f;
@@ -152,6 +152,7 @@ namespace Barotrauma
max += Character.Info.Job.Prefab.VitalityModifier; max += Character.Info.Job.Prefab.VitalityModifier;
} }
max *= Character.StaticHealthMultiplier; max *= Character.StaticHealthMultiplier;
max *= 1f + Character.GetStatValue(StatTypes.MaximumHealthMultiplier);
return max * Character.HealthMultiplier; 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 public float OxygenAmount
{ {
get get
@@ -191,7 +206,11 @@ namespace Barotrauma
public float Stun public float Stun
{ {
get { return stunAffliction.Strength; } 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; } public float StunTimer { get; private set; }
@@ -223,7 +242,7 @@ namespace Barotrauma
this.Character = character; this.Character = character;
InitIrremovableAfflictions(); InitIrremovableAfflictions();
Vitality = maxVitality; Vitality = maxVitality;
minVitality = character.IsHuman ? -100.0f : 0.0f; 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(Limb limb) => limb == null ? null : limbHealths[limb.HealthIndex];
private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb, excludeSevered: false)); private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb, excludeSevered: false));
/// <summary>
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
/// </summary>
private IEnumerable<Affliction> GetMatchingAfflictions(LimbHealth limb)
=> limb.Afflictions.Union(afflictions.Where(a => GetMatchingLimbHealth(a) == limb));
/// <summary> /// <summary>
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
/// </summary> /// </summary>
@@ -298,7 +323,7 @@ namespace Barotrauma
public Affliction GetAffliction(string identifier, bool allowLimbAfflictions = true) public Affliction GetAffliction(string identifier, bool allowLimbAfflictions = true)
=> GetAffliction(a => a.Prefab.Identifier == identifier, allowLimbAfflictions); => 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); => GetAffliction(a => a.Prefab.AfflictionType == afflictionType, allowLimbAfflictions);
private Affliction GetAffliction(Func<Affliction, bool> predicate, bool allowLimbAfflictions = true) private Affliction GetAffliction(Func<Affliction, bool> predicate, bool allowLimbAfflictions = true)
@@ -402,62 +427,61 @@ namespace Barotrauma
return strength; 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.IsBuff && Unkillable || Character.GodMode) { return; }
if (affliction.Prefab.LimbSpecific) if (affliction.Prefab.LimbSpecific)
{ {
if (targetLimb == null) 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 //if a limb-specific affliction is applied to no specific limb, apply to all limbs
foreach (LimbHealth limbHealth in limbHealths) foreach (LimbHealth limbHealth in limbHealths)
{ {
AddLimbAffliction(limbHealth, affliction); AddLimbAffliction(limbHealth, affliction, allowStacking: allowStacking);
} }
} }
else else
{ {
var should = new LuaResult(GameMain.Lua.hook.Call("afflictionApplied", new object[] { this, affliction, targetLimb })); AddLimbAffliction(targetLimb, affliction, allowStacking: allowStacking);
if (should.Bool())
return;
AddLimbAffliction(targetLimb, affliction);
} }
} }
else else
{ {
var should = new LuaResult(GameMain.Lua.hook.Call("afflictionApplied", new object[] { this, affliction })); AddAffliction(affliction, allowStacking: allowStacking);
if (should.Bool())
return;
AddAffliction(affliction);
} }
} }
public float GetResistance(string resistanceId) public float GetResistance(AfflictionPrefab affliction)
{ {
float resistance = 0.0f; float resistance = 0.0f;
for (int i = 0; i < afflictions.Count; i++) for (int i = 0; i < afflictions.Count; i++)
{ {
if (!afflictions[i].Prefab.IsBuff) continue; resistance += afflictions[i].GetResistance(affliction);
float temp = afflictions[i].GetResistance(resistanceId);
if (temp > resistance) resistance = temp;
} }
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<Affliction> matchingAfflictions = new List<Affliction>(); private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
public void ReduceAffliction(Limb targetLimb, string affliction, float amount) public void ReduceAffliction(Limb targetLimb, string affliction, float amount, ActionType? treatmentAction = null)
{ {
matchingAfflictions.Clear(); matchingAfflictions.Clear();
matchingAfflictions.AddRange(afflictions); matchingAfflictions.AddRange(afflictions);
@@ -486,6 +510,14 @@ namespace Barotrauma
for (int i = matchingAfflictions.Count - 1; i >= 0; i--) for (int i = matchingAfflictions.Count - 1; i >= 0; i--)
{ {
var matchingAffliction = matchingAfflictions[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) if (matchingAffliction.Strength < reduceAmount)
{ {
float surplus = reduceAmount - matchingAffliction.Strength; float surplus = reduceAmount - matchingAffliction.Strength;
@@ -500,6 +532,17 @@ namespace Barotrauma
{ {
matchingAffliction.Strength -= reduceAmount; matchingAffliction.Strength -= reduceAmount;
amount -= 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(); CalculateVitality();
@@ -515,11 +558,6 @@ namespace Barotrauma
return; 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) foreach (Affliction newAffliction in attackResult.Afflictions)
{ {
if (newAffliction.Prefab.LimbSpecific) if (newAffliction.Prefab.LimbSpecific)
@@ -530,15 +568,15 @@ namespace Barotrauma
{ {
AddAffliction(newAffliction, allowStacking); AddAffliction(newAffliction, allowStacking);
} }
} }
} }
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount) public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
{ {
if (Unkillable || Character.GodMode) { return; } if (Unkillable || Character.GodMode) { return; }
foreach (LimbHealth limbHealth in limbHealths) foreach (LimbHealth limbHealth in limbHealths)
{ {
limbHealth.Afflictions.RemoveAll(a => limbHealth.Afflictions.RemoveAll(a =>
a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType || a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType ||
a.Prefab.AfflictionType == AfflictionPrefab.Burn.AfflictionType || a.Prefab.AfflictionType == AfflictionPrefab.Burn.AfflictionType ||
a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType); a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType);
@@ -562,9 +600,9 @@ namespace Barotrauma
else 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. // 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. // Therefore with e.g. 80 health, the max damage per limb would be 40.
// Having at least 20 damage on both legs would cause maximum limping. // Having at least 40 damage on both legs would cause maximum limping.
float max = MaxVitality / 4; float max = MaxVitality / 2;
if (string.IsNullOrEmpty(afflictionType)) if (string.IsNullOrEmpty(afflictionType))
{ {
float damage = GetAfflictionStrength("damage", limb, true); float damage = GetAfflictionStrength("damage", limb, true);
@@ -595,6 +633,22 @@ namespace Barotrauma
CalculateVitality(); 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) private void AddLimbAffliction(Limb limb, Affliction newAffliction, bool allowStacking = true)
{ {
if (!newAffliction.Prefab.LimbSpecific || limb == null) { return; } if (!newAffliction.Prefab.LimbSpecific || limb == null) { return; }
@@ -616,7 +670,7 @@ namespace Barotrauma
{ {
if (newAffliction.Prefab == affliction.Prefab) 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) if (allowStacking)
{ {
// Add the existing strength // 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 //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 //or modify the affliction instance of an Attack or a StatusEffect
var copyAffliction = newAffliction.Prefab.Instantiate( 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); newAffliction.Source);
limbHealth.Afflictions.Add(copyAffliction); limbHealth.Afflictions.Add(copyAffliction);
Character.HealthUpdateInterval = 0.0f; Character.HealthUpdateInterval = 0.0f;
CalculateVitality(); CalculateVitality();
@@ -660,6 +714,7 @@ namespace Barotrauma
private void AddAffliction(Affliction newAffliction, bool allowStacking = true) private void AddAffliction(Affliction newAffliction, bool allowStacking = true)
{ {
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; } 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 (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab) if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab)
{ {
@@ -672,7 +727,7 @@ namespace Barotrauma
{ {
if (newAffliction.Prefab == affliction.Prefab) 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) if (allowStacking)
{ {
// Add the existing strength // 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 //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 //or modify the affliction instance of an Attack or a StatusEffect
afflictions.Add(newAffliction.Prefab.Instantiate( 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)); source: newAffliction.Source));
Character.HealthUpdateInterval = 0.0f; Character.HealthUpdateInterval = 0.0f;
@@ -706,8 +761,6 @@ namespace Barotrauma
} }
} }
partial void UpdateProjSpecific(float deltaTime);
partial void UpdateLimbAfflictionOverlays(); partial void UpdateLimbAfflictionOverlays();
public void Update(float deltaTime) public void Update(float deltaTime)
@@ -716,6 +769,8 @@ namespace Barotrauma
StunTimer = Stun > 0 ? StunTimer + deltaTime : 0; StunTimer = Stun > 0 ? StunTimer + deltaTime : 0;
if (Character.GodMode) { return; }
for (int i = 0; i < limbHealths.Count; i++) for (int i = 0; i < limbHealths.Count; i++)
{ {
for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
@@ -743,11 +798,11 @@ namespace Barotrauma
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
} }
} }
for (int i = afflictions.Count - 1; i >= 0; i--) for (int i = afflictions.Count - 1; i >= 0; i--)
{ {
var affliction = afflictions[i]; var affliction = afflictions[i];
if (irremovableAfflictions.Contains(affliction)) continue; if (irremovableAfflictions.Contains(affliction)) { continue; }
if (affliction.Strength <= 0.0f) if (affliction.Strength <= 0.0f)
{ {
SteamAchievementManager.OnAfflictionRemoved(affliction, Character); SteamAchievementManager.OnAfflictionRemoved(affliction, Character);
@@ -761,9 +816,21 @@ namespace Barotrauma
affliction.DamagePerSecondTimer += deltaTime; affliction.DamagePerSecondTimer += deltaTime;
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); 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(); CalculateVitality();
if (Vitality <= MinVitality) 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) private void UpdateOxygen(float deltaTime)
{ {
if (!Character.NeedsOxygen) { return; } if (!Character.NeedsOxygen) { return; }
@@ -780,16 +873,21 @@ namespace Barotrauma
if (IsUnconscious) if (IsUnconscious)
{ {
//the character dies of oxygen deprivation in 100 seconds after losing consciousness //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 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); UpdateOxygenProjSpecific(prevOxygen, deltaTime);
} }
partial void UpdateOxygenProjSpecific(float prevOxygen, float deltaTime); partial void UpdateOxygenProjSpecific(float prevOxygen, float deltaTime);
partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime); partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime);
@@ -805,8 +903,6 @@ namespace Barotrauma
Vitality = MaxVitality; Vitality = MaxVitality;
if (Unkillable || Character.GodMode) { return; } if (Unkillable || Character.GodMode) { return; }
float damageResistanceMultiplier = 1f - GetResistance("damage");
foreach (LimbHealth limbHealth in limbHealths) foreach (LimbHealth limbHealth in limbHealths)
{ {
foreach (Affliction affliction in limbHealth.Afflictions) foreach (Affliction affliction in limbHealth.Afflictions)
@@ -822,7 +918,6 @@ namespace Barotrauma
{ {
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type]; vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type];
} }
vitalityDecrease *= damageResistanceMultiplier;
Vitality -= vitalityDecrease; Vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease); affliction.CalculateDamagePerSecond(vitalityDecrease);
} }
@@ -831,7 +926,6 @@ namespace Barotrauma
foreach (Affliction affliction in afflictions) foreach (Affliction affliction in afflictions)
{ {
float vitalityDecrease = affliction.GetVitalityDecrease(this); float vitalityDecrease = affliction.GetVitalityDecrease(this);
vitalityDecrease *= damageResistanceMultiplier;
Vitality -= vitalityDecrease; Vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease); affliction.CalculateDamagePerSecond(vitalityDecrease);
} }
@@ -847,9 +941,10 @@ namespace Barotrauma
private void Kill() private void Kill()
{ {
if (Unkillable || Character.GodMode) { return; } if (Unkillable || Character.GodMode) { return; }
var causeOfDeath = GetCauseOfDeath(); var (type, affliction) = GetCauseOfDeath();
Character.Kill(causeOfDeath.First, causeOfDeath.Second); UpdateSkinTint();
Character.Kill(type, affliction);
#if CLIENT #if CLIENT
DisplayVitalityDelay = 0.0f; DisplayVitalityDelay = 0.0f;
DisplayedVitality = Vitality; DisplayedVitality = Vitality;
@@ -882,7 +977,7 @@ namespace Barotrauma
} }
} }
public Pair<CauseOfDeathType, Affliction> GetCauseOfDeath() public (CauseOfDeathType type, Affliction affliction) GetCauseOfDeath()
{ {
List<Affliction> currentAfflictions = GetAllAfflictions(true); List<Affliction> currentAfflictions = GetAllAfflictions(true);
@@ -903,7 +998,7 @@ namespace Barotrauma
causeOfDeath = Character.AnimController.InWater ? CauseOfDeathType.Drowning : CauseOfDeathType.Suffocation; causeOfDeath = Character.AnimController.InWater ? CauseOfDeathType.Drowning : CauseOfDeathType.Suffocation;
} }
return new Pair<CauseOfDeathType, Affliction>(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! // 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
/// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param> /// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param>
/// <param name="normalize">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.</param> /// <param name="normalize">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.</param>
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param> /// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, float randomization = 0.0f) public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float randomization = 0.0f)
{ {
//key = item identifier //key = item identifier
//float = suitability //float = suitability
treatmentSuitability.Clear(); treatmentSuitability.Clear();
float minSuitability = -10, maxSuitability = 10; 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<string, float> treatment in affliction.Prefab.TreatmentSuitability) foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
{ {
if (!treatmentSuitability.ContainsKey(treatment.Key)) if (!treatmentSuitability.ContainsKey(treatment.Key))
@@ -988,10 +1084,22 @@ namespace Barotrauma
treatmentSuitability[treatment] += Rand.Range(-100.0f, 100.0f) * randomization; treatmentSuitability[treatment] += Rand.Range(-100.0f, 100.0f) * randomization;
} }
} }
IEnumerable<Affliction> getAfflictions(Limb limb)
{
if (limb == null)
{
return GetAllAfflictions();
}
else
{
return GetMatchingAfflictions(GetMatchingLimbHealth(limb));
}
}
} }
private readonly List<Affliction> activeAfflictions = new List<Affliction>(); private readonly List<Affliction> activeAfflictions = new List<Affliction>();
private readonly List<Pair<LimbHealth, Affliction>> limbAfflictions = new List<Pair<LimbHealth, Affliction>>(); private readonly List<(LimbHealth limbHealth, Affliction affliction)> limbAfflictions = new List<(LimbHealth limbHealth, Affliction affliction)>();
public void ServerWrite(IWriteMessage msg) public void ServerWrite(IWriteMessage msg)
{ {
activeAfflictions.Clear(); activeAfflictions.Clear();
@@ -1007,7 +1115,7 @@ namespace Barotrauma
{ {
msg.Write(affliction.Prefab.UIntIdentifier); msg.Write(affliction.Prefab.UIntIdentifier);
msg.WriteRangedSingle( 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); 0.0f, affliction.Prefab.MaxStrength, 8);
msg.Write((byte)affliction.Prefab.PeriodicEffects.Count()); msg.Write((byte)affliction.Prefab.PeriodicEffects.Count());
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects) foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects)
@@ -1022,22 +1130,22 @@ namespace Barotrauma
foreach (Affliction limbAffliction in limbHealth.Afflictions) foreach (Affliction limbAffliction in limbHealth.Afflictions)
{ {
if (limbAffliction.Strength <= 0.0f || limbAffliction.Strength < limbAffliction.Prefab.ActivationThreshold) continue; if (limbAffliction.Strength <= 0.0f || limbAffliction.Strength < limbAffliction.Prefab.ActivationThreshold) continue;
limbAfflictions.Add(new Pair<LimbHealth, Affliction>(limbHealth, limbAffliction)); limbAfflictions.Add((limbHealth, limbAffliction));
} }
} }
msg.Write((byte)limbAfflictions.Count); 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.WriteRangedInteger(limbHealths.IndexOf(limbHealth), 0, limbHealths.Count - 1);
msg.Write(limbAffliction.Second.Prefab.UIntIdentifier); msg.Write(affliction.Prefab.UIntIdentifier);
msg.WriteRangedSingle( msg.WriteRangedSingle(
MathHelper.Clamp(limbAffliction.Second.Strength, 0.0f, limbAffliction.Second.Prefab.MaxStrength), MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength),
0.0f, limbAffliction.Second.Prefab.MaxStrength, 8); 0.0f, affliction.Prefab.MaxStrength, 8);
msg.Write((byte)limbAffliction.Second.Prefab.PeriodicEffects.Count()); msg.Write((byte)affliction.Prefab.PeriodicEffects.Count());
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in limbAffliction.Second.Prefab.PeriodicEffects) 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. /// Automatically filters out buffs.
/// </summary> /// </summary>
public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> afflictions, bool excludeBuffs = true) => public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> 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) public void Save(XElement healthElement)
{ {
@@ -11,7 +11,7 @@ using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.MapCreatures.Behavior; using Barotrauma.MapCreatures.Behavior;
using MoonSharp.Interpreter; using Barotrauma.Abilities;
#if CLIENT #if CLIENT
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@@ -25,7 +25,7 @@ namespace Barotrauma
public ItemPrefab Prefab => prefab as ItemPrefab; public ItemPrefab Prefab => prefab as ItemPrefab;
public static bool ShowLinks = true; public static bool ShowLinks = true;
private HashSet<string> tags; private HashSet<string> tags;
private bool isWire, isLogic; private bool isWire, isLogic;
@@ -37,7 +37,6 @@ namespace Barotrauma
set set
{ {
currentHull = value; currentHull = value;
ParentRuin = currentHull?.ParentRuin;
} }
} }
@@ -98,14 +97,16 @@ namespace Barotrauma
private Dictionary<string, Connection> connections; private Dictionary<string, Connection> connections;
private List<Repairable> repairables; private readonly List<Repairable> repairables;
private Queue<float> impactQueue = new Queue<float>(); private Quality qualityComponent;
private readonly Queue<float> impactQueue = new Queue<float>();
//a dictionary containing lists of the status effects in all the components of the item //a dictionary containing lists of the status effects in all the components of the item
private bool[] hasStatusEffectsOfType; private readonly bool[] hasStatusEffectsOfType;
private Dictionary<ActionType, List<StatusEffect>> statusEffectLists; private readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; } public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; }
private bool? hasInGameEditableProperties; private bool? hasInGameEditableProperties;
@@ -170,7 +171,7 @@ namespace Barotrauma
} }
} }
} }
public override string Name public override string Name
{ {
get { return prefab.Name; } get { return prefab.Name; }
@@ -232,7 +233,6 @@ namespace Barotrauma
{ {
if (character != null && character.IsOnPlayerTeam) if (character != null && character.IsOnPlayerTeam)
{ {
return IsPlayerTeamInteractable; return IsPlayerTeamInteractable;
} }
else else
@@ -254,6 +254,12 @@ namespace Barotrauma
{ {
if (!Prefab.AllowRotatingInEditor) { return; } if (!Prefab.AllowRotatingInEditor) { return; }
rotationRad = MathHelper.ToRadians(value); rotationRad = MathHelper.ToRadians(value);
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen)
{
SetContainedItemPositions();
}
#endif
} }
} }
@@ -261,7 +267,7 @@ namespace Barotrauma
{ {
get { return Prefab.ImpactTolerance; } get { return Prefab.ImpactTolerance; }
} }
public float InteractDistance public float InteractDistance
{ {
get { return Prefab.InteractDistance; } get { return Prefab.InteractDistance; }
@@ -344,7 +350,7 @@ namespace Barotrauma
get; get;
protected set; 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.")] [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 public Color ContainerColor
{ {
@@ -360,9 +366,9 @@ namespace Barotrauma
{ {
get get
{ {
return return
Container?.prefab.Identifier ?? Container?.prefab.Identifier ??
ParentInventory?.Owner?.ToString() ?? ParentInventory?.Owner?.ToString() ??
""; "";
} }
set { /*do nothing*/ } set { /*do nothing*/ }
@@ -449,42 +455,48 @@ namespace Barotrauma
} }
public bool IsFullCondition => MathUtils.NearlyEqual(Condition, MaxCondition); 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); public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition);
private float offsetOnSelectedMultiplier = 1.0f; private float offsetOnSelectedMultiplier = 1.0f;
[Serialize(1.0f, false)] [Serialize(1.0f, false)]
public float OffsetOnSelectedMultiplier public float OffsetOnSelectedMultiplier
{ {
get => offsetOnSelectedMultiplier; get => offsetOnSelectedMultiplier;
set => offsetOnSelectedMultiplier = value; set => offsetOnSelectedMultiplier = value;
} }
private float healthMultiplier = 1.0f; private float healthMultiplier = 1.0f;
[Serialize(1.0f, true, "Multiply the maximum condition by this value")] [Serialize(1.0f, true, "Multiply the maximum condition by this value")]
public float HealthMultiplier public float HealthMultiplier
{ {
get => healthMultiplier; get => healthMultiplier;
set set { healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); }
{
healthMultiplier = value;
}
} }
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, //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 //we'll just use NaN (which does nothing) and set the default value in the constructor/load
[Serialize(float.NaN, false), Editable] [Serialize(float.NaN, false), Editable]
public float Condition public float Condition
{ {
get { return condition; } get { return condition; }
set set
{ {
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (!MathUtils.IsValid(value)) { return; } if (!MathUtils.IsValid(value)) { return; }
if (Indestructible) { return; } if (Indestructible) { return; }
if (InvulnerableToDamage && value <= condition) { return;} if (InvulnerableToDamage && value <= condition) { return; }
float prev = condition; float prev = condition;
bool wasInFullCondition = IsFullCondition; bool wasInFullCondition = IsFullCondition;
@@ -501,7 +513,7 @@ namespace Barotrauma
#endif #endif
ApplyStatusEffects(ActionType.OnBroken, 1.0f, null); ApplyStatusEffects(ActionType.OnBroken, 1.0f, null);
} }
SetActiveSprite(); SetActiveSprite();
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
@@ -541,6 +553,12 @@ namespace Barotrauma
set => indestructible = value; 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")] [Editable, Serialize(false, isSaveable: true, "When enabled will prevent the item from taking damage from all sources")]
public bool InvulnerableToDamage { get; set; } public bool InvulnerableToDamage { get; set; }
@@ -614,9 +632,24 @@ namespace Barotrauma
get { return Prefab.UseInHealthInterface; } get { return Prefab.UseInHealthInterface; }
} }
public int Quality
{
get
{
return qualityComponent?.QualityLevel ?? 0;
}
set
{
if (qualityComponent != null)
{
qualityComponent.QualityLevel = value;
}
}
}
public bool InWater public bool InWater
{ {
get get
{ {
//if the item has an active physics body, inWater is updated in the Update method //if the item has an active physics body, inWater is updated in the Update method
if (body != null && body.Enabled) { return inWater; } if (body != null && body.Enabled) { return inWater; }
@@ -733,16 +766,16 @@ namespace Barotrauma
{ {
get { return allPropertyObjects; } get { return allPropertyObjects; }
} }
public bool IgnoreByAI(Character character) => HasTag("ignorebyai") || OrderedToBeIgnored && character.IsOnPlayerTeam; public bool IgnoreByAI(Character character) => HasTag("ignorebyai") || OrderedToBeIgnored && character.IsOnPlayerTeam;
public bool OrderedToBeIgnored { get; set; } public bool OrderedToBeIgnored { get; set; }
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID) public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID)
: this(new Rectangle( : this(new Rectangle(
(int)(position.X - itemPrefab.sprite.size.X / 2 * itemPrefab.Scale), (int)(position.X - itemPrefab.sprite.size.X / 2 * itemPrefab.Scale),
(int)(position.Y + itemPrefab.sprite.size.Y / 2 * itemPrefab.Scale), (int)(position.Y + itemPrefab.sprite.size.Y / 2 * itemPrefab.Scale),
(int)(itemPrefab.sprite.size.X * itemPrefab.Scale), (int)(itemPrefab.sprite.size.X * itemPrefab.Scale),
(int)(itemPrefab.sprite.size.Y * itemPrefab.Scale)), (int)(itemPrefab.sprite.size.Y * itemPrefab.Scale)),
itemPrefab, submarine, id: id) itemPrefab, submarine, id: id)
{ {
@@ -757,10 +790,10 @@ namespace Barotrauma
{ {
spriteColor = prefab.SpriteColor; spriteColor = prefab.SpriteColor;
components = new List<ItemComponent>(); components = new List<ItemComponent>();
drawableComponents = new List<IDrawableComponent>(); hasComponentsToDraw = false; drawableComponents = new List<IDrawableComponent>(); hasComponentsToDraw = false;
tags = new HashSet<string>(); tags = new HashSet<string>();
repairables = new List<Repairable>(); repairables = new List<Repairable>();
defaultRect = newRect; defaultRect = newRect;
rect = newRect; rect = newRect;
@@ -768,6 +801,8 @@ namespace Barotrauma
condition = MaxCondition; condition = MaxCondition;
lastSentCondition = condition; lastSentCondition = condition;
AllowDeconstruct = itemPrefab.AllowDeconstruct;
allPropertyObjects.Add(this); allPropertyObjects.Add(this);
XElement element = itemPrefab.ConfigElement; XElement element = itemPrefab.ConfigElement;
@@ -796,10 +831,10 @@ namespace Barotrauma
body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile; body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
} }
if (collisionCategory != null) if (collisionCategory != null)
{ {
if (!Physics.TryParseCollisionCategory(collisionCategory, out Category cat)) 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 else
{ {
@@ -927,6 +962,8 @@ namespace Barotrauma
ownInventory = itemContainer.Inventory; ownInventory = itemContainer.Inventory;
} }
qualityComponent = GetComponent<Quality>();
InitProjSpecific(); InitProjSpecific();
if (callOnItemLoaded) 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 (Components.Any(ic => ic is Wire) && Components.All(ic => ic is Wire || ic is Holdable)) { isWire = true; }
if (HasTag("logic")) { isLogic = true; } if (HasTag("logic")) { isLogic = true; }
ApplyStatusEffects(ActionType.OnSpawn, 1.0f);
Components.ForEach(c => c.ApplyStatusEffects(ActionType.OnSpawn, 1.0f));
} }
partial void InitProjSpecific(); partial void InitProjSpecific();
@@ -990,7 +1030,7 @@ namespace Barotrauma
continue; continue;
} }
clone.components[i].requiredItems[kvp.Key][j].JoinedIdentifiers = clone.components[i].requiredItems[kvp.Key][j].JoinedIdentifiers =
kvp.Value[j].JoinedIdentifiers; kvp.Value[j].JoinedIdentifiers;
} }
} }
@@ -998,7 +1038,7 @@ namespace Barotrauma
if (FlippedX) clone.FlipX(false); if (FlippedX) clone.FlipX(false);
if (FlippedY) clone.FlipY(false); if (FlippedY) clone.FlipY(false);
foreach (ItemComponent component in clone.components) foreach (ItemComponent component in clone.components)
{ {
component.OnItemLoaded(); component.OnItemLoaded();
@@ -1009,7 +1049,7 @@ namespace Barotrauma
var containedClone = containedItem.Clone(); var containedClone = containedItem.Clone();
clone.ownInventory.TryPutItem(containedClone as Item, null); clone.ownInventory.TryPutItem(containedClone as Item, null);
} }
return clone; return clone;
} }
@@ -1023,7 +1063,7 @@ namespace Barotrauma
updateableComponents.Add(component); updateableComponents.Add(component);
} }
component.OnActiveStateChanged += (bool isActive) => component.OnActiveStateChanged += (bool isActive) =>
{ {
bool hasSounds = false; bool hasSounds = false;
#if CLIENT #if CLIENT
@@ -1031,7 +1071,7 @@ namespace Barotrauma
#endif #endif
//component doesn't need to be updated if it isn't active, doesn't have a parent that could activate it, //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 //nor status effects, sounds or conditionals that would need to run
if (!isActive && if (!isActive &&
!hasSounds && !hasSounds &&
component.Parent == null && component.Parent == null &&
(component.IsActiveConditionals == null || !component.IsActiveConditionals.Any()) && (component.IsActiveConditionals == null || !component.IsActiveConditionals.Any()) &&
@@ -1041,8 +1081,8 @@ namespace Barotrauma
} }
else else
{ {
if (!updateableComponents.Contains(component)) if (!updateableComponents.Contains(component))
{ {
updateableComponents.Add(component); updateableComponents.Add(component);
this.isActive = true; this.isActive = true;
} }
@@ -1103,7 +1143,7 @@ namespace Barotrauma
if (typeof(T) == typeof(ItemComponent)) if (typeof(T) == typeof(ItemComponent))
{ {
return (T)components.FirstOrDefault(); return (T)components.FirstOrDefault();
} }
return default; return default;
} }
@@ -1116,12 +1156,17 @@ namespace Barotrauma
if (!componentsByType.ContainsKey(typeof(T))) { return Enumerable.Empty<T>(); } if (!componentsByType.ContainsKey(typeof(T))) { return Enumerable.Empty<T>(); }
return components.Where(c => c is T).Cast<T>(); return components.Where(c => c is T).Cast<T>();
} }
public float GetQualityModifier(Quality.StatType statType)
{
return GetComponent<Quality>()?.GetValue(statType) ?? 0.0f;
}
public void RemoveContained(Item contained) public void RemoveContained(Item contained)
{ {
ownInventory?.RemoveItem(contained); ownInventory?.RemoveItem(contained);
contained.Container = null; contained.Container = null;
} }
public void SetTransform(Vector2 simPosition, float rotation, bool findNewHull = true, bool setPrevTransform = true) 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(); foreach (Item item in ItemList) item.FindHull();
} }
public Hull FindHull() public Hull FindHull()
{ {
if (parentInventory != null && parentInventory.Owner != null) if (parentInventory != null && parentInventory.Owner != null)
@@ -1278,7 +1323,7 @@ namespace Barotrauma
} }
Submarine = parentInventory.Owner.Submarine; Submarine = parentInventory.Owner.Submarine;
if (body != null) body.Submarine = Submarine; if (body != null) { body.Submarine = Submarine; }
return CurrentHull; return CurrentHull;
} }
@@ -1305,7 +1350,7 @@ namespace Barotrauma
return rootContainer; return rootContainer;
} }
/// <summary> /// <summary>
/// Should this item or any of its containers be ignored by the AI? /// Should this item or any of its containers be ignored by the AI?
/// </summary> /// </summary>
@@ -1357,7 +1402,7 @@ namespace Barotrauma
(component as ItemContainer)?.SetContainedItemPositions(); (component as ItemContainer)?.SetContainedItemPositions();
} }
} }
public void AddTag(string tag) public void AddTag(string tag)
{ {
if (tags.Contains(tag)) { return; } if (tags.Contains(tag)) { return; }
@@ -1418,7 +1463,7 @@ namespace Barotrauma
ApplyStatusEffect(effect, type, deltaTime, character, limb, useTarget, isNetworkEvent, false, worldPosition); ApplyStatusEffect(effect, type, deltaTime, character, limb, useTarget, isNetworkEvent, false, worldPosition);
} }
} }
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>(); readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
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) 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 (condition == 0.0f && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { return; }
} }
if (effect.type != type) { return; } if (effect.type != type) { return; }
bool hasTargets = effect.TargetIdentifiers == null; bool hasTargets = effect.TargetIdentifiers == null;
targets.Clear(); targets.Clear();
if (effect.HasTargetType(StatusEffect.TargetType.Contained)) if (effect.HasTargetType(StatusEffect.TargetType.Contained))
{ {
foreach (Item containedItem in ContainedItems) foreach (Item containedItem in ContainedItems)
@@ -1444,6 +1489,11 @@ namespace Barotrauma
continue; continue;
} }
if (effect.TargetSlot > -1)
{
if (OwnInventory.FindIndex(containedItem) != effect.TargetSlot) { continue; }
}
hasTargets = true; hasTargets = true;
targets.Add(containedItem); targets.Add(containedItem);
} }
@@ -1501,10 +1551,10 @@ namespace Barotrauma
{ {
targets.Add(limb); targets.Add(limb);
} }
if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) targets.Add(Container); if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) { targets.Add(Container); }
effect.Apply(type, deltaTime, this, targets, worldPosition); effect.Apply(type, deltaTime, this, targets, worldPosition);
} }
@@ -1526,7 +1576,7 @@ namespace Barotrauma
private bool IsInWater() private bool IsInWater()
{ {
if (CurrentHull == null) { return true; } if (CurrentHull == null) { return true; }
float surfaceY = CurrentHull.Surface; float surfaceY = CurrentHull.Surface;
return CurrentHull.WaterVolume > 0.0f && Position.Y < surfaceY; return CurrentHull.WaterVolume > 0.0f && Position.Y < surfaceY;
} }
@@ -1562,9 +1612,10 @@ namespace Barotrauma
} }
} }
aiTarget?.Update(deltaTime); if (aiTarget != null)
{
GameMain.Lua.hook.Call("itemThink." + prefab.Identifier, new object[] { this, deltaTime }); aiTarget.Update(deltaTime);
}
if (!isActive) { return; } if (!isActive) { return; }
@@ -1580,7 +1631,7 @@ namespace Barotrauma
bool shouldBeActive = true; bool shouldBeActive = true;
foreach (var conditional in ic.IsActiveConditionals) foreach (var conditional in ic.IsActiveConditionals)
{ {
if (!ConditionalMatches(conditional)) if (!ConditionalMatches(conditional))
{ {
shouldBeActive = false; shouldBeActive = false;
break; break;
@@ -1678,14 +1729,22 @@ namespace Barotrauma
} }
} }
public void UpdateTransform() public void UpdateTransform()
{ {
if (body == null) { return; } if (body == null) { return; }
Submarine prevSub = Submarine; Submarine prevSub = Submarine;
FindHull(); var projectile = GetComponent<Projectile>();
if (projectile?.StickTarget?.UserData is Limb limb)
{
Submarine = body.Submarine = limb.character?.Submarine;
currentHull = limb.character?.CurrentHull;
}
else
{
FindHull();
}
if (Submarine == null && prevSub != null) if (Submarine == null && prevSub != null)
{ {
@@ -1713,7 +1772,7 @@ namespace Barotrauma
rect.X = (int)(displayPos.X - rect.Width / 2.0f); rect.X = (int)(displayPos.X - rect.Width / 2.0f);
rect.Y = (int)(displayPos.Y + rect.Height / 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) Math.Abs(body.LinearVelocity.Y) > NetConfig.MaxPhysicsBodyVelocity)
{ {
body.LinearVelocity = new Vector2( body.LinearVelocity = new Vector2(
@@ -1752,17 +1811,23 @@ namespace Barotrauma
Vector2 drag = body.LinearVelocity * volume; Vector2 drag = body.LinearVelocity * volume;
body.ApplyForce((uplift - drag) * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); body.ApplyForce((uplift - drag) * 10.0f);
//apply simple angular drag //apply simple angular drag
body.ApplyTorque(body.AngularVelocity * volume * -0.05f); body.ApplyTorque(body.AngularVelocity * volume * -0.05f);
} }
private bool OnCollision(Fixture f1, Fixture f2, Contact contact) private bool OnCollision(Fixture f1, Fixture f2, Contact contact)
{ {
if (transformDirty) { return false; } if (transformDirty) { return false; }
var projectile = GetComponent<Projectile>();
if (projectile?.IgnoredBodies != null)
{
if (projectile.IgnoredBodies.Contains(f2.Body)) { return false; }
}
contact.GetWorldManifold(out Vector2 normal, out _); contact.GetWorldManifold(out Vector2 normal, out _);
if (contact.FixtureA.Body == f1.Body) { normal = -normal; } if (contact.FixtureA.Body == f1.Body) { normal = -normal; }
float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal); float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal);
@@ -1791,7 +1856,7 @@ namespace Barotrauma
foreach (Item contained in ContainedItems) foreach (Item contained in ContainedItems)
{ {
if (contained.body != null) { contained.HandleCollision(impact); } 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 //call the base method even if the item can't flip, to handle repositioning when flipping the whole sub
base.FlipX(relativeToSub); base.FlipX(relativeToSub);
if (!Prefab.CanFlipX) if (!Prefab.CanFlipX)
{ {
flippedX = false; flippedX = false;
return; return;
} }
if (Prefab.AllowRotatingInEditor) if (Prefab.AllowRotatingInEditor)
@@ -1822,7 +1887,8 @@ namespace Barotrauma
foreach (ItemComponent component in components) foreach (ItemComponent component in components)
{ {
component.FlipX(relativeToSub); component.FlipX(relativeToSub);
} }
SetContainedItemPositions();
} }
public override void FlipY(bool relativeToSub) public override void FlipY(bool relativeToSub)
@@ -1847,6 +1913,7 @@ namespace Barotrauma
{ {
component.FlipY(relativeToSub); component.FlipY(relativeToSub);
} }
SetContainedItemPositions();
} }
/// <summary> /// <summary>
@@ -1875,7 +1942,7 @@ namespace Barotrauma
if (component != null && !connectedComponents.Contains(component)) if (component != null && !connectedComponents.Contains(component))
{ {
connectedComponents.Add(component); connectedComponents.Add(component);
} }
} }
} }
@@ -1906,28 +1973,28 @@ namespace Barotrauma
return connectedComponents; return connectedComponents;
} }
public static readonly Pair<string, string>[] connectionPairs = new Pair<string, string>[] public static readonly (string input, string output)[] connectionPairs = new (string input, string output)[]
{ {
new Pair<string, string>("power_in", "power_out"), ("power_in", "power_out"),
new Pair<string, string>("signal_in1", "signal_out1"), ("signal_in1", "signal_out1"),
new Pair<string, string>("signal_in2", "signal_out2"), ("signal_in2", "signal_out2"),
new Pair<string, string>("signal_in3", "signal_out3"), ("signal_in3", "signal_out3"),
new Pair<string, string>("signal_in4", "signal_out4"), ("signal_in4", "signal_out4"),
new Pair<string, string>("signal_in", "signal_out"), ("signal_in", "signal_out"),
new Pair<string, string>("signal_in1", "signal_out"), ("signal_in1", "signal_out"),
new Pair<string, string>("signal_in2", "signal_out") ("signal_in2", "signal_out")
}; };
private void GetConnectedComponentsRecursive<T>(Connection c, HashSet<Connection> alreadySearched, List<T> connectedComponents) where T : ItemComponent private void GetConnectedComponentsRecursive<T>(Connection c, HashSet<Connection> alreadySearched, List<T> connectedComponents) where T : ItemComponent
{ {
alreadySearched.Add(c); alreadySearched.Add(c);
var recipients = c.Recipients; var recipients = c.Recipients;
foreach (Connection recipient in recipients) foreach (Connection recipient in recipients)
{ {
if (alreadySearched.Contains(recipient)) { continue; } if (alreadySearched.Contains(recipient)) { continue; }
var component = recipient.Item.GetComponent<T>(); var component = recipient.Item.GetComponent<T>();
if (component != null && !connectedComponents.Contains(component)) if (component != null && !connectedComponents.Contains(component))
{ {
connectedComponents.Add(component); connectedComponents.Add(component);
@@ -1949,23 +2016,23 @@ namespace Barotrauma
} }
} }
recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents); recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents);
} }
foreach (Pair<string, string> 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 (pairedConnection != null)
{ {
if (alreadySearched.Contains(pairedConnection)) { continue; } if (alreadySearched.Contains(pairedConnection)) { continue; }
GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents); 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 (pairedConnection != null)
{ {
if (alreadySearched.Contains(pairedConnection)) { continue; } 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 //try finding the controller with the simpler non-recursive method first
var controllers = GetConnectedComponents<Controller>(); var controllers = GetConnectedComponents<Controller>();
if (controllers.None()) { controllers = GetConnectedComponents<Controller>(recursive: true); } bool needsTag = tags != null && tags.Length > 0;
return controllers.Count < 2 ? controllers.FirstOrDefault() : if (controllers.None() || (needsTag && controllers.None(c => c.Item.HasTag(tags))))
(controllers.FirstOrDefault(c => c.GetFocusTarget() == this) ?? controllers.FirstOrDefault()); {
controllers = GetConnectedComponents<Controller>(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; return controller != null;
} }
@@ -2058,8 +2134,6 @@ namespace Barotrauma
} while (CoroutineManager.DeltaTime <= 0.0f); } while (CoroutineManager.DeltaTime <= 0.0f);
delayedSignals.Remove((signal, connection)); delayedSignals.Remove((signal, connection));
signal.source = this;
connection.SendSignal(signal); connection.SendSignal(signal);
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
@@ -2089,11 +2163,6 @@ namespace Barotrauma
public bool TryInteract(Character picker, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceActionKey = false) 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; } if (CampaignInteractionType != CampaignMode.InteractionType.None) { return false; }
bool picked = false, selected = false; bool picked = false, selected = false;
@@ -2151,7 +2220,7 @@ namespace Barotrauma
} }
#endif #endif
if (!pickHit && !selectHit) { continue; } if (!pickHit && !selectHit) { continue; }
bool showUiMsg = false; bool showUiMsg = false;
#if CLIENT #if CLIENT
if (!ic.HasRequiredSkills(picker, out Skill tempRequiredSkill)) { hasRequiredSkills = false; skillMultiplier = ic.GetSkillMultiplier(); } if (!ic.HasRequiredSkills(picker, out Skill tempRequiredSkill)) { hasRequiredSkills = false; skillMultiplier = ic.GetSkillMultiplier(); }
@@ -2201,7 +2270,7 @@ namespace Barotrauma
if (Container != null) Container.RemoveContained(this); if (Container != null) Container.RemoveContained(this);
return true; return true;
} }
public float GetContainedItemConditionPercentage() public float GetContainedItemConditionPercentage()
@@ -2218,7 +2287,7 @@ namespace Barotrauma
if (maxCondition > 0.0f) if (maxCondition > 0.0f)
{ {
return condition / maxCondition; return condition / maxCondition;
} }
return -1; return -1;
} }
@@ -2232,11 +2301,6 @@ namespace Barotrauma
if (condition == 0.0f) { return; } 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; bool remove = false;
foreach (ItemComponent ic in components) foreach (ItemComponent ic in components)
@@ -2253,7 +2317,7 @@ namespace Barotrauma
#if CLIENT #if CLIENT
ic.PlaySound(ActionType.OnUse, character); ic.PlaySound(ActionType.OnUse, character);
#endif #endif
ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb); ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb);
if (ic.DeleteOnUse) { remove = true; } if (ic.DeleteOnUse) { remove = true; }
@@ -2270,12 +2334,6 @@ namespace Barotrauma
{ {
if (condition == 0.0f) { return; } 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; bool remove = false;
foreach (ItemComponent ic in components) foreach (ItemComponent ic in components)
@@ -2307,14 +2365,9 @@ namespace Barotrauma
public void ApplyTreatment(Character user, Character character, Limb targetLimb) 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 //can't apply treatment to dead characters
if (character.IsDead) return; if (character.IsDead) { return; }
if (!UseInHealthInterface) return; if (!UseInHealthInterface) { return; }
#if CLIENT #if CLIENT
if (GameMain.Client != null) if (GameMain.Client != null)
@@ -2324,10 +2377,12 @@ namespace Barotrauma
} }
#endif #endif
float applyOnSelfFraction = user?.GetStatValue(StatTypes.ApplyTreatmentsOnSelfFraction) ?? 0.0f;
bool remove = false; bool remove = false;
foreach (ItemComponent ic in components) 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); bool success = Rand.Range(0.0f, 0.5f) < ic.DegreeOfSuccess(user);
ActionType actionType = success ? ActionType.OnUse : ActionType.OnFailure; ActionType actionType = success ? ActionType.OnUse : ActionType.OnFailure;
@@ -2336,7 +2391,19 @@ namespace Barotrauma
ic.PlaySound(actionType, user); ic.PlaySound(actionType, user);
#endif #endif
ic.WasUsed = true; 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) 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); } if (remove) { Spawner?.AddToRemoveQueue(this); }
} }
public bool Combine(Item item, Character user) 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; } if (item == this) { return false; }
bool isCombined = false; bool isCombined = false;
foreach (ItemComponent ic in components) foreach (ItemComponent ic in components)
@@ -2373,11 +2444,6 @@ namespace Barotrauma
public void Drop(Character dropper, bool createNetworkEvent = true) 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 (createNetworkEvent)
{ {
if (parentInventory != null && !parentInventory.Owner.Removed && !Removed && if (parentInventory != null && !parentInventory.Owner.Removed && !Removed &&
@@ -2411,28 +2477,25 @@ namespace Barotrauma
} }
foreach (ItemComponent ic in components) { ic.Drop(dropper); } foreach (ItemComponent ic in components) { ic.Drop(dropper); }
if (Container != null) if (Container != null)
{ {
SetTransform(Container.SimPosition, 0.0f); SetTransform(Container.SimPosition, 0.0f);
Container.RemoveContained(this); Container.RemoveContained(this);
Container = null; Container = null;
} }
if (parentInventory != null) if (parentInventory != null)
{ {
parentInventory.RemoveItem(this); parentInventory.RemoveItem(this);
parentInventory = null; parentInventory = null;
} }
SetContainedItemPositions();
} }
public void Equip(Character character) public void Equip(Character character)
{ {
var should = new LuaResult(GameMain.Lua.hook.Call("itemEquip", new object[] { this, character}));
if (should.Bool())
return;
if (Removed) if (Removed)
{ {
DebugConsole.ThrowError($"Tried to equip a removed item ({Name}).\n{Environment.StackTrace.CleanupStackTrace()}"); DebugConsole.ThrowError($"Tried to equip a removed item ({Name}).\n{Environment.StackTrace.CleanupStackTrace()}");
@@ -2444,11 +2507,6 @@ namespace Barotrauma
public void Unequip(Character character) 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); } foreach (ItemComponent ic in components) { ic.Unequip(character); }
} }
@@ -2460,7 +2518,7 @@ namespace Barotrauma
foreach (var itemProperty in itemProperties) foreach (var itemProperty in itemProperties)
{ {
allProperties.Add(new Pair<object, SerializableProperty>(this, itemProperty)); allProperties.Add(new Pair<object, SerializableProperty>(this, itemProperty));
} }
foreach (ItemComponent ic in components) foreach (ItemComponent ic in components)
{ {
List<SerializableProperty> componentProperties = SerializableProperty.GetProperties<T>(ic); List<SerializableProperty> componentProperties = SerializableProperty.GetProperties<T>(ic);
@@ -2542,6 +2600,14 @@ namespace Barotrauma
{ {
msg.Write((int)value); 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 else
{ {
throw new NotImplementedException("Serializing item properties of the type \"" + value.GetType() + "\" not supported"); throw new NotImplementedException("Serializing item properties of the type \"" + value.GetType() + "\" not supported");
@@ -2590,8 +2656,8 @@ namespace Barotrauma
{ {
string val = msg.ReadString(); string val = msg.ReadString();
logValue = val; logValue = val;
if (allowEditing) if (allowEditing)
{ {
property.TrySetValue(parentObject, val); property.TrySetValue(parentObject, val);
} }
} }
@@ -2649,13 +2715,26 @@ namespace Barotrauma
logValue = XMLExtensions.RectToString(val); logValue = XMLExtensions.RectToString(val);
if (allowEditing) { property.TrySetValue(parentObject, 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)) else if (typeof(Enum).IsAssignableFrom(type))
{ {
int intVal = msg.ReadInt32(); int intVal = msg.ReadInt32();
try try
{ {
if (allowEditing) if (allowEditing)
{ {
property.TrySetValue(parentObject, Enum.ToObject(type, intVal)); property.TrySetValue(parentObject, Enum.ToObject(type, intVal));
logValue = property.GetValue(parentObject).ToString(); logValue = property.GetValue(parentObject).ToString();
} }
@@ -2684,10 +2763,9 @@ namespace Barotrauma
{ {
CoroutineManager.StopCoroutines(logPropertyChangeCoroutine); 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); }, delay: 1.0f);
} }
#endif #endif
@@ -2925,7 +3003,7 @@ namespace Barotrauma
{ {
component.OnItemLoaded(); component.OnItemLoaded();
} }
return item; return item;
} }
@@ -2975,7 +3053,7 @@ namespace Barotrauma
(int)(rect.X - subPosition.X) + "," + (int)(rect.X - subPosition.X) + "," +
(int)(rect.Y - subPosition.Y) + "," + (int)(rect.Y - subPosition.Y) + "," +
width + "," + height)); width + "," + height));
if (linkedTo != null && linkedTo.Count > 0) if (linkedTo != null && linkedTo.Count > 0)
{ {
bool isOutpost = Submarine != null && Submarine.Info.IsOutpost; bool isOutpost = Submarine != null && Submarine.Info.IsOutpost;
@@ -3025,7 +3103,7 @@ namespace Barotrauma
ic.OnMapLoaded(); ic.OnMapLoaded();
} }
} }
/// <summary> /// <summary>
/// Remove the item so that it doesn't appear to exist in the game world (stop sounds, remove bodies etc) /// 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 /// but don't reset anything that's required for cloning the item
@@ -3033,7 +3111,7 @@ namespace Barotrauma
public override void ShallowRemove() public override void ShallowRemove()
{ {
base.ShallowRemove(); base.ShallowRemove();
foreach (ItemComponent ic in components) foreach (ItemComponent ic in components)
{ {
ic.ShallowRemove(); ic.ShallowRemove();
@@ -3055,7 +3133,7 @@ namespace Barotrauma
return; return;
} }
DebugConsole.Log("Removing item " + Name + " (ID: " + ID + ")"); DebugConsole.Log("Removing item " + Name + " (ID: " + ID + ")");
base.Remove(); base.Remove();
foreach (Character character in Character.CharacterList) foreach (Character character in Character.CharacterList)
@@ -3074,6 +3152,8 @@ namespace Barotrauma
} }
} }
connections?.Clear();
if (parentInventory != null) if (parentInventory != null)
{ {
if (parentInventory is CharacterInventory characterInventory) if (parentInventory is CharacterInventory characterInventory)
@@ -3099,6 +3179,8 @@ namespace Barotrauma
body = null; body = null;
} }
CurrentHull = null;
if (StaticFixtures != null) if (StaticFixtures != null)
{ {
foreach (Fixture fixture in StaticFixtures) foreach (Fixture fixture in StaticFixtures)