Merge remote-tracking branch 'upstream/master'
This commit is contained in:
3
.github/DISCUSSION_TEMPLATE/bug-reports.yml
vendored
3
.github/DISCUSSION_TEMPLATE/bug-reports.yml
vendored
@@ -73,8 +73,7 @@ body:
|
||||
label: Version
|
||||
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
|
||||
options:
|
||||
- v1.10.7.2 (Autumn Update 2025 Hotfix 4)
|
||||
- v1.11.0.0 (Unstable)
|
||||
- v1.11.4.1 (Winter Update 2025)
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
{
|
||||
public override void DebugDraw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (Character.IsUnconscious || !Character.Enabled || !Enabled) { return; }
|
||||
if (Character.IsUnconscious || !Character.Enabled || !Enabled || Screen.Selected?.Cam is { Zoom: < 0.4f }) { return; }
|
||||
|
||||
Vector2 pos = Character.DrawPosition;
|
||||
pos.Y = -pos.Y;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
|
||||
{
|
||||
if (Character == Character.Controlled) { return; }
|
||||
if (!DebugAI) { return; }
|
||||
if (!DebugAI || Screen.Selected?.Cam is { Zoom: < 0.4f }) { return; }
|
||||
Vector2 pos = Character.DrawPosition;
|
||||
pos.Y = -pos.Y;
|
||||
Vector2 textOffset = new Vector2(-40, -160);
|
||||
|
||||
@@ -35,11 +35,7 @@ namespace Barotrauma
|
||||
CharacterStateInfo serverPos = character.MemState.Last();
|
||||
if (!character.isSynced)
|
||||
{
|
||||
SetPosition(serverPos.Position, lerp: false);
|
||||
Collider.LinearVelocity = Vector2.Zero;
|
||||
character.MemLocalState.Clear();
|
||||
character.LastNetworkUpdateID = serverPos.ID;
|
||||
character.isSynced = true;
|
||||
SyncPosition(serverPos);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -198,11 +194,7 @@ namespace Barotrauma
|
||||
|
||||
if (!character.isSynced)
|
||||
{
|
||||
SetPosition(serverPos.Position, lerp: false);
|
||||
Collider.LinearVelocity = Vector2.Zero;
|
||||
character.MemLocalState.Clear();
|
||||
character.LastNetworkUpdateID = serverPos.ID;
|
||||
character.isSynced = true;
|
||||
SyncPosition(serverPos);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -319,6 +311,15 @@ namespace Barotrauma
|
||||
if (character.MemLocalState.Count > 120) { character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120); }
|
||||
character.MemState.Clear();
|
||||
}
|
||||
|
||||
void SyncPosition(CharacterStateInfo serverPos)
|
||||
{
|
||||
SetPosition(serverPos.Position, lerp: false);
|
||||
Collider.LinearVelocity = Vector2.Zero;
|
||||
character.MemLocalState.Clear();
|
||||
character.LastNetworkUpdateID = serverPos.ID;
|
||||
character.isSynced = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -657,7 +658,7 @@ namespace Barotrauma
|
||||
|
||||
public void DebugDraw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!GameMain.DebugDraw || !character.Enabled) { return; }
|
||||
if (!GameMain.DebugDraw || !character.Enabled || Screen.Selected?.Cam is { Zoom: < 0.2f }) { return; }
|
||||
if (simplePhysicsEnabled) { return; }
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
|
||||
@@ -54,15 +54,29 @@ namespace Barotrauma
|
||||
get { return controlled; }
|
||||
set
|
||||
{
|
||||
if (controlled == value) return;
|
||||
if ((!(controlled is null)) && (!(Screen.Selected?.Cam is null)) && value is null)
|
||||
if (controlled == value && controlled == null)
|
||||
{
|
||||
Screen.Selected.Cam.TargetPos = Vector2.Zero;
|
||||
Lights.LightManager.ViewTarget = null;
|
||||
// Return early, but only when setting the controlled to null, because on controlling a character, we'll want to ensure that the target is both enabled and unfrozen.
|
||||
return;
|
||||
}
|
||||
if (controlled != value)
|
||||
{
|
||||
CharacterHealth.OpenHealthWindow = null;
|
||||
if (controlled != null && value == null)
|
||||
{
|
||||
if (Screen.Selected?.Cam is Camera camera)
|
||||
{
|
||||
camera.TargetPos = Vector2.Zero;;
|
||||
}
|
||||
Lights.LightManager.ViewTarget = null;
|
||||
}
|
||||
}
|
||||
controlled = value;
|
||||
if (controlled != null) controlled.Enabled = true;
|
||||
CharacterHealth.OpenHealthWindow = null;
|
||||
if (controlled != null)
|
||||
{
|
||||
controlled.Enabled = true;
|
||||
controlled.AnimController.Frozen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +456,7 @@ namespace Barotrauma
|
||||
{
|
||||
cam.OffsetAmount = targetOffsetAmount = maxOffset;
|
||||
}
|
||||
else if (SelectedItem != null && ViewTarget == null &&
|
||||
else if (SelectedItem != null && ViewTarget == null && !IsIncapacitated &&
|
||||
SelectedItem.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this)))
|
||||
{
|
||||
cam.OffsetAmount = targetOffsetAmount = 0.0f;
|
||||
@@ -456,7 +470,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (Lights.LightManager.ViewTarget == this)
|
||||
{
|
||||
if (GUI.PauseMenuOpen || IsUnconscious)
|
||||
if (GUI.PauseMenuOpen || IsIncapacitated)
|
||||
{
|
||||
if (deltaTime > 0.0f)
|
||||
{
|
||||
|
||||
@@ -23,8 +23,8 @@ namespace Barotrauma
|
||||
memState.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
//freeze AI characters if more than x seconds have passed since last update from the server
|
||||
|
||||
//freeze other characters (than the controlled) if more than x seconds have passed since last update from the server
|
||||
if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - NetConfig.FreezeCharacterIfPositionDataMissingDelay)
|
||||
{
|
||||
AnimController.Frozen = true;
|
||||
|
||||
@@ -104,6 +104,8 @@ namespace Barotrauma
|
||||
private GUILayoutGroup treatmentLayout;
|
||||
private GUIListBox recommendedTreatmentContainer;
|
||||
|
||||
private LocalizedString prevHighlightedAfflictionDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Timer for updating visuals (limb tints and overlays) caused by the affliction
|
||||
/// </summary>
|
||||
@@ -120,7 +122,7 @@ namespace Barotrauma
|
||||
|
||||
private float updateDisplayedAfflictionsTimer;
|
||||
private const float UpdateDisplayedAfflictionsInterval = 0.5f;
|
||||
private List<Affliction> currentDisplayedAfflictions = new List<Affliction>();
|
||||
private readonly List<Affliction> currentDisplayedAfflictions = new List<Affliction>();
|
||||
|
||||
public float DisplayedVitality, DisplayVitalityDelay;
|
||||
|
||||
@@ -662,7 +664,8 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
forceAfflictionContainerUpdate = true;
|
||||
currentDisplayedAfflictions = GetAllAfflictions(mergeSameAfflictions: true, predicate: a => a.ShouldShowIcon(Character) && a.Prefab.Icon != null);
|
||||
currentDisplayedAfflictions.Clear();
|
||||
currentDisplayedAfflictions.AddRange(GetAllAfflictions(mergeSameAfflictions: true, predicate: a => a.ShouldShowIcon(Character) && a.Prefab.Icon != null));
|
||||
currentDisplayedAfflictions.Sort((a1, a2) =>
|
||||
{
|
||||
int dmgPerSecond = Math.Sign(a1.DamagePerSecond - a2.DamagePerSecond);
|
||||
@@ -1165,7 +1168,6 @@ namespace Barotrauma
|
||||
statusIconVisibleTime[afflictionPrefab] += deltaTime;
|
||||
|
||||
Color color = GetAfflictionIconColor(afflictionPrefab, affliction);
|
||||
|
||||
var matchingIcon =
|
||||
afflictionIconContainer.GetChildByUserData(afflictionPrefab) ??
|
||||
hiddenAfflictionIconContainer.GetChildByUserData(afflictionPrefab);
|
||||
@@ -1177,9 +1179,20 @@ namespace Barotrauma
|
||||
ToolTip = $"‖color:{color.ToStringHex()}‖{affliction.Prefab.Name}‖color:end‖",
|
||||
CanBeSelected = false
|
||||
};
|
||||
new GUIImage(new RectTransform(Vector2.One, matchingIcon.RectTransform, Anchor.BottomCenter), afflictionPrefab.Icon, scaleToFit: true)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
if (afflictionPrefab.HideIconAfterDelay && statusIconVisibleTime[afflictionPrefab] > HideStatusIconDelay)
|
||||
{
|
||||
matchingIcon.RectTransform.Parent = hiddenAfflictionIconContainer.RectTransform;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (affliction.Prefab.ShowDescriptionInTooltip)
|
||||
{
|
||||
matchingIcon.ToolTip = matchingIcon.ToolTip + "\n" + affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.Self);
|
||||
matchingIcon.ToolTip = $"‖color:{color.ToStringHex()}‖{affliction.Prefab.Name}‖color:end‖" + "\n" + affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.Self);
|
||||
}
|
||||
if (affliction == pressureAffliction)
|
||||
{
|
||||
@@ -1191,14 +1204,6 @@ namespace Barotrauma
|
||||
}
|
||||
matchingIcon.ToolTip = RichString.Rich(matchingIcon.ToolTip);
|
||||
|
||||
new GUIImage(new RectTransform(Vector2.One, matchingIcon.RectTransform, Anchor.BottomCenter), afflictionPrefab.Icon, scaleToFit: true)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
if (afflictionPrefab.HideIconAfterDelay && statusIconVisibleTime[afflictionPrefab] > HideStatusIconDelay)
|
||||
{
|
||||
matchingIcon.RectTransform.Parent = hiddenAfflictionIconContainer.RectTransform;
|
||||
}
|
||||
var image = matchingIcon.GetChild<GUIImage>();
|
||||
image.Color = color;
|
||||
@@ -1574,12 +1579,14 @@ namespace Barotrauma
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
prevHighlightedAfflictionDescription = affliction.Prefab.GetDescription(
|
||||
affliction.Strength,
|
||||
Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter);
|
||||
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform),
|
||||
RichString.Rich(affliction.Prefab.GetDescription(
|
||||
affliction.Strength,
|
||||
Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter)),
|
||||
RichString.Rich(prevHighlightedAfflictionDescription),
|
||||
textAlignment: Alignment.TopLeft, wrap: true)
|
||||
{
|
||||
UserData = "description",
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
@@ -1724,7 +1731,6 @@ namespace Barotrauma
|
||||
var labelContainer = parent.GetChildByUserData("label");
|
||||
|
||||
var strengthText = labelContainer.GetChildByUserData("strength") as GUITextBlock;
|
||||
|
||||
strengthText.Text = affliction.GetStrengthText();
|
||||
|
||||
strengthText.TextColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red,
|
||||
@@ -1743,6 +1749,19 @@ namespace Barotrauma
|
||||
vitalityText.TextColor = vitalityDecrease <= 0 ? GUIStyle.Green :
|
||||
Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
|
||||
}
|
||||
|
||||
var newDescription =
|
||||
affliction.Prefab.GetDescription(
|
||||
affliction.Strength,
|
||||
Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter);
|
||||
if (newDescription != prevHighlightedAfflictionDescription)
|
||||
{
|
||||
if (parent.GetChildByUserData("description") is GUITextBlock descriptionText)
|
||||
{
|
||||
descriptionText.Text = RichString.Rich(newDescription);
|
||||
}
|
||||
prevHighlightedAfflictionDescription = newDescription;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnItemDropped(Item item, bool ignoreMousePos)
|
||||
|
||||
@@ -1,78 +1,48 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MissionPrefab : PrefabWithUintIdentifier
|
||||
internal sealed partial class MissionPrefab : PrefabWithUintIdentifier
|
||||
{
|
||||
private ImmutableArray<Sprite> portraits = new ImmutableArray<Sprite>();
|
||||
|
||||
private ImmutableArray<Sprite> portraits = [];
|
||||
public bool HasPortraits => portraits.Length > 0;
|
||||
|
||||
public Sprite Icon
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public Sprite Icon { get; private set; }
|
||||
public Color IconColor { get; private set; }
|
||||
|
||||
public Color IconColor
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool DisplayTargetHudIcons
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float HudIconMaxDistance
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Sprite HudIcon
|
||||
{
|
||||
get
|
||||
{
|
||||
return hudIcon ?? Icon;
|
||||
}
|
||||
}
|
||||
|
||||
public Color HudIconColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return hudIconColor ?? IconColor;
|
||||
}
|
||||
}
|
||||
public Color ProgressBarColor { get; private set; }
|
||||
public bool DisplayTargetHudIcons { get; private set; }
|
||||
public float HudIconMaxDistance { get; private set; }
|
||||
|
||||
private Sprite hudIcon;
|
||||
public Sprite HudIcon => hudIcon ?? Icon;
|
||||
|
||||
private Color? hudIconColor;
|
||||
public Color HudIconColor => hudIconColor ?? IconColor;
|
||||
|
||||
public Color ProgressBarColor { get; private set; }
|
||||
|
||||
private ImmutableDictionary<int, Identifier> overrideMusicOnState;
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element)
|
||||
private void ParseConfigElementClient(ContentXElement element, MissionPrefab variantOf = null)
|
||||
{
|
||||
DisplayTargetHudIcons = element.GetAttributeBool("displaytargethudicons", false);
|
||||
HudIconMaxDistance = element.GetAttributeFloat("hudiconmaxdistance", 1000.0f);
|
||||
Dictionary<int, Identifier> overrideMusic = new Dictionary<int, Identifier>();
|
||||
List<Sprite> portraits = new List<Sprite>();
|
||||
foreach (var subElement in element.Elements())
|
||||
HudIconMaxDistance = element.GetAttributeFloat("hudiconmaxdistance", 1000f);
|
||||
Dictionary<int, Identifier> overrideMusic = [];
|
||||
List<Sprite> portraits = [];
|
||||
foreach (ContentXElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "icon":
|
||||
Icon = new Sprite(subElement);
|
||||
Icon = new Sprite(subElement, GetTexturePath(subElement, variantOf));
|
||||
IconColor = subElement.GetAttributeColor("color", Color.White);
|
||||
break;
|
||||
case "hudicon":
|
||||
hudIcon = new Sprite(subElement);
|
||||
hudIcon = new Sprite(subElement, GetTexturePath(subElement, variantOf));
|
||||
hudIconColor = subElement.GetAttributeColor("color");
|
||||
break;
|
||||
case "overridemusic":
|
||||
@@ -81,7 +51,7 @@ namespace Barotrauma
|
||||
subElement.GetAttributeIdentifier("type", Identifier.Empty));
|
||||
break;
|
||||
case "portrait":
|
||||
var portrait = new Sprite(subElement, lazyLoad: true);
|
||||
Sprite portrait = new(subElement, GetTexturePath(subElement, variantOf), lazyLoad: true);
|
||||
if (portrait != null)
|
||||
{
|
||||
portraits.Add(portrait);
|
||||
@@ -89,7 +59,7 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.portraits = portraits.ToImmutableArray();
|
||||
this.portraits = [.. portraits];
|
||||
overrideMusicOnState = overrideMusic.ToImmutableDictionary();
|
||||
ProgressBarColor = element.GetAttributeColor(nameof(ProgressBarColor), GUIStyle.Blue);
|
||||
}
|
||||
@@ -109,6 +79,11 @@ namespace Barotrauma
|
||||
return portraits[Math.Abs(randomSeed) % portraits.Length];
|
||||
}
|
||||
|
||||
public string GetTexturePath(ContentXElement subElement, MissionPrefab variantOf = null)
|
||||
=> subElement.DoesAttributeReferenceFileNameAlone("texture")
|
||||
? Path.GetDirectoryName(variantOf?.ContentFile.Path ?? ContentFile.Path)
|
||||
: "";
|
||||
|
||||
partial void DisposeProjectSpecific()
|
||||
{
|
||||
Icon?.Remove();
|
||||
|
||||
@@ -3338,7 +3338,13 @@ namespace Barotrauma
|
||||
if (!CanIssueOrders) { return false; }
|
||||
var character = userData as Character;
|
||||
int priority = GetManualOrderPriority(character, order);
|
||||
SetCharacterOrder(character, order.WithManualPriority(priority).WithOrderGiver(Character.Controlled));
|
||||
Item targetEntity = null;
|
||||
if (order.MustSetTarget && order.TargetEntity == null)
|
||||
{
|
||||
var matchingItems = order.GetMatchingItems(GetTargetSubmarine(), true, interactableFor: characterContext ?? Character.Controlled);
|
||||
targetEntity = matchingItems.FirstOrDefault();
|
||||
}
|
||||
SetCharacterOrder(character, order.WithItemComponent(targetEntity).WithManualPriority(priority).WithOrderGiver(Character.Controlled));
|
||||
DisableCommandUI();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Barotrauma
|
||||
|
||||
public override void ShowStartMessage()
|
||||
{
|
||||
foreach (Mission mission in Missions.ToList())
|
||||
foreach (Mission mission in Missions.OrderBy(m => m.Prefab.IsSideObjective).ToList())
|
||||
{
|
||||
if (!mission.Prefab.ShowStartMessage) { continue; }
|
||||
new GUIMessageBox(
|
||||
|
||||
@@ -112,8 +112,16 @@ namespace Barotrauma
|
||||
|
||||
GUITextBlock.AutoScaleAndNormalize(newCampaignButton.TextBlock, loadCampaignButton.TextBlock);
|
||||
|
||||
GameMain.NetLobbyScreen.CampaignSetupUI.StartNewGame = GameMain.Client.SetupNewCampaign;
|
||||
GameMain.NetLobbyScreen.CampaignSetupUI.LoadGame = GameMain.Client.SetupLoadCampaign;
|
||||
GameMain.NetLobbyScreen.CampaignSetupUI.StartNewGame = (SubmarineInfo sub, string saveName, string mapSeed, CampaignSettings settings) =>
|
||||
{
|
||||
GameMain.NetLobbyScreen.SetAFKSelected(false);
|
||||
GameMain.Client.SetupNewCampaign(sub, saveName, mapSeed, settings);
|
||||
};
|
||||
GameMain.NetLobbyScreen.CampaignSetupUI.LoadGame = (string filePath, Option<uint> backupIndex) =>
|
||||
{
|
||||
GameMain.NetLobbyScreen.SetAFKSelected(false);
|
||||
GameMain.Client.SetupLoadCampaign(filePath, backupIndex);
|
||||
};
|
||||
}
|
||||
|
||||
partial void InitProjSpecific()
|
||||
@@ -169,6 +177,7 @@ namespace Barotrauma
|
||||
{
|
||||
StartRound = () =>
|
||||
{
|
||||
GameMain.NetLobbyScreen.SetAFKSelected(false);
|
||||
GameMain.Client.RequestStartRound();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -22,6 +22,9 @@ namespace Barotrauma
|
||||
|
||||
private GUIButton createEventButton;
|
||||
|
||||
public LevelGenerationParams BackgroundParams { get; private set; }
|
||||
public Vector2 WaterParticleOffset;
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
@@ -68,6 +71,12 @@ namespace Barotrauma
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (Level.Loaded == null)
|
||||
{
|
||||
BackgroundParams ??= LevelGenerationParams.LevelParams.Where(lp => !lp.AllowedBiomeIdentifiers.Contains("endzone")).GetRandom(Rand.RandSync.Unsynced);
|
||||
GameMain.LightManager.AmbientLight = BackgroundParams.AmbientLightColor;
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
@@ -93,6 +102,8 @@ namespace Barotrauma
|
||||
sEvent.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
BackgroundParams?.UpdateWaterParticleOffset(ref WaterParticleOffset, BackgroundParams.WaterParticleVelocity, deltaTime);
|
||||
}
|
||||
|
||||
private void GenerateOutpost(Submarine submarine)
|
||||
|
||||
@@ -173,7 +173,8 @@ namespace Barotrauma
|
||||
List<Mission> missionsToDisplay = new List<Mission>(selectedMissions.Where(m => m.Prefab.ShowInMenus));
|
||||
if (startLocation != null)
|
||||
{
|
||||
foreach (Mission mission in startLocation.SelectedMissions)
|
||||
//side objectives can't be selected manually (they're always selected), we need to add them separately from SelectedMissions
|
||||
foreach (Mission mission in startLocation.SelectedMissions.Union(startLocation.AvailableMissions.Where(m => m.Prefab.IsSideObjective)))
|
||||
{
|
||||
if (missionsToDisplay.Contains(mission)) { continue; }
|
||||
if (!mission.Prefab.ShowInMenus) { continue; }
|
||||
|
||||
@@ -119,8 +119,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!attached)
|
||||
{
|
||||
Drop(false, null);
|
||||
item.SetTransform(simPosition, 0.0f);
|
||||
item.Submarine = sub;
|
||||
item.SetTransform(simPosition, 0.0f, forceSubmarine: sub);
|
||||
AttachToWall();
|
||||
PlaySound(ActionType.OnUse, attacher);
|
||||
ApplyStatusEffects(ActionType.OnUse, (float)Timing.Step, character: attacher, user: attacher);
|
||||
@@ -142,8 +141,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
item.SetTransform(simPosition, 0.0f);
|
||||
item.Submarine = sub;
|
||||
item.SetTransform(simPosition, 0.0f, forceSubmarine: sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
_chargeSoundWindupPitchSlide = new Vector2(
|
||||
Math.Max(value.X, SoundChannel.MinFrequencyMultiplier),
|
||||
Math.Min(value.Y, SoundChannel.MaxFrequencyMultiplier));
|
||||
MathHelper.Clamp(value.X, SoundChannel.MinFrequencyMultiplier, SoundChannel.MaxFrequencyMultiplier),
|
||||
MathHelper.Clamp(value.Y, SoundChannel.MinFrequencyMultiplier, SoundChannel.MaxFrequencyMultiplier));
|
||||
}
|
||||
}
|
||||
private Vector2 _chargeSoundWindupPitchSlide;
|
||||
|
||||
@@ -409,10 +409,10 @@ namespace Barotrauma.Items.Components
|
||||
loopingSoundChannel.Looping = true;
|
||||
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
|
||||
loopingSoundChannel.Far = loopingSound.Range;
|
||||
}
|
||||
if (loopingSound.RoundSound.Stream)
|
||||
{
|
||||
loopingSoundChannel.StreamSeekPos = loopingSound.RoundSound.LastStreamSeekPos;
|
||||
if (loopingSound.RoundSound.Stream)
|
||||
{
|
||||
loopingSoundChannel.StreamSeekPos = loopingSound.RoundSound.LastStreamSeekPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,12 +434,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (FabricationRecipe fi in fabricationRecipes.Values)
|
||||
{
|
||||
RichString recipeTooltip = RichString.Rich(fi.TargetItem.Description);
|
||||
if (fi.RequiresRecipe)
|
||||
{
|
||||
recipeTooltip += "\n\n" + $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{TextManager.Get("fabricatorrequiresrecipe")}‖color:end‖";
|
||||
}
|
||||
recipeTooltip = RichString.Rich(recipeTooltip);
|
||||
RichString recipeTooltip =
|
||||
fi.RequiresRecipe ?
|
||||
RichString.Rich(fi.TargetItem.Description + "\n\n" + $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{TextManager.Get("fabricatorrequiresrecipe")}‖color:end‖") :
|
||||
RichString.Rich(fi.TargetItem.Description);
|
||||
|
||||
var frame = new GUIFrame(new RectTransform(new Point(itemList.Content.Rect.Width, (int)(40 * GUI.yScale)), itemList.Content.RectTransform), style: null)
|
||||
{
|
||||
@@ -894,10 +892,11 @@ namespace Barotrauma.Items.Components
|
||||
if (outputContainer.Inventory.IsEmpty())
|
||||
{
|
||||
var itemIcon = targetItem.TargetItem.InventoryIcon ?? targetItem.TargetItem.Sprite;
|
||||
Color iconColor = itemIcon == targetItem.TargetItem.Sprite ? targetItem.TargetItem.SpriteColor : targetItem.TargetItem.InventoryIconColor;
|
||||
itemIcon.Draw(
|
||||
spriteBatch,
|
||||
slotRect.Center.ToVector2(),
|
||||
color: Color.Lerp(targetItem.TargetItem.InventoryIconColor, Color.TransparentBlack, 0.5f),
|
||||
color: Color.Lerp(iconColor, Color.TransparentBlack, 0.5f),
|
||||
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y) * 0.9f);
|
||||
}
|
||||
}
|
||||
@@ -1132,24 +1131,27 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!selectedRecipe.TargetItem.Description.IsNullOrEmpty())
|
||||
{
|
||||
RichString richDescription = RichString.Rich(selectedRecipe.TargetItem.Description);
|
||||
|
||||
var descriptionParent = largeUI ? paddedReqFrame : paddedFrame;
|
||||
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), descriptionParent.RectTransform),
|
||||
RichString.Rich(selectedRecipe.TargetItem.Description),
|
||||
richDescription,
|
||||
font: GUIStyle.SmallFont, wrap: true);
|
||||
if (!largeUI)
|
||||
{
|
||||
description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
|
||||
}
|
||||
|
||||
while (description.Rect.Height + nameBlock.Rect.Height > descriptionParent.Rect.Height)
|
||||
while (description.Rect.Height + nameBlock.Rect.Height > descriptionParent.Rect.Height / 2)
|
||||
{
|
||||
var lines = description.WrappedText.Split('\n');
|
||||
if (lines.Count <= 1) { break; }
|
||||
var newString = string.Join('\n', lines.Take(lines.Count - 1));
|
||||
string newString = string.Join('\n', lines.Take(lines.Count - 1));
|
||||
description.Text = newString.Substring(0, newString.Length - 4) + "...";
|
||||
description.CalculateHeightFromText();
|
||||
description.ToolTip = selectedRecipe.TargetItem.Description;
|
||||
description.ToolTip = richDescription;
|
||||
}
|
||||
description.Text.RetrieveValue();
|
||||
}
|
||||
|
||||
IEnumerable<Skill> inadequateSkills = Enumerable.Empty<Skill>();
|
||||
@@ -1328,7 +1330,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
var childContainer = child.GetChild<GUILayoutGroup>();
|
||||
childContainer.GetChild<GUITextBlock>().TextColor = baseColor * (canBeFabricated ? 1.0f : 0.5f);
|
||||
childContainer.GetChild<GUIImage>().Color = recipe.TargetItem.InventoryIconColor * (canBeFabricated ? 1.0f : 0.5f);
|
||||
|
||||
GUIImage icon = childContainer.GetChild<GUIImage>();
|
||||
Color iconColor = icon.Sprite == recipe.TargetItem.Sprite ? recipe.TargetItem.SpriteColor : recipe.TargetItem.InventoryIconColor;
|
||||
childContainer.GetChild<GUIImage>().Color = iconColor * (canBeFabricated ? 1.0f : 0.5f);
|
||||
|
||||
var limitReachedText = child.FindChild(nameof(FabricationLimitReachedText));
|
||||
limitReachedText.Visible = !canBeFabricated && fabricationLimits.TryGetValue(recipe.RecipeHash, out int amount) && amount <= 0;
|
||||
|
||||
@@ -137,46 +137,30 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (var (position, emitter) in pumpOutEmitters)
|
||||
{
|
||||
if (item.CurrentHull != null && item.CurrentHull.Surface < item.Rect.Location.Y + position.Y) { continue; }
|
||||
|
||||
//only emit "pump out" particles when underwater
|
||||
Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition;
|
||||
relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? item.RotationRad : -item.RotationRad);
|
||||
float angle = -item.RotationRad;
|
||||
if (item.FlippedX)
|
||||
{
|
||||
relativeParticlePos.X = -relativeParticlePos.X;
|
||||
angle += MathHelper.Pi;
|
||||
}
|
||||
if (item.FlippedY)
|
||||
{
|
||||
relativeParticlePos.Y = -relativeParticlePos.Y;
|
||||
}
|
||||
|
||||
emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle,
|
||||
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, -currFlow / maxFlow));
|
||||
if (item.CurrentHull != null && item.CurrentHull.Surface < item.Rect.Location.Y + position.Y) { continue; }
|
||||
Emit(position, emitter);
|
||||
}
|
||||
}
|
||||
else if (currFlow > 0f)
|
||||
{
|
||||
foreach (var (position, emitter) in pumpInEmitters)
|
||||
{
|
||||
Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition;
|
||||
relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? item.RotationRad : -item.RotationRad);
|
||||
float angle = -item.RotationRad;
|
||||
if (item.FlippedX)
|
||||
{
|
||||
relativeParticlePos.X = -relativeParticlePos.X;
|
||||
angle += MathHelper.Pi;
|
||||
}
|
||||
if (item.FlippedY)
|
||||
{
|
||||
relativeParticlePos.Y = -relativeParticlePos.Y;
|
||||
}
|
||||
emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle,
|
||||
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, currFlow / maxFlow));
|
||||
Emit(position, emitter);
|
||||
}
|
||||
}
|
||||
|
||||
void Emit(Vector2 position, ParticleEmitter emitter)
|
||||
{
|
||||
Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition;
|
||||
if (item.FlippedX) { relativeParticlePos.X = -relativeParticlePos.X; }
|
||||
if (item.FlippedY) { relativeParticlePos.Y = -relativeParticlePos.Y; }
|
||||
relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, -item.RotationRad);
|
||||
float angle = -item.RotationRad;
|
||||
if (item.FlippedX) { angle += MathHelper.Pi; }
|
||||
emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle,
|
||||
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, currFlow / maxFlow), mirrorAngle: item.FlippedX ^ item.FlippedY);
|
||||
}
|
||||
}
|
||||
|
||||
private float flickerTimer;
|
||||
|
||||
@@ -1406,7 +1406,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void RegisterExplosion(Explosion explosion, Vector2 worldPosition)
|
||||
{
|
||||
if (Character.Controlled?.SelectedItem != item) { return; }
|
||||
if (Character.Controlled?.SelectedItem == null) { return; }
|
||||
if (Character.Controlled.SelectedItem != Item && !Character.Controlled.SelectedItem.linkedTo.Contains(Item)) { return; }
|
||||
if (explosion.Attack.StructureDamage <= 0 && explosion.Attack.ItemDamage <= 0 && explosion.EmpStrength <= 0) { return; }
|
||||
Vector2 transducerCenter = GetTransducerPos();
|
||||
if (Vector2.DistanceSquared(worldPosition, transducerCenter) > range * range) { return; }
|
||||
@@ -1564,7 +1565,15 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Item item in Item.SonarVisibleItems)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(item.Prefab.SonarSize > 0.0f);
|
||||
if (item.CurrentHull == null)
|
||||
|
||||
if (item.CurrentHull != null) { continue; }
|
||||
|
||||
bool isItemVisible =
|
||||
item.ParentInventory == null ||
|
||||
item.GetComponent<Holdable>() is { IsActive: true } ||
|
||||
item.Container?.GetComponent<ItemContainer>() is { HideItems: false };
|
||||
|
||||
if (isItemVisible)
|
||||
{
|
||||
float pointDist = ((item.WorldPosition - pingSource) * displayScale).LengthSquared();
|
||||
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma.Items.Components
|
||||
int connectorIntervalLeft = GetConnectorIntervalLeft(height, panel);
|
||||
int connectorIntervalRight = GetConnectorIntervalRight(height, panel);
|
||||
|
||||
foreach (Connection c in panel.Connections)
|
||||
foreach (Connection c in panel.Connections.OrderBy(static c => c.DisplayOrder))
|
||||
{
|
||||
//if dragging a wire, let the Inventory know so that the wire can be
|
||||
//dropped or dragged from the panel to the players inventory
|
||||
@@ -192,7 +192,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
DrawWire(spriteBatch, equippedWire, new Vector2(x + width / 2, y + height - 150 * GUI.Scale),
|
||||
new Vector2(x + width / 2, y + height),
|
||||
null, panel, "");
|
||||
null, panel, label: GetWireLabel(connection: null, wire: equippedWire));
|
||||
|
||||
if (DraggingConnected == equippedWire)
|
||||
{
|
||||
@@ -209,13 +209,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (wire == DraggingConnected && mouseInRect) { continue; }
|
||||
if (wire.HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; }
|
||||
|
||||
Connection recipient = wire.OtherConnection(null);
|
||||
LocalizedString label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
|
||||
if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); }
|
||||
DrawWire(spriteBatch, wire, new Vector2(x, y + height - 100 * GUI.Scale),
|
||||
new Vector2(x, y + height),
|
||||
null, panel, label);
|
||||
null, panel, label: GetWireLabel(connection: null, wire));
|
||||
x += (int)step;
|
||||
}
|
||||
|
||||
@@ -290,21 +286,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (wire.Hidden || (DraggingConnected == wire && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; }
|
||||
if (wire.HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; }
|
||||
|
||||
Connection recipient = wire.OtherConnection(this);
|
||||
LocalizedString label;
|
||||
if (wire.Item.IsLayerHidden)
|
||||
{
|
||||
label = TextManager.Get("ConnectionLocked");
|
||||
}
|
||||
else
|
||||
{
|
||||
label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
|
||||
if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); }
|
||||
}
|
||||
|
||||
DrawWire(spriteBatch, wire, position, wirePosition, equippedWire, panel, label);
|
||||
|
||||
DrawWire(spriteBatch, wire, position, wirePosition, equippedWire, panel, label: GetWireLabel(connection: this, wire: wire));
|
||||
wirePosition.Y += wireInterval;
|
||||
}
|
||||
|
||||
@@ -362,6 +344,22 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private static LocalizedString GetWireLabel(Connection connection, Wire wire)
|
||||
{
|
||||
Connection recipient = wire.OtherConnection(connection);
|
||||
LocalizedString label;
|
||||
if (wire.Item.IsLayerHidden)
|
||||
{
|
||||
label = TextManager.Get("ConnectionLocked");
|
||||
}
|
||||
else
|
||||
{
|
||||
label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
|
||||
if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); }
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
public void Flash(Color? color = null, float flashDuration = 1.5f)
|
||||
{
|
||||
FlashTimer = flashDuration;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ConnectionSelectorComponent : ItemComponent
|
||||
{
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
SelectedConnection = msg.ReadRangedInteger(0, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -733,7 +733,7 @@ namespace Barotrauma
|
||||
{
|
||||
updateableComponents.Add(ic);
|
||||
}
|
||||
isActive = true;
|
||||
IsActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2170,7 +2170,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
isActive = true;
|
||||
IsActive = true;
|
||||
|
||||
if (positionBuffer.Count > 0)
|
||||
{
|
||||
@@ -2515,8 +2515,7 @@ namespace Barotrauma
|
||||
{
|
||||
inventory.TryPutItem(item, user: null, allowedSlots: item.AllowedSlots, createNetworkEvent: false);
|
||||
}
|
||||
item.SetTransform(inventory.Owner.SimPosition, 0.0f);
|
||||
item.Submarine = inventory.Owner.Submarine;
|
||||
item.SetTransform(inventory.Owner.SimPosition, 0.0f, forceSubmarine: inventory.Owner.Submarine);
|
||||
if (inventory.Owner is Character { Enabled: false } && item.body != null)
|
||||
{
|
||||
item.body.Enabled = false;
|
||||
|
||||
@@ -230,7 +230,12 @@ namespace Barotrauma
|
||||
if (!primaryMouseButtonHeld && !secondaryMouseButtonHeld && !doubleClicked && !secondaryDoubleClicked) { return; }
|
||||
|
||||
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
Hull hull = FindHull(position);
|
||||
Hull hull =
|
||||
Screen.Selected is { IsEditor: true } ?
|
||||
//use the unoptimized version in the editor to make sure newly placed hulls are found
|
||||
//(FindHull uses the "EntityGrid" which is generated after loading the sub)
|
||||
FindHullUnoptimized(position) :
|
||||
FindHull(position);
|
||||
|
||||
if (hull == null || hull.IdFreed) { return; }
|
||||
if (EditWater)
|
||||
@@ -361,7 +366,7 @@ namespace Barotrauma
|
||||
new Rectangle(drawRect.X, -drawRect.Y, rect.Width, rect.Height),
|
||||
GUIStyle.Red * ((100.0f - OxygenPercentage) / 400.0f) * alpha, true, 0, (int)Math.Max(MathF.Ceiling(1.5f / Screen.Selected.Cam.Zoom), 1.0f));
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
if (GameMain.DebugDraw && Screen.Selected?.Cam is { Zoom: > 0.5f })
|
||||
{
|
||||
GUIStyle.SmallFont.DrawString(spriteBatch, "Pressure: " + ((int)pressure - rect.Y).ToString() +
|
||||
" - Oxygen: " + ((int)OxygenPercentage), new Vector2(drawRect.X + 5, -drawRect.Y + 5), Color.White);
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
namespace Barotrauma;
|
||||
|
||||
internal partial class LevelGenerationParams : PrefabWithUintIdentifier
|
||||
{
|
||||
/// <remarks>Doesn't call SpriteBatch.Begin and SpriteBatch.End; They must be called manually.</remarks>
|
||||
public void DrawBackgrounds(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
if (BackgroundTopSprite == null) { return; }
|
||||
|
||||
Vector2 backgroundPos = cam.WorldViewCenter.FlipY() * 0.05f;
|
||||
int backgroundSize = (int)BackgroundTopSprite.size.Y;
|
||||
if (backgroundPos.Y >= backgroundSize) { return; }
|
||||
|
||||
if (backgroundPos.Y < 0f)
|
||||
{
|
||||
BackgroundTopSprite.SourceRect = new Rectangle((int)backgroundPos.X, (int)backgroundPos.Y, backgroundSize, (int)Math.Min(-backgroundPos.Y, backgroundSize));
|
||||
BackgroundTopSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, Math.Min(-backgroundPos.Y, GameMain.GraphicsHeight)),
|
||||
color: BackgroundTextureColor);
|
||||
}
|
||||
if (-backgroundPos.Y < GameMain.GraphicsHeight && BackgroundSprite != null)
|
||||
{
|
||||
BackgroundSprite.SourceRect = new Rectangle((int)backgroundPos.X, (int)Math.Max(backgroundPos.Y, 0), backgroundSize, backgroundSize);
|
||||
BackgroundSprite.DrawTiled(spriteBatch, (backgroundPos.Y < 0f) ? new Vector2(0f, (int)-backgroundPos.Y) : Vector2.Zero,
|
||||
new Vector2(GameMain.GraphicsWidth, (int)Math.Min(Math.Ceiling(backgroundSize - backgroundPos.Y), backgroundSize)),
|
||||
color: BackgroundTextureColor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <remarks>Doesn't call SpriteBatch.Begin and SpriteBatch.End; They must be called manually.</remarks>
|
||||
public void DrawWaterParticles(SpriteBatch spriteBatch, Camera cam, Vector2 offset)
|
||||
{
|
||||
if (WaterParticles == null || cam.Zoom <= 0.05f) { return; }
|
||||
|
||||
float textureScale = WaterParticleScale;
|
||||
Vector2 textureSize = new Vector2(WaterParticles.Texture.Width, WaterParticles.Texture.Height);
|
||||
Vector2 origin = new Vector2(cam.WorldView.X, -cam.WorldView.Y);
|
||||
offset -= origin;
|
||||
|
||||
// Draw 4 layers of particles.
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float scale = 1f - i * 0.2f;
|
||||
float alpha = MathUtils.InverseLerp(0.05f, 0.1f, cam.Zoom * scale);
|
||||
if (alpha == 0f) { continue; }
|
||||
|
||||
Vector2 newOffset = offset * scale;
|
||||
newOffset += cam.WorldView.Size.ToVector2() * (1f - scale) * 0.5f;
|
||||
newOffset -= new Vector2(256f * i);
|
||||
|
||||
float newTextureScale = scale * textureScale;
|
||||
|
||||
Vector2 newSize = textureSize * scale;
|
||||
while (newOffset.X <= -newSize.X) { newOffset.X += newSize.X; }
|
||||
while (newOffset.X > 0f) { newOffset.X -= newSize.X; }
|
||||
while (newOffset.Y <= -newSize.Y) { newOffset.Y += newSize.Y; }
|
||||
while (newOffset.Y > 0f) { newOffset.Y -= newSize.Y; }
|
||||
|
||||
WaterParticles.DrawTiled(spriteBatch, origin + newOffset, cam.WorldView.Size.ToVector2() - newOffset,
|
||||
color: WaterParticleColor * alpha, textureScale: new Vector2(newTextureScale));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateWaterParticleOffset(ref Vector2 offset, Vector2 velocity, float deltaTime)
|
||||
{
|
||||
if (WaterParticles == null) { return; }
|
||||
Vector2 waterTextureSize = WaterParticles.size * WaterParticleScale;
|
||||
offset += velocity.FlipY() * WaterParticleScale * deltaTime;
|
||||
offset.X %= waterTextureSize.X;
|
||||
offset.Y %= waterTextureSize.Y;
|
||||
}
|
||||
}
|
||||
@@ -234,15 +234,7 @@ namespace Barotrauma
|
||||
|
||||
WaterRenderer.Instance?.ScrollWater(waterParticleVelocity, deltaTime);
|
||||
|
||||
if (level.GenerationParams.WaterParticles != null)
|
||||
{
|
||||
Vector2 waterTextureSize = level.GenerationParams.WaterParticles.size * level.GenerationParams.WaterParticleScale;
|
||||
waterParticleOffset += new Vector2(waterParticleVelocity.X, -waterParticleVelocity.Y) * level.GenerationParams.WaterParticleScale * deltaTime;
|
||||
while (waterParticleOffset.X <= -waterTextureSize.X) { waterParticleOffset.X += waterTextureSize.X; }
|
||||
while (waterParticleOffset.X >= waterTextureSize.X){ waterParticleOffset.X -= waterTextureSize.X; }
|
||||
while (waterParticleOffset.Y <= -waterTextureSize.Y) { waterParticleOffset.Y += waterTextureSize.Y; }
|
||||
while (waterParticleOffset.Y >= waterTextureSize.Y) { waterParticleOffset.Y -= waterTextureSize.Y; }
|
||||
}
|
||||
level.GenerationParams.UpdateWaterParticleOffset(ref waterParticleOffset, waterParticleVelocity, deltaTime);
|
||||
}
|
||||
|
||||
public static VertexPositionColorTexture[] GetColoredVertices(VertexPositionTexture[] vertices, Color color)
|
||||
@@ -274,36 +266,7 @@ namespace Barotrauma
|
||||
ParticleManager particleManager = null)
|
||||
{
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap);
|
||||
|
||||
Vector2 backgroundPos = cam.WorldViewCenter;
|
||||
|
||||
backgroundPos.Y = -backgroundPos.Y;
|
||||
backgroundPos *= 0.05f;
|
||||
|
||||
if (level.GenerationParams.BackgroundTopSprite != null)
|
||||
{
|
||||
int backgroundSize = (int)level.GenerationParams.BackgroundTopSprite.size.Y;
|
||||
if (backgroundPos.Y < backgroundSize)
|
||||
{
|
||||
if (backgroundPos.Y < 0)
|
||||
{
|
||||
var backgroundTop = level.GenerationParams.BackgroundTopSprite;
|
||||
backgroundTop.SourceRect = new Rectangle((int)backgroundPos.X, (int)backgroundPos.Y, backgroundSize, (int)Math.Min(-backgroundPos.Y, backgroundSize));
|
||||
backgroundTop.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, Math.Min(-backgroundPos.Y, GameMain.GraphicsHeight)),
|
||||
color: level.BackgroundTextureColor);
|
||||
}
|
||||
if (-backgroundPos.Y < GameMain.GraphicsHeight && level.GenerationParams.BackgroundSprite != null)
|
||||
{
|
||||
var background = level.GenerationParams.BackgroundSprite;
|
||||
background.SourceRect = new Rectangle((int)backgroundPos.X, (int)Math.Max(backgroundPos.Y, 0), backgroundSize, backgroundSize);
|
||||
background.DrawTiled(spriteBatch,
|
||||
(backgroundPos.Y < 0) ? new Vector2(0.0f, (int)-backgroundPos.Y) : Vector2.Zero,
|
||||
new Vector2(GameMain.GraphicsWidth, (int)Math.Min(Math.Ceiling(backgroundSize - backgroundPos.Y), backgroundSize)),
|
||||
color: level.BackgroundTextureColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
level.GenerationParams.DrawBackgrounds(spriteBatch, cam);
|
||||
spriteBatch.End();
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred,
|
||||
@@ -317,42 +280,7 @@ namespace Barotrauma
|
||||
backgroundCreatureManager?.Draw(spriteBatch, cam);
|
||||
}
|
||||
|
||||
if (level.GenerationParams.WaterParticles != null && cam.Zoom > 0.05f)
|
||||
{
|
||||
float textureScale = level.GenerationParams.WaterParticleScale;
|
||||
|
||||
Rectangle srcRect = new Rectangle(0, 0, 2048, 2048);
|
||||
Vector2 origin = new Vector2(cam.WorldView.X, -cam.WorldView.Y);
|
||||
Vector2 offset = -origin + waterParticleOffset;
|
||||
while (offset.X <= -srcRect.Width * textureScale) offset.X += srcRect.Width * textureScale;
|
||||
while (offset.X > 0.0f) offset.X -= srcRect.Width * textureScale;
|
||||
while (offset.Y <= -srcRect.Height * textureScale) offset.Y += srcRect.Height * textureScale;
|
||||
while (offset.Y > 0.0f) offset.Y -= srcRect.Height * textureScale;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float scale = (1.0f - i * 0.2f);
|
||||
|
||||
//alpha goes from 1.0 to 0.0 when scale is in the range of 0.1 - 0.05
|
||||
float alpha = (cam.Zoom * scale) < 0.1f ? (cam.Zoom * scale - 0.05f) * 20.0f : 1.0f;
|
||||
if (alpha <= 0.0f) continue;
|
||||
|
||||
Vector2 offsetS = offset * scale
|
||||
+ new Vector2(cam.WorldView.Width, cam.WorldView.Height) * (1.0f - scale) * 0.5f
|
||||
- new Vector2(256.0f * i);
|
||||
|
||||
float texScale = scale * textureScale;
|
||||
|
||||
while (offsetS.X <= -srcRect.Width * texScale) offsetS.X += srcRect.Width * texScale;
|
||||
while (offsetS.X > 0.0f) offsetS.X -= srcRect.Width * texScale;
|
||||
while (offsetS.Y <= -srcRect.Height * texScale) offsetS.Y += srcRect.Height * texScale;
|
||||
while (offsetS.Y > 0.0f) offsetS.Y -= srcRect.Height * texScale;
|
||||
|
||||
level.GenerationParams.WaterParticles.DrawTiled(
|
||||
spriteBatch, origin + offsetS,
|
||||
new Vector2(cam.WorldView.Width - offsetS.X, cam.WorldView.Height - offsetS.Y),
|
||||
color: level.GenerationParams.WaterParticleColor * alpha, textureScale: new Vector2(texScale));
|
||||
}
|
||||
}
|
||||
level.GenerationParams.DrawWaterParticles(spriteBatch, cam, waterParticleOffset);
|
||||
|
||||
GameMain.ParticleManager?.Draw(spriteBatch, inWater: true, inSub: false, ParticleBlendState.AlphaBlend, background: true);
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Barotrauma.Lights
|
||||
}
|
||||
|
||||
private float pulseAmount;
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, IsPropertySaveable.Yes, description: "How much light pulsates (in Hz). 0 = not at all, 1 = alternates between full brightness and off.")]
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, IsPropertySaveable.Yes, description: "How much light pulsates. 0 = not at all, 1 = alternates between full brightness and off.")]
|
||||
public float PulseAmount
|
||||
{
|
||||
get { return pulseAmount; }
|
||||
|
||||
@@ -397,7 +397,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!description.IsNullOrEmpty())
|
||||
{
|
||||
CreateTextWithIcon(description, locationTypeToDisplay.Sprite);
|
||||
CreateTextWithIcon(description, iconSprite: locationTypeToDisplay.Sprite);
|
||||
}
|
||||
|
||||
int highestSubTier = location.HighestSubmarineTierAvailable();
|
||||
@@ -417,32 +417,29 @@ namespace Barotrauma
|
||||
}
|
||||
if (highestSubTier > 0)
|
||||
{
|
||||
CreateTextWithIcon(TextManager.GetWithVariable("advancedsub.all", "[tiernumber]", highestSubTier.ToString()), icon: null, style: "LocationOverlaySubmarineIcon");
|
||||
CreateTextWithIcon(TextManager.GetWithVariable("advancedsub.all", "[tiernumber]", highestSubTier.ToString()), iconStyle: "LocationOverlaySubmarineIcon");
|
||||
}
|
||||
if (overrideTiers != null)
|
||||
{
|
||||
foreach (var (subClass, tier) in overrideTiers)
|
||||
{
|
||||
CreateTextWithIcon(TextManager.GetWithVariable($"advancedsub.{subClass}", "[tiernumber]", tier.ToString()), icon: null, style: "LocationOverlaySubmarineIcon");
|
||||
CreateTextWithIcon(TextManager.GetWithVariable($"advancedsub.{subClass}", "[tiernumber]", tier.ToString()), iconStyle: "LocationOverlaySubmarineIcon");
|
||||
}
|
||||
}
|
||||
|
||||
CreateSpacing(10);
|
||||
|
||||
void CreateTextWithIcon(LocalizedString text, Sprite icon, string style = null)
|
||||
void CreateTextWithIcon(LocalizedString text, Sprite iconSprite = null, string iconStyle = null)
|
||||
{
|
||||
var textHolder = new GUILayoutGroup(new RectTransform(new Point(content.Rect.Width, (int)GUIStyle.Font.MeasureString(text).Y), content.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
CanBeFocused = true
|
||||
};
|
||||
var guiIcon =
|
||||
style == null ?
|
||||
new GUIImage(new RectTransform(Vector2.One * 1.25f, textHolder.RectTransform, scaleBasis: ScaleBasis.BothHeight), icon) :
|
||||
new GUIImage(new RectTransform(Vector2.One * 1.25f, textHolder.RectTransform, scaleBasis: ScaleBasis.BothHeight), style);
|
||||
var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.9f, 1.0f), textHolder.RectTransform), text);
|
||||
textBlock.RectTransform.MinSize = new Point((int)textBlock.TextSize.X, 0);
|
||||
textHolder.RectTransform.MinSize = new Point((int)textBlock.TextSize.X + guiIcon.Rect.Width, 0);
|
||||
GUITextBlock textBox = new(new RectTransform(new Vector2(0.9f, 0f), content.RectTransform), text, wrap: true);
|
||||
|
||||
if (iconSprite == null && iconStyle == null) { return; }
|
||||
float iconSize = GUIStyle.Font.LineHeight * 1.5f;
|
||||
|
||||
textBox.Padding = textBox.Padding with { X = iconSize + 5f };
|
||||
RectTransform iconTF = new(new Point((int)iconSize), textBox.RectTransform, Anchor.CenterLeft) { IsFixedSize = true };
|
||||
if (iconSprite != null) { new GUIImage(iconTF, iconSprite, scaleToFit: true); }
|
||||
if (iconStyle != null) { new GUIImage(iconTF, iconStyle, scaleToFit: true); }
|
||||
}
|
||||
|
||||
void CreateSpacing(int height)
|
||||
@@ -486,10 +483,11 @@ namespace Barotrauma
|
||||
CreateSpacing(20);
|
||||
}
|
||||
|
||||
locationInfoOverlay.RectTransform.NonScaledSize =
|
||||
new Point(
|
||||
Math.Max(locationInfoOverlay.Rect.Width, (int)(content.Children.Max(c => c is GUITextBlock textBlock ? textBlock.TextSize.X : c.RectTransform.MinSize.X) * 1.2f)),
|
||||
(int)(content.Children.Sum(c => c.Rect.Height) / content.RectTransform.RelativeSize.Y));
|
||||
float childWidth = Math.Max(locationInfoOverlay.Rect.Width, content.Children.Max(c => c is GUITextBlock textBlock ? textBlock.TextSize.X + textBlock.Padding.X + textBlock.Padding.Z : c.RectTransform.MinSize.X));
|
||||
childWidth = Math.Max(locationInfoOverlay.Rect.Width, childWidth);
|
||||
float childHeight = content.Children.Sum(c => c.Rect.Height);
|
||||
Vector2 childSize = new Vector2(childWidth, childHeight) / content.RectTransform.RelativeSize;
|
||||
locationInfoOverlay.RectTransform.NonScaledSize = childSize.ToPoint();
|
||||
}
|
||||
|
||||
partial void ClearAnimQueue()
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Barotrauma
|
||||
|
||||
//which entities have been selected for editing
|
||||
public static HashSet<MapEntity> SelectedList { get; private set; } = new HashSet<MapEntity>();
|
||||
private static List<Rectangle> oldRects = new List<Rectangle>();
|
||||
private static Vector2 entityMovementNudge;
|
||||
|
||||
public static List<MapEntity> CopiedList = new List<MapEntity>();
|
||||
|
||||
@@ -267,7 +269,7 @@ namespace Barotrauma
|
||||
i++;
|
||||
}
|
||||
highlightedEntities.Insert(i, e);
|
||||
if (i == 0) highLightedEntity = e;
|
||||
if (i == 0) { highLightedEntity = e; }
|
||||
}
|
||||
}
|
||||
UpdateHighlighting(highlightedEntities);
|
||||
@@ -278,10 +280,19 @@ namespace Barotrauma
|
||||
|
||||
if (GUI.KeyboardDispatcher.Subscriber == null)
|
||||
{
|
||||
Vector2 nudge = GetNudgeAmount();
|
||||
if (nudge != Vector2.Zero)
|
||||
Vector2 previousNudge = entityMovementNudge;
|
||||
entityMovementNudge = GetNudgeAmount();
|
||||
if (entityMovementNudge != Vector2.Zero)
|
||||
{
|
||||
foreach (MapEntity entityToNudge in SelectedList) { entityToNudge.Move(nudge); }
|
||||
if (previousNudge == Vector2.Zero)
|
||||
{
|
||||
oldRects = SelectedList.Select(entity => entity.Rect).ToList();
|
||||
}
|
||||
foreach (MapEntity entityToNudge in SelectedList) { entityToNudge.Move(entityMovementNudge); }
|
||||
}
|
||||
else if (previousNudge != Vector2.Zero)
|
||||
{
|
||||
SubEditorScreen.StoreCommand(new TransformCommand(new List<MapEntity>(SelectedList), SelectedList.Select(entity => entity.Rect).ToList(), oldRects, resized: false));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -352,7 +363,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
SubEditorScreen.StoreCommand(new TransformCommand(new List<MapEntity>(SelectedList),SelectedList.Select(entity => entity.Rect).ToList(), oldRects, false));
|
||||
SubEditorScreen.StoreCommand(new TransformCommand(new List<MapEntity>(SelectedList), SelectedList.Select(entity => entity.Rect).ToList(), oldRects, resized: false));
|
||||
if (deposited.Any() && deposited.Any(entity => entity is Item))
|
||||
{
|
||||
var depositedItems = deposited.Where(entity => entity is Item).Cast<Item>().ToList();
|
||||
@@ -508,7 +519,7 @@ namespace Barotrauma
|
||||
selectionSize = Vector2.Zero;
|
||||
selectionPos = Vector2.Zero;
|
||||
}
|
||||
|
||||
|
||||
public static Vector2 GetNudgeAmount(bool doHold = true)
|
||||
{
|
||||
Vector2 nudgeAmount = Vector2.Zero;
|
||||
@@ -532,10 +543,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayerInput.KeyHit(Keys.Up)) nudgeAmount.Y = 1f;
|
||||
if (PlayerInput.KeyHit(Keys.Down)) nudgeAmount.Y = -1f;
|
||||
if (PlayerInput.KeyHit(Keys.Left)) nudgeAmount.X = -1f;
|
||||
if (PlayerInput.KeyHit(Keys.Right)) nudgeAmount.X = 1f;
|
||||
if (PlayerInput.KeyHit(Keys.Up)) { nudgeAmount.Y = 1f; }
|
||||
if (PlayerInput.KeyHit(Keys.Down)) { nudgeAmount.Y = -1f; }
|
||||
if (PlayerInput.KeyHit(Keys.Left)) { nudgeAmount.X = -1f;}
|
||||
if (PlayerInput.KeyHit(Keys.Right)) { nudgeAmount.X = 1f; }
|
||||
|
||||
return nudgeAmount;
|
||||
}
|
||||
|
||||
@@ -172,16 +172,19 @@ namespace Barotrauma
|
||||
GUI.DrawRectangle(spriteBatch, drawPos - ExitPointSize.ToVector2() / 2, ExitPointSize.ToVector2(), Color.Cyan, thickness: 5);
|
||||
}
|
||||
|
||||
GUIStyle.SmallFont.DrawString(spriteBatch,
|
||||
ID.ToString(),
|
||||
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30),
|
||||
color);
|
||||
if (Tunnel?.Type != null)
|
||||
if (Screen.Selected?.Cam is { Zoom: > 0.4f })
|
||||
{
|
||||
GUIStyle.SmallFont.DrawString(spriteBatch,
|
||||
Tunnel.Type.ToString(),
|
||||
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 45),
|
||||
color);
|
||||
ID.ToString(),
|
||||
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30),
|
||||
color);
|
||||
if (Tunnel?.Type != null)
|
||||
{
|
||||
GUIStyle.SmallFont.DrawString(spriteBatch,
|
||||
Tunnel.Type.ToString(),
|
||||
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 45),
|
||||
color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -239,6 +239,13 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//if we're still downloading mods, we're not ready to receive the campaign save
|
||||
if (fileType == (byte)FileTransferType.CampaignSave && Screen.Selected is ModDownloadScreen)
|
||||
{
|
||||
GameMain.Client.CancelFileTransfer(transferId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidateInitialData(fileType, fileName, fileSize, out string errorMsg))
|
||||
{
|
||||
|
||||
@@ -562,6 +562,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
SendLobbyUpdate();
|
||||
}
|
||||
if (Timing.TotalTime > LastMissingCampaignSubRequestTime)
|
||||
{
|
||||
TryRequestMissingCampaignSubs();
|
||||
LastMissingCampaignSubRequestTime = Timing.TotalTime + MissingCampaignSubRequestInterval;
|
||||
}
|
||||
}
|
||||
|
||||
if (ServerSettings.VoiceChatEnabled)
|
||||
@@ -2284,13 +2289,7 @@ namespace Barotrauma.Networking
|
||||
if (GameMain.Client.IsServerOwner) { RequestSelectMode(modeIndex); }
|
||||
}
|
||||
|
||||
if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
|
||||
{
|
||||
foreach (SubmarineInfo sub in ServerSubmarines.Where(s => !ServerSettings.HiddenSubs.Contains(s.Name)))
|
||||
{
|
||||
GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, NetLobbyScreen.SubmarineDeliveryData.Campaign);
|
||||
}
|
||||
}
|
||||
TryRequestMissingCampaignSubs();
|
||||
|
||||
GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating);
|
||||
GameMain.NetLobbyScreen.SetAllowAFK(allowAFK);
|
||||
@@ -2646,6 +2645,21 @@ namespace Barotrauma.Networking
|
||||
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
private double LastMissingCampaignSubRequestTime;
|
||||
|
||||
const double MissingCampaignSubRequestInterval = 10.0f;
|
||||
|
||||
private void TryRequestMissingCampaignSubs()
|
||||
{
|
||||
if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
|
||||
{
|
||||
foreach (SubmarineInfo sub in ServerSubmarines.Where(s => !ServerSettings.HiddenSubs.Contains(s.Name)))
|
||||
{
|
||||
GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, NetLobbyScreen.SubmarineDeliveryData.Campaign);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestFile(FileTransferType fileType, string file, string fileHash)
|
||||
{
|
||||
DebugConsole.Log(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
@@ -91,7 +91,7 @@ namespace Barotrauma.Networking
|
||||
});
|
||||
initializationStep = ConnectionInitialization.AuthInfoAndVersion;
|
||||
|
||||
timeout = NetworkConnection.TimeoutThreshold;
|
||||
timeout = NetworkConnection.TimeoutThresholdNotInGame;
|
||||
heartbeatTimer = 1.0;
|
||||
|
||||
isActive = true;
|
||||
@@ -139,7 +139,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
timeout = Screen.Selected == GameMain.GameScreen
|
||||
? NetworkConnection.TimeoutThresholdInGame
|
||||
: NetworkConnection.TimeoutThreshold;
|
||||
: NetworkConnection.TimeoutThresholdNotInGame;
|
||||
|
||||
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
|
||||
@@ -354,9 +354,11 @@ namespace Barotrauma
|
||||
{
|
||||
List<Mission> availableMissions = currentDisplayLocation.GetMissionsInConnection(connection).Where(m => m.Prefab.ShowInMenus || GameMain.DebugDraw).ToList();
|
||||
|
||||
if (!availableMissions.Any()) { availableMissions.Insert(0, null); }
|
||||
if (availableMissions.None()) { availableMissions.Insert(0, null); }
|
||||
|
||||
availableMissions.AddRange(location.AvailableMissions.Where(m => m.Locations[0] == m.Locations[1]));
|
||||
//show side objectives last
|
||||
availableMissions.Sort((m1, m2) => (m1?.Prefab.IsSideObjective ?? false).CompareTo(m2?.Prefab.IsSideObjective ?? false));
|
||||
|
||||
missionList.Content.ClearChildren();
|
||||
|
||||
@@ -390,6 +392,10 @@ namespace Barotrauma
|
||||
};
|
||||
|
||||
LocalizedString missionName = mission?.Name ?? TextManager.Get("NoMission");
|
||||
if (mission is { Prefab.IsSideObjective: true })
|
||||
{
|
||||
missionName = TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), missionName);
|
||||
}
|
||||
if (GameMain.DebugDraw && mission != null)
|
||||
{
|
||||
if (!mission.Prefab.ShowInMenus) { missionName = $"[HIDDEN] {missionName}"; }
|
||||
@@ -406,7 +412,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
GUITickBox tickBox = null;
|
||||
if (!isMissionInNextLocation && mission.Prefab.ShowInMenus)
|
||||
if (!isMissionInNextLocation && mission.Prefab.ShowInMenus && !mission.Prefab.IsSideObjective)
|
||||
{
|
||||
tickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, missionNameBlock.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionNameBlock.Padding.X, 0) }, label: string.Empty)
|
||||
{
|
||||
@@ -539,7 +545,7 @@ namespace Barotrauma
|
||||
int missionCount = 0;
|
||||
if (GameMain.GameSession != null && Campaign.Map?.CurrentLocation?.SelectedMissions != null)
|
||||
{
|
||||
missionCount = Campaign.Map.CurrentLocation.SelectedMissions.Count(m => m.Locations.Contains(location) && !GameMain.GameSession.Missions.Contains(m));
|
||||
missionCount = Campaign.Map.CurrentLocation.SelectedMissions.Count(m => m.Locations.Contains(location) && !GameMain.GameSession.Missions.Contains(m) && !m.Prefab.IsSideObjective);
|
||||
}
|
||||
return TextManager.AddPunctuation(':', TextManager.Get("Missions"), $"{missionCount}/{Campaign.Settings.TotalMaxMissionCount}");
|
||||
}
|
||||
@@ -551,9 +557,9 @@ namespace Barotrauma
|
||||
OnClicked = (GUIButton btn, object obj) =>
|
||||
{
|
||||
if (missionList.Content.FindChild(c => c is GUITickBox tickBox && tickBox.Selected, recursive: true) == null &&
|
||||
missionList.Content.Children.Any(c => c.UserData is Mission { Prefab.ShowInMenus: true } mission && mission.Locations.Contains(Campaign?.Map?.CurrentLocation)))
|
||||
missionList.Content.Children.Any(c => c.UserData is Mission { Prefab.ShowInMenus: true, Prefab.IsSideObjective: false } mission && mission.Locations.Contains(Campaign?.Map?.CurrentLocation)))
|
||||
{
|
||||
var noMissionVerification = new GUIMessageBox(string.Empty, TextManager.Get("nomissionprompt"), new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
|
||||
var noMissionVerification = new GUIMessageBox(string.Empty, TextManager.Get("nomissionprompt"), [TextManager.Get("yes"), TextManager.Get("no")]);
|
||||
noMissionVerification.Buttons[0].OnClicked = (btn, userdata) =>
|
||||
{
|
||||
StartRound?.Invoke();
|
||||
@@ -648,7 +654,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateMaxMissions(Location location)
|
||||
{
|
||||
hasMaxMissions = Campaign.NumberOfMissionsAtLocation(location) >= Campaign.Settings.TotalMaxMissionCount;
|
||||
hasMaxMissions = Campaign.NumberOfSelectableMissionsAtLocation(location) >= Campaign.Settings.TotalMaxMissionCount;
|
||||
}
|
||||
|
||||
public readonly struct PlayerBalanceElement
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Transactions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -349,14 +348,25 @@ namespace Barotrauma
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
graphics.SetRenderTarget(renderTargetBackground);
|
||||
if (Level.Loaded == null)
|
||||
if (Level.Loaded != null)
|
||||
{
|
||||
graphics.Clear(new Color(11, 18, 26, 255));
|
||||
Level.Loaded.DrawBack(graphics, spriteBatch, cam);
|
||||
}
|
||||
else if (GameMain.GameSession.GameMode is TestGameMode testMode)
|
||||
{
|
||||
graphics.Clear(testMode.BackgroundParams.BackgroundColor);
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap);
|
||||
testMode.BackgroundParams.DrawBackgrounds(spriteBatch, cam);
|
||||
spriteBatch.End();
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null, cam.Transform);
|
||||
testMode.BackgroundParams.DrawWaterParticles(spriteBatch, cam, testMode.WaterParticleOffset);
|
||||
spriteBatch.End();
|
||||
}
|
||||
else
|
||||
{
|
||||
//graphics.Clear(new Color(255, 255, 255, 255));
|
||||
Level.Loaded.DrawBack(graphics, spriteBatch, cam);
|
||||
graphics.Clear(new Color(11, 18, 26, 255));
|
||||
}
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
|
||||
|
||||
@@ -382,6 +382,10 @@ namespace Barotrauma
|
||||
GameMain.LuaCs.CheckInitialize();
|
||||
}
|
||||
}
|
||||
else if (GameMain.Client.FileReceiver.ActiveTransfers.None())
|
||||
{
|
||||
GameMain.Client.RequestFile(FileTransferType.Mod, currentDownload.Name, currentDownload.Hash.StringRepresentation);
|
||||
}
|
||||
}
|
||||
|
||||
public void CurrentDownloadFinished(FileReceiver.FileTransferIn transfer)
|
||||
|
||||
@@ -2221,11 +2221,7 @@ namespace Barotrauma
|
||||
if (GameMain.Client == null) { return true; }
|
||||
|
||||
//the player presumably no longer wants to be afk if they clicked the start button
|
||||
if (afkBox.Selected)
|
||||
{
|
||||
afkBox.Flash(GUIStyle.Green);
|
||||
}
|
||||
afkBox.Selected = false;
|
||||
SetAFKSelected(false);
|
||||
|
||||
if (CampaignSetupFrame.Visible && CampaignSetupUI != null)
|
||||
{
|
||||
@@ -2357,7 +2353,7 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
afkBox.Visible = !GameMain.Client.IsServerOwner && GameMain.Client.ServerSettings.AllowAFK;
|
||||
afkBox.Visible = GameMain.Client.IsServerOwner || GameMain.Client.ServerSettings.AllowAFK;
|
||||
GameMain.Client.Voting.ResetVotes(GameMain.Client.ConnectedClients);
|
||||
joinOnGoingRoundButton.OnClicked = (btn, userdata) =>
|
||||
{
|
||||
@@ -3082,6 +3078,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAFKSelected(bool selected)
|
||||
{
|
||||
if (afkBox.Selected != selected)
|
||||
{
|
||||
afkBox.Flash(GUIStyle.Green);
|
||||
afkBox.Selected = selected;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAutoRestart(bool enabled, float timer = 0.0f)
|
||||
{
|
||||
autoRestartBox.Selected = enabled;
|
||||
@@ -4994,7 +4999,7 @@ namespace Barotrauma
|
||||
};
|
||||
|
||||
micIcon = new GUIImage(new RectTransform(new Vector2(0.05f, 1.0f), chatRow.RectTransform), style: "GUIMicrophoneUnavailable");
|
||||
chatInput.Select();
|
||||
chatInput.Select(ignoreSelectSound: true);
|
||||
}
|
||||
|
||||
//this needs to be done even if we're using the existing chatinput instance instead of creating a new one,
|
||||
|
||||
@@ -1126,6 +1126,10 @@ namespace Barotrauma
|
||||
{
|
||||
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("CannotJoinSteamServer.SteamNotInitialized"));
|
||||
}
|
||||
else if (endpoint is EosP2PEndpoint && !EosInterface.Core.IsInitialized)
|
||||
{
|
||||
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("EosStatus.NotInitialized"));
|
||||
}
|
||||
else
|
||||
{
|
||||
JoinServer(endpoint.ToEnumerable().ToImmutableArray(), "");
|
||||
@@ -1183,9 +1187,29 @@ namespace Barotrauma
|
||||
|
||||
endpointBox.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
okButton.Enabled = favoriteButton.Enabled = !string.IsNullOrEmpty(text);
|
||||
okButton.Enabled = favoriteButton.Enabled = !string.IsNullOrEmpty(text);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Connect on enter press, gotta go fast
|
||||
endpointBox.OnEnterPressed += (textBox, text) =>
|
||||
{
|
||||
if (okButton.Enabled)
|
||||
{
|
||||
if (okButton.PlaySoundOnSelect)
|
||||
{
|
||||
SoundPlayer.PlayUISound(okButton.ClickSound);
|
||||
}
|
||||
okButton.OnClicked.Invoke(okButton, okButton.UserData);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Focus on and select all the text, for easier input/deletion (after the dialog is shown, apparently it takes a moment)
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
endpointBox.Select(ignoreSelectSound: true);
|
||||
}, 0.1f);
|
||||
}
|
||||
|
||||
private void RemoveMsgFromServerList()
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace Barotrauma
|
||||
|
||||
#region Transform Editor
|
||||
private const float TransformWidgetOffset = 300f;
|
||||
private const float RotationSnapIncrement = MathF.PI / 36f;
|
||||
private const float ScaleSnapIncrement = 0.1f;
|
||||
|
||||
private GUITickBox rotateToolToggle, scaleToolToggle;
|
||||
public bool TransformWidgetSelected => TransformWidget.IsSelected;
|
||||
@@ -141,6 +143,7 @@ namespace Barotrauma
|
||||
if (rotateToolToggle.Selected)
|
||||
{
|
||||
transformCommand.RotationRad = MathUtils.VectorToAngle(PlayerInput.MousePosition - Cam.WorldToScreen(transformCommand.Pivot));
|
||||
if (!PlayerInput.IsShiftDown()) { transformCommand.RotationRad = MathUtils.RoundTowardsClosest(transformCommand.RotationRad.Value, RotationSnapIncrement); }
|
||||
rotationString = TextManager.GetWithVariable("SubEditor.TransformWidget.Rotation", "[value]", MathHelper.ToDegrees(transformCommand.RotationRad.Value).ToString("0.000", CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
@@ -148,7 +151,9 @@ namespace Barotrauma
|
||||
LocalizedString scaleString = null;
|
||||
if (scaleToolToggle.Selected)
|
||||
{
|
||||
transformCommand.ScaleMult = Math.Clamp(Vector2.Distance(PlayerInput.MousePosition, Cam.WorldToScreen(transformCommand.Pivot)) / (TransformWidgetOffset * GUI.Scale), transformCommand.MinScale, transformCommand.MaxScale);
|
||||
transformCommand.ScaleMult = Vector2.Distance(PlayerInput.MousePosition, Cam.WorldToScreen(transformCommand.Pivot)) / (TransformWidgetOffset * GUI.Scale);
|
||||
if (!PlayerInput.IsShiftDown()) { transformCommand.ScaleMult = MathUtils.RoundTowardsClosest(transformCommand.ScaleMult.Value, ScaleSnapIncrement); }
|
||||
transformCommand.ScaleMult = Math.Clamp(transformCommand.ScaleMult.Value, transformCommand.MinScale, transformCommand.MaxScale);
|
||||
scaleString = TextManager.GetWithVariable("SubEditor.TransformWidget.Scale", "[value]", transformCommand.ScaleMult.Value.ToString("0.000", CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
|
||||
@@ -260,8 +260,16 @@ namespace Barotrauma
|
||||
{
|
||||
continue;
|
||||
}
|
||||
split[j] = split[j].Replace(" & ", " & ");
|
||||
xmlContentByLanguage[languageName].Add($"<{split[0]}>{split[j]}</{split[0]}>");
|
||||
|
||||
string textContent = split[j];
|
||||
if (textContent == "#NAME?")
|
||||
{
|
||||
throw new Exception(
|
||||
$"Error while converting csv to xml: #NAME? value found on line {row}, language: {languageName}." +
|
||||
" This indicates a missing value in the csv file (some text got accidentally converted to a broken formula in the localization sheet?).");
|
||||
}
|
||||
textContent = textContent.Replace(" & ", " & ");
|
||||
xmlContentByLanguage[languageName].Add($"<{split[0]}>{textContent}</{split[0]}>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.10.7.2</Version>
|
||||
<Version>1.11.4.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.10.7.2</Version>
|
||||
<Version>1.11.4.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.10.7.2</Version>
|
||||
<Version>1.11.4.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.10.7.2</Version>
|
||||
<Version>1.11.4.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.10.7.2</Version>
|
||||
<Version>1.11.4.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!Enabled) { return 1000.0f; }
|
||||
|
||||
Vector2 comparePosition = recipient.SpectatePos == null ? recipient.Character.WorldPosition : recipient.SpectatePos.Value;
|
||||
Vector2 comparePosition = recipient.SpectatePos ?? recipient.Character.WorldPosition;
|
||||
|
||||
float distance = Vector2.Distance(comparePosition, WorldPosition);
|
||||
if (recipient.Character?.ViewTarget != null)
|
||||
@@ -199,7 +199,9 @@ namespace Barotrauma
|
||||
UInt16 networkUpdateID = msg.ReadUInt16();
|
||||
byte inputCount = msg.ReadByte();
|
||||
|
||||
if (AllowInput) { Enabled = true; }
|
||||
// Doesn't seem to work consistently (at least with simulated long loading time 120), because sometimes there's some stun on the character. Anyway, can't see why we'd have to check AllowInput here.
|
||||
//if (AllowInput) { Enabled = true; }
|
||||
Enabled = true;
|
||||
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
@@ -802,9 +804,7 @@ namespace Barotrauma
|
||||
|
||||
if (msg.LengthBytes - initialMsgLength >= 255 && restrictMessageSize)
|
||||
{
|
||||
string errorMsg = $"Error when writing character spawn data for \"{Name}\": data exceeded 255 bytes (info: {infoLength}, orders: {ordersLength}, total: {msg.LengthBytes - initialMsgLength})";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Character.WriteSpawnData:TooMuchData", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
DebugConsole.AddWarning($"Character spawn data for \"{Name}\" exceeded 255 bytes (info: {infoLength}, orders: {ordersLength}, total: {msg.LengthBytes - initialMsgLength})");
|
||||
}
|
||||
|
||||
TryWriteStatus(msg);
|
||||
@@ -820,9 +820,7 @@ namespace Barotrauma
|
||||
msg.WriteBoolean(false);
|
||||
if (msgLengthBeforeStatus < 255)
|
||||
{
|
||||
string errorMsg = $"Error when writing character spawn data for \"{Name}\": status data caused the length of the message to exceed 255 bytes ({msgLengthBeforeStatus} + {tempBuffer.LengthBytes})";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Character.WriteSpawnData:TooMuchDataForStatus", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
DebugConsole.ThrowError($"Character spawn data for \"{Name}\" caused the length of the message to exceed 255 bytes ({msgLengthBeforeStatus} + {tempBuffer.LengthBytes})");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -278,7 +278,14 @@ namespace Barotrauma
|
||||
characterInfo.ApplyDeathEffects();
|
||||
}
|
||||
c.CharacterInfo = characterInfo;
|
||||
SetClientCharacterData(c);
|
||||
|
||||
// Only create new character data if the connected client has an active character (which they might not,
|
||||
// eg. if they are in the lobby). Otherwise the CharacterCampaignData constructor would fall back to a new
|
||||
// Character object, overwriting the inventory and wallet with empty values.
|
||||
if (c.Character != null)
|
||||
{
|
||||
SetClientCharacterData(c);
|
||||
}
|
||||
}
|
||||
|
||||
//refresh the character data of clients who aren't in the server anymore
|
||||
@@ -1105,10 +1112,6 @@ namespace Barotrauma
|
||||
bool predicate(SoldItem i) => allowedToSellInventoryItems != (i.Origin == SoldItem.SellOrigin.Character);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log($"{sender.Name} attempted to buy or sell items without having access to a store NPC.", ServerLog.MessageType.Error);
|
||||
}
|
||||
|
||||
if ((purchasedUpgrades.Any() || purchasedItemSwaps.Any()) &&
|
||||
HasCampaignInteractionAvailable(sender, InteractionType.Upgrade))
|
||||
@@ -1142,10 +1145,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log($"{sender.Name} attempted to buy upgrades without having access to an NPC offering upgrades.", ServerLog.MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasCampaignInteractionAvailable(Client sender, InteractionType interactionType)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ConnectionSelectorComponent : ItemComponent
|
||||
{
|
||||
private CoroutineHandle sendStateCoroutine;
|
||||
private int lastSentConnectionIndex;
|
||||
private float sendStateTimer;
|
||||
|
||||
partial void OnStateChanged()
|
||||
{
|
||||
sendStateTimer = 0.5f;
|
||||
if (sendStateCoroutine == null)
|
||||
{
|
||||
sendStateCoroutine = CoroutineManager.StartCoroutine(SendStateAfterDelay());
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> SendStateAfterDelay()
|
||||
{
|
||||
while (sendStateTimer > 0.0f)
|
||||
{
|
||||
sendStateTimer -= CoroutineManager.DeltaTime;
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
if (item.Removed || GameMain.NetworkMember == null)
|
||||
{
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
sendStateCoroutine = null;
|
||||
if (lastSentConnectionIndex != selectedConnectionIndex)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.WriteRangedInteger(selectedConnectionIndex, 0, 255);
|
||||
lastSentConnectionIndex = selectedConnectionIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1857,6 +1857,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
//client presumably isn't afk if they clicked to start a round
|
||||
sender.AFK = false;
|
||||
|
||||
bool continueCampaign = inc.ReadBoolean();
|
||||
if (mpCampaign != null && mpCampaign.GameOver || continueCampaign)
|
||||
{
|
||||
@@ -2042,7 +2045,8 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
if (!FileSender.ActiveTransfers.Any(t => t.Connection == c.Connection && t.FileType == FileTransferType.CampaignSave))
|
||||
//don't send the campaign save if there's any other transfers running (client waiting for subs, mods, or already transferring the campaign save)
|
||||
if (FileSender.ActiveTransfers.None(t => t.Connection == c.Connection))
|
||||
{
|
||||
FileSender.StartTransfer(c.Connection, FileTransferType.CampaignSave, GameMain.GameSession.DataPath.SavePath);
|
||||
c.LastCampaignSaveSendTime = (campaign.LastSaveID, (float)NetTime.Now);
|
||||
@@ -3077,7 +3081,7 @@ namespace Barotrauma.Networking
|
||||
WayPoint jobItemSpawnPoint = mainSubWaypoints != null ? mainSubWaypoints[i] : spawnWaypoints[i];
|
||||
|
||||
Character spawnedCharacter = Character.Create(teamClients[i].CharacterInfo, spawnWaypoints[i].WorldPosition, teamClients[i].CharacterInfo.Name, isRemotePlayer: true, hasAi: false);
|
||||
spawnedCharacter.AnimController.Frozen = true;
|
||||
//spawnedCharacter.AnimController.Frozen = true;
|
||||
spawnedCharacter.TeamID = teamID;
|
||||
teamClients[i].Character = spawnedCharacter;
|
||||
var characterData = campaign?.GetClientCharacterData(teamClients[i]);
|
||||
@@ -4359,7 +4363,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void SetClientCharacter(Client client, Character newCharacter)
|
||||
{
|
||||
if (client == null) return;
|
||||
if (client == null) { return; }
|
||||
|
||||
//the client's previous character is no longer a remote player
|
||||
if (client.Character != null)
|
||||
@@ -4385,13 +4389,14 @@ namespace Barotrauma.Networking
|
||||
newCharacter.LastNetworkUpdateID = client.Character.LastNetworkUpdateID;
|
||||
}
|
||||
|
||||
if (newCharacter.Info != null && newCharacter.Info.Character == null)
|
||||
if (newCharacter.Info is { Character: null })
|
||||
{
|
||||
newCharacter.Info.Character = newCharacter;
|
||||
}
|
||||
|
||||
newCharacter.SetOwnerClient(client);
|
||||
newCharacter.Enabled = true;
|
||||
newCharacter.AnimController.Frozen = false;
|
||||
client.Character = newCharacter;
|
||||
client.CharacterInfo = newCharacter.Info;
|
||||
CreateEntityEvent(newCharacter, new Character.ControlEventData(client));
|
||||
|
||||
@@ -128,11 +128,11 @@ namespace Barotrauma.Networking
|
||||
//remove old events that have been sent to all clients, they are redundant now
|
||||
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
||||
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
||||
if (GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration)
|
||||
if (GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration)
|
||||
{
|
||||
events.RemoveAll(e =>
|
||||
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
|
||||
e.CreateTime < Timing.TotalTime - NetConfig.EventRemovalTime);
|
||||
e.CreateTime < Timing.TotalTime - server.ServerSettings.EventRemovalTime);
|
||||
}
|
||||
|
||||
for (int i = events.Count - 1; i >= 0; i--)
|
||||
@@ -226,7 +226,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (Timing.TotalTime - lastWarningTime > 5.0 &&
|
||||
Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
|
||||
GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration)
|
||||
GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration)
|
||||
{
|
||||
lastWarningTime = Timing.TotalTime;
|
||||
string warningMsg = $"WARNING: ServerEntityEventManager is lagging behind! Last sent id: {lastSentToAnyone}, latest create id: {ID}";
|
||||
@@ -240,8 +240,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||
if (firstEventToResend != null &&
|
||||
GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration &&
|
||||
((lastSentToAnyoneTime - firstEventToResend.CreateTime) > NetConfig.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > NetConfig.OldEventKickTime))
|
||||
GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration &&
|
||||
((lastSentToAnyoneTime - firstEventToResend.CreateTime) > server.ServerSettings.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > server.ServerSettings.OldEventKickTime))
|
||||
{
|
||||
// This event is 10 seconds older than the last one we've successfully sent,
|
||||
// kick everyone that hasn't received it yet, this is way too old
|
||||
|
||||
@@ -55,13 +55,13 @@ namespace Barotrauma.Networking
|
||||
PasswordRetries = 0;
|
||||
PasswordSalt = null;
|
||||
UpdateTime = Timing.TotalTime + Timing.Step * 3.0;
|
||||
TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
TimeOut = NetworkConnection.TimeoutThresholdNotInGame;
|
||||
AuthSessionStarted = false;
|
||||
}
|
||||
|
||||
public void Heartbeat()
|
||||
{
|
||||
TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
TimeOut = NetworkConnection.TimeoutThresholdNotInGame;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
protected void ReadConnectionInitializationStep(PendingClient pendingClient, IReadMessage inc, ConnectionInitialization initializationStep)
|
||||
{
|
||||
pendingClient.TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
pendingClient.TimeOut = NetworkConnection.TimeoutThresholdNotInGame;
|
||||
|
||||
if (pendingClient.InitializationStep != initializationStep) { return; }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.10.7.2</Version>
|
||||
<Version>1.11.4.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<Missions>
|
||||
<Mission identifier="missionvariant" variantof="killcrawlerswarm1" name="One Million Crawlers" description=":3" difficulty="4" commonness="100000000" reward="20000" sonarlabel="character.crawler">
|
||||
<monster min="25" max="50"/>
|
||||
<Reputation amount="10"/>
|
||||
</Mission>
|
||||
</Missions>
|
||||
@@ -0,0 +1,3 @@
|
||||
<contentpackage name="Mission Variants Test" modversion="1.0.0" corepackage="False" gameversion="1.10.2.0">
|
||||
<Missions file="%ModDir%/Missions.xml"/>
|
||||
</contentpackage>
|
||||
@@ -1223,6 +1223,7 @@ namespace Barotrauma
|
||||
if (otherCharacter.SelectedCharacter == null ||
|
||||
!otherCharacter.SelectedCharacter.IsDead ||
|
||||
otherCharacter.SelectedCharacter.TeamID != Character.TeamID ||
|
||||
otherCharacter.IsPet ||
|
||||
otherCharacter.IsInstigator)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace Barotrauma
|
||||
if (target.Submarine != character.Submarine) { return; }
|
||||
Reset();
|
||||
TargetCharacter = target;
|
||||
targetBody = target.AnimController.Collider.FarseerBody;
|
||||
targetBody = target.AnimController.MainLimb.body.FarseerBody;
|
||||
attachSurfaceNormal = Vector2.Normalize(character.WorldPosition - target.WorldPosition);
|
||||
}
|
||||
|
||||
|
||||
@@ -769,7 +769,7 @@ namespace Barotrauma
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
lethalDmg = attack.GetTotalCharacterDamage();
|
||||
float max = lethalDmg + 1;
|
||||
if (weapon.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
@@ -795,7 +795,7 @@ namespace Barotrauma
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
lethalDmg = attack.GetTotalCharacterDamage();
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
if (diff < 0)
|
||||
@@ -809,7 +809,7 @@ namespace Barotrauma
|
||||
{
|
||||
// Cannot do stun damage -> use the melee damage to determine the priority.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
priority = attack?.GetTotalDamage() ?? priority / 2;
|
||||
priority = attack?.GetTotalCharacterDamage() ?? priority / 2;
|
||||
}
|
||||
// Reduce the priority of the weapon, if we don't have requires skills to use it.
|
||||
float startPriority = priority;
|
||||
@@ -960,7 +960,7 @@ namespace Barotrauma
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
lethalDmg = attack.GetTotalCharacterDamage();
|
||||
}
|
||||
return lethalDmg;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -94,6 +95,7 @@ namespace Barotrauma
|
||||
if (potentialDeconstructor?.InputContainer == null) { continue; }
|
||||
if (!potentialDeconstructor.InputContainer.Inventory.CanBePut(Item)) { continue; }
|
||||
if (!potentialDeconstructor.Item.HasAccess(character)) { continue; }
|
||||
if (Item.Prefab.DeconstructItems.None(it => it.IsValidDeconstructor(otherItem))) { continue; }
|
||||
float distFactor = GetDistanceFactor(Item.WorldPosition, potentialDeconstructor.Item.WorldPosition, factorAtMaxDistance: 0.2f);
|
||||
if (distFactor > bestDistFactor)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -30,6 +31,7 @@ namespace Barotrauma
|
||||
if (character.Submarine == null ||
|
||||
Item.ItemList.None(it =>
|
||||
it.GetComponent<Deconstructor>() != null &&
|
||||
!it.IgnoreByAI(character) &&
|
||||
it.IsInteractable(character) &&
|
||||
character.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true, allowDifferentTeam: true, allowDifferentType: true)))
|
||||
{
|
||||
@@ -60,6 +62,9 @@ namespace Barotrauma
|
||||
protected override bool IsValidTarget(Item target)
|
||||
{
|
||||
if (target == null || target.Removed) { return false; }
|
||||
//bots can't handle deconstructing items that require another item to deconstruct, let's not try to do that
|
||||
//in the vanilla game, this means unidentified genetic materials, which we don't want to "deconstruct" anyway
|
||||
if (target.Prefab.DeconstructItems.All(d => d.RequiredOtherItem.Length > 0)) { return false; }
|
||||
// If the target was selected as a valid target, we'll have to accept it so that the objective can be completed.
|
||||
// The validity changes when a character picks the item up.
|
||||
if (!IsValidTarget(target, character, checkInventory: true))
|
||||
|
||||
@@ -98,18 +98,11 @@ namespace Barotrauma
|
||||
vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100;
|
||||
if (affliction.Strength > affliction.Prefab.TreatmentThreshold)
|
||||
{
|
||||
if (affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
|
||||
//vitality loss is not required to treat this affliction -> evaluate the strength of the affliction too
|
||||
if (!affliction.Prefab.VitalityLossRequiredForTreatment)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
else if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
else if (affliction.Prefab == AfflictionPrefab.HuskInfection)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
|
||||
@@ -437,8 +437,25 @@ namespace Barotrauma
|
||||
public bool TargetItemsMatchItem(Item item, Identifier option = default)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct && !Item.DeconstructItems.Contains(item)) { return true; }
|
||||
if (Identifier == Tags.DontDeconstructThis && Item.DeconstructItems.Contains(item)) { return true; }
|
||||
|
||||
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct)
|
||||
{
|
||||
if (item.AllowDeconstruct && !Item.DeconstructItems.Contains(item) &&
|
||||
//only allow deconstructing if there are deconstruction recipes that
|
||||
item.Prefab.DeconstructItems.Any(deconstructItem =>
|
||||
//1. don't require any additional items (bots can't handle that)
|
||||
deconstructItem.RequiredOtherItem.None() &&
|
||||
//2. don't require a research station (bots don't know how to use those)
|
||||
(deconstructItem.RequiredDeconstructor.Length == 0 || deconstructItem.RequiredDeconstructor.Any(d => d != Tags.GeneticResearchStation))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (Identifier == Tags.DontDeconstructThis)
|
||||
{
|
||||
if (Item.DeconstructItems.Contains(item)) { return true; }
|
||||
}
|
||||
|
||||
ImmutableArray<Identifier> targetItems = GetTargetItems(option);
|
||||
return TargetItemsMatchItem(targetItems, item);
|
||||
}
|
||||
|
||||
@@ -216,6 +216,7 @@ namespace Barotrauma
|
||||
{
|
||||
UpdateTemporaryAnimations();
|
||||
UpdateAnim(deltaTime);
|
||||
CheckRopeState();
|
||||
}
|
||||
|
||||
protected abstract void UpdateAnim(float deltaTime);
|
||||
@@ -1134,6 +1135,25 @@ namespace Barotrauma
|
||||
character.TeleportTo(pos);
|
||||
}
|
||||
|
||||
protected void CheckRopeState()
|
||||
{
|
||||
if (!shouldHangWithRope)
|
||||
{
|
||||
StopHangingWithRope();
|
||||
}
|
||||
if (!shouldHoldToRope)
|
||||
{
|
||||
StopHoldingToRope();
|
||||
}
|
||||
if (!shouldBeDraggedWithRope)
|
||||
{
|
||||
StopGettingDraggedWithRope();
|
||||
}
|
||||
shouldHoldToRope = false;
|
||||
shouldHangWithRope = false;
|
||||
shouldBeDraggedWithRope = false;
|
||||
}
|
||||
|
||||
private void StartAnimation(Animation animation)
|
||||
{
|
||||
if (animation == Animation.UsingItem)
|
||||
|
||||
@@ -478,21 +478,6 @@ namespace Barotrauma
|
||||
aiming = false;
|
||||
wasAimingMelee = aimingMelee;
|
||||
aimingMelee = false;
|
||||
if (!shouldHangWithRope)
|
||||
{
|
||||
StopHangingWithRope();
|
||||
}
|
||||
if (!shouldHoldToRope)
|
||||
{
|
||||
StopHoldingToRope();
|
||||
}
|
||||
if (!shouldBeDraggedWithRope)
|
||||
{
|
||||
StopGettingDraggedWithRope();
|
||||
}
|
||||
shouldHoldToRope = false;
|
||||
shouldHangWithRope = false;
|
||||
shouldBeDraggedWithRope = false;
|
||||
}
|
||||
|
||||
void UpdateStanding()
|
||||
@@ -1256,34 +1241,25 @@ namespace Barotrauma
|
||||
float prevVitality = target.Vitality;
|
||||
bool wasCritical = prevVitality < 0.0f;
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code
|
||||
float cprBoost = character.GetStatValue(StatTypes.CPRBoost);
|
||||
float skill = character.GetSkillLevel(Tags.MedicalSkill);
|
||||
bool oxygenAvailable = target.OxygenAvailable >= CharacterHealth.InsufficientOxygenThreshold;
|
||||
|
||||
//Serverside code
|
||||
if (oxygenAvailable && GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
target.Oxygen += deltaTime * 0.5f; //Stabilize them
|
||||
}
|
||||
|
||||
float cprBoost = character.GetStatValue(StatTypes.CPRBoost);
|
||||
|
||||
int skill = (int)character.GetSkillLevel(Tags.MedicalSkill);
|
||||
|
||||
if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
if (cprBoost >= 1f)
|
||||
{
|
||||
//prevent the patient from suffocating no matter how fast their oxygen level is dropping
|
||||
target.Oxygen = Math.Max(target.Oxygen, -10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
//Serverside code
|
||||
if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
if (target.Oxygen < -10.0f)
|
||||
{
|
||||
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
|
||||
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
|
||||
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
|
||||
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
|
||||
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
|
||||
target.Oxygen += stabilizationAmount * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1317,7 +1293,7 @@ namespace Barotrauma
|
||||
}
|
||||
//need to CPR for at least a couple of seconds before the target can be revived
|
||||
//(reviving the target when the CPR has barely started looks strange)
|
||||
if (cprAnimTimer > 2.0f && GameMain.NetworkMember is not { IsClient: true })
|
||||
if (oxygenAvailable && cprAnimTimer > 2.0f && GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
float reviveChance = skill * CPRSettings.Active.ReviveChancePerSkill;
|
||||
reviveChance = (float)Math.Pow(reviveChance, CPRSettings.Active.ReviveChanceExponent);
|
||||
@@ -1343,7 +1319,7 @@ namespace Barotrauma
|
||||
//got the character back into a non-critical state, increase medical skill
|
||||
//BUT only if it has been more than 10 seconds since the character revived someone
|
||||
//otherwise it's easy to abuse the system by repeatedly reviving in a low-oxygen room
|
||||
if (!target.IsDead)
|
||||
if (!target.IsDead || !oxygenAvailable)
|
||||
{
|
||||
target.CharacterHealth.RecalculateVitality();
|
||||
if (wasCritical && target.Vitality > 0.0f && Timing.TotalTime > lastReviveTime + 10.0f)
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Barotrauma
|
||||
get { return frozen; }
|
||||
set
|
||||
{
|
||||
if (frozen == value) return;
|
||||
if (frozen == value) { return; }
|
||||
|
||||
frozen = value;
|
||||
|
||||
@@ -1063,7 +1063,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void FindHull(Vector2? worldPosition = null, bool setSubmarine = true)
|
||||
/// <param name="setInWater">Should the character be immediately considered "in water" if it's outside hulls (normally checked in Update)</param>
|
||||
public void FindHull(Vector2? worldPosition = null, bool setSubmarine = true, bool setInWater = false)
|
||||
{
|
||||
Vector2 findPos = worldPosition == null ? this.WorldPosition : (Vector2)worldPosition;
|
||||
if (!MathUtils.IsValid(findPos))
|
||||
@@ -1076,6 +1077,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Hull newHull = Hull.FindHull(findPos, currentHull);
|
||||
if (setInWater && newHull == null)
|
||||
{
|
||||
inWater = true;
|
||||
}
|
||||
|
||||
if (newHull == currentHull) { return; }
|
||||
|
||||
|
||||
@@ -403,14 +403,19 @@ namespace Barotrauma
|
||||
return (Duration == 0.0f) ? dmg : dmg * deltaTime;
|
||||
}
|
||||
|
||||
public float GetTotalDamage(bool includeStructureDamage = false)
|
||||
/// <summary>
|
||||
/// Returns the total damage (vitality decrease) this attack causes on characters.
|
||||
/// </summary>
|
||||
public float GetTotalCharacterDamage()
|
||||
{
|
||||
float totalDamage = includeStructureDamage ? StructureDamage : 0.0f;
|
||||
float totalDamage = 0.0f;
|
||||
foreach (Affliction affliction in Afflictions.Keys)
|
||||
{
|
||||
totalDamage += affliction.GetVitalityDecrease(null);
|
||||
float afflictionVitalityDecrease = affliction.GetVitalityDecrease(null);
|
||||
if (affliction.AffectedByAttackMultipliers) { afflictionVitalityDecrease *= DamageMultiplier; }
|
||||
totalDamage += afflictionVitalityDecrease;
|
||||
}
|
||||
return totalDamage * DamageMultiplier;
|
||||
return totalDamage;
|
||||
}
|
||||
|
||||
public Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float itemDamage, float range = 0.0f)
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Barotrauma
|
||||
|
||||
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
|
||||
{
|
||||
public readonly static List<Character> CharacterList = new List<Character>();
|
||||
public static readonly List<Character> CharacterList = new List<Character>();
|
||||
|
||||
public static int CharacterUpdateInterval = 1;
|
||||
private static int characterUpdateTick = 1;
|
||||
@@ -42,7 +42,13 @@ namespace Barotrauma
|
||||
|
||||
partial void UpdateLimbLightSource(Limb limb);
|
||||
|
||||
private bool enabled = true;
|
||||
private bool initialized;
|
||||
private bool enabled;
|
||||
//characters start disabled in the multiplayer mode, and are enabled if/when
|
||||
// - controlled by the player
|
||||
// - client receives a position update from the server
|
||||
// - server receives an input message from the client controlling the character
|
||||
// - if an AICharacter, the server enables it when close enough to any of the players
|
||||
public bool Enabled
|
||||
{
|
||||
get
|
||||
@@ -51,7 +57,12 @@ namespace Barotrauma
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == enabled) { return; }
|
||||
if (initialized && value == enabled)
|
||||
{
|
||||
// Ensure that we'll set the value and run the code below at least once, because otherwise the states might be out of sync.
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
if (Removed)
|
||||
{
|
||||
@@ -83,7 +94,6 @@ namespace Barotrauma
|
||||
//we only want to enable the physics body if it's an actual holdable item, not e.g. a wearable item like handcuffs
|
||||
item.body.Enabled = true;
|
||||
}
|
||||
|
||||
}
|
||||
AnimController.Collider.Enabled = value;
|
||||
}
|
||||
@@ -112,6 +122,13 @@ namespace Barotrauma
|
||||
if (!CharacterList.Contains(this)) { CharacterList.Add(this); }
|
||||
if (AiTarget != null && !AITarget.List.Contains(AiTarget)) { AITarget.List.Add(AiTarget); }
|
||||
}
|
||||
if (Inventory != null)
|
||||
{
|
||||
foreach (var item in Inventory.FindAllItems(recursive: true))
|
||||
{
|
||||
item.IsActive = !disabledByEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1625,18 +1642,14 @@ namespace Barotrauma
|
||||
PressureProtection = int.MaxValue;
|
||||
}
|
||||
|
||||
AnimController.SetPosition(ConvertUnits.ToSimUnits(position));
|
||||
CharacterHealth.CheckForErrors();
|
||||
|
||||
AnimController.FindHull(null);
|
||||
AnimController.SetPosition(ConvertUnits.ToSimUnits(position));
|
||||
AnimController.FindHull(setInWater: true);
|
||||
if (AnimController.CurrentHull != null) { Submarine = AnimController.CurrentHull.Submarine; }
|
||||
|
||||
CharacterList.Add(this);
|
||||
|
||||
//characters start disabled in the multiplayer mode, and are enabled if/when
|
||||
// - controlled by the player
|
||||
// - client receives a position update from the server
|
||||
// - server receives an input message from the client controlling the character
|
||||
// - if an AICharacter, the server enables it when close enough to any of the players
|
||||
|
||||
Enabled = GameMain.NetworkMember == null;
|
||||
|
||||
if (info != null)
|
||||
@@ -3311,17 +3324,22 @@ namespace Barotrauma
|
||||
|
||||
public static void UpdateAll(float deltaTime, Camera cam)
|
||||
{
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) // single player or server
|
||||
{
|
||||
foreach (Character c in CharacterList)
|
||||
{
|
||||
if (c is not AICharacter && !c.IsRemotePlayer) { continue; }
|
||||
|
||||
if (c.IsPlayer || (c.IsBot && !c.IsDead))
|
||||
// TODO: The logic below seems to be overly complicated and quite confusing
|
||||
if (c is not AICharacter && !c.IsRemotePlayer) { continue; } // confusing -> what this line is intended for? local player? But that's handled below...
|
||||
if (c.IsRemotePlayer)
|
||||
{
|
||||
// Let the client tell when to enable the character. If we force it enabled here, it may e.g. get killed while still loading a round.
|
||||
continue;
|
||||
}
|
||||
if (c.IsLocalPlayer || (c.IsBot && !c.IsDead))
|
||||
{
|
||||
c.Enabled = true;
|
||||
}
|
||||
else if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
else if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) // mp server
|
||||
{
|
||||
//disable AI characters that are far away from all clients and the host's character and not controlled by anyone
|
||||
float closestPlayerDist = c.GetDistanceToClosestPlayer();
|
||||
@@ -3338,7 +3356,7 @@ namespace Barotrauma
|
||||
c.Enabled = true;
|
||||
}
|
||||
}
|
||||
else if (Submarine.MainSub != null)
|
||||
else if (Submarine.MainSub != null) // sp only?
|
||||
{
|
||||
//disable AI characters that are far away from the sub and the controlled character
|
||||
float distSqr = Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition);
|
||||
@@ -3382,7 +3400,7 @@ namespace Barotrauma
|
||||
foreach (Character character in GameMain.LuaCs.Game.UpdatePriorityCharacters)
|
||||
{
|
||||
if (character.Removed) { continue; }
|
||||
|
||||
Debug.Assert(character is { Removed: false });
|
||||
character.Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
@@ -3444,8 +3462,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Inventory.GetAllItems(checkForDuplicates: false))
|
||||
{
|
||||
if (item.body == null || item.body.Enabled) { continue; }
|
||||
item.SetTransform(SimPosition, 0.0f);
|
||||
item.Submarine = Submarine;
|
||||
item.SetTransform(SimPosition, 0.0f, forceSubmarine: Submarine);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4600,7 +4617,10 @@ namespace Barotrauma
|
||||
|
||||
SetStun(stun);
|
||||
|
||||
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
|
||||
if (attacker != null && attacker != this &&
|
||||
attacker.IsOnPlayerTeam &&
|
||||
GameMain.NetworkMember != null &&
|
||||
!GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
|
||||
{
|
||||
if (attacker.TeamID == TeamID)
|
||||
{
|
||||
|
||||
@@ -85,6 +85,8 @@ namespace Barotrauma
|
||||
|
||||
public double AppliedAsSuccessfulTreatmentTime, AppliedAsFailedTreatmentTime;
|
||||
|
||||
public bool AffectedByAttackMultipliers => Prefab.AffectedByAttackMultipliers;
|
||||
|
||||
public float Duration;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -164,15 +164,18 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case InfectionState.Transition:
|
||||
if (character == Character.Controlled)
|
||||
if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true })
|
||||
{
|
||||
if (character == Character.Controlled)
|
||||
{
|
||||
#if CLIENT
|
||||
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUIStyle.Red);
|
||||
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUIStyle.Red);
|
||||
#endif
|
||||
}
|
||||
else if (character.IsBot)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoghuskcantspeak").Value, delay: Rand.Range(0.5f, 5.0f), identifier: "huskcantspeak".ToIdentifier());
|
||||
}
|
||||
else if (character.IsBot)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoghuskcantspeak").Value, delay: Rand.Range(0.5f, 5.0f), identifier: "huskcantspeak".ToIdentifier());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case InfectionState.Active:
|
||||
|
||||
@@ -699,7 +699,13 @@ namespace Barotrauma
|
||||
/// and the health UI will render the affected limb in green rather than red.
|
||||
/// </summary>
|
||||
public readonly bool IsBuff;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should the affliction be affected by damage multipliers on an attack (e.g. when the attacker has talents that boost damage).
|
||||
/// By default, afflictions defined as buffs aren't affected.
|
||||
/// </summary>
|
||||
public readonly bool AffectedByAttackMultipliers;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, this affliction can affect characters that are marked as
|
||||
/// machines, such as the Fractal Guardian.
|
||||
@@ -780,6 +786,14 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly float TreatmentSuggestionThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// Does the affliction need to have caused some amount of vitality loss for bots to consider treating it?
|
||||
/// Normally bots use vitality loss as a way to determine what kind of injuries need treatment, but some afflictions (e.g. poisons, infections)
|
||||
/// might require treatment regardless of the vitality loss. If disabled, the bots will use the strength of the affliction to evaluate the severity instead of the vitality loss.
|
||||
/// Defaults to true for all afflictions that aren't of the type Paralysis, Poison or HuskInfection.
|
||||
/// </summary>
|
||||
public readonly bool VitalityLossRequiredForTreatment;
|
||||
|
||||
/// <summary>
|
||||
/// Bots will not try to treat the affliction if the character has any of these afflictions
|
||||
/// </summary>
|
||||
@@ -838,14 +852,15 @@ namespace Barotrauma
|
||||
public readonly bool DamageParticles;
|
||||
|
||||
/// <summary>
|
||||
/// An arbitrary modifier that affects how much medical skill is increased when you apply the affliction on a target.
|
||||
/// If the affliction causes damage or is of the 'poison' or 'paralysis' type, the skill is increased only when the target is hostile.
|
||||
/// If the affliction is of the 'buff' type, the skill is increased only when the target is friendly.
|
||||
/// A modifier that affects how much medical skill is increased when you apply this affliction on a target.
|
||||
/// If the affliction causes damage or is of the 'poison' or 'paralysis' type, the skill is increased only when the target is hostile, and the modifier is multiplied by the amount of vitality the enemy lost.
|
||||
/// If the affliction is of the 'buff' type, the skill is increased only when the target is friendly, and the modifier is multiplied by the strength of the affliction the target gained.
|
||||
/// </summary>
|
||||
public readonly float MedicalSkillGain;
|
||||
|
||||
/// <summary>
|
||||
/// An arbitrary modifier that affects how much weapons skill is increased when you apply the affliction on a target.
|
||||
/// A modifier that affects how much weapons skill is increased when you apply the affliction on a target.
|
||||
/// Multiplied by the amount of vitality the enemy lost.
|
||||
/// The skill is increased only when the target is hostile.
|
||||
/// </summary>
|
||||
public readonly float WeaponsSkillGain;
|
||||
@@ -925,6 +940,7 @@ namespace Barotrauma
|
||||
ShowDescriptionInTooltip = element.GetAttributeBool(nameof(ShowDescriptionInTooltip), true);
|
||||
|
||||
IsBuff = element.GetAttributeBool(nameof(IsBuff), false);
|
||||
AffectedByAttackMultipliers = element.GetAttributeBool(nameof(AffectedByAttackMultipliers), def: !IsBuff);
|
||||
AffectMachines = element.GetAttributeBool(nameof(AffectMachines), true);
|
||||
|
||||
ShowBarInHealthMenu = element.GetAttributeBool("showbarinhealthmenu", true);
|
||||
@@ -977,6 +993,11 @@ namespace Barotrauma
|
||||
TreatmentThreshold = element.GetAttributeFloat(nameof(TreatmentThreshold), Math.Max(ActivationThreshold, 10.0f));
|
||||
TreatmentSuggestionThreshold = element.GetAttributeFloat(nameof(TreatmentSuggestionThreshold), TreatmentThreshold);
|
||||
|
||||
bool alwaysRequiresTreatment = AfflictionType == ParalysisType || AfflictionType == PoisonType || this is AfflictionPrefabHusk;
|
||||
|
||||
VitalityLossRequiredForTreatment = element.GetAttributeBool(nameof(VitalityLossRequiredForTreatment),
|
||||
def: !alwaysRequiresTreatment);
|
||||
|
||||
DamageOverlayAlpha = element.GetAttributeFloat(nameof(DamageOverlayAlpha), 0.0f);
|
||||
BurnOverlayAlpha = element.GetAttributeFloat(nameof(BurnOverlayAlpha), 0.0f);
|
||||
|
||||
|
||||
@@ -309,6 +309,19 @@ namespace Barotrauma
|
||||
InitProjSpecific(element, character);
|
||||
}
|
||||
|
||||
public void CheckForErrors()
|
||||
{
|
||||
for (int i = 0; i < limbHealths.Count; i++)
|
||||
{
|
||||
if (Character.AnimController.Limbs.None(l => l.HealthIndex == i))
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Potential error in character {Character.DisplayName}: none of the limbs have been set to use the LimbHealth #{i}, and it will do nothing. "
|
||||
+ "Did you forget to set the HealthIndex values of the limbs?", contentPackage: Character.ContentPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitIrremovableAfflictions()
|
||||
{
|
||||
irremovableAfflictions.Add(bloodlossAffliction = new Affliction(AfflictionPrefab.Bloodloss, 0.0f));
|
||||
@@ -1132,20 +1145,38 @@ namespace Barotrauma
|
||||
|
||||
// We need to use another list of the afflictions when we call the status effects triggered by afflictions,
|
||||
// because those status effects may add or remove other afflictions while iterating the collection.
|
||||
private readonly List<Affliction> afflictionsCopy = new List<Affliction>();
|
||||
private readonly List<Affliction> afflictionsCopy = [];
|
||||
|
||||
private bool isApplyingAfflictionStatusEffects;
|
||||
public void ApplyAfflictionStatusEffects(ActionType type)
|
||||
{
|
||||
afflictionsCopy.Clear();
|
||||
afflictionsCopy.AddRange(afflictions.Keys);
|
||||
foreach (Affliction affliction in afflictionsCopy)
|
||||
if (isApplyingAfflictionStatusEffects)
|
||||
{
|
||||
affliction.ApplyStatusEffects(type, 1.0f, this, targetLimb: GetAfflictionLimb(affliction));
|
||||
//pretty hacky: if we're already in the process of applying afflictions' status effects
|
||||
//(i.e. calling this method caused some additional afflictions to appear and trigger status effects)
|
||||
//let's instantiate a new list so we don't end up modifying afflictionsCopy while enumerating it
|
||||
foreach (Affliction affliction in afflictions.Keys.ToList())
|
||||
{
|
||||
affliction.ApplyStatusEffects(type, 1.0f, this, targetLimb: GetAfflictionLimb(affliction));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isApplyingAfflictionStatusEffects = true;
|
||||
afflictionsCopy.Clear();
|
||||
afflictionsCopy.AddRange(afflictions.Keys);
|
||||
isApplyingAfflictionStatusEffects = true;
|
||||
foreach (Affliction affliction in afflictionsCopy)
|
||||
{
|
||||
affliction.ApplyStatusEffects(type, 1.0f, this, targetLimb: GetAfflictionLimb(affliction));
|
||||
}
|
||||
isApplyingAfflictionStatusEffects = false;
|
||||
}
|
||||
}
|
||||
|
||||
public (CauseOfDeathType type, Affliction affliction) GetCauseOfDeath()
|
||||
{
|
||||
List<Affliction> currentAfflictions = GetAllAfflictions(true);
|
||||
IEnumerable<Affliction> currentAfflictions = GetAllAfflictions(true);
|
||||
|
||||
Affliction strongestAffliction = null;
|
||||
float largestStrength = 0.0f;
|
||||
@@ -1168,7 +1199,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private readonly List<Affliction> allAfflictions = new List<Affliction>();
|
||||
private List<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
||||
private IEnumerable<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
||||
{
|
||||
allAfflictions.Clear();
|
||||
if (!mergeSameAfflictions)
|
||||
|
||||
@@ -843,7 +843,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (!foundMatchingModifier && random > affliction.Probability) { continue; }
|
||||
float finalDamageModifier = damageMultiplier;
|
||||
float finalDamageModifier = affliction.AffectedByAttackMultipliers ? damageMultiplier : 1.0f;
|
||||
if (character.EmpVulnerability > 0 && affliction.Prefab.AfflictionType == AfflictionPrefab.EMPType)
|
||||
{
|
||||
finalDamageModifier *= character.EmpVulnerability;
|
||||
|
||||
@@ -14,14 +14,41 @@ namespace Barotrauma
|
||||
public enum SpawnLocationType
|
||||
{
|
||||
Any,
|
||||
/// <summary>
|
||||
/// Spawnpoint inside the main submarine.
|
||||
/// </summary>
|
||||
MainSub,
|
||||
/// <summary>
|
||||
/// Spawnpoint inside an outpost.
|
||||
/// </summary>
|
||||
Outpost,
|
||||
/// <summary>
|
||||
/// Spawnpoint on the main path through the level.
|
||||
/// </summary>
|
||||
MainPath,
|
||||
/// <summary>
|
||||
/// Spawnpoint in a cave. Only valid if there are caves in the level.
|
||||
/// </summary>
|
||||
Cave,
|
||||
/// <summary>
|
||||
/// Spawnpoint in an abyss cave. Only valid if there are abyss caves in the level.
|
||||
/// </summary>
|
||||
AbyssCave,
|
||||
/// <summary>
|
||||
/// Spawnpoint in a ruin. Only valid if there are ruins in the level.
|
||||
/// </summary>
|
||||
Ruin,
|
||||
/// <summary>
|
||||
/// Spawnpoint in a wreck. Only valid if there are wrecks in the level.
|
||||
/// </summary>
|
||||
Wreck,
|
||||
/// <summary>
|
||||
/// Spawnpoint in a beacon station. Only valid if there are beacon stations in the level.
|
||||
/// </summary>
|
||||
BeaconStation,
|
||||
/// <summary>
|
||||
/// A spawnpoint on the main path through the level. The difference to the <see cref="MainPath"/> type is that the closest possible spawnpoint is chosen.
|
||||
/// </summary>
|
||||
NearMainSub
|
||||
}
|
||||
|
||||
@@ -425,7 +452,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnPointsWithCorrectType = potentialSpawnPoints.Where(wp => wp.SpawnType != SpawnType.Path);
|
||||
spawnPointsWithCorrectType = potentialSpawnPoints;
|
||||
}
|
||||
if (spawnPointsWithCorrectType.Any())
|
||||
{
|
||||
@@ -485,7 +512,12 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//spawnpoints that match the desired criteria found, choose the best one next
|
||||
IEnumerable<WayPoint> validSpawnPoints = potentialSpawnPoints;
|
||||
// preferring non-path spawnpoints if there's any available
|
||||
var nonPathSpawnPoints = potentialSpawnPoints.Where(wp => wp.SpawnType != SpawnType.Path);
|
||||
var validSpawnPoints =
|
||||
nonPathSpawnPoints.Any() && spawnPointType != SpawnType.Path ?
|
||||
nonPathSpawnPoints :
|
||||
potentialSpawnPoints;
|
||||
|
||||
//don't spawn in an airlock module if there are other options
|
||||
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false);
|
||||
|
||||
@@ -8,9 +8,9 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MissionPrefab : PrefabWithUintIdentifier
|
||||
internal sealed partial class MissionPrefab : PrefabWithUintIdentifier, IImplementsVariants<MissionPrefab>
|
||||
{
|
||||
public static readonly PrefabCollection<MissionPrefab> Prefabs = new PrefabCollection<MissionPrefab>();
|
||||
public static readonly PrefabCollection<MissionPrefab> Prefabs = [];
|
||||
|
||||
/// <summary>
|
||||
/// The keys here are for backwards compatibility, tying the old mission types to the appropriate class.
|
||||
@@ -42,7 +42,7 @@ namespace Barotrauma
|
||||
{ "Combat".ToIdentifier(), typeof(CombatMission) }
|
||||
};
|
||||
|
||||
public static readonly HashSet<Identifier> HiddenMissionTypes = new HashSet<Identifier>() { "GoTo".ToIdentifier(), "End".ToIdentifier() };
|
||||
public static readonly HashSet<Identifier> HiddenMissionTypes = ["GoTo".ToIdentifier(), "End".ToIdentifier()];
|
||||
|
||||
public class ReputationReward
|
||||
{
|
||||
@@ -58,110 +58,117 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
private ConstructorInfo constructor;
|
||||
|
||||
public readonly Identifier Type;
|
||||
public Identifier Type { get; private set; }
|
||||
|
||||
public readonly Type MissionClass;
|
||||
public Type MissionClass { get; private set; }
|
||||
|
||||
public readonly bool MultiplayerOnly, SingleplayerOnly;
|
||||
public bool MultiplayerOnly { get; private set; }
|
||||
public bool SingleplayerOnly { get; private set; }
|
||||
|
||||
public readonly Identifier TextIdentifier;
|
||||
public Identifier TextIdentifier { get; private set; }
|
||||
|
||||
public readonly ImmutableHashSet<Identifier> Tags;
|
||||
public ImmutableHashSet<Identifier> Tags { get; private set; }
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
public readonly LocalizedString Description;
|
||||
public readonly LocalizedString SuccessMessage;
|
||||
public readonly LocalizedString FailureMessage;
|
||||
public readonly LocalizedString SonarLabel;
|
||||
public readonly Identifier SonarIconIdentifier;
|
||||
public LocalizedString Name { get; private set; }
|
||||
public LocalizedString Description { get; private set; }
|
||||
public LocalizedString SuccessMessage { get; private set; }
|
||||
public LocalizedString FailureMessage { get; private set; }
|
||||
public LocalizedString SonarLabel { get; private set; }
|
||||
public Identifier SonarIconIdentifier { get; private set; }
|
||||
|
||||
public readonly Identifier AchievementIdentifier;
|
||||
public Identifier AchievementIdentifier { get; private set; }
|
||||
|
||||
public readonly ImmutableList<ReputationReward> ReputationRewards;
|
||||
public ImmutableList<ReputationReward> ReputationRewards { get; private set; }
|
||||
|
||||
public readonly List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>
|
||||
DataRewards = new List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>();
|
||||
public readonly List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)> DataRewards = [];
|
||||
|
||||
public readonly int Commonness;
|
||||
public int Commonness { get; private set; }
|
||||
/// <summary>
|
||||
/// Displayed difficulty (indicator)
|
||||
/// </summary>
|
||||
public readonly int? Difficulty;
|
||||
public int? Difficulty { get; private set; }
|
||||
public const int MinDifficulty = 1, MaxDifficulty = 4;
|
||||
/// <summary>
|
||||
/// The actual minimum difficulty of the level allowed for this mission to trigger.
|
||||
/// </summary>
|
||||
public readonly int MinLevelDifficulty = 0;
|
||||
public int MinLevelDifficulty { get; private set; } = 0;
|
||||
/// <summary>
|
||||
/// The actual maximum difficulty of the level allowed for this mission to trigger.
|
||||
/// </summary>
|
||||
public readonly int MaxLevelDifficulty = 100;
|
||||
public int MaxLevelDifficulty { get; private set; } = 100;
|
||||
|
||||
public readonly int Reward;
|
||||
public int Reward { get; private set; }
|
||||
|
||||
public readonly float ExperienceMultiplier;
|
||||
public float ExperienceMultiplier { get; private set; }
|
||||
|
||||
// The titles and bodies of the popup messages during the mission, shown when the state of the mission changes. The order matters.
|
||||
public readonly ImmutableArray<LocalizedString> Headers;
|
||||
public readonly ImmutableArray<LocalizedString> Messages;
|
||||
public ImmutableArray<LocalizedString> Headers { get; private set; }
|
||||
public ImmutableArray<LocalizedString> Messages { get; private set; }
|
||||
|
||||
public readonly bool AllowRetry;
|
||||
public bool AllowRetry { get; private set; }
|
||||
|
||||
public readonly bool ShowSonarLabels;
|
||||
public bool ShowSonarLabels { get; private set; }
|
||||
|
||||
public readonly bool ShowInMenus, ShowStartMessage;
|
||||
public bool ShowInMenus { get; private set; }
|
||||
public bool ShowStartMessage { get; private set; }
|
||||
|
||||
public readonly bool IsSideObjective;
|
||||
/// <summary>
|
||||
/// Makes the mission not count for the maximum mission limit, and forces it to always be selected when it's available in a level.
|
||||
/// </summary>
|
||||
public bool IsSideObjective { get; private set; }
|
||||
|
||||
public readonly bool AllowOtherMissionsInLevel;
|
||||
public bool AllowOtherMissionsInLevel { get; private set; }
|
||||
|
||||
public readonly bool RequireWreck, RequireRuin, RequireBeaconStation, RequireThalamusWreck;
|
||||
public readonly bool SpawnBeaconStationInMiddle;
|
||||
public bool RequireWreck { get; private set; }
|
||||
public bool RequireRuin { get; private set; }
|
||||
public bool RequireBeaconStation { get; private set; }
|
||||
public bool RequireThalamusWreck { get; private set; }
|
||||
public bool SpawnBeaconStationInMiddle { get; private set; }
|
||||
|
||||
public readonly bool AllowOutpostNPCs;
|
||||
public bool AllowOutpostNPCs { get; private set; }
|
||||
|
||||
public readonly Identifier ForceOutpostGenerationParameters;
|
||||
public Identifier ForceOutpostGenerationParameters { get; private set; }
|
||||
|
||||
public readonly RespawnMode? ForceRespawnMode;
|
||||
public RespawnMode? ForceRespawnMode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set, the players can choose which outpost is used for the mission (selected from the outposts that have this tag). Only works in multiplayer.
|
||||
/// </summary>
|
||||
public readonly Identifier AllowOutpostSelectionFromTag;
|
||||
public Identifier AllowOutpostSelectionFromTag { get; private set; }
|
||||
|
||||
public readonly bool LoadSubmarines = true;
|
||||
public bool LoadSubmarines { get; private set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, locations this mission takes place in cannot change their type
|
||||
/// </summary>
|
||||
public readonly bool BlockLocationTypeChanges;
|
||||
public bool BlockLocationTypeChanges { get; private set; }
|
||||
|
||||
public readonly bool ShowProgressBar;
|
||||
public readonly bool ShowProgressInNumbers;
|
||||
public readonly int MaxProgressState;
|
||||
public readonly LocalizedString ProgressBarLabel;
|
||||
public bool ShowProgressBar { get; private set; }
|
||||
public bool ShowProgressInNumbers { get; private set; }
|
||||
public int MaxProgressState { get; private set; }
|
||||
public LocalizedString ProgressBarLabel { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only be received when travelling from a location of the first type to a location of the second type
|
||||
/// </summary>
|
||||
public readonly List<(Identifier from, Identifier to)> AllowedConnectionTypes;
|
||||
public List<(Identifier from, Identifier to)> AllowedConnectionTypes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only be received in these location types
|
||||
/// </summary>
|
||||
public readonly List<Identifier> AllowedLocationTypes = new List<Identifier>();
|
||||
public readonly List<Identifier> AllowedLocationTypes = [];
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only happen in locations owned by this faction. In the mission mode, the location is forced to be owned by this faction.
|
||||
/// </summary>
|
||||
public readonly Identifier RequiredLocationFaction;
|
||||
public Identifier RequiredLocationFaction { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Show entities belonging to these sub categories when the mission starts
|
||||
/// </summary>
|
||||
public readonly List<string> UnhideEntitySubCategories = new List<string>();
|
||||
public List<string> UnhideEntitySubCategories { get; private set; }
|
||||
|
||||
public class TriggerEvent
|
||||
{
|
||||
@@ -186,22 +193,39 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<TriggerEvent> TriggerEvents = new List<TriggerEvent>();
|
||||
public readonly List<TriggerEvent> TriggerEvents = [];
|
||||
|
||||
public LocationTypeChange LocationTypeChangeOnCompleted;
|
||||
|
||||
public readonly ContentXElement ConfigElement;
|
||||
private readonly ContentXElement originalElement;
|
||||
public ContentXElement ConfigElement { get; private set; }
|
||||
|
||||
public Identifier VariantOf { get; }
|
||||
public MissionPrefab ParentPrefab { get; set; }
|
||||
|
||||
public MissionPrefab(ContentXElement element, MissionsFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
ConfigElement = element;
|
||||
ConfigElement = originalElement = element;
|
||||
|
||||
TextIdentifier = element.GetAttributeIdentifier("textidentifier", Identifier);
|
||||
VariantOf = element.VariantOf();
|
||||
if (!VariantOf.IsEmpty) { return; } // Don't read the XML until the PrefabCollection loads the parent.
|
||||
ParseConfigElement();
|
||||
}
|
||||
|
||||
Tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToImmutableHashSet();
|
||||
public void InheritFrom(MissionPrefab parent)
|
||||
{
|
||||
ConfigElement = originalElement.CreateVariantXML(parent.ConfigElement);
|
||||
ParseConfigElement(parent);
|
||||
}
|
||||
|
||||
Name = GetText(element.GetAttributeString("name", ""), "MissionName");
|
||||
Description = GetText(element.GetAttributeString("description", ""), "MissionDescription");
|
||||
private void ParseConfigElement(MissionPrefab variantOf = null)
|
||||
{
|
||||
TextIdentifier = ConfigElement.GetAttributeIdentifier("textidentifier", Identifier);
|
||||
|
||||
Tags = [.. ConfigElement.GetAttributeIdentifierArray("tags", [])];
|
||||
|
||||
Name = GetText(ConfigElement.GetAttributeString("name", ""), "MissionName");
|
||||
Description = GetText(ConfigElement.GetAttributeString("description", ""), "MissionDescription");
|
||||
|
||||
LocalizedString GetText(string textTag, string textTagPrefix)
|
||||
{
|
||||
@@ -211,105 +235,100 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return
|
||||
//prefer finding a text based on the specific text tag defined in the mission config
|
||||
TextManager.Get(textTag)
|
||||
//2nd option: the "default" format (MissionName.SomeMission)
|
||||
.Fallback(TextManager.Get($"{textTagPrefix}.{TextIdentifier}"))
|
||||
//last option: use the text in the xml as-is with no localization
|
||||
.Fallback(textTag);
|
||||
return TextManager.Get(textTag) // Prefer finding a text based on the specific text tag defined in the mission config.
|
||||
.Fallback(TextManager.Get($"{textTagPrefix}.{TextIdentifier}")) // 2nd option: the "default" format (MissionName.SomeMission).
|
||||
.Fallback(textTag); // Last option: Use the text in the xml as-is with no localization.
|
||||
}
|
||||
}
|
||||
|
||||
Reward = element.GetAttributeInt(nameof(Reward), 1);
|
||||
ExperienceMultiplier = element.GetAttributeFloat(nameof(ExperienceMultiplier), 1.0f);
|
||||
AllowRetry = element.GetAttributeBool(nameof(AllowRetry), false);
|
||||
ShowSonarLabels = element.GetAttributeBool(nameof(ShowSonarLabels), true);
|
||||
ShowInMenus = element.GetAttributeBool(nameof(ShowInMenus), true);
|
||||
ShowStartMessage = element.GetAttributeBool(nameof(ShowStartMessage), true);
|
||||
IsSideObjective = element.GetAttributeBool("sideobjective", false);
|
||||
Reward = ConfigElement.GetAttributeInt(nameof(Reward), 1);
|
||||
ExperienceMultiplier = ConfigElement.GetAttributeFloat(nameof(ExperienceMultiplier), 1f);
|
||||
AllowRetry = ConfigElement.GetAttributeBool(nameof(AllowRetry), false);
|
||||
ShowSonarLabels = ConfigElement.GetAttributeBool(nameof(ShowSonarLabels), true);
|
||||
ShowInMenus = ConfigElement.GetAttributeBool(nameof(ShowInMenus), true);
|
||||
ShowStartMessage = ConfigElement.GetAttributeBool(nameof(ShowStartMessage), true);
|
||||
IsSideObjective = ConfigElement.GetAttributeBool("sideobjective", false);
|
||||
|
||||
RequireWreck = element.GetAttributeBool(nameof(RequireWreck), false);
|
||||
RequireThalamusWreck = element.GetAttributeBool(nameof(RequireThalamusWreck), false);
|
||||
RequireRuin = element.GetAttributeBool(nameof(RequireRuin), false);
|
||||
RequireBeaconStation = element.GetAttributeBool(nameof(RequireBeaconStation), false);
|
||||
SpawnBeaconStationInMiddle = element.GetAttributeBool(nameof(SpawnBeaconStationInMiddle), false);
|
||||
if (RequireThalamusWreck) { RequireWreck = true; }
|
||||
RequireWreck = ConfigElement.GetAttributeBool(nameof(RequireWreck), false);
|
||||
RequireThalamusWreck = ConfigElement.GetAttributeBool(nameof(RequireThalamusWreck), false);
|
||||
RequireRuin = ConfigElement.GetAttributeBool(nameof(RequireRuin), false);
|
||||
RequireBeaconStation = ConfigElement.GetAttributeBool(nameof(RequireBeaconStation), false);
|
||||
SpawnBeaconStationInMiddle = ConfigElement.GetAttributeBool(nameof(SpawnBeaconStationInMiddle), false);
|
||||
RequireWreck |= RequireThalamusWreck;
|
||||
|
||||
LoadSubmarines = element.GetAttributeBool(nameof(LoadSubmarines), true);
|
||||
LoadSubmarines = ConfigElement.GetAttributeBool(nameof(LoadSubmarines), true);
|
||||
|
||||
BlockLocationTypeChanges = element.GetAttributeBool(nameof(BlockLocationTypeChanges), false);
|
||||
RequiredLocationFaction = element.GetAttributeIdentifier(nameof(RequiredLocationFaction), Identifier.Empty);
|
||||
Commonness = element.GetAttributeInt(nameof(Commonness), 1);
|
||||
AllowOtherMissionsInLevel = element.GetAttributeBool(nameof(AllowOtherMissionsInLevel), true);
|
||||
BlockLocationTypeChanges = ConfigElement.GetAttributeBool(nameof(BlockLocationTypeChanges), false);
|
||||
RequiredLocationFaction = ConfigElement.GetAttributeIdentifier(nameof(RequiredLocationFaction), Identifier.Empty);
|
||||
Commonness = ConfigElement.GetAttributeInt(nameof(Commonness), 1);
|
||||
AllowOtherMissionsInLevel = ConfigElement.GetAttributeBool(nameof(AllowOtherMissionsInLevel), true);
|
||||
|
||||
if (element.GetAttribute("difficulty") != null)
|
||||
if (ConfigElement.GetAttribute("difficulty") != null)
|
||||
{
|
||||
int difficulty = element.GetAttributeInt(nameof(Difficulty), MinDifficulty);
|
||||
int difficulty = ConfigElement.GetAttributeInt(nameof(Difficulty), MinDifficulty);
|
||||
Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
|
||||
}
|
||||
MinLevelDifficulty = element.GetAttributeInt(nameof(MinLevelDifficulty), MinLevelDifficulty);
|
||||
MaxLevelDifficulty = element.GetAttributeInt(nameof(MaxLevelDifficulty), MaxLevelDifficulty);
|
||||
MinLevelDifficulty = ConfigElement.GetAttributeInt(nameof(MinLevelDifficulty), MinLevelDifficulty);
|
||||
MaxLevelDifficulty = ConfigElement.GetAttributeInt(nameof(MaxLevelDifficulty), MaxLevelDifficulty);
|
||||
MinLevelDifficulty = Math.Clamp(MinLevelDifficulty, 0, Math.Min(MaxLevelDifficulty, 100));
|
||||
MaxLevelDifficulty = Math.Clamp(MaxLevelDifficulty, Math.Max(MinLevelDifficulty, 0), 100);
|
||||
|
||||
AllowOutpostNPCs = element.GetAttributeBool(nameof(AllowOutpostNPCs), true);
|
||||
ForceOutpostGenerationParameters = element.GetAttributeIdentifier(nameof(ForceOutpostGenerationParameters), Identifier.Empty);
|
||||
AllowOutpostSelectionFromTag = element.GetAttributeIdentifier(nameof(AllowOutpostSelectionFromTag), Identifier.Empty);
|
||||
AllowOutpostNPCs = ConfigElement.GetAttributeBool(nameof(AllowOutpostNPCs), true);
|
||||
ForceOutpostGenerationParameters = ConfigElement.GetAttributeIdentifier(nameof(ForceOutpostGenerationParameters), Identifier.Empty);
|
||||
AllowOutpostSelectionFromTag = ConfigElement.GetAttributeIdentifier(nameof(AllowOutpostSelectionFromTag), Identifier.Empty);
|
||||
|
||||
if (element.GetAttribute(nameof(ForceRespawnMode)) != null)
|
||||
if (ConfigElement.GetAttribute(nameof(ForceRespawnMode)) != null)
|
||||
{
|
||||
ForceRespawnMode = element.GetAttributeEnum(nameof(ForceRespawnMode), RespawnMode.MidRound);
|
||||
ForceRespawnMode = ConfigElement.GetAttributeEnum(nameof(ForceRespawnMode), RespawnMode.MidRound);
|
||||
}
|
||||
|
||||
ShowProgressBar = element.GetAttributeBool(nameof(ShowProgressBar), false);
|
||||
ShowProgressInNumbers = element.GetAttributeBool(nameof(ShowProgressInNumbers), false);
|
||||
MaxProgressState = element.GetAttributeInt(nameof(MaxProgressState), 1);
|
||||
string progressBarLabel = element.GetAttributeString(nameof(ProgressBarLabel), "");
|
||||
ShowProgressBar = ConfigElement.GetAttributeBool(nameof(ShowProgressBar), false);
|
||||
ShowProgressInNumbers = ConfigElement.GetAttributeBool(nameof(ShowProgressInNumbers), false);
|
||||
MaxProgressState = ConfigElement.GetAttributeInt(nameof(MaxProgressState), 1);
|
||||
string progressBarLabel = ConfigElement.GetAttributeString(nameof(ProgressBarLabel), "");
|
||||
ProgressBarLabel = TextManager.Get(progressBarLabel).Fallback(progressBarLabel);
|
||||
|
||||
string successMessageTag = element.GetAttributeString("successmessage", "");
|
||||
string successMessageTag = ConfigElement.GetAttributeString("successmessage", "");
|
||||
SuccessMessage = TextManager.Get($"MissionSuccess.{TextIdentifier}");
|
||||
if (!string.IsNullOrEmpty(successMessageTag))
|
||||
{
|
||||
SuccessMessage = SuccessMessage
|
||||
.Fallback(TextManager.Get(successMessageTag))
|
||||
.Fallback(successMessageTag);
|
||||
.Fallback(TextManager.Get(successMessageTag))
|
||||
.Fallback(successMessageTag);
|
||||
}
|
||||
SuccessMessage = SuccessMessage.Fallback(TextManager.Get("missioncompleted"));
|
||||
|
||||
string failureMessageTag = element.GetAttributeString("failuremessage", "");
|
||||
string failureMessageTag = ConfigElement.GetAttributeString("failuremessage", "");
|
||||
FailureMessage = TextManager.Get($"MissionFailure.{TextIdentifier}");
|
||||
if (!string.IsNullOrEmpty(failureMessageTag))
|
||||
{
|
||||
FailureMessage = FailureMessage
|
||||
.Fallback(TextManager.Get(failureMessageTag))
|
||||
.Fallback(failureMessageTag);
|
||||
.Fallback(TextManager.Get(failureMessageTag))
|
||||
.Fallback(failureMessageTag);
|
||||
}
|
||||
FailureMessage = FailureMessage.Fallback(TextManager.Get("missionfailed"));
|
||||
|
||||
string sonarLabelTag = element.GetAttributeString("sonarlabel", "");
|
||||
SonarLabel =
|
||||
TextManager.Get($"MissionSonarLabel.{sonarLabelTag}")
|
||||
.Fallback(TextManager.Get(sonarLabelTag))
|
||||
.Fallback(TextManager.Get($"MissionSonarLabel.{TextIdentifier}"));
|
||||
string sonarLabelTag = ConfigElement.GetAttributeString("sonarlabel", "");
|
||||
SonarLabel = TextManager.Get($"MissionSonarLabel.{sonarLabelTag}")
|
||||
.Fallback(TextManager.Get(sonarLabelTag))
|
||||
.Fallback(TextManager.Get($"MissionSonarLabel.{TextIdentifier}"));
|
||||
if (!string.IsNullOrEmpty(sonarLabelTag))
|
||||
{
|
||||
SonarLabel = SonarLabel.Fallback(sonarLabelTag);
|
||||
}
|
||||
|
||||
SonarIconIdentifier = element.GetAttributeIdentifier("sonaricon", "");
|
||||
SonarIconIdentifier = ConfigElement.GetAttributeIdentifier("sonaricon", "");
|
||||
|
||||
MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false);
|
||||
SingleplayerOnly = element.GetAttributeBool("singleplayeronly", false);
|
||||
MultiplayerOnly = ConfigElement.GetAttributeBool("multiplayeronly", false);
|
||||
SingleplayerOnly = ConfigElement.GetAttributeBool("singleplayeronly", false);
|
||||
|
||||
AchievementIdentifier = element.GetAttributeIdentifier("achievementidentifier", "");
|
||||
AchievementIdentifier = ConfigElement.GetAttributeIdentifier("achievementidentifier", "");
|
||||
|
||||
UnhideEntitySubCategories = element.GetAttributeStringArray("unhideentitysubcategories", Array.Empty<string>()).ToList();
|
||||
UnhideEntitySubCategories = [.. ConfigElement.GetAttributeStringArray("unhideentitysubcategories", [])];
|
||||
|
||||
var headers = new List<LocalizedString>();
|
||||
var messages = new List<LocalizedString>();
|
||||
AllowedConnectionTypes = new List<(Identifier from, Identifier to)>();
|
||||
List<LocalizedString> headers = [];
|
||||
List<LocalizedString> messages = [];
|
||||
AllowedConnectionTypes = [];
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
@@ -322,26 +341,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
List<ReputationReward> reputationRewards = new List<ReputationReward>();
|
||||
List<ReputationReward> reputationRewards = [];
|
||||
int messageIndex = 0;
|
||||
foreach (var subElement in element.Elements())
|
||||
foreach (ContentXElement subElement in ConfigElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "message":
|
||||
if (messageIndex > headers.Count - 1)
|
||||
if (messageIndex >= headers.Count)
|
||||
{
|
||||
headers.Add(string.Empty);
|
||||
messages.Add(string.Empty);
|
||||
}
|
||||
headers[messageIndex] =
|
||||
TextManager.Get($"MissionHeader{messageIndex}.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(subElement.GetAttributeString("header", "")))
|
||||
.Fallback(subElement.GetAttributeString("header", ""));
|
||||
messages[messageIndex] =
|
||||
TextManager.Get($"MissionMessage{messageIndex}.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(subElement.GetAttributeString("text", "")))
|
||||
.Fallback(subElement.GetAttributeString("text", ""));
|
||||
headers[messageIndex] = TextManager.Get($"MissionHeader{messageIndex}.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(subElement.GetAttributeString("header", "")))
|
||||
.Fallback(subElement.GetAttributeString("header", ""));
|
||||
messages[messageIndex] = TextManager.Get($"MissionMessage{messageIndex}.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(subElement.GetAttributeString("text", "")))
|
||||
.Fallback(subElement.GetAttributeString("text", ""));
|
||||
messageIndex++;
|
||||
break;
|
||||
case "locationtype":
|
||||
@@ -352,9 +369,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
AllowedConnectionTypes.Add((
|
||||
subElement.GetAttributeIdentifier("from", ""),
|
||||
subElement.GetAttributeIdentifier("to", "")));
|
||||
AllowedConnectionTypes.Add((subElement.GetAttributeIdentifier("from", ""), subElement.GetAttributeIdentifier("to", "")));
|
||||
}
|
||||
break;
|
||||
case "locationtypechange":
|
||||
@@ -375,7 +390,7 @@ namespace Barotrauma
|
||||
string operatingString = subElement.GetAttributeString("operation", string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(operatingString))
|
||||
{
|
||||
operation = (SetDataAction.OperationType) Enum.Parse(typeof(SetDataAction.OperationType), operatingString);
|
||||
operation = (SetDataAction.OperationType)Enum.Parse(typeof(SetDataAction.OperationType), operatingString);
|
||||
}
|
||||
|
||||
DataRewards.Add((identifier, value, operation));
|
||||
@@ -386,13 +401,13 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
Headers = headers.ToImmutableArray();
|
||||
Messages = messages.ToImmutableArray();
|
||||
ReputationRewards = reputationRewards.ToImmutableList();
|
||||
Headers = [.. headers];
|
||||
Messages = [.. messages];
|
||||
ReputationRewards = [.. reputationRewards];
|
||||
|
||||
MissionClass = FindMissionClass(ConfigElement);
|
||||
Type = ConfigElement.GetAttributeIdentifier(nameof(Type), Identifier.Empty);
|
||||
|
||||
MissionClass = FindMissionClass(element);
|
||||
Type = element.GetAttributeIdentifier(nameof(Type), Identifier.Empty);
|
||||
|
||||
#if DEBUG
|
||||
if (MissionClass == typeof(MonsterMission) && SonarLabel.IsNullOrEmpty())
|
||||
{
|
||||
@@ -403,17 +418,19 @@ namespace Barotrauma
|
||||
if (!LoadSubmarines && MissionClass != typeof(CombatMission))
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in mission {Identifier}: Disabling submarines is only intended for combat missions taking place in an outpost, and may lead to issues in other types of missions.",
|
||||
contentPackage: element.ContentPackage);
|
||||
contentPackage: ConfigElement.ContentPackage);
|
||||
}
|
||||
|
||||
constructor = FindMissionConstructor(element, MissionClass);
|
||||
constructor = FindMissionConstructor(ConfigElement, MissionClass);
|
||||
if (constructor == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!",
|
||||
contentPackage: element.ContentPackage);
|
||||
contentPackage: ConfigElement.ContentPackage);
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
#if CLIENT
|
||||
ParseConfigElementClient(ConfigElement, variantOf);
|
||||
#endif
|
||||
}
|
||||
|
||||
private Type FindMissionClass(ContentXElement element)
|
||||
@@ -476,8 +493,6 @@ namespace Barotrauma
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
|
||||
public bool IsAllowed(Location from, Location to)
|
||||
{
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace Barotrauma
|
||||
|
||||
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
|
||||
{
|
||||
if (GameMain.NetworkMember == null && monsterElement.GetAttributeBool("multiplayeronly", false)) { continue; }
|
||||
|
||||
speciesName = monsterElement.GetAttributeIdentifier("character", Identifier.Empty);
|
||||
int defaultCount = monsterElement.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Barotrauma
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
private readonly Dictionary<Item, StatusEffect> statusEffectOnApproach = new Dictionary<Item, StatusEffect>();
|
||||
|
||||
//string = filename, point = min,max
|
||||
private readonly HashSet<Tuple<CharacterPrefab, Point>> monsterPrefabs = new HashSet<Tuple<CharacterPrefab, Point>>();
|
||||
//key = monster to spawn, point = min,max
|
||||
private readonly List<Tuple<CharacterPrefab, Point>> monsterPrefabs = new List<Tuple<CharacterPrefab, Point>>();
|
||||
|
||||
private float itemSpawnRadius = 800.0f;
|
||||
private readonly float approachItemsRadius = 1000.0f;
|
||||
@@ -70,6 +70,8 @@ namespace Barotrauma
|
||||
|
||||
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
|
||||
{
|
||||
if (GameMain.NetworkMember == null && monsterElement.GetAttributeBool("multiplayeronly", false)) { continue; }
|
||||
|
||||
Identifier speciesName = monsterElement.GetAttributeIdentifier("character", Identifier.Empty);
|
||||
int defaultCount = monsterElement.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
|
||||
@@ -300,7 +300,9 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
Submarine refSub = GetReferenceSub(acceptRemoteControlledSubs: true);
|
||||
if (Submarine.MainSubs.Length == 2 && Submarine.MainSubs[1] != null)
|
||||
//randomly choose which main sub to spawn the monsters around if there's 2 player subs (e.g. in the PvP mode)
|
||||
if (Submarine.MainSubs.Length == 2 &&
|
||||
Submarine.MainSubs[1] is { Info.Type: SubmarineType.Player })
|
||||
{
|
||||
refSub = Submarine.MainSubs.GetRandom(Rand.RandSync.Unsynced);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,19 @@ namespace Barotrauma
|
||||
|
||||
private static async Task<AuthTicket> GetSteamAuthTicket()
|
||||
{
|
||||
var authTicket = await SteamManager.GetAuthTicketForGameAnalyticsConsent();
|
||||
var authTicketTask = SteamManager.GetAuthTicketForGameAnalyticsConsent();
|
||||
|
||||
// Add a timeout to prevent the game from freezing indefinitely
|
||||
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(10));
|
||||
|
||||
var completedTask = await Task.WhenAny(authTicketTask, timeoutTask);
|
||||
if (completedTask == timeoutTask)
|
||||
{
|
||||
throw new TimeoutException("Timed out while trying to retrieve Steamworks authentication ticket for GameAnalytics.");
|
||||
}
|
||||
|
||||
var authTicket = await authTicketTask;
|
||||
|
||||
return authTicket.TryUnwrap(out var ticketUnwrapped) && ticketUnwrapped.Data is { Length: > 0 }
|
||||
? new AuthTicket(ToolBoxCore.ByteArrayToHexString(ticketUnwrapped.Data), Platform.Steam) //convert byte array to hex
|
||||
: throw new Exception("Could not retrieve Steamworks authentication ticket for GameAnalytics");
|
||||
|
||||
@@ -690,7 +690,7 @@ namespace Barotrauma
|
||||
foreach (Item containedItem in character.Inventory.AllItemsMod)
|
||||
{
|
||||
//only put into containers that draw the inventory (not ones with a hidden inventory like circuit boxes!)
|
||||
if (containedItem.OwnInventory?.Container is { DrawInventory: true } &&
|
||||
if (containedItem.OwnInventory?.Container is { DrawInventory: true } container && container.IsAccessible() &&
|
||||
containedItem.OwnInventory.TryPutItem(item, user: null, item.AllowedSlots))
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -469,7 +469,8 @@ namespace Barotrauma
|
||||
foreach (var mission in currentLocation.AvailableMissions)
|
||||
{
|
||||
//if the mission isn't shown in menus, it cannot be selected by the player -> must be something that is supposed to be automatically selected
|
||||
if (!mission.Prefab.ShowInMenus)
|
||||
//side objectives are also automatically selected
|
||||
if (!mission.Prefab.ShowInMenus || mission.Prefab.IsSideObjective)
|
||||
{
|
||||
currentLocation.SelectMission(mission);
|
||||
}
|
||||
@@ -1429,18 +1430,18 @@ namespace Barotrauma
|
||||
map = null;
|
||||
}
|
||||
|
||||
public int NumberOfMissionsAtLocation(Location location)
|
||||
public int NumberOfSelectableMissionsAtLocation(Location location)
|
||||
{
|
||||
return Map?.CurrentLocation?.SelectedMissions?.Count(m => m.Locations.Contains(location)) ?? 0;
|
||||
return Map?.CurrentLocation?.SelectedMissions?.Count(m => m.Locations.Contains(location) && !m.Prefab.IsSideObjective) ?? 0;
|
||||
}
|
||||
|
||||
public void CheckTooManyMissions(Location currentLocation, Client sender)
|
||||
{
|
||||
foreach (Location location in currentLocation.Connections.Select(c => c.OtherLocation(currentLocation)))
|
||||
{
|
||||
if (NumberOfMissionsAtLocation(location) > Settings.TotalMaxMissionCount)
|
||||
if (NumberOfSelectableMissionsAtLocation(location) > Settings.TotalMaxMissionCount)
|
||||
{
|
||||
DebugConsole.AddWarning($"Client {sender.Name} had too many missions selected for location {location.DisplayName}! Count was {NumberOfMissionsAtLocation(location)}. Deselecting extra missions.");
|
||||
DebugConsole.AddWarning($"Client {sender.Name} had too many missions selected for location {location.DisplayName}! Count was {NumberOfSelectableMissionsAtLocation(location)}. Deselecting extra missions.");
|
||||
foreach (Mission mission in currentLocation.SelectedMissions.Where(m => m.Locations[1] == location).Skip(Settings.TotalMaxMissionCount).ToList())
|
||||
{
|
||||
currentLocation.DeselectMission(mission);
|
||||
|
||||
@@ -30,11 +30,23 @@ namespace Barotrauma
|
||||
: base(preset)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
var mission = Mission.LoadRandom(locations, seed, requireCorrectLocationType: false, missionTypes, difficultyLevel: GameMain.NetworkMember.ServerSettings.SelectedLevelDifficulty);
|
||||
float difficulty = GameMain.NetworkMember.ServerSettings.SelectedLevelDifficulty;
|
||||
var mission = Mission.LoadRandom(locations, seed, requireCorrectLocationType: false, missionTypes, difficultyLevel: difficulty);
|
||||
if (mission == null)
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Could not find any missions matching the mission types {string.Join(", ", missionTypes.Select(m => m.Value))} " +
|
||||
$"and the difficulty {difficulty}. Ignoring the difficulty requirement...");
|
||||
mission = Mission.LoadRandom(locations, seed, requireCorrectLocationType: false, missionTypes);
|
||||
}
|
||||
if (mission != null)
|
||||
{
|
||||
missions.Add(mission);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find any missions matching the mission types {string.Join(", ", missionTypes.Select(m => m.Value))}.");
|
||||
}
|
||||
}
|
||||
|
||||
protected static IEnumerable<MissionPrefab> ValidateMissionPrefabs(IEnumerable<MissionPrefab> missionPrefabs, Dictionary<Identifier, Type> missionClasses)
|
||||
|
||||
@@ -741,7 +741,7 @@ namespace Barotrauma
|
||||
var missionsToShow = missions.Where(m => m.Prefab.ShowStartMessage);
|
||||
if (missionsToShow.Count() > 1)
|
||||
{
|
||||
string joinedMissionNames = string.Join(", ", missions.Select(m => m.Name));
|
||||
string joinedMissionNames = string.Join(", ", missions.Where(static m => m.Prefab.ShowInMenus).Select(static m => m.Name));
|
||||
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), joinedMissionNames), Color.CadetBlue, playSound: false);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -226,6 +226,14 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsSlotEmpty(InvSlotType limbSlot)
|
||||
{
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
if (SlotTypes[i] == limbSlot && slots[i].Empty()) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be put in the inventory in a slot of the specified type (i.e. is there a suitable free slot or a stack the item can be put in).
|
||||
@@ -438,7 +446,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
int placedInSlot = -1;
|
||||
foreach (InvSlotType allowedSlot in allowedSlots)
|
||||
//order by whether the slot is empty, i.e. try putting in free slots first before trying to unequip items from occupied slots
|
||||
foreach (InvSlotType allowedSlot in allowedSlots.OrderBy(slotType => IsSlotEmpty(slotType) ? 0 : 1))
|
||||
{
|
||||
if (allowedSlot.HasFlag(InvSlotType.RightHand) && character.AnimController.GetLimb(LimbType.RightHand) == null) { continue; }
|
||||
if (allowedSlot.HasFlag(InvSlotType.LeftHand) && character.AnimController.GetLimb(LimbType.LeftHand) == null) { continue; }
|
||||
|
||||
@@ -87,6 +87,13 @@ namespace Barotrauma.Items.Components
|
||||
"Normally there's no need to touch this setting, but if you notice the docking position is incorrect (for example due to some unusual docking port configuration without hulls or doors), you can use this to enforce the direction.")]
|
||||
public DirectionType ForceDockingDirection { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Was the docking port docked at the end of the previous round.")]
|
||||
public bool WasDocked
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public DockingPort DockingTarget { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -280,6 +287,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
OnDocked?.Invoke();
|
||||
OnDocked = null;
|
||||
|
||||
WasDocked = true;
|
||||
DockingTarget.Docked = true;
|
||||
}
|
||||
|
||||
public void Lock(bool isNetworkMessage, bool applyEffects = true, bool moveSubs = true)
|
||||
@@ -988,6 +998,8 @@ namespace Barotrauma.Items.Components
|
||||
Item.Submarine.EnableObstructedWaypoints(DockingTarget.Item.Submarine);
|
||||
obstructedWayPointsDisabled = false;
|
||||
|
||||
WasDocked = false;
|
||||
DockingTarget.WasDocked = false;
|
||||
DockingTarget.Undock();
|
||||
DockingTarget = null;
|
||||
|
||||
@@ -1052,6 +1064,16 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
//PRETTY HACKY:
|
||||
//the docking port was docked on the previous round, but not any more -
|
||||
//must mean that whatever it was docked to (e.g. some enemy sub or respawn shuttle) no longer exists
|
||||
//let's send an "on_undock" signal so circuits can react to the undocking that never "actually" happened
|
||||
if (!docked && WasDocked)
|
||||
{
|
||||
item.SendSignal("1", "on_undock");
|
||||
WasDocked = false;
|
||||
}
|
||||
|
||||
dockingCooldown -= deltaTime;
|
||||
if (DockingTarget == null)
|
||||
{
|
||||
@@ -1208,19 +1230,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.linkedTo.Any()) { return; }
|
||||
|
||||
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
|
||||
foreach (MapEntity entity in linked)
|
||||
{
|
||||
if (!(entity is Item linkedItem)) { continue; }
|
||||
|
||||
var dockingPort = linkedItem.GetComponent<DockingPort>();
|
||||
if (dockingPort != null)
|
||||
if (item.linkedTo.Any())
|
||||
{
|
||||
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
|
||||
foreach (MapEntity entity in linked)
|
||||
{
|
||||
Dock(dockingPort);
|
||||
}
|
||||
if (entity is not Item linkedItem) { continue; }
|
||||
|
||||
var dockingPort = linkedItem.GetComponent<DockingPort>();
|
||||
if (dockingPort != null)
|
||||
{
|
||||
Dock(dockingPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
|
||||
@@ -154,6 +154,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("0,0", IsPropertySaveable.Yes)]
|
||||
public Point DisallowAttachingOverSize
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Should the item be attached to a wall by default when it's placed in the submarine editor.")]
|
||||
public bool AttachedByDefault
|
||||
{
|
||||
@@ -496,13 +503,19 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 diff = new Vector2(
|
||||
(heldHand.SimPosition.X - arm.SimPosition.X) / 2f,
|
||||
(heldHand.SimPosition.Y - arm.SimPosition.Y) / 2.5f);
|
||||
item.SetTransform(heldHand.SimPosition + diff, 0.0f);
|
||||
|
||||
//we have forced the item to be in the same sub as the dropper above,
|
||||
//and are placing it to the position of the hands in "local" coordinates
|
||||
//which may be outside the sub if the character is e.g. standing half-way through the airlock
|
||||
// -> let's use the forceSubmarine argument ensure the item is still considered to be in the sub's coordinate space,
|
||||
// or it will end up in a weird state and seemingly disappear
|
||||
item.SetTransform(heldHand.SimPosition + diff, 0.0f, forceSubmarine: picker.Submarine);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.SetTransform(picker.SimPosition, 0.0f);
|
||||
}
|
||||
}
|
||||
item.SetTransform(picker.SimPosition, 0.0f, forceSubmarine: picker.Submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
picker.Inventory.RemoveItem(item);
|
||||
@@ -621,17 +634,34 @@ namespace Barotrauma.Items.Components
|
||||
if (disallowAttachingOverTags.Any() || !AllowAttachInsideDoors)
|
||||
{
|
||||
var connectedHulls = item.CurrentHull?.GetConnectedHulls(includingThis: true, searchDepth: 5, ignoreClosedGaps: true);
|
||||
Vector2 size = item.Rect.Size.ToVector2() / 2;
|
||||
|
||||
Vector2 size = DisallowAttachingOverSize == Point.Zero ?
|
||||
item.Rect.Size.ToVector2() :
|
||||
DisallowAttachingOverSize.ToVector2() * item.Scale;
|
||||
size /= 2f;
|
||||
|
||||
foreach (Item otherItem in Item.ItemList)
|
||||
{
|
||||
if (otherItem == item || otherItem.body is { BodyType: BodyType.Dynamic, Enabled: true }) { continue; }
|
||||
if (connectedHulls != null && !connectedHulls.Contains(otherItem.CurrentHull)) { continue; }
|
||||
if (disallowAttachingOverTags.None(tag => otherItem.HasTag(tag)) &&
|
||||
if (disallowAttachingOverTags.None(otherItem.HasTag) &&
|
||||
(otherItem.GetComponent<Door>() == null || AllowAttachInsideDoors))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Rectangle worldRect = otherItem.WorldRect;
|
||||
|
||||
if (otherItem.GetComponent<Holdable>() is Holdable otherHoldable)
|
||||
{
|
||||
if (!otherHoldable.attached) { continue; }
|
||||
if (otherHoldable.DisallowAttachingOverSize != Point.Zero)
|
||||
{
|
||||
Vector2 scaledSize = otherHoldable.DisallowAttachingOverSize.ToVector2() * item.Scale;
|
||||
worldRect = new Rectangle(
|
||||
otherItem.WorldPosition.ToPoint() - new Point((int)(scaledSize.X / 2), (int)(-scaledSize.Y / 2)),
|
||||
scaledSize.ToPoint());
|
||||
}
|
||||
}
|
||||
if (attachPos.X + size.X < worldRect.X || attachPos.X - size.X > worldRect.Right) { continue; }
|
||||
if (attachPos.Y - size.Y > worldRect.Y || attachPos.Y + size.Y < worldRect.Y - worldRect.Height) { continue; }
|
||||
tempOverlappingItems.Add(otherItem);
|
||||
|
||||
@@ -539,8 +539,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (targetEntity != null)
|
||||
{
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, attackMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, attackMultiplier: damageMultiplier);
|
||||
}
|
||||
|
||||
if (DeleteOnUse)
|
||||
|
||||
@@ -922,7 +922,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float afflictionMultiplier = 1.0f)
|
||||
/// <param name="attackMultiplier">Multiplier used on afflictions caused by the status effects, except ones that <see cref="AfflictionPrefab.AffectedByAttackMultipliers">have been configured to not be affected by attack multipliers.</see></param>
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float attackMultiplier = 1.0f)
|
||||
{
|
||||
if (statusEffectLists == null) { return; }
|
||||
|
||||
@@ -934,7 +935,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (broken && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { continue; }
|
||||
if (user != null) { effect.SetUser(user); }
|
||||
effect.AfflictionMultiplier = afflictionMultiplier;
|
||||
effect.AttackMultiplier = attackMultiplier;
|
||||
var c = character;
|
||||
if (user != null && effect.HasTargetType(StatusEffect.TargetType.Character) && !effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
@@ -942,7 +943,7 @@ namespace Barotrauma.Items.Components
|
||||
c = user;
|
||||
}
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, c, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition);
|
||||
effect.AfflictionMultiplier = 1.0f;
|
||||
effect.AttackMultiplier = 1.0f;
|
||||
reducesCondition |= effect.ReducesItemCondition();
|
||||
}
|
||||
//if any of the effects reduce the item's condition, set the user for OnBroken effects as well
|
||||
|
||||
@@ -832,7 +832,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float FabricationDegreeOfSuccess(Character character, ImmutableArray<Skill> skills)
|
||||
{
|
||||
if (skills.Length == 0) { return 1.0f; }
|
||||
if (skills.Length == 0) { return 0.5f; }
|
||||
if (character == null) { return 0.0f; }
|
||||
|
||||
float minDegreeOfSuccess = 1.0f;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes the item inherit the condition from a linked wall or multiple - or in other words, makes it essentially treat the health of the wall as its own health.
|
||||
/// The wall section with the most damage determines the condition (i.e. the item will be fully broken if there's at least one fully broken wall section).
|
||||
/// </summary>
|
||||
class InheritConditionFromLinkedWall(Item item, ContentXElement element) : ItemComponent(item, element)
|
||||
{
|
||||
private readonly List<Structure> linkedWalls = [];
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
foreach (var linkedTo in item.linkedTo)
|
||||
{
|
||||
if (linkedTo is Structure structure &&
|
||||
structure.HasBody)
|
||||
{
|
||||
linkedWalls.Add(structure);
|
||||
structure.OnHealthChanged += (_, _) => UpdateCondition();
|
||||
}
|
||||
}
|
||||
if (linkedWalls.None())
|
||||
{
|
||||
DebugConsole.AddWarning($"The item {item.Name} ({item.Prefab.Identifier}) is not linked to any walls with a physics body. The {nameof(InheritConditionFromLinkedWall)} component will do nothing.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateCondition()
|
||||
{
|
||||
float lowestHealthPercent = 1.0f;
|
||||
foreach (var wall in linkedWalls)
|
||||
{
|
||||
foreach (var section in wall.Sections)
|
||||
{
|
||||
lowestHealthPercent = Math.Min(lowestHealthPercent, 1.0f - section.damage / wall.MaxHealth);
|
||||
}
|
||||
}
|
||||
item.Condition = item.MaxCondition * lowestHealthPercent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,7 @@ namespace Barotrauma.Items.Components
|
||||
item.CurrentHull.GetLinkedHulls(linkedHulls, includeHiddenHulls: true);
|
||||
foreach (var linkedHull in linkedHulls)
|
||||
{
|
||||
if (linkedHull == item.CurrentHull) { continue; }
|
||||
hullWaterVolume += linkedHull.WaterVolume;
|
||||
totalHullVolume += linkedHull.Volume;
|
||||
}
|
||||
@@ -148,7 +149,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!IsActive || Disabled) { return; }
|
||||
if (flowPercentage <= 0f && item.CurrentHull.WaterVolume <= 0f) { return; }
|
||||
|
||||
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor);
|
||||
float powerFactor = Math.Min(PowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor);
|
||||
|
||||
currFlow = flowPercentage / 100.0f * MaxFlow * powerFactor;
|
||||
if (item.GetComponent<Repairable>() is { IsTinkering: true } repairable)
|
||||
|
||||
@@ -247,9 +247,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
bool fissionRateControlledBySignals = signalControlledTargetFissionRate.HasValue && lastReceivedFissionRateSignalTime > Timing.TotalTime - 1;
|
||||
bool turbineOutputRateControlledBySignals = signalControlledTargetTurbineOutput.HasValue && lastReceivedTurbineOutputSignalTime > Timing.TotalTime - 1;
|
||||
|
||||
//rapidly adjust the reactor in the first few seconds of the round to prevent overvoltages if the load changed between rounds
|
||||
//(unless the reactor is being operated by a player)
|
||||
if (GameMain.GameSession is { RoundDuration: <5 } && lastUser is not { IsPlayer: true })
|
||||
if (GameMain.GameSession is { RoundDuration: < 5 } && lastUser is not { IsPlayer: true } && PowerOn && AutoTemp &&
|
||||
!fissionRateControlledBySignals && !turbineOutputRateControlledBySignals)
|
||||
{
|
||||
UpdateAutoTemp(100.0f, (float)(Timing.Step * 10.0f));
|
||||
}
|
||||
@@ -263,7 +267,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float maxPowerOut = GetMaxOutput();
|
||||
|
||||
if (signalControlledTargetFissionRate.HasValue && lastReceivedFissionRateSignalTime > Timing.TotalTime - 1)
|
||||
if (fissionRateControlledBySignals)
|
||||
{
|
||||
TargetFissionRate = adjustValueWithoutOverShooting(TargetFissionRate, signalControlledTargetFissionRate.Value, deltaTime * 5.0f);
|
||||
#if CLIENT
|
||||
@@ -274,7 +278,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
signalControlledTargetFissionRate = null;
|
||||
}
|
||||
if (signalControlledTargetTurbineOutput.HasValue && lastReceivedTurbineOutputSignalTime > Timing.TotalTime - 1)
|
||||
if (turbineOutputRateControlledBySignals)
|
||||
{
|
||||
TargetTurbineOutput = adjustValueWithoutOverShooting(TargetTurbineOutput, signalControlledTargetTurbineOutput.Value, deltaTime * 5.0f);
|
||||
#if CLIENT
|
||||
|
||||
@@ -641,8 +641,7 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < hits.Count; i++)
|
||||
{
|
||||
var h = hits[i];
|
||||
item.SetTransform(h.Point, rotation);
|
||||
item.Submarine = h.Submarine;
|
||||
item.SetTransform(h.Point, rotation, forceSubmarine: h.Submarine);
|
||||
item.UpdateTransform();
|
||||
if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
|
||||
{
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Barotrauma.Items.Components
|
||||
var inputBuilder = ImmutableArray.CreateBuilder<CircuitBoxInputConnection>();
|
||||
var outputBuilder = ImmutableArray.CreateBuilder<CircuitBoxOutputConnection>();
|
||||
|
||||
foreach (Connection conn in Item.Connections)
|
||||
foreach (Connection conn in Item.Connections.OrderBy(static c => c.DisplayOrder))
|
||||
{
|
||||
if (conn.IsOutput)
|
||||
{
|
||||
@@ -236,9 +236,7 @@ namespace Barotrauma.Items.Components
|
||||
cloneNode.ReplaceAllConnectionLabelOverrides(origNode.ConnectionLabelOverrides);
|
||||
}
|
||||
|
||||
if (!clonedContainedItems.Any()) { return; }
|
||||
|
||||
foreach (var origComp in original.Components)
|
||||
foreach (CircuitBoxComponent origComp in original.Components)
|
||||
{
|
||||
if (!clonedContainedItems.TryGetValue(origComp.Item.ID, out var clonedItem)) { continue; }
|
||||
var newComponent = new CircuitBoxComponent(origComp.ID, clonedItem, origComp.Position, this, origComp.UsedResource);
|
||||
@@ -661,6 +659,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
wire.From.Connection.CircuitBoxConnections.Remove(wire.To);
|
||||
wire.To.Connection.CircuitBoxConnections.Remove(wire.From);
|
||||
|
||||
if (wire.From is CircuitBoxInputConnection input)
|
||||
{
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Barotrauma.Items.Components
|
||||
//how many wires can be linked to this connection in total
|
||||
public readonly int MaxWires = 5;
|
||||
|
||||
public readonly int DisplayOrder;
|
||||
|
||||
public readonly string Name;
|
||||
private readonly LocalizedString _displayName;
|
||||
public LocalizedString DisplayName
|
||||
@@ -92,7 +94,7 @@ namespace Barotrauma.Items.Components
|
||||
return "Connection (" + item.Name + ", " + Name + ")";
|
||||
}
|
||||
|
||||
public Connection(ContentXElement element, ConnectionPanel connectionPanel, IdRemap idRemap)
|
||||
public Connection(ContentXElement element, int connectionIndex, ConnectionPanel connectionPanel, IdRemap idRemap, bool isItemSwap)
|
||||
{
|
||||
|
||||
#if CLIENT
|
||||
@@ -117,25 +119,44 @@ namespace Barotrauma.Items.Components
|
||||
IsOutput = element.Name.ToString() == "output";
|
||||
Name = element.GetAttributeString("name", IsOutput ? "output" : "input");
|
||||
|
||||
int displayOrder;
|
||||
if (element.GetAttribute("displayorderoverride") is not { } displayOrderAttr)
|
||||
{
|
||||
var sameElements = connectionPanel.Connections.Where(c => c.IsOutput == IsOutput);
|
||||
displayOrder = !sameElements.Any() ? 0 : sameElements.Max(static c => c.DisplayOrder) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayOrder = displayOrderAttr.GetAttributeInt(0);
|
||||
}
|
||||
|
||||
DisplayOrder = displayOrder;
|
||||
|
||||
string displayNameTag = "", fallbackTag = "";
|
||||
//if displayname is not present, attempt to find it from the prefab
|
||||
if (element.GetAttribute("displayname") == null)
|
||||
{
|
||||
foreach (var subElement in item.Prefab.ConfigElement.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("connectionpanel", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
if (!subElement.Name.ToString().Equals("connectionpanel", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
int prefabConnectionIndex = 0;
|
||||
foreach (XElement connectionElement in subElement.Elements())
|
||||
{
|
||||
string prefabConnectionName = connectionElement.GetAttributeString("name", null);
|
||||
if (prefabConnectionName.IsNullOrEmpty()) { continue; }
|
||||
|
||||
string[] aliases = connectionElement.GetAttributeStringArray("aliases", Array.Empty<string>());
|
||||
if (prefabConnectionName == Name || aliases.Contains(Name))
|
||||
if (prefabConnectionName == Name || aliases.Contains(Name) ||
|
||||
//when swapping items, we move wires based on the order of the connections, not the names
|
||||
//= we should find a connection based on the index if the name doesn't match
|
||||
(isItemSwap && connectionIndex == prefabConnectionIndex))
|
||||
{
|
||||
displayNameTag = connectionElement.GetAttributeString("displayname", "");
|
||||
fallbackTag = connectionElement.GetAttributeString("fallbackdisplayname", "");
|
||||
}
|
||||
prefabConnectionIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -78,10 +78,10 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "input":
|
||||
Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
|
||||
Connections.Add(new Connection(subElement, connectionIndex: Connections.Count, this, IdRemap.DiscardId, isItemSwap: false));
|
||||
break;
|
||||
case "output":
|
||||
Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
|
||||
Connections.Add(new Connection(subElement, connectionIndex: Connections.Count, this, IdRemap.DiscardId, isItemSwap: false));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -293,10 +293,10 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "input":
|
||||
loadedConnections.Add(new Connection(subElement, this, idRemap));
|
||||
loadedConnections.Add(new Connection(subElement, connectionIndex: loadedConnections.Count, this, idRemap, isItemSwap));
|
||||
break;
|
||||
case "output":
|
||||
loadedConnections.Add(new Connection(subElement, this, idRemap));
|
||||
loadedConnections.Add(new Connection(subElement, connectionIndex: loadedConnections.Count, this, idRemap, isItemSwap));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace Barotrauma.Items.Components;
|
||||
/// <summary>
|
||||
/// Base class for signal components that can select between input/output connections (e.g. multiplexer and demultiplexer components)
|
||||
/// </summary>
|
||||
abstract class ConnectionSelectorComponent : ItemComponent
|
||||
abstract partial class ConnectionSelectorComponent : ItemComponent, IServerSerializable
|
||||
{
|
||||
protected int selectedConnectionIndex;
|
||||
protected string selectedConnectionIndexStr;
|
||||
@@ -22,6 +23,8 @@ abstract class ConnectionSelectorComponent : ItemComponent
|
||||
get { return selectedConnectionIndex; }
|
||||
set
|
||||
{
|
||||
int prevIndex = selectedConnectionIndex; // store original, so we know if the state has changed and can sync it in MP
|
||||
|
||||
selectedConnectionIndex = Math.Max(0, value);
|
||||
//don't clamp until we've determined how many connections the item has
|
||||
//(can't be done until the connection panel component has been loaded too)
|
||||
@@ -31,6 +34,11 @@ abstract class ConnectionSelectorComponent : ItemComponent
|
||||
}
|
||||
selectedConnectionName = GetConnectionName(selectedConnectionIndex);
|
||||
selectedConnectionIndexStr = selectedConnectionIndex.ToString();
|
||||
|
||||
if (prevIndex != selectedConnectionIndex)
|
||||
{
|
||||
OnStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +63,8 @@ abstract class ConnectionSelectorComponent : ItemComponent
|
||||
{
|
||||
}
|
||||
|
||||
partial void OnStateChanged();
|
||||
|
||||
protected abstract string GetConnectionName(int connectionIndex);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -63,10 +63,7 @@ namespace Barotrauma.Items.Components
|
||||
/// This can be used to make them additionally work the other way around, periodically getting the current value of the property from the item and refreshing the UI.
|
||||
/// </summary>
|
||||
public float GetValueInterval { get; set; } = -1.0f;
|
||||
|
||||
#if CLIENT
|
||||
public float GetValueTimer;
|
||||
#endif
|
||||
|
||||
public string Name => "CustomInterfaceElement";
|
||||
|
||||
@@ -248,7 +245,7 @@ namespace Barotrauma.Items.Components
|
||||
ciElement.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal == ciElement.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(ciElement);
|
||||
IsActive |= ciElement.ContinuousSignal;
|
||||
IsActive |= ciElement.ContinuousSignal || ciElement.GetValueInterval > 0.0f;
|
||||
}
|
||||
|
||||
InitProjSpecific();
|
||||
@@ -348,13 +345,10 @@ namespace Barotrauma.Items.Components
|
||||
//make sure the clients know about the states of the checkboxes and text fields
|
||||
if (customInterfaceElementList.Any())
|
||||
{
|
||||
if (item.FullyInitialized)
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
if (!item.Removed) { item.CreateServerEvent(this); }
|
||||
}, delay: 0.1f);
|
||||
}
|
||||
if (item.FullyInitialized && !item.Removed) { item.CreateServerEvent(this); }
|
||||
}, delay: 0.1f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -418,6 +412,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
|
||||
{
|
||||
if (ciElement.GetValueInterval > 0.0f)
|
||||
{
|
||||
ciElement.GetValueTimer -= deltaTime;
|
||||
if (ciElement.GetValueTimer <= 0.0f)
|
||||
{
|
||||
SetSignalToPropertyValue(ciElement);
|
||||
ciElement.GetValueTimer = ciElement.GetValueInterval;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ciElement.ContinuousSignal && ciElement.PropertyName != "Voltage") { continue; }
|
||||
//TODO: allow changing output when a tickbox is not selected
|
||||
if (!string.IsNullOrEmpty(ciElement.Signal) && ciElement.Connection != null)
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, IsPropertySaveable.Yes, description: "How much light pulsates (in Hz). 0 = not at all, 1 = alternates between full brightness and off.")]
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, IsPropertySaveable.Yes, description: "How much light pulsates. 0 = not at all, 1 = alternates between full brightness and off.")]
|
||||
public float PulseAmount
|
||||
{
|
||||
get { return pulseAmount; }
|
||||
|
||||
@@ -268,7 +268,7 @@ namespace Barotrauma.Items.Components
|
||||
public float RotationSpeedHighSkill { get; private set; }
|
||||
|
||||
[Serialize("0,0,0,0", IsPropertySaveable.Yes, description: "Optional screen tint color when the item is being operated (R,G,B,A)."),
|
||||
Editable]
|
||||
Editable(TransferToSwappedItem = true)]
|
||||
public Color HudTint { get; set; }
|
||||
|
||||
[Header(localizedTextTag: "sp.turret.AutoOperate.propertyheader")]
|
||||
|
||||
@@ -462,10 +462,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public float ImpactTolerance => Prefab.ImpactTolerance;
|
||||
private float impactTolerance;
|
||||
[Serialize(0.0f, IsPropertySaveable.No), ConditionallyEditable(ConditionallyEditable.ConditionType.ReceivesSubmarineImpacts, MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float ImpactTolerance
|
||||
{
|
||||
get { return impactTolerance; }
|
||||
set { impactTolerance = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
public float ImpactDamage => Prefab.ImpactDamage;
|
||||
public float ImpactDamageProbability => Prefab.ImpactDamageProbability;
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "The amount of damage the item takes from impacts. Acts as a multiplier on the strength of the impact. Note that ImpactTolerance must be set for impacts to register."),
|
||||
ConditionallyEditable(ConditionallyEditable.ConditionType.ReceivesSubmarineImpacts, MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float ImpactDamage { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "Probability for impacts to register. Defaults to 1. Note that ImpactTolerance must also be set for impacts to register."),
|
||||
ConditionallyEditable(ConditionallyEditable.ConditionType.ReceivesSubmarineImpacts, MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float ImpactDamageProbability { get; set; }
|
||||
|
||||
public const float SubmarineImpactCooldown = 0.1f;
|
||||
|
||||
public double LastSubmarineImpactTime;
|
||||
|
||||
public float InteractDistance => Prefab.InteractDistance;
|
||||
|
||||
@@ -1559,7 +1574,7 @@ namespace Barotrauma
|
||||
if (!updateableComponents.Contains(component))
|
||||
{
|
||||
updateableComponents.Add(component);
|
||||
this.isActive = true;
|
||||
this.IsActive = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1650,7 +1665,19 @@ namespace Barotrauma
|
||||
contained.Container = null;
|
||||
}
|
||||
|
||||
public void SetTransform(Vector2 simPosition, float rotation, bool findNewHull = true, bool setPrevTransform = true)
|
||||
/// <summary>
|
||||
/// Sets the position and rotation of the item, and its physics body if it has one.
|
||||
/// </summary>
|
||||
/// <param name="simPosition">Position in simulation units.</param>
|
||||
/// <param name="rotation">Rotation in radians</param>
|
||||
/// <param name="findNewHull">Should the hull the item is inside be immediately updated? Generally only useful to set to false
|
||||
/// for performance reasons when finding the hull is unnecessary (e.g. if it's being forced to something after setting the transform).</param>
|
||||
/// <param name="setPrevTransform">Should the previous transform of the item be immediately set to the new one?
|
||||
/// The previous transform is used to interpolate draw positions/rotations, and you should generally only set this to false if
|
||||
/// you're trying to simulate movement instead of simply teleporting the item somewhere.</param>
|
||||
/// <param name="forceSubmarine">If you know the position is in a specific sub's coordinate space and want to ensure the item
|
||||
/// is still considered to be in that sub even if the transform ended up outside hulls.</param>
|
||||
public void SetTransform(Vector2 simPosition, float rotation, bool findNewHull = true, bool setPrevTransform = true, Submarine forceSubmarine = null)
|
||||
{
|
||||
if (!MathUtils.IsValid(simPosition))
|
||||
{
|
||||
@@ -1688,6 +1715,7 @@ namespace Barotrauma
|
||||
rect.Y = (int)MathF.Round(displayPos.Y + rect.Height / 2.0f);
|
||||
|
||||
if (findNewHull) { FindHull(); }
|
||||
if (forceSubmarine != null) { Submarine = forceSubmarine; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1859,7 +1887,7 @@ namespace Barotrauma
|
||||
if (newRootContainer != RootContainer)
|
||||
{
|
||||
RootContainer = newRootContainer;
|
||||
isActive = true;
|
||||
IsActive = true;
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
containedItem.RefreshRootContainer();
|
||||
@@ -2374,12 +2402,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool isActive = true;
|
||||
/// <summary>
|
||||
/// Inactive items are not updated. Note that actions such as dropping can reactivate the item, and that the item can go inactive by itself if it no longer needs updating;
|
||||
/// </summary>
|
||||
public bool IsActive = true;
|
||||
|
||||
public bool IsInRemoveQueue;
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!isActive || IsLayerHidden || IsInRemoveQueue) { return; }
|
||||
if (!IsActive || IsLayerHidden || IsInRemoveQueue) { return; }
|
||||
|
||||
if (impactQueue != null)
|
||||
{
|
||||
@@ -2545,7 +2577,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
positionBuffer.Clear();
|
||||
#endif
|
||||
isActive = false;
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2706,7 +2738,7 @@ namespace Barotrauma
|
||||
impactQueue.Enqueue(impact);
|
||||
}
|
||||
|
||||
isActive = true;
|
||||
IsActive = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -3496,7 +3528,7 @@ namespace Barotrauma
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
isActive = true;
|
||||
IsActive = true;
|
||||
body.Enabled = true;
|
||||
body.PhysEnabled = true;
|
||||
body.ResetDynamics();
|
||||
@@ -3636,7 +3668,7 @@ namespace Barotrauma
|
||||
item.body.Enabled = item.body.PhysEnabled = isFirst;
|
||||
if (isFirst)
|
||||
{
|
||||
item.isActive = true;
|
||||
item.IsActive = true;
|
||||
item.body.ResetDynamics();
|
||||
}
|
||||
}
|
||||
@@ -4401,13 +4433,18 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var connection in thisConnectionPanel.Connections)
|
||||
{
|
||||
var newConnection = newConnectionPanel.Connections.FirstOrDefault(c => c.Name == connection.Name);
|
||||
if (newConnection == null) { continue; }
|
||||
foreach (var wire in connection.Wires)
|
||||
{
|
||||
int connectionIndex = wire.Connections.IndexOf(connection);
|
||||
int wireConnectionIndex = wire.Connections.IndexOf(connection);
|
||||
wire.RemoveConnection(this);
|
||||
wire.Connect(newConnection, connectionIndex, addNode: false);
|
||||
int thisConnectionIndex = connection.ConnectionPanel.Connections.IndexOf(connection);
|
||||
if (thisConnectionIndex < 0 || thisConnectionIndex >= newConnectionPanel.Connections.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to move a wire from the connection {connection.Name} when swapping the item {Name} with {newItem.Name}. The new item probably does not have the same number of connections as the previous one.");
|
||||
continue;
|
||||
}
|
||||
Connection newConnection = newConnectionPanel.Connections[thisConnectionIndex];
|
||||
wire.Connect(newConnection, wireConnectionIndex, addNode: false);
|
||||
newConnection.ConnectWire(wire);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,20 +813,6 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DamagedByMonsters { get; private set; }
|
||||
|
||||
private float impactTolerance;
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float ImpactTolerance
|
||||
{
|
||||
get { return impactTolerance; }
|
||||
set { impactTolerance = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "The amount of damage the item takes from impacts. Acts as a multiplier on the strength of the impact. Note that ImpactTolerance must be set for impacts to register.")]
|
||||
public float ImpactDamage { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "Probability for impacts to register. Defaults to 1. Note that ImpactTolerance must also be set for impacts to register.")]
|
||||
public float ImpactDamageProbability { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, "If true, submarine impacts will trigger OnImpact effects. Only applies to items with a null or non-dynamic physics body - items with dynamic bodies always react to impacts.")]
|
||||
public bool ReceiveSubmarineImpacts { get; set; }
|
||||
|
||||
|
||||
@@ -404,7 +404,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker, displayRange);
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -465,12 +465,12 @@ namespace Barotrauma
|
||||
|
||||
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
|
||||
|
||||
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
|
||||
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker, float range)
|
||||
{
|
||||
if (attack.Range <= 0.0f) { return; }
|
||||
if (range <= 0.0f) { return; }
|
||||
|
||||
//long range for the broad distance check, because large characters may still be in range even if their collider isn't
|
||||
float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f);
|
||||
float broadRange = Math.Max(range * 10.0f, 10000.0f);
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
@@ -518,7 +518,7 @@ namespace Barotrauma
|
||||
float limbRadius = limb.body.GetMaxExtent();
|
||||
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
|
||||
|
||||
if (dist > attack.Range) { continue; }
|
||||
if (dist > range) { continue; }
|
||||
|
||||
float distFactor =
|
||||
DistanceFalloff ?
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user