Unstable 0.1500.6.0 (1984 edition)

This commit is contained in:
Markus Isberg
2021-10-09 00:22:02 +09:00
parent 08bdfc6cea
commit c8943ef9c4
96 changed files with 1817 additions and 559 deletions
@@ -284,7 +284,7 @@ namespace Barotrauma
limb.LastImpactSoundTime = (float)Timing.TotalTime; limb.LastImpactSoundTime = (float)Timing.TotalTime;
if (!string.IsNullOrWhiteSpace(limb.HitSoundTag)) if (!string.IsNullOrWhiteSpace(limb.HitSoundTag))
{ {
bool inWater = limb.inWater; bool inWater = limb.InWater;
if (character.CurrentHull != null && if (character.CurrentHull != null &&
character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height + 5.0f && character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height + 5.0f &&
limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent()) limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent())
@@ -348,16 +348,27 @@ namespace Barotrauma
float increment = 0.001f; float increment = 0.001f;
foreach (Character otherCharacter in Character.CharacterList) foreach (Character otherCharacter in Character.CharacterList)
{ {
if (otherCharacter == character) continue; if (otherCharacter == character) { continue; }
startDepth += increment; startDepth += increment;
} }
//make sure each limb has a distinct depth value //make sure each limb has a distinct depth value
List<Limb> depthSortedLimbs = Limbs.OrderBy(l => l.ActiveSprite == null ? 0.0f : l.ActiveSprite.Depth).ToList(); List<Limb> depthSortedLimbs = Limbs.OrderBy(l => l.DefaultSpriteDepth).ToList();
foreach (Limb limb in Limbs) foreach (Limb limb in Limbs)
{ {
if (limb.ActiveSprite != null) if (limb.ActiveSprite == null) { continue; }
limb.ActiveSprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f; limb.ActiveSprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f;
} }
foreach (Limb limb in Limbs)
{
if (limb.ActiveSprite == null) { continue; }
if (limb.Params.InheritLimbDepth == LimbType.None) { continue; }
var matchingLimb = GetLimb(limb.Params.InheritLimbDepth);
if (matchingLimb != null)
{
limb.ActiveSprite.Depth = matchingLimb.ActiveSprite.Depth - 0.0000001f;
}
}
depthSortedLimbs.Reverse(); depthSortedLimbs.Reverse();
inversedLimbDrawOrder = depthSortedLimbs.ToArray(); inversedLimbDrawOrder = depthSortedLimbs.ToArray();
} }
@@ -567,6 +578,11 @@ namespace Barotrauma
pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos); pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos);
if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.Position; } if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.Position; }
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true); GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true);
Vector2 aimPos = humanoid.AimSourceWorldPos;
aimPos.Y = -aimPos.Y;
GUI.DrawLine(spriteBatch, aimPos - Vector2.UnitY * 3, aimPos + Vector2.UnitY * 3, Color.Red);
GUI.DrawLine(spriteBatch, aimPos - Vector2.UnitX * 3, aimPos + Vector2.UnitX * 3, Color.Red);
} }
if (character.MemState.Count > 1) if (character.MemState.Count > 1)
@@ -405,7 +405,7 @@ namespace Barotrauma
if (GameMain.NetworkMember.RespawnManager?.UseRespawnPrompt ?? false) if (GameMain.NetworkMember.RespawnManager?.UseRespawnPrompt ?? false)
{ {
CoroutineManager.InvokeAfter(() => CoroutineManager.Invoke(() =>
{ {
if (controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; } if (controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; }
var respawnPrompt = new GUIMessageBox( var respawnPrompt = new GUIMessageBox(
@@ -1052,8 +1052,18 @@ namespace Barotrauma
Position + Vector2.UnitY * 150.0f, Position + Vector2.UnitY * 150.0f,
Vector2.UnitY * 10.0f, Vector2.UnitY * 10.0f,
playSound: true, playSound: true,
subId: Submarine?.ID ?? -1);; subId: Submarine?.ID ?? -1);
} }
} }
partial void OnTalentGiven(string talentIdentifier)
{
GUI.AddMessage(TextManager.Get("talentname." + talentIdentifier.ToString()),
GUI.Style.Yellow,
Position + Vector2.UnitY * 150.0f,
Vector2.UnitY * 10.0f,
playSound: true,
subId: Submarine?.ID ?? -1);
}
} }
} }
@@ -680,7 +680,7 @@ namespace Barotrauma
createAttachmentSlider(info.MoustacheIndex, WearableType.Moustache); createAttachmentSlider(info.MoustacheIndex, WearableType.Moustache);
createAttachmentSlider(info.FaceAttachmentIndex, WearableType.FaceAttachment); createAttachmentSlider(info.FaceAttachmentIndex, WearableType.FaceAttachment);
void createColorSelector(string labelTag, IEnumerable<Color> options, Func<Color> getter, void createColorSelector(string labelTag, IEnumerable<(Color Color, float Commonness)> options, Func<Color> getter,
Action<Color> setter) Action<Color> setter)
{ {
var selectorItemRT = createItemRectTransform(labelTag, 0.4f); var selectorItemRT = createItemRectTransform(labelTag, 0.4f);
@@ -714,7 +714,7 @@ namespace Barotrauma
dropdown.ListBox.Content.RectTransform), dropdown.ListBox.Content.RectTransform),
style: "ListBoxElement") style: "ListBoxElement")
{ {
UserData = option, UserData = option.Color,
CanBeFocused = true CanBeFocused = true
}; };
var colorElement = var colorElement =
@@ -723,9 +723,9 @@ namespace Barotrauma
scaleBasis: ScaleBasis.Smallest), scaleBasis: ScaleBasis.Smallest),
style: null) style: null)
{ {
Color = option, Color = option.Color,
HoverColor = option, HoverColor = option.Color,
OutlineColor = Color.Lerp(Color.Black, option, 0.5f), OutlineColor = Color.Lerp(Color.Black, option.Color, 0.5f),
CanBeFocused = false CanBeFocused = false
}; };
} }
@@ -784,19 +784,9 @@ namespace Barotrauma
{ {
OnClicked = (button, o) => OnClicked = (button, o) =>
{ {
var headPreset = info.Heads.Keys.GetRandom(Rand.RandSync.Unsynced); info.Head = new HeadInfo();
info.Head.gender = headPreset.Gender; info.SetGenderAndRace(Rand.RandSync.Unsynced);
info.Head.race = headPreset.Race; info.SetColors();
info.Head.HeadSpriteId = headPreset.ID;
info.Head.HairIndex = Rand.Int(countAttachmentsOfType(WearableType.Hair), Rand.RandSync.Unsynced);
info.Head.BeardIndex = Rand.Int(countAttachmentsOfType(WearableType.Beard), Rand.RandSync.Unsynced);
info.Head.MoustacheIndex = Rand.Int(countAttachmentsOfType(WearableType.Moustache), Rand.RandSync.Unsynced);
info.Head.FaceAttachmentIndex = Rand.Int(countAttachmentsOfType(WearableType.FaceAttachment), Rand.RandSync.Unsynced);
info.Head.HairColor = info.HairColors.GetRandom(Rand.RandSync.Unsynced);
info.Head.FacialHairColor = info.FacialHairColors.GetRandom(Rand.RandSync.Unsynced);
info.Head.SkinColor = info.SkinColors.GetRandom(Rand.RandSync.Unsynced);
RecreateFrameContents(); RecreateFrameContents();
info.RefreshHead(); info.RefreshHead();
@@ -998,10 +998,14 @@ namespace Barotrauma
if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null) if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null)
{ {
List<(Affliction affliction, string text)> statusIcons = new List<(Affliction affliction, string text)>(); List<(Affliction affliction, string text)> statusIcons = new List<(Affliction affliction, string text)>();
if (Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 5.0f) if (Character.InPressure)
{
statusIcons.Add((pressureAffliction, TextManager.Get("PressureHUDWarning"))); statusIcons.Add((pressureAffliction, TextManager.Get("PressureHUDWarning")));
}
if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold) if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold)
{
statusIcons.Add((oxygenLowAffliction, TextManager.Get("OxygenHUDWarning"))); statusIcons.Add((oxygenLowAffliction, TextManager.Get("OxygenHUDWarning")));
}
foreach (Affliction affliction in currentDisplayedAfflictions) foreach (Affliction affliction in currentDisplayedAfflictions)
{ {
@@ -109,6 +109,7 @@ namespace Barotrauma
private float wetTimer; private float wetTimer;
private float dripParticleTimer; private float dripParticleTimer;
private float deadTimer; private float deadTimer;
private Color? randomColor;
/// <summary> /// <summary>
/// Note that different limbs can share the same deformations. /// Note that different limbs can share the same deformations.
@@ -166,6 +167,8 @@ namespace Barotrauma
} }
} }
public float DefaultSpriteDepth { get; private set; }
public WearableSprite HuskSprite { get; private set; } public WearableSprite HuskSprite { get; private set; }
public WearableSprite HerpesSprite { get; private set; } public WearableSprite HerpesSprite { get; private set; }
@@ -309,12 +312,23 @@ namespace Barotrauma
Deformations.AddRange(deformations); Deformations.AddRange(deformations);
NonConditionalDeformations.AddRange(deformations); NonConditionalDeformations.AddRange(deformations);
break; break;
case "randomcolor":
randomColor = subElement.GetAttributeColorArray("colors", null)?.GetRandom();
if (randomColor.HasValue)
{
Params.GetSprite().Color = randomColor.Value;
}
break;
case "lightsource": case "lightsource":
LightSource = new LightSource(subElement, GetConditionalTarget()) LightSource = new LightSource(subElement, GetConditionalTarget())
{ {
ParentBody = body, ParentBody = body,
SpriteScale = Vector2.One * Scale * TextureScale SpriteScale = Vector2.One * Scale * TextureScale
}; };
if (randomColor.HasValue)
{
LightSource.Color = new Color(randomColor.Value.R, randomColor.Value.G, randomColor.Value.B, LightSource.Color.A);
}
InitialLightSourceColor = LightSource.Color; InitialLightSourceColor = LightSource.Color;
InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha; InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha;
break; break;
@@ -383,6 +397,7 @@ namespace Barotrauma
return deformations; return deformations;
} }
} }
DefaultSpriteDepth = ActiveSprite.Depth;
LightSource?.CheckConditionals(); LightSource?.CheckConditionals();
} }
@@ -571,8 +586,8 @@ namespace Barotrauma
{ {
foreach (ParticleEmitter emitter in character.DamageEmitters) foreach (ParticleEmitter emitter in character.DamageEmitters)
{ {
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; } if (InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; }
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
ParticlePrefab overrideParticle = null; ParticlePrefab overrideParticle = null;
foreach (DamageModifier damageModifier in result.AppliedDamageModifiers) foreach (DamageModifier damageModifier in result.AppliedDamageModifiers)
{ {
@@ -593,8 +608,8 @@ namespace Barotrauma
foreach (ParticleEmitter emitter in character.BloodEmitters) foreach (ParticleEmitter emitter in character.BloodEmitters)
{ {
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; } if (InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; }
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount); emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount);
} }
} }
@@ -618,7 +633,7 @@ namespace Barotrauma
} }
} }
if (inWater) if (InWater)
{ {
wetTimer = 1.0f; wetTimer = 1.0f;
} }
@@ -2447,14 +2447,44 @@ namespace Barotrauma
{ {
if (messages.Any(msg => msg.Text == message)) { return; } if (messages.Any(msg => msg.Text == message)) { return; }
messages.Add(new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? LargeFont)); messages.Add(new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? LargeFont));
if (playSound) SoundPlayer.PlayUISound(GUISoundType.UIMessage); if (playSound) { SoundPlayer.PlayUISound(GUISoundType.UIMessage); }
} }
public static void AddMessage(string message, Color color, Vector2 pos, Vector2 velocity, float lifeTime = 3.0f, bool playSound = true, GUISoundType soundType = GUISoundType.UIMessage, int subId = -1) public static void AddMessage(string message, Color color, Vector2 pos, Vector2 velocity, float lifeTime = 3.0f, bool playSound = true, GUISoundType soundType = GUISoundType.UIMessage, int subId = -1)
{ {
Submarine sub = Submarine.Loaded.FirstOrDefault(s => s.ID == subId); Submarine sub = Submarine.Loaded.FirstOrDefault(s => s.ID == subId);
messages.Add(new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, LargeFont, sub: sub));
if (playSound) SoundPlayer.PlayUISound(soundType); var newMessage = new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, LargeFont, sub: sub);
if (playSound) { SoundPlayer.PlayUISound(soundType); }
bool overlapFound = true;
int tries = 0;
while (overlapFound)
{
overlapFound = false;
foreach (var otherMessage in messages)
{
float xDiff = otherMessage.Pos.X - newMessage.Pos.X;
if (Math.Abs(xDiff) > (newMessage.Size.X + otherMessage.Size.X) / 2) { continue; }
float yDiff = otherMessage.Pos.Y - newMessage.Pos.Y;
if (Math.Abs(yDiff) > (newMessage.Size.Y + otherMessage.Size.Y) / 2) { continue; }
Vector2 moveDir = -(new Vector2(xDiff, yDiff) + Rand.Vector(1.0f));
if (moveDir.LengthSquared() > 0.0001f)
{
moveDir = Vector2.Normalize(moveDir);
}
else
{
moveDir = Rand.Vector(1.0f);
}
moveDir.Y = -Math.Abs(moveDir.Y);
newMessage.Pos += moveDir * 20;
overlapFound = true;
}
tries++;
if (tries > 20) { break; }
}
messages.Add(newMessage);
} }
public static void ClearMessages() public static void ClearMessages()
@@ -41,13 +41,13 @@ namespace Barotrauma
public SpriteSheet SavingIndicator { get; private set; } public SpriteSheet SavingIndicator { get; private set; }
public UISprite UIGlow { get; private set; } public UISprite UIGlow { get; private set; }
public UISprite TalentGlow { get; private set; }
public UISprite PingCircle { get; private set; } public UISprite PingCircle { get; private set; }
public UISprite UIGlowCircular { get; private set; } public UISprite UIGlowCircular { get; private set; }
public UISprite UIGlowSolidCircular { get; private set; } public UISprite UIGlowSolidCircular { get; private set; }
public UISprite UIThermalGlow { get; private set; }
public UISprite ButtonPulse { get; private set; } public UISprite ButtonPulse { get; private set; }
@@ -91,8 +91,8 @@ namespace Barotrauma
public Color TextColorDark { get; private set; } = Color.Black * 0.9f; public Color TextColorDark { get; private set; } = Color.Black * 0.9f;
public Color TextColorDim { get; private set; } = Color.White * 0.6f; public Color TextColorDim { get; private set; } = Color.White * 0.6f;
public Color ItemQualityColorPoor { get; private set; } = Color.Gray; public Color ItemQualityColorPoor { get; private set; } = Color.DarkRed;
public Color ItemQualityColorNormal { get; private set; } = Color.White; public Color ItemQualityColorNormal { get; private set; } = Color.Gray;
public Color ItemQualityColorGood { get; private set; } = Color.LightGreen; public Color ItemQualityColorGood { get; private set; } = Color.LightGreen;
public Color ItemQualityColorExcellent { get; private set; } = Color.LightBlue; public Color ItemQualityColorExcellent { get; private set; } = Color.LightBlue;
public Color ItemQualityColorMasterwork { get; private set; } = Color.MediumPurple; public Color ItemQualityColorMasterwork { get; private set; } = Color.MediumPurple;
@@ -250,9 +250,6 @@ namespace Barotrauma
case "uiglow": case "uiglow":
UIGlow = new UISprite(subElement); UIGlow = new UISprite(subElement);
break; break;
case "talentglow":
TalentGlow = new UISprite(subElement);
break;
case "pingcircle": case "pingcircle":
PingCircle = new UISprite(subElement); PingCircle = new UISprite(subElement);
break; break;
@@ -268,6 +265,9 @@ namespace Barotrauma
case "uiglowsolidcircular": case "uiglowsolidcircular":
UIGlowSolidCircular = new UISprite(subElement); UIGlowSolidCircular = new UISprite(subElement);
break; break;
case "uithermalglow":
UIThermalGlow = new UISprite(subElement);
break;
case "endroundbuttonpulse": case "endroundbuttonpulse":
ButtonPulse = new UISprite(subElement); ButtonPulse = new UISprite(subElement);
break; break;
@@ -141,6 +141,10 @@ namespace Barotrauma
{ {
int talentCount = selectedTalents.Count - controlled.Info.UnlockedTalents.Count; int talentCount = selectedTalents.Count - controlled.Info.UnlockedTalents.Count;
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{
talentApplyButton.Flash(GUI.Style.Orange);
}
} }
if (selectedTab != InfoFrameTab.Crew) return; if (selectedTab != InfoFrameTab.Crew) return;
@@ -1184,7 +1188,7 @@ namespace Barotrauma
private Color unselectableColor = new Color(100, 100, 100, 225); private Color unselectableColor = new Color(100, 100, 100, 225);
private Color pressedColor = new Color(60, 60, 60, 225); private Color pressedColor = new Color(60, 60, 60, 225);
private readonly List<(GUIButton button, GUIComponent icon, GUIImage glow)> talentButtons = new List<(GUIButton button, GUIComponent icon, GUIImage glow)>(); private readonly List<(GUIButton button, GUIComponent icon)> talentButtons = new List<(GUIButton button, GUIComponent icon)>();
private readonly List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)> talentCornerIcons = new List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)>(); private readonly List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)> talentCornerIcons = new List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)>();
private List<string> selectedTalents = new List<string>(); private List<string> selectedTalents = new List<string>();
@@ -1453,9 +1457,7 @@ namespace Barotrauma
}; };
} }
GUIImage iconGlow = new GUIImage(new RectTransform(Vector2.One, iconImage.RectTransform, anchor: Anchor.Center), sprite: GUI.Style.TalentGlow.Sprite, scaleToFit: true) { Visible = false }; talentButtons.Add((talentButton, iconImage));
talentButtons.Add((talentButton, iconImage, iconGlow));
} }
talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight)); talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight));
@@ -1567,20 +1569,20 @@ namespace Barotrauma
string talentIdentifier = talentButton.button.UserData as string; string talentIdentifier = talentButton.button.UserData as string;
bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier); bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier);
Color newTalentColor = unselectable ? unselectableColor : unselectedColor; Color newTalentColor = unselectable ? unselectableColor : unselectedColor;
Color hoverColor = Color.White;
talentButton.glow.Visible = false;
if (controlledCharacter.HasTalent(talentIdentifier)) if (controlledCharacter.HasTalent(talentIdentifier))
{ {
newTalentColor = new Color(140,225,140,255); newTalentColor = GUI.Style.Green;
} }
else if (selectedTalents.Contains(talentIdentifier)) else if (selectedTalents.Contains(talentIdentifier))
{ {
newTalentColor = new Color(174,164,124,255); newTalentColor = GUI.Style.Orange;
talentButton.glow.Visible = true; hoverColor = Color.Lerp(GUI.Style.Orange, Color.White, 0.7f);
} }
talentButton.icon.Color = newTalentColor; talentButton.icon.Color = newTalentColor;
talentButton.icon.HoverColor = hoverColor;
} }
CreateTalentSkillList(controlledCharacter, skillListBox); CreateTalentSkillList(controlledCharacter, skillListBox);
@@ -1045,10 +1045,10 @@ namespace Barotrauma
public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true) public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true)
{ {
int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton); return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category));
} }
public static GUIFrame CreateUpgradeEntry(RectTransform parent, Sprite sprite, string title, string body, int price, object? userData, bool addBuyButton = true, bool addProgressBar = true, string buttonStyle = "UpgradeBuyButton") public static GUIFrame CreateUpgradeEntry(RectTransform parent, Sprite sprite, string title, string body, int price, object? userData, bool addBuyButton = true, bool addProgressBar = true, string buttonStyle = "UpgradeBuyButton", UpgradePrefab upgradePrefab = null, int currentLevel = 0)
{ {
float progressBarHeight = 0.25f; float progressBarHeight = 0.25f;
@@ -1089,7 +1089,7 @@ namespace Barotrauma
//negative price = refund //negative price = refund
if (price < 0) { formattedPrice = "+" + formattedPrice; } if (price < 0) { formattedPrice = "+" + formattedPrice; }
buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" }; buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" };
var priceText = new GUITextBlock(rectT(1, 0.4f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center); var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center);
if (price < 0) if (price < 0)
{ {
priceText.TextColor = GUI.Style.Green; priceText.TextColor = GUI.Style.Green;
@@ -1099,6 +1099,11 @@ namespace Barotrauma
priceText.Text = string.Empty; priceText.Text = string.Empty;
} }
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: buttonStyle) { Enabled = false }; new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: buttonStyle) { Enabled = false };
if (upgradePrefab != null)
{
var increaseText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), "", textAlignment: Alignment.Center);
UpdateUpgradePercentageText(increaseText, upgradePrefab, currentLevel);
}
} }
description.CalculateHeightFromText(); description.CalculateHeightFromText();
@@ -1127,6 +1132,19 @@ namespace Barotrauma
return prefabFrame; return prefabFrame;
} }
private static void UpdateUpgradePercentageText(GUITextBlock text, UpgradePrefab upgradePrefab, int currentLevel)
{
float nextIncrease = upgradePrefab.IncreaseOnTooltip * (Math.Min(currentLevel + 1, upgradePrefab.MaxLevel));
if (nextIncrease != 0f)
{
text.Text = $"{Math.Round(nextIncrease, 1)} %";
if (currentLevel == upgradePrefab.MaxLevel)
{
text.TextColor = Color.Gray;
}
}
}
private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item>? itemsOnSubmarine) private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item>? itemsOnSubmarine)
{ {
if (Campaign is null) { return; } if (Campaign is null) { return; }
@@ -1541,7 +1559,9 @@ namespace Barotrauma
if (prefabFrame.FindChild("buybutton", true) is { } buttonParent) if (prefabFrame.FindChild("buybutton", true) is { } buttonParent)
{ {
GUITextBlock priceLabel = buttonParent.GetChild<GUITextBlock>(); List<GUITextBlock> textBlocks = buttonParent.GetAllChildren<GUITextBlock>().ToList();
GUITextBlock priceLabel = textBlocks[0];
int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
if (priceLabel != null && !WaitForServerUpdate) if (priceLabel != null && !WaitForServerUpdate)
@@ -1562,6 +1582,11 @@ namespace Barotrauma
button.Enabled = false; button.Enabled = false;
} }
} }
GUITextBlock increaseLabel = textBlocks[1];
if (increaseLabel != null && !WaitForServerUpdate)
{
UpdateUpgradePercentageText(increaseLabel, prefab, currentLevel);
}
} }
} }
@@ -0,0 +1,61 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components
{
internal partial class EntitySpawnerComponent
{
public Vector2 DrawSize => Vector2.Zero;
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{
if (!editing) { return; }
switch (SpawnAreaShape)
{
case AreaShape.Rectangle:
{
RectangleF rect = GetAreaRectangle(SpawnAreaBounds, SpawnAreaOffset, draw: true);
GUI.DrawRectangle(spriteBatch, rect.Location, rect.Size, GUI.Style.Red, isFilled: false, 0f, 4f);
if (MaximumAmountRangePadding > 0f)
{
rect.Inflate(MaximumAmountRangePadding, MaximumAmountRangePadding);
GUI.DrawRectangle(spriteBatch, rect.Location, rect.Size, GUI.Style.Red, isFilled: false, 0f, 2f);
}
break;
}
case AreaShape.Circle:
Vector2 center = item.WorldPosition;
center.Y = -center.Y;
center += SpawnAreaOffset;
spriteBatch.DrawCircle(center, SpawnAreaRadius, 32, GUI.Style.Red, thickness: 4f);
if (MaximumAmountRangePadding > 0f)
{
spriteBatch.DrawCircle(center, SpawnAreaRadius + MaximumAmountRangePadding, 32, GUI.Style.Red, thickness: 2f);
}
break;
}
if (!OnlySpawnWhenCrewInRange) { return; }
switch (CrewAreaShape)
{
case AreaShape.Rectangle:
{
RectangleF rect = GetAreaRectangle(CrewAreaBounds, CrewAreaOffset, draw: true);
GUI.DrawRectangle(spriteBatch, rect.Location, rect.Size, GUI.Style.Green, isFilled: false, 0f, 4f);
break;
}
case AreaShape.Circle:
Vector2 center = item.WorldPosition;
center.Y = -center.Y;
center += CrewAreaOffset;
spriteBatch.DrawCircle(center, CrewAreaRadius, 32, GUI.Style.Green);
break;
}
}
}
}
@@ -342,7 +342,7 @@ namespace Barotrauma.Items.Components
new Vector2(currentItemPos.X, -currentItemPos.Y), new Vector2(currentItemPos.X, -currentItemPos.Y),
isWiringMode ? containedItem.GetSpriteColor() * 0.15f : containedItem.GetSpriteColor(), isWiringMode ? containedItem.GetSpriteColor() * 0.15f : containedItem.GetSpriteColor(),
origin, origin,
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation + MathHelper.ToRadians(-item.Rotation)), -(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation ),
containedItem.Scale, containedItem.Scale,
spriteEffects, spriteEffects,
depth: containedSpriteDepth); depth: containedSpriteDepth);
@@ -78,7 +78,6 @@ namespace Barotrauma.Items.Components
None, None,
HullStatus, HullStatus,
ElectricalView, ElectricalView,
HullCondition,
ItemFinder ItemFinder
} }
@@ -238,7 +237,6 @@ namespace Barotrauma.Items.Components
{ {
true when EnableHullStatus => MiniMapMode.HullStatus, true when EnableHullStatus => MiniMapMode.HullStatus,
true when EnableElectricalView => MiniMapMode.ElectricalView, true when EnableElectricalView => MiniMapMode.ElectricalView,
true when EnableHullCondition => MiniMapMode.HullCondition,
true when EnableItemFinder => MiniMapMode.ItemFinder, true when EnableItemFinder => MiniMapMode.ItemFinder,
_ => MiniMapMode.None _ => MiniMapMode.None
}; };
@@ -263,8 +261,7 @@ namespace Barotrauma.Items.Components
modeSwitchButtons = ImmutableArray.Create modeSwitchButtons = ImmutableArray.Create
( (
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullStatus") { UserData = MiniMapMode.HullStatus, Enabled = EnableHullStatus, ToolTip = TextManager.Get("StatusMonitorButton.HullStatus.Tooltip") }, new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullStatus") { UserData = MiniMapMode.HullStatus, Enabled = EnableHullStatus, ToolTip = TextManager.Get("StatusMonitorButton.HullStatus.Tooltip") },
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ElectricalView") { UserData = MiniMapMode.ElectricalView, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.ElectricalView.Tooltip") }, new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ElectricalView") { UserData = MiniMapMode.ElectricalView, Enabled = EnableElectricalView, ToolTip = TextManager.Get("StatusMonitorButton.ElectricalView.Tooltip") },
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullCondition") { UserData = MiniMapMode.HullCondition, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.HullCondition.Tooltip") },
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ItemFinder") { UserData = MiniMapMode.ItemFinder, Enabled = EnableItemFinder, ToolTip = TextManager.Get("StatusMonitorButton.ItemFinder.Tooltip") } new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ItemFinder") { UserData = MiniMapMode.ItemFinder, Enabled = EnableItemFinder, ToolTip = TextManager.Get("StatusMonitorButton.ItemFinder.Tooltip") }
); );
@@ -493,7 +490,7 @@ namespace Barotrauma.Items.Components
displayedSubs.Add(item.Submarine); displayedSubs.Add(item.Submarine);
displayedSubs.AddRange(item.Submarine.DockedTo); displayedSubs.AddRange(item.Submarine.DockedTo);
subEntities = MapEntity.mapEntityList.Where(me => me.Submarine == item.Submarine && !me.HiddenInGame).OrderByDescending(w => w.SpriteDepth).ToList(); subEntities = MapEntity.mapEntityList.Where(me => (item.Submarine is { } sub && sub.IsEntityFoundOnThisSub(me, includingConnectedSubs: true, allowDifferentType: false)) && !me.HiddenInGame).OrderByDescending(w => w.SpriteDepth).ToList();
BakeSubmarine(item.Submarine, parentRect); BakeSubmarine(item.Submarine, parentRect);
elementSize = GuiFrame.Rect.Size; elementSize = GuiFrame.Rect.Size;
@@ -598,7 +595,6 @@ namespace Barotrauma.Items.Components
if (currentMode == MiniMapMode.HullStatus && !EnableHullStatus || if (currentMode == MiniMapMode.HullStatus && !EnableHullStatus ||
currentMode == MiniMapMode.ElectricalView && !EnableElectricalView || currentMode == MiniMapMode.ElectricalView && !EnableElectricalView ||
currentMode == MiniMapMode.HullCondition && !EnableHullCondition ||
currentMode == MiniMapMode.ItemFinder && !EnableItemFinder) currentMode == MiniMapMode.ItemFinder && !EnableItemFinder)
{ {
SetDefaultMode(); SetDefaultMode();
@@ -606,8 +602,7 @@ namespace Barotrauma.Items.Components
modeSwitchButtons[0].Enabled = EnableHullStatus; modeSwitchButtons[0].Enabled = EnableHullStatus;
modeSwitchButtons[1].Enabled = EnableElectricalView; modeSwitchButtons[1].Enabled = EnableElectricalView;
modeSwitchButtons[2].Enabled = EnableHullCondition; modeSwitchButtons[2].Enabled = EnableItemFinder;
modeSwitchButtons[3].Enabled = EnableItemFinder;
} }
private void UpdateIDCards(Submarine sub) private void UpdateIDCards(Submarine sub)
@@ -642,14 +637,14 @@ namespace Barotrauma.Items.Components
return; return;
} }
if (currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition) if (currentMode == MiniMapMode.HullStatus)
{ {
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.End(); spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect;
if (currentMode == MiniMapMode.HullCondition && item.Submarine != null) if (item.Submarine != null)
{ {
var sprite = GUI.Style.UIGlowSolidCircular?.Sprite; var sprite = GUI.Style.UIGlowSolidCircular?.Sprite;
float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f; float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f;
@@ -849,7 +844,6 @@ namespace Barotrauma.Items.Components
switch (currentMode) switch (currentMode)
{ {
case MiniMapMode.HullStatus: case MiniMapMode.HullStatus:
case MiniMapMode.HullCondition:
UpdateHullStatus(); UpdateHullStatus();
miniMapFrame.Visible = true; miniMapFrame.Visible = true;
reportFrame.Visible = true; reportFrame.Visible = true;
@@ -1089,7 +1083,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
bool hullsVisible = currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition; bool hullsVisible = currentMode == MiniMapMode.HullStatus;
foreach (var (entity, component) in hullStatusComponents) foreach (var (entity, component) in hullStatusComponents)
{ {
@@ -1202,7 +1196,7 @@ namespace Barotrauma.Items.Components
GameMain.GameScreen.BlueprintEffect.Parameters["width"].SetValue((float)texture.Width); GameMain.GameScreen.BlueprintEffect.Parameters["width"].SetValue((float)texture.Width);
GameMain.GameScreen.BlueprintEffect.Parameters["height"].SetValue((float)texture.Height); GameMain.GameScreen.BlueprintEffect.Parameters["height"].SetValue((float)texture.Height);
Color blueprintBlue = BlueprintBlue * currentMode switch { MiniMapMode.HullStatus => 0.1f, MiniMapMode.HullCondition => 0.1f, MiniMapMode.ElectricalView => 0.1f, _ => 0.5f }; Color blueprintBlue = BlueprintBlue * currentMode switch { MiniMapMode.HullStatus => 0.1f, MiniMapMode.ElectricalView => 0.1f, _ => 0.5f };
Vector2 origin = new Vector2(texture.Width / 2f, texture.Height / 2f); Vector2 origin = new Vector2(texture.Width / 2f, texture.Height / 2f);
float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom; float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom;
@@ -132,7 +132,15 @@ namespace Barotrauma.Items.Components
private bool isConnectedToSteering; private bool isConnectedToSteering;
private static string caveLabel, ruinLabel; private static string caveLabel;
[Serialize(false, false)]
public bool RightLayout
{
get;
set;
}
private bool AllowUsingMineralScanner => private bool AllowUsingMineralScanner =>
HasMineralScanner && !isConnectedToSteering; HasMineralScanner && !isConnectedToSteering;
@@ -316,7 +324,7 @@ namespace Barotrauma.Items.Components
"", warningColor, GUI.LargeFont, Alignment.Center); "", warningColor, GUI.LargeFont, Alignment.Center);
// Setup layout for nav terminal // Setup layout for nav terminal
if (isConnectedToSteering) if (isConnectedToSteering || RightLayout)
{ {
controlContainer.RectTransform.RelativeOffset = controlBoxOffset; controlContainer.RectTransform.RelativeOffset = controlBoxOffset;
controlContainer.RectTransform.SetPosition(Anchor.TopRight); controlContainer.RectTransform.SetPosition(Anchor.TopRight);
@@ -83,7 +83,7 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{ {
if (target == null) { return; } if (target == null || target.Removed) { return; }
Vector2 startPos = GetSourcePos(); Vector2 startPos = GetSourcePos();
startPos.Y = -startPos.Y; startPos.Y = -startPos.Y;
@@ -103,7 +103,7 @@ namespace Barotrauma.Items.Components
{ {
Vector2 barrelPos = FarseerPhysics.ConvertUnits.ToDisplayUnits(weapon.TransformedBarrelPos); Vector2 barrelPos = FarseerPhysics.ConvertUnits.ToDisplayUnits(weapon.TransformedBarrelPos);
barrelPos.Y = -barrelPos.Y; barrelPos.Y = -barrelPos.Y;
startPos += barrelPos * item.Scale; startPos += barrelPos;
} }
} }
Vector2 endPos = new Vector2(target.DrawPosition.X, target.DrawPosition.Y); Vector2 endPos = new Vector2(target.DrawPosition.X, target.DrawPosition.Y);
@@ -1,6 +1,7 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -58,13 +59,13 @@ namespace Barotrauma.Items.Components
}; };
float x = 1.0f / (1 + RequiredSignalCount); float x = 1.0f / (1 + RequiredSignalCount);
float y = (x * paddedFrame.Rect.Width) / paddedFrame.Rect.Height; float y = Math.Min((x * paddedFrame.Rect.Width) / paddedFrame.Rect.Height, 0.5f);
Vector2 relativeSize = new Vector2(x, y); Vector2 relativeSize = new Vector2(x, y);
var containerSection = new GUIFrame(new RectTransform(new Vector2(x, 1.0f), paddedFrame.RectTransform), style: null); var containerSection = new GUIFrame(new RectTransform(new Vector2(x, 1.0f), paddedFrame.RectTransform), style: null);
var containerSlot = new GUIFrame(new RectTransform(new Vector2(1.0f, y), containerSection.RectTransform, anchor: Anchor.Center), style: null); var containerSlot = new GUIFrame(new RectTransform(new Vector2(1.0f, y), containerSection.RectTransform, anchor: Anchor.Center), style: null);
containerHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.2f), containerSlot.RectTransform, Anchor.BottomCenter), style: null); containerHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.2f), containerSlot.RectTransform, Anchor.BottomCenter), style: null);
containerIndicator = new GUIImage(new RectTransform(new Vector2(0.5f, 0.5f * y), containerSection.RectTransform, anchor: Anchor.Center) { RelativeOffset = new Vector2(0.0f, 0.05f + 0.5f * y) }, containerIndicator = new GUIImage(new RectTransform(new Vector2(0.5f, 0.5f * (1.0f - y)), containerSection.RectTransform, anchor: Anchor.BottomCenter),
style: "IndicatorLightRed", scaleToFit: true); style: "IndicatorLightRed", scaleToFit: true);
for (int i = 0; i < RequiredSignalCount; i++) for (int i = 0; i < RequiredSignalCount; i++)
@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
} }
[Serialize(false, false)] [Serialize(false, false)]
public bool SeeThroughWalls public bool ThermalGoggles
{ {
get; get;
private set; private set;
@@ -76,6 +76,8 @@ namespace Barotrauma.Items.Components
private bool isEquippable; private bool isEquippable;
private float thermalEffectState;
public IEnumerable<Character> VisibleCharacters public IEnumerable<Character> VisibleCharacters
{ {
get get
@@ -109,6 +111,9 @@ namespace Barotrauma.Items.Components
refEntity = item; refEntity = item;
} }
thermalEffectState += deltaTime;
thermalEffectState %= 10000.0f;
if (updateTimer > 0.0f) if (updateTimer > 0.0f)
{ {
updateTimer -= deltaTime; updateTimer -= deltaTime;
@@ -125,7 +130,7 @@ namespace Barotrauma.Items.Components
if (dist < Range * Range) if (dist < Range * Range)
{ {
Vector2 diff = c.WorldPosition - refEntity.WorldPosition; Vector2 diff = c.WorldPosition - refEntity.WorldPosition;
if (SeeThroughWalls || Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) if (Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
{ {
visibleCharacters.Add(c); visibleCharacters.Add(c);
} }
@@ -180,21 +185,38 @@ namespace Barotrauma.Items.Components
} }
} }
if (SeeThroughWalls) if (ThermalGoggles)
{ {
spriteBatch.End(); spriteBatch.End();
GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(Color.Red.ToVector4() * (0.35f + (float)Math.Sin(Timing.TotalTime * 1.6f) * 0.05f)); GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(Color.Red.ToVector4() * (0.3f + MathF.Sin(thermalEffectState) * 0.05f));
GameMain.LightManager.SolidColorEffect.CurrentTechnique = GameMain.LightManager.SolidColorEffect.Techniques["SolidColorBlur"]; GameMain.LightManager.SolidColorEffect.CurrentTechnique = GameMain.LightManager.SolidColorEffect.Techniques["SolidColorBlur"];
GameMain.LightManager.SolidColorEffect.Parameters["blurDistance"].SetValue(0.03f + (float)Math.Sin(Timing.TotalTime) * 0.01f); GameMain.LightManager.SolidColorEffect.Parameters["blurDistance"].SetValue(0.01f + MathF.Sin(thermalEffectState) * 0.005f);
GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply(); GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect);
foreach (Character c in visibleCharacters) Entity refEntity = equipper;
if (!isEquippable || refEntity == null)
{ {
if (c == character || !c.Enabled || c.Removed) { continue; } refEntity = item;
}
foreach (Character c in Character.CharacterList)
{
if (c == character || !c.Enabled || c.Removed || c.Params.HideInThermalGoggles) { continue; }
if (!ShowDeadCharacters && c.IsDead) { continue; }
float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
if (dist > Range * Range) { continue; }
Sprite pingCircle = GUI.Style.UIThermalGlow.Sprite;
foreach (Limb limb in c.AnimController.Limbs) foreach (Limb limb in c.AnimController.Limbs)
{ {
limb.Draw(spriteBatch, Screen.Selected.Cam, disableDeformations: true); if (limb.Mass < 1.0f) { continue; }
float noise1 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.02f);
float noise2 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.008f);
Vector2 spriteScale = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) / pingCircle.size * (noise1 * 0.5f + 2f);
Vector2 drawPos = new Vector2(limb.body.DrawPosition.X + (noise1 - 0.5f) * 100, -limb.body.DrawPosition.Y + (noise2 - 0.5f) * 100);
pingCircle.Draw(spriteBatch, drawPos, 0.0f, scale: Math.Max(spriteScale.X, spriteScale.Y));
} }
} }
@@ -317,7 +317,7 @@ namespace Barotrauma
string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White);
toolTip = $"‖color:{colorStr}‖{name}‖color:end‖"; toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
if (item.Quality > 0) if (item.GetComponent<Quality>() != null)
{ {
// substring by to get rid of the empty space at start, text file should be adjusted // substring by to get rid of the empty space at start, text file should be adjusted
toolTip += $"\n{TextManager.GetWithVariable("itemname.quality" + item.Quality, "[itemname]", "", fallBackTag: "itemname.quality3")?.Substring(1)}"; toolTip += $"\n{TextManager.GetWithVariable("itemname.quality" + item.Quality, "[itemname]", "", fallBackTag: "itemname.quality3")?.Substring(1)}";
@@ -1567,7 +1567,7 @@ namespace Barotrauma
if (containedItem != null && itemContainer.Inventory.Capacity == 1) if (containedItem != null && itemContainer.Inventory.Capacity == 1)
{ {
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0)); int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0));
if (maxStackSize > 1) if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
{ {
containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize; containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize;
} }
@@ -721,6 +721,7 @@ namespace Barotrauma
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall")
{ {
ToolTip = TextManager.Get("MirrorEntityXToolTip"), ToolTip = TextManager.Get("MirrorEntityXToolTip"),
Enabled = Prefab.CanFlipX,
OnClicked = (button, data) => OnClicked = (button, data) =>
{ {
foreach (MapEntity me in SelectedList) foreach (MapEntity me in SelectedList)
@@ -734,6 +735,7 @@ namespace Barotrauma
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall") new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall")
{ {
ToolTip = TextManager.Get("MirrorEntityYToolTip"), ToolTip = TextManager.Get("MirrorEntityYToolTip"),
Enabled = Prefab.CanFlipY,
OnClicked = (button, data) => OnClicked = (button, data) =>
{ {
foreach (MapEntity me in SelectedList) foreach (MapEntity me in SelectedList)
@@ -197,7 +197,9 @@ namespace Barotrauma.Lights
float spriteRange = Math.Max( float spriteRange = Math.Max(
light.LightSprite.size.X * light.SpriteScale.X * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.X - 0.5f)), light.LightSprite.size.X * light.SpriteScale.X * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.X - 0.5f)),
light.LightSprite.size.Y * light.SpriteScale.Y * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.Y - 0.5f))); light.LightSprite.size.Y * light.SpriteScale.Y * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.Y - 0.5f)));
range = Math.Max(spriteRange, range);
float targetSize = Math.Max(light.LightTextureTargetSize.X, light.LightTextureTargetSize.Y);
range = Math.Max(Math.Max(spriteRange, targetSize), range);
} }
if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, range, viewRect)) { continue; } if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, range, viewRect)) { continue; }
activeLights.Add(light); activeLights.Add(light);
@@ -376,6 +376,22 @@ namespace Barotrauma.Lights
} }
} }
private Vector2 lightTextureTargetSize;
public Vector2 LightTextureTargetSize
{
get => lightTextureTargetSize;
set
{
NeedsRecalculation = true;
NeedsHullCheck = true;
lightTextureTargetSize = value;
}
}
public Vector2 LightTextureOffset { get; set; }
public Vector2 LightTextureScale { get; set; } = Vector2.One;
public float TextureRange public float TextureRange
{ {
get get
@@ -1228,10 +1244,19 @@ namespace Barotrauma.Lights
} }
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
LightSprite.Draw( Color color = new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) * CurrentBrightness);
spriteBatch, drawPos,
new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) * CurrentBrightness), if (LightTextureTargetSize != Vector2.Zero)
origin, -Rotation + MathHelper.ToRadians(LightSourceParams.Rotation), SpriteScale, LightSpriteEffect); {
LightSprite.DrawTiled(spriteBatch, drawPos, LightTextureTargetSize, color, startOffset: LightTextureOffset, textureScale: LightTextureScale);
}
else
{
LightSprite.Draw(
spriteBatch, drawPos,
color,
origin, -Rotation + MathHelper.ToRadians(LightSourceParams.Rotation), SpriteScale, LightSpriteEffect);
}
} }
if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f) if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
@@ -1318,7 +1343,7 @@ namespace Barotrauma.Lights
Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition; Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition;
lightEffect.World = lightEffect.World =
Matrix.CreateTranslation(-new Vector3(position, 0.0f)) * Matrix.CreateTranslation(-new Vector3(position, 0.0f)) *
Matrix.CreateRotationZ(rotateVertices) * Matrix.CreateRotationZ(rotateVertices - MathHelper.ToRadians(LightSourceParams.Rotation)) *
Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) * Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) *
transform; transform;
@@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Lights;
namespace Barotrauma namespace Barotrauma
{ {
@@ -1148,6 +1149,15 @@ namespace Barotrauma
var oldData = new List<Rectangle> { prevRect.Value }; var oldData = new List<Rectangle> { prevRect.Value };
SubEditorScreen.StoreCommand(new TransformCommand(new List<MapEntity> { this }, newData, oldData, true)); SubEditorScreen.StoreCommand(new TransformCommand(new List<MapEntity> { this }, newData, oldData, true));
} }
if (this is Structure structure)
{
foreach (LightSource light in structure.Lights)
{
light.LightTextureTargetSize = Rect.Size.ToVector2();
light.Position = rect.Location.ToVector2();
}
}
prevRect = null; prevRect = null;
} }
} }
@@ -20,6 +20,8 @@ namespace Barotrauma
private readonly Dictionary<DecorativeSprite, DecorativeSprite.State> spriteAnimState = new Dictionary<DecorativeSprite, DecorativeSprite.State>(); private readonly Dictionary<DecorativeSprite, DecorativeSprite.State> spriteAnimState = new Dictionary<DecorativeSprite, DecorativeSprite.State>();
public readonly List<LightSource> Lights = new List<LightSource>();
public override bool SelectableInEditor public override bool SelectableInEditor
{ {
get get
@@ -91,6 +93,22 @@ namespace Barotrauma
} }
} }
private void SetLightTextureOffset()
{
Vector2 textOffset = textureOffset;
if (FlippedX) { textOffset.X = -textOffset.X; }
if (FlippedY) { textOffset.Y = -textOffset.Y; }
foreach (LightSource light in Lights)
{
Vector2 bgOffset = new Vector2(
MathUtils.PositiveModulo((int)-textOffset.X, light.texture.Width),
MathUtils.PositiveModulo((int)-textOffset.Y, light.texture.Height));
light.LightTextureOffset = bgOffset;
}
}
public GUIComponent CreateEditingHUD(bool inGame = false) public GUIComponent CreateEditingHUD(bool inGame = false)
{ {
int heightScaled = (int)(20 * GUI.Scale); int heightScaled = (int)(20 * GUI.Scale);
@@ -267,6 +285,8 @@ namespace Barotrauma
//color = Color.Lerp(color, Color.Gold, 0.5f); //color = Color.Lerp(color, Color.Gold, 0.5f);
color = spriteColor; color = spriteColor;
Vector2 rectSize = rect.Size.ToVector2(); Vector2 rectSize = rect.Size.ToVector2();
if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; } if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; }
if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; } if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; }
@@ -288,7 +288,7 @@ namespace Barotrauma.Networking
heartbeatTimer = 5.0; heartbeatTimer = 5.0;
#if DEBUG #if DEBUG
CoroutineManager.InvokeAfter(() => CoroutineManager.Invoke(() =>
{ {
if (GameMain.Client == null) { return; } if (GameMain.Client == null) { return; }
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; } if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; }
@@ -3272,7 +3272,7 @@ namespace Barotrauma
oldProperties[color].Add(sEntity); oldProperties[color].Add(sEntity);
} }
List<ISerializableEntity> affected = entities.Select(t => t.Entity).Where(se => se is MapEntity { Removed: false }).ToList(); List<ISerializableEntity> affected = entities.Select(t => t.Entity).Where(se => se is MapEntity { Removed: false } || se is ItemComponent).ToList();
StoreCommand(new PropertyCommand(affected, property.Name, newColor, oldProperties)); StoreCommand(new PropertyCommand(affected, property.Name, newColor, oldProperties));
if (MapEntity.EditingHUD != null && (MapEntity.EditingHUD.UserData == entity || (!(entity is ItemComponent ic) || MapEntity.EditingHUD.UserData == ic.Item))) if (MapEntity.EditingHUD != null && (MapEntity.EditingHUD.UserData == entity || (!(entity is ItemComponent ic) || MapEntity.EditingHUD.UserData == ic.Item)))
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.5.0</Version> <Version>0.1500.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.5.0</Version> <Version>0.1500.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.5.0</Version> <Version>0.1500.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.5.0</Version> <Version>0.1500.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.5.0</Version> <Version>0.1500.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -1717,16 +1717,84 @@ namespace Barotrauma
{ {
foreach (Client c in GameMain.Server.ConnectedClients) foreach (Client c in GameMain.Server.ConnectedClients)
{ {
if (c.Character != revivedCharacter) continue; if (c.Character != revivedCharacter) { continue; }
//clients stop controlling the character when it dies, force control back //clients stop controlling the character when it dies, force control back
GameMain.Server.SetClientCharacter(c, revivedCharacter); GameMain.Server.SetClientCharacter(c, revivedCharacter);
break; break;
} }
} }
} }
); );
AssignOnClientRequestExecute(
"givetalent",
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
Character targetCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args, false);
if (targetCharacter == null) { return; }
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c =>
c.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
c.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase));
if (talentPrefab == null)
{
GameMain.Server.SendConsoleMessage("Couldn't find the talent \"" + args[0] + "\".", client);
return;
}
targetCharacter.GiveTalent(talentPrefab);
NewMessage($"Talent \"{talentPrefab.DisplayName}\" given to \"{targetCharacter.Name}\" by \"{client.Name}\".");
GameMain.Server.SendConsoleMessage($"Gave talent \"{talentPrefab.DisplayName}\" to \"{targetCharacter.Name}\".", client);
}
);
AssignOnClientRequestExecute(
"unlocktalents",
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
var targetCharacter = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled;
if (targetCharacter == null) { return; }
List<TalentTree> talentTrees = new List<TalentTree>();
if (args.Length == 0 || args[0].Equals("all", StringComparison.OrdinalIgnoreCase))
{
talentTrees.AddRange(TalentTree.JobTalentTrees.Values);
}
else
{
var job = JobPrefab.Prefabs.Find(jp => jp.Name != null && jp.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase));
if (job == null)
{
GameMain.Server.SendConsoleMessage($"Failed to find the job \"{args[0]}\".", client);
return;
}
if (!TalentTree.JobTalentTrees.TryGetValue(job.Identifier, out TalentTree talentTree))
{
GameMain.Server.SendConsoleMessage($"No talents configured for the job \"{args[0]}\".", client);
return;
}
talentTrees.Add(talentTree);
}
foreach (var talentTree in talentTrees)
{
foreach (var subTree in talentTree.TalentSubTrees)
{
foreach (var option in subTree.TalentOptionStages)
{
foreach (var talent in option.Talents)
{
targetCharacter.GiveTalent(talent);
NewMessage($"Talent \"{talent.DisplayName}\" given to \"{targetCharacter.Name}\" by \"{client.Name}\".");
GameMain.Server.SendConsoleMessage($"Gave talent \"{talent.DisplayName}\" to \"{targetCharacter.Name}\".", client);
NewMessage($"Unlocked talent \"{talent.DisplayName}\".");
}
}
}
}
}
);
AssignOnClientRequestExecute( AssignOnClientRequestExecute(
"freeze", "freeze",
(Client client, Vector2 cursorWorldPos, string[] args) => (Client client, Vector2 cursorWorldPos, string[] args) =>
@@ -200,7 +200,7 @@ namespace Barotrauma
} }
} }
public void SaveInventories() public void SavePlayers()
{ {
List<CharacterCampaignData> prevCharacterData = new List<CharacterCampaignData>(characterData); List<CharacterCampaignData> prevCharacterData = new List<CharacterCampaignData>(characterData);
//client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has a character) //client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has a character)
@@ -220,12 +220,13 @@ namespace Barotrauma
continue; continue;
} }
} }
if (c.Character?.Info == null) { continue; } var characterInfo = c.Character?.Info ?? c.CharacterInfo;
if (c.Character.IsDead && c.Character.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) if (characterInfo == null) { continue; }
if (characterInfo.CauseOfDeath?.Type != CauseOfDeathType.Disconnected)
{ {
continue; RespawnManager.ReduceCharacterSkills(characterInfo);
} }
c.CharacterInfo = c.Character.Info; c.CharacterInfo = characterInfo;
characterData.RemoveAll(cd => cd.MatchesClient(c)); characterData.RemoveAll(cd => cd.MatchesClient(c));
characterData.Add(new CharacterCampaignData(c)); characterData.Add(new CharacterCampaignData(c));
} }
@@ -313,7 +314,7 @@ namespace Barotrauma
if (success) if (success)
{ {
SaveInventories(); SavePlayers();
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
var panel2 = selectedWire.Connections[1]?.ConnectionPanel; var panel2 = selectedWire.Connections[1]?.ConnectionPanel;
if (panel2 != null && panel2 != this) { panel2.item.CreateServerEvent(panel2); } if (panel2 != null && panel2 != this) { panel2.item.CreateServerEvent(panel2); }
CoroutineManager.InvokeAfter(() => CoroutineManager.Invoke(() =>
{ {
item.CreateServerEvent(this); item.CreateServerEvent(this);
if (panel1 != null && panel1 != this) { panel1.item.CreateServerEvent(panel1); } if (panel1 != null && panel1 != this) { panel1.item.CreateServerEvent(panel1); }
@@ -1318,7 +1318,7 @@ namespace Barotrauma.Networking
Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage);
if (mpCampaign != null && Level.IsLoadedOutpost) if (mpCampaign != null && Level.IsLoadedOutpost)
{ {
mpCampaign.SaveInventories(); mpCampaign.SavePlayers();
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
} }
@@ -374,12 +374,7 @@ namespace Barotrauma.Networking
} }
else else
{ {
foreach (Skill skill in characterInfos[i].Job.Skills) ReduceCharacterSkills(characterInfos[i]);
{
var skillPrefab = characterInfos[i].Job.Prefab.Skills.Find(s => skill.Prefab == s);
if (skillPrefab == null) { continue; }
skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.X, SkillReductionOnCampaignMidroundRespawn);
}
} }
} }
} }
@@ -493,6 +488,17 @@ namespace Barotrauma.Networking
} }
} }
public static void ReduceCharacterSkills(CharacterInfo characterInfo)
{
if (characterInfo?.Job == null) { return; }
foreach (Skill skill in characterInfo.Job.Skills)
{
var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Prefab == s);
if (skillPrefab == null) { continue; }
skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.X, SkillReductionOnCampaignMidroundRespawn);
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{ {
msg.WriteRangedInteger((int)CurrentState, 0, Enum.GetNames(typeof(State)).Length); msg.WriteRangedInteger((int)CurrentState, 0, Enum.GetNames(typeof(State)).Length);
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.5.0</Version> <Version>0.1500.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -195,6 +195,7 @@
<OutpostModule file="Content/Map/RuinModules/Alien_Chasm2.sub" /> <OutpostModule file="Content/Map/RuinModules/Alien_Chasm2.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Chasm3.sub" /> <OutpostModule file="Content/Map/RuinModules/Alien_Chasm3.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Entrance2.sub" /> <OutpostModule file="Content/Map/RuinModules/Alien_Entrance2.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Entrance3.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Storage1R.sub" /> <OutpostModule file="Content/Map/RuinModules/Alien_Storage1R.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Storage1L.sub" /> <OutpostModule file="Content/Map/RuinModules/Alien_Storage1L.sub" />
<OutpostModule file="Content/Map/RuinModules/Alien_Offices1.sub" /> <OutpostModule file="Content/Map/RuinModules/Alien_Offices1.sub" />
@@ -105,7 +105,6 @@ namespace Barotrauma
protected readonly float colliderWidth; protected readonly float colliderWidth;
protected readonly float minGapSize; protected readonly float minGapSize;
protected readonly float minHullSize;
protected readonly float colliderLength; protected readonly float colliderLength;
protected readonly float avoidLookAheadDistance; protected readonly float avoidLookAheadDistance;
@@ -119,7 +118,6 @@ namespace Barotrauma
colliderLength = size.Y; colliderLength = size.Y;
avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f); avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f);
minGapSize = ConvertUnits.ToDisplayUnits(Math.Min(colliderWidth, colliderLength)); minGapSize = ConvertUnits.ToDisplayUnits(Math.Min(colliderWidth, colliderLength));
minHullSize = ConvertUnits.ToDisplayUnits(Math.Max(colliderLength, colliderWidth) * 1.1f);
} }
public virtual void OnAttacked(Character attacker, AttackResult attackResult) { } public virtual void OnAttacked(Character attacker, AttackResult attackResult) { }
@@ -148,6 +148,8 @@ namespace Barotrauma
private CirclePhase CirclePhase; private CirclePhase CirclePhase;
private float currentAttackIntensity; private float currentAttackIntensity;
private CoroutineHandle disableTailCoroutine;
private readonly IEnumerable<Body> myBodies; private readonly IEnumerable<Body> myBodies;
public LatchOntoAI LatchOntoAI { get; private set; } public LatchOntoAI LatchOntoAI { get; private set; }
@@ -556,7 +558,7 @@ namespace Barotrauma
} }
else else
{ {
if (Character.Submarine != null) if (Character.Submarine != null && Character.Params.UsePathFinding)
{ {
if (steeringManager != insideSteering) if (steeringManager != insideSteering)
{ {
@@ -678,8 +680,21 @@ namespace Barotrauma
} }
} }
float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition); float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget); float reactDist = GetPerceivingRange(SelectedAiTarget);
if (sqrDist > Math.Pow(reactDist + movementMargin, 2)) Vector2 offset = Vector2.Zero;
if (selectedTargetingParams != null)
{
if (selectedTargetingParams.ReactDistance > 0)
{
reactDist = selectedTargetingParams.ReactDistance;
}
offset = selectedTargetingParams.Offset;
}
if (offset != Vector2.Zero)
{
reactDist += offset.Length();
}
if (sqrDist > MathUtils.Pow2(reactDist + movementMargin))
{ {
movementMargin = State == AIState.FleeTo ? 0 : reactDist; movementMargin = State == AIState.FleeTo ? 0 : reactDist;
run = true; run = true;
@@ -692,17 +707,43 @@ namespace Barotrauma
{ {
SteeringManager.Reset(); SteeringManager.Reset();
Character.AnimController.TargetMovement = Vector2.Zero; Character.AnimController.TargetMovement = Vector2.Zero;
float force = Character.AnimController.SwimSlowParams.SteerTorque; if (Character.AnimController.InWater)
Character.AnimController.Collider.MoveToPos(SelectedAiTarget.Entity.SimPosition, force);
if (SelectedAiTarget.Entity is Item item)
{ {
Character.AnimController.Collider.SmoothRotate(MathHelper.ToRadians(item.Rotation), force); float force = Character.AnimController.Collider.Mass / 10;
Character.AnimController.Collider.MoveToPos(SelectedAiTarget.Entity.SimPosition + ConvertUnits.ToSimUnits(offset), force);
if (SelectedAiTarget.Entity is Item item)
{
float rotation = item.Rotation;
Character.AnimController.Collider.SmoothRotate(rotation, Character.AnimController.SwimFastParams.SteerTorque);
var mainLimb = Character.AnimController.MainLimb;
if (mainLimb.type == LimbType.Head)
{
mainLimb.body.SmoothRotate(rotation, Character.AnimController.SwimFastParams.HeadTorque);
}
else
{
mainLimb.body.SmoothRotate(rotation, Character.AnimController.SwimFastParams.TorsoTorque);
}
}
if (disableTailCoroutine == null && SelectedAiTarget.Entity is Item i && i.HasTag("guardianshelter"))
{
if (!CoroutineManager.IsCoroutineRunning(disableTailCoroutine))
{
disableTailCoroutine = CoroutineManager.Invoke(() =>
{
if (Character != null && !Character.Removed)
{
Character.AnimController.HideAndDisable(LimbType.Tail, ignoreCollisions: false);
}
}, 1f);
}
}
Character.AnimController.ApplyPose(
new Vector2(0, -1),
new Vector2(0, -1),
new Vector2(0, -1),
new Vector2(0, -1), footMoveForce: 1);
} }
Character.AnimController.ApplyPose(
new Vector2(0, -1),
new Vector2(0, -1),
new Vector2(0, -1),
new Vector2(0, -1), footMoveForce: 1);
} }
else else
{ {
@@ -895,7 +936,7 @@ namespace Barotrauma
else if (targetHulls.Any()) else if (targetHulls.Any())
{ {
patrolTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); patrolTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
var path = PathSteering.PathFinder.FindPath(Character.SimPosition, patrolTarget.SimPosition, minGapSize: minGapSize * 1.5f, nodeFilter: n => NodeFilter(n) && PatrolNodeFilter(n)); var path = PathSteering.PathFinder.FindPath(Character.SimPosition, patrolTarget.SimPosition, minGapSize: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n));
if (path.Unreachable) if (path.Unreachable)
{ {
@@ -926,7 +967,7 @@ namespace Barotrauma
} }
if (patrolTarget != null && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable) if (patrolTarget != null && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable)
{ {
PathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: n => NodeFilter(n) && PatrolNodeFilter(n)); PathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n));
return; return;
} }
} }
@@ -938,8 +979,6 @@ namespace Barotrauma
UpdateIdle(deltaTime, followLastTarget); UpdateIdle(deltaTime, followLastTarget);
} }
private bool NodeFilter(PathNode n) => n.Waypoint.CurrentHull == null || n.Waypoint.CurrentHull.Rect.Width > minHullSize && n.Waypoint.CurrentHull.Rect.Height > minHullSize;
private void FindTargetHulls() private void FindTargetHulls()
{ {
if (Character.Submarine == null) { return; } if (Character.Submarine == null) { return; }
@@ -1479,8 +1518,11 @@ namespace Barotrauma
if (!canAttack || distance > margin) if (!canAttack || distance > margin)
{ {
// Steer towards the target if in the same room and swimming // Steer towards the target if in the same room and swimming
if (Character.CurrentHull != null && ((Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) && // Ruins have walls/pillars inside hulls and therefore we should navigate around them using the path steering.
(targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull)))) if (Character.CurrentHull != null &&
Character.Submarine != null && !Character.Submarine.Info.IsRuin &&
(Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) &&
targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
{ {
Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition; Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition;
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(steerPos - myPos)); SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(steerPos - myPos));
@@ -1490,7 +1532,6 @@ namespace Barotrauma
pathSteering.SteeringSeek(steerPos, weight: 2, pathSteering.SteeringSeek(steerPos, weight: 2,
minGapWidth: minGapSize, minGapWidth: minGapSize,
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null),
nodeFilter: NodeFilter,
checkVisiblity: true); checkVisiblity: true);
if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
@@ -1526,7 +1567,7 @@ namespace Barotrauma
} }
else else
{ {
pathSteering.SteeringSeek(steerPos, weight: 5, minGapWidth: minGapSize, NodeFilter); pathSteering.SteeringSeek(steerPos, weight: 5, minGapWidth: minGapSize);
} }
} }
else else
@@ -1750,7 +1791,7 @@ namespace Barotrauma
{ {
if (pathSteering != null) if (pathSteering != null)
{ {
pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize, NodeFilter); pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize);
} }
else else
{ {
@@ -1762,7 +1803,7 @@ namespace Barotrauma
// Too close // Too close
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false); UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
} }
if (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2)) if (Character.CurrentHull == null && (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2)))
{ {
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 30); SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 30);
} }
@@ -2282,12 +2323,12 @@ namespace Barotrauma
State = AIState.Idle; State = AIState.Idle;
return; return;
} }
if (Character.CurrentHull != null && PathSteering != null) if (Character.CurrentHull != null && steeringManager == insideSteering)
{ {
// Inside // Inside, but not inside ruins
Character targetCharacter = SelectedAiTarget.Entity as Character;
if ((Character.AnimController.InWater || !Character.AnimController.CanWalk) && if ((Character.AnimController.InWater || !Character.AnimController.CanWalk) &&
(targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull) || Character.CanSeeTarget(SelectedAiTarget.Entity))) Character.Submarine != null && !Character.Submarine.Info.IsRuin &&
SelectedAiTarget.Entity is Character c && VisibleHulls.Contains(c.CurrentHull))
{ {
// Steer towards the target if in the same room and swimming // Steer towards the target if in the same room and swimming
Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition); Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition);
@@ -2299,11 +2340,12 @@ namespace Barotrauma
else else
{ {
// Use path finding // Use path finding
PathSteering.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), weight: 2, minGapWidth: minGapSize, NodeFilter); PathSteering.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), weight: 2, minGapWidth: minGapSize);
if (!PathSteering.IsPathDirty && PathSteering.CurrentPath.Unreachable) if (!PathSteering.IsPathDirty && PathSteering.CurrentPath.Unreachable)
{ {
// Can't reach // Can't reach
State = AIState.Idle; State = AIState.Idle;
IgnoreTarget(SelectedAiTarget);
return; return;
} }
} }
@@ -2419,10 +2461,12 @@ namespace Barotrauma
} }
else else
{ {
// Ignore all structures, items, and hulls inside wrecks and beacons // Ignore all structures, items, and hulls inside these subs.
if (aiTarget.Entity.Submarine != null) if (aiTarget.Entity.Submarine != null)
{ {
if (aiTarget.Entity.Submarine.Info.IsWreck || aiTarget.Entity.Submarine.Info.IsBeacon || UnattackableSubmarines.Contains(aiTarget.Entity.Submarine)) if (aiTarget.Entity.Submarine.Info.IsWreck ||
aiTarget.Entity.Submarine.Info.IsBeacon ||
UnattackableSubmarines.Contains(aiTarget.Entity.Submarine))
{ {
continue; continue;
} }
@@ -2433,6 +2477,7 @@ namespace Barotrauma
if (character.CurrentHull != null) { continue; } if (character.CurrentHull != null) { continue; }
// Ignore ruins // Ignore ruins
if (hull.Submarine == null) { continue; } if (hull.Submarine == null) { continue; }
if (hull.Submarine.Info.IsRuin) { continue; }
} }
Door door = null; Door door = null;
@@ -2448,14 +2493,6 @@ namespace Barotrauma
continue; continue;
} }
} }
if (door == null)
{
// Ignore items inside ruins, unless we are in the same hull. We can't target the ruin walls.
if (item.Submarine == null && item.CurrentHull != Character.CurrentHull)
{
continue;
}
}
foreach (var prio in AIParams.Targets) foreach (var prio in AIParams.Targets)
{ {
if (item.HasTag(prio.Tag)) if (item.HasTag(prio.Tag))
@@ -2499,6 +2536,7 @@ namespace Barotrauma
} }
if (s.IsPlatform) { continue; } if (s.IsPlatform) { continue; }
if (s.Submarine == null) { continue; } if (s.Submarine == null) { continue; }
if (s.Submarine.Info.IsRuin) { continue; }
bool isCharacterInside = character.CurrentHull != null; bool isCharacterInside = character.CurrentHull != null;
bool isInnerWall = s.prefab.Tags.Contains("inner"); bool isInnerWall = s.prefab.Tags.Contains("inner");
if (isInnerWall && !isCharacterInside) if (isInnerWall && !isCharacterInside)
@@ -2709,7 +2747,11 @@ namespace Barotrauma
{ {
dist *= 0.9f; dist *= 0.9f;
} }
if (!CanPerceive(aiTarget, dist)) { continue; }
if (!CanPerceive(aiTarget, dist, checkVisibility: SelectedAiTarget != aiTarget))
{
continue;
}
if (SelectedAiTarget == aiTarget) if (SelectedAiTarget == aiTarget)
{ {
@@ -2720,7 +2762,6 @@ namespace Barotrauma
//if the target is very close, the distance doesn't make much difference //if the target is very close, the distance doesn't make much difference
// -> just ignore the distance and attack whatever has the highest priority // -> just ignore the distance and attack whatever has the highest priority
dist = Math.Max(dist, 100.0f); dist = Math.Max(dist, 100.0f);
AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true); AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true);
if (Character.Submarine != null && !Character.Submarine.Info.IsRuin && Character.CurrentHull != null) if (Character.Submarine != null && !Character.Submarine.Info.IsRuin && Character.CurrentHull != null)
{ {
@@ -2907,6 +2948,7 @@ namespace Barotrauma
wallTarget = null; wallTarget = null;
if (SelectedAiTarget == null) { return; } if (SelectedAiTarget == null) { return; }
if (SelectedAiTarget.Entity == null) { return; } if (SelectedAiTarget.Entity == null) { return; }
if (!canAttackWalls) { return; }
if (HasValidPath(requireNonDirty: true)) { return; } if (HasValidPath(requireNonDirty: true)) { return; }
wallHits.Clear(); wallHits.Clear();
Structure wall = null; Structure wall = null;
@@ -3129,7 +3171,7 @@ namespace Barotrauma
{ {
_selectedAiTarget = null; _selectedAiTarget = null;
} }
else if (CanPerceive(_selectedAiTarget)) else if (CanPerceive(_selectedAiTarget, checkVisibility: false))
{ {
var memory = GetTargetMemory(_selectedAiTarget, false); var memory = GetTargetMemory(_selectedAiTarget, false);
if (memory != null) if (memory != null)
@@ -3367,6 +3409,12 @@ namespace Barotrauma
protected override void OnStateChanged(AIState from, AIState to) protected override void OnStateChanged(AIState from, AIState to)
{ {
LatchOntoAI?.DeattachFromBody(reset: true); LatchOntoAI?.DeattachFromBody(reset: true);
if (disableTailCoroutine != null)
{
CoroutineManager.StopCoroutines(disableTailCoroutine);
Character.AnimController.RestoreTemporarilyDisabled();
disableTailCoroutine = null;
}
Character.AnimController.ReleaseStuckLimbs(); Character.AnimController.ReleaseStuckLimbs();
AttackingLimb = null; AttackingLimb = null;
movementMargin = 0; movementMargin = 0;
@@ -3382,11 +3430,16 @@ namespace Barotrauma
private float GetPerceivingRange(AITarget target) => Math.Max(target.SightRange * Sight, target.SoundRange * Hearing); private float GetPerceivingRange(AITarget target) => Math.Max(target.SightRange * Sight, target.SoundRange * Hearing);
private bool CanPerceive(AITarget target, float dist = -1, float distSquared = -1) private bool CanPerceive(AITarget target, float dist = -1, float distSquared = -1, bool checkVisibility = false)
{ {
bool insideSightRange;
bool insideSoundRange;
checkVisibility = checkVisibility && Character.Submarine != null && target.Entity.Submarine == Character.Submarine;
if (dist > 0) if (dist > 0)
{ {
return dist <= target.SightRange * Sight || dist <= target.SoundRange * Hearing; insideSightRange = IsInRange(dist, target.SightRange, Sight);
if (!checkVisibility && insideSightRange) { return true; }
insideSoundRange = IsInRange(dist, target.SoundRange, Hearing);
} }
else else
{ {
@@ -3394,8 +3447,42 @@ namespace Barotrauma
{ {
distSquared = Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition); distSquared = Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition);
} }
return distSquared <= MathUtils.Pow(target.SightRange * Sight, 2) || distSquared <= MathUtils.Pow(target.SoundRange * Hearing, 2); insideSightRange = IsInRangeSqr(distSquared, target.SightRange, Sight);
if (!checkVisibility && insideSightRange) { return true; }
insideSoundRange = IsInRangeSqr(distSquared, target.SoundRange, Hearing);
} }
if (!checkVisibility)
{
return insideSightRange || insideSoundRange;
}
else
{
if (!insideSightRange && !insideSoundRange) { return false; }
// Inside the same submarine -> check whether the target is behind a wall
if (target.Entity is Character c && VisibleHulls.Contains(c.CurrentHull) || target.Entity is Item i && VisibleHulls.Contains(i.CurrentHull))
{
return insideSightRange || insideSoundRange;
}
else
{
// No line of sight to the target -> Ignore sight and use only half of the sound range
if (dist > 0)
{
return IsInRange(dist, target.SoundRange, Hearing / 2);
}
else
{
if (distSquared < 0)
{
distSquared = Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition);
}
return IsInRangeSqr(distSquared, target.SoundRange, Hearing / 2);
}
}
}
bool IsInRange(float dist, float range, float perception) => dist <= range * perception;
bool IsInRangeSqr(float distSquared, float range, float perception) => distSquared <= MathUtils.Pow2(range * perception);
} }
public void ReevaluateAttacks() public void ReevaluateAttacks()
@@ -34,9 +34,6 @@ namespace Barotrauma
private float flipTimer; private float flipTimer;
private const float FlipInterval = 0.5f; private const float FlipInterval = 0.5f;
private float teamChangeTimer;
private const float TeamChangeInterval = 0.5f;
public const float HULL_SAFETY_THRESHOLD = 40; public const float HULL_SAFETY_THRESHOLD = 40;
public const float HULL_LOW_OXYGEN_PERCENTAGE = 30; public const float HULL_LOW_OXYGEN_PERCENTAGE = 30;
@@ -1124,7 +1121,7 @@ namespace Barotrauma
{ {
(GameMain.GameSession?.GameMode as CampaignMode)?.OutpostNPCAttacked(Character, attacker, attackResult); (GameMain.GameSession?.GameMode as CampaignMode)?.OutpostNPCAttacked(Character, attacker, attackResult);
// Inform other NPCs // Inform other NPCs
if (cumulativeDamage > 1) if (cumulativeDamage > 1 || totalDamage >= 10)
{ {
InformOtherNPCs(cumulativeDamage); InformOtherNPCs(cumulativeDamage);
} }
@@ -26,7 +26,7 @@ namespace Barotrauma
public bool AttachToWalls { get; private set; } public bool AttachToWalls { get; private set; }
public bool AttachToCharacters { get; private set; } public bool AttachToCharacters { get; private set; }
private readonly float minDeattachSpeed, maxDeattachSpeed, maxAttachDuration; private readonly float minDeattachSpeed, maxDeattachSpeed, maxAttachDuration, coolDown;
private readonly float damageOnDetach, detachStun; private readonly float damageOnDetach, detachStun;
private readonly bool weld; private readonly bool weld;
private float deattachCheckTimer; private float deattachCheckTimer;
@@ -61,6 +61,7 @@ namespace Barotrauma
minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 5.0f); minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 5.0f);
maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 8.0f)); maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 8.0f));
maxAttachDuration = element.GetAttributeFloat("maxattachduration", -1.0f); maxAttachDuration = element.GetAttributeFloat("maxattachduration", -1.0f);
coolDown = element.GetAttributeFloat("cooldown", 2f);
damageOnDetach = element.GetAttributeFloat("damageondetach", 0.0f); damageOnDetach = element.GetAttributeFloat("damageondetach", 0.0f);
detachStun = element.GetAttributeFloat("detachstun", 0.0f); detachStun = element.GetAttributeFloat("detachstun", 0.0f);
localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("localattachpos", Vector2.Zero)); localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("localattachpos", Vector2.Zero));
@@ -283,6 +284,7 @@ namespace Barotrauma
if (maxAttachDuration > 0) if (maxAttachDuration > 0)
{ {
deattach = true; deattach = true;
attachCooldown = coolDown;
} }
if (!deattach && targetWall != null && targetSubmarine != null) if (!deattach && targetWall != null && targetSubmarine != null)
{ {
@@ -291,7 +293,7 @@ namespace Barotrauma
if (enemyAI.CanPassThroughHole(targetWall, targetSection)) if (enemyAI.CanPassThroughHole(targetWall, targetSection))
{ {
deattach = true; deattach = true;
attachCooldown = 2; attachCooldown = coolDown;
} }
if (!deattach) if (!deattach)
{ {
@@ -310,7 +312,7 @@ namespace Barotrauma
{ {
deattach = true; deattach = true;
character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true); character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true);
attachCooldown = detachStun * 2; attachCooldown = Math.Max(detachStun * 2, coolDown);
} }
} }
} }
@@ -1048,6 +1048,7 @@ namespace Barotrauma
if (closeEnough) if (closeEnough)
{ {
UseWeapon(deltaTime); UseWeapon(deltaTime);
character.AIController.SteeringManager.Reset();
} }
else if (!character.IsFacing(Enemy.WorldPosition)) else if (!character.IsFacing(Enemy.WorldPosition))
{ {
@@ -163,7 +163,7 @@ namespace Barotrauma
CoroutineManager.StopCoroutines(coroutine); CoroutineManager.StopCoroutines(coroutine);
DelayedObjectives.Remove(objective); DelayedObjectives.Remove(objective);
} }
coroutine = CoroutineManager.InvokeAfter(() => coroutine = CoroutineManager.Invoke(() =>
{ {
//round ended before the coroutine finished //round ended before the coroutine finished
#if CLIENT #if CLIENT
@@ -357,8 +357,11 @@ namespace Barotrauma
float itemAngle; float itemAngle;
Holdable holdable = item.GetComponent<Holdable>(); Holdable holdable = item.GetComponent<Holdable>();
float torsoRotation = torso.Rotation; float torsoRotation = torso.Rotation;
bool equippedInRightHand = character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item && rightHand != null && !rightHand.IsSevered;
bool equippedInLefthand = character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == item && leftHand != null && !leftHand.IsSevered; Item rightHandItem = character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand);
bool equippedInRightHand = rightHandItem == item && rightHand != null && !rightHand.IsSevered;
Item leftHandItem = character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand);
bool equippedInLefthand = leftHandItem == item && leftHand != null && !leftHand.IsSevered;
if (aim && !isClimbing && !usingController && character.Stun <= 0.0f && itemPos != Vector2.Zero && !character.IsIncapacitated) if (aim && !isClimbing && !usingController && character.Stun <= 0.0f && itemPos != Vector2.Zero && !character.IsIncapacitated)
{ {
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition); Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
@@ -366,17 +369,23 @@ namespace Barotrauma
holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torsoRotation * Dir; holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torsoRotation * Dir;
holdAngle += GetAimWobble(rightHand, leftHand, item); holdAngle += GetAimWobble(rightHand, leftHand, item);
itemAngle = torsoRotation + holdAngle * Dir; itemAngle = torsoRotation + holdAngle * Dir;
if (holdable.ControlPose) if (holdable.ControlPose)
{ {
var head = GetLimb(LimbType.Head); //if holding two items that should control the characters' pose, let the item in the right hand do it
if (head != null) bool anotherItemControlsPose = equippedInLefthand && rightHandItem != item && (rightHandItem?.GetComponent<Holdable>()?.ControlPose ?? false);
if (!anotherItemControlsPose)
{ {
head.body.SmoothRotate(itemAngle, force: 30 * head.Mass); var head = GetLimb(LimbType.Head);
} if (head != null)
if (TargetMovement == Vector2.Zero && inWater) {
{ head.body.SmoothRotate(itemAngle, force: 30 * head.Mass);
torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f; }
torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f); if (TargetMovement == Vector2.Zero && inWater)
{
torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f;
torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
}
} }
aiming = true; aiming = true;
} }
@@ -506,7 +515,11 @@ namespace Barotrauma
DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)"); DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)");
} }
#endif #endif
HandIK(i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i], CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength); HandIK(
i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i],
CurrentAnimationParams.ArmIKStrength,
CurrentAnimationParams.HandIKStrength,
maxAngularVelocity: 15.0f);
} }
} }
} }
@@ -531,7 +544,7 @@ namespace Barotrauma
return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength; return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength;
} }
public void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f) public void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f, float maxAngularVelocity = float.PositiveInfinity)
{ {
Vector2 shoulderPos; Vector2 shoulderPos;
@@ -572,11 +585,20 @@ namespace Barotrauma
armAngle -= MathHelper.TwoPi; armAngle -= MathHelper.TwoPi;
} }
arm?.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false); if (arm?.body != null && Math.Abs(arm.body.AngularVelocity) < maxAngularVelocity)
{
arm.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false);
}
float forearmAngle = armAngle + lowerArmAngle; float forearmAngle = armAngle + lowerArmAngle;
forearm?.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false); if (forearm?.body != null && Math.Abs(forearm.body.AngularVelocity) < maxAngularVelocity)
float handAngle = forearm != null ? forearmAngle : armAngle; {
hand?.body.SmoothRotate(handAngle, 10.0f * handTorque * hand.Mass, wrapAngle: false); forearm.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false);
}
if (hand?.body != null && Math.Abs(hand.body.AngularVelocity) < maxAngularVelocity)
{
float handAngle = forearm != null ? forearmAngle : armAngle;
hand.body.SmoothRotate(handAngle, 10.0f * handTorque * hand.Mass, wrapAngle: false);
}
} }
public void ApplyPose(Vector2 leftHandPos, Vector2 rightHandPos, Vector2 leftFootPos, Vector2 rightFootPos, float footMoveForce = 10) public void ApplyPose(Vector2 leftHandPos, Vector2 rightHandPos, Vector2 leftFootPos, Vector2 rightFootPos, float footMoveForce = 10)
@@ -672,7 +694,7 @@ namespace Barotrauma
forearmLength += Vector2.Distance( forearmLength += Vector2.Distance(
rightHand.PullJointLocalAnchorA, rightHand.PullJointLocalAnchorA,
rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB); rightWrist.LimbA.type == LimbType.RightHand ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB);
} }
} }
} }
@@ -815,7 +815,7 @@ namespace Barotrauma
{ {
limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true); limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true);
} }
if (limb.Params.BlinkFrequency > 0) if (limb.Params.BlinkFrequency > 0 && !limb.Params.OnlyBlinkInWater)
{ {
limb.UpdateBlink(deltaTime, MainLimb.Rotation); limb.UpdateBlink(deltaTime, MainLimb.Rotation);
} }
@@ -170,7 +170,7 @@ namespace Barotrauma
{ {
get get
{ {
float shoulderHeight = Collider.height / 2.0f - 0.1f; float shoulderHeight = Collider.height / 2.0f;
if (inWater) if (inWater)
{ {
shoulderHeight += 0.4f; shoulderHeight += 0.4f;
@@ -987,7 +987,7 @@ namespace Barotrauma
return; return;
} }
handPos += head.LinearVelocity * 0.1f; handPos += head.LinearVelocity.ClampLength(1.0f) * 0.1f;
// Not sure why the params has to be flipped, but it works. // Not sure why the params has to be flipped, but it works.
var handMoveAmount = CurrentSwimParams.HandMoveAmount.Flip(); var handMoveAmount = CurrentSwimParams.HandMoveAmount.Flip();
@@ -1002,7 +1002,7 @@ namespace Barotrauma
Vector2 rightHandPos = new Vector2(-handPosX, -handPosY) + handMoveOffset; Vector2 rightHandPos = new Vector2(-handPosX, -handPosY) + handMoveOffset;
rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X); rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix); rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()); float speedMultiplier = Math.Min(character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()), 1.0f);
// Limb hand, Vector2 pos, float force = 1.0f // Limb hand, Vector2 pos, float force = 1.0f
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
} }
@@ -1012,7 +1012,7 @@ namespace Barotrauma
Vector2 leftHandPos = new Vector2(handPosX, handPosY) + handMoveOffset; Vector2 leftHandPos = new Vector2(handPosX, handPosY) + handMoveOffset;
leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X); leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X);
leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix); leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix);
float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()); float speedMultiplier = Math.Min(character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()), 1.0f);
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
} }
} }
@@ -1212,24 +1212,24 @@ namespace Barotrauma
//the room where the ragdoll is in is used as the "guess", meaning that it's checked first //the room where the ragdoll is in is used as the "guess", meaning that it's checked first
Hull limbHull = currentHull == null ? null : Hull.FindHull(limb.WorldPosition, currentHull); Hull limbHull = currentHull == null ? null : Hull.FindHull(limb.WorldPosition, currentHull);
bool prevInWater = limb.inWater; bool prevInWater = limb.InWater;
limb.inWater = false; limb.InWater = false;
if (forceStanding) if (forceStanding)
{ {
limb.inWater = false; limb.InWater = false;
} }
else if (limbHull == null) else if (limbHull == null)
{ {
//limb isn't in any room -> it's in the water //limb isn't in any room -> it's in the water
limb.inWater = true; limb.InWater = true;
if (limb.type == LimbType.Head) headInWater = true; if (limb.type == LimbType.Head) headInWater = true;
} }
else if (limbHull.WaterVolume > 0.0f && Submarine.RectContains(limbHull.Rect, limb.Position)) else if (limbHull.WaterVolume > 0.0f && Submarine.RectContains(limbHull.Rect, limb.Position))
{ {
if (limb.Position.Y < limbHull.Surface) if (limb.Position.Y < limbHull.Surface)
{ {
limb.inWater = true; limb.InWater = true;
surfaceY = limbHull.Surface; surfaceY = limbHull.Surface;
if (limb.type == LimbType.Head) if (limb.type == LimbType.Head)
{ {
@@ -1237,7 +1237,7 @@ namespace Barotrauma
} }
} }
//the limb has gone through the surface of the water //the limb has gone through the surface of the water
if (Math.Abs(limb.LinearVelocity.Y) > 5.0f && limb.inWater != prevInWater) if (Math.Abs(limb.LinearVelocity.Y) > 5.0f && limb.InWater != prevInWater)
{ {
Splash(limb, limbHull); Splash(limb, limbHull);
@@ -1435,7 +1435,7 @@ namespace Barotrauma
flowForce *= 1 - Math.Clamp(character.GetStatValue(StatTypes.FlowResistance), 0f, 1f); flowForce *= 1 - Math.Clamp(character.GetStatValue(StatTypes.FlowResistance), 0f, 1f);
float flowForceMagnitude = flowForce.Length(); float flowForceMagnitude = flowForce.Length();
float limbMultipier = limbs.Count(l => l.inWater) / (float)limbs.Length; float limbMultipier = limbs.Count(l => l.InWater) / (float)limbs.Length;
//if the force strong enough, stun the character to let it get thrown around by the water //if the force strong enough, stun the character to let it get thrown around by the water
if ((flowForceMagnitude * limbMultipier) - flowStunTolerance > StunForceThreshold) if ((flowForceMagnitude * limbMultipier) - flowStunTolerance > StunForceThreshold)
{ {
@@ -1472,7 +1472,7 @@ namespace Barotrauma
Collider.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); Collider.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
foreach (Limb limb in limbs) foreach (Limb limb in limbs)
{ {
if (!limb.inWater) { continue; } if (!limb.InWater) { continue; }
limb.body.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); limb.body.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
} }
} }
@@ -1840,9 +1840,23 @@ namespace Barotrauma
public void ReleaseStuckLimbs() public void ReleaseStuckLimbs()
{ {
Limbs.ForEach(l => l.Release()); // Commented out, because stuck limbs is not a feature that we currently use, as it would require that we sync all the limbs, which we don't do.
//Limbs.ForEach(l => l.Release());
} }
public void HideAndDisable(LimbType limbType, float duration = 0, bool ignoreCollisions = true)
{
foreach (var limb in Limbs)
{
if (limb.type == limbType)
{
limb.HideAndDisable(duration, ignoreCollisions);
}
}
}
public void RestoreTemporarilyDisabled() => Limbs.ForEach(l => l.ReEnable());
public void Remove() public void Remove()
{ {
if (Limbs != null) if (Limbs != null)
@@ -313,6 +313,15 @@ namespace Barotrauma
public string TraitorCurrentObjective = ""; public string TraitorCurrentObjective = "";
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase); public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
/// <summary>
/// Can be used by status effects to check the character's gender
/// </summary>
public bool IsMale => Info != null && Info.HasGenders && Info.Gender == Gender.Male;
/// <summary>
/// Can be used by status effects to check the character's gender
/// </summary>
public bool IsFemale => Info != null && Info.HasGenders && Info.Gender == Gender.Female;
private float attackCoolDown; private float attackCoolDown;
public List<OrderInfo> CurrentOrders => Info?.CurrentOrders; public List<OrderInfo> CurrentOrders => Info?.CurrentOrders;
@@ -581,6 +590,14 @@ namespace Barotrauma
} }
} }
/// <summary>
/// Can be used by status effects to check whether the characters is in a high-pressure environment
/// </summary>
public bool InPressure
{
get { return CurrentHull == null || CurrentHull.LethalPressure > 5.0f; }
}
public const float KnockbackCooldown = 5.0f; public const float KnockbackCooldown = 5.0f;
public float KnockbackCooldownTimer; public float KnockbackCooldownTimer;
@@ -641,22 +658,13 @@ namespace Barotrauma
public bool DisableHealthWindow; public bool DisableHealthWindow;
public float Vitality // These properties needs to be exposed for status effects
{ public float Vitality => CharacterHealth.Vitality;
get { return CharacterHealth.Vitality; } public float Health => Vitality;
}
public float Health
{
get { return CharacterHealth.Vitality; }
}
public float HealthPercentage => CharacterHealth.HealthPercentage; public float HealthPercentage => CharacterHealth.HealthPercentage;
public float MaxVitality => CharacterHealth.MaxVitality;
public float MaxVitality public float MaxHealth => MaxVitality;
{ public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
get { return CharacterHealth.MaxVitality; }
}
public float Bloodloss public float Bloodloss
{ {
@@ -2405,7 +2413,7 @@ namespace Barotrauma
var head = AnimController.GetLimb(LimbType.Head); var head = AnimController.GetLimb(LimbType.Head);
bool headInWater = head == null ? bool headInWater = head == null ?
AnimController.InWater : AnimController.InWater :
head.inWater; head.InWater;
//climb ladders automatically when pressing up/down inside their trigger area //climb ladders automatically when pressing up/down inside their trigger area
Ladder currentLadder = SelectedConstruction?.GetComponent<Ladder>(); Ladder currentLadder = SelectedConstruction?.GetComponent<Ladder>();
if ((SelectedConstruction == null || currentLadder != null) && if ((SelectedConstruction == null || currentLadder != null) &&
@@ -3813,6 +3821,7 @@ namespace Barotrauma
foreach (var joint in AnimController.LimbJoints) foreach (var joint in AnimController.LimbJoints)
{ {
if (joint.LimbA.type == LimbType.Head || joint.LimbB.type == LimbType.Head) { continue; }
if (joint.revoluteJoint != null) if (joint.revoluteJoint != null)
{ {
joint.revoluteJoint.LimitEnabled = false; joint.revoluteJoint.LimitEnabled = false;
@@ -3950,7 +3959,10 @@ namespace Barotrauma
foreach (Limb limb in AnimController.Limbs) foreach (Limb limb in AnimController.Limbs)
{ {
#if CLIENT #if CLIENT
if (limb.LightSource != null) limb.LightSource.Color = limb.InitialLightSourceColor; if (limb.LightSource != null)
{
limb.LightSource.Color = limb.InitialLightSourceColor;
}
#endif #endif
limb.body.Enabled = true; limb.body.Enabled = true;
limb.IsSevered = false; limb.IsSevered = false;
@@ -4369,7 +4381,6 @@ namespace Barotrauma
if (!info.UnlockedTalents.Add(talentPrefab.Identifier)) { return false; } if (!info.UnlockedTalents.Add(talentPrefab.Identifier)) { return false; }
} }
DebugConsole.AddWarning("added " + talentPrefab.OriginalName);
CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this); CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this);
characterTalent.ActivateTalent(addingFirstTime); characterTalent.ActivateTalent(addingFirstTime);
characterTalents.Add(characterTalent); characterTalents.Add(characterTalent);
@@ -4377,7 +4388,10 @@ namespace Barotrauma
#if SERVER #if SERVER
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents }); GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents });
#endif #endif
if (addingFirstTime)
{
OnTalentGiven(talentPrefab.Identifier);
}
return true; return true;
} }
@@ -4441,6 +4455,7 @@ namespace Barotrauma
} }
partial void OnMoneyChanged(int prevAmount, int newAmount); partial void OnMoneyChanged(int prevAmount, int newAmount);
partial void OnTalentGiven(string talentIdentifier);
/// <summary> /// <summary>
/// This dictionary is used for stats that are required very frequently. Not very performant, but easier to develop with for now. /// This dictionary is used for stats that are required very frequently. Not very performant, but easier to develop with for now.
@@ -451,9 +451,9 @@ namespace Barotrauma
set => Head.FaceAttachmentIndex = value; set => Head.FaceAttachmentIndex = value;
} }
public readonly ImmutableArray<Color> HairColors; public readonly ImmutableArray<(Color Color, float Commonness)> HairColors;
public readonly ImmutableArray<Color> FacialHairColors; public readonly ImmutableArray<(Color Color, float Commonness)> FacialHairColors;
public readonly ImmutableArray<Color> SkinColors; public readonly ImmutableArray<(Color Color, float Commonness)> SkinColors;
public Color HairColor public Color HairColor
{ {
@@ -522,15 +522,12 @@ namespace Barotrauma
// TODO: support for variants // TODO: support for variants
Head = new HeadInfo(); Head = new HeadInfo();
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false); HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
Head.gender = GetRandomGender(randSync);
HasRaces = CharacterConfigElement.GetAttributeBool("races", false); HasRaces = CharacterConfigElement.GetAttributeBool("races", false);
Head.race = GetRandomRace(randSync); SetGenderAndRace(randSync);
CalculateHeadSpriteRange();
HeadSpriteId = GetRandomHeadID(randSync);
Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, variant); Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, variant);
HairColors = CharacterConfigElement.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); HairColors = CharacterConfigElement.GetAttributeTupleArray("haircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray();
FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); FacialHairColors = CharacterConfigElement.GetAttributeTupleArray("facialhaircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray();
SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray(); SkinColors = CharacterConfigElement.GetAttributeTupleArray("skincolors", new (Color, float)[] { (new Color(255, 215, 200, 255), 100f) }).ToImmutableArray();
SetColors(); SetColors();
if (!string.IsNullOrEmpty(name)) if (!string.IsNullOrEmpty(name))
@@ -580,26 +577,38 @@ namespace Barotrauma
return name; return name;
} }
public static Color SelectRandomColor(in ImmutableArray<(Color Color, float Commonness)> array)
=> ToolBox.SelectWeightedRandom(array, array.Select(p => p.Commonness).ToArray(), Rand.RandSync.Unsynced)
.Color;
private void SetGenderAndRace(Rand.RandSync randSync)
{
Head.gender = GetRandomGender(randSync);
Head.race = GetRandomRace(randSync);
CalculateHeadSpriteRange();
HeadSpriteId = GetRandomHeadID(randSync);
}
private void SetColors() private void SetColors()
{ {
HairColor = HairColors.GetRandom(); HairColor = SelectRandomColor(HairColors);
FacialHairColor = FacialHairColors.GetRandom(); FacialHairColor = SelectRandomColor(FacialHairColors);
SkinColor = SkinColors.GetRandom(); SkinColor = SelectRandomColor(SkinColors);
} }
private void CheckColors() private void CheckColors()
{ {
if (HairColor == Color.Black) if (HairColor == Color.Black)
{ {
HairColor = HairColors.GetRandom(); HairColor = SelectRandomColor(HairColors);
} }
if (FacialHairColor == Color.Black) if (FacialHairColor == Color.Black)
{ {
FacialHairColor = FacialHairColors.GetRandom(); FacialHairColor = SelectRandomColor(FacialHairColors);
} }
if (SkinColor == Color.Black) if (SkinColor == Color.Black)
{ {
SkinColor = SkinColors.GetRandom(); SkinColor = SelectRandomColor(SkinColors);
} }
} }
@@ -610,7 +619,6 @@ namespace Barotrauma
idCounter++; idCounter++;
Name = infoElement.GetAttributeString("name", ""); Name = infoElement.GetAttributeString("name", "");
OriginalName = infoElement.GetAttributeString("originalname", null); OriginalName = infoElement.GetAttributeString("originalname", null);
string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant();
Salary = infoElement.GetAttributeInt("salary", 1000); Salary = infoElement.GetAttributeInt("salary", 1000);
ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0); ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0);
UnlockedTalents = new HashSet<string>(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true)); UnlockedTalents = new HashSet<string>(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true));
@@ -642,9 +650,9 @@ namespace Barotrauma
{ {
race = GetRandomRace(Rand.RandSync.Unsynced); race = GetRandomRace(Rand.RandSync.Unsynced);
} }
HairColors = CharacterConfigElement.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); HairColors = CharacterConfigElement.GetAttributeTupleArray("haircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray();
FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); FacialHairColors = CharacterConfigElement.GetAttributeTupleArray("facialhaircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray();
SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray(); SkinColors = CharacterConfigElement.GetAttributeTupleArray("skincolors", new (Color, float)[] { (new Color(255, 215, 200, 255), 100f) }).ToImmutableArray();
RecreateHead( RecreateHead(
infoElement.GetAttributeInt("headspriteid", 1), infoElement.GetAttributeInt("headspriteid", 1),
@@ -655,9 +663,35 @@ namespace Barotrauma
infoElement.GetAttributeInt("moustacheindex", -1), infoElement.GetAttributeInt("moustacheindex", -1),
infoElement.GetAttributeInt("faceattachmentindex", -1)); infoElement.GetAttributeInt("faceattachmentindex", -1));
SkinColor = infoElement.GetAttributeColor("skincolor", Color.White); //backwards compatibility
HairColor = infoElement.GetAttributeColor("haircolor", Color.White); if (infoElement.Attribute("skincolor") == null && infoElement.Attribute("race") != null)
FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.White); {
string raceStr = infoElement.GetAttributeString("race", string.Empty);
Race obsoleteRace = Race.None;
Enum.TryParse(raceStr, ignoreCase: true, out obsoleteRace);
switch (obsoleteRace)
{
case Race.White:
case Race.None:
SkinColor = new Color(255, 215, 200, 255);
break;
case Race.Brown:
SkinColor = new Color(158, 95, 72, 255);
break;
case Race.Black:
SkinColor = new Color(153, 75, 42, 255);
break;
case Race.Asian:
SkinColor = new Color(191, 116, 61, 255);
break;
}
}
else
{
SkinColor = infoElement.GetAttributeColor("skincolor", Color.Black);
}
HairColor = infoElement.GetAttributeColor("haircolor", Color.Black);
FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.Black);
CheckColors(); CheckColors();
if (string.IsNullOrEmpty(Name)) if (string.IsNullOrEmpty(Name))
@@ -1128,7 +1162,7 @@ namespace Barotrauma
return (int)(salary * Job.Prefab.PriceMultiplier); return (int)(salary * Job.Prefab.PriceMultiplier);
} }
public void IncreaseSkillLevel(string skillIdentifier, float increase, Vector2 pos) public void IncreaseSkillLevel(string skillIdentifier, float increase, Vector2 pos, bool gainedFromApprenticeship = false)
{ {
if (Job == null || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) || Character == null) { return; } if (Job == null || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) || Character == null) { return; }
@@ -1148,7 +1182,7 @@ namespace Barotrauma
{ {
// assume we are getting at least 1 point in skill, since this logic only runs in such cases // assume we are getting at least 1 point in skill, since this logic only runs in such cases
float increaseSinceLastSkillPoint = MathHelper.Max(increase, 1f); float increaseSinceLastSkillPoint = MathHelper.Max(increase, 1f);
var abilitySkillGain = new AbilityValueStringCharacter(increaseSinceLastSkillPoint, skillIdentifier, Character); var abilitySkillGain = new AbilitySkillGain(increaseSinceLastSkillPoint, skillIdentifier, Character, gainedFromApprenticeship);
Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, abilitySkillGain); Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, abilitySkillGain);
foreach (Character character in Character.GetFriendlyCrew(Character)) foreach (Character character in Character.GetFriendlyCrew(Character))
{ {
@@ -1747,4 +1781,19 @@ namespace Barotrauma
RemoveAfterRound = retainAfterRound; RemoveAfterRound = retainAfterRound;
} }
} }
class AbilitySkillGain : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter
{
public AbilitySkillGain(float value, string abilityString, Character character, bool gainedFromApprenticeship)
{
Value = value;
String = abilityString;
Character = character;
GainedFromApprenticeship = gainedFromApprenticeship;
}
public Character Character { get; set; }
public float Value { get; set; }
public string String { get; set; }
public bool GainedFromApprenticeship { get; set; }
}
} }
@@ -749,7 +749,7 @@ namespace Barotrauma
StunTimer = Stun > 0 ? StunTimer + deltaTime : 0; StunTimer = Stun > 0 ? StunTimer + deltaTime : 0;
FaceTint = DefaultFaceTint; if (Character.GodMode) { return; }
for (int i = 0; i < limbHealths.Count; i++) for (int i = 0; i < limbHealths.Count; i++)
{ {
@@ -775,10 +775,6 @@ namespace Barotrauma
{ {
UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime); UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime);
} }
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
} }
} }
@@ -798,10 +794,6 @@ namespace Barotrauma
var affliction = afflictions[i]; var affliction = afflictions[i];
affliction.Update(this, null, deltaTime); affliction.Update(this, null, deltaTime);
affliction.DamagePerSecondTimer += deltaTime; affliction.DamagePerSecondTimer += deltaTime;
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
} }
@@ -818,7 +810,7 @@ namespace Barotrauma
} }
UpdateLimbAfflictionOverlays(); UpdateLimbAfflictionOverlays();
UpdateSkinTint();
CalculateVitality(); CalculateVitality();
if (Vitality <= MinVitality) if (Vitality <= MinVitality)
@@ -827,6 +819,32 @@ namespace Barotrauma
} }
} }
private void UpdateSkinTint()
{
FaceTint = DefaultFaceTint;
BodyTint = Color.TransparentBlack;
for (int i = 0; i < limbHealths.Count; i++)
{
for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
{
var affliction = limbHealths[i].Afflictions[j];
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
}
}
for (int i = 0; i < afflictions.Count; i++)
{
var affliction = afflictions[i];
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
}
}
private void UpdateOxygen(float deltaTime) private void UpdateOxygen(float deltaTime)
{ {
if (!Character.NeedsOxygen) { return; } if (!Character.NeedsOxygen) { return; }
@@ -905,6 +923,7 @@ namespace Barotrauma
if (Unkillable || Character.GodMode) { return; } if (Unkillable || Character.GodMode) { return; }
var (type, affliction) = GetCauseOfDeath(); var (type, affliction) = GetCauseOfDeath();
UpdateSkinTint();
Character.Kill(type, affliction); Character.Kill(type, affliction);
#if CLIENT #if CLIENT
DisplayVitalityDelay = 0.0f; DisplayVitalityDelay = 0.0f;
@@ -218,7 +218,7 @@ namespace Barotrauma
public Vector2 StepOffset => ConvertUnits.ToSimUnits(Params.StepOffset) * ragdoll.RagdollParams.JointScale; public Vector2 StepOffset => ConvertUnits.ToSimUnits(Params.StepOffset) * ragdoll.RagdollParams.JointScale;
public bool inWater; public bool InWater { get; set; }
private FixedMouseJoint pullJoint; private FixedMouseJoint pullJoint;
@@ -535,8 +535,11 @@ namespace Barotrauma
public string Name => Params.Name; public string Name => Params.Name;
// Exposed for status effects // These properties are exposed for status effects
public bool IsDead => character.IsDead; public bool IsDead => character.IsDead;
public float Health => character.Health;
public float HealthPercentage => character.HealthPercentage;
public AIState AIState => character.AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
public bool CanBeSeveredAlive public bool CanBeSeveredAlive
{ {
@@ -804,7 +807,7 @@ namespace Barotrauma
{ {
UpdateProjSpecific(deltaTime); UpdateProjSpecific(deltaTime);
if (inWater) if (InWater)
{ {
body.ApplyWaterForces(); body.ApplyWaterForces();
} }
@@ -847,20 +850,25 @@ namespace Barotrauma
attack?.UpdateCoolDown(deltaTime); attack?.UpdateCoolDown(deltaTime);
} }
private bool temporarilyDisabled;
private float reEnableTimer = -1; private float reEnableTimer = -1;
public void HideAndDisable(float duration = 0) public void HideAndDisable(float duration = 0, bool ignoreCollisions = true)
{ {
if (Hidden || Disabled) { return; }
if (ignoreCollisions && IgnoreCollisions) { return; }
temporarilyDisabled = true;
Hidden = true; Hidden = true;
Disabled = true; Disabled = true;
IgnoreCollisions = true; IgnoreCollisions = ignoreCollisions;
if (duration > 0) if (duration > 0)
{ {
reEnableTimer = duration; reEnableTimer = duration;
} }
} }
private void ReEnable() public void ReEnable()
{ {
if (!temporarilyDisabled) { return; }
Hidden = false; Hidden = false;
Disabled = false; Disabled = false;
IgnoreCollisions = false; IgnoreCollisions = false;
@@ -14,9 +14,9 @@ namespace Barotrauma
NotDefined, NotDefined,
Walk, Walk,
Run, Run,
Crouch,
SwimSlow, SwimSlow,
SwimFast SwimFast,
Crouch
} }
abstract class GroundedMovementParams : AnimationParams abstract class GroundedMovementParams : AnimationParams
@@ -79,12 +79,18 @@ namespace Barotrauma
[Serialize(10f, true, description: "How effectively/easily the character eats other characters. Affects the forces, the amount of particles, and the time required before the target is eaten away"), Editable(MinValueFloat = 1, MaxValueFloat = 1000, ValueStep = 1)] [Serialize(10f, true, description: "How effectively/easily the character eats other characters. Affects the forces, the amount of particles, and the time required before the target is eaten away"), Editable(MinValueFloat = 1, MaxValueFloat = 1000, ValueStep = 1)]
public float EatingSpeed { get; set; } public float EatingSpeed { get; set; }
[Serialize(true, true), Editable]
public bool UsePathFinding { get; set; }
[Serialize(1f, true, "Decreases the intensive path finding call frequency. Set to a lower value for insignificant creatures to improve performance."), Editable(minValue: 0f, maxValue: 1f)] [Serialize(1f, true, "Decreases the intensive path finding call frequency. Set to a lower value for insignificant creatures to improve performance."), Editable(minValue: 0f, maxValue: 1f)]
public float PathFinderPriority { get; set; } public float PathFinderPriority { get; set; }
[Serialize(false, true), Editable] [Serialize(false, true), Editable]
public bool HideInSonar { get; set; } public bool HideInSonar { get; set; }
[Serialize(false, true), Editable]
public bool HideInThermalGoggles { get; set; }
[Serialize(0f, true), Editable] [Serialize(0f, true), Editable]
public float SonarDisruption { get; set; } public float SonarDisruption { get; set; }
@@ -706,6 +712,9 @@ namespace Barotrauma
[Serialize(-1f, true, description: "A generic max threshold. Not used if set to negative."), Editable] [Serialize(-1f, true, description: "A generic max threshold. Not used if set to negative."), Editable]
public float ThresholdMax { get; private set; } public float ThresholdMax { get; private set; }
[Serialize("0.0, 0.0", true), Editable]
public Vector2 Offset { get; private set; }
[Serialize(AttackPattern.Straight, true), Editable] [Serialize(AttackPattern.Straight, true), Editable]
public AttackPattern AttackPattern { get; set; } public AttackPattern AttackPattern { get; set; }
@@ -597,6 +597,9 @@ namespace Barotrauma
[Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)] [Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)]
public float SpriteOrientation { get; set; } public float SpriteOrientation { get; set; }
[Serialize(LimbType.None, true, description: "If set, the limb sprite will use the same sprite depth as the specified limb. Generally only useful for limbs that get added on the ragdoll on the fly (e.g. extra limbs added via gene splicing).")]
public LimbType InheritLimbDepth { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)] [Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float SteerForce { get; set; } public float SteerForce { get; set; }
@@ -679,6 +682,9 @@ namespace Barotrauma
[Serialize(50f, true), Editable] [Serialize(50f, true), Editable]
public float BlinkForce { get; set; } public float BlinkForce { get; set; }
[Serialize(false, true), Editable]
public bool OnlyBlinkInWater { get; set; }
[Serialize(TransitionMode.Linear, true), Editable] [Serialize(TransitionMode.Linear, true), Editable]
public TransitionMode BlinkTransitionIn { get; private set; } public TransitionMode BlinkTransitionIn { get; private set; }
@@ -87,19 +87,6 @@ namespace Barotrauma.Abilities
public string String { get; set; } public string String { get; set; }
} }
class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter
{
public AbilityValueStringCharacter(float value, string abilityString, Character character)
{
Value = value;
String = abilityString;
Character = character;
}
public Character Character { get; set; }
public float Value { get; set; }
public string String { get; set; }
}
class AbilityStringCharacter : AbilityObject, IAbilityCharacter, IAbilityString class AbilityStringCharacter : AbilityObject, IAbilityCharacter, IAbilityString
{ {
public AbilityStringCharacter(string abilityString, Character character) public AbilityStringCharacter(string abilityString, Character character)
@@ -125,7 +125,6 @@ namespace Barotrauma.Abilities
return null; return null;
} }
DebugConsole.AddWarning("Instantiated " + characterAbility + " for talent " + characterAbilityGroup.CharacterTalent.DebugIdentifier);
return characterAbility; return characterAbility;
} }
} }
@@ -1,33 +1,60 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
{ {
class CharacterAbilityApplyForce : CharacterAbility class CharacterAbilityApplyForce : CharacterAbility
{ {
private readonly float impulseStrength; private readonly float force;
private readonly float maxVelocity; private readonly float maxVelocity;
private readonly string afflictionIdentifier; private readonly string afflictionIdentifier;
private readonly HashSet<LimbType> limbTypes = new HashSet<LimbType>();
public override bool AppliesEffectOnIntervalUpdate => true; public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityApplyForce(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityApplyForce(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
impulseStrength = abilityElement.GetAttributeFloat("impulsestrength", 0f); force = abilityElement.GetAttributeFloat("force", 0f);
maxVelocity = abilityElement.GetAttributeFloat("maxvelocity", 10f); maxVelocity = abilityElement.GetAttributeFloat("maxvelocity", 10f);
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", ""); afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
string[] limbTypesStr = abilityElement.GetAttributeStringArray("limbtypes", new string[0]);
foreach (string limbTypeStr in limbTypesStr)
{
if (Enum.TryParse(limbTypeStr, out LimbType limbType))
{
limbTypes.Add(limbType);
}
else
{
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - \"{limbTypeStr}\" is not a valid limb type.");
}
}
} }
protected override void ApplyEffect() protected override void ApplyEffect()
{ {
Affliction affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier); float strength = 1.0f;
if (!string.IsNullOrEmpty(afflictionIdentifier))
if (affliction == null) { return; } {
Affliction affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier);
if (affliction == null) { return; }
strength = affliction.Strength / affliction.Prefab.MaxStrength;
}
foreach (Limb limb in Character.AnimController.Limbs) foreach (Limb limb in Character.AnimController.Limbs)
{ {
limb.body.ApplyForce(Vector2.Normalize(limb.Mass * Character.AnimController.TargetMovement) * impulseStrength * (affliction.Strength / affliction.Prefab.MaxStrength), maxVelocity); if (limb.IsSevered || limb.Removed) { continue; }
if (limbTypes.Any())
{
if (!limbTypes.Contains(limb.type)) { continue; }
}
if (Character.AnimController.TargetMovement.LengthSquared() < 0.001f) { continue; }
limb.body.ApplyForce(Vector2.Normalize(limb.Mass * Character.AnimController.TargetMovement) * force * strength, maxVelocity);
} }
} }
} }
@@ -8,13 +8,13 @@ namespace Barotrauma.Abilities
class CharacterAbilityAlienHoarder : CharacterAbility class CharacterAbilityAlienHoarder : CharacterAbility
{ {
private readonly float addedDamageMultiplierPerItem; private readonly float addedDamageMultiplierPerItem;
private readonly int maxAmount; private readonly float maxAddedDamageMultiplier;
private readonly string[] tags; private readonly string[] tags;
public CharacterAbilityAlienHoarder(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityAlienHoarder(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
addedDamageMultiplierPerItem = abilityElement.GetAttributeFloat("addeddamagemultiplierperitem", 0f); addedDamageMultiplierPerItem = abilityElement.GetAttributeFloat("addeddamagemultiplierperitem", 0f);
maxAmount = abilityElement.GetAttributeInt("maxamount", 0); maxAddedDamageMultiplier = abilityElement.GetAttributeFloat("maxaddedddamagemultiplier", float.MaxValue);
tags = abilityElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true); tags = abilityElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
} }
@@ -30,7 +30,7 @@ namespace Barotrauma.Abilities
totalAddedDamageMultiplier += addedDamageMultiplierPerItem; totalAddedDamageMultiplier += addedDamageMultiplierPerItem;
} }
} }
attackData.DamageMultiplier += addedDamageMultiplierPerItem; attackData.DamageMultiplier += Math.Min(totalAddedDamageMultiplier, maxAddedDamageMultiplier);
} }
else else
{ {
@@ -12,9 +12,9 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect(AbilityObject abilityObject) protected override void ApplyEffect(AbilityObject abilityObject)
{ {
if ((abilityObject as IAbilityString)?.String is string skillIdentifier && (abilityObject as IAbilityCharacter)?.Character is Character character) if (abilityObject is AbilitySkillGain abilitySkillGain && !abilitySkillGain.GainedFromApprenticeship && abilitySkillGain.Character != Character)
{ {
Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f); Character.Info?.IncreaseSkillLevel(abilitySkillGain.String, 1.0f, Character.Position + Vector2.UnitY * 175.0f, gainedFromApprenticeship: true);
} }
} }
} }
@@ -58,12 +58,7 @@ namespace Barotrauma
return handle; return handle;
} }
public static CoroutineHandle Invoke(Action action) public static CoroutineHandle Invoke(Action action, float delay = 0f)
{
return StartCoroutine(DoInvokeAfter(action, 0.0f));
}
public static CoroutineHandle InvokeAfter(Action action, float delay)
{ {
return StartCoroutine(DoInvokeAfter(action, delay)); return StartCoroutine(DoInvokeAfter(action, delay));
} }
@@ -845,15 +845,24 @@ namespace Barotrauma
var character = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled; var character = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled;
if (character != null) if (character != null)
{ {
character.GiveTalent(args[0]); TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c =>
c.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
c.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase));
if (talentPrefab == null)
{
ThrowError($"Couldn't find the talent \"{args[0]}\".");
return;
}
character.GiveTalent(talentPrefab);
NewMessage($"Gave talent \"{talentPrefab.DisplayName}\" to \"{character.Name}\".");
} }
}, },
() => () =>
{ {
List<string> talentNames = new List<string>(); List<string> talentNames = new List<string>();
foreach (TalentPrefab itemPrefab in TalentPrefab.TalentPrefabs) foreach (TalentPrefab talent in TalentPrefab.TalentPrefabs)
{ {
talentNames.Add(itemPrefab.Identifier); talentNames.Add(talent.DisplayName);
} }
return new string[][] return new string[][]
@@ -863,24 +872,15 @@ namespace Barotrauma
}; };
}, isCheat: true)); }, isCheat: true));
commands.Add(new Command("unlocktalents", "unlocktalents [all/[jobname]]: give the controlled characters all the talents of the specified class", (string[] args) => commands.Add(new Command("unlocktalents", "unlocktalents [all/[jobname]] [character]: give the specified character all the talents of the specified class", (string[] args) =>
{ {
if (Character.Controlled == null) { return; } var character = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled;
if (character == null) { return; }
List<TalentTree> talentTrees = new List<TalentTree>();
if (args.Length == 0 || args[0].Equals("all", StringComparison.OrdinalIgnoreCase)) if (args.Length == 0 || args[0].Equals("all", StringComparison.OrdinalIgnoreCase))
{ {
foreach (var talentTree in TalentTree.JobTalentTrees) talentTrees.AddRange(TalentTree.JobTalentTrees.Values);
{
foreach (var subTree in talentTree.Value.TalentSubTrees)
{
foreach (var option in subTree.TalentOptionStages)
{
foreach (var talent in option.Talents)
{
Character.Controlled.GiveTalent(talent);
}
}
}
}
} }
else else
{ {
@@ -895,13 +895,19 @@ namespace Barotrauma
ThrowError($"No talents configured for the job \"{args[0]}\"."); ThrowError($"No talents configured for the job \"{args[0]}\".");
return; return;
} }
talentTrees.Add(talentTree);
}
foreach (var talentTree in talentTrees)
{
foreach (var subTree in talentTree.TalentSubTrees) foreach (var subTree in talentTree.TalentSubTrees)
{ {
foreach (var option in subTree.TalentOptionStages) foreach (var option in subTree.TalentOptionStages)
{ {
foreach (var talent in option.Talents) foreach (var talent in option.Talents)
{ {
Character.Controlled.GiveTalent(talent); character.GiveTalent(talent);
NewMessage($"Unlocked talent \"{talent.DisplayName}\".");
} }
} }
} }
@@ -913,7 +919,8 @@ namespace Barotrauma
availableArgs.AddRange(JobPrefab.Prefabs.Select(j => j.Name)); availableArgs.AddRange(JobPrefab.Prefabs.Select(j => j.Name));
return new string[][] return new string[][]
{ {
availableArgs.ToArray() availableArgs.ToArray(),
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
}; };
}, isCheat: true)); }, isCheat: true));
@@ -1,6 +1,4 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class GodModeAction : EventAction
{
[Serialize(true, true)]
public bool Enabled { get; set; }
[Serialize("", true)]
public string TargetTag { get; set; }
public GodModeAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
private bool isFinished = false;
public override bool IsFinished(ref string goTo)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
public override void Update(float deltaTime)
{
if (isFinished) { return; }
var targets = ParentEvent.GetTargets(TargetTag);
foreach (var target in targets)
{
if (target != null && target is Character character)
{
character.GodMode = Enabled;
}
}
isFinished = true;
}
public override string ToDebugString()
{
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(GodModeAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
(Enabled ? "Enable godmode" : "Disable godmode");
}
}
}
@@ -91,7 +91,7 @@ namespace Barotrauma
int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1); int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1);
for (int i = 0; i < amount; i++) for (int i = 0; i < amount; i++)
{ {
CoroutineManager.InvokeAfter(() => CoroutineManager.Invoke(() =>
{ {
//round ended before the coroutine finished //round ended before the coroutine finished
if (GameMain.GameSession == null || Level.Loaded == null) { return; } if (GameMain.GameSession == null || Level.Loaded == null) { return; }
@@ -359,10 +359,19 @@ namespace Barotrauma
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier)); crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier));
crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier)); crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier));
int experienceGain = (int)(baseExperienceGain * experienceGainMultiplier.Value);
#if CLIENT
foreach (Character character in crewCharacters) foreach (Character character in crewCharacters)
{ {
character.Info.GiveExperience((int)(baseExperienceGain * experienceGainMultiplier.Value), isMissionExperience: true); character.Info.GiveExperience(experienceGain, isMissionExperience: true);
} }
#else
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)
{
//give the experience to the stored characterinfo if the client isn't currently controlling a character
(c.Character?.Info ?? c.CharacterInfo)?.GiveExperience(experienceGain, isMissionExperience: true);
}
#endif
// apply money gains afterwards to prevent them from affecting XP gains // apply money gains afterwards to prevent them from affecting XP gains
var moneyGainMission = new AbilityValueMission(1f, this); var moneyGainMission = new AbilityValueMission(1f, this);
@@ -21,10 +21,11 @@ namespace Barotrauma
private bool disallowed; private bool disallowed;
private readonly Level.PositionType spawnPosType; private readonly Level.PositionType spawnPosType;
private readonly string spawnPointTag;
private bool spawnPending; private bool spawnPending;
private int maxAmountPerLevel = int.MaxValue; private readonly int maxAmountPerLevel = int.MaxValue;
public List<Character> Monsters => monsters; public List<Character> Monsters => monsters;
public Vector2? SpawnPos => spawnPos; public Vector2? SpawnPos => spawnPos;
@@ -87,6 +88,8 @@ namespace Barotrauma
spawnPosType = Level.PositionType.Abyss; spawnPosType = Level.PositionType.Abyss;
} }
spawnPointTag = prefab.ConfigElement.GetAttributeString("spawnpointtag", string.Empty);
offset = prefab.ConfigElement.GetAttributeFloat("offset", 0); offset = prefab.ConfigElement.GetAttributeFloat("offset", 0);
scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 500), 0, 3000); scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 500), 0, 3000);
@@ -285,7 +288,8 @@ namespace Barotrauma
spawnPos = chosenPosition.Position.ToVector2(); spawnPos = chosenPosition.Position.ToVector2();
if (chosenPosition.Submarine != null || chosenPosition.Ruin != null) if (chosenPosition.Submarine != null || chosenPosition.Ruin != null)
{ {
var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine, useSyncedRand: false); var spawnPoint =
WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine, useSyncedRand: false, spawnPointTag: spawnPointTag);
if (spawnPoint != null) if (spawnPoint != null)
{ {
System.Diagnostics.Debug.Assert(spawnPoint.Submarine == (chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine)); System.Diagnostics.Debug.Assert(spawnPoint.Submarine == (chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine));
@@ -447,7 +451,7 @@ namespace Barotrauma
for (int i = 0; i < amount; i++) for (int i = 0; i < amount; i++)
{ {
string seed = Level.Loaded.Seed + i.ToString(); string seed = Level.Loaded.Seed + i.ToString();
CoroutineManager.InvokeAfter(() => CoroutineManager.Invoke(() =>
{ {
//round ended before the coroutine finished //round ended before the coroutine finished
if (GameMain.GameSession == null || Level.Loaded == null) { return; } if (GameMain.GameSession == null || Level.Loaded == null) { return; }
@@ -10,6 +10,11 @@ namespace Barotrauma
{ {
public static bool OutputDebugInfo = false; public static bool OutputDebugInfo = false;
/// <summary>
/// If we are spawning in an area where difficulty should not be a factor, assume difficulty is at the exact "middle"
/// </summary>
public const float DefaultDifficultyModifier = 0f;
public static void PlaceIfNeeded() public static void PlaceIfNeeded()
{ {
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; } if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
@@ -23,6 +28,7 @@ namespace Barotrauma
subs.ForEach(s => s.Info.InitialSuppliesSpawned = true); subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
} }
float difficultyModifier = GetLevelDifficultyModifier();
foreach (var sub in Submarine.Loaded) foreach (var sub in Submarine.Loaded)
{ {
if (sub.Info.Type == SubmarineType.Player || if (sub.Info.Type == SubmarineType.Player ||
@@ -32,7 +38,7 @@ namespace Barotrauma
{ {
continue; continue;
} }
Place(sub.ToEnumerable()); Place(sub.ToEnumerable(), difficultyModifier: difficultyModifier);
} }
if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost) if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost)
@@ -42,12 +48,23 @@ namespace Barotrauma
} }
} }
public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer) private const float MaxDifficultyModifier = 0.2f;
/// <summary>
/// Spawn probability of loot is modified by difficulty, -20% less loot at 0% difficulty and +20% loot at 100% difficulty.
/// </summary>
private static float GetLevelDifficultyModifier()
{ {
Place(sub.ToEnumerable(), regeneratedContainer); return Math.Clamp(Level.Loaded?.Difficulty is float difficulty ? (difficulty / 100f) * (MaxDifficultyModifier * 2) - MaxDifficultyModifier : DefaultDifficultyModifier, -MaxDifficultyModifier, MaxDifficultyModifier);
} }
private static void Place(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null) public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer)
{
// Level difficulty currently doesn't affect regenerated loot for the sake of simplicity
Place(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer);
}
private static void Place(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null, float difficultyModifier = DefaultDifficultyModifier)
{ {
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
{ {
@@ -167,7 +184,7 @@ namespace Barotrauma
} }
foreach (var validContainer in validContainers) foreach (var validContainer in validContainers)
{ {
var newItems = SpawnItem(itemPrefab, containers, validContainer); var newItems = SpawnItem(itemPrefab, containers, validContainer, difficultyModifier);
if (newItems.Any()) if (newItems.Any())
{ {
spawnedItems.AddRange(newItems); spawnedItems.AddRange(newItems);
@@ -201,11 +218,18 @@ namespace Barotrauma
return validContainers; return validContainers;
} }
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer) private static readonly float[] qualityCommonnesses = new float[Quality.MaxQuality + 1]
{
0.85f,
0.125f,
0.0225f,
0.0025f,
};
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier)
{ {
List<Item> spawnedItems = new List<Item>(); List<Item> spawnedItems = new List<Item>();
bool success = false; if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability * (1f + difficultyModifier)) { return spawnedItems; }
if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability) { return spawnedItems; }
// Don't add dangerously reactive materials in thalamus wrecks // Don't add dangerously reactive materials in thalamus wrecks
if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater")) if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater"))
{ {
@@ -220,10 +244,24 @@ namespace Barotrauma
break; break;
} }
if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; } if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; }
int quality = 0;
float qualityCommmonnessSum = qualityCommonnesses.Sum();
float randomNumber = Rand.Range(0f, qualityCommmonnessSum, Rand.RandSync.Server);
for (int k = qualityCommonnesses.Length - 1; k >= 0; k--)
{
if (randomNumber < qualityCommonnesses[k])
{
quality = k;
break;
}
}
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine) var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine)
{ {
SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost, SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost,
AllowStealing = validContainer.Key.Item.AllowStealing, AllowStealing = validContainer.Key.Item.AllowStealing,
Quality = quality,
OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex, OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex,
OriginalContainerIndex = OriginalContainerIndex =
Item.ItemList.Where(it => it.Submarine == validContainer.Key.Item.Submarine && it.OriginalModuleIndex == validContainer.Key.Item.OriginalModuleIndex).ToList().IndexOf(validContainer.Key.Item) Item.ItemList.Where(it => it.Submarine == validContainer.Key.Item.Submarine && it.OriginalModuleIndex == validContainer.Key.Item.OriginalModuleIndex).ToList().IndexOf(validContainer.Key.Item)
@@ -235,7 +273,6 @@ namespace Barotrauma
spawnedItems.Add(item); spawnedItems.Add(item);
validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false); validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false);
containers.AddRange(item.GetComponents<ItemContainer>()); containers.AddRange(item.GetComponents<ItemContainer>());
success = true;
} }
return spawnedItems; return spawnedItems;
} }
@@ -563,13 +563,16 @@ namespace Barotrauma
public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{ {
List<Item> takenItems = new List<Item>(); List<Item> takenItems = new List<Item>();
foreach (Item item in Item.ItemList) if (Level.Loaded?.Type == LevelData.LevelType.Outpost)
{ {
if (!item.SpawnedInOutpost || item.OriginalModuleIndex < 0) { continue; } foreach (Item item in Item.ItemList)
var owner = item.GetRootInventoryOwner();
if ((!(owner?.Submarine?.Info?.IsOutpost ?? false)) || (owner is Character character && character.TeamID == CharacterTeamType.Team1) || item.Submarine == null || !item.Submarine.Info.IsOutpost)
{ {
takenItems.Add(item); if (!item.SpawnedInOutpost || item.OriginalModuleIndex < 0) { continue; }
var owner = item.GetRootInventoryOwner();
if ((!(owner?.Submarine?.Info?.IsOutpost ?? false)) || (owner is Character character && character.TeamID == CharacterTeamType.Team1) || item.Submarine == null || !item.Submarine.Info.IsOutpost)
{
takenItems.Add(item);
}
} }
} }
if (map != null && CargoManager != null) if (map != null && CargoManager != null)
@@ -0,0 +1,280 @@
#nullable enable
using System;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
internal partial class EntitySpawnerComponent : ItemComponent, IDrawableComponent
{
public enum AreaShape
{
Rectangle,
Circle
}
[Editable, Serialize("", true, "Identifier of the item to spawn, does nothing if SpeciesName is set. Separate by comma to have multiple items spawn at random.")]
public string? ItemIdentifier { get; set; }
[Editable, Serialize("", true, "Species name of the creature to spawn, takes priority if ItemIdentifier is set. Separate by comma to have multiple creatures spawn at random.")]
public string? SpeciesName { get; set; }
[Editable, Serialize(true, true, "Only spawn if crew members are within certain area")]
public bool OnlySpawnWhenCrewInRange { get; set; }
[Editable, Serialize(AreaShape.Rectangle, true, "Shape of the area where crew members need to stay")]
public AreaShape CrewAreaShape { get; set; }
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 0, ValueStep = 10f), Serialize("500,500", true, "Size of the rectangle where crew members need to stay. Does nothing if CrewAreaShape is set to Circle")]
public Vector2 CrewAreaBounds { get; set; }
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 0, ValueStep = 10f), Serialize(500f, true, "Radius of the circle to spawn stuff in. Does nothing if CrewAreaShape is set to Rectangle")]
public float CrewAreaRadius { get; set; }
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = int.MinValue, ValueStep = 10f), Serialize("0,0", true, "Offset of the crew area from the center of the item")]
public Vector2 CrewAreaOffset { get; set; }
[Editable, Serialize(AreaShape.Rectangle, true, "Shape of the area where enemies or items are spawned")]
public AreaShape SpawnAreaShape { get; set; }
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 0, ValueStep = 10f), Serialize("500,500", true, "Size of the rectangle where items or creatures will be spawned. Does nothing if SpawnAreaShape is set to Circle")]
public Vector2 SpawnAreaBounds { get; set; }
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 0, ValueStep = 10f), Serialize(500f, true, "Radius of the circle where items or creatures will be spawned. Does nothing if SpawnAreaShape is set to Rectangle")]
public float SpawnAreaRadius { get; set; }
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = int.MinValue, ValueStep = 10f), Serialize("0,0", true, "Offset of the spawn area from the center of the item")]
public Vector2 SpawnAreaOffset { get; set; }
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = int.MinValue, ValueStep = 1f), Serialize("10,40", true, "Time range between spawn attempts in seconds")]
public Vector2 SpawnTimerRange { get; set; }
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 1f, ValueStep = 1f, DecimalCount = 0), Serialize("1,3", true, "Minumum and maximum amount of items or creatures to spawn in one attempt")]
public Vector2 SpawnAmountRange { get; set; }
[Editable(MinValueInt = 0), Serialize(8, true, "Amount of items or creatures in the spawn area that will prevent further items or creatures from being spawned")]
public int MaximumAmount { get; set; }
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = int.MinValue, ValueStep = 10f), Serialize(500f, true, "Inflate the circle of rectangle by this value to extend the area that counts towards the maximum amount of items or enemies to be spawned")]
public float MaximumAmountRangePadding { get; set; }
[Serialize(true, true, "")]
public bool CanSpawn { get; set; } = true;
private float SpawnTimer;
private float? SpawnTimerGoal;
public EntitySpawnerComponent(Item item, XElement element) : base(item, element)
{
IsActive = true;
}
public override void OnItemLoaded()
{
if (!string.IsNullOrWhiteSpace(ItemIdentifier))
{
string[] allItems = ItemIdentifier.Split(',');
foreach (string itemIdentifier in allItems)
{
string trimmedString = itemIdentifier.Trim();
bool found = false;
foreach (ItemPrefab prefab in ItemPrefab.Prefabs)
{
if (string.Equals(trimmedString, prefab.Identifier, StringComparison.OrdinalIgnoreCase))
{
found = true;
break;
}
}
if (!found)
{
DebugConsole.ThrowError($"Error loading {nameof(EntitySpawnerComponent)} - item prefab \"" + name + "\" (identifier \"" + trimmedString + "\") not found.");
}
}
}
base.OnItemLoaded();
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
item.SendSignal(CanSpawn ? "1" : "0", "state_out");
if (GameMain.NetworkMember is { IsClient: true }) { return; }
SpawnTimerGoal ??= Rand.Range(Math.Min(SpawnTimerRange.X, SpawnTimerRange.Y), Math.Max(SpawnTimerRange.X, SpawnTimerRange.Y), Rand.RandSync.Unsynced);
SpawnTimer += deltaTime;
if (SpawnTimer > SpawnTimerGoal)
{
Spawn();
SpawnTimerGoal = null;
SpawnTimer = 0;
}
}
public override void ReceiveSignal(Signal signal, Connection connection)
{
bool isNonZero = signal.value != "0";
switch (connection.Name)
{
case "set_state":
CanSpawn = isNonZero;
break;
case "toggle" when isNonZero:
CanSpawn = !CanSpawn;
break;
}
}
private RectangleF GetAreaRectangle(Vector2 size, Vector2 offset, bool draw)
{
Vector2 pos = item.WorldPosition;
if (draw)
{
pos.Y = -pos.Y;
}
pos += offset;
RectangleF rect = new RectangleF(pos.X - size.X / 2f, pos.Y - size.Y / 2f, size.X, size.Y);
return rect;
}
private bool CanSpawnMore()
{
if (!CanSpawn) { return false; }
if (OnlySpawnWhenCrewInRange)
{
if (!Character.CharacterList.Any(c => !c.IsDead && c.IsOnPlayerTeam && IsInRange(c.WorldPosition, crewArea: true, rangePad: false)))
{
return false;
}
}
int amount;
if (!string.IsNullOrWhiteSpace(SpeciesName))
{
amount = Character.CharacterList.Count(c => !c.IsDead && c.SpeciesName.Equals(SpeciesName, StringComparison.OrdinalIgnoreCase) && IsInRange(c.WorldPosition, crewArea: false, rangePad: true));
}
else if (!string.IsNullOrWhiteSpace(ItemIdentifier))
{
amount = Item.ItemList.Count(it => it.Submarine == item.Submarine && it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.OrdinalIgnoreCase) && IsInRange(it.WorldPosition, crewArea: false, rangePad: true));
}
else
{
return false;
}
return amount < MaximumAmount;
}
private bool IsInRange(Vector2 worldPos, bool crewArea = false, bool rangePad = false)
{
Vector2 offset = crewArea ? CrewAreaOffset : SpawnAreaOffset;
offset.Y = -offset.Y;
switch (crewArea ? CrewAreaShape : SpawnAreaShape)
{
case AreaShape.Circle:
Vector2 center = item.WorldPosition + offset;
float distance = (crewArea ? CrewAreaRadius : SpawnAreaRadius) + (rangePad ? MaximumAmountRangePadding : 0);
return Vector2.DistanceSquared(worldPos, center) < distance * distance;
case AreaShape.Rectangle:
RectangleF rect = GetAreaRectangle(crewArea ? CrewAreaBounds : SpawnAreaBounds, offset, draw: false);
if (rangePad)
{
rect.Inflate(MaximumAmountRangePadding, MaximumAmountRangePadding);
}
return rect.Contains(worldPos);
}
return false;
}
public void Spawn()
{
if (!CanSpawnMore()) { return; }
int minAmount = Math.Min((int)SpawnAmountRange.X, (int)SpawnAmountRange.Y),
maxAmount = Math.Max((int)SpawnAmountRange.X, (int)SpawnAmountRange.Y);
int amount = Rand.Range(minAmount, maxAmount, Rand.RandSync.Unsynced);
Vector2 offset = SpawnAreaOffset;
offset.Y = -offset.Y;
switch (SpawnAreaShape)
{
case AreaShape.Circle:
{
var (x, y) = item.WorldPosition + offset;
for (int i = 0; i < Math.Max(1, amount); i++)
{
float angle = Rand.Range(-MathHelper.TwoPi, MathHelper.TwoPi);
float distance = Rand.Range(0, SpawnAreaRadius, Rand.RandSync.Unsynced);
Vector2 spawnPos = new Vector2(x + distance * (float)Math.Cos(angle), y + distance * (float)Math.Sin(angle));
SpawnEntity(spawnPos);
}
break;
}
case AreaShape.Rectangle:
{
RectangleF rect = GetAreaRectangle(SpawnAreaBounds, offset, draw: false);
for (int i = 0; i < Math.Max(1, amount); i++)
{
float minX = Math.Min(rect.Left, rect.Right),
maxX = Math.Max(rect.Left, rect.Right),
minY = Math.Min(rect.Top, rect.Bottom),
maxY = Math.Max(rect.Top, rect.Bottom);
Vector2 spawnPos = new Vector2(Rand.Range(minX, maxX, Rand.RandSync.Unsynced), Rand.Range(minY, maxY, Rand.RandSync.Unsynced));
SpawnEntity(spawnPos);
}
break;
}
}
void SpawnEntity(Vector2 pos)
{
if (!string.IsNullOrWhiteSpace(SpeciesName))
{
string[] allSpecies = SpeciesName.Split(',');
string species = allSpecies.GetRandom().Trim();
Entity.Spawner.AddToSpawnQueue(species, pos);
}
else if (!string.IsNullOrWhiteSpace(ItemIdentifier))
{
string[] allItems = ItemIdentifier.Split(',');
string itemIdentifier = allItems.GetRandom().Trim();
ItemPrefab? prefab = ItemPrefab.Find(null, itemIdentifier);
if (prefab is null) { return; }
if (item.Submarine is { } sub)
{
pos -= sub.Position;
}
Entity.Spawner.AddToSpawnQueue(prefab, pos, item.Submarine);
}
}
}
}
}
@@ -598,9 +598,12 @@ namespace Barotrauma.Items.Components
DebugConsole.AddWarning("Character without CharacterInfo attempting to attach a limited attachable item!"); DebugConsole.AddWarning("Character without CharacterInfo attempting to attach a limited attachable item!");
return false; return false;
} }
Vector2 attachPos = GetAttachPosition(character, useWorldCoordinates: true);
Structure attachTarget = Structure.GetAttachTarget(attachPos);
int maxAttachableCount = (int)character.Info.GetSavedStatValue(StatTypes.MaxAttachableCount, item.Prefab.Identifier); int maxAttachableCount = (int)character.Info.GetSavedStatValue(StatTypes.MaxAttachableCount, item.Prefab.Identifier);
int currentlyAttachedCount = Item.ItemList.Count( int currentlyAttachedCount = Item.ItemList.Count(
i => i.Submarine == item.Submarine && i.GetComponent<Holdable>() is Holdable holdable && holdable.Attached && i.Prefab.Identifier == item.prefab.Identifier); i => i.Submarine == attachTarget?.Submarine && i.GetComponent<Holdable>() is Holdable holdable && holdable.Attached && i.Prefab.Identifier == item.prefab.Identifier);
if (currentlyAttachedCount >= maxAttachableCount) if (currentlyAttachedCount >= maxAttachableCount)
{ {
#if CLIENT #if CLIENT
@@ -77,6 +77,7 @@ namespace Barotrauma.Items.Components
}; };
} }
item.IsShootable = true; item.IsShootable = true;
item.RequireAimToUse = element.Parent.GetAttributeBool("requireaimtouse", true);
PreferredContainedItems = element.GetAttributeStringArray("preferredcontaineditems", new string[0], convertToLowerInvariant: true); PreferredContainedItems = element.GetAttributeStringArray("preferredcontaineditems", new string[0], convertToLowerInvariant: true);
} }
@@ -210,7 +211,7 @@ namespace Barotrauma.Items.Components
bool aim = item.RequireAimToUse && picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim; bool aim = item.RequireAimToUse && picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim;
if (aim) if (aim)
{ {
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4)); hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4));
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true); ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
} }
else else
@@ -222,16 +223,16 @@ namespace Barotrauma.Items.Components
else else
{ {
// TODO: We might want to make this configurable // TODO: We might want to make this configurable
hitPos = MathUtils.WrapAnglePi(hitPos - deltaTime * 15f); hitPos -= deltaTime * 15f;
if (Swing) if (Swing)
{ {
ac.HoldItem(deltaTime, item, handlePos, SwingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos); ac.HoldItem(deltaTime, item, handlePos, SwingPos, Vector2.Zero, aim: false, hitPos, holdAngle);
} }
else else
{ {
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle); ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
} }
if (hitPos < -MathHelper.PiOver2) if (hitPos < -MathHelper.Pi)
{ {
RestoreCollision(); RestoreCollision();
hitting = false; hitting = false;
@@ -74,8 +74,8 @@ namespace Barotrauma.Items.Components
{ {
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation); Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation);
Vector2 flippedPos = barrelPos; Vector2 flippedPos = barrelPos;
if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X; if (item.body.Dir < 0.0f) { flippedPos.X = -flippedPos.X; }
return Vector2.Transform(flippedPos, bodyTransform); return Vector2.Transform(flippedPos, bodyTransform) * item.Scale;
} }
} }
@@ -712,7 +712,7 @@ namespace Barotrauma.Items.Components
humanAnim.Crouching = true; humanAnim.Crouching = true;
} }
} }
if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.inWater)) if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.InWater))
{ {
// Steer closer // Steer closer
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering) if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
@@ -522,7 +522,7 @@ namespace Barotrauma.Items.Components
if (Math.Abs(item.Rotation) > 0.01f) if (Math.Abs(item.Rotation) > 0.01f)
{ {
Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation)); Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation));
transformedItemPos = Vector2.Transform(transformedItemPos, transform); transformedItemPos = Vector2.Transform(transformedItemPos - item.Position, transform) + item.Position;
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform); transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
@@ -550,6 +550,10 @@ namespace Barotrauma.Items.Components
currentRotation *= item.body.Dir; currentRotation *= item.body.Dir;
currentRotation += item.body.Rotation; currentRotation += item.body.Rotation;
} }
else
{
currentRotation += MathHelper.ToRadians(-item.Rotation);
}
int i = 0; int i = 0;
Vector2 currentItemPos = transformedItemPos; Vector2 currentItemPos = transformedItemPos;
@@ -64,13 +64,6 @@ namespace Barotrauma.Items.Components
set; set;
} }
[Editable, Serialize(true, true, description: "Enable hull condition mode.")]
public bool EnableHullCondition
{
get;
set;
}
[Editable, Serialize(true, true, description: "Enable item finder mode.")] [Editable, Serialize(true, true, description: "Enable item finder mode.")]
public bool EnableItemFinder public bool EnableItemFinder
{ {
@@ -148,30 +141,49 @@ namespace Barotrauma.Items.Components
hullDatas.Add(sourceHull, hullData); hullDatas.Add(sourceHull, hullData);
} }
if (hullData.Distort) return; if (hullData.Distort) { return; }
switch (connection.Name) switch (connection.Name)
{ {
case "water_data_in": case "water_data_in":
//cheating a bit because water detectors don't actually send the water level //cheating a bit because water detectors don't actually send the water level
float waterAmount;
if (source.GetComponent<WaterDetector>() == null) if (source.GetComponent<WaterDetector>() == null)
{ {
hullData.ReceivedWaterAmount = Rand.Range(0.0f, 1.0f); waterAmount = Rand.Range(0.0f, 1.0f);
} }
else else
{ {
hullData.ReceivedWaterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f); waterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
}
hullData.ReceivedWaterAmount = waterAmount;
foreach (var linked in sourceHull.linkedTo)
{
if (!(linked is Hull linkedHull)) { continue; }
if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData))
{
linkedHullData = new HullData();
hullDatas.Add(linkedHull, linkedHullData);
}
linkedHullData.ReceivedWaterAmount = waterAmount;
} }
break; break;
case "oxygen_data_in": case "oxygen_data_in":
float oxy; if (!float.TryParse(signal.value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float oxy))
if (!float.TryParse(signal.value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out oxy))
{ {
oxy = Rand.Range(0.0f, 100.0f); oxy = Rand.Range(0.0f, 100.0f);
} }
hullData.ReceivedOxygenAmount = oxy; hullData.ReceivedOxygenAmount = oxy;
foreach (var linked in sourceHull.linkedTo)
{
if (!(linked is Hull linkedHull)) { continue; }
if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData))
{
linkedHullData = new HullData();
hullDatas.Add(linkedHull, linkedHullData);
}
linkedHullData.ReceivedOxygenAmount = oxy;
}
break; break;
} }
} }
@@ -9,6 +9,14 @@ namespace Barotrauma.Items.Components
{ {
public const int MaxQuality = 3; public const int MaxQuality = 3;
public static readonly float[] QualityCommonnesses = new float[]
{
0.8f,
0.15f,
0.045f,
0.005f,
};
public enum StatType public enum StatType
{ {
Condition, Condition,
@@ -39,7 +47,18 @@ namespace Barotrauma.Items.Components
public int QualityLevel public int QualityLevel
{ {
get { return qualityLevel; } get { return qualityLevel; }
set { qualityLevel = MathHelper.Clamp(value, 0, MaxQuality); } set
{
if (value == qualityLevel) { return; }
bool wasInFullCondition = item.IsFullCondition;
qualityLevel = MathHelper.Clamp(value, 0, MaxQuality);
//set the condition to the new max condition
if (wasInFullCondition && statValues.ContainsKey(StatType.Condition))
{
item.Condition = item.MaxCondition;
}
}
} }
public Quality(Item item, XElement element) : base(item, element) public Quality(Item item, XElement element) : base(item, element)
@@ -376,7 +376,7 @@ namespace Barotrauma.Items.Components
tinkeringDuration -= deltaTime; tinkeringDuration -= deltaTime;
// not great to interject it here, should be less reliant on returning // not great to interject it here, should be less reliant on returning
float conditionDecrease = deltaTime * (CurrentFixer.GetStatValue(StatTypes.TinkeringDamage) / item.MaxCondition) * 100f; float conditionDecrease = deltaTime * (CurrentFixer.GetStatValue(StatTypes.TinkeringDamage) / item.Prefab.Health) * 100f;
item.Condition -= conditionDecrease; item.Condition -= conditionDecrease;
if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f) if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f)
@@ -424,7 +424,8 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
float conditionIncrease = deltaTime / (fixDuration / item.MaxCondition); // scale with prefab's health instead of real health to ensure repair speed remains static with upgrades
float conditionIncrease = deltaTime / (fixDuration / item.Prefab.Health);
item.Condition += conditionIncrease; item.Condition += conditionIncrease;
#if SERVER #if SERVER
GameMain.Server.KarmaManager.OnItemRepaired(CurrentFixer, this, conditionIncrease); GameMain.Server.KarmaManager.OnItemRepaired(CurrentFixer, this, conditionIncrease);
@@ -458,7 +459,8 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
float conditionDecrease = deltaTime / (fixDuration / item.MaxCondition); // scale with prefab's health instead of real health to ensure sabotage speed remains static with (any) upgrades
float conditionDecrease = deltaTime / (fixDuration / item.Prefab.Health);
item.Condition -= conditionDecrease; item.Condition -= conditionDecrease;
} }
@@ -159,7 +159,10 @@ namespace Barotrauma.Items.Components
{ {
lightColor = value; lightColor = value;
#if CLIENT #if CLIENT
if (Light != null) Light.Color = IsActive ? lightColor : Color.Transparent; if (Light != null)
{
Light.Color = IsActive ? lightColor : Color.Transparent;
}
#endif #endif
} }
} }
@@ -178,7 +178,7 @@ namespace Barotrauma.Items.Components
//signal strength diminishes by distance //signal strength diminishes by distance
float sentSignalStrength = signal.strength * float sentSignalStrength = signal.strength *
MathHelper.Clamp(1.0f - (Vector2.Distance(item.WorldPosition, wifiComp.item.WorldPosition) / wifiComp.range), 0.0f, 1.0f); MathHelper.Clamp(1.0f - (Vector2.Distance(item.WorldPosition, wifiComp.item.WorldPosition) / wifiComp.range), 0.0f, 1.0f);
Signal s = new Signal(signal.value, ++signal.stepsTaken, sender: signal.sender, source: signal.source, Signal s = new Signal(signal.value, signal.stepsTaken + 1, sender: signal.sender, source: signal.source,
power: 0.0f, strength: sentSignalStrength); power: 0.0f, strength: sentSignalStrength);
if (wifiComp.signalOutConnection != null) if (wifiComp.signalOutConnection != null)
@@ -48,8 +48,8 @@ namespace Barotrauma
return false; return false;
} }
} }
if (items[0].Prefab.Identifier != item.Prefab.Identifier || if (items[0].Quality != item.Quality) { return false; }
items.Count + 1 > item.Prefab.MaxStackSize) if (items[0].Prefab.Identifier != item.Prefab.Identifier || items.Count + 1 > item.Prefab.MaxStackSize)
{ {
return false; return false;
} }
@@ -254,6 +254,12 @@ namespace Barotrauma
{ {
if (!Prefab.AllowRotatingInEditor) { return; } if (!Prefab.AllowRotatingInEditor) { return; }
rotationRad = MathHelper.ToRadians(value); rotationRad = MathHelper.ToRadians(value);
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen)
{
SetContainedItemPositions();
}
#endif
} }
} }
@@ -1868,6 +1874,7 @@ namespace Barotrauma
{ {
component.FlipX(relativeToSub); component.FlipX(relativeToSub);
} }
SetContainedItemPositions();
} }
public override void FlipY(bool relativeToSub) public override void FlipY(bool relativeToSub)
@@ -1892,6 +1899,7 @@ namespace Barotrauma
{ {
component.FlipY(relativeToSub); component.FlipY(relativeToSub);
} }
SetContainedItemPositions();
} }
/// <summary> /// <summary>
@@ -2112,8 +2120,6 @@ namespace Barotrauma
} while (CoroutineManager.DeltaTime <= 0.0f); } while (CoroutineManager.DeltaTime <= 0.0f);
delayedSignals.Remove((signal, connection)); delayedSignals.Remove((signal, connection));
signal.source = this;
connection.SendSignal(signal); connection.SendSignal(signal);
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
@@ -2470,6 +2476,8 @@ namespace Barotrauma
parentInventory.RemoveItem(this); parentInventory.RemoveItem(this);
parentInventory = null; parentInventory = null;
} }
SetContainedItemPositions();
} }
public void Equip(Character character) public void Equip(Character character)
@@ -2741,7 +2749,7 @@ namespace Barotrauma
{ {
CoroutineManager.StopCoroutines(logPropertyChangeCoroutine); CoroutineManager.StopCoroutines(logPropertyChangeCoroutine);
} }
logPropertyChangeCoroutine = CoroutineManager.InvokeAfter(() => logPropertyChangeCoroutine = CoroutineManager.Invoke(() =>
{ {
GameServer.Log($"{sender.Character.Name} set the value \"{property.Name}\" of the item \"{Name}\" to \"{logValue}\".", ServerLog.MessageType.ItemInteraction); GameServer.Log($"{sender.Character.Name} set the value \"{property.Name}\" of the item \"{Name}\" to \"{logValue}\".", ServerLog.MessageType.ItemInteraction);
}, delay: 1.0f); }, delay: 1.0f);
@@ -920,7 +920,8 @@ namespace Barotrauma
CanSpriteFlipY = subElement.GetAttributeBool("canflipy", true); CanSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
sprite = new Sprite(subElement, spriteFolder, lazyLoad: true); sprite = new Sprite(subElement, spriteFolder, lazyLoad: true);
if (subElement.Attribute("sourcerect") == null) if (subElement.Attribute("sourcerect") == null &&
subElement.Attribute("sheetindex") == null)
{ {
DebugConsole.ThrowError("Warning - sprite sourcerect not configured for item \"" + Name + "\"!"); DebugConsole.ThrowError("Warning - sprite sourcerect not configured for item \"" + Name + "\"!");
} }
@@ -1152,11 +1153,8 @@ namespace Barotrauma
{ {
DebugConsole.ThrowError("Error in item prefab \"" + Name + "\" - suitable treatments should be defined using item identifiers, not item names."); DebugConsole.ThrowError("Error in item prefab \"" + Name + "\" - suitable treatments should be defined using item identifiers, not item names.");
} }
string treatmentIdentifier = (subElement.GetAttributeString("identifier", null) ?? subElement.GetAttributeString("type", string.Empty)).ToLowerInvariant();
string treatmentIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
float suitability = subElement.GetAttributeFloat("suitability", 0.0f); float suitability = subElement.GetAttributeFloat("suitability", 0.0f);
treatmentSuitability.Add(treatmentIdentifier, suitability); treatmentSuitability.Add(treatmentIdentifier, suitability);
break; break;
} }
@@ -1829,7 +1829,7 @@ namespace Barotrauma
private void GenerateRuin(Point ruinPos, bool mirror) private void GenerateRuin(Point ruinPos, bool mirror)
{ {
var ruinGenerationParams = RuinGenerationParams.GetRandom(); var ruinGenerationParams = RuinGenerationParams.GetRandom(Rand.RandSync.Server);
LocationType locationType = StartLocation?.Type; LocationType locationType = StartLocation?.Type;
if (locationType == null) if (locationType == null)
@@ -1839,7 +1839,7 @@ namespace Barotrauma
{ {
locationType = LocationType.List.Where(lt => locationType = LocationType.List.Where(lt =>
ruinGenerationParams.AllowedLocationTypes.Any(allowedType => ruinGenerationParams.AllowedLocationTypes.Any(allowedType =>
allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(); allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(Rand.RandSync.Server);
} }
} }
@@ -3594,7 +3594,7 @@ namespace Barotrauma
{ {
locationType = LocationType.List.Where(lt => locationType = LocationType.List.Where(lt =>
outpostGenerationParams.AllowedLocationTypes.Any(allowedType => outpostGenerationParams.AllowedLocationTypes.Any(allowedType =>
allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(); allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(Rand.RandSync.Server);
} }
} }
@@ -3953,7 +3953,7 @@ namespace Barotrauma
bool TryGetExtraSpawnPoint(out Vector2 point) bool TryGetExtraSpawnPoint(out Vector2 point)
{ {
point = Vector2.Zero; point = Vector2.Zero;
var hull = Hull.hullList.FindAll(h => h.Submarine == wreck).GetRandom(); var hull = Hull.hullList.FindAll(h => h.Submarine == wreck).GetRandom(Rand.RandSync.Unsynced);
if (hull != null) if (hull != null)
{ {
point = hull.WorldPosition; point = hull.WorldPosition;
@@ -44,7 +44,7 @@ namespace Barotrauma.RuinGeneration
this.filePath = filePath; this.filePath = filePath;
} }
public static RuinGenerationParams GetRandom() public static RuinGenerationParams GetRandom(Rand.RandSync randSync = Rand.RandSync.Server)
{ {
if (paramsList == null) { LoadAll(); } if (paramsList == null) { LoadAll(); }
@@ -54,7 +54,7 @@ namespace Barotrauma.RuinGeneration
return new RuinGenerationParams(null, null); return new RuinGenerationParams(null, null);
} }
return paramsList[Rand.Int(paramsList.Count, Rand.RandSync.Server)]; return paramsList[Rand.Int(paramsList.Count, randSync)];
} }
private static void LoadAll() private static void LoadAll()
@@ -90,7 +90,7 @@ namespace Barotrauma
private const float StoreMaxReputationModifier = 0.1f; private const float StoreMaxReputationModifier = 0.1f;
private const float StoreSellPriceModifier = 0.8f; private const float StoreSellPriceModifier = 0.8f;
private const float DailySpecialPriceModifier = 0.9f; private const float DailySpecialPriceModifier = 0.5f;
private const float RequestGoodPriceModifier = 1.5f; private const float RequestGoodPriceModifier = 1.5f;
public const int StoreInitialBalance = 5000; public const int StoreInitialBalance = 5000;
/// <summary> /// <summary>
@@ -472,7 +472,8 @@ namespace Barotrauma
foreach (LocationConnection connection in Connections) foreach (LocationConnection connection in Connections)
{ {
connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f); float difficulty = GetLevelDifficulty(connection.CenterPos.X / Width);
connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f);
} }
AssignBiomes(); AssignBiomes();
@@ -482,7 +483,7 @@ namespace Barotrauma
{ {
location.LevelData = new LevelData(location) location.LevelData = new LevelData(location)
{ {
Difficulty = MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f) Difficulty = MathHelper.Clamp(GetLevelDifficulty(location.MapPosition.X / Width), 0.0f, 100.0f)
}; };
location.UnlockInitialMissions(); location.UnlockInitialMissions();
} }
@@ -490,6 +491,14 @@ namespace Barotrauma
{ {
connection.LevelData = new LevelData(connection); connection.LevelData = new LevelData(connection);
} }
float GetLevelDifficulty(float areaDifficulty)
{
const float CurveModifier = 1.5f;
const float DifficultyMultiplier = 1.1f;
const float BaseDifficulty = -3f;
return (float)(1 - Math.Pow(1 - areaDifficulty, CurveModifier)) * DifficultyMultiplier * 100f + BaseDifficulty;
}
} }
partial void GenerateLocationConnectionVisuals(); partial void GenerateLocationConnectionVisuals();
@@ -85,7 +85,15 @@ namespace Barotrauma
var subInfo = new SubmarineInfo(outpostModuleFile.Path); var subInfo = new SubmarineInfo(outpostModuleFile.Path);
if (subInfo.OutpostModuleInfo != null) if (subInfo.OutpostModuleInfo != null)
{ {
if (subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin") != generationParams is RuinGeneration.RuinGenerationParams) { continue; } if (generationParams is RuinGeneration.RuinGenerationParams)
{
//if the module doesn't have the ruin flag or any other flag used in the generation params, don't use it in ruins
if (!subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin") &&
!generationParams.ModuleCounts.Any(m => subInfo.OutpostModuleInfo.ModuleFlags.Contains(m.Key)))
{
continue;
}
}
outpostModules.Add(subInfo); outpostModules.Add(subInfo);
} }
} }
@@ -11,6 +11,7 @@ using System.Xml.Linq;
using Barotrauma.Abilities; using Barotrauma.Abilities;
#if CLIENT #if CLIENT
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Lights;
#endif #endif
namespace Barotrauma namespace Barotrauma
@@ -216,6 +217,13 @@ namespace Barotrauma
UpdateSections(); UpdateSections();
} }
} }
#if CLIENT
foreach (LightSource light in Lights)
{
light.SpriteScale = scale * textureScale;
}
#endif
} }
} }
@@ -231,6 +239,13 @@ namespace Barotrauma
textureScale = new Vector2( textureScale = new Vector2(
MathHelper.Clamp(value.X, 0.01f, 10), MathHelper.Clamp(value.X, 0.01f, 10),
MathHelper.Clamp(value.Y, 0.01f, 10)); MathHelper.Clamp(value.Y, 0.01f, 10));
#if CLIENT
foreach (LightSource light in Lights)
{
light.LightTextureScale = textureScale * scale;
}
#endif
} }
} }
@@ -239,7 +254,13 @@ namespace Barotrauma
public Vector2 TextureOffset public Vector2 TextureOffset
{ {
get { return textureOffset; } get { return textureOffset; }
set { textureOffset = value; } set
{
textureOffset = value;
#if CLIENT
SetLightTextureOffset();
#endif
}
} }
@@ -364,6 +385,12 @@ namespace Barotrauma
#if CLIENT #if CLIENT
convexHulls?.ForEach(x => x.Move(amount)); convexHulls?.ForEach(x => x.Move(amount));
foreach (LightSource light in Lights)
{
light.LightTextureTargetSize = rect.Size.ToVector2();
light.Position = rect.Location.ToVector2();
}
#endif #endif
} }
@@ -433,6 +460,41 @@ namespace Barotrauma
SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this); SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this);
#if CLIENT
foreach (XElement subElement in sp.ConfigElement.Elements())
{
if (subElement.Name.ToString().Equals("light", StringComparison.OrdinalIgnoreCase))
{
Vector2 pos = rect.Location.ToVector2();
pos.Y += rect.Height;
LightSource light = new LightSource(subElement)
{
ParentSub = Submarine,
Position = rect.Location.ToVector2(),
CastShadows = false,
IsBackground = false,
Color = subElement.GetAttributeColor("lightcolor", Color.White),
SpriteScale = Vector2.One,
Range = 0,
LightTextureTargetSize = rect.Size.ToVector2(),
LightTextureScale = textureScale * scale,
LightSourceParams =
{
Flicker = subElement.GetAttributeFloat("flicker", 0f),
FlickerSpeed = subElement.GetAttributeFloat("flickerspeed", 0f),
PulseAmount = subElement.GetAttributeFloat("pulseamount", 0f),
PulseFrequency = subElement.GetAttributeFloat("pulsefrequency", 0f),
BlinkFrequency = subElement.GetAttributeFloat("blinkfrequency", 0f)
}
};
Lights.Add(light);
SetLightTextureOffset();
}
}
#endif
// Only add ai targets automatically to submarine/outpost walls // Only add ai targets automatically to submarine/outpost walls
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !submarine.Info.IsWreck && !NoAITarget) if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !submarine.Info.IsWreck && !NoAITarget)
{ {
@@ -693,6 +755,10 @@ namespace Barotrauma
#if CLIENT #if CLIENT
if (convexHulls != null) convexHulls.ForEach(x => x.Remove()); if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
foreach (LightSource light in Lights)
{
light.Remove();
}
#endif #endif
} }
@@ -725,6 +791,10 @@ namespace Barotrauma
#if CLIENT #if CLIENT
if (convexHulls != null) convexHulls.ForEach(x => x.Remove()); if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
foreach (LightSource light in Lights)
{
light.Remove();
}
#endif #endif
} }
@@ -291,7 +291,8 @@ namespace Barotrauma
{ {
case "sprite": case "sprite":
sp.sprite = new Sprite(subElement, lazyLoad: true); sp.sprite = new Sprite(subElement, lazyLoad: true);
if (subElement.Attribute("sourcerect") == null) if (subElement.Attribute("sourcerect") == null &&
subElement.Attribute("sheetindex") == null)
{ {
DebugConsole.ThrowError("Warning - sprite sourcerect not configured for structure \"" + sp.name + "\"!"); DebugConsole.ThrowError("Warning - sprite sourcerect not configured for structure \"" + sp.name + "\"!");
} }
@@ -914,9 +914,11 @@ namespace Barotrauma
mapEntity.Move(-HiddenSubPosition); mapEntity.Move(-HiddenSubPosition);
} }
var prevBodyType = subBody.Body.BodyType;
Vector2 pos = new Vector2(subBody.Position.X, subBody.Position.Y); Vector2 pos = new Vector2(subBody.Position.X, subBody.Position.Y);
subBody.Body.Remove(); subBody.Body.Remove();
subBody = new SubmarineBody(this); subBody = new SubmarineBody(this);
subBody.Body.BodyType = prevBodyType;
SetPosition(pos, new List<Submarine>(parents.Where(p => p != this))); SetPosition(pos, new List<Submarine>(parents.Where(p => p != this)));
if (entityGrid != null) if (entityGrid != null)
@@ -1429,6 +1431,11 @@ namespace Barotrauma
} }
} }
} }
else if (info.IsRuin)
{
ShowSonarMarker = false;
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
}
} }
if (entityGrid != null) if (entityGrid != null)
@@ -868,11 +868,12 @@ namespace Barotrauma
} }
} }
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false) public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false, string spawnPointTag = null)
{ {
return WayPointList.GetRandom(wp => return WayPointList.GetRandom(wp =>
wp.Submarine == sub && wp.Submarine == sub &&
wp.spawnType == spawnType && wp.spawnType == spawnType &&
(string.IsNullOrEmpty(spawnPointTag) || wp.Tags.Any(t => t.Equals(spawnPointTag, StringComparison.OrdinalIgnoreCase))) &&
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)), (assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced); useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
} }
@@ -92,7 +92,7 @@ namespace Barotrauma
} }
case ConditionType.AllowRotating: case ConditionType.AllowRotating:
{ {
return entity is Item item && item.Prefab.AllowRotatingInEditor && Screen.Selected == GameMain.SubEditorScreen; return entity is Item item && item.body == null && item.Prefab.AllowRotatingInEditor && Screen.Selected == GameMain.SubEditorScreen;
} }
} }
return false; return false;
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -15,6 +16,22 @@ namespace Barotrauma
{ {
public static class XMLExtensions public static class XMLExtensions
{ {
private static ImmutableDictionary<Type, Func<string, object, object>> converters
= new Dictionary<Type, Func<string, object, object>>()
{
{ typeof(string), (str, defVal) => str },
{ typeof(int), (str, defVal) => int.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out int result) ? result : defVal },
{ typeof(uint), (str, defVal) => uint.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out uint result) ? result : defVal },
{ typeof(UInt64), (str, defVal) => UInt64.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out UInt64 result) ? result : defVal },
{ typeof(float), (str, defVal) => float.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out float result) ? result : defVal },
{ typeof(bool), (str, defVal) => bool.TryParse(str, out bool result) ? result : defVal },
{ typeof(Color), (str, defVal) => ParseColor(str) },
{ typeof(Vector2), (str, defVal) => ParseVector2(str) },
{ typeof(Vector3), (str, defVal) => ParseVector3(str) },
{ typeof(Vector4), (str, defVal) => ParseVector4(str) },
{ typeof(Rectangle), (str, defVal) => ParseRect(str, true) }
}.ToImmutableDictionary();
public static string ParseContentPathFromUri(this XObject element) => System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri); public static string ParseContentPathFromUri(this XObject element) => System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri);
public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings
@@ -485,6 +502,25 @@ namespace Barotrauma
return ParseRect(element.Attribute(name).Value, false); return ParseRect(element.Attribute(name).Value, false);
} }
//TODO: nested tuples and and n-uples where n!=2 are unsupported
public static (T1, T2) GetAttributeTuple<T1, T2>(this XElement element, string name, (T1, T2) defaultValue)
{
string strValue = element.GetAttributeString(name, $"({defaultValue.Item1}, {defaultValue.Item2})").Trim();
return ParseTuple(strValue, defaultValue);
}
public static (T1, T2)[] GetAttributeTupleArray<T1, T2>(this XElement element, string name,
(T1, T2)[] defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
string stringValue = element.Attribute(name).Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
return stringValue.Split(';').Select(s => ParseTuple<T1, T2>(s, default)).ToArray();
}
public static string ElementInnerText(this XElement el) public static string ElementInnerText(this XElement el)
{ {
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
@@ -525,13 +561,29 @@ namespace Barotrauma
=> $"{color.R},{color.G},{color.B},{color.A}"; => $"{color.R},{color.G},{color.B},{color.A}";
public static string ToStringHex(this Color color) public static string ToStringHex(this Color color)
=> $"#{color.R:X2}{color.G:X2}{color.B:X2}{color.A:X2}"; => $"#{color.R:X2}{color.G:X2}{color.B:X2}"
+ ((color.A < 255) ? $"{color.A:X2}" : "");
public static string RectToString(Rectangle rect) public static string RectToString(Rectangle rect)
{ {
return rect.X + "," + rect.Y + "," + rect.Width + "," + rect.Height; return rect.X + "," + rect.Y + "," + rect.Width + "," + rect.Height;
} }
public static (T1, T2) ParseTuple<T1, T2>(string strValue, (T1, T2) defaultValue)
{
strValue = strValue.Trim();
//require parentheses
if (strValue[0] != '(' || strValue[^1] != ')') { return defaultValue; }
//remove parentheses
strValue = strValue[1..^1];
string[] elems = strValue.Split(',');
if (elems.Length != 2) { return defaultValue; }
return ((T1)converters[typeof(T1)].Invoke(elems[0], defaultValue.Item1),
(T2)converters[typeof(T2)].Invoke(elems[1], defaultValue.Item2));
}
public static Point ParsePoint(string stringPoint, bool errorMessages = true) public static Point ParsePoint(string stringPoint, bool errorMessages = true)
{ {
string[] components = stringPoint.Split(','); string[] components = stringPoint.Split(',');
@@ -197,6 +197,18 @@ namespace Barotrauma
} }
} }
public class GiveTalentInfo
{
public string[] TalentIdentifiers;
public bool GiveRandom;
public GiveTalentInfo(XElement element, string parentDebugName)
{
TalentIdentifiers = element.GetAttributeStringArray("talentidentifiers", new string[0], convertToLowerInvariant: true);
GiveRandom = element.GetAttributeBool("giverandom", false);
}
}
public class CharacterSpawnInfo : ISerializableEntity public class CharacterSpawnInfo : ISerializableEntity
{ {
public string Name => $"Character Spawn Info ({SpeciesName})"; public string Name => $"Character Spawn Info ({SpeciesName})";
@@ -274,6 +286,8 @@ namespace Barotrauma
private readonly bool spawnItemRandomly; private readonly bool spawnItemRandomly;
private readonly List<CharacterSpawnInfo> spawnCharacters; private readonly List<CharacterSpawnInfo> spawnCharacters;
public readonly List<GiveTalentInfo> giveTalentInfos;
private readonly List<AITrigger> aiTriggers; private readonly List<AITrigger> aiTriggers;
private readonly List<EventPrefab> triggeredEvents; private readonly List<EventPrefab> triggeredEvents;
@@ -374,6 +388,7 @@ namespace Barotrauma
spawnItems = new List<ItemSpawnInfo>(); spawnItems = new List<ItemSpawnInfo>();
spawnItemRandomly = element.GetAttributeBool("spawnitemrandomly", false); spawnItemRandomly = element.GetAttributeBool("spawnitemrandomly", false);
spawnCharacters = new List<CharacterSpawnInfo>(); spawnCharacters = new List<CharacterSpawnInfo>();
giveTalentInfos = new List<GiveTalentInfo>();
aiTriggers = new List<AITrigger>(); aiTriggers = new List<AITrigger>();
Afflictions = new List<Affliction>(); Afflictions = new List<Affliction>();
Explosions = new List<Explosion>(); Explosions = new List<Explosion>();
@@ -576,7 +591,7 @@ namespace Barotrauma
break; break;
case "requiredaffliction": case "requiredaffliction":
requiredAfflictions ??= new HashSet<(string, float)>(); requiredAfflictions ??= new HashSet<(string, float)>();
string[] ids = subElement.GetAttributeStringArray("identifier", new string[0]); string[] ids = subElement.GetAttributeStringArray("identifier", null) ?? subElement.GetAttributeStringArray("type", new string[0]);
foreach (string afflictionId in ids) foreach (string afflictionId in ids)
{ {
requiredAfflictions.Add(( requiredAfflictions.Add((
@@ -669,6 +684,10 @@ namespace Barotrauma
var newSpawnCharacter = new CharacterSpawnInfo(subElement, parentDebugName); var newSpawnCharacter = new CharacterSpawnInfo(subElement, parentDebugName);
if (!string.IsNullOrWhiteSpace(newSpawnCharacter.SpeciesName)) { spawnCharacters.Add(newSpawnCharacter); } if (!string.IsNullOrWhiteSpace(newSpawnCharacter.SpeciesName)) { spawnCharacters.Add(newSpawnCharacter); }
break; break;
case "givetalentinfo":
var newGiveTalentInfo = new GiveTalentInfo(subElement, parentDebugName);
if (newGiveTalentInfo.TalentIdentifiers.Any()) { giveTalentInfos.Add(newGiveTalentInfo); }
break;
case "aitrigger": case "aitrigger":
aiTriggers.Add(new AITrigger(subElement)); aiTriggers.Add(new AITrigger(subElement));
break; break;
@@ -1305,6 +1324,33 @@ namespace Barotrauma
} }
} }
} }
if (giveTalentInfos.Any() && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient))
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter?.Info == null) { continue; }
if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; }
// for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well
IEnumerable<string> disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)));
foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos)
{
IEnumerable<string> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s));
if (viableTalents.None()) { continue; }
if (giveTalentInfo.GiveRandom)
{
targetCharacter.GiveTalent(viableTalents.GetRandom(), true);
}
else
{
foreach (string talent in viableTalents)
{
targetCharacter.GiveTalent(talent, true);
}
}
}
}
} }
if (FireSize > 0.0f && entity != null) if (FireSize > 0.0f && entity != null)
@@ -1367,6 +1413,7 @@ namespace Barotrauma
}); });
} }
} }
if (spawnItemRandomly) if (spawnItemRandomly)
{ {
SpawnItem(spawnItems.GetRandom()); SpawnItem(spawnItems.GetRandom());
@@ -147,7 +147,7 @@ namespace Barotrauma
/// <returns></returns> /// <returns></returns>
private static float ApplyPercentage(float value, float amount, int times) private static float ApplyPercentage(float value, float amount, int times)
{ {
return times <= 0 ? value : ApplyPercentage(value + (value * amount / 100), amount, --times); return (1f + (amount / 100f * times)) * value;
} }
public static PropertyReference[] ParseAttributes(IEnumerable<XAttribute> attributes, Upgrade upgrade) public static PropertyReference[] ParseAttributes(IEnumerable<XAttribute> attributes, Upgrade upgrade)
@@ -68,7 +68,13 @@ namespace Barotrauma
Name = element.GetAttributeString("name", string.Empty); Name = element.GetAttributeString("name", string.Empty);
IsWallUpgrade = element.GetAttributeBool("wallupgrade", false); IsWallUpgrade = element.GetAttributeBool("wallupgrade", false);
if (string.IsNullOrWhiteSpace(Name)) string nameIdentifier = element.GetAttributeString("nameidentifier", "");
if (!string.IsNullOrWhiteSpace(nameIdentifier))
{
Name = TextManager.Get($"{nameIdentifier}", returnNull: true) ?? string.Empty;
}
else if (string.IsNullOrWhiteSpace(Name))
{ {
Name = TextManager.Get($"UpgradeCategory.{Identifier}", true) ?? string.Empty; Name = TextManager.Get($"UpgradeCategory.{Identifier}", true) ?? string.Empty;
} }
@@ -131,6 +137,8 @@ namespace Barotrauma
public string Description { get; } public string Description { get; }
public float IncreaseOnTooltip { get; }
public string Identifier { get; } public string Identifier { get; }
public string FilePath { get; } public string FilePath { get; }
@@ -172,7 +180,13 @@ namespace Barotrauma
var targetProperties = new Dictionary<string, string[]>(); var targetProperties = new Dictionary<string, string[]>();
if (string.IsNullOrWhiteSpace(Name)) string nameIdentifier = element.GetAttributeString("nameidentifier", "");
if (!string.IsNullOrWhiteSpace(nameIdentifier))
{
Name = TextManager.Get($"UpgradeName.{nameIdentifier}", returnNull: true) ?? string.Empty;
}
else if (string.IsNullOrWhiteSpace(Name))
{ {
Name = TextManager.Get($"UpgradeName.{Identifier}", returnNull: true) ?? string.Empty; Name = TextManager.Get($"UpgradeName.{Identifier}", returnNull: true) ?? string.Empty;
} }
@@ -182,6 +196,8 @@ namespace Barotrauma
Description = TextManager.Get($"UpgradeDescription.{Identifier}", returnNull: true) ?? string.Empty; Description = TextManager.Get($"UpgradeDescription.{Identifier}", returnNull: true) ?? string.Empty;
} }
IncreaseOnTooltip = element.GetAttributeFloat("increaseontooltip", 0f);
DebugConsole.Log(" " + Name); DebugConsole.Log(" " + Name);
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
+62 -1
View File
@@ -1,3 +1,54 @@
---------------------------------------------------------------------------------------------------------
v0.1500.6.0
---------------------------------------------------------------------------------------------------------
Additions and changes:
- Improvements and additions to the alien ruins and the new fractal guardians.
- More improvements and fixes to the overhauled character sprites and animations.
- Upgrade system reworked to work better in conjunction with new talents and quality systems. Quality of life upgrades made better or cheaper, hull upgrades are less effective towards the lategame but are better early, reorganized categories.
- Talent adjustments and balancing.
- Nerfed thermal goggles: some enemies are invisible to them and targets behind walls are much less clear.
- Removed the "burndamage" damage type (not the same as "burn") that was added as a temporary workaround to allow pulse lasers to bypass monster's damage modifiers.
- Made welding tools a bit less effective early to compensate for increases to their effectiveness from quality/talents.
- Fuel tanks, rods, grenades and other quality-based items can be stacked again. Items of different qualities still can't be put in the same stack.
- Endocrine Booster now gives a random talent.
- New sprite for the mudraptor beak grown by splicing mudraptor genes.
- Fabricating fuel rods now requirse electrical skills instead of mechanical.
- Reactor now requires electrical skills instead of mechanical to repair.
- When the status monitor receives the oxygen/water level for a hull, it registers it on all the linked hulls as well (-> no need to put an oxygen/water detector in all the hulls of a multi-hull room).
- Changed how skillbooks work: the skills are gained when you "finish" reading the book instead of continuously to prevent UI message spam and overlap, the books can be used on others in the health interface or by hitting them with the book.
- Color the selected (but unapplied) talent icons orange instead of changing the icon in the talent UI, made the apply button flash when there's unapplied talents.
- Merged status monitor's status and hull condition tabs.
- Halved concussion's damage threshold to make it possible for more sources of damage to trigger it, + halved the probability to compensate.
Fixes:
- Fixed high-quality batteries/tanks (or other items with an increased max condition) spawning in non-full condition (unstable only).
- Fixed ropes sometimes crashing the game (unstable only).
- Fixed some items occasionally disappearing from outposts when re-entering them. The most noticeable symptom was wires disappearing from the outpost's airlock, preventing the hatch from opening (unstable only).
- Fixed multiplayer campaign characters resetting if they're dead when the round ends.
- Fixed clients who aren't currently controlling a character not getting XP in the mp campaign (unstable only).
- The overdosed NPC in the "good samaritan" event can't die until the player has triggered the event (completing the event after the NPC had already died made no sense).
- Fixed paralyzant (and many other meds that don't do direct damage) not triggering guards.
- Fixed sonar monitor's UI being unnecessarily small.
- Fixed contained items inside contained items (e.g. magazines in a rifle on a weapon holder) not rotating in the sub editor.
- Fixed boarding axe (unstable only).
- Fixed signal source being wrong on delayed electrical signals (= signals that were delayed for the next frame after they'd passed through 10 steps). Most noticeably affected status monitors that need to know which oxygen/water detector a signal came from.
- Fixed WifiComponents delaying the signals based on the number of receivers, not how many steps the signal has actually taken, contributing to the previous issue.
- Hopefully fixed an oversight in sub editor where changing ItemComponent color with HSV picker would create an error in the console.
- Fixed inability to hit downed characters with short melee weapons like the diving knife.
- Fixed inability to sell nasonov and faraday artifacts (unstable only).
- Fixed concussion description (unstable only).
- Fixes gender-specific affliction sound effects (e.g. vomiting) not playing (unstable only).
- Fixed humans' "aim source position" being too low, causing aim to be slightly off (unstable only).
- Fixed artifact transport case displaying as empty when there's a nasonov/faraday artifact inside (unstable only).
- Fixed "infiltration" event getting stuck on one of the conversation options.
- Fixed ruin's physics bodies being dynamic in mirrored levels (causing them to sink).
- Backwards compatibility: assign skin colors to characters saved in previous versions.
- Assign random skin/hair colors to characters without one configured instead of defaulting to white. Fixes all tutorial characters having white skin/hair.
Modding:
- Added support for tileable light textures for Structures by using <light> XML element that has the same syntax as <LightComponent> does for Items.
- Added "InPressure" property to characters.
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
v0.1500.5.0 v0.1500.5.0
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
@@ -8,7 +59,6 @@ Additions and changes:
- New Alien Ruin mission: kill the guardians inhabiting the ruin and destroy their pods. - New Alien Ruin mission: kill the guardians inhabiting the ruin and destroy their pods.
- Improvements and fixes to the overhauled character sprites and animations. - Improvements and fixes to the overhauled character sprites and animations.
- More talent improvements and additions. - More talent improvements and additions.
- Balanced loot spawn rates.
- Cap the amount argument of the spawnitem command to 100 to prevent freezing/crashing when trying to spawn a ridiculous amount of the item. - Cap the amount argument of the spawnitem command to 100 to prevent freezing/crashing when trying to spawn a ridiculous amount of the item.
- Nerfed concussions: they now require a larger amount of damage to the head to trigger and slowly heal by themselves. - Nerfed concussions: they now require a larger amount of damage to the head to trigger and slowly heal by themselves.
- Added "unlocktalents [job]" command. - Added "unlocktalents [job]" command.
@@ -19,6 +69,17 @@ Additions and changes:
- Added button to randomize character appearance in the character customization menus. - Added button to randomize character appearance in the character customization menus.
- Permanently reduce character skills when respawning mid-round. The talent system makes it easier to gain skills and permanent improvements to the character, and this change is intended to balance that out. - Permanently reduce character skills when respawning mid-round. The talent system makes it easier to gain skills and permanent improvements to the character, and this change is intended to balance that out.
Balance changes:
- Reduced loot in wrecks.
- Difficulty affects the amount of loot.
- Reduced the amount of weapons and grenades in wrecks, pirate ships and abandoned outposts.
- Disabled stacking quality-based items (experimental change, feedback is welcome).
- Reduced diving suits damage resistances.
- Buffed vigor and haste.
- Modifed characters' base vitalities.
- Adjustments to monster stats.
- Reduced mission experience gains, level difficulty affects mission experience.
Fixes: Fixes:
- Fixed crash when firing a syring gun (unstable only). - Fixed crash when firing a syring gun (unstable only).
- Fixed crashing when using meds in multiplayer with friendly fire turned off (unstable only). - Fixed crashing when using meds in multiplayer with friendly fire turned off (unstable only).