Unstable 0.1500.8.0

This commit is contained in:
Markus Isberg
2021-10-16 10:32:38 +09:00
parent de917c5d74
commit fc2f7b76da
57 changed files with 608 additions and 302 deletions
@@ -688,8 +688,10 @@ namespace Barotrauma
new GUIFrame( new GUIFrame(
new RectTransform(Vector2.One * 0.7f, dropdownButton.RectTransform, Anchor.CenterLeft) new RectTransform(Vector2.One * 0.7f, dropdownButton.RectTransform, Anchor.CenterLeft)
{ RelativeOffset = new Vector2(0.05f, 0.0f) }, style: null); { RelativeOffset = new Vector2(0.05f, 0.0f) }, style: null);
Color? previewingColor = null;
dropdown.OnSelected = (component, color) => dropdown.OnSelected = (component, color) =>
{ {
previewingColor = null;
setter((Color)color); setter((Color)color);
buttonFrame.Color = getter(); buttonFrame.Color = getter();
buttonFrame.HoverColor = getter(); buttonFrame.HoverColor = getter();
@@ -727,25 +729,24 @@ namespace Barotrauma
dropdown.Select(dropdown.ListBox.Content.GetChildIndex(childToSelect)); dropdown.Select(dropdown.ListBox.Content.GetChildIndex(childToSelect));
//The following exists to track mouseover to preview colors before selecting them //The following exists to track mouseover to preview colors before selecting them
bool previewingColor = false;
new GUICustomComponent(new RectTransform(Vector2.One, buttonFrame.RectTransform), new GUICustomComponent(new RectTransform(Vector2.One, buttonFrame.RectTransform),
onUpdate: (deltaTime, component) => onUpdate: (deltaTime, component) =>
{ {
if (GUI.MouseOn is GUIFrame { Parent: { } p } hoveredFrame && dropdown.ListBox.Content.IsParentOf(hoveredFrame)) if (GUI.MouseOn is GUIFrame { Parent: { } p } hoveredFrame && dropdown.ListBox.Content.IsParentOf(hoveredFrame))
{ {
previewingColor = true; previewingColor ??= getter();
Color color = (Color)(dropdown.ListBox.Content.FindChild(c => Color color = (Color)(dropdown.ListBox.Content.FindChild(c =>
c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData); c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData ?? getter());
setter(color); setter(color);
buttonFrame.Color = getter(); buttonFrame.Color = getter();
buttonFrame.HoverColor = getter(); buttonFrame.HoverColor = getter();
} }
else if (previewingColor) else if (previewingColor.HasValue)
{ {
setter((Color)dropdown.SelectedData); setter(previewingColor.Value);
buttonFrame.Color = getter(); buttonFrame.Color = getter();
buttonFrame.HoverColor = getter(); buttonFrame.HoverColor = getter();
previewingColor = false; previewingColor = null;
} }
}, onDraw: null) }, onDraw: null)
{ {
@@ -299,7 +299,7 @@ namespace Barotrauma
break; break;
case ServerNetObject.ENTITY_EVENT: case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 12); int eventType = msg.ReadRangedInteger(0, 13);
switch (eventType) switch (eventType)
{ {
case 0: //NetEntityEvent.Type.InventoryState case 0: //NetEntityEvent.Type.InventoryState
@@ -476,6 +476,18 @@ namespace Barotrauma
int moneyAmount = msg.ReadInt32(); int moneyAmount = msg.ReadInt32();
SetMoney(moneyAmount); SetMoney(moneyAmount);
break; break;
case 13: //NetEntityEvent.Type.UpdatePermanentStats:
byte savedStatValueCount = msg.ReadByte();
StatTypes statType = (StatTypes)msg.ReadByte();
info?.ClearSavedStatValues(statType);
for (int i = 0; i < savedStatValueCount; i++)
{
string statIdentifier = msg.ReadString();
float statValue = msg.ReadSingle();
bool removeOnDeath = msg.ReadBoolean();
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
}
break;
} }
msg.ReadPadBits(); msg.ReadPadBits();
@@ -476,6 +476,7 @@ namespace Barotrauma
if (Screen.Selected == GameMain.SubEditorScreen) if (Screen.Selected == GameMain.SubEditorScreen)
{ {
NewMessage("WARNING: Switching directly from the submarine editor to the game view may cause bugs and crashes. Use with caution.", Color.Orange); NewMessage("WARNING: Switching directly from the submarine editor to the game view may cause bugs and crashes. Use with caution.", Color.Orange);
Entity.Spawner ??= new EntitySpawner();
} }
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
})); }));
@@ -488,6 +489,8 @@ namespace Barotrauma
Submarine.MainSub = Submarine.Load(subInfo, true); Submarine.MainSub = Submarine.Load(subInfo, true);
} }
GameMain.SubEditorScreen.Select(enableAutoSave: Screen.Selected != GameMain.GameScreen); GameMain.SubEditorScreen.Select(enableAutoSave: Screen.Selected != GameMain.GameScreen);
Entity.Spawner?.Remove();
Entity.Spawner = null;
}, isCheat: true)); }, isCheat: true));
commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) => commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) =>
@@ -139,7 +139,7 @@ namespace Barotrauma
GameSession.UpdateTalentNotificationIndicator(talentPointNotification); GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null) if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null)
{ {
int talentCount = selectedTalents.Count - controlled.Info.UnlockedTalents.Count; int talentCount = selectedTalents.Count - controlled.Info.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f) if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{ {
@@ -1254,7 +1254,7 @@ namespace Barotrauma
return; return;
} }
selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
@@ -1541,7 +1541,7 @@ namespace Barotrauma
string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString(); string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString();
int talentCount = selectedTalents.Count - controlledCharacter.Info.UnlockedTalents.Count; int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count();
if (talentCount > 0) if (talentCount > 0)
{ {
@@ -1612,7 +1612,7 @@ namespace Barotrauma
private bool ResetTalentSelection(GUIButton guiButton, object userData) private bool ResetTalentSelection(GUIButton guiButton, object userData)
{ {
Character controlledCharacter = Character.Controlled; Character controlledCharacter = Character.Controlled;
selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentButtons(); UpdateTalentButtons();
return true; return true;
} }
@@ -544,7 +544,7 @@ namespace Barotrauma
if (Character.Controlled.CurrentHull == null) { return; } if (Character.Controlled.CurrentHull == null) { return; }
if (HumanAIController.IsBallastFloraNoticeable(Character.Controlled, Character.Controlled.CurrentHull)) if (HumanAIController.IsBallastFloraNoticeable(Character.Controlled, Character.Controlled.CurrentHull))
{ {
if (DisplayHint("onballastflorainfected")) { return; } if (IsOnFriendlySub() && DisplayHint("onballastflorainfected")) { return; }
} }
foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps) foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps)
{ {
@@ -552,7 +552,7 @@ namespace Barotrauma
if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; } if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; }
if (!gap.IsRoomToRoom) if (!gap.IsRoomToRoom)
{ {
if (!(Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item)) { continue; } if (!IsWearingDivingSuit()) { continue; }
if (Character.Controlled.IsProtectedFromPressure()) { continue; } if (Character.Controlled.IsProtectedFromPressure()) { continue; }
if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; } if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; }
continue; continue;
@@ -561,10 +561,16 @@ namespace Barotrauma
{ {
if (me == Character.Controlled.CurrentHull) { continue; } if (me == Character.Controlled.CurrentHull) { continue; }
if (!(me is Hull adjacentHull)) { continue; } if (!(me is Hull adjacentHull)) { continue; }
if (!IsOnFriendlySub()) { continue; }
if (IsWearingDivingSuit()) { continue; }
if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { return; } if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { return; }
if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage")) { return; } if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage")) { return; }
} }
static bool IsWearingDivingSuit() => Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item;
} }
static bool IsOnFriendlySub() => Character.Controlled.Submarine is Submarine sub && (sub.TeamID == Character.Controlled.TeamID || sub.TeamID == CharacterTeamType.FriendlyNPC);
} }
private static void CheckReminders() private static void CheckReminders()
@@ -39,7 +39,7 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn) if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled)
{ {
Vector2 origin = Light.LightSprite.Origin; Vector2 origin = Light.LightSprite.Origin;
if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; } if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; }
@@ -529,21 +529,30 @@ namespace Barotrauma.Items.Components
}; };
}*/ }*/
string name = GetRecipeNameAndAmount(selectedItem); string itemName = GetRecipeNameAndAmount(selectedItem);
string name = itemName;
float quality = GetFabricatedItemQuality(selectedItem, user); float quality = GetFabricatedItemQuality(selectedItem, user);
if (quality > 0) if (quality > 0)
{ {
name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", name+'\n', fallBackTag: "itemname.quality3"); name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n', fallBackTag: "itemname.quality3");
} }
var nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), var nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
name, textAlignment: Alignment.CenterLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont, parseRichText: true) name, textAlignment: Alignment.TopLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont, parseRichText: true)
{ {
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W);
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, nameBlock.Padding.Z, nameBlock.Padding.W); if (nameBlock.TextScale < 0.7f)
{
nameBlock.SetRichText(TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName, fallBackTag: "itemname.quality3"));
nameBlock.AutoScaleHorizontal = false;
nameBlock.TextScale = 0.7f;
nameBlock.Wrap = true;
nameBlock.SetTextPos();
nameBlock.RectTransform.MinSize = new Point(0, (int)(nameBlock.TextSize.Y * nameBlock.TextScale));
}
if (!string.IsNullOrWhiteSpace(selectedItem.TargetItem.Description)) if (!string.IsNullOrWhiteSpace(selectedItem.TargetItem.Description))
{ {
@@ -555,6 +564,7 @@ namespace Barotrauma.Items.Components
while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height) while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height)
{ {
var lines = description.WrappedText.Split('\n'); var lines = description.WrappedText.Split('\n');
if (lines.Length <= 1) { break; }
var newString = string.Join('\n', lines.Take(lines.Length - 1)); var newString = string.Join('\n', lines.Take(lines.Length - 1));
description.Text = newString.Substring(0, newString.Length - 4) + "..."; description.Text = newString.Substring(0, newString.Length - 4) + "...";
description.CalculateHeightFromText(); description.CalculateHeightFromText();
@@ -1465,6 +1465,7 @@ namespace Barotrauma.Networking
bool respawnAllowed = inc.ReadBoolean(); bool respawnAllowed = inc.ReadBoolean();
serverSettings.AllowDisguises = inc.ReadBoolean(); serverSettings.AllowDisguises = inc.ReadBoolean();
serverSettings.AllowRewiring = inc.ReadBoolean(); serverSettings.AllowRewiring = inc.ReadBoolean();
serverSettings.AllowFriendlyFire = inc.ReadBoolean();
serverSettings.LockAllDefaultWires = inc.ReadBoolean(); serverSettings.LockAllDefaultWires = inc.ReadBoolean();
serverSettings.AllowRagdollButton = inc.ReadBoolean(); serverSettings.AllowRagdollButton = inc.ReadBoolean();
GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
@@ -884,7 +884,7 @@ namespace Barotrauma
// Switch the type ambience if nothing playing atm or the currently playing clip is not suitable anymore // Switch the type ambience if nothing playing atm or the currently playing clip is not suitable anymore
else if (targetMusic[typeAmbienceTrackIndex] == null || currentMusic[typeAmbienceTrackIndex] == null || !currentMusic[typeAmbienceTrackIndex].IsPlaying() || suitableTypeAmbiences.None(m => m.File == currentMusic[typeAmbienceTrackIndex].Filename)) else if (targetMusic[typeAmbienceTrackIndex] == null || currentMusic[typeAmbienceTrackIndex] == null || !currentMusic[typeAmbienceTrackIndex].IsPlaying() || suitableTypeAmbiences.None(m => m.File == currentMusic[typeAmbienceTrackIndex].Filename))
{ {
targetMusic[mainTrackIndex] = suitableMusic.GetRandom(); targetMusic[typeAmbienceTrackIndex] = suitableTypeAmbiences.GetRandom();
} }
//get the appropriate intensity layers for current situation //get the appropriate intensity layers for current situation
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.7.0</Version> <Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.7.0</Version> <Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.7.0</Version> <Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.7.0</Version> <Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.7.0</Version> <Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -32,6 +32,12 @@ namespace Barotrauma
} }
} }
partial void OnPermanentStatChanged(StatTypes statType)
{
if (Character == null || Character.Removed) { return; }
GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdatePermanentStats, statType });
}
public void ServerWrite(IWriteMessage msg) public void ServerWrite(IWriteMessage msg)
{ {
msg.Write(ID); msg.Write(ID);
@@ -66,8 +72,8 @@ namespace Barotrauma
msg.Write((byte)0); msg.Write((byte)0);
} }
// TODO: animations // TODO: animations
msg.Write((byte)savedStatValues.SelectMany(s => s.Value).Count()); msg.Write((byte)SavedStatValues.SelectMany(s => s.Value).Count());
foreach (var savedStatValuePair in savedStatValues) foreach (var savedStatValuePair in SavedStatValues)
{ {
foreach (var savedStatValue in savedStatValuePair.Value) foreach (var savedStatValue in savedStatValuePair.Value)
{ {
@@ -310,7 +310,7 @@ namespace Barotrauma
if (extraData != null) if (extraData != null)
{ {
const int min = 0, max = 12; const int min = 0, max = 13;
switch ((NetEntityEvent.Type)extraData[0]) switch ((NetEntityEvent.Type)extraData[0])
{ {
case NetEntityEvent.Type.InventoryState: case NetEntityEvent.Type.InventoryState:
@@ -438,6 +438,30 @@ namespace Barotrauma
msg.WriteRangedInteger(12, min, max); msg.WriteRangedInteger(12, min, max);
msg.Write(GameMain.GameSession.Campaign.Money); msg.Write(GameMain.GameSession.Campaign.Money);
break; break;
case NetEntityEvent.Type.UpdatePermanentStats:
msg.WriteRangedInteger(13, min, max);
if (Info == null || extraData.Length < 2 || !(extraData[1] is StatTypes statType))
{
msg.Write((byte)0);
msg.Write((byte)0);
}
else if (!Info.SavedStatValues.ContainsKey(statType))
{
msg.Write((byte)0);
msg.Write((byte)statType);
}
else
{
msg.Write((byte)Info.SavedStatValues[statType].Count);
msg.Write((byte)statType);
foreach (var savedStatValue in Info.SavedStatValues[statType])
{
msg.Write(savedStatValue.StatIdentifier);
msg.Write(savedStatValue.StatValue);
msg.Write(savedStatValue.RemoveOnDeath);
}
}
break;
default: default:
DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")");
break; break;
@@ -1677,10 +1677,25 @@ namespace Barotrauma
return; return;
} }
bool relativeStrength = false;
if (args.Length > 4)
{
bool.TryParse(args[4], out relativeStrength);
}
Character targetCharacter = (args.Length <= 2) ? client.Character : FindMatchingCharacter(args.Skip(2).ToArray()); Character targetCharacter = (args.Length <= 2) ? client.Character : FindMatchingCharacter(args.Skip(2).ToArray());
if (targetCharacter != null) if (targetCharacter != null)
{ {
targetCharacter.CharacterHealth.ApplyAffliction(targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength)); Limb targetLimb = targetCharacter.AnimController.MainLimb;
if (args.Length > 3)
{
targetLimb = targetCharacter.AnimController.Limbs.FirstOrDefault(l => l.type.ToString().Equals(args[3], StringComparison.OrdinalIgnoreCase));
}
if (relativeStrength)
{
afflictionStrength *= targetCharacter.MaxVitality / afflictionPrefab.MaxStrength;
}
targetCharacter.CharacterHealth.ApplyAffliction(targetLimb ?? targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength));
} }
} }
); );
@@ -2171,6 +2186,7 @@ namespace Barotrauma
if (client == null) if (client == null)
{ {
GameMain.Server.SendConsoleMessage("Client \"" + args[0] + "\" not found.", senderClient); GameMain.Server.SendConsoleMessage("Client \"" + args[0] + "\" not found.", senderClient);
return;
} }
var character = FindMatchingCharacter(args.Skip(1).ToArray(), false); var character = FindMatchingCharacter(args.Skip(1).ToArray(), false);
@@ -1,4 +1,6 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -11,11 +13,65 @@ namespace Barotrauma
get { return itemData != null; } get { return itemData != null; }
} }
partial void InitProjSpecific(Client client) public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false)
{ {
Name = client.Name;
ClientEndPoint = client.Connection.EndPointString; ClientEndPoint = client.Connection.EndPointString;
SteamID = client.SteamID; SteamID = client.SteamID;
CharacterInfo = client.CharacterInfo; CharacterInfo = client.CharacterInfo;
healthData = new XElement("health");
client.Character?.CharacterHealth?.Save(healthData);
if (giveRespawnPenaltyAffliction)
{
var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction();
healthData.Add(new XElement("Affliction",
new XAttribute("identifier", respawnPenaltyAffliction.Identifier),
new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture))));
}
if (client.Character?.Inventory != null)
{
itemData = new XElement("inventory");
Character.SaveInventory(client.Character.Inventory, itemData);
}
OrderData = new XElement("orders");
if (client.CharacterInfo != null)
{
CharacterInfo.SaveOrderData(client.CharacterInfo, OrderData);
}
}
public CharacterCampaignData(XElement element)
{
Name = element.GetAttributeString("name", "Unnamed");
ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", "");
string steamID = element.GetAttributeString("steamid", "");
if (!string.IsNullOrEmpty(steamID))
{
ulong.TryParse(steamID, out ulong parsedID);
SteamID = parsedID;
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "character":
case "characterinfo":
CharacterInfo = new CharacterInfo(subElement);
break;
case "inventory":
itemData = subElement;
break;
case "health":
healthData = subElement;
break;
case "orders":
OrderData = subElement;
break;
}
}
} }
public bool MatchesClient(Client client) public bool MatchesClient(Client client)
@@ -209,6 +209,11 @@ namespace Barotrauma
//refresh the character data of clients who are still in the server //refresh the character data of clients who are still in the server
foreach (Client c in GameMain.Server.ConnectedClients) foreach (Client c in GameMain.Server.ConnectedClients)
{ {
if (c.Character != null && c.Character.Info == null)
{
c.Character = null;
}
if (c.HasSpawned && c.CharacterInfo != null && c.CharacterInfo.CauseOfDeath != null && c.CharacterInfo.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) if (c.HasSpawned && c.CharacterInfo != null && c.CharacterInfo.CauseOfDeath != null && c.CharacterInfo.CauseOfDeath?.Type != CauseOfDeathType.Disconnected)
{ {
//the client has opted to spawn this round with Reaper's Tax //the client has opted to spawn this round with Reaper's Tax
@@ -228,7 +233,7 @@ namespace Barotrauma
} }
c.CharacterInfo = characterInfo; c.CharacterInfo = characterInfo;
characterData.RemoveAll(cd => cd.MatchesClient(c)); characterData.RemoveAll(cd => cd.MatchesClient(c));
characterData.Add(new CharacterCampaignData(c)); characterData.Add(new CharacterCampaignData(c));
} }
//refresh the character data of clients who aren't in the server anymore //refresh the character data of clients who aren't in the server anymore
@@ -2470,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);
@@ -92,7 +92,7 @@ namespace Barotrauma
case VoteType.Mode: case VoteType.Mode:
string modeIdentifier = inc.ReadString(); string modeIdentifier = inc.ReadString();
GameModePreset mode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier); GameModePreset mode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier);
if (!mode.Votable) { break; } if (mode == null || !mode.Votable) { break; }
sender.SetVote(voteType, mode); sender.SetVote(voteType, mode);
break; break;
case VoteType.EndRound: case VoteType.EndRound:
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.7.0</Version> <Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@@ -346,7 +347,7 @@ namespace Barotrauma
} }
#region Escape #region Escape
public abstract void Escape(float deltaTime); public abstract bool Escape(float deltaTime);
public Gap EscapeTarget { get; private set; } public Gap EscapeTarget { get; private set; }
@@ -425,17 +426,38 @@ namespace Barotrauma
} }
if (EscapeTarget != null) if (EscapeTarget != null)
{ {
SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10); Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
float sqrDist = Vector2.DistanceSquared(Character.SimPosition, EscapeTarget.SimPosition); float sqrDist = diff.LengthSquared();
if (sqrDist < 0.5f || Character.CurrentHull == null || HasValidPath(requireNonDirty: true, requireUnfinished: false) && pathSteering.CurrentPath.Finished) if (Character.CurrentHull == null || sqrDist < MathUtils.Pow2(50) || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished)
{ {
// Very close to the target, outside, or at the end of the path -> just steer towards it manually without using the path // Very close to the target, outside, or at the end of the path -> try to steer through the gap
SteeringManager.Reset(); SteeringManager.Reset();
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(EscapeTarget.WorldPosition - Character.WorldPosition)); pathSteering?.ResetPath();
if (sqrDist < 4) if (sqrDist < MathUtils.Pow2(50))
{ {
return true; // Very close -> just keep steering forward
var forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2);
SteeringManager.SteeringManual(deltaTime, forward);
} }
else if (Character.CurrentHull == null)
{
// Outside -> steer away from the target
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(-diff));
}
else
{
// Still inside -> steer towards the target
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(diff));
}
return sqrDist < MathUtils.Pow2(200);
}
else if (pathSteering != null)
{
pathSteering.SteeringSeek(EscapeTarget.SimPosition, weight: 1, minGapSize);
}
else
{
SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10);
} }
} }
else else
@@ -1903,7 +1903,7 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs(); Character.AnimController.ReleaseStuckLimbs();
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1); LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1);
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; } if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
if (Character.Params.CanInteract) if (Character.Params.CanInteract && attackResult.Damage > 10)
{ {
ReleaseDragTargets(); ReleaseDragTargets();
} }
@@ -3607,12 +3607,12 @@ namespace Barotrauma
public bool CanPassThroughHole(Structure wall, int sectionIndex) => CanPassThroughHole(wall, sectionIndex, requiredHoleCount); public bool CanPassThroughHole(Structure wall, int sectionIndex) => CanPassThroughHole(wall, sectionIndex, requiredHoleCount);
public override void Escape(float deltaTime) public override bool Escape(float deltaTime)
{ {
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)) if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
{ {
State = AIState.Idle; State = AIState.Idle;
return; return false;
} }
else if (SelectedTargetMemory is AITargetMemory targetMemory && SelectedAiTarget?.Entity is Character) else if (SelectedTargetMemory is AITargetMemory targetMemory && SelectedAiTarget?.Entity is Character)
{ {
@@ -3653,6 +3653,7 @@ namespace Barotrauma
} }
} }
} }
return isSteeringThroughGap;
void SteerAwayFromTheEnemy() void SteerAwayFromTheEnemy()
{ {
@@ -1364,10 +1364,7 @@ namespace Barotrauma
ObjectiveManager.WaitTimer = waitDuration; ObjectiveManager.WaitTimer = waitDuration;
} }
public override void Escape(float deltaTime) public override bool Escape(float deltaTime) => UpdateEscape(deltaTime, canAttackDoors: false);
{
UpdateEscape(deltaTime, canAttackDoors: false);
}
private void CheckCrouching(float deltaTime) private void CheckCrouching(float deltaTime)
{ {
@@ -269,7 +269,7 @@ namespace Barotrauma
{ {
var waypoint = CurrentPath.Nodes[i]; var waypoint = CurrentPath.Nodes[i];
float directDistance = Vector2.DistanceSquared(character.WorldPosition, waypoint.WorldPosition); float directDistance = Vector2.DistanceSquared(character.WorldPosition, waypoint.WorldPosition);
if (directDistance > (pathDistance * pathDistance) || Submarine.PickBody(host.SimPosition, waypoint.SimPosition, collisionCategory: Physics.CollisionLevel) != null) if (directDistance > (pathDistance * pathDistance) || Submarine.PickBody(host.SimPosition, waypoint.SimPosition, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
{ {
pathDistance -= CurrentPath.GetLength(startIndex: i - 1, endIndex: i); pathDistance -= CurrentPath.GetLength(startIndex: i - 1, endIndex: i);
continue; continue;
@@ -9,6 +9,7 @@ namespace Barotrauma
public override string Identifier { get; set; } = "return"; public override string Identifier { get; set; } = "return";
private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective; private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective;
private bool usingEscapeBehavior; private bool usingEscapeBehavior;
private bool isSteeringThroughGap;
public Submarine ReturnTarget { get; } public Submarine ReturnTarget { get; }
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier) public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
@@ -57,7 +58,7 @@ namespace Barotrauma
return; return;
} }
bool shouldUseEscapeBehavior = false; bool shouldUseEscapeBehavior = false;
if (character.CurrentHull != null) if (character.CurrentHull != null || isSteeringThroughGap)
{ {
if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget)) if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget))
{ {
@@ -67,8 +68,8 @@ namespace Barotrauma
{ {
HumanAIController.ResetEscape(); HumanAIController.ResetEscape();
} }
HumanAIController.Escape(deltaTime); isSteeringThroughGap = HumanAIController.Escape(deltaTime);
if (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable) if (!isSteeringThroughGap && (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable))
{ {
Abandon = true; Abandon = true;
} }
@@ -92,7 +93,10 @@ namespace Barotrauma
RemoveSubObjective(ref moveInCaveObjective); RemoveSubObjective(ref moveInCaveObjective);
RemoveSubObjective(ref moveOutsideObjective); RemoveSubObjective(ref moveOutsideObjective);
TryAddSubObjective(ref moveInsideObjective, TryAddSubObjective(ref moveInsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveInsideObjective), onCompleted: () => RemoveSubObjective(ref moveInsideObjective),
onAbandon: () => Abandon = true); onAbandon: () => Abandon = true);
} }
@@ -110,7 +114,7 @@ namespace Barotrauma
IsCompleted = true; IsCompleted = true;
} }
} }
else if (moveInCaveObjective == null && moveOutsideObjective == null) else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null)
{ {
if (HumanAIController.IsInsideCave) if (HumanAIController.IsInsideCave)
{ {
@@ -134,7 +138,8 @@ namespace Barotrauma
TryAddSubObjective(ref moveInCaveObjective, TryAddSubObjective(ref moveInCaveObjective,
constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager) constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager)
{ {
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint endNodeFilter = n => n.Waypoint == closestOutsideWaypoint,
AllowGoingOutside = true
}, },
onCompleted: () => RemoveSubObjective(ref moveInCaveObjective), onCompleted: () => RemoveSubObjective(ref moveInCaveObjective),
onAbandon: () => Abandon = true); onAbandon: () => Abandon = true);
@@ -170,7 +175,10 @@ namespace Barotrauma
RemoveSubObjective(ref moveInsideObjective); RemoveSubObjective(ref moveInsideObjective);
RemoveSubObjective(ref moveInCaveObjective); RemoveSubObjective(ref moveInCaveObjective);
TryAddSubObjective(ref moveOutsideObjective, TryAddSubObjective(ref moveOutsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveOutsideObjective), onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
onAbandon: () => Abandon = true); onAbandon: () => Abandon = true);
} }
@@ -221,6 +229,7 @@ namespace Barotrauma
moveInCaveObjective = null; moveInCaveObjective = null;
moveOutsideObjective = null; moveOutsideObjective = null;
usingEscapeBehavior = false; usingEscapeBehavior = false;
isSteeringThroughGap = false;
HumanAIController.ResetEscape(); HumanAIController.ResetEscape();
} }
@@ -236,6 +236,7 @@ namespace Barotrauma
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
if (body != null) if (body != null)
{ {
if (body.UserData is Submarine) { return false; }
if (body.UserData is Structure s && !s.IsPlatform) { return false; } if (body.UserData is Structure s && !s.IsPlatform) { return false; }
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; } if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; }
} }
@@ -684,18 +684,15 @@ namespace Barotrauma
if (rightHand != null && !rightHand.Disabled) if (rightHand != null && !rightHand.Disabled)
{ {
HandIK(rightHand, torso.SimPosition + posAddition + HandIK(rightHand,
new Vector2( torso.SimPosition + posAddition + new Vector2(-handPos.X, (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY),
-handPos.X, CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
(Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
} }
if (leftHand != null && !leftHand.Disabled) if (leftHand != null && !leftHand.Disabled)
{ {
HandIK(leftHand, torso.SimPosition + posAddition + HandIK(leftHand,
new Vector2( torso.SimPosition + posAddition + new Vector2(handPos.X, (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY),
handPos.X, CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
(Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
} }
} }
else else
@@ -705,9 +702,7 @@ namespace Barotrauma
Vector2 footPos = colliderPos; Vector2 footPos = colliderPos;
if (Crouching) if (Crouching)
{ {
footPos = new Vector2( footPos = new Vector2(Math.Sign(stepSize.X * i) * Dir * 0.35f, colliderPos.Y);
Math.Sign(stepSize.X * i) * Dir * 0.4f,
colliderPos.Y);
if (Math.Sign(footPos.X) != Math.Sign(Dir)) if (Math.Sign(footPos.X) != Math.Sign(Dir))
{ {
//lift the foot at the back up a bit //lift the foot at the back up a bit
@@ -728,9 +723,16 @@ namespace Barotrauma
{ {
foot.DebugRefPos = colliderPos; foot.DebugRefPos = colliderPos;
foot.DebugTargetPos = footPos; foot.DebugTargetPos = footPos;
MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength); float footMoveForce = CurrentGroundedParams.FootMoveStrength;
FootIK(foot, footPos, float legBendTorque = CurrentGroundedParams.LegBendTorque;
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians); if (Crouching)
{
// Keeps the pose
legBendTorque = 100;
footMoveForce *= 2;
}
MoveLimb(foot, footPos, footMoveForce);
FootIK(foot, footPos, legBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
} }
} }
@@ -760,6 +762,12 @@ namespace Barotrauma
forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f * CurrentGroundedParams.ArmMoveStrength); forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f * CurrentGroundedParams.ArmMoveStrength);
} }
} }
// Try to keep the wrist straight
LimbJoint wrist = GetJointBetweenLimbs(foreArmType, hand.type);
if (wrist != null)
{
hand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 100f * CurrentGroundedParams.HandMoveStrength);
}
} }
} }
} }
@@ -1008,6 +1016,12 @@ namespace Barotrauma
speedMultiplier = Math.Min(speedMultiplier, 0.1f); speedMultiplier = Math.Min(speedMultiplier, 0.1f);
} }
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
// Try to keep the wrist straight
LimbJoint wrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand);
if (wrist != null)
{
rightHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * rightHand.Mass * 100f * CurrentSwimParams.HandMoveStrength);
}
} }
if (leftHand != null && !leftHand.Disabled) if (leftHand != null && !leftHand.Disabled)
@@ -1021,6 +1035,12 @@ namespace Barotrauma
speedMultiplier = Math.Min(speedMultiplier, 0.1f); speedMultiplier = Math.Min(speedMultiplier, 0.1f);
} }
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
// Try to keep the wrist straight
LimbJoint wrist = GetJointBetweenLimbs(LimbType.LeftForearm, LimbType.LeftHand);
if (wrist != null)
{
leftHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * leftHand.Mass * 100f * CurrentSwimParams.HandMoveStrength);
}
} }
} }
@@ -665,6 +665,7 @@ namespace Barotrauma
public float MaxVitality => CharacterHealth.MaxVitality; public float MaxVitality => CharacterHealth.MaxVitality;
public float MaxHealth => MaxVitality; public float MaxHealth => MaxVitality;
public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle; public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
public bool IsLatched => AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached;
public float Bloodloss public float Bloodloss
{ {
@@ -2751,7 +2752,8 @@ namespace Barotrauma
} }
else if (this != Controlled) else if (this != Controlled)
{ {
IsRagdolled = IsKeyDown(InputType.Ragdoll); wasRagdolled = IsRagdolled;
IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll);
} }
//Keep us ragdolled if we were forced or we're too speedy to unragdoll //Keep us ragdolled if we were forced or we're too speedy to unragdoll
else if (allowRagdoll && (!IsRagdolled || !tooFastToUnragdoll)) else if (allowRagdoll && (!IsRagdolled || !tooFastToUnragdoll))
@@ -2765,13 +2767,18 @@ namespace Barotrauma
{ {
wasRagdolled = IsRagdolled; wasRagdolled = IsRagdolled;
IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.25f; } if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.5f; }
} }
} }
if (!wasRagdolled && IsRagdolled && selfRagdolled) if (!wasRagdolled && IsRagdolled)
{ {
CheckTalents(AbilityEffectType.OnSelfRagdoll); if (selfRagdolled)
{
CheckTalents(AbilityEffectType.OnSelfRagdoll);
}
// currently does not work when you are stunned, like it should
CheckTalents(AbilityEffectType.OnRagdoll);
} }
lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f); lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
@@ -3535,11 +3542,6 @@ namespace Barotrauma
if (Removed) { return new AttackResult(); } if (Removed) { return new AttackResult(); }
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
{
if (attacker.TeamID == TeamID) { return new AttackResult(); }
}
float closestDistance = 0.0f; float closestDistance = 0.0f;
foreach (Limb limb in AnimController.Limbs) foreach (Limb limb in AnimController.Limbs)
{ {
@@ -3602,7 +3604,11 @@ namespace Barotrauma
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
{ {
if (attacker.TeamID == TeamID) { return new AttackResult(); } if (attacker.TeamID == TeamID)
{
afflictions = afflictions.Where(a => !a.Prefab.IsBuff);
if (!afflictions.Any()) { return new AttackResult(); }
}
} }
#if CLIENT #if CLIENT
@@ -4385,14 +4391,14 @@ namespace Barotrauma
info.UnlockedTalents.Add(talentPrefab.Identifier); info.UnlockedTalents.Add(talentPrefab.Identifier);
if (characterTalents.Any(t => t.Prefab == talentPrefab)) { return false; } if (characterTalents.Any(t => t.Prefab == talentPrefab)) { return false; }
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents });
#endif
CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this); CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this);
characterTalent.ActivateTalent(addingFirstTime); characterTalent.ActivateTalent(addingFirstTime);
characterTalents.Add(characterTalent); characterTalents.Add(characterTalent);
characterTalent.AddedThisRound = addingFirstTime; characterTalent.AddedThisRound = addingFirstTime;
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents });
#endif
if (addingFirstTime) if (addingFirstTime)
{ {
OnTalentGiven(talentPrefab.Identifier); OnTalentGiven(talentPrefab.Identifier);
@@ -216,6 +216,17 @@ namespace Barotrauma
public HashSet<string> UnlockedTalents { get; private set; } = new HashSet<string>(); public HashSet<string> UnlockedTalents { get; private set; } = new HashSet<string>();
/// <summary>
/// Endocrine boosters can unlock talents outside the user's talent tree. This method is used to cull them from the selection
/// </summary>
public IEnumerable<string> GetUnlockedTalentsInTree()
{
if (!TalentTree.JobTalentTrees.TryGetValue(Job.Prefab.Identifier, out TalentTree talentTree)) { return Enumerable.Empty<string>(); }
return UnlockedTalents.Where(t => talentTree.TalentIsInTree(t));
}
public int AdditionalTalentPoints { get; set; } public int AdditionalTalentPoints { get; set; }
private Sprite _headSprite; private Sprite _headSprite;
@@ -1220,7 +1231,7 @@ namespace Barotrauma
var experienceGainMultiplier = new AbilityValue(1f); var experienceGainMultiplier = new AbilityValue(1f);
if (isMissionExperience) if (isMissionExperience)
{ {
Character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier); Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
} }
experienceGainMultiplier.Value += Character.GetStatValue(StatTypes.ExperienceGainMultiplier); experienceGainMultiplier.Value += Character.GetStatValue(StatTypes.ExperienceGainMultiplier);
@@ -1252,7 +1263,7 @@ namespace Barotrauma
public int GetAvailableTalentPoints() public int GetAvailableTalentPoints()
{ {
// hashset always has at least 1 // hashset always has at least 1
return Math.Max(GetTotalTalentPoints() - UnlockedTalents.Count, 0); return Math.Max(GetTotalTalentPoints() - GetUnlockedTalentsInTree().Count(), 0);
} }
public float GetProgressTowardsNextLevel() public float GetProgressTowardsNextLevel()
@@ -1297,6 +1308,8 @@ namespace Barotrauma
partial void OnExperienceChanged(int prevAmount, int newAmount); partial void OnExperienceChanged(int prevAmount, int newAmount);
partial void OnPermanentStatChanged(StatTypes statType);
public void Rename(string newName) public void Rename(string newName)
{ {
if (string.IsNullOrEmpty(newName)) { return; } if (string.IsNullOrEmpty(newName)) { return; }
@@ -1362,7 +1375,7 @@ namespace Barotrauma
Job.Save(charElement); Job.Save(charElement);
XElement savedStatElement = new XElement("savedstatvalues"); XElement savedStatElement = new XElement("savedstatvalues");
foreach (var statValuePair in savedStatValues) foreach (var statValuePair in SavedStatValues)
{ {
foreach (var savedStat in statValuePair.Value) foreach (var savedStat in statValuePair.Value)
{ {
@@ -1708,26 +1721,42 @@ namespace Barotrauma
} }
// This could maybe be a LookUp instead? // This could maybe be a LookUp instead?
private readonly Dictionary<StatTypes, List<SavedStatValue>> savedStatValues = new Dictionary<StatTypes, List<SavedStatValue>>(); public readonly Dictionary<StatTypes, List<SavedStatValue>> SavedStatValues = new Dictionary<StatTypes, List<SavedStatValue>>();
public void ResetSavedStatValues() public void ClearSavedStatValues()
{ {
foreach (var savedStatValue in savedStatValues.SelectMany(s => s.Value)) foreach (StatTypes statType in SavedStatValues.Keys)
{ {
if (savedStatValue.RemoveOnDeath) OnPermanentStatChanged(statType);
{
savedStatValue.StatValue = 0f;
}
} }
SavedStatValues.Clear();
} }
public void ClearSavedStatValues(StatTypes statType)
{
SavedStatValues.Remove(statType);
OnPermanentStatChanged(statType);
}
public void ResetSavedStatValue(string statIdentifier) public void ResetSavedStatValue(string statIdentifier)
{ {
savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f); foreach (StatTypes statType in SavedStatValues.Keys)
{
bool changed = false;
foreach (SavedStatValue savedStatValue in SavedStatValues[statType])
{
if (savedStatValue.StatIdentifier != statIdentifier) { continue; }
if (MathUtils.NearlyEqual(savedStatValue.StatValue, 0.0f)) { continue; }
savedStatValue.StatValue = 0.0f;
changed = true;
}
if (changed) { OnPermanentStatChanged(statType); }
}
} }
public float GetSavedStatValue(StatTypes statType) public float GetSavedStatValue(StatTypes statType)
{ {
if (savedStatValues.TryGetValue(statType, out var statValues)) if (SavedStatValues.TryGetValue(statType, out var statValues))
{ {
return statValues.Sum(v => v.StatValue); return statValues.Sum(v => v.StatValue);
} }
@@ -1738,7 +1767,7 @@ namespace Barotrauma
} }
public float GetSavedStatValue(StatTypes statType, string statIdentifier) public float GetSavedStatValue(StatTypes statType, string statIdentifier)
{ {
if (savedStatValues.TryGetValue(statType, out var statValues)) if (SavedStatValues.TryGetValue(statType, out var statValues))
{ {
return statValues.Where(s => s.StatIdentifier.Equals(statIdentifier, StringComparison.OrdinalIgnoreCase)).Sum(v => v.StatValue); return statValues.Where(s => s.StatIdentifier.Equals(statIdentifier, StringComparison.OrdinalIgnoreCase)).Sum(v => v.StatValue);
} }
@@ -1750,19 +1779,24 @@ namespace Barotrauma
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue, bool setValue = false) public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue, bool setValue = false)
{ {
if (!savedStatValues.ContainsKey(statType)) if (!SavedStatValues.ContainsKey(statType))
{ {
savedStatValues.Add(statType, new List<SavedStatValue>()); SavedStatValues.Add(statType, new List<SavedStatValue>());
} }
if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) bool changed = false;
if (SavedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat)
{ {
float prevValue = savedStat.StatValue;
savedStat.StatValue = setValue ? value : MathHelper.Min(savedStat.StatValue + value, maxValue); savedStat.StatValue = setValue ? value : MathHelper.Min(savedStat.StatValue + value, maxValue);
changed = !MathUtils.NearlyEqual(savedStat.StatValue, prevValue);
} }
else else
{ {
savedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound)); SavedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound));
changed = true;
} }
if (changed) { OnPermanentStatChanged(statType); }
} }
} }
@@ -11,6 +11,7 @@ namespace Barotrauma.Abilities
protected readonly List<StatusEffect> statusEffects; protected readonly List<StatusEffect> statusEffects;
private readonly bool nearbyCharactersAppliesToSelf; private readonly bool nearbyCharactersAppliesToSelf;
private readonly bool nearbyCharactersAppliesToAllies;
private readonly bool applyToSelected; private readonly bool applyToSelected;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>(); readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
@@ -20,6 +21,7 @@ namespace Barotrauma.Abilities
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false); applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true); nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true);
nearbyCharactersAppliesToAllies = abilityElement.GetAttributeBool("nearbycharactersappliestoallies", true);
} }
protected void ApplyEffectSpecific(Character targetCharacter) protected void ApplyEffectSpecific(Character targetCharacter)
@@ -40,6 +42,10 @@ namespace Barotrauma.Abilities
{ {
targets.RemoveAll(c => c == Character); targets.RemoveAll(c => c == Character);
} }
if (!nearbyCharactersAppliesToAllies)
{
targets.RemoveAll(c => c is Character otherCharacter && HumanAIController.IsFriendly(otherCharacter, Character));
}
statusEffect.SetUser(Character); statusEffect.SetUser(Character);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
} }
@@ -56,17 +62,20 @@ namespace Barotrauma.Abilities
} }
} }
protected override void ApplyEffect() protected override void ApplyEffect()
{
ApplyEffectSpecific(Character);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{ {
if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter) if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter)
{ {
ApplyEffectSpecific(selectedCharacter); ApplyEffectSpecific(selectedCharacter);
} }
else if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) else
{
ApplyEffectSpecific(Character);
}
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter)
{ {
ApplyEffectSpecific(targetCharacter); ApplyEffectSpecific(targetCharacter);
} }
@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
private readonly bool setValue; private readonly bool setValue;
//private readonly float maximumValue; //private readonly float maximumValue;
public override bool AllowClientSimulation => true;
public override bool AppliesEffectOnIntervalUpdate => true; public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
@@ -6,6 +6,7 @@ namespace Barotrauma.Abilities
{ {
private readonly string statIdentifier; private readonly string statIdentifier;
public override bool AppliesEffectOnIntervalUpdate => true; public override bool AppliesEffectOnIntervalUpdate => true;
public override bool AllowClientSimulation => true;
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
@@ -31,7 +31,7 @@ namespace Barotrauma.Abilities
} }
} }
if (closestCharacter.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; } if (closestCharacter.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; }
if (closestDistance < squaredMaxDistance) if (closestDistance < squaredMaxDistance)
{ {
@@ -66,6 +66,11 @@ namespace Barotrauma
} }
} }
public bool TalentIsInTree(string talentIdentifier)
{
return TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))).Any(c => c == talentIdentifier);
}
public static void LoadFromFile(ContentFile file) public static void LoadFromFile(ContentFile file)
{ {
DebugConsole.Log("Loading talent tree: " + file.Path); DebugConsole.Log("Loading talent tree: " + file.Path);
@@ -602,7 +602,7 @@ namespace Barotrauma
} }
})); }));
commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) => commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type] [use relative strength]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) =>
{ {
if (args.Length < 2) { return; } if (args.Length < 2) { return; }
@@ -622,14 +622,12 @@ namespace Barotrauma
} }
bool relativeStrength = false; bool relativeStrength = false;
if (args.Length > 2) if (args.Length > 4)
{ {
bool.TryParse(args[2], out relativeStrength); bool.TryParse(args[4], out relativeStrength);
} }
Character targetCharacter = (relativeStrength || args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] }); Character targetCharacter = args.Length <= 2 ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] });
if (targetCharacter != null) if (targetCharacter != null)
{ {
Limb targetLimb = targetCharacter.AnimController.MainLimb; Limb targetLimb = targetCharacter.AnimController.MainLimb;
@@ -1889,9 +1887,9 @@ namespace Barotrauma
if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client))
{ {
#if DEBUG #if DEBUG
AddWarning("You're not permitted to use the command \"{matchingCommand.Name}\". Executing the command anyway because this is a debug build."); AddWarning($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\". Executing the command anyway because this is a debug build.");
#else #else
ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); ThrowError($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\"!");
return; return;
#endif #endif
} }
@@ -47,6 +47,7 @@
OnReduceAffliction, OnReduceAffliction,
OnAddDamageAffliction, OnAddDamageAffliction,
OnSelfRagdoll, OnSelfRagdoll,
OnRagdoll,
OnRoundEnd, OnRoundEnd,
OnAnyMissionCompleted, OnAnyMissionCompleted,
OnAllMissionsCompleted, OnAllMissionsCompleted,
@@ -179,7 +179,7 @@ namespace Barotrauma
if (eventSet == null) { return; } if (eventSet == null) { return; }
if (eventSet.OncePerOutpost) if (eventSet.OncePerOutpost)
{ {
foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.prefab)) foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.Prefabs))
{ {
if (!level.LevelData.NonRepeatableEvents.Contains(ep)) if (!level.LevelData.NonRepeatableEvents.Contains(ep))
{ {
@@ -434,23 +434,31 @@ namespace Barotrauma
} }
} }
var suitablePrefabs = eventSet.EventPrefabs.FindAll(e => bool isPrefabSuitable(EventPrefab p)
string.IsNullOrEmpty(e.prefab.BiomeIdentifier) || => string.IsNullOrEmpty(p.BiomeIdentifier) ||
e.prefab.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase)); p.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase);
var suitablePrefabSubsets = eventSet.EventPrefabs
.FindAll(p => p.Prefabs.Any(isPrefabSuitable));
for (int i = 0; i < applyCount; i++) for (int i = 0; i < applyCount; i++)
{ {
if (eventSet.ChooseRandom) if (eventSet.ChooseRandom)
{ {
if (suitablePrefabs.Count > 0) if (suitablePrefabSubsets.Count > 0)
{ {
var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(suitablePrefabs); var unusedEvents = suitablePrefabSubsets.ToList();
for (int j = 0; j < eventSet.EventCount; j++) for (int j = 0; j < eventSet.EventCount; j++)
{ {
if (unusedEvents.All(e => CalculateCommonness(e.prefab, e.commonness) <= 0.0f)) { break; } if (unusedEvents.All(e => e.Prefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) { break; }
(EventPrefab eventPrefab, float commonness, float probability) = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e.prefab, e.commonness)).ToList(), rand); EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Prefabs.Max(p => CalculateCommonness(p, e.Commonness))).ToList(), rand);
if (eventPrefab != null && rand.NextDouble() <= probability) (IEnumerable<EventPrefab> eventPrefabs, float commonness, float probability) = subEventPrefab;
if (eventPrefabs != null && rand.NextDouble() <= probability)
{ {
var finalPrefabs = eventPrefabs.Where(isPrefabSuitable).ToArray();
var finalPrefabCommonnesses = finalPrefabs.Select(p => p.Commonness).ToArray();
var eventPrefab = ToolBox.SelectWeightedRandom(finalPrefabs, finalPrefabCommonnesses, rand);
var newEvent = eventPrefab.CreateInstance(); var newEvent = eventPrefab.CreateInstance();
if (newEvent == null) { continue; } if (newEvent == null) { continue; }
newEvent.Init(true); newEvent.Init(true);
@@ -465,7 +473,7 @@ namespace Barotrauma
selectedEvents.Add(eventSet, new List<Event>()); selectedEvents.Add(eventSet, new List<Event>());
} }
selectedEvents[eventSet].Add(newEvent); selectedEvents[eventSet].Add(newEvent);
unusedEvents.Remove((eventPrefab, commonness, probability)); unusedEvents.Remove(subEventPrefab);
} }
} }
} }
@@ -480,9 +488,13 @@ namespace Barotrauma
} }
else else
{ {
foreach ((EventPrefab eventPrefab, float commonness, float probability) in suitablePrefabs) foreach ((IEnumerable<EventPrefab> eventPrefabs, float commonness, float probability) in suitablePrefabSubsets)
{ {
if (rand.NextDouble() > probability) { continue; } if (rand.NextDouble() > probability) { continue; }
var finalPrefabs = eventPrefabs.Where(isPrefabSuitable).ToArray();
var finalPrefabCommonnesses = finalPrefabs.Select(p => p.Commonness).ToArray();
var eventPrefab = ToolBox.SelectWeightedRandom(finalPrefabs, finalPrefabCommonnesses, rand);
var newEvent = eventPrefab.CreateInstance(); var newEvent = eventPrefab.CreateInstance();
if (newEvent == null) { continue; } if (newEvent == null) { continue; }
newEvent.Init(true); newEvent.Init(true);
@@ -866,7 +878,7 @@ namespace Barotrauma
private float CalculateDistanceTraveled() private float CalculateDistanceTraveled()
{ {
if (level == null) { return 0.0f; } if (level == null || pathFinder == null) { return 0.0f; }
var refEntity = GetRefEntity(); var refEntity = GetRefEntity();
if (refEntity == null) { return 0.0f; } if (refEntity == null) { return 0.0f; }
Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition); Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition);
@@ -40,7 +40,7 @@ namespace Barotrauma
BiomeIdentifier = ConfigElement.GetAttributeString("biome", string.Empty); BiomeIdentifier = ConfigElement.GetAttributeString("biome", string.Empty);
Commonness = element.GetAttributeFloat("commonness", 1.0f); Commonness = element.GetAttributeFloat("commonness", 1.0f);
Probability = Math.Clamp(element.GetAttributeFloat(1.0f, "probability", "spawnprobability"), 0, 1); Probability = Math.Clamp(element.GetAttributeFloat(1.0f, "probability", "spawnprobability"), 0, 1);
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true); TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", EventType != typeof(ScriptedEvent));
UnlockPathEvent = element.GetAttributeBool("unlockpathevent", false); UnlockPathEvent = element.GetAttributeBool("unlockpathevent", false);
UnlockPathTooltip = element.GetAttributeString("unlockpathtooltip", "lockedpathtooltip"); UnlockPathTooltip = element.GetAttributeString("unlockpathtooltip", "lockedpathtooltip");
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
@@ -48,10 +49,10 @@ namespace Barotrauma
List<EventPrefab> eventPrefabs = new List<EventPrefab>(PrefabList); List<EventPrefab> eventPrefabs = new List<EventPrefab>(PrefabList);
foreach (var eventSet in List) foreach (var eventSet in List)
{ {
eventPrefabs.AddRange(eventSet.EventPrefabs.Select(ep => ep.prefab)); eventPrefabs.AddRange(eventSet.EventPrefabs.SelectMany(ep => ep.Prefabs));
foreach (var childSet in eventSet.ChildSets) foreach (var childSet in eventSet.ChildSets)
{ {
eventPrefabs.AddRange(childSet.EventPrefabs.Select(ep => ep.prefab)); eventPrefabs.AddRange(childSet.EventPrefabs.SelectMany(ep => ep.Prefabs));
} }
} }
return eventPrefabs; return eventPrefabs;
@@ -98,7 +99,48 @@ namespace Barotrauma
public readonly Dictionary<string, float> Commonness; public readonly Dictionary<string, float> Commonness;
public readonly List<(EventPrefab prefab, float commonness, float probability)> EventPrefabs; public struct SubEventPrefab
{
public SubEventPrefab(string debugIdentifier, string[] prefabIdentifiers, float? commonness, float? probability)
{
EventPrefab tryFindPrefab(string id)
{
var prefab = PrefabList.Find(p => p.Identifier.Equals(id, StringComparison.OrdinalIgnoreCase));
if (prefab is null)
{
DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{id}\".");
}
return prefab;
}
this.Prefabs = prefabIdentifiers
.Select(tryFindPrefab)
.Where(p => p != null)
.ToImmutableArray();
this.Commonness = commonness ?? this.Prefabs.Select(p => p.Commonness).Max();
this.Probability = probability ?? this.Prefabs.Select(p => p.Probability).Max();
}
public SubEventPrefab(EventPrefab prefab, float commonness, float probability)
{
Prefabs = prefab.ToEnumerable().ToImmutableArray();
Commonness = commonness;
Probability = probability;
}
public readonly ImmutableArray<EventPrefab> Prefabs;
public readonly float Commonness;
public readonly float Probability;
public void Deconstruct(out IEnumerable<EventPrefab> prefabs, out float commonness, out float probability)
{
prefabs = Prefabs;
commonness = Commonness;
probability = Probability;
}
}
public readonly List<SubEventPrefab> EventPrefabs;
public readonly List<EventSet> ChildSets; public readonly List<EventSet> ChildSets;
@@ -112,7 +154,7 @@ namespace Barotrauma
{ {
DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier; DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier;
Commonness = new Dictionary<string, float>(); Commonness = new Dictionary<string, float>();
EventPrefabs = new List<(EventPrefab prefab, float commonness, float probability)>(); EventPrefabs = new List<SubEventPrefab>();
ChildSets = new List<EventSet>(); ChildSets = new List<EventSet>();
BiomeIdentifier = element.GetAttributeString("biome", string.Empty); BiomeIdentifier = element.GetAttributeString("biome", string.Empty);
@@ -178,23 +220,20 @@ namespace Barotrauma
//an element with just an identifier = reference to an event prefab //an element with just an identifier = reference to an event prefab
if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase)) if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase))
{ {
string identifier = subElement.GetAttributeString("identifier", ""); string[] identifiers = subElement.GetAttributeStringArray("identifier", Array.Empty<string>());
var prefab = PrefabList.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
if (prefab == null) float commonness = subElement.GetAttributeFloat("commonness", -1f);
{ float probability = subElement.GetAttributeFloat("probability", -1f);
DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{identifier}\"."); EventPrefabs.Add(new SubEventPrefab(
} debugIdentifier,
else identifiers,
{ commonness>=0f ? commonness : (float?)null,
float commonness = subElement.GetAttributeFloat("commonness", prefab.Commonness); probability>=0f ? probability : (float?)null));
float probability = subElement.GetAttributeFloat("probability", prefab.Probability);
EventPrefabs.Add((prefab, commonness, probability));
}
} }
else else
{ {
var prefab = new EventPrefab(subElement); var prefab = new EventPrefab(subElement);
EventPrefabs.Add((prefab, prefab.Commonness, prefab.Probability)); EventPrefabs.Add(new SubEventPrefab(prefab, prefab.Commonness, prefab.Probability));
} }
break; break;
} }
@@ -346,13 +385,13 @@ namespace Barotrauma
{ {
if (thisSet.ChooseRandom) if (thisSet.ChooseRandom)
{ {
var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(thisSet.EventPrefabs); var unusedEvents = thisSet.EventPrefabs.ToList();
for (int i = 0; i < thisSet.EventCount; i++) for (int i = 0; i < thisSet.EventCount; i++)
{ {
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.commonness).ToList(), Rand.RandSync.Unsynced); var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced);
if (eventPrefab.prefab != null) if (eventPrefab.Prefabs.Any(p => p != null))
{ {
AddEvent(stats, eventPrefab.prefab); AddEvents(stats, eventPrefab.Prefabs);
unusedEvents.Remove(eventPrefab); unusedEvents.Remove(eventPrefab);
} }
} }
@@ -361,7 +400,7 @@ namespace Barotrauma
{ {
foreach (var eventPrefab in thisSet.EventPrefabs) foreach (var eventPrefab in thisSet.EventPrefabs)
{ {
AddEvent(stats, eventPrefab.prefab); AddEvents(stats, eventPrefab.Prefabs);
} }
} }
foreach (var childSet in thisSet.ChildSets) foreach (var childSet in thisSet.ChildSets)
@@ -370,6 +409,9 @@ namespace Barotrauma
} }
} }
static void AddEvents(EventDebugStats stats, IEnumerable<EventPrefab> eventPrefabs)
=> eventPrefabs.ForEach(p => AddEvent(stats, p));
static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab) static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab)
{ {
if (eventPrefab.EventType == typeof(MonsterEvent)) if (eventPrefab.EventType == typeof(MonsterEvent))
@@ -363,7 +363,7 @@ namespace Barotrauma
#if CLIENT #if CLIENT
foreach (Character character in crewCharacters) foreach (Character character in crewCharacters)
{ {
character.Info.GiveExperience(experienceGain, isMissionExperience: true); character.Info?.GiveExperience(experienceGain, isMissionExperience: true);
} }
#else #else
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients) foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)
@@ -218,12 +218,12 @@ namespace Barotrauma
return validContainers; return validContainers;
} }
private static readonly float[] qualityCommonnesses = new float[Quality.MaxQuality + 1] private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1]
{ {
0.85f, (0, 0.85f),
0.125f, (1, 0.125f),
0.0225f, (2, 0.0225f),
0.0025f, (3, 0.0025f),
}; };
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier) private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier)
@@ -243,20 +243,15 @@ namespace Barotrauma
containers.Remove(validContainer.Key); containers.Remove(validContainer.Key);
break; break;
} }
if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; }
int quality = 0;
float qualityCommmonnessSum = qualityCommonnesses.Sum();
float randomNumber = Rand.Range(0f, qualityCommmonnessSum, Rand.RandSync.Server);
for (int k = qualityCommonnesses.Length - 1; k >= 0; k--)
{
if (randomNumber < qualityCommonnesses[k])
{
quality = k;
break;
}
}
var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.prefab == itemPrefab);
int quality =
existingItem?.Quality ??
ToolBox.SelectWeightedRandom(
qualityCommonnesses.Select(q => q.quality).ToList(),
qualityCommonnesses.Select(q => q.commonness).ToList(),
Rand.RandSync.Server);
if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; }
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine) var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine)
{ {
SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost, SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost,
@@ -1,6 +1,4 @@
using Barotrauma.Networking; using System.Xml.Linq;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -29,65 +27,6 @@ namespace Barotrauma
private XElement healthData; private XElement healthData;
public XElement OrderData { get; private set; } public XElement OrderData { get; private set; }
partial void InitProjSpecific(Client client);
public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false)
{
Name = client.Name;
InitProjSpecific(client);
healthData = new XElement("health");
client.Character?.CharacterHealth?.Save(healthData);
if (giveRespawnPenaltyAffliction)
{
var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction();
healthData.Add(new XElement("Affliction",
new XAttribute("identifier", respawnPenaltyAffliction.Identifier),
new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture))));
}
if (client.Character?.Inventory != null)
{
itemData = new XElement("inventory");
Character.SaveInventory(client.Character.Inventory, itemData);
}
OrderData = new XElement("orders");
if (client.Character != null)
{
CharacterInfo.SaveOrderData(client.Character.Info, OrderData);
}
}
public CharacterCampaignData(XElement element)
{
Name = element.GetAttributeString("name", "Unnamed");
ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", "");
string steamID = element.GetAttributeString("steamid", "");
if (!string.IsNullOrEmpty(steamID))
{
ulong.TryParse(steamID, out ulong parsedID);
SteamID = parsedID;
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "character":
case "characterinfo":
CharacterInfo = new CharacterInfo(subElement);
break;
case "inventory":
itemData = subElement;
break;
case "health":
healthData = subElement;
break;
case "orders":
OrderData = subElement;
break;
}
}
}
public void Refresh(Character character) public void Refresh(Character character)
{ {
healthData = new XElement("health"); healthData = new XElement("health");
@@ -141,10 +141,10 @@ namespace Barotrauma
(SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1); (SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1);
} }
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition) public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null)
{ {
return return
base.CanBePutInSlot(itemPrefab, i, condition) && base.CanBePutInSlot(itemPrefab, i, condition, quality) &&
(SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1); (SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1);
} }
@@ -268,7 +268,7 @@ namespace Barotrauma.Items.Components
{ {
string[] allSpecies = SpeciesName.Split(','); string[] allSpecies = SpeciesName.Split(',');
string species = allSpecies.GetRandom().Trim(); string species = allSpecies.GetRandom().Trim();
Entity.Spawner.AddToSpawnQueue(species, pos); Entity.Spawner?.AddToSpawnQueue(species, pos);
} }
else if (!string.IsNullOrWhiteSpace(ItemIdentifier)) else if (!string.IsNullOrWhiteSpace(ItemIdentifier))
{ {
@@ -282,7 +282,7 @@ namespace Barotrauma.Items.Components
pos -= sub.Position; pos -= sub.Position;
} }
Entity.Spawner.AddToSpawnQueue(prefab, pos, item.Submarine); Entity.Spawner?.AddToSpawnQueue(prefab, pos, item.Submarine);
} }
} }
} }
@@ -642,10 +642,8 @@ namespace Barotrauma.Items.Components
item.Drop(character); item.Drop(character);
item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false); item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false);
} }
AttachToWall();
} }
AttachToWall();
return true; return true;
} }
@@ -291,13 +291,16 @@ namespace Barotrauma.Items.Components
int index = Inventory.FindIndex(containedItem); int index = Inventory.FindIndex(containedItem);
if (index >= 0 && index < slotRestrictions.Length) if (index >= 0 && index < slotRestrictions.Length)
{ {
RelatedItem ri = slotRestrictions[index].ContainableItems?.Find(ci => ci.MatchesItem(containedItem)); if (slotRestrictions[index].ContainableItems != null)
if (ri != null)
{ {
activeContainedItems.RemoveAll(i => i.Item == containedItem); activeContainedItems.RemoveAll(i => i.Item == containedItem);
foreach (StatusEffect effect in ri.statusEffects) foreach (var containableItem in slotRestrictions[index].ContainableItems)
{ {
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.ExcludeBroken)); if (!containableItem.MatchesItem(containedItem)) { continue; }
foreach (StatusEffect effect in containableItem.statusEffects)
{
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken));
}
} }
} }
} }
@@ -34,6 +34,8 @@ namespace Barotrauma.Items.Components
public OxygenGenerator(Item item, XElement element) public OxygenGenerator(Item item, XElement element)
: base(item, element) : base(item, element)
{ {
//randomize update timer so all oxygen generators don't update at the same time
ventUpdateTimer = Rand.Range(0.0f, VentUpdateInterval);
IsActive = true; IsActive = true;
} }
@@ -78,6 +80,7 @@ namespace Barotrauma.Items.Components
private void GetVents() private void GetVents()
{ {
totalHullVolume = 0.0f;
ventList ??= new List<(Vent vent, float hullVolume)>(); ventList ??= new List<(Vent vent, float hullVolume)>();
ventList.Clear(); ventList.Clear();
foreach (MapEntity entity in item.linkedTo) foreach (MapEntity entity in item.linkedTo)
@@ -87,13 +90,14 @@ namespace Barotrauma.Items.Components
Vent vent = linkedItem.GetComponent<Vent>(); Vent vent = linkedItem.GetComponent<Vent>();
if (vent?.Item.CurrentHull == null) { continue; } if (vent?.Item.CurrentHull == null) { continue; }
totalHullVolume += vent.Item.CurrentHull.Volume;
ventList.Add((vent, vent.Item.CurrentHull.Volume)); ventList.Add((vent, vent.Item.CurrentHull.Volume));
} }
for (int i = 0; i < ventList.Count; i++) for (int i = 0; i < ventList.Count; i++)
{ {
Vent vent = ventList[i].vent; Vent vent = ventList[i].vent;
foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: false, searchDepth: 5, ignoreClosedGaps: true)) foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: false, searchDepth: 3, ignoreClosedGaps: true))
{ {
//another vent in the connected hull -> don't add it to this vent's total hull volume //another vent in the connected hull -> don't add it to this vent's total hull volume
if (ventList.Any(v => v.vent != vent && v.vent.Item.CurrentHull == connectedHull)) { continue; } if (ventList.Any(v => v.vent != vent && v.vent.Item.CurrentHull == connectedHull)) { continue; }
@@ -57,7 +57,7 @@ namespace Barotrauma
return true; return true;
} }
public bool CanBePut(ItemPrefab itemPrefab, float? condition = null) public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null)
{ {
if (itemPrefab == null) { return false; } if (itemPrefab == null) { return false; }
if (items.Count > 0) if (items.Count > 0)
@@ -82,6 +82,11 @@ namespace Barotrauma
if (items.Any(it => !it.IsFullCondition)) { return false; } if (items.Any(it => !it.IsFullCondition)) { return false; }
} }
if (quality.HasValue)
{
if (items[0].Quality != quality.Value) { return false; }
}
if (items[0].Prefab.Identifier != itemPrefab.Identifier || if (items[0].Prefab.Identifier != itemPrefab.Identifier ||
items.Count + 1 > itemPrefab.MaxStackSize) items.Count + 1 > itemPrefab.MaxStackSize)
{ {
@@ -172,6 +177,11 @@ namespace Barotrauma
items.Clear(); items.Clear();
} }
public void RemoveWhere(Func<Item, bool> predicate)
{
items.RemoveAll(it => predicate(it));
}
public bool Any() public bool Any()
{ {
return items.Count > 0; return items.Count > 0;
@@ -422,16 +432,16 @@ namespace Barotrauma
return slots[i].CanBePut(item, ignoreCondition); return slots[i].CanBePut(item, ignoreCondition);
} }
public bool CanBePut(ItemPrefab itemPrefab, float? condition = null) public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null)
{ {
for (int i = 0; i < capacity; i++) for (int i = 0; i < capacity; i++)
{ {
if (CanBePutInSlot(itemPrefab, i, condition)) { return true; } if (CanBePutInSlot(itemPrefab, i, condition, quality)) { return true; }
} }
return false; return false;
} }
public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null) public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null, int? quality = null)
{ {
if (i < 0 || i >= slots.Length) { return false; } if (i < 0 || i >= slots.Length) { return false; }
return slots[i].CanBePut(itemPrefab, condition); return slots[i].CanBePut(itemPrefab, condition);
@@ -503,7 +513,7 @@ namespace Barotrauma
{ {
return true; return true;
} }
return return
TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: true) || TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: true) ||
TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: false); TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: false);
} }
@@ -776,11 +786,17 @@ namespace Barotrauma
{ {
for (int j = 0; j < capacity; j++) for (int j = 0; j < capacity; j++)
{ {
if (slots[j].Contains(item)) { slots[j].RemoveAllItems(); }; if (slots[j].Contains(item))
{
slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it));
}
} }
for (int j = 0; j < otherInventory.capacity; j++) for (int j = 0; j < otherInventory.capacity; j++)
{ {
if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) { otherInventory.slots[j].RemoveAllItems(); } if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault()))
{
otherInventory.slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it));
}
} }
} }
@@ -51,11 +51,11 @@ namespace Barotrauma
return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i); return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i);
} }
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition) public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null)
{ {
if (i < 0 || i >= slots.Length) { return false; } if (i < 0 || i >= slots.Length) { return false; }
if (!container.CanBeContained(itemPrefab, i)) { return false; } if (!container.CanBeContained(itemPrefab, i)) { return false; }
return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.GetMaxStackSize(i); return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition, quality) && slots[i].ItemCount < container.GetMaxStackSize(i);
} }
public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition)
@@ -864,6 +864,16 @@ namespace Barotrauma
foreach (AbyssIsland island in AbyssIslands) foreach (AbyssIsland island in AbyssIslands)
{ {
island.Area = new Rectangle(borders.Width - island.Area.Right, island.Area.Y, island.Area.Width, island.Area.Height); island.Area = new Rectangle(borders.Width - island.Area.Right, island.Area.Y, island.Area.Width, island.Area.Height);
foreach (var cell in island.Cells)
{
if (!mirroredSites.Contains(cell.Site))
{
if (cell.Site.Coord.X % GridCellSize < 1.0f &&
cell.Site.Coord.X % GridCellSize >= 0.0f) { cell.Site.Coord.X += 1.0f; }
cell.Site.Coord.X = borders.Width - cell.Site.Coord.X;
mirroredSites.Add(cell.Site);
}
}
} }
for (int i = 0; i < ruinPositions.Count; i++) for (int i = 0; i < ruinPositions.Count; i++)
@@ -1972,7 +1982,7 @@ namespace Barotrauma
ConvertUnits.ToSimUnits(entranceWayPoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null; ConvertUnits.ToSimUnits(entranceWayPoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null;
}); });
if (closestWp == null) { continue; } if (closestWp == null) { continue; }
entranceWayPoint.ConnectTo(closestWp); ConnectWaypoints(entranceWayPoint, closestWp, outSideWaypointInterval);
} }
//create a waypoint path from the ruin to the closest tunnel //create a waypoint path from the ruin to the closest tunnel
@@ -1023,7 +1023,12 @@ namespace Barotrauma
#if CLIENT #if CLIENT
if (playSound && damageAmount > 0) if (playSound && damageAmount > 0)
{ {
SoundPlayer.PlayDamageSound(attack.StructureSoundType, damageAmount, worldPosition, tags: Tags); string damageSound = Prefab.DamageSound;
if (string.IsNullOrWhiteSpace(damageSound))
{
damageSound = attack.StructureSoundType;
}
SoundPlayer.PlayDamageSound(damageSound, damageAmount, worldPosition, tags: Tags);
} }
#endif #endif
@@ -167,6 +167,9 @@ namespace Barotrauma
private set { size = value; } private set { size = value; }
} }
[Serialize("", true)]
public string DamageSound { get; private set; }
public Vector2 ScaledSize => size * Scale; public Vector2 ScaledSize => size * Scale;
protected Vector2 textureScale = Vector2.One; protected Vector2 textureScale = Vector2.One;
@@ -26,6 +26,7 @@ namespace Barotrauma.Networking
UpdateExperience, UpdateExperience,
UpdateTalents, UpdateTalents,
UpdateMoney, UpdateMoney,
UpdatePermanentStats,
} }
public readonly Entity Entity; public readonly Entity Entity;
@@ -1284,68 +1284,66 @@ namespace Barotrauma
} }
} }
int i = 0; if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
foreach (int giveExperience in giveExperiences)
{ {
Character targetCharacter = CharacterFromTarget(target); // these effects do not need to be run clientside, as they are replicated from server to clients anyway
if (targetCharacter != null && !targetCharacter.Removed)
{
targetCharacter?.Info?.GiveExperience(giveExperience);
i++;
}
}
if (giveSkills.Any()) foreach (int giveExperience in giveExperiences)
{
foreach ((string skillIdentifier, float amount) in giveSkills)
{ {
Character targetCharacter = CharacterFromTarget(target); Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter != null && !targetCharacter.Removed) if (targetCharacter != null && !targetCharacter.Removed)
{ {
if (skillIdentifier?.ToLowerInvariant() == "randomskill") targetCharacter?.Info?.GiveExperience(giveExperience);
}
}
if (giveSkills.Any())
{
foreach ((string skillIdentifier, float amount) in giveSkills)
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter != null && !targetCharacter.Removed)
{ {
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) if (skillIdentifier?.ToLowerInvariant() == "randomskill")
{ {
// don't let clients simulate random skill gain targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount);
continue;
string GetRandomSkill()
{
return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom();
}
} }
targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount); else
string GetRandomSkill()
{ {
return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom(); targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount);
} }
} }
else
{
targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount);
}
} }
} }
}
if (giveTalentInfos.Any() && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient))
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter?.Info == null) { continue; }
if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; }
// for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well
IEnumerable<string> disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)));
foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos) if (giveTalentInfos.Any())
{ {
IEnumerable<string> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s)); Character targetCharacter = CharacterFromTarget(target);
if (viableTalents.None()) { continue; } if (targetCharacter?.Info == null) { continue; }
if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; }
// for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well
IEnumerable<string> disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)));
if (giveTalentInfo.GiveRandom) foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos)
{ {
targetCharacter.GiveTalent(viableTalents.GetRandom(), true); IEnumerable<string> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s));
} if (viableTalents.None()) { continue; }
else
{ if (giveTalentInfo.GiveRandom)
foreach (string talent in viableTalents)
{ {
targetCharacter.GiveTalent(talent, true); targetCharacter.GiveTalent(viableTalents.GetRandom(), true);
}
else
{
foreach (string talent in viableTalents)
{
targetCharacter.GiveTalent(talent, true);
}
} }
} }
} }
+36 -1
View File
@@ -1,3 +1,38 @@
---------------------------------------------------------------------------------------------------------
v0.1500.8.0
---------------------------------------------------------------------------------------------------------
Additions and changes:
- More improvements and fixes to the alien ruins and the new fractal guardians.
- Improvements to character animations and sprites.
- Made ruin scan/clear missions available in outposts.
- Talent adjustments and fixes.
- Adjustments to outpost distribution: natural formations greatly reduced in the 1st zone, cities slightly reduced in the 1st zone, outposts (including specialized ones) increased in the 3rd and 4th zone.
- Made magnesium a little more common in stores and wrecks.
- Temporarily disabled magnesium exploding in water to prevent issues with talents related to it.
- Added an additonal ambience track for the ruins.
- New sounds for alien ruins and the guardians.
- Portable pumps turn off automatically when not attached to a wall.
Fixes:
- Fixed outpost events always unlocking the same escort mission.
- Fixed server occasionally failing to end the round and spamming the clients with XP notifications. Happened when the server was trying to give experience for the completed missions and there were clients in the server whose character had been removed (unstable only).
- Fixed shotgun shells not having a fabrication recipe (unstable only).
- Fixed thermal goggles being sold in outposts (unstable only).
- Fixes to ruin waypoint generation (unstable only).
- Fixes to pathfinding outside ruins (unstable only).
- Fixed oxygen generator output constantly decreasing due to the changes in the previous build (unstable only).
- Fixed long item names being unreadable on the fabricator UI.
- The hints about flooded rooms and ballast flora aren't shown in ruins, wrecks or enemy subs.
- Fixed "setclientcharacter" command crashing the server if the specified character is not found.
- Fixed autofilling subs with supplies sometimes causing high-quality items to appear on the floor near containers (unstable only).
- Fixed guardian spears being fabricatable (unstable only).
- Fixed "stowaway" event triggering an event cooldown, preventing monsters from spawning at the beginning of the round.
- Fixed clients (excluding the host) always considering friendly fire to be disabled, leading to minor cosmetic desyncs when a player applies afflictions on another one (i.e. there was a brief delay before the afflictions update client-side).
- Fixed inability to apply buffs on the crew when friendly fire is disabled.
- Fixed ItemContainers only applying the StatusEffects from the first matching Containable, even if there's multiple. Prevented the artifact-specific effects of artifact holder from executing.
- Fixed "giveaffliction" command's limbtype argument not working in multiplayer.
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
v0.1500.7.0 v0.1500.7.0
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
@@ -7,7 +42,7 @@ Additions and changes:
- Added a colored border to high-quality items' inventory slots. - Added a colored border to high-quality items' inventory slots.
- Changed the look of the skill/xp notifications to accommodate the larger numbers of notifications you can get from talents and skillbooks. - Changed the look of the skill/xp notifications to accommodate the larger numbers of notifications you can get from talents and skillbooks.
- Added a fabricator and deconstructor to Azimuth and slightly lowered its maximum speed. - Added a fabricator and deconstructor to Azimuth and slightly lowered its maximum speed.
- Increased Azimuth's battery out relay max power- - Increased Azimuth's battery out relay max power.
- Field Medic now only triggers on missions. - Field Medic now only triggers on missions.
- Reduced gravity sphere's force to make it possible to escape it with a diving suit on. - Reduced gravity sphere's force to make it possible to escape it with a diving suit on.
- Diving suit and human ragdoll damagemodifier changes: the suits now offer less protection, but humans have a bit more natural protection towards physical damage types. - Diving suit and human ragdoll damagemodifier changes: the suits now offer less protection, but humans have a bit more natural protection towards physical damage types.