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

View File

@@ -688,8 +688,10 @@ namespace Barotrauma
new GUIFrame(
new RectTransform(Vector2.One * 0.7f, dropdownButton.RectTransform, Anchor.CenterLeft)
{ RelativeOffset = new Vector2(0.05f, 0.0f) }, style: null);
Color? previewingColor = null;
dropdown.OnSelected = (component, color) =>
{
previewingColor = null;
setter((Color)color);
buttonFrame.Color = getter();
buttonFrame.HoverColor = getter();
@@ -727,25 +729,24 @@ namespace Barotrauma
dropdown.Select(dropdown.ListBox.Content.GetChildIndex(childToSelect));
//The following exists to track mouseover to preview colors before selecting them
bool previewingColor = false;
new GUICustomComponent(new RectTransform(Vector2.One, buttonFrame.RectTransform),
onUpdate: (deltaTime, component) =>
{
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 =>
c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData);
c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData ?? getter());
setter(color);
buttonFrame.Color = getter();
buttonFrame.HoverColor = getter();
}
else if (previewingColor)
else if (previewingColor.HasValue)
{
setter((Color)dropdown.SelectedData);
setter(previewingColor.Value);
buttonFrame.Color = getter();
buttonFrame.HoverColor = getter();
previewingColor = false;
previewingColor = null;
}
}, onDraw: null)
{

View File

@@ -299,7 +299,7 @@ namespace Barotrauma
break;
case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 12);
int eventType = msg.ReadRangedInteger(0, 13);
switch (eventType)
{
case 0: //NetEntityEvent.Type.InventoryState
@@ -476,6 +476,18 @@ namespace Barotrauma
int moneyAmount = msg.ReadInt32();
SetMoney(moneyAmount);
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();

View File

@@ -476,6 +476,7 @@ namespace Barotrauma
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);
Entity.Spawner ??= new EntitySpawner();
}
GameMain.GameScreen.Select();
}));
@@ -488,6 +489,8 @@ namespace Barotrauma
Submarine.MainSub = Submarine.Load(subInfo, true);
}
GameMain.SubEditorScreen.Select(enableAutoSave: Screen.Selected != GameMain.GameScreen);
Entity.Spawner?.Remove();
Entity.Spawner = null;
}, isCheat: true));
commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) =>

View File

@@ -139,7 +139,7 @@ namespace Barotrauma
GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
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;
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{
@@ -1254,7 +1254,7 @@ namespace Barotrauma
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)
{
@@ -1541,7 +1541,7 @@ namespace Barotrauma
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)
{
@@ -1612,7 +1612,7 @@ namespace Barotrauma
private bool ResetTalentSelection(GUIButton guiButton, object userData)
{
Character controlledCharacter = Character.Controlled;
selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList();
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentButtons();
return true;
}

View File

@@ -544,7 +544,7 @@ namespace Barotrauma
if (Character.Controlled.CurrentHull == null) { return; }
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)
{
@@ -552,7 +552,7 @@ namespace Barotrauma
if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; }
if (!gap.IsRoomToRoom)
{
if (!(Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item)) { continue; }
if (!IsWearingDivingSuit()) { continue; }
if (Character.Controlled.IsProtectedFromPressure()) { continue; }
if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; }
continue;
@@ -561,10 +561,16 @@ namespace Barotrauma
{
if (me == Character.Controlled.CurrentHull) { continue; }
if (!(me is Hull adjacentHull)) { continue; }
if (!IsOnFriendlySub()) { continue; }
if (IsWearingDivingSuit()) { continue; }
if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { 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()

View File

@@ -39,7 +39,7 @@ namespace Barotrauma.Items.Components
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;
if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; }

View File

@@ -529,21 +529,30 @@ namespace Barotrauma.Items.Components
};
}*/
string name = GetRecipeNameAndAmount(selectedItem);
string itemName = GetRecipeNameAndAmount(selectedItem);
string name = itemName;
float quality = GetFabricatedItemQuality(selectedItem, user);
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),
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
};
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, nameBlock.Padding.Z, nameBlock.Padding.W);
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), 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))
{
@@ -555,6 +564,7 @@ namespace Barotrauma.Items.Components
while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height)
{
var lines = description.WrappedText.Split('\n');
if (lines.Length <= 1) { break; }
var newString = string.Join('\n', lines.Take(lines.Length - 1));
description.Text = newString.Substring(0, newString.Length - 4) + "...";
description.CalculateHeightFromText();

View File

@@ -1465,6 +1465,7 @@ namespace Barotrauma.Networking
bool respawnAllowed = inc.ReadBoolean();
serverSettings.AllowDisguises = inc.ReadBoolean();
serverSettings.AllowRewiring = inc.ReadBoolean();
serverSettings.AllowFriendlyFire = inc.ReadBoolean();
serverSettings.LockAllDefaultWires = inc.ReadBoolean();
serverSettings.AllowRagdollButton = inc.ReadBoolean();
GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();

View File

@@ -884,7 +884,7 @@ namespace Barotrauma
// 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))
{
targetMusic[mainTrackIndex] = suitableMusic.GetRandom();
targetMusic[typeAmbienceTrackIndex] = suitableTypeAmbiences.GetRandom();
}
//get the appropriate intensity layers for current situation

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1500.7.0</Version>
<Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1500.7.0</Version>
<Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1500.7.0</Version>
<Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.7.0</Version>
<Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.7.0</Version>
<Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -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)
{
msg.Write(ID);
@@ -66,8 +72,8 @@ namespace Barotrauma
msg.Write((byte)0);
}
// TODO: animations
msg.Write((byte)savedStatValues.SelectMany(s => s.Value).Count());
foreach (var savedStatValuePair in savedStatValues)
msg.Write((byte)SavedStatValues.SelectMany(s => s.Value).Count());
foreach (var savedStatValuePair in SavedStatValues)
{
foreach (var savedStatValue in savedStatValuePair.Value)
{

View File

@@ -310,7 +310,7 @@ namespace Barotrauma
if (extraData != null)
{
const int min = 0, max = 12;
const int min = 0, max = 13;
switch ((NetEntityEvent.Type)extraData[0])
{
case NetEntityEvent.Type.InventoryState:
@@ -438,6 +438,30 @@ namespace Barotrauma
msg.WriteRangedInteger(12, min, max);
msg.Write(GameMain.GameSession.Campaign.Money);
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:
DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")");
break;

View File

@@ -1677,10 +1677,25 @@ namespace Barotrauma
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());
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)
{
GameMain.Server.SendConsoleMessage("Client \"" + args[0] + "\" not found.", senderClient);
return;
}
var character = FindMatchingCharacter(args.Skip(1).ToArray(), false);

View File

@@ -1,4 +1,6 @@
using Barotrauma.Networking;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -11,11 +13,65 @@ namespace Barotrauma
get { return itemData != null; }
}
partial void InitProjSpecific(Client client)
public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false)
{
Name = client.Name;
ClientEndPoint = client.Connection.EndPointString;
SteamID = client.SteamID;
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)

View File

@@ -209,6 +209,11 @@ namespace Barotrauma
//refresh the character data of clients who are still in the server
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)
{
//the client has opted to spawn this round with Reaper's Tax
@@ -228,7 +233,7 @@ namespace Barotrauma
}
c.CharacterInfo = characterInfo;
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

View File

@@ -2470,6 +2470,7 @@ namespace Barotrauma.Networking
msg.Write(serverSettings.AllowRespawn && missionAllowRespawn);
msg.Write(serverSettings.AllowDisguises);
msg.Write(serverSettings.AllowRewiring);
msg.Write(serverSettings.AllowFriendlyFire);
msg.Write(serverSettings.LockAllDefaultWires);
msg.Write(serverSettings.AllowRagdollButton);
msg.Write(serverSettings.UseRespawnShuttle);

View File

@@ -92,7 +92,7 @@ namespace Barotrauma
case VoteType.Mode:
string modeIdentifier = inc.ReadString();
GameModePreset mode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier);
if (!mode.Votable) { break; }
if (mode == null || !mode.Votable) { break; }
sender.SetVote(voteType, mode);
break;
case VoteType.EndRound:

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.7.0</Version>
<Version>0.1500.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
@@ -346,7 +347,7 @@ namespace Barotrauma
}
#region Escape
public abstract void Escape(float deltaTime);
public abstract bool Escape(float deltaTime);
public Gap EscapeTarget { get; private set; }
@@ -425,17 +426,38 @@ namespace Barotrauma
}
if (EscapeTarget != null)
{
SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10);
float sqrDist = Vector2.DistanceSquared(Character.SimPosition, EscapeTarget.SimPosition);
if (sqrDist < 0.5f || Character.CurrentHull == null || HasValidPath(requireNonDirty: true, requireUnfinished: false) && pathSteering.CurrentPath.Finished)
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
float sqrDist = diff.LengthSquared();
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.SteeringManual(deltaTime, Vector2.Normalize(EscapeTarget.WorldPosition - Character.WorldPosition));
if (sqrDist < 4)
pathSteering?.ResetPath();
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

View File

@@ -1903,7 +1903,7 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs();
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1);
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
if (Character.Params.CanInteract)
if (Character.Params.CanInteract && attackResult.Damage > 10)
{
ReleaseDragTargets();
}
@@ -3607,12 +3607,12 @@ namespace Barotrauma
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))
{
State = AIState.Idle;
return;
return false;
}
else if (SelectedTargetMemory is AITargetMemory targetMemory && SelectedAiTarget?.Entity is Character)
{
@@ -3653,6 +3653,7 @@ namespace Barotrauma
}
}
}
return isSteeringThroughGap;
void SteerAwayFromTheEnemy()
{

View File

@@ -1364,10 +1364,7 @@ namespace Barotrauma
ObjectiveManager.WaitTimer = waitDuration;
}
public override void Escape(float deltaTime)
{
UpdateEscape(deltaTime, canAttackDoors: false);
}
public override bool Escape(float deltaTime) => UpdateEscape(deltaTime, canAttackDoors: false);
private void CheckCrouching(float deltaTime)
{

View File

@@ -269,7 +269,7 @@ namespace Barotrauma
{
var waypoint = CurrentPath.Nodes[i];
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);
continue;

View File

@@ -9,6 +9,7 @@ namespace Barotrauma
public override string Identifier { get; set; } = "return";
private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective;
private bool usingEscapeBehavior;
private bool isSteeringThroughGap;
public Submarine ReturnTarget { get; }
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
@@ -57,7 +58,7 @@ namespace Barotrauma
return;
}
bool shouldUseEscapeBehavior = false;
if (character.CurrentHull != null)
if (character.CurrentHull != null || isSteeringThroughGap)
{
if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget))
{
@@ -67,8 +68,8 @@ namespace Barotrauma
{
HumanAIController.ResetEscape();
}
HumanAIController.Escape(deltaTime);
if (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable)
isSteeringThroughGap = HumanAIController.Escape(deltaTime);
if (!isSteeringThroughGap && (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable))
{
Abandon = true;
}
@@ -92,7 +93,10 @@ namespace Barotrauma
RemoveSubObjective(ref moveInCaveObjective);
RemoveSubObjective(ref moveOutsideObjective);
TryAddSubObjective(ref moveInsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager),
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveInsideObjective),
onAbandon: () => Abandon = true);
}
@@ -110,7 +114,7 @@ namespace Barotrauma
IsCompleted = true;
}
}
else if (moveInCaveObjective == null && moveOutsideObjective == null)
else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null)
{
if (HumanAIController.IsInsideCave)
{
@@ -134,7 +138,8 @@ namespace Barotrauma
TryAddSubObjective(ref moveInCaveObjective,
constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager)
{
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint,
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveInCaveObjective),
onAbandon: () => Abandon = true);
@@ -170,7 +175,10 @@ namespace Barotrauma
RemoveSubObjective(ref moveInsideObjective);
RemoveSubObjective(ref moveInCaveObjective);
TryAddSubObjective(ref moveOutsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager),
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
onAbandon: () => Abandon = true);
}
@@ -221,6 +229,7 @@ namespace Barotrauma
moveInCaveObjective = null;
moveOutsideObjective = null;
usingEscapeBehavior = false;
isSteeringThroughGap = false;
HumanAIController.ResetEscape();
}

View File

@@ -236,6 +236,7 @@ namespace Barotrauma
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
if (body != null)
{
if (body.UserData is Submarine) { 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; }
}

View File

@@ -684,18 +684,15 @@ namespace Barotrauma
if (rightHand != null && !rightHand.Disabled)
{
HandIK(rightHand, torso.SimPosition + posAddition +
new Vector2(
-handPos.X,
(Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
HandIK(rightHand,
torso.SimPosition + posAddition + new Vector2(-handPos.X, (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY),
CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
}
if (leftHand != null && !leftHand.Disabled)
{
HandIK(leftHand, torso.SimPosition + posAddition +
new Vector2(
handPos.X,
(Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
HandIK(leftHand,
torso.SimPosition + posAddition + new Vector2(handPos.X, (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY),
CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
}
}
else
@@ -705,9 +702,7 @@ namespace Barotrauma
Vector2 footPos = colliderPos;
if (Crouching)
{
footPos = new Vector2(
Math.Sign(stepSize.X * i) * Dir * 0.4f,
colliderPos.Y);
footPos = new Vector2(Math.Sign(stepSize.X * i) * Dir * 0.35f, colliderPos.Y);
if (Math.Sign(footPos.X) != Math.Sign(Dir))
{
//lift the foot at the back up a bit
@@ -728,9 +723,16 @@ namespace Barotrauma
{
foot.DebugRefPos = colliderPos;
foot.DebugTargetPos = footPos;
MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength);
FootIK(foot, footPos,
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
float footMoveForce = CurrentGroundedParams.FootMoveStrength;
float legBendTorque = CurrentGroundedParams.LegBendTorque;
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);
}
}
// 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);
}
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)
@@ -1021,6 +1035,12 @@ namespace Barotrauma
speedMultiplier = Math.Min(speedMultiplier, 0.1f);
}
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);
}
}
}

View File

@@ -665,6 +665,7 @@ namespace Barotrauma
public float MaxVitality => CharacterHealth.MaxVitality;
public float MaxHealth => MaxVitality;
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
{
@@ -2751,7 +2752,8 @@ namespace Barotrauma
}
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
else if (allowRagdoll && (!IsRagdolled || !tooFastToUnragdoll))
@@ -2765,13 +2767,18 @@ namespace Barotrauma
{
wasRagdolled = IsRagdolled;
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);
@@ -3535,11 +3542,6 @@ namespace Barotrauma
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;
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.TeamID == TeamID) { return new AttackResult(); }
if (attacker.TeamID == TeamID)
{
afflictions = afflictions.Where(a => !a.Prefab.IsBuff);
if (!afflictions.Any()) { return new AttackResult(); }
}
}
#if CLIENT
@@ -4385,14 +4391,14 @@ namespace Barotrauma
info.UnlockedTalents.Add(talentPrefab.Identifier);
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.ActivateTalent(addingFirstTime);
characterTalents.Add(characterTalent);
characterTalent.AddedThisRound = addingFirstTime;
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents });
#endif
if (addingFirstTime)
{
OnTalentGiven(talentPrefab.Identifier);

View File

@@ -216,6 +216,17 @@ namespace Barotrauma
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; }
private Sprite _headSprite;
@@ -1220,7 +1231,7 @@ namespace Barotrauma
var experienceGainMultiplier = new AbilityValue(1f);
if (isMissionExperience)
{
Character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
}
experienceGainMultiplier.Value += Character.GetStatValue(StatTypes.ExperienceGainMultiplier);
@@ -1252,7 +1263,7 @@ namespace Barotrauma
public int GetAvailableTalentPoints()
{
// hashset always has at least 1
return Math.Max(GetTotalTalentPoints() - UnlockedTalents.Count, 0);
return Math.Max(GetTotalTalentPoints() - GetUnlockedTalentsInTree().Count(), 0);
}
public float GetProgressTowardsNextLevel()
@@ -1297,6 +1308,8 @@ namespace Barotrauma
partial void OnExperienceChanged(int prevAmount, int newAmount);
partial void OnPermanentStatChanged(StatTypes statType);
public void Rename(string newName)
{
if (string.IsNullOrEmpty(newName)) { return; }
@@ -1362,7 +1375,7 @@ namespace Barotrauma
Job.Save(charElement);
XElement savedStatElement = new XElement("savedstatvalues");
foreach (var statValuePair in savedStatValues)
foreach (var statValuePair in SavedStatValues)
{
foreach (var savedStat in statValuePair.Value)
{
@@ -1708,26 +1721,42 @@ namespace Barotrauma
}
// 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)
{
savedStatValue.StatValue = 0f;
}
OnPermanentStatChanged(statType);
}
SavedStatValues.Clear();
}
public void ClearSavedStatValues(StatTypes statType)
{
SavedStatValues.Remove(statType);
OnPermanentStatChanged(statType);
}
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)
{
if (savedStatValues.TryGetValue(statType, out var statValues))
if (SavedStatValues.TryGetValue(statType, out var statValues))
{
return statValues.Sum(v => v.StatValue);
}
@@ -1738,7 +1767,7 @@ namespace Barotrauma
}
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);
}
@@ -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)
{
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);
changed = !MathUtils.NearlyEqual(savedStat.StatValue, prevValue);
}
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); }
}
}

View File

@@ -11,6 +11,7 @@ namespace Barotrauma.Abilities
protected readonly List<StatusEffect> statusEffects;
private readonly bool nearbyCharactersAppliesToSelf;
private readonly bool nearbyCharactersAppliesToAllies;
private readonly bool applyToSelected;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
@@ -20,6 +21,7 @@ namespace Barotrauma.Abilities
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true);
nearbyCharactersAppliesToAllies = abilityElement.GetAttributeBool("nearbycharactersappliestoallies", true);
}
protected void ApplyEffectSpecific(Character targetCharacter)
@@ -40,6 +42,10 @@ namespace Barotrauma.Abilities
{
targets.RemoveAll(c => c == Character);
}
if (!nearbyCharactersAppliesToAllies)
{
targets.RemoveAll(c => c is Character otherCharacter && HumanAIController.IsFriendly(otherCharacter, Character));
}
statusEffect.SetUser(Character);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
}
@@ -56,17 +62,20 @@ namespace Barotrauma.Abilities
}
}
protected override void ApplyEffect()
{
ApplyEffectSpecific(Character);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (applyToSelected && Character.SelectedCharacter is Character 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);
}

View File

@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
private readonly bool setValue;
//private readonly float maximumValue;
public override bool AllowClientSimulation => true;
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)

View File

@@ -6,6 +6,7 @@ namespace Barotrauma.Abilities
{
private readonly string statIdentifier;
public override bool AppliesEffectOnIntervalUpdate => true;
public override bool AllowClientSimulation => true;
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{

View File

@@ -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)
{

View File

@@ -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)
{
DebugConsole.Log("Loading talent tree: " + file.Path);

View File

@@ -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; }
@@ -622,14 +622,12 @@ namespace Barotrauma
}
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)
{
Limb targetLimb = targetCharacter.AnimController.MainLimb;
@@ -1889,9 +1887,9 @@ namespace Barotrauma
if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client))
{
#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
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;
#endif
}

View File

@@ -47,6 +47,7 @@
OnReduceAffliction,
OnAddDamageAffliction,
OnSelfRagdoll,
OnRagdoll,
OnRoundEnd,
OnAnyMissionCompleted,
OnAllMissionsCompleted,

View File

@@ -179,7 +179,7 @@ namespace Barotrauma
if (eventSet == null) { return; }
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))
{
@@ -434,23 +434,31 @@ namespace Barotrauma
}
}
var suitablePrefabs = eventSet.EventPrefabs.FindAll(e =>
string.IsNullOrEmpty(e.prefab.BiomeIdentifier) ||
e.prefab.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase));
bool isPrefabSuitable(EventPrefab p)
=> string.IsNullOrEmpty(p.BiomeIdentifier) ||
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++)
{
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++)
{
if (unusedEvents.All(e => CalculateCommonness(e.prefab, e.commonness) <= 0.0f)) { break; }
(EventPrefab eventPrefab, float commonness, float probability) = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e.prefab, e.commonness)).ToList(), rand);
if (eventPrefab != null && rand.NextDouble() <= probability)
if (unusedEvents.All(e => e.Prefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) { break; }
EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Prefabs.Max(p => CalculateCommonness(p, e.Commonness))).ToList(), rand);
(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();
if (newEvent == null) { continue; }
newEvent.Init(true);
@@ -465,7 +473,7 @@ namespace Barotrauma
selectedEvents.Add(eventSet, new List<Event>());
}
selectedEvents[eventSet].Add(newEvent);
unusedEvents.Remove((eventPrefab, commonness, probability));
unusedEvents.Remove(subEventPrefab);
}
}
}
@@ -480,9 +488,13 @@ namespace Barotrauma
}
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; }
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();
if (newEvent == null) { continue; }
newEvent.Init(true);
@@ -866,7 +878,7 @@ namespace Barotrauma
private float CalculateDistanceTraveled()
{
if (level == null) { return 0.0f; }
if (level == null || pathFinder == null) { return 0.0f; }
var refEntity = GetRefEntity();
if (refEntity == null) { return 0.0f; }
Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition);

View File

@@ -40,7 +40,7 @@ namespace Barotrauma
BiomeIdentifier = ConfigElement.GetAttributeString("biome", string.Empty);
Commonness = element.GetAttributeFloat("commonness", 1.0f);
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);
UnlockPathTooltip = element.GetAttributeString("unlockpathtooltip", "lockedpathtooltip");

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
@@ -48,10 +49,10 @@ namespace Barotrauma
List<EventPrefab> eventPrefabs = new List<EventPrefab>(PrefabList);
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)
{
eventPrefabs.AddRange(childSet.EventPrefabs.Select(ep => ep.prefab));
eventPrefabs.AddRange(childSet.EventPrefabs.SelectMany(ep => ep.Prefabs));
}
}
return eventPrefabs;
@@ -98,7 +99,48 @@ namespace Barotrauma
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;
@@ -112,7 +154,7 @@ namespace Barotrauma
{
DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier;
Commonness = new Dictionary<string, float>();
EventPrefabs = new List<(EventPrefab prefab, float commonness, float probability)>();
EventPrefabs = new List<SubEventPrefab>();
ChildSets = new List<EventSet>();
BiomeIdentifier = element.GetAttributeString("biome", string.Empty);
@@ -178,23 +220,20 @@ namespace Barotrauma
//an element with just an identifier = reference to an event prefab
if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase))
{
string identifier = subElement.GetAttributeString("identifier", "");
var prefab = PrefabList.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
if (prefab == null)
{
DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{identifier}\".");
}
else
{
float commonness = subElement.GetAttributeFloat("commonness", prefab.Commonness);
float probability = subElement.GetAttributeFloat("probability", prefab.Probability);
EventPrefabs.Add((prefab, commonness, probability));
}
string[] identifiers = subElement.GetAttributeStringArray("identifier", Array.Empty<string>());
float commonness = subElement.GetAttributeFloat("commonness", -1f);
float probability = subElement.GetAttributeFloat("probability", -1f);
EventPrefabs.Add(new SubEventPrefab(
debugIdentifier,
identifiers,
commonness>=0f ? commonness : (float?)null,
probability>=0f ? probability : (float?)null));
}
else
{
var prefab = new EventPrefab(subElement);
EventPrefabs.Add((prefab, prefab.Commonness, prefab.Probability));
EventPrefabs.Add(new SubEventPrefab(prefab, prefab.Commonness, prefab.Probability));
}
break;
}
@@ -346,13 +385,13 @@ namespace Barotrauma
{
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++)
{
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.commonness).ToList(), Rand.RandSync.Unsynced);
if (eventPrefab.prefab != null)
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced);
if (eventPrefab.Prefabs.Any(p => p != null))
{
AddEvent(stats, eventPrefab.prefab);
AddEvents(stats, eventPrefab.Prefabs);
unusedEvents.Remove(eventPrefab);
}
}
@@ -361,7 +400,7 @@ namespace Barotrauma
{
foreach (var eventPrefab in thisSet.EventPrefabs)
{
AddEvent(stats, eventPrefab.prefab);
AddEvents(stats, eventPrefab.Prefabs);
}
}
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)
{
if (eventPrefab.EventType == typeof(MonsterEvent))

View File

@@ -363,7 +363,7 @@ namespace Barotrauma
#if CLIENT
foreach (Character character in crewCharacters)
{
character.Info.GiveExperience(experienceGain, isMissionExperience: true);
character.Info?.GiveExperience(experienceGain, isMissionExperience: true);
}
#else
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)

View File

@@ -218,12 +218,12 @@ namespace Barotrauma
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.125f,
0.0225f,
0.0025f,
(0, 0.85f),
(1, 0.125f),
(2, 0.0225f),
(3, 0.0025f),
};
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);
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)
{
SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost,

View File

@@ -1,6 +1,4 @@
using Barotrauma.Networking;
using System.Globalization;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -29,65 +27,6 @@ namespace Barotrauma
private XElement healthData;
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)
{
healthData = new XElement("health");

View File

@@ -141,10 +141,10 @@ namespace Barotrauma
(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
base.CanBePutInSlot(itemPrefab, i, condition) &&
base.CanBePutInSlot(itemPrefab, i, condition, quality) &&
(SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1);
}

View File

@@ -268,7 +268,7 @@ namespace Barotrauma.Items.Components
{
string[] allSpecies = SpeciesName.Split(',');
string species = allSpecies.GetRandom().Trim();
Entity.Spawner.AddToSpawnQueue(species, pos);
Entity.Spawner?.AddToSpawnQueue(species, pos);
}
else if (!string.IsNullOrWhiteSpace(ItemIdentifier))
{
@@ -282,7 +282,7 @@ namespace Barotrauma.Items.Components
pos -= sub.Position;
}
Entity.Spawner.AddToSpawnQueue(prefab, pos, item.Submarine);
Entity.Spawner?.AddToSpawnQueue(prefab, pos, item.Submarine);
}
}
}

View File

@@ -642,10 +642,8 @@ namespace Barotrauma.Items.Components
item.Drop(character);
item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false);
}
AttachToWall();
}
AttachToWall();
return true;
}

View File

@@ -291,13 +291,16 @@ namespace Barotrauma.Items.Components
int index = Inventory.FindIndex(containedItem);
if (index >= 0 && index < slotRestrictions.Length)
{
RelatedItem ri = slotRestrictions[index].ContainableItems?.Find(ci => ci.MatchesItem(containedItem));
if (ri != null)
if (slotRestrictions[index].ContainableItems != null)
{
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));
}
}
}
}

View File

@@ -34,6 +34,8 @@ namespace Barotrauma.Items.Components
public OxygenGenerator(Item item, XElement 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;
}
@@ -78,6 +80,7 @@ namespace Barotrauma.Items.Components
private void GetVents()
{
totalHullVolume = 0.0f;
ventList ??= new List<(Vent vent, float hullVolume)>();
ventList.Clear();
foreach (MapEntity entity in item.linkedTo)
@@ -87,13 +90,14 @@ namespace Barotrauma.Items.Components
Vent vent = linkedItem.GetComponent<Vent>();
if (vent?.Item.CurrentHull == null) { continue; }
totalHullVolume += vent.Item.CurrentHull.Volume;
ventList.Add((vent, vent.Item.CurrentHull.Volume));
}
for (int i = 0; i < ventList.Count; i++)
{
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
if (ventList.Any(v => v.vent != vent && v.vent.Item.CurrentHull == connectedHull)) { continue; }

View File

@@ -57,7 +57,7 @@ namespace Barotrauma
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 (items.Count > 0)
@@ -82,6 +82,11 @@ namespace Barotrauma
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 ||
items.Count + 1 > itemPrefab.MaxStackSize)
{
@@ -172,6 +177,11 @@ namespace Barotrauma
items.Clear();
}
public void RemoveWhere(Func<Item, bool> predicate)
{
items.RemoveAll(it => predicate(it));
}
public bool Any()
{
return items.Count > 0;
@@ -422,16 +432,16 @@ namespace Barotrauma
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++)
{
if (CanBePutInSlot(itemPrefab, i, condition)) { return true; }
if (CanBePutInSlot(itemPrefab, i, condition, quality)) { return true; }
}
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; }
return slots[i].CanBePut(itemPrefab, condition);
@@ -503,7 +513,7 @@ namespace Barotrauma
{
return true;
}
return
return
TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: true) ||
TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: false);
}
@@ -776,11 +786,17 @@ namespace Barotrauma
{
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++)
{
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));
}
}
}

View File

@@ -51,11 +51,11 @@ namespace Barotrauma
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 (!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)

View File

@@ -864,6 +864,16 @@ namespace Barotrauma
foreach (AbyssIsland island in AbyssIslands)
{
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++)
@@ -1972,7 +1982,7 @@ namespace Barotrauma
ConvertUnits.ToSimUnits(entranceWayPoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null;
});
if (closestWp == null) { continue; }
entranceWayPoint.ConnectTo(closestWp);
ConnectWaypoints(entranceWayPoint, closestWp, outSideWaypointInterval);
}
//create a waypoint path from the ruin to the closest tunnel

View File

@@ -1023,7 +1023,12 @@ namespace Barotrauma
#if CLIENT
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

View File

@@ -167,6 +167,9 @@ namespace Barotrauma
private set { size = value; }
}
[Serialize("", true)]
public string DamageSound { get; private set; }
public Vector2 ScaledSize => size * Scale;
protected Vector2 textureScale = Vector2.One;

View File

@@ -26,6 +26,7 @@ namespace Barotrauma.Networking
UpdateExperience,
UpdateTalents,
UpdateMoney,
UpdatePermanentStats,
}
public readonly Entity Entity;

View File

@@ -1284,68 +1284,66 @@ namespace Barotrauma
}
}
int i = 0;
foreach (int giveExperience in giveExperiences)
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter != null && !targetCharacter.Removed)
{
targetCharacter?.Info?.GiveExperience(giveExperience);
i++;
}
}
// these effects do not need to be run clientside, as they are replicated from server to clients anyway
if (giveSkills.Any())
{
foreach ((string skillIdentifier, float amount) in giveSkills)
foreach (int giveExperience in giveExperiences)
{
Character targetCharacter = CharacterFromTarget(target);
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
continue;
targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount);
string GetRandomSkill()
{
return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom();
}
}
targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount);
string GetRandomSkill()
else
{
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)
{
IEnumerable<string> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s));
if (viableTalents.None()) { continue; }
if (giveTalentInfos.Any())
{
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)));
if (giveTalentInfo.GiveRandom)
foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos)
{
targetCharacter.GiveTalent(viableTalents.GetRandom(), true);
}
else
{
foreach (string talent in viableTalents)
IEnumerable<string> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s));
if (viableTalents.None()) { continue; }
if (giveTalentInfo.GiveRandom)
{
targetCharacter.GiveTalent(talent, true);
targetCharacter.GiveTalent(viableTalents.GetRandom(), true);
}
else
{
foreach (string talent in viableTalents)
{
targetCharacter.GiveTalent(talent, true);
}
}
}
}

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
---------------------------------------------------------------------------------------------------------
@@ -7,7 +42,7 @@ Additions and changes:
- 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.
- 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.
- 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.