v1.6.17.0 (Unto the Breach update)

This commit is contained in:
Regalis11
2024-10-22 17:29:04 +03:00
parent e74b3cdb17
commit 6e6c17e100
417 changed files with 17166 additions and 5870 deletions

View File

@@ -48,7 +48,7 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
}
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity}", GUIStyle.Red, Color.Black);
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"{targetValue.FormatZeroDecimal()} (M: {SelectedTargetMemory?.Priority.FormatZeroDecimal()}, P: {SelectedTargetingParams?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"{targetValue.FormatZeroDecimal()} (M: {CurrentTargetMemory?.Priority.FormatZeroDecimal()}, P: {CurrentTargetingParams?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
}
/*GUIStyle.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, GUIStyle.Red);
@@ -73,7 +73,7 @@ namespace Barotrauma
}
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black);
if (State == AIState.Attack && selectedTargetingParams != null && selectedTargetingParams.AttackPattern == AttackPattern.Circle)
if (State == AIState.Attack && currentTargetingParams != null && currentTargetingParams.AttackPattern == AttackPattern.Circle)
{
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 100.0f, CirclePhase.ToString(), stateColor, Color.Black);
}
@@ -134,8 +134,8 @@ namespace Barotrauma
//GUI.DrawLine(spriteBatch, pos, ConvertUnits.ToDisplayUnits(steeringManager.AvoidLookAheadPos.X, -steeringManager.AvoidLookAheadPos.Y), Color.Orange, width: 4);
}
}
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 4);
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2);
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
}
}
}

View File

@@ -27,7 +27,8 @@ namespace Barotrauma
protected float lastRecvPositionUpdateTime;
private float hudInfoHeight = 100.0f;
private const float DefaultHudInfoHeight = 78.0f;
private float hudInfoHeight = DefaultHudInfoHeight;
private List<CharacterSound> sounds;
@@ -471,7 +472,7 @@ namespace Barotrauma
if (!GUI.InputBlockingMenuOpen)
{
if (SelectedItem != null &&
(SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
(SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null && ic.CloseByClickingOutsideGUIFrame && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
((ViewTarget as Item)?.Prefab.FocusOnSelected ?? false) && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)))
{
if (GameMain.Client != null)
@@ -543,7 +544,10 @@ namespace Barotrauma
{
if (attackResult.Damage <= 1.0f) { return; }
}
PlaySound(CharacterSound.SoundType.Damage, maxInterval: 2);
if (AIState != AIState.PlayDead)
{
PlaySound(CharacterSound.SoundType.Damage, maxInterval: 2);
}
}
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log)
@@ -588,7 +592,6 @@ namespace Barotrauma
}
}
sounds.ForEach(s => s.Sound?.Dispose());
sounds.Clear();
if (GameMain.GameSession?.CrewManager != null &&
@@ -814,9 +817,12 @@ namespace Barotrauma
PlaySound(CharacterSound.SoundType.Idle);
}
break;
case AIState.PlayDead:
case AIState.Freeze:
case AIState.Hiding:
break;
default:
var petBehavior = enemyAI.PetBehavior;
if (petBehavior != null &&
if (enemyAI.PetBehavior is PetBehavior petBehavior &&
(petBehavior.Happiness < petBehavior.UnhappyThreshold || petBehavior.Hunger > petBehavior.HungryThreshold))
{
PlaySound(CharacterSound.SoundType.Unhappy);
@@ -948,7 +954,9 @@ namespace Barotrauma
Controlled != this &&
Submarine != null &&
Controlled.Submarine == Submarine &&
GameSettings.CurrentConfig.Graphics.LosMode != LosMode.None)
GameSettings.CurrentConfig.Graphics.LosMode != LosMode.None &&
//less restrictions on name tag visibility in PvP mode (always show them if the character is visible)
GameMain.GameSession?.GameMode is not PvPMode)
{
float yPos = Controlled.AnimController.FloorY - 1.5f;
@@ -965,15 +973,16 @@ namespace Barotrauma
Vector2 pos = DrawPosition;
pos.Y += hudInfoHeight;
if (CurrentHull != null && DrawPosition.Y > CurrentHull.WorldRect.Y - 130.0f)
float paddingBelowCeiling = 30.0f;
if (CurrentHull != null && DrawPosition.Y + DefaultHudInfoHeight > CurrentHull.WorldRect.Y - paddingBelowCeiling)
{
float lowerAmount = DrawPosition.Y - (CurrentHull.WorldRect.Y - 130.0f);
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f - lowerAmount, 0.1f);
float lowerAmount = (DrawPosition.Y + DefaultHudInfoHeight) - (CurrentHull.WorldRect.Y - paddingBelowCeiling);
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, DefaultHudInfoHeight - lowerAmount, 0.1f);
hudInfoHeight = Math.Max(hudInfoHeight, 20.0f);
}
else
{
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f, 0.1f);
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, DefaultHudInfoHeight, 0.1f);
}
pos.Y = -pos.Y;
@@ -1013,6 +1022,8 @@ namespace Barotrauma
CampaignInteractionType == CampaignMode.InteractionType.None ?
MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) :
1.0f;
//full name tag visibility in PvP mode to make it easier to tell who's an enemy
float nameTextAlpha = GameMain.GameSession?.GameMode is PvPMode ? 1.0f : hudInfoAlpha;
if (!GUI.DisableCharacterNames && hudInfoVisible &&
(controlled == null || this != controlled.FocusedCharacter || IsPet) && cam.Zoom > 0.4f)
@@ -1030,7 +1041,7 @@ namespace Barotrauma
}
Vector2 nameSize = GUIStyle.Font.MeasureString(name);
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Vector2 namePos = new Vector2(pos.X, pos.Y - 5.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Color nameColor = GetNameColor();
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
@@ -1057,7 +1068,7 @@ namespace Barotrauma
}
GUIStyle.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f);
GUIStyle.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
GUIStyle.Font.DrawString(spriteBatch, name, namePos, nameColor * nameTextAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
{
GUIStyle.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
@@ -1068,15 +1079,18 @@ namespace Barotrauma
if (petBehavior != null && !IsDead && !IsUnconscious)
{
var petStatus = petBehavior.GetCurrentStatusIndicatorType();
var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
if (iconStyle != null)
if (petStatus != PetBehavior.StatusIndicatorType.None)
{
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
if (iconStyle != null)
{
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
}
}
}
}
@@ -1100,7 +1114,7 @@ namespace Barotrauma
}
}
if (Params.ShowHealthBar && CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
if (Params.ShowHealthBar && CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible && AIState != AIState.PlayDead && AIState != AIState.Hiding)
{
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
@@ -1233,7 +1247,7 @@ namespace Barotrauma
public Color GetNameColor()
{
CharacterTeamType team = teamID;
if (Info?.IsDisguisedAsAnother != null)
if (Info is { IsDisguisedAsAnother: true })
{
var idCard = Inventory.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent<IdCard>();
if (idCard != null)
@@ -1249,18 +1263,22 @@ namespace Barotrauma
}
}
CharacterTeamType myTeam =
Controlled?.TeamID ??
GameMain.Client?.MyClient?.TeamID ??
CharacterTeamType.Team1;
Color nameColor = GUIStyle.TextColorNormal;
if (Controlled != null && team != Controlled.TeamID)
if (TeamID == CharacterTeamType.FriendlyNPC)
{
if (TeamID == CharacterTeamType.FriendlyNPC)
{
nameColor = UniqueNameColor ?? Color.SkyBlue;
}
else
{
nameColor = GUIStyle.Red;
}
nameColor = UniqueNameColor ?? Color.SkyBlue;
}
else if (team != myTeam)
{
//opposing team is red when controlling a character
nameColor = GUIStyle.Red;
}
return nameColor;
}

View File

@@ -752,7 +752,7 @@ namespace Barotrauma
}
textPos.X += 10.0f * GUI.Scale;
if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet)
if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet && character.IsFriendly(character.FocusedCharacter))
{
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use),
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);

View File

@@ -17,7 +17,8 @@ namespace Barotrauma
private static Sprite infoAreaPortraitBG;
public bool LastControlled;
public int CrewListIndex { get; set; } = -1;
public int CrewListIndex { get; set; } = int.MaxValue; //default to the bottom of the list
private Sprite disguisedPortrait;
private List<WearableSprite> disguisedAttachmentSprites;
@@ -32,6 +33,8 @@ namespace Barotrauma
private float tintHighlightThreshold;
private float tintHighlightMultiplier;
public bool ShowTalentResetPopupOnOpen = true;
public static void Init()
{
infoAreaPortraitBG = GUIStyle.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite();
@@ -208,7 +211,7 @@ namespace Barotrauma
return frame;
}
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel)
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel, bool forceNotification)
{
if (TeamID == CharacterTeamType.FriendlyNPC) { return; }
if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; }
@@ -226,6 +229,18 @@ namespace Barotrauma
specialIncrease ? GUIStyle.Orange : GUIStyle.Green,
playSound: Character == Character.Controlled, skillIdentifier, increase);
}
else if (forceNotification)
{
float change = newLevel - prevLevel;
if (Math.Abs(change) > 0.01f)
{
string sign = change > 0 ? "+" : "-";
Character?.AddMessage(
$"{sign}{Math.Round(change, 2)} {TextManager.Get("SkillName." + skillIdentifier).Value}",
specialIncrease ? GUIStyle.Orange : GUIStyle.Green,
playSound: Character == Character.Controlled);
}
}
}
partial void OnExperienceChanged(int prevAmount, int newAmount)
@@ -586,6 +601,8 @@ namespace Barotrauma
ch.ExperiencePoints = inc.ReadInt32();
ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints);
ch.PermanentlyDead = inc.ReadBoolean();
ch.TalentRefundPoints = inc.ReadInt32();
ch.TalentResetCount = inc.ReadInt32();
return ch;
}

View File

@@ -154,6 +154,9 @@ namespace Barotrauma
case TreatmentEventData _:
msg.WriteBoolean(AnimController.Anim == AnimController.Animation.CPR);
break;
case ConfirmRefundEventData _:
//do nothing
break;
case CharacterStatusEventData _:
//do nothing
break;
@@ -202,12 +205,16 @@ namespace Barotrauma
keys[(int)InputType.Use].Held = useInput;
keys[(int)InputType.Use].SetState(false, useInput);
bool crouching = msg.ReadBoolean();
if (AnimController is HumanoidAnimController)
{
bool crouching = msg.ReadBoolean();
keys[(int)InputType.Crouch].Held = crouching;
keys[(int)InputType.Crouch].SetState(false, crouching);
}
else if (AnimController is FishAnimController fishAnim)
{
fishAnim.Reverse = msg.ReadBoolean();
}
bool attackInput = msg.ReadBoolean();
keys[(int)InputType.Attack].Held = attackInput;
@@ -395,13 +402,13 @@ namespace Barotrauma
ReadStatus(msg);
break;
case EventType.UpdateSkills:
int skillCount = msg.ReadByte();
for (int i = 0; i < skillCount; i++)
Identifier skillIdentifier = msg.ReadIdentifier();
if (!skillIdentifier.IsEmpty)
{
Identifier skillIdentifier = msg.ReadIdentifier();
bool forceNotification = msg.ReadBoolean();
float skillLevel = msg.ReadSingle();
info?.SetSkillLevel(skillIdentifier, skillLevel);
}
info?.SetSkillLevel(skillIdentifier, skillLevel, forceNotification: forceNotification);
}
break;
case EventType.SetAttackTarget:
case EventType.ExecuteAttack:
@@ -512,7 +519,12 @@ namespace Barotrauma
break;
case EventType.UpdateExperience:
int experienceAmount = msg.ReadInt32();
info?.SetExperience(experienceAmount);
int additionalTalentPoints = msg.ReadInt32();
if (info != null)
{
info.SetExperience(experienceAmount);
info.AdditionalTalentPoints = additionalTalentPoints;
}
break;
case EventType.UpdateTalents:
ushort talentCount = msg.ReadUInt16();
@@ -527,6 +539,20 @@ namespace Barotrauma
int moneyAmount = msg.ReadInt32();
SetMoney(moneyAmount);
break;
case EventType.UpdateTalentRefundPoints:
int refundPoints = msg.ReadInt32();
if (info != null)
{
if (refundPoints > info.TalentRefundPoints)
{
info.ShowTalentResetPopupOnOpen = true;
}
info.TalentRefundPoints = refundPoints;
}
break;
case EventType.ConfirmTalentRefund:
Info?.RefundTalents();
break;
case EventType.UpdatePermanentStats:
byte savedStatValueCount = msg.ReadByte();
StatTypes statType = (StatTypes)msg.ReadByte();

View File

@@ -31,8 +31,7 @@ namespace Barotrauma
}
public static Sprite DamageOverlay => DamageOverlayPrefab.Prefabs.ActivePrefab.DamageOverlay;
private Point screenResolution;
private float uiScale, inventoryScale;
@@ -105,6 +104,12 @@ namespace Barotrauma
private GUILayoutGroup treatmentLayout;
private GUIListBox recommendedTreatmentContainer;
/// <summary>
/// Timer for updating visuals (limb tints and overlays) caused by the affliction
/// </summary>
private float updateVisualsTimer = Rand.Range(0.0f, UpdateVisualsInterval);
const float UpdateVisualsInterval = 0.5f;
private float distortTimer;
// 0-1
@@ -461,15 +466,17 @@ namespace Barotrauma
private void OnAttacked(Character attacker, AttackResult attackResult)
{
if (Math.Abs(attackResult.Damage) < 0.01f) { return; }
DamageOverlayTimer = MathHelper.Clamp(attackResult.Damage / MaxVitality, DamageOverlayTimer, 1.0f);
if (healthShadowDelay <= 0.0f) { healthShadowDelay = 1.0f; }
if (ShowDamageOverlay)
{
DamageOverlayTimer = MathHelper.Clamp(attackResult.Damage / MaxVitality, DamageOverlayTimer, 1.0f);
float additionalIntensity = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 0.1f, attackResult.Damage / MaxVitality));
damageIntensity = MathHelper.Clamp(damageIntensity + additionalIntensity, 0, 1);
}
if (healthShadowDelay <= 0.0f) { healthShadowDelay = 1.0f; }
if (healthBarPulsateTimer <= 0.0f) { healthBarPulsatePhase = 0.0f; }
healthBarPulsateTimer = 1.0f;
float additionalIntensity = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 0.1f, attackResult.Damage / MaxVitality));
damageIntensity = MathHelper.Clamp(damageIntensity + additionalIntensity, 0, 1);
DisplayVitalityDelay = 0.5f;
}
@@ -1036,6 +1043,12 @@ namespace Barotrauma
var affliction = kvp.Key;
affliction.Prefab.AfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
var activeEffect = affliction.GetActiveEffect();
if (activeEffect is { ThermalOverlayRange: > 0.0f })
{
StatusHUD.DrawThermalOverlay(spriteBatch, Character, Character, activeEffect.ThermalOverlayColor, activeEffect.ThermalOverlayRange, effectState: (float)Timing.TotalTimeUnpaused, showDeadCharacters: false);
}
}
float damageOverlayAlpha = DamageOverlayTimer;
@@ -1380,7 +1393,7 @@ namespace Barotrauma
recommendedTreatmentContainer.Content.ClearChildren();
float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel("medical");
float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel(Tags.MedicalSkill);
//key = item identifier
//float = suitability
@@ -1949,7 +1962,6 @@ namespace Barotrauma
}
}
private bool ShouldDisplayAfflictionOnLimb(KeyValuePair<Affliction, LimbHealth> kvp, LimbHealth limbHealth)
{
if (!kvp.Key.ShouldShowIcon(Character)) { return false; }
@@ -2058,23 +2070,23 @@ namespace Barotrauma
newAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength));
}
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
{
//deactivate afflictions that weren't included in the network message
if (!newAfflictions.Any(a => kvp.Key.Prefab == a.afflictionPrefab && kvp.Value == a.limb))
if (newAfflictions.None(a => affliction.Prefab == a.afflictionPrefab && limbHealth == a.limb))
{
kvp.Key.Strength = 0.0f;
affliction.Strength = 0.0f;
}
}
foreach (var (limb, afflictionPrefab, strength) in newAfflictions)
{
Affliction existingAffliction = null;
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
{
if (kvp.Key.Prefab == afflictionPrefab && kvp.Value == limb)
if (affliction.Prefab == afflictionPrefab && limbHealth == limb)
{
existingAffliction = kvp.Key;
existingAffliction = affliction;
break;
}
}
@@ -2123,9 +2135,8 @@ namespace Barotrauma
if (!Character.Params.Health.ApplyAfflictionColors) { return; }
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
foreach ((Affliction affliction, LimbHealth _) in afflictions)
{
var affliction = kvp.Key;
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
@@ -2137,17 +2148,23 @@ namespace Barotrauma
{
foreach (Limb limb in Character.AnimController.Limbs)
{
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
limb.BurnOverlayStrength = 0.0f;
limb.DamageOverlayStrength = 0.0f;
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
}
foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
{
if (affliction.Prefab.BurnOverlayAlpha <= 0.0f && affliction.Prefab.DamageOverlayAlpha <= 0.0f) { continue; }
float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
float damageOverlayStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
foreach (Limb limb in Character.AnimController.Limbs)
{
var affliction = kvp.Key;
float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
if (kvp.Value == limbHealths[limb.HealthIndex] || !affliction.Prefab.LimbSpecific)
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
if (limbHealth == limbHealths[limb.HealthIndex] || !affliction.Prefab.LimbSpecific)
{
limb.BurnOverlayStrength += burnStrength;
limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
limb.DamageOverlayStrength += damageOverlayStrength;
}
else
{

View File

@@ -1,14 +1,13 @@
using Microsoft.Xna.Framework;
using System.Linq;
using System;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class JobPrefab : PrefabWithUintIdentifier
{
public GUIButton CreateInfoFrame(out GUIComponent buttonContainer)
public GUIButton CreateInfoFrame(bool isPvP, out GUIComponent buttonContainer)
{
int width = 500, height = 400;
@@ -29,57 +28,28 @@ namespace Barotrauma
TextManager.Get("Skills"), font: GUIStyle.LargeFont);
foreach (SkillPrefab skill in Skills)
{
var levelRange = skill.GetLevelRange(isPvP);
string levelStr =
levelRange.End > levelRange.Start ?
(int)levelRange.Start + " - " + (int)levelRange.End :
((int)levelRange.Start).ToString();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), (int)skill.LevelRange.Start + " - " + (int)skill.LevelRange.End),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), levelStr),
font: GUIStyle.SmallFont);
}
buttonContainer = paddedFrame;
/*if (!ItemIdentifiers.TryGetValue(variant, out var itemIdentifiers)) { return backFrame; }
var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight)
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) })
{
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
TextManager.Get("Items", "mapentitycategory.equipment"), font: GUIStyle.LargeFont);
foreach (string identifier in itemIdentifiers.Distinct())
{
if (!(MapEntityPrefab.Find(name: null, identifier: identifier) is ItemPrefab itemPrefab)) { continue; }
int count = itemIdentifiers.Count(i => i == identifier);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
" - " + (count == 1 ? itemPrefab.Name : itemPrefab.Name + " x" + count),
font: GUIStyle.SmallFont);
}*/
return frameHolder;
}
public class OutfitPreview
public IEnumerable<Sprite> GetJobOutfitSprites(CharacterTeamType team, bool isPvPMode)
{
public readonly List<(Sprite sprite, Vector2 drawOffset)> Sprites;
public Vector2 Dimensions;
public OutfitPreview()
{
Sprites = new List<(Sprite sprite, Vector2 drawOffset)>();
Dimensions = Vector2.One;
}
public void AddSprite(Sprite sprite, Vector2 drawOffset)
{
Sprites.Add((sprite, drawOffset));
}
}
public List<OutfitPreview> GetJobOutfitSprites(CharacterInfoPrefab charInfoPrefab, bool useInventoryIcon, out Vector2 maxDimensions)
{
List<OutfitPreview> outfitPreviews = new List<OutfitPreview>();
maxDimensions = Vector2.One;
var equipIdentifiers = Element.GetChildElements("ItemSet").Elements().Where(e => e.GetAttributeBool("outfit", false)).Select(e => e.GetAttributeIdentifier("identifier", ""));
var equipIdentifiers = JobItems
.SelectMany(kvp => kvp.Value)
.Where(j => j.Outfit)
.Select(j => j.GetItemIdentifier(team, isPvPMode));
List<ItemPrefab> outfitPrefabs = new List<ItemPrefab>();
foreach (var equipIdentifier in equipIdentifiers)
@@ -88,45 +58,9 @@ namespace Barotrauma
if (itemPrefab != null) { outfitPrefabs.Add(itemPrefab); }
}
if (!outfitPrefabs.Any()) { return null; }
if (!outfitPrefabs.Any()) { return Enumerable.Empty<Sprite>(); }
for (int i = 0; i < outfitPrefabs.Count; i++)
{
var outfitPreview = new OutfitPreview();
if (!ItemSets.TryGetValue(i, out var itemSetElement)) { continue; }
var previewElement = itemSetElement.GetChildElement("PreviewSprites");
if (previewElement == null || useInventoryIcon)
{
if (outfitPrefabs[i] is ItemPrefab prefab && prefab.InventoryIcon != null)
{
outfitPreview.AddSprite(prefab.InventoryIcon, Vector2.Zero);
outfitPreview.Dimensions = prefab.InventoryIcon.SourceRect.Size.ToVector2();
maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
}
outfitPreviews.Add(outfitPreview);
continue;
}
var children = previewElement.Elements().ToList();
for (int n = 0; n < children.Count; n++)
{
var spriteElement = children[n];
string spriteTexture = charInfoPrefab.ReplaceVars(spriteElement.GetAttributeString("texture", ""), charInfoPrefab.Heads.First());
var sprite = new Sprite(spriteElement, file: spriteTexture);
sprite.size = new Vector2(sprite.SourceRect.Width, sprite.SourceRect.Height);
outfitPreview.AddSprite(sprite, children[n].GetAttributeVector2("offset", Vector2.Zero));
}
outfitPreview.Dimensions = previewElement.GetAttributeVector2("dims", Vector2.One);
maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
outfitPreviews.Add(outfitPreview);
}
return outfitPreviews;
return outfitPrefabs.Select(p => p.InventoryIcon ?? p.Sprite);
}
}
}

View File

@@ -184,12 +184,6 @@ namespace Barotrauma
public Sprite DamagedSprite { get; private set; }
public bool Hide
{
get => Params.Hide;
set => Params.Hide = value;
}
public List<ConditionalSprite> ConditionalSprites { get; private set; } = new List<ConditionalSprite>();
private Dictionary<DecorativeSprite, SpriteState> spriteAnimState = new Dictionary<DecorativeSprite, SpriteState>();
private Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
@@ -198,6 +192,7 @@ namespace Barotrauma
{
public float RotationState;
public float OffsetState;
public float ScaleState;
public Vector2 RandomOffsetMultiplier = new Vector2(Rand.Range(-1.0f, 1.0f), Rand.Range(-1.0f, 1.0f));
public float RandomRotationFactor = Rand.Range(0.0f, 1.0f);
public float RandomScaleFactor = Rand.Range(0.0f, 1.0f);
@@ -301,7 +296,16 @@ namespace Barotrauma
DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams, ref _damagedTexturePath), sourceRectScale: sourceRectScale);
break;
case "conditionalsprite":
var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null, ref _texturePath), sourceRectScale: sourceRectScale);
string conditionalSpritePath = string.Empty;
GetSpritePath(subElement.GetChildElement("sprite") ?? subElement.GetChildElement("deformablesprite") ?? subElement, null, ref conditionalSpritePath);
if (conditionalSpritePath.IsNullOrEmpty())
{
DebugConsole.ThrowError($"Failed to find a sprite path in the conditional sprite defined in {character.SpeciesName}, limb {type}.",
contentPackage: subElement.ContentPackage);
}
var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(),
file: conditionalSpritePath,
sourceRectScale: sourceRectScale);
ConditionalSprites.Add(conditionalSprite);
if (conditionalSprite.DeformableSprite != null)
{
@@ -475,7 +479,7 @@ namespace Barotrauma
private string _damagedTexturePath;
private string GetSpritePath(ContentXElement element, SpriteParams spriteParams, ref string path)
{
if (path == null)
if (path.IsNullOrEmpty())
{
if (spriteParams != null)
{
@@ -952,8 +956,8 @@ namespace Barotrauma
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c, decorativeSprite.Sprite.Origin,
-body.Rotation + rotation, decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
depth: activeSprite.Depth - depthStep);
depthStep += step;
}

View File

@@ -86,7 +86,7 @@ namespace Barotrauma
var componentEditor = new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame: !isEditor, showName: false, titleFont: GUIStyle.SubHeadingFont)
{
Readonly = CircuitBox.Locked
Readonly = CircuitBox.IsLocked()
};
fieldCount += componentEditor.Fields.Count;

View File

@@ -35,7 +35,7 @@ namespace Barotrauma
public List<CircuitBoxWireRenderer> VirtualWires = new();
public bool Locked => CircuitBox.Locked;
public bool Locked => CircuitBox.IsLocked();
public CircuitBoxUI(CircuitBox box)
{
@@ -786,6 +786,7 @@ namespace Barotrauma
if (wireOption.TryUnwrap(out var wire))
{
CircuitBox.RemoveWires(wire.IsSelected ? wireSelection : ImmutableArray.Create(wire));
return;
}
switch (nodeOption)

View File

@@ -169,9 +169,9 @@ namespace Barotrauma
return doc;
}
public void Save(string path)
public void Save(string path, bool catchUnauthorizedAccessExceptions = true)
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
Directory.CreateDirectory(Path.GetDirectoryName(path)!, catchUnauthorizedAccessExceptions);
ToXDocument().SaveSafe(path);
}
}

View File

@@ -265,7 +265,7 @@ namespace Barotrauma.Transition
subs = getFiles(oldSubsPath, "*.sub");
itemAssemblies = getFiles(oldItemAssembliesPath, "*.xml");
string[] allOldMods = Directory.GetDirectories(oldModsPath, "*", System.IO.SearchOption.TopDirectoryOnly);
string[] allOldMods = Directory.GetDirectories(oldModsPath, "*");
var publishedItems = await SteamManager.Workshop.GetPublishedItems();
foreach (var modDir in allOldMods)

View File

@@ -587,6 +587,39 @@ namespace Barotrauma
}
GameMain.CharacterEditorScreen.Select();
}));
commands.Add(new Command("settainted", "settainted [true/false]: Sets tainted effect on hovered genetic material.",
onExecute: (string[] args) =>
{
if (Character.Controlled == null)
{
NewMessage("No controlled character!", Color.Red);
return;
}
Item focusedItem = Character.Controlled?.FocusedItem ?? Inventory.SelectedSlot?.Item;
if (focusedItem == null)
{
NewMessage("No focused item, hover on something!", Color.Red);
return;
}
var geneticMaterial = focusedItem.GetComponent<GeneticMaterial>();
if (geneticMaterial == null)
{
NewMessage("Not hovering on a genetic material!", Color.Red);
return;
}
else
{
bool newValue = args.None(arg => string.Equals(arg, "false", StringComparison.InvariantCultureIgnoreCase));
geneticMaterial.SetTainted(newValue);
NewMessage($"Set tainted to {newValue} for {focusedItem.Name}", Color.Yellow);
}
}, isCheat: true));
commands.Add(new Command("quickstart", "Starts a singleplayer sandbox", (string[] args) =>
{
@@ -625,6 +658,113 @@ namespace Barotrauma
GameMain.MainMenuScreen.QuickStart(fixedSeed: false, subName, difficulty, levelGenerationParams);
}, getValidArgs: () => new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().OrderBy(s => s).ToArray() }));
commands.Add(new Command("forcewreck", "forcewreck [wreckname] (optional, ThalamusSpawn)[Random/Forced/Disabled]: When generating levels, ensures a specific wreck is generated. Second optional parameter to control thalamus spawning.", (string[] args) =>
{
if (args.Length > 0)
{
var submarineFile = GetSubmarineFile<WreckFile>(args[0]);
if (submarineFile != null)
{
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(i => i.FilePath == submarineFile.Path.Value);
if (matchingSub != null)
{
NewMessage($"Setting ForceWreck to: {matchingSub.Name}, {submarineFile.Path}", color: Color.Yellow);
LevelData.ConsoleForceWreck = matchingSub;
}
}
else
{
NewMessage($"Can't find: {args[0]}", color: Color.Red);
}
}
if (args.Length > 1)
{
string forceThalamusArg = args[1];
if (Enum.TryParse(forceThalamusArg, out LevelData.ThalamusSpawn result))
{
NewMessage($"Setting ThalamusSpawn to: {result}", color: Color.Yellow);
LevelData.ForceThalamus = result;
}
else
{
NewMessage($"Can't parse argument: {forceThalamusArg}", color: Color.Red);
}
}
else
{
NewMessage($"Setting ThalamusSpawn to: {LevelData.ThalamusSpawn.Random}", color: Color.Yellow);
LevelData.ForceThalamus = LevelData.ThalamusSpawn.Random;
}
},
() =>
{
return new string[][]
{
ListSubmarineFileNames<WreckFile>(),
new string[] { LevelData.ThalamusSpawn.Random.ToString(), LevelData.ThalamusSpawn.Forced.ToString(), LevelData.ThalamusSpawn.Disabled.ToString() }
};
}, isCheat: true));
commands.Add(new Command("forcebeaconstation|forcebeacon", "forcebeaconstation [station name]: When generating levels, ensures a specific beacon station is generated.", (string[] args) =>
{
if (args.Length > 0)
{
var submarineFile = GetSubmarineFile<BeaconStationFile>(args[0]);
if (submarineFile != null)
{
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(i => i.FilePath == submarineFile.Path.Value);
if (matchingSub != null)
{
NewMessage($"Setting ForceBeaconStation to: {matchingSub.Name}, {submarineFile.Path}", color: Color.Yellow);
LevelData.ConsoleForceBeaconStation = matchingSub;
}
}
else
{
NewMessage($"Can't find: {args[0]}", color: Color.Red);
}
}
},
() =>
{
return new string[][]
{
ListSubmarineFileNames<BeaconStationFile>()
};
}, isCheat: true));
commands.Add(new Command("reloadcontentfile", "reloadcontentfile [filepath]: Reloads a specific content xml file during runtime.", (string[] args) =>
{
if (args.Length > 0)
{
string pathArgument = args[0];
var contentFile = GetContentFile(pathArgument);
if (contentFile != null)
{
NewMessage($"Reloading content file: {pathArgument}", Color.Yellow);
contentFile.UnloadFile();
contentFile.LoadFile();
}
else
{
NewMessage($"Can't find {args[0]} to reload", color:Color.Red);
}
}
},
() =>
{
return new string[][]
{
ListContentFilePaths()
};
}, isCheat: true));
commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks networking debug logging.", (string[] args) =>
{
@@ -783,6 +923,7 @@ namespace Barotrauma
AssignRelayToServer("spreadsheetexport", false);
#if DEBUG
AssignRelayToServer("listspamfilters", false);
AssignRelayToServer("showitemxml", false);
AssignRelayToServer("crash", false);
AssignRelayToServer("showballastflorasprite", false);
AssignRelayToServer("simulatedlatency", false);
@@ -1924,7 +2065,7 @@ namespace Barotrauma
addIfMissing($"missionname.{missionId}".ToIdentifier(), language);
}
if (missionPrefab.Type == MissionType.Combat)
if (missionPrefab.Type == Tags.MissionTypeCombat)
{
addIfMissing($"MissionDescriptionNeutral.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionDescription1.{missionId}".ToIdentifier(), language);
@@ -2360,6 +2501,34 @@ namespace Barotrauma
GameMain.SubEditorScreen.LoadSub(wreckedSubmarineInfo);
}));
commands.Add(new Command("showitemxml", "showitemxml [item]: Shows the XML configuration of an item in the console and copies it to the clipboard. Useful for debugging variants that partially override the XML of the base item for example.", (string[] args) =>
{
if (args.Length == 0)
{
ThrowError("Please specify the name or identifier of the item.");
return;
}
string itemNameOrId = args[0].ToLowerInvariant();
ItemPrefab itemPrefab =
(MapEntityPrefab.FindByName(itemNameOrId) ??
MapEntityPrefab.FindByIdentifier(itemNameOrId.ToIdentifier())) as ItemPrefab;
if (itemPrefab == null)
{
ThrowError("Item \"{itemNameOrId}\" not found!");
return;
}
string xmlStr = itemPrefab.ConfigElement.Element.ToString();
NewMessage(xmlStr);
Clipboard.SetText(xmlStr);
}, getValidArgs: () =>
{
return new string[][]
{
GetItemNameOrIdParams().ToArray()
};
}));
#if DEBUG
commands.Add(new Command("deathprompt", "Shows the death prompt for testing purposes.", (string[] args) =>
{
@@ -2665,7 +2834,7 @@ namespace Barotrauma
string[] lines;
try
{
lines = File.ReadAllLines(sourcePath);
lines = File.ReadAllLines(sourcePath, catchUnauthorizedAccessExceptions: false);
}
catch (Exception e)
{
@@ -2757,7 +2926,6 @@ namespace Barotrauma
foreach (EventPrefab eventPrefab in EventSet.GetAllEventPrefabs())
{
if (eventPrefab is not TraitorEventPrefab) { continue; }
if (eventPrefab.Identifier.IsEmpty)
{
continue;
@@ -2812,7 +2980,7 @@ namespace Barotrauma
}
string textId = $"EventText.{parentName}";
if (!string.IsNullOrEmpty(text) && !text.Contains("EventText.", StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrEmpty(text) && !text.StartsWith("EventText.", StringComparison.OrdinalIgnoreCase) && !text.StartsWith("Tutorial.", StringComparison.OrdinalIgnoreCase))
{
if (existingTexts.TryGetValue(text, out string existingTextId))
{
@@ -2982,9 +3150,18 @@ namespace Barotrauma
}));
#if DEBUG
commands.Add(new Command("playovervc", "Plays a sound over voice chat.", (args) =>
{
VoipCapture.Instance?.SetOverrideSound(args.Length > 0 ? args[0] : null);
}));
commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) =>
{
if (args.Length != 1) { return; }
if (args.Length != 1)
{
ThrowError("Please specify a language to check.");
return;
}
TextManager.CheckForDuplicates(args[0].ToIdentifier().ToLanguageIdentifier());
}));
@@ -3443,9 +3620,7 @@ namespace Barotrauma
}
RagdollParams ragdollParams = character.AnimController.RagdollParams;
ragdollParams.LimbScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
var pos = character.WorldPosition;
character.AnimController.Recreate();
character.TeleportTo(pos);
character.AnimController.RecreateAndRespawn();
}, isCheat: true));
commands.Add(new Command("jointscale", "Define the jointscale for the controlled character. Provide id or name if you want to target another character. Note: the changes are not saved!", (string[] args) =>
@@ -3468,9 +3643,7 @@ namespace Barotrauma
}
RagdollParams ragdollParams = character.AnimController.RagdollParams;
ragdollParams.JointScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
var pos = character.WorldPosition;
character.AnimController.Recreate();
character.TeleportTo(pos);
character.AnimController.RecreateAndRespawn();
}, isCheat: true));
commands.Add(new Command("ragdollscale", "Rescale the ragdoll of the controlled character. Provide id or name if you want to target another character. Note: the changes are not saved!", (string[] args) =>
@@ -3494,9 +3667,7 @@ namespace Barotrauma
RagdollParams ragdollParams = character.AnimController.RagdollParams;
ragdollParams.LimbScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
ragdollParams.JointScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
var pos = character.WorldPosition;
character.AnimController.Recreate();
character.TeleportTo(pos);
character.AnimController.RecreateAndRespawn();
}, isCheat: true));
commands.Add(new Command("recreateragdoll", "Recreate the ragdoll of the controlled character. Provide id or name if you want to target another character.", (string[] args) =>
@@ -3507,21 +3678,43 @@ namespace Barotrauma
ThrowError("Not controlling any character!");
return;
}
var pos = character.WorldPosition;
character.AnimController.Recreate();
character.TeleportTo(pos);
}, isCheat: true));
character.AnimController.RecreateAndRespawn();
}, isCheat: true,
getValidArgs: () => new[] { GetSpawnedSpeciesNames() }));
commands.Add(new Command("resetragdoll", "Reset the ragdoll of the controlled character. Provide id or name if you want to target another character.", (string[] args) =>
commands.Add(new Command("resetragdoll", "Reset the ragdoll of the controlled character (and all of the same species). Provide species name if you want to target another character.", (string[] args) =>
{
var character = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, true);
if (character == null)
IEnumerable<Character> characters;
if (args.Length == 0)
{
ThrowError("Not controlling any character!");
if (Character.Controlled == null)
{
ThrowError("Invalid species name! Press [TAB] to get valid options in this context.");
return;
}
// Reset all characters of the same species, because the same params affect them too.
characters = FindMatchingSpecies(Character.Controlled.SpeciesName.ToString());
}
else
{
characters = FindMatchingSpecies(args);
}
if (characters.None())
{
ThrowError("Invalid species name!");
return;
}
character.AnimController.ResetRagdoll(forceReload: true);
}, isCheat: true));
characters.ForEach(c => c.AnimController.ResetRagdoll());
foreach (Character character in characters)
{
// Variant scale multiplier doesn't work without recreating the ragdoll.
if (!character.VariantOf.IsEmpty)
{
character.AnimController.RecreateAndRespawn();
}
}
}, isCheat: true,
getValidArgs: () => new[] { GetSpawnedSpeciesNames() }));
commands.Add(new Command("loadanimation", "Loads an animation variation by name for the controlled character. The animation file has to be in the correct animations folder. Note: the changes are not saved!", (string[] args) =>
{
@@ -3634,7 +3827,7 @@ namespace Barotrauma
ThrowError("Cannot use the flipx command while playing online.");
return;
}
if (Submarine.MainSub.SubBody != null) { Submarine.MainSub?.FlipX(); }
if (Submarine.MainSub?.SubBody != null) { Submarine.MainSub.FlipX(); }
}, isCheat: true));
commands.Add(new Command("head", "Load the head sprite and the wearables (hair etc). Required argument: head id. Optional arguments: hair index, beard index, moustache index, face attachment index.", args =>

View File

@@ -549,6 +549,33 @@ namespace Barotrauma
}
public void ClientRead(IReadMessage msg)
{
if (GameMain.GameSession.IsRunning && !GameMain.Instance.LoadingScreenOpen)
{
ClientApplyNetworkMessage(msg);
}
else
{
//if the game session is not currently running (round still loading),
//we need to wait because the entities the status effect / conversation / etc targets may not exist yet
CoroutineManager.StartCoroutine(ApplyNetworkMessageWhenRoundLoaded(msg));
}
}
public IEnumerable<CoroutineStatus> ApplyNetworkMessageWhenRoundLoaded(IReadMessage msg)
{
while (GameMain.GameSession is { IsRunning: false } || GameMain.Instance.LoadingScreenOpen)
{
yield return new WaitForSeconds(1.0f);
}
if (GameMain.GameSession != null && GameMain.Client != null)
{
ClientApplyNetworkMessage(msg);
}
yield return CoroutineStatus.Success;
}
public void ClientApplyNetworkMessage(IReadMessage msg)
{
NetworkEventType eventType = (NetworkEventType)msg.ReadByte();
switch (eventType)

View File

@@ -59,10 +59,13 @@ namespace Barotrauma
if (character.Submarine != null && character.AIController is EnemyAIController enemyAi)
{
enemyAi.UnattackableSubmarines.Add(character.Submarine);
enemyAi.UnattackableSubmarines.Add(Submarine.MainSub);
foreach (Submarine sub in Submarine.MainSub.DockedTo)
if (Submarine.MainSub != null)
{
enemyAi.UnattackableSubmarines.Add(sub);
enemyAi.UnattackableSubmarines.Add(Submarine.MainSub);
foreach (Submarine sub in Submarine.MainSub.DockedTo)
{
enemyAi.UnattackableSubmarines.Add(sub);
}
}
}
}

View File

@@ -1,7 +1,17 @@
namespace Barotrauma
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Barotrauma
{
partial class CombatMission : Mission
{
private readonly Dictionary<byte, int> clientKills = new Dictionary<byte, int>();
private readonly Dictionary<byte, int> clientDeaths = new Dictionary<byte, int>();
private readonly Dictionary<ushort, int> botKills = new Dictionary<ushort, int>();
private readonly Dictionary<ushort, int> botDeaths = new Dictionary<ushort, int>();
public override LocalizedString Description
{
get
@@ -21,5 +31,93 @@
public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => false;
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
{
get
{
if (targetSubmarine == null)
{
yield break;
}
else
{
yield return (targetSubmarineSonarLabel is { Loaded: true } ? targetSubmarineSonarLabel : targetSubmarine.Info.DisplayName, targetSubmarine.WorldPosition);
}
}
}
public static Color GetTeamColor(CharacterTeamType teamID)
{
if (teamID == CharacterTeamType.Team1)
{
return GUIStyle.GetComponentStyle("CoalitionIcon")?.Color ?? GUIStyle.Blue;
}
else if (teamID == CharacterTeamType.Team2)
{
return GUIStyle.GetComponentStyle("SeparatistIcon")?.Color ?? GUIStyle.Orange;
}
return Color.White;
}
public int GetClientKillCount(Client client)
{
if (clientKills.TryGetValue(client.SessionId, out int kills))
{
return kills;
}
return 0;
}
public int GetClientDeathCount(Client client)
{
if (clientDeaths.TryGetValue(client.SessionId, out int deaths))
{
return deaths;
}
return 0;
}
public int GetBotKillCount(CharacterInfo botInfo)
{
if (botKills.TryGetValue(botInfo.ID, out int kills))
{
return kills;
}
return 0;
}
public int GetBotDeathCount(CharacterInfo botInfo)
{
if (botDeaths.TryGetValue(botInfo.ID, out int deaths))
{
return deaths;
}
return 0;
}
public override void ClientRead(IReadMessage msg)
{
base.ClientRead(msg);
Scores[0] = msg.ReadUInt16();
Scores[1] = msg.ReadUInt16();
uint clientCount = msg.ReadVariableUInt32();
for (int i = 0; i < clientCount; i++)
{
byte clientId = msg.ReadByte();
clientDeaths[clientId] = (int)msg.ReadVariableUInt32();
clientKills[clientId] = (int)msg.ReadVariableUInt32();
}
uint botCount = msg.ReadVariableUInt32();
for (int i = 0; i < botCount; i++)
{
ushort botId = msg.ReadUInt16();
botDeaths[botId] = (int)msg.ReadVariableUInt32();
botKills[botId] = (int)msg.ReadVariableUInt32();
}
}
}
}

View File

@@ -58,7 +58,7 @@ internal class DeathPrompt
const float FadeInDuration = 1.0f;
bool permadeath = GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.Permadeath };
bool ironman = GameMain.NetworkMember is { ServerSettings: { RespawnMode: RespawnMode.Permadeath, IronmanMode: true } };
bool ironman = GameMain.NetworkMember is { ServerSettings.IronmanModeActive: true };
var background = new GUICustomComponent(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), onDraw: DrawBackground)
{

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Text;
using Barotrauma.Extensions;
namespace Barotrauma
@@ -84,16 +83,31 @@ namespace Barotrauma
{
currentDirectory += "/";
}
fileSystemWatcher?.Dispose();
fileSystemWatcher = new System.IO.FileSystemWatcher(currentDirectory)
try
{
Filter = "*",
NotifyFilter = System.IO.NotifyFilters.LastWrite | System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName
};
fileSystemWatcher.Created += OnFileSystemChanges;
fileSystemWatcher.Deleted += OnFileSystemChanges;
fileSystemWatcher.Renamed += OnFileSystemChanges;
fileSystemWatcher.EnableRaisingEvents = true;
fileSystemWatcher?.Dispose();
fileSystemWatcher = new System.IO.FileSystemWatcher(currentDirectory)
{
Filter = "*",
NotifyFilter = System.IO.NotifyFilters.LastWrite | System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName
};
fileSystemWatcher.Created += OnFileSystemChanges;
fileSystemWatcher.Deleted += OnFileSystemChanges;
fileSystemWatcher.Renamed += OnFileSystemChanges;
fileSystemWatcher.EnableRaisingEvents = true;
}
catch (System.IO.FileNotFoundException exception)
{
DebugConsole.ThrowError("Failed to set the current directory, possibly due to insufficient access permissions.", exception);
}
catch (ArgumentException exception)
{
DebugConsole.ThrowError("Failed to set the current directory, possibly because it was deleted.", exception);
}
catch (Exception exception)
{
DebugConsole.ThrowError("Failed to set the current directory for an unknown reason.", exception);
}
RefreshFileList();
}
}
@@ -218,6 +232,14 @@ namespace Barotrauma
{
if (Directory.Exists(txt))
{
var attributes = System.IO.File.GetAttributes(txt);
if (attributes.HasAnyFlag(System.IO.FileAttributes.System) || attributes.HasAnyFlag(System.IO.FileAttributes.Hidden))
{
// System and hidden folders should be filtered out when populating the options, but the user can still write or copy-paste the path in the text field,
// which will throw a file not found exception when the file system watcher starts. Therefore, this extra check.
tb.Text = CurrentDirectory;
return false;
}
CurrentDirectory = txt;
return true;
}
@@ -354,20 +376,19 @@ namespace Barotrauma
var directories = Directory.EnumerateDirectories(currentDirectory, "*" + filterBox!.Text + "*");
foreach (var directory in directories)
{
string txt = directory;
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
if (!txt.EndsWith("/")) { txt += "/"; }
//get directory info
DirectoryInfo dirInfo = new DirectoryInfo(directory);
try
{
//this will throw an exception if the directory can't be opened
Directory.GetDirectories(directory);
//this will intentionally throw an exception if the directory can't be opened
System.IO.Directory.GetDirectories(directory);
}
catch (UnauthorizedAccessException)
{
// Skip the folders that can't be accessed.
continue;
}
string txt = directory;
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
if (!txt.EndsWith("/")) { txt += "/"; }
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
{
UserData = ItemIsDirectory.Yes

View File

@@ -469,7 +469,7 @@ namespace Barotrauma
"Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount + " (" + GameMain.SoundManager.UniqueLoadedSoundCount + " unique)", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
soundTextY += yStep;
for (int i = 0; i < SoundManager.SOURCE_COUNT; i++)
for (int i = 0; i < SoundManager.SourceCount; i++)
{
Color clr = Color.White;
string soundStr = i + ": ";
@@ -1546,7 +1546,7 @@ namespace Barotrauma
private static readonly VertexPositionColorTexture[] donutVerts = new VertexPositionColorTexture[DonutSegments * 4];
public static void DrawDonutSection(
SpriteBatch sb, Vector2 center, Range<float> radii, float sectionRad, Color clr, float depth = 0.0f)
SpriteBatch sb, Vector2 center, Range<float> radii, float sectionRad, Color clr, float depth = 0.0f, float rotationRad = 0.0f)
{
float getRadius(int vertexIndex)
=> (vertexIndex % 4) switch
@@ -1589,7 +1589,7 @@ namespace Barotrauma
for (int vertexIndex = 0; vertexIndex < maxDirectionIndex * 4; vertexIndex++)
{
donutVerts[vertexIndex].Color = clr;
donutVerts[vertexIndex].Position = new Vector3(center + getDirection(vertexIndex) * getRadius(vertexIndex), 0.0f);
donutVerts[vertexIndex].Position = new Vector3(center + Vector2.Transform(getDirection(vertexIndex) * getRadius(vertexIndex), Matrix.CreateRotationZ(rotationRad)), 0.0f);
}
sb.Draw(solidWhiteTexture, donutVerts, depth, count: maxDirectionIndex);
}
@@ -1856,9 +1856,16 @@ namespace Barotrauma
Vector2 pos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) - new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2();
sheet.Draw(spriteBatch, (int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale: new Vector2(Scale));
}
#endregion
#region Element creation
public static void DrawCapsule(SpriteBatch sb, Vector2 origin, float length, float radius, float rotation, Color clr, float depth = 0, float thickness = 1)
{
DrawDonutSection(sb, origin + Vector2.Transform(-new Vector2(length / 2, 0), Matrix.CreateRotationZ(rotation)), new Range<float>(radius - thickness / 2, radius + thickness / 2), MathHelper.Pi, clr, depth, rotation - MathHelper.Pi);
DrawRectangle(sb, origin, new Vector2(length, radius * 2), new Vector2(length / 2, radius), rotation, clr, depth, thickness);
DrawDonutSection(sb, origin + Vector2.Transform(new Vector2(length / 2, 0), Matrix.CreateRotationZ(rotation)), new Range<float>(radius - thickness / 2, radius + thickness / 2), MathHelper.Pi, clr, depth, rotation);
}
#endregion
#region Element creation
public static Texture2D CreateCircle(int radius, bool filled = false)
{

View File

@@ -159,6 +159,34 @@ namespace Barotrauma
}
}
private GUIComponent holdOverlay;
private bool requireHold;
public bool RequireHold
{
get => requireHold;
set
{
requireHold = value;
if (value)
{
holdOverlay ??= new GUIFrame(new RectTransform(new Vector2(0.5f, 1f), Frame.RectTransform, Anchor.CenterLeft), style: null)
{
Color = GUIStyle.Yellow * 0.33f,
CanBeFocused = false,
IgnoreLayoutGroups = true,
Visible = true
};
}
else if (holdOverlay != null)
{
holdOverlay.Visible = false;
}
}
}
public float HoldDurationSeconds { get; set; } = 5f;
private float holdTimer;
public bool Pulse { get; set; }
private float pulseTimer;
private float pulseExpand;
@@ -220,7 +248,7 @@ namespace Barotrauma
Rectangle expandRect = Rect;
float expand = (pulseExpand * 20.0f) * GUI.Scale;
expandRect.Inflate(expand, expand);
GUIStyle.EndRoundButtonPulse.Draw(spriteBatch, expandRect, ToolBox.GradientLerp(pulseExpand, Color.White, Color.White, Color.Transparent));
}
}
@@ -240,6 +268,11 @@ namespace Barotrauma
}
if (PlayerInput.PrimaryMouseButtonHeld())
{
if (RequireHold)
{
holdTimer += deltaTime;
}
if (OnPressed != null)
{
if (OnPressed())
@@ -254,25 +287,34 @@ namespace Barotrauma
}
else if (PlayerInput.PrimaryMouseButtonClicked())
{
if (PlaySoundOnSelect)
if (!RequireHold || holdTimer > HoldDurationSeconds)
{
SoundPlayer.PlayUISound(ClickSound);
}
if (OnClicked != null)
{
if (OnClicked(this, UserData))
if (PlaySoundOnSelect)
{
State = ComponentState.Selected;
SoundPlayer.PlayUISound(ClickSound);
}
if (OnClicked != null)
{
if (OnClicked(this, UserData))
{
State = ComponentState.Selected;
}
}
else
{
Selected = !Selected;
}
}
else
{
Selected = !Selected;
}
}
else
{
holdTimer = 0.0f;
}
}
else
{
holdTimer = 0.0f;
if (!ExternalHighlight)
{
State = Selected ? ComponentState.Selected : ComponentState.None;
@@ -283,6 +325,20 @@ namespace Barotrauma
}
}
if (RequireHold)
{
float width = MathHelper.Clamp(holdTimer / HoldDurationSeconds, 0f, 1f);
if (!MathUtils.NearlyEqual(width, holdOverlay.RectTransform.RelativeSize.X))
{
holdOverlay.RectTransform.RelativeSize = new Vector2(width, 1f);
}
holdOverlay.Color =
holdTimer >= HoldDurationSeconds
? Color.Green * 0.33f
: Color.Red * 0.33f;
}
foreach (GUIComponent child in Children)
{
child.State = State;

View File

@@ -9,8 +9,21 @@ namespace Barotrauma
{
public class GUIDropDown : GUIComponent, IKeyboardSubscriber
{
/// <param name="selected">The component that was selected from the dropdown.</param>
/// <param name="obj"><see cref="GUIComponent.UserData"/> of the component selected from the dropdown.</param>
public delegate bool OnSelectedHandler(GUIComponent selected, object obj = null);
/// <summary>
/// Triggers when some item is cliecked from the dropdown.
/// Note that <see cref="SelectedData"/> is not set yet when this callback triggers, and returning false from the callback disallows selecting it.
/// If you want to access the new value, use the obj argument.
/// </summary>
public OnSelectedHandler OnSelected;
/// <summary>
/// Triggers after an item has been selected from the dropdown, all validation has been done and the new value has been set.
/// </summary>
public OnSelectedHandler AfterSelected;
public OnSelectedHandler OnDropped;
private readonly GUIButton button;
@@ -166,7 +179,7 @@ namespace Barotrauma
public Vector4 Padding => button.TextBlock.Padding;
public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false, Alignment textAlignment = Alignment.CenterLeft) : base(style, rectT)
public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false, Alignment textAlignment = Alignment.CenterLeft, float listBoxScale = 1) : base(style, rectT)
{
text ??= LocalizedString.EmptyString;
@@ -185,13 +198,21 @@ namespace Barotrauma
Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter;
Pivot listPivot = dropAbove ? Pivot.BottomCenter : Pivot.TopCenter;
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
listBox = new GUIListBox(new RectTransform(new Point((int)(Rect.Width * listBoxScale), Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
{ IsFixedSize = false }, style: null)
{
Enabled = !selectMultiple,
PlaySoundOnSelect = true,
};
if (!selectMultiple) { listBox.OnSelected = SelectItem; }
if (!selectMultiple)
{
listBox.AfterSelected = (component, obj) =>
{
SelectItem(component, obj);
AfterSelected?.Invoke(component, obj);
return true;
};
}
GUIStyle.Apply(listBox, "GUIListBox", this);
GUIStyle.Apply(listBox.ContentBackground, "GUIListBox", this);
@@ -199,6 +220,8 @@ namespace Barotrauma
{
icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), button.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, null, scaleToFit: true);
icon.ApplyStyle(button.Style.ChildStyles["dropdownicon".ToIdentifier()]);
//move the text away from the icon
button.TextBlock.Padding += new Vector4(0, 0, icon.Rect.Width, 0);
}
currentHighestParent = FindHighestParent();
@@ -249,12 +272,12 @@ namespace Barotrauma
toolTip ??= "";
if (selectMultiple)
{
var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, style: "ListBoxElement", color: color)
var frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, style: "ListBoxElement", color: color)
{
UserData = userData,
ToolTip = toolTip
};
new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text)
var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text)
{
UserData = userData,
ToolTip = toolTip,
@@ -266,6 +289,11 @@ namespace Barotrauma
return false;
}
if (OnSelected != null && !OnSelected.Invoke(tb.Parent, tb.Parent.UserData))
{
return false;
}
List<LocalizedString> texts = new List<LocalizedString>();
selectedDataMultiple.Clear();
selectedIndexMultiple.Clear();
@@ -282,8 +310,7 @@ namespace Barotrauma
i++;
}
button.Text = LocalizedString.Join(", ", texts);
// TODO: The callback is called at least twice, remove this?
OnSelected?.Invoke(tb.Parent, tb.Parent.UserData);
AfterSelected?.Invoke(tb.Parent, SelectedData);
return true;
}
};
@@ -291,7 +318,7 @@ namespace Barotrauma
}
else
{
return new GUITextBlock(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, text, style: "ListBoxElement", color: color, textColor: textColor)
return new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, text, style: "ListBoxElement", color: color, textColor: textColor)
{
UserData = userData,
ToolTip = toolTip
@@ -328,9 +355,8 @@ namespace Barotrauma
}
button.Text = textBlock?.Text ?? "";
}
OnSelected?.Invoke(component, obj);
Dropped = false;
// TODO: OnSelected can be called multiple times and when it shouldn't be called -> turn into an event so that nobody else can call it.
OnSelected?.Invoke(component, component.UserData);
return true;
}
@@ -344,6 +370,7 @@ namespace Barotrauma
{
listBox.Select(userData);
}
AfterSelected?.Invoke(SelectedComponent, SelectedData);
}
public void Select(int index)
@@ -360,6 +387,7 @@ namespace Barotrauma
{
listBox.Select(index);
}
AfterSelected?.Invoke(this, SelectedData);
}
private bool wasOpened;

View File

@@ -14,8 +14,17 @@ namespace Barotrauma
protected List<GUIComponent> selected;
public delegate bool OnSelectedHandler(GUIComponent component, object obj);
/// <summary>
/// Triggers when some element is clicked on the listbox.
/// Note that <see cref="SelectedData"/> is not set yet when this callback triggers, and returning false from the callback disallows selecting it.
/// </summary>
public OnSelectedHandler OnSelected;
/// <summary>
/// Triggers after some element has been selected from the listbox.
/// </summary>
public OnSelectedHandler AfterSelected;
public delegate object CheckSelectedHandler();
public CheckSelectedHandler CheckSelected;
@@ -1151,6 +1160,8 @@ namespace Barotrauma
{
SoundPlayer.PlayUISound(GUISoundType.Select);
}
AfterSelected?.Invoke(child, SelectedData);
}
public void Select(IEnumerable<GUIComponent> children)
@@ -1160,8 +1171,9 @@ namespace Barotrauma
selected.Clear();
selected.AddRange(children.Where(c => Content.Children.Contains(c)));
foreach (var child in selected) { OnSelected?.Invoke(child, child.UserData); }
AfterSelected?.Invoke(children.FirstOrDefault(), SelectedData);
}
public void Deselect()
{
Selected = false;
@@ -1172,6 +1184,15 @@ namespace Barotrauma
selected.Clear();
}
public void DeselectElement(GUIComponent child)
{
if (child == null) { return; }
if (selected.Contains(child))
{
selected.Remove(child);
}
}
public void UpdateScrollBarSize()
{
scrollBarNeedsRecalculation = false;
@@ -1268,7 +1289,7 @@ namespace Barotrauma
ContentBackground.DrawManually(spriteBatch, alsoChildren: false);
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (HideChildrenOutsideFrame)
if (HideChildrenOutsideFrame && Content.CountChildren > 0)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Content.Rect);
@@ -1306,7 +1327,7 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, drawRect, Color.White * 0.5f, thickness: 2f);
}
if (HideChildrenOutsideFrame)
if (HideChildrenOutsideFrame && Content.CountChildren > 0)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;

View File

@@ -51,6 +51,16 @@ namespace Barotrauma
public readonly static GUISprite InteractionLabelBackground = new GUISprite("InteractionLabelBackground");
public readonly static GUISprite BrokenIcon = new GUISprite("BrokenIcon");
public readonly static GUISprite YouAreHereCircle = new GUISprite("YouAreHereCircle");
public readonly static GUISprite SubLocationIcon = new GUISprite("SubLocationIcon");
public readonly static GUISprite ShuttleIcon = new GUISprite("ShuttleIcon");
public readonly static GUISprite WreckIcon = new GUISprite("WreckIcon");
public readonly static GUISprite CaveIcon = new GUISprite("CaveIcon");
public readonly static GUISprite OutpostIcon = new GUISprite("OutpostIcon");
public readonly static GUISprite RuinIcon = new GUISprite("RuinIcon");
public readonly static GUISprite EnemyIcon = new GUISprite("EnemyIcon");
public readonly static GUISprite CorpseIcon = new GUISprite("CorpseIcon");
public readonly static GUISprite BeaconIcon = new GUISprite("BeaconIcon");
public readonly static GUISprite Radiation = new GUISprite("Radiation");
public readonly static GUISpriteSheet RadiationAnimSpriteSheet = new GUISpriteSheet("RadiationAnimSpriteSheet");
@@ -71,7 +81,7 @@ namespace Barotrauma
public readonly static GUISprite EndRoundButtonPulse = new GUISprite("EndRoundButtonPulse");
public readonly static GUISpriteSheet FocusIndicator = new GUISpriteSheet("FocusIndicator");
public readonly static GUISprite IconOverflowIndicator = new GUISprite("IconOverflowIndicator");
/// <summary>

View File

@@ -742,8 +742,8 @@ namespace Barotrauma
private (LocalizedString header, LocalizedString body) GetItemTransferWarningText()
{
var header = TextManager.Get("itemtransferheader").Fallback("lowfuelheader", useDefaultLanguageIfFound: false);
var body = TextManager.Get("itemtransferwarning").Fallback("lowfuelwarning", useDefaultLanguageIfFound: false);
var header = TextManager.Get("itemtransferheader").Fallback(TextManager.Get("lowfuelheader"), useDefaultLanguageIfFound: false);
var body = TextManager.Get("itemtransferwarning").Fallback(TextManager.Get("lowfuelwarning"), useDefaultLanguageIfFound: false);
return (header, body);
}

View File

@@ -416,7 +416,10 @@ namespace Barotrauma
=> TextManager.GetWithVariable("percentageformat", "[value]", $"{(int)MathF.Round(value)}");
}
var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine");
if (Submarine.MainSub != null)
{
createTabButton(InfoFrameTab.Submarine, "submarine");
}
var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character");
talentsButton.OnAddedToGUIUpdateList += (component) =>
@@ -463,12 +466,14 @@ namespace Barotrauma
}
}
private const float jobColumnWidthPercentage = 0.138f,
characterColumnWidthPercentage = 0.45f,
pingColumnWidthPercentage = 0.206f,
walletColumnWidthPercentage = 0.206f;
private const float JobColumnWidthPercentage = 0.138f,
CharacterColumnWidthPercentage = 0.45f,
KillColumnWidthPercentage = 0.1f,
DeathColumnWidthPercentage = 0.1f,
PingColumnWidthPercentage = 0.15f,
WalletColumnWidthPercentage = 0.206f;
private int jobColumnWidth, characterColumnWidth, pingColumnWidth, walletColumnWidth;
private int jobColumnWidth, characterColumnWidth, pingColumnWidth, walletColumnWidth, deathColumnWidth, killColumnWidth;
private void CreateCrewListFrame(GUIFrame crewFrame)
{
@@ -496,11 +501,21 @@ namespace Barotrauma
{
if (teamIDs.Count > 1)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, nameHeight), content.RectTransform), CombatMission.GetTeamName(teamIDs[i]), textColor: i == 0 ? GUIStyle.Green : GUIStyle.Orange) { ForceUpperCase = ForceUpperCase.Yes };
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, nameHeight), content.RectTransform), CombatMission.GetTeamName(teamIDs[i]), textColor: CombatMission.GetTeamColor(teamIDs[i]))
{
ForceUpperCase = ForceUpperCase.Yes
};
var teamIcon = new GUIImage(new RectTransform(Vector2.One, nameText.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight),
style: teamIDs[i] == CharacterTeamType.Team2 ? "SeparatistIcon" : "CoalitionIcon")
{
Color = nameText.TextColor
};
nameText.Padding = new Vector4(teamIcon.Rect.Width + nameText.Padding.X, nameText.Padding.Y, nameText.Padding.Z, nameText.Padding.W);
}
headerFrames[i] = new GUILayoutGroup(new RectTransform(Vector2.Zero, content.RectTransform, Anchor.TopLeft, Pivot.BottomLeft) { AbsoluteOffset = new Point(2, -1) }, isHorizontal: true)
{
Stretch = true,
AbsoluteSpacing = 2,
UserData = i
};
@@ -587,8 +602,8 @@ namespace Barotrauma
sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
characterButton.RectTransform.RelativeSize = new Vector2((1f - jobColumnWidthPercentage * sizeMultiplier) * sizeMultiplier, 1f);
jobButton.RectTransform.RelativeSize = new Vector2(JobColumnWidthPercentage * sizeMultiplier, 1f);
characterButton.RectTransform.RelativeSize = new Vector2((1f - JobColumnWidthPercentage * sizeMultiplier) * sizeMultiplier, 1f);
jobButton.TextBlock.Font = characterButton.TextBlock.Font = GUIStyle.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = false;
@@ -626,7 +641,8 @@ namespace Barotrauma
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
AbsoluteSpacing = 2
AbsoluteSpacing = 2,
Stretch = true
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => character.Info.DrawJobIcon(sb, component.Rect))
@@ -639,21 +655,29 @@ namespace Barotrauma
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
paddedFrame.Recalculate();
linkedGUIList.Add(new LinkedGUI(character, frame, textBlock: null));
}
private void CreateMultiPlayerListContentHolder(GUILayoutGroup headerFrame)
{
bool isCampaign = GameMain.GameSession?.Campaign is MultiPlayerCampaign;
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
GUIButton pingButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("serverlistping"), style: "GUIButtonSmallFreeScale");
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(JobColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(CharacterColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
if (GameMain.GameSession?.GameMode is PvPMode)
{
var killButton = new GUIButton(new RectTransform(new Vector2(KillColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("killcount"), style: "GUIButtonSmallFreeScale");
killColumnWidth = killButton.Rect.Width;
var deathButton = new GUIButton(new RectTransform(new Vector2(DeathColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("deathcount"), style: "GUIButtonSmallFreeScale");
deathColumnWidth = deathButton.Rect.Width;
}
GUIButton pingButton = new GUIButton(new RectTransform(new Vector2(PingColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("serverlistping"), style: "GUIButtonSmallFreeScale");
if (isCampaign)
{
GUIButton walletButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform)
{
RelativeSize = new Vector2(walletColumnWidthPercentage * sizeMultiplier, 1f)
}, TextManager.Get("crewwallet.wallet"), style: "GUIButtonSmallFreeScale")
GUIButton walletButton = new GUIButton(new RectTransform(new Vector2(WalletColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("crewwallet.wallet"), style: "GUIButtonSmallFreeScale")
{
TextBlock = { Font = GUIStyle.HotkeyFont },
CanBeFocused = false,
@@ -662,15 +686,12 @@ namespace Barotrauma
walletColumnWidth = walletButton.Rect.Width;
}
sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
characterButton.RectTransform.RelativeSize = new Vector2((characterColumnWidthPercentage + (isCampaign ? 0 : walletColumnWidthPercentage)) * sizeMultiplier, 1f);
pingButton.RectTransform.RelativeSize = new Vector2(pingColumnWidthPercentage * sizeMultiplier, 1f);
jobButton.TextBlock.Font = characterButton.TextBlock.Font = pingButton.TextBlock.Font = GUIStyle.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = pingButton.CanBeFocused = false;
jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = pingButton.ForceUpperCase = ForceUpperCase.Yes;
foreach (var btn in headerFrame.GetAllChildren<GUIButton>())
{
btn.TextBlock.Font = GUIStyle.HotkeyFont;
btn.CanBeFocused = false;
btn.ForceUpperCase = ForceUpperCase.Yes;
}
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
@@ -692,7 +713,7 @@ namespace Barotrauma
{
foreach (Character character in crew.Where(c => c.TeamID == teamIDs[i]))
{
if (!(character is AICharacter) && connectedClients.Any(c => c.Character == null && c.Name == character.Name)) { continue; }
if (character is not AICharacter && connectedClients.Any(c => c.Character == null && c.Name == character.Name)) { continue; }
CreateMultiPlayerCharacterElement(character, GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.Character == character), i);
}
}
@@ -723,7 +744,8 @@ namespace Barotrauma
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
AbsoluteSpacing = 2
AbsoluteSpacing = 2,
Stretch = true
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => character.Info.DrawJobIcon(sb, component.Rect))
@@ -736,6 +758,19 @@ namespace Barotrauma
if (client != null)
{
CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon);
if (GameMain.GameSession?.GameMode is PvPMode)
{
new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetClientKillCount(client) ?? 0).ToString()
};
new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetClientDeathCount(client) ?? 0).ToString()
};
}
linkedGUIList.Add(new LinkedGUI(client, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon));
@@ -745,6 +780,18 @@ namespace Barotrauma
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
if (GameMain.GameSession?.GameMode is PvPMode)
{
new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotKillCount(character.Info) ?? 0).ToString()
};
new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotDeathCount(character.Info) ?? 0).ToString()
};
}
if (character is AICharacter)
{
linkedGUIList.Add(new LinkedGUI(character, frame,
@@ -764,6 +811,8 @@ namespace Barotrauma
}
CreateWalletCrewFrame(character, paddedFrame);
paddedFrame.Recalculate();
}
private void CreateMultiPlayerClientElement(Client client)
@@ -785,7 +834,8 @@ namespace Barotrauma
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
AbsoluteSpacing = 2
AbsoluteSpacing = 2,
Stretch = true
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center),
@@ -797,6 +847,19 @@ namespace Barotrauma
};
CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon);
if (GameMain.GameSession?.GameMode is PvPMode)
{
new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetClientKillCount(client) ?? 0).ToString()
};
new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetClientDeathCount(client) ?? 0).ToString()
};
}
linkedGUIList.Add(new LinkedGUI(client, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon));
@@ -805,6 +868,8 @@ namespace Barotrauma
{
CreateWalletCrewFrame(character, paddedFrame);
}
paddedFrame.Recalculate();
}
private int GetTeamIndex(Client client)
@@ -842,7 +907,7 @@ namespace Barotrauma
private void CreateWalletCrewFrame(Character character, GUILayoutGroup paddedFrame)
{
if (!(GameMain.GameSession?.Campaign is MultiPlayerCampaign)) { return; }
if (GameMain.GameSession?.Campaign is not MultiPlayerCampaign) { return; }
GUILayoutGroup walletLayout = new GUILayoutGroup(new RectTransform(new Point(walletColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.Center)
{
@@ -947,7 +1012,6 @@ namespace Barotrauma
float iconWidth = iconSize.X / (float)characterColumnWidth;
int xOffset = (int)(jobColumnWidth + characterNameBlock.TextPos.X - GUIStyle.Font.MeasureString(characterNameBlock.Text).X / 2f - paddedFrame.AbsoluteSpacing - iconWidth * paddedFrame.Rect.Width);
permissionIcon = new GUIImage(new RectTransform(new Vector2(iconWidth, 1f), paddedFrame.RectTransform) { AbsoluteOffset = new Point(xOffset + 2, 0) }, permissionIconSprite) { IgnoreLayoutGroups = true };
if (client.Character != null && client.Character.IsDead)
{
@@ -977,10 +1041,7 @@ namespace Barotrauma
}
else if (client.Character != null && client.Character.IsDead)
{
if (client.Character.Info != null)
{
client.Character.Info.DrawJobIcon(spriteBatch, area);
}
client.Character.Info?.DrawJobIcon(spriteBatch, area);
}
else
{
@@ -1663,6 +1724,8 @@ namespace Barotrauma
private static void CreateSubmarineInfo(GUIFrame infoFrame, Submarine sub)
{
if (sub == null) { return; }
GUIFrame subInfoFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
GUIFrame paddedFrame = new GUIFrame(new RectTransform(Vector2.One * 0.97f, subInfoFrame.RectTransform, Anchor.Center), style: null);
@@ -1765,7 +1828,7 @@ namespace Barotrauma
{
parent.Content.ClearChildren();
List<GUITextBlock> skillNames = new List<GUITextBlock>();
foreach (Skill skill in info.Job.GetSkills())
foreach (Skill skill in info.Job.GetSkills().OrderByDescending(static s => s.Level))
{
GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.0f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = true };
var skillName = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value));

View File

@@ -7,6 +7,7 @@ using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using static Barotrauma.TalentTree;
using static Barotrauma.TalentTree.TalentStages;
@@ -83,6 +84,10 @@ namespace Barotrauma
private GUIButton? talentApplyButton,
talentResetButton;
private delegate void StartAnimation(RectangleF start, RectangleF end, float duration);
private StartAnimation? startAnimation;
private GUIComponent? talentMainArea;
public void CreateGUI(GUIFrame parent, Character? targetCharacter)
{
parent.ClearChildren();
@@ -136,7 +141,7 @@ namespace Barotrauma
GUILayoutGroup playerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), containerFrame.RectTransform, Anchor.TopCenter));
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
if (!GameMain.NetLobbyScreen.PermadeathMode)
if (!GameMain.NetLobbyScreen.PermadeathMode && GameMain.GameSession?.GameMode is not PvPMode)
{
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
@@ -341,7 +346,15 @@ namespace Barotrauma
private void CreateTalentMenu(GUIComponent parent, CharacterInfo info, TalentTree tree)
{
GUIListBox mainList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), parent.RectTransform, anchor: Anchor.TopCenter));
talentMainArea = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), parent.RectTransform, Anchor.TopCenter), style: null );
GUIListBox mainList = new GUIListBox(new RectTransform(Vector2.One, talentMainArea.RectTransform));
startAnimation = CreatePopupAnimationHandler(talentMainArea);
if (info is { TalentRefundPoints: > 0, ShowTalentResetPopupOnOpen: true })
{
CreateTalentResetPopup(talentMainArea);
}
selectedTalents = info.GetUnlockedTalentsInTree().ToHashSet();
@@ -425,6 +438,124 @@ namespace Barotrauma
}
}
private void CreateTalentResetPopup(GUIComponent parent)
{
bool hasResetTalentsBefore = character?.Info.TalentResetCount > 0;
var bgBlocker = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, anchor: Anchor.Center), style: "GUIBackgroundBlocker")
{
IgnoreLayoutGroups = true
};
var popup = new GUIFrame(new RectTransform(new Vector2(0.6f, 0.8f), bgBlocker.RectTransform, Anchor.Center));
var popupLayout = new GUILayoutGroup(new RectTransform(ToolBox.PaddingSizeParentRelative(popup.RectTransform, 0.95f), popup.RectTransform, Anchor.Center), isHorizontal: false);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), popupLayout.RectTransform), TextManager.Get("talentresetheader"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center);
new GUITextBlock(new RectTransform(new Vector2(1.0f, hasResetTalentsBefore ? 0.25f : 0.5f), popupLayout.RectTransform), TextManager.Get("talentresetprompt"), wrap: true);
if (hasResetTalentsBefore)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), popupLayout.RectTransform), TextManager.Get("talentresetpromptwarning"), wrap: true)
{
TextColor = GUIStyle.Red
};
}
var buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), popupLayout.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true);
var confirmButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonLayout.RectTransform), TextManager.Get("holdtoconfirm"))
{
RequireHold = true,
HoldDurationSeconds = 1.5f,
OnClicked = (button, o) =>
{
if (character is null || characterInfo is null) { return false; }
characterInfo.RefundTalents();
selectedTalents.Clear();
UpdateTalentInfo();
bgBlocker.Visible = false;
return true;
}
};
var denyButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonLayout.RectTransform), TextManager.Get("decidelater"))
{
RequireHold = false,
OnClicked = (button, userData) =>
{
if (talentResetButton is not { } resetButton) { return false; }
startAnimation?.Invoke(popup.Rect, resetButton.Rect, 0.25f);
resetButton.Flash(GUIStyle.Green);
bgBlocker.Visible = false;
if (characterInfo != null)
{
characterInfo.ShowTalentResetPopupOnOpen = false;
}
return true;
}
};
}
private static StartAnimation CreatePopupAnimationHandler(GUIComponent parent)
{
bool drawAnimation = false;
float animDur = 1f,
animTimer = 0f;
RectangleF drawRect = RectangleF.Empty,
animStartRect = RectangleF.Empty,
animEndRect = RectangleF.Empty;
void StartAnimation(RectangleF start, RectangleF end, float duration)
{
animStartRect = start;
animEndRect = end;
animTimer = 0;
animDur = duration;
drawRect = start;
drawAnimation = true;
}
void OnDraw(SpriteBatch batch, GUICustomComponent component)
{
if (!drawAnimation) { return; }
GUIComponentStyle style = GUIStyle.GetComponentStyle("GUIFrame");
style.Sprites[GUIComponent.ComponentState.None][0].Draw(batch, drawRect, Color.White);
}
void OnUpdate(float f, GUICustomComponent component)
{
if (!drawAnimation) { return; }
animTimer += f;
if (animTimer > animDur)
{
drawRect = animEndRect;
drawAnimation = false;
return;
}
float lerp = animTimer / animDur;
drawRect = new RectangleF(
MathHelper.Lerp(animStartRect.X, animEndRect.X, lerp),
MathHelper.Lerp(animStartRect.Y, animEndRect.Y, lerp),
MathHelper.Lerp(animStartRect.Width, animEndRect.Width, lerp),
MathHelper.Lerp(animStartRect.Height, animEndRect.Height, lerp));
}
new GUICustomComponent(new RectTransform(Vector2.One, parent.RectTransform), onDraw: OnDraw, onUpdate: OnUpdate)
{
IgnoreLayoutGroups = true,
CanBeFocused = false
};
return StartAnimation;
}
private void CreateTalentOption(GUIComponent parent, TalentSubTree subTree, int index, TalentOption talentOption, CharacterInfo info, int specializationCount)
{
int elementPadding = GUI.IntScale(8);
@@ -679,6 +810,15 @@ namespace Barotrauma
private bool ResetTalentSelection(GUIButton guiButton, object userData)
{
if (characterInfo is null) { return false; }
int newTalentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
// if we don't have talents selected, and we have points to refund, show the refund popup
if (characterInfo.TalentRefundPoints > 0 && newTalentCount == 0)
{
CreateTalentResetPopup(talentMainArea!);
return true;
}
selectedTalents = characterInfo.GetUnlockedTalentsInTree().ToHashSet();
UpdateTalentInfo();
return true;
@@ -844,12 +984,31 @@ namespace Barotrauma
}
}
private static readonly LocalizedString refundText = TextManager.Get("refund"),
resetText = TextManager.Get("reset");
public void Update()
{
if (characterInfo is null || talentResetButton is null || talentApplyButton is null) { return; }
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
talentApplyButton.Enabled = talentCount > 0;
talentResetButton.Enabled = talentCount > 0 || characterInfo.TalentRefundPoints > 0;
if (talentCount == 0 && characterInfo.TalentRefundPoints > 0)
{
if (talentResetButton.FlashTimer <= 0.0f)
{
talentResetButton.Flash(GUIStyle.Orange);
}
talentResetButton.Text = refundText;
}
else
{
talentResetButton.Text = resetText;
}
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{
talentApplyButton.Flash(GUIStyle.Orange);
@@ -893,6 +1052,22 @@ namespace Barotrauma
return info.GetIdentifierUsingOriginalName() == ownCharacterInfo.GetIdentifierUsingOriginalName();
}
private static bool IsOnSameTeam(CharacterInfo? info)
{
if (info is null) { return false; }
CharacterTeamType? ownCharacterTeam = Character.Controlled?.TeamID ?? GameMain.Client?.MyClient?.TeamID;
if (ownCharacterTeam is null) { return false; }
return info.TeamID == ownCharacterTeam;
}
private static bool IsSpectatingInMultiplayer()
{
if (GameMain.Client?.MyClient is not { } myClient) { return false; }
return myClient.Spectating;
}
public static bool CanManageTalents(CharacterInfo targetInfo)
{
// in singleplayer we can do whatever we want
@@ -901,10 +1076,16 @@ namespace Barotrauma
// always allow managing talents for own character
if (IsOwnCharacter(targetInfo)) { return true; }
// disallow managing talents while spectating
if (IsSpectatingInMultiplayer()) { return false; }
// don't allow controlling non-bot characters
if (targetInfo.Character is not { IsBot: true }) { return false; }
// lastly check if we have the permission to do this
// only allow managing talents for bots on the same team
if (!IsOnSameTeam(targetInfo)) { return false; }
// lastly, check if we have the permission to do this
return GameMain.Client is { } client && client.HasPermission(ClientPermissions.ManageBotTalents);
}
}

View File

@@ -779,7 +779,7 @@ namespace Barotrauma
{
try
{
SaveUtil.LoadGame(saveFiles.OrderBy(file => file.SaveTime).Last().FilePath);
SaveUtil.LoadGame(CampaignDataPath.CreateRegular(saveFiles.OrderBy(file => file.SaveTime).Last().FilePath));
}
catch (Exception e)
{
@@ -1145,7 +1145,7 @@ namespace Barotrauma
}
GameSession.Campaign?.End();
SaveUtil.SaveGame(GameSession.SavePath);
SaveUtil.SaveGame(GameSession.DataPath);
}
if (Client != null)

View File

@@ -28,7 +28,7 @@ namespace Barotrauma
public GUIComponent ReportButtonFrame { get; set; }
private GUIFrame guiFrame;
private GUIFrame crewArea;
private GUILayoutGroup crewArea;
private GUIListBox crewList;
private float crewListOpenState;
private bool _isCrewMenuOpen = true;
@@ -93,12 +93,30 @@ namespace Barotrauma
#region Crew Area
crewArea = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), style: null, color: Color.Transparent)
crewArea = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), childAnchor: Anchor.TopCenter)
{
CanBeFocused = false
Stretch = true
};
crewArea.RectTransform.NonScaledSize = HUDLayoutSettings.CrewArea.Size;
for (int i = 0; i < 2; i++)
{
CharacterTeamType teamId = i == 0 ? CharacterTeamType.Team1 : CharacterTeamType.Team2;
var nameText = new GUITextBlock(new RectTransform(new Point(crewArea.Rect.Width - GUI.IntScale(10), GUI.IntScale(30)), crewArea.RectTransform), CombatMission.GetTeamName(teamId), textColor: CombatMission.GetTeamColor(teamId))
{
ForceUpperCase = ForceUpperCase.Yes,
TextGetter = () => CombatMission.GetTeamName(teamId),
Visible = false,
IgnoreLayoutGroups = true,
UserData = teamId
};
var teamIcon = new GUIImage(new RectTransform(Vector2.One, nameText.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight), style: i == 0 ? "CoalitionIcon" : "SeparatistIcon")
{
Color = nameText.TextColor
};
nameText.Padding = new Vector4(teamIcon.Rect.Width + nameText.Padding.X, nameText.Padding.Y, nameText.Padding.Z, nameText.Padding.W);
}
// AbsoluteOffset is set in UpdateProjectSpecific based on crewListOpenState
crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false)
{
@@ -562,9 +580,19 @@ namespace Barotrauma
public bool CharacterClicked(GUIComponent component, object selection)
{
if (!AllowCharacterSwitch) { return false; }
if (!(selection is Character character) || character.IsDead || character.IsUnconscious) { return false; }
if (selection is not Character character || character.IsDead || character.IsUnconscious) { return false; }
if (!character.IsOnPlayerTeam) { return false; }
if (GameMain.IsMultiplayer)
{
if (Character.Controlled == null)
{
Camera cam = Screen.Selected.Cam;
cam.Position = character.DrawPosition;
}
return true;
}
SelectCharacter(character);
if (GUI.KeyboardDispatcher.Subscriber == crewList) { GUI.KeyboardDispatcher.Subscriber = null; }
return true;
@@ -631,10 +659,9 @@ namespace Barotrauma
{
if (crewList != this.crewList) { return; }
if (draggedElementData is not Character) { return; }
if (!IsSinglePlayer) { return; }
if (crewList.HasDraggedElementIndexChanged)
{
UpdateCrewListIndices();
if (IsSinglePlayer) { UpdateCrewListIndices(); }
}
else
{
@@ -645,10 +672,13 @@ namespace Barotrauma
private void ResetCrewListIndex(Character c)
{
if (c?.Info == null) { return; }
c.Info.CrewListIndex = -1;
UpdateCrewListIndices();
//default to the bottom of the list
c.Info.CrewListIndex = int.MaxValue;
}
/// <summary>
/// Refresh the <see cref="CharacterInfo.CrewListIndex"/> of the characters based on their order in the crew list
/// </summary>
private void UpdateCrewListIndices()
{
if (crewList == null) { return; }
@@ -661,20 +691,23 @@ namespace Barotrauma
}
}
/// <summary>
/// Order the crew list according to the characters' <see cref="CharacterInfo.CrewListIndex"/>
/// </summary>
private void SortCrewList()
{
if (crewList == null) { return; }
crewList.Content.RectTransform.SortChildren((x, y) =>
{
var infoX = (x.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
var infoY = (y.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
if (infoX.HasValue)
int? index1 = (x.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
int? index2 = (y.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
if (index1.HasValue)
{
return infoY.HasValue ? infoX.Value.CompareTo(infoY.Value) : -1;
return index2.HasValue ? index1.Value.CompareTo(index2.Value) : -1;
}
else
{
return infoY.HasValue ? 1 : 0;
return index2.HasValue ? 1 : 0;
}
});
UpdateCrewListIndices();
@@ -1635,6 +1668,18 @@ namespace Barotrauma
{
crewArea.Visible = characters.Count > 0 && CharacterHealth.OpenHealthWindow == null;
var myTeam = Character.Controlled?.TeamID ?? GameMain.Client?.MyClient?.TeamID;
if (GameMain.GameSession?.GameMode is PvPMode)
{
var team1Text = crewArea.GetChildByUserData(CharacterTeamType.Team1);
team1Text.Visible = myTeam == CharacterTeamType.Team1;
team1Text.IgnoreLayoutGroups = !team1Text.Visible;
var team2Text = crewArea.GetChildByUserData(CharacterTeamType.Team2);
team2Text.Visible = myTeam == CharacterTeamType.Team2;
team2Text.IgnoreLayoutGroups = !team2Text.Visible;
}
foreach (GUIComponent characterComponent in crewList.Content.Children)
{
if (characterComponent.UserData is Character character)
@@ -1645,7 +1690,7 @@ namespace Barotrauma
continue;
}
characterComponent.Visible = Character.Controlled == null || Character.Controlled.TeamID == character.TeamID;
characterComponent.Visible = Character.Controlled == null || myTeam == character.TeamID;
if (character.TeamID == CharacterTeamType.FriendlyNPC && Character.Controlled != null &&
(character.CurrentHull == Character.Controlled.CurrentHull || Vector2.DistanceSquared(Character.Controlled.WorldPosition, character.WorldPosition) < 500.0f * 500.0f))
{
@@ -2098,7 +2143,7 @@ namespace Barotrauma
CreateNodeConnectors();
if (Character.Controlled != null)
{
Character.Controlled.dontFollowCursor = true;
Character.Controlled.FollowCursor = false;
}
HintManager.OnShowCommandInterface();
@@ -2242,7 +2287,7 @@ namespace Barotrauma
returnNodeHotkey = expandNodeHotkey = Keys.None;
if (Character.Controlled != null)
{
Character.Controlled.dontFollowCursor = false;
Character.Controlled.FollowCursor = true;
}
}
@@ -2511,7 +2556,7 @@ namespace Barotrauma
// --> Create shortcut node for Steer order
if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["steer"]) &&
subItems.Find(i => i.HasTag(Tags.NavTerminal) && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedItem == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
nav.GetComponent<Steering>() is Steering { HasPower: true } steering)
{
var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering);
AddOrderNode(order);

View File

@@ -395,13 +395,15 @@ namespace Barotrauma
protected void TryEndRoundWithFuelCheck(Action onConfirm, Action onReturnToMapScreen)
{
if (Submarine.MainSub == null) { return; }
Submarine.MainSub.CheckFuel();
bool lowFuel = Submarine.MainSub.Info.LowFuel;
if (PendingSubmarineSwitch != null)
{
lowFuel = TransferItemsOnSubSwitch ? (lowFuel && PendingSubmarineSwitch.LowFuel) : PendingSubmarineSwitch.LowFuel;
}
if (Level.IsLoadedFriendlyOutpost && lowFuel && CargoManager.PurchasedItems.None(i => i.Value.Any(pi => pi.ItemPrefab.Tags.Contains("reactorfuel"))))
if (Level.IsLoadedFriendlyOutpost && lowFuel && CargoManager.PurchasedItems.None(i => i.Value.Any(pi => pi.ItemPrefab.Tags.Contains(Tags.ReactorFuel))))
{
var extraConfirmationBox =
new GUIMessageBox(TextManager.Get("lowfuelheader"),

View File

@@ -541,7 +541,7 @@ namespace Barotrauma
{
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty, mapSeed);
GameMain.GameSession = new GameSession(null, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty, mapSeed);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
@@ -1044,7 +1044,7 @@ namespace Barotrauma
return false;
}
public override void Save(XElement element)
public override void Save(XElement element, bool isSavingOnLoading)
{
//do nothing, the clients get the save files from the server
}

View File

@@ -240,7 +240,7 @@ namespace Barotrauma
if (!savedOnStart)
{
GUI.SetSavingIndicatorState(true);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
SaveUtil.SaveGame(GameMain.GameSession.DataPath, isSavingOnLoading: true);
savedOnStart = true;
}
@@ -448,7 +448,7 @@ namespace Barotrauma
if (success)
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
SaveUtil.SaveGame(GameMain.GameSession.DataPath);
}
else
{
@@ -479,7 +479,7 @@ namespace Barotrauma
protected override void EndCampaignProjSpecific()
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
SaveUtil.SaveGame(GameMain.GameSession.DataPath);
GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
GameMain.CampaignEndScreen.OnFinished = () =>
@@ -672,7 +672,7 @@ namespace Barotrauma
}
}
public override void Save(XElement element)
public override void Save(XElement element, bool isSavingOnLoading)
{
XElement modeElement = new XElement("SinglePlayerCampaign",
new XAttribute("purchasedlostshuttles", PurchasedLostShuttles),

View File

@@ -7,7 +7,7 @@ using Barotrauma.Items.Components;
namespace Barotrauma
{
class TestGameMode : GameMode
partial class TestGameMode : GameMode
{
public Action OnRoundEnd;
@@ -22,18 +22,6 @@ namespace Barotrauma
private GUIButton createEventButton;
public TestGameMode(GameModePreset preset) : base(preset)
{
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs.OrderBy(p => p.Identifier))
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
var variant = Rand.Range(0, jobPrefab.Variants);
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant));
}
}
}
public override void Start()
{
base.Start();
@@ -42,17 +30,24 @@ namespace Barotrauma
foreach (Submarine submarine in Submarine.Loaded)
{
submarine.NeutralizeBallast();
//normally the body would be made static during level generation,
//but in the test mode we load the outpost/wreck/beacon as if it was a normal sub and need to do this manually
if (submarine.Info.Type == SubmarineType.Outpost ||
submarine.Info.Type == SubmarineType.OutpostModule ||
submarine.Info.Type == SubmarineType.Wreck ||
submarine.Info.Type == SubmarineType.BeaconStation)
switch (submarine.Info.Type)
{
submarine.PhysicsBody.BodyType = FarseerPhysics.BodyType.Static;
case SubmarineType.Outpost:
case SubmarineType.OutpostModule:
case SubmarineType.Wreck:
case SubmarineType.BeaconStation:
//normally the body would be made static during level generation,
//but in the test mode we load the outpost/wreck/beacon as if it was a normal sub and need to do this manually
submarine.PhysicsBody.BodyType = FarseerPhysics.BodyType.Static;
if (submarine.Info.ShouldBeRuin)
{
submarine.Info.Type = SubmarineType.Ruin;
}
submarine.TeamID = submarine.Info.IsOutpost ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None;
break;
}
}
if (SpawnOutpost)
{
GenerateOutpost(Submarine.MainSub);

View File

@@ -86,7 +86,7 @@ namespace Barotrauma.Tutorials
yield return CoroutineStatus.Running;
GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null);
GameMain.GameSession = new GameSession(subInfo, Option.None, GameModePreset.Tutorial, missionPrefabs: null);
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
if (generationParams is not null)
@@ -138,7 +138,7 @@ namespace Barotrauma.Tutorials
character = Character.Create(charInfo, wayPoint.WorldPosition, "", isRemotePlayer: false, hasAi: false);
character.TeamID = CharacterTeamType.Team1;
Character.Controlled = character;
character.GiveJobItems(null);
character.GiveJobItems(isPvPMode: false, null);
var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier());
if (idCard == null)

View File

@@ -282,10 +282,10 @@ namespace Barotrauma
public void SetRespawnInfo(string text, Color textColor, bool waitForNextRoundRespawn, bool hideButtons = false)
{
if (topLeftButtonGroup == null) { return; }
bool permadeathMode = GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath };
bool ironmanMode = GameMain.NetworkMember is { ServerSettings: { RespawnMode: RespawnMode.Permadeath, IronmanMode: true } };
bool ironmanMode = GameMain.NetworkMember?.ServerSettings is { IronmanModeActive: true };
bool hasRespawnOptions;
if (permadeathMode)
{

View File

@@ -205,7 +205,7 @@ namespace Barotrauma
if (Character.Controlled.SelectedItem.GetComponent<Reactor>() is Reactor reactor && reactor.PowerOn &&
Character.Controlled.SelectedItem.OwnInventory?.AllItems is IEnumerable<Item> containedItems &&
containedItems.Count(i => i.HasTag(Tags.Fuel)) > 1)
containedItems.Count(i => i.HasTag(Tags.ReactorFuel)) > 1)
{
if (DisplayHint("onisinteracting.reactorwithextrarods".ToIdentifier())) { return; }
}

View File

@@ -0,0 +1,84 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
partial class PvPMode : MissionMode
{
private GUIComponent scoreContainer;
private readonly GUITextBlock[] scoreTexts = new GUITextBlock[2];
private readonly GUITextBlock[] scoreTextShadows = new GUITextBlock[2];
private readonly int[] prevScores = new int[2];
private void InitUI()
{
scoreContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), childAnchor: Anchor.TopRight)
{
CanBeFocused = false
};
for (int i = 0; i < 2; i++)
{
var frame = new GUIFrame(new RectTransform(new Point(scoreContainer.Rect.Width, GUI.IntScale(80)), scoreContainer.RectTransform), style: null)
{
CanBeFocused = false
};
new GUIImage(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: i == 0 ? "CoalitionIcon" : "SeparatistIcon")
{
CanBeFocused = false
};
scoreTextShadows[i] = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(GUI.IntScale(38), GUI.IntScale(2)) },
string.Empty, textColor: GUIStyle.TextColorDark, textAlignment: Alignment.CenterRight, font: GUIStyle.SubHeadingFont)
{
CanBeFocused = false
};
scoreTexts[i] = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(GUI.IntScale(40), 0) },
string.Empty, textAlignment: Alignment.CenterRight, font: GUIStyle.SubHeadingFont)
{
CanBeFocused = false
};
}
}
public override void AddToGUIUpdateList()
{
base.AddToGUIUpdateList();
if (scoreContainer == null) { InitUI(); }
scoreContainer.Visible = false;
foreach (var mission in Missions)
{
if (mission is CombatMission combatMission && combatMission.HasWinScore)
{
for (int i = 0; i < 2; i++)
{
var scoreText = scoreTexts[i];
//one team very close to the win score, start flashing the score
if (combatMission.Scores[i] > combatMission.WinScore * 0.9f ||
combatMission.Scores[i] == combatMission.WinScore - 1)
{
if (scoreText.Parent.FlashTimer <= 0.0f)
{
scoreText.Parent.Flash(GUIStyle.Orange);
scoreText.Pulsate(Vector2.One, Vector2.One * 1.2f, scoreText.Parent.FlashTimer);
}
}
if (prevScores[i] != combatMission.Scores[i] || scoreText.Text.IsNullOrEmpty())
{
scoreText.Text = scoreTextShadows[i].Text = $"{combatMission.Scores[i]}/{combatMission.WinScore}";
scoreText.Parent.Flash(GUIStyle.Green);
scoreText.Parent.GetAnyChild<GUIImage>().Pulsate(Vector2.One, Vector2.One * 1.2f, scoreText.Parent.FlashTimer);
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
}
scoreText.Parent.RectTransform.NonScaledSize =
new Point(
(int)(scoreText.TextSize.X + scoreText.Padding.X + scoreText.Padding.X) + scoreText.Parent.GetChild<GUIImage>().Rect.Width + GUI.IntScale(10),
scoreText.Parent.Rect.Height);
scoreText.Parent.ForceLayoutRecalculation();
prevScores[i] = combatMission.Scores[i];
}
scoreContainer.Visible = true;
}
}
scoreContainer.AddToGUIUpdateList();
}
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
@@ -13,11 +14,13 @@ namespace Barotrauma
private float crewListAnimDelay = 0.25f;
private float missionIconAnimDelay;
private const float jobColumnWidthPercentage = 0.11f;
private const float characterColumnWidthPercentage = 0.44f;
private const float statusColumnWidthPercentage = 0.45f;
private const float JobColumnWidthPercentage = 0.1f;
private const float CharacterColumnWidthPercentage = 0.4f;
private const float StatusColumnWidthPercentage = 0.12f;
private const float KillColumnWidthPercentage = 0.1f;
private const float DeathColumnWidthPercentage = 0.1f;
private int jobColumnWidth, characterColumnWidth, statusColumnWidth;
private int jobColumnWidth, characterColumnWidth, statusColumnWidth, killColumnWidth, deathColumnWidth;
private readonly List<Mission> selectedMissions;
private readonly Location startLocation, endLocation;
@@ -109,6 +112,16 @@ namespace Barotrauma
CombatMission.GetTeamName(CharacterTeamType.Team2), textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont);
crewHeader2.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader2.Rect.Height * 2.0f));
CreateCrewList(crewContent2, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID == CharacterTeamType.Team2), traitorResults);
if (CombatMission.Winner != CharacterTeamType.None)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewHeader.RectTransform),
TextManager.Get(CombatMission.Winner == CharacterTeamType.Team1 ? "pvpmode.victory" : "pvpmode.defeat"), textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont,
textColor: CombatMission.Winner == CharacterTeamType.Team1 ? GUIStyle.Green : GUIStyle.Red);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewHeader2.RectTransform),
TextManager.Get(CombatMission.Winner == CharacterTeamType.Team2 ? "pvpmode.victory" : "pvpmode.defeat"), textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont,
textColor: CombatMission.Winner == CharacterTeamType.Team2 ? GUIStyle.Green : GUIStyle.Red);
}
}
//header -------------------------------------------------------------------------------
@@ -242,7 +255,7 @@ namespace Barotrauma
if (selectedMissions.Contains(mission))
{
UpdateMissionStateIcon(mission.Completed, missionIcon, animDelay);
UpdateMissionStateIcon(mission is CombatMission combatMission ? CombatMission.IsInWinningTeam(GameMain.Client?.Character) : mission.Completed, missionIcon, animDelay);
animDelay += 0.25f;
}
}
@@ -567,7 +580,11 @@ namespace Barotrauma
LocalizedString locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.DisplayName : startLocation?.DisplayName;
string textTag;
if (gameOver)
if (gameMode is PvPMode)
{
textTag = "RoundSummaryRoundHasEnded";
}
else if (gameOver)
{
textTag = "RoundSummaryGameOver";
}
@@ -596,7 +613,14 @@ namespace Barotrauma
textTag = "RoundSummaryReturnToEmptyLocation";
break;
default:
textTag = Submarine.MainSub.AtEndExit ? "RoundSummaryProgress" : "RoundSummaryReturn";
if (Submarine.MainSub == null)
{
textTag = "RoundSummaryRoundHasEnded";
}
else
{
textTag = Submarine.MainSub.AtEndExit ? "RoundSummaryProgress" : "RoundSummaryReturn";
}
break;
}
}
@@ -628,22 +652,28 @@ namespace Barotrauma
{
var headerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform, Anchor.TopCenter, minSize: new Point(0, (int)(30 * GUI.Scale))) { }, isHorizontal: true)
{
AbsoluteSpacing = 2
AbsoluteSpacing = 2,
Stretch = true
};
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale");
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(JobColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(CharacterColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
float sizeMultiplier = 1.0f;
//sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
if (gameMode is PvPMode)
{
var killButton = new GUIButton(new RectTransform(new Vector2(KillColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("killcount"), style: "GUIButtonSmallFreeScale");
killColumnWidth = killButton.Rect.Width;
var deathButton = new GUIButton(new RectTransform(new Vector2(DeathColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("deathcount"), style: "GUIButtonSmallFreeScale");
deathColumnWidth = deathButton.Rect.Width;
}
jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
characterButton.RectTransform.RelativeSize = new Vector2(characterColumnWidthPercentage * sizeMultiplier, 1f);
statusButton.RectTransform.RelativeSize = new Vector2(statusColumnWidthPercentage * sizeMultiplier, 1f);
GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(StatusColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale");
jobButton.TextBlock.Font = characterButton.TextBlock.Font = statusButton.TextBlock.Font = GUIStyle.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = statusButton.CanBeFocused = false;
jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = statusButton.ForceUpperCase = ForceUpperCase.Yes;
foreach (var btn in headerFrame.GetAllChildren<GUIButton>())
{
btn.TextBlock.Font = GUIStyle.HotkeyFont;
btn.ForceUpperCase = ForceUpperCase.Yes;
btn.CanBeFocused = false;
}
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
@@ -658,8 +688,28 @@ namespace Barotrauma
headerFrame.RectTransform.RelativeSize -= new Vector2(crewList.ScrollBar.RectTransform.RelativeSize.X, 0.0f);
killCounts.Clear();
if (GameMain.NetworkMember != null)
{
foreach (CharacterInfo characterInfo in characterInfos)
{
if (characterInfo == null) { continue; }
Character character = characterInfo.Character;
Client ownerClient = GameMain.NetworkMember.ConnectedClients.FirstOrDefault(c => c.Character == character);
int killCount = 0, deathCount = 0;
foreach (var mission in selectedMissions)
{
if (mission is not CombatMission combatMission) { continue; }
killCount += ownerClient == null ? combatMission.GetBotKillCount(characterInfo) : combatMission.GetClientKillCount(ownerClient);
deathCount += ownerClient == null ? combatMission.GetBotDeathCount(characterInfo) : combatMission.GetClientDeathCount(ownerClient);
}
killCounts[characterInfo] = killCount;
deathCounts[characterInfo] = deathCount;
}
}
float delay = crewListAnimDelay;
foreach (CharacterInfo characterInfo in characterInfos)
foreach (CharacterInfo characterInfo in characterInfos.OrderByDescending(ci => killCounts.GetValueOrDefault(ci)))
{
if (characterInfo == null) { continue; }
CreateCharacterElement(characterInfo, crewList, traitorResults, delay);
@@ -670,6 +720,10 @@ namespace Barotrauma
return crewList;
}
private readonly Dictionary<CharacterInfo, int> killCounts = new();
private readonly Dictionary<CharacterInfo, int> deathCounts = new();
private void CreateCharacterElement(CharacterInfo characterInfo, GUIListBox listBox, TraitorManager.TraitorResults? traitorResults, float animDelay)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(45)), listBox.Content.RectTransform), style: "ListBoxElement")
@@ -741,8 +795,19 @@ namespace Barotrauma
}
}
if (gameMode is PvPMode pvpMode)
{
new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
killCounts.GetValueOrDefault(characterInfo).ToString(), textAlignment: Alignment.Center);
new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
deathCounts.GetValueOrDefault(characterInfo).ToString(), textAlignment: Alignment.Center);
}
GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(statusText.Value, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: statusColor);
ToolBox.LimitString(statusText.Value, GUIStyle.Font, statusColumnWidth), textAlignment: Alignment.Center, textColor: statusColor, font: GUIStyle.SmallFont)
{
ToolTip = statusText.Value
};
frame.FadeIn(animDelay, 0.15f);
foreach (var child in frame.GetAllChildren())

View File

@@ -48,25 +48,46 @@ namespace Barotrauma.Items.Components
description += '\n' + containedDescription;
}
}
if (GameMain.DevMode && Tainted && selectedTaintedEffect != null)
{
description = $"{description}\n{selectedTaintedEffect.Name}: {selectedTaintedEffect.GetDescription(0f, AfflictionPrefab.Description.TargetType.OtherCharacter)}";
}
}
public void ModifyDeconstructInfo(Deconstructor deconstructor, ref LocalizedString buttonText, ref LocalizedString infoText)
{
if (deconstructor.InputContainer.Inventory.AllItems.Count() == 2)
{
var otherGeneticMaterial =
deconstructor.InputContainer.Inventory.AllItems.FirstOrDefault(it => it != item && it.Prefab == item.Prefab)?.GetComponent<GeneticMaterial>();
var otherItem = deconstructor.InputContainer.Inventory.AllItems.FirstOrDefault(it => it != item);
if (otherItem == null)
{
return;
}
var otherGeneticMaterial = otherItem.GetComponent<GeneticMaterial>();
if (otherGeneticMaterial == null)
{
buttonText = TextManager.Get("researchstation.combine");
infoText = TextManager.Get("researchstation.combine.infotext");
return;
}
else
var combineRefineResult = GetCombineRefineResult(otherGeneticMaterial);
if (combineRefineResult == CombineResult.None)
{
infoText = TextManager.Get("researchstation.novalidcombination");
}
else if (combineRefineResult == CombineResult.Refined)
{
buttonText = TextManager.Get("researchstation.refine");
int taintedProbability = (int)(GetTaintedProbabilityOnRefine(otherGeneticMaterial, Character.Controlled) * 100);
infoText = TextManager.GetWithVariable("researchstation.refine.infotext", "[taintedprobability]", taintedProbability.ToString());
}
else
{
buttonText = TextManager.Get("researchstation.combine");
infoText = TextManager.Get("researchstation.combine.infotext");
}
}
}

View File

@@ -177,6 +177,8 @@ namespace Barotrauma.Items.Components
//don't draw the crosshair if the item is in some other type of equip slot than hands (e.g. assault rifle in the bag slot)
if (!character.HeldItems.Contains(item)) { return; }
base.DrawHUD(spriteBatch, character);
GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) &&
GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused;

View File

@@ -216,6 +216,7 @@ namespace Barotrauma.Items.Components
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
base.DrawHUD(spriteBatch, character);
GUI.HideCursor = targetSections.Count > 0;
}

View File

@@ -150,6 +150,17 @@ namespace Barotrauma.Items.Components
public GUIFrame GuiFrame { get; set; }
/// <summary>
/// Overlay (just a non-interactable sprite) drawn when the item is selected, equipped or focused to via Controllers (e.g. when operating a turret via a periscope or a camera via a monitor).
/// </summary>
public Sprite HUDOverlay { get; set; }
public float HUDOverlayAnimSpeed
{
get;
set;
}
private GUIDragHandle guiFrameDragHandle;
private bool guiFrameUpdatePending;
@@ -161,6 +172,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(true, IsPropertySaveable.No)]
public bool CloseByClickingOutsideGUIFrame
{
get;
set;
}
private ItemComponent linkToUIComponent;
[Serialize("", IsPropertySaveable.No)]
public string LinkUIToComponent
@@ -261,7 +279,7 @@ namespace Barotrauma.Items.Components
if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
//above the top boundary of the level (in an inactive respawn shuttle?)
if (item.Submarine != null && Level.Loaded != null && item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y)
if (item.Submarine != null && item.Submarine.IsAboveLevel)
{
return;
}
@@ -340,7 +358,7 @@ namespace Barotrauma.Items.Components
}
else if (soundSelectionMode == SoundSelectionMode.Manual)
{
index = Math.Clamp(ManuallySelectedSound, 0, matchingSounds.Count);
index = Math.Clamp(ManuallySelectedSound, 0, matchingSounds.Count - 1);
}
else
{
@@ -416,12 +434,23 @@ namespace Barotrauma.Items.Components
if (sound == null) { return 0.0f; }
if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; }
if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
SerializableProperty property = null;
ISerializableEntity targetEntity = null;
if (SerializableProperties.TryGetValue(sound.VolumeProperty, out property))
{
targetEntity = this;
}
else if (Item.SerializableProperties.TryGetValue(sound.VolumeProperty, out property))
{
targetEntity = Item;
}
if (property != null)
{
float newVolume;
try
{
newVolume = property.GetFloatValue(this);
newVolume = property.GetFloatValue(targetEntity);
}
catch
{
@@ -470,7 +499,24 @@ namespace Barotrauma.Items.Components
return linkToUIComponent;
}
public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { }
public virtual void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (HUDOverlay != null)
{
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
if (HUDOverlay is SpriteSheet spriteSheet)
{
spriteSheet.Draw(spriteBatch,
spriteIndex: (int)(Math.Floor(Timing.TotalTimeUnpaused * HUDOverlayAnimSpeed) % spriteSheet.FrameCount),
pos: screenSize / 2, color: Color.White, origin: HUDOverlay.Origin, rotate: 0, scale: screenSize / spriteSheet.FrameSize.ToVector2());
}
else
{
HUDOverlay.Draw(spriteBatch,
pos: screenSize / 2, color: Color.White, origin: HUDOverlay.Origin, rotate: 0, scale: screenSize / HUDOverlay.size);
}
}
}
public virtual void AddToGUIUpdateList(int order = 0)
{
@@ -513,6 +559,13 @@ namespace Barotrauma.Items.Components
GuiFrameSource = subElement;
ReloadGuiFrame();
break;
case "hudoverlayanimated":
HUDOverlay = new SpriteSheet(subElement);
HUDOverlayAnimSpeed = subElement.GetAttributeFloat("animspeed", 1.0f);
break;
case "hudoverlay":
HUDOverlay = new Sprite(subElement);
break;
case "alternativelayout":
AlternativeLayout = GUILayoutSettings.Load(subElement);
break;

View File

@@ -458,54 +458,14 @@ namespace Barotrauma.Items.Components
public void DrawContainedItems(SpriteBatch spriteBatch, float itemDepth, Color? overrideColor = null)
{
Vector2 transformedItemPos = ItemPos * item.Scale;
Vector2 transformedItemInterval = ItemInterval * item.Scale;
Vector2 transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f);
Vector2 transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y);
var rootBody = item.RootContainer?.body ?? item.body;
if (item.body == null)
{
if (item.FlippedX)
{
transformedItemPos.X = -transformedItemPos.X;
transformedItemPos.X += item.Rect.Width;
transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
}
if (item.FlippedY)
{
transformedItemPos.Y = -transformedItemPos.Y;
transformedItemPos.Y -= item.Rect.Height;
transformedItemInterval.Y = -transformedItemInterval.Y;
transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
}
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; }
if (Math.Abs(item.RotationRad) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
transformedItemPos = Vector2.Transform(transformedItemPos - item.DrawPosition, transform) + item.DrawPosition;
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
}
}
else
{
Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
if (item.body.Dir == -1.0f)
{
transformedItemPos.X = -transformedItemPos.X;
transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
}
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemPos += item.body.DrawPosition;
}
Vector2 transformedItemPos = GetContainedPosition(
drawPosition: true,
out Vector2 transformedItemIntervalHorizontal,
out Vector2 transformedItemIntervalVertical,
out bool flippedX,
out bool flippedY);
Vector2 currentItemPos = transformedItemPos;
@@ -525,19 +485,19 @@ namespace Barotrauma.Items.Components
if (item.body != null)
{
Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
pos.X *= item.body.Dir;
pos.X *= rootBody.Dir;
itemPos = Vector2.Transform(pos, transform) + item.body.DrawPosition;
}
else
{
itemPos = pos;
// This code is aped based on above. Not tested.
if (item.FlippedX)
if (flippedX)
{
itemPos.X = -itemPos.X;
itemPos.X += item.Rect.Width;
}
if (item.FlippedY)
if (flippedY)
{
itemPos.Y = -itemPos.Y;
itemPos.Y -= item.Rect.Height;
@@ -555,15 +515,15 @@ namespace Barotrauma.Items.Components
}
}
if (AutoInteractWithContained)
if (CanAutoInteractWithContained(contained.Item) && Screen.Selected is not { IsEditor: true })
{
contained.Item.IsHighlighted = item.IsHighlighted;
item.IsHighlighted = false;
}
Vector2 origin = contained.Item.Sprite.Origin;
if (item.FlippedX) { origin.X = contained.Item.Sprite.SourceRect.Width - origin.X; }
if (item.FlippedY) { origin.Y = contained.Item.Sprite.SourceRect.Height - origin.Y; }
if (flippedX) { origin.X = contained.Item.Sprite.SourceRect.Width - origin.X; }
if (flippedY) { origin.Y = contained.Item.Sprite.SourceRect.Height - origin.Y; }
float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? contained.Item.Sprite.Depth : ContainedSpriteDepth;
if (i < containedSpriteDepths.Length)
@@ -578,12 +538,13 @@ namespace Barotrauma.Items.Components
{
spriteRotation = contained.Rotation;
}
bool flipX = (item.body != null && item.body.Dir == -1) || item.FlippedX;
bool flipX = rootBody is { Dir: -1 } || flippedX;
if (flipX)
{
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipVertically : SpriteEffects.FlipHorizontally;
}
bool flipY = item.FlippedY;
bool flipY = flippedY;
if (flipY)
{
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
@@ -598,7 +559,8 @@ namespace Barotrauma.Items.Components
contained.Item.Scale,
spriteEffects,
depth: containedSpriteDepth);
contained.Item.DrawDecorativeSprites(spriteBatch, itemPos, flipX,flipY, (contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
contained.Item.DrawDecorativeSprites(spriteBatch, itemPos, flipX, flipY, (contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
containedSpriteDepth, overrideColor);
foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>())
@@ -620,7 +582,7 @@ namespace Barotrauma.Items.Components
}
else
{
currentItemPos += transformedItemInterval;
currentItemPos += transformedItemIntervalHorizontal + transformedItemIntervalVertical;
}
}
}

View File

@@ -41,7 +41,7 @@ namespace Barotrauma.Items.Components
}
private string text;
[Serialize("", IsPropertySaveable.Yes, translationTextTag: "Label.", description: "The text displayed in the label.", alwaysUseInstanceValues: true), Editable(100)]
[Serialize("", IsPropertySaveable.Yes, translationTextTag: "Label.", description: "The text displayed in the label.", alwaysUseInstanceValues: true), Editable(MaxLength = 100)]
public string Text
{
get { return text; }

View File

@@ -11,6 +11,7 @@ namespace Barotrauma.Items.Components
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
base.DrawHUD(spriteBatch, character);
if (focusTarget != null && character.ViewTarget == focusTarget)
{
foreach (ItemComponent ic in focusTarget.Components)
@@ -23,6 +24,15 @@ namespace Barotrauma.Items.Components
}
}
public override void AddToGUIUpdateList(int order = 0)
{
base.AddToGUIUpdateList(order);
if (focusTarget != null && Character.Controlled.ViewTarget == focusTarget)
{
focusTarget.AddToGUIUpdateList(order);
}
}
partial void HideHUDs(bool value)
{
if (isHUDsHidden == value) { return; }

View File

@@ -154,12 +154,14 @@ namespace Barotrauma.Items.Components
{
infoArea.Text = TextManager.Get(InfoText).Fallback(InfoText);
}
if (IsActive)
{
activateButton.Text = TextManager.Get("DeconstructorCancel");
infoArea.Text = string.Empty;
return;
}
bool outputsFound = false;
foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: true))
{
@@ -174,27 +176,34 @@ namespace Barotrauma.Items.Components
}
inputItem.GetComponent<GeneticMaterial>()?.ModifyDeconstructInfo(this, ref buttonText, ref infoText);
activateButton.Text = buttonText;
if (infoArea != null)
{
infoArea.Text = infoText;
}
infoArea.Text = infoText;
return;
}
}
LocalizedString activateButtonText = TextManager.Get(ActivateButtonText);
activateButton.Enabled = outputsFound || !InputContainer.Inventory.IsEmpty();
activateButton.Text = activateButtonText;
//no valid outputs found: check if we're missing some required items from the input slots and display a message about it if possible
if (!outputsFound && infoArea != null)
{
foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: false))
{
LocalizedString infoText = string.Empty;
if (deconstructItem.RequiredOtherItem.Any() && !string.IsNullOrEmpty(deconstructItem.InfoTextOnOtherItemMissing))
{
LocalizedString missingItemName = TextManager.Get("entityname." + deconstructItem.RequiredOtherItem.First());
infoArea.Text = TextManager.GetWithVariable(deconstructItem.InfoTextOnOtherItemMissing, "[itemname]", missingItemName);
infoText = TextManager.GetWithVariable(deconstructItem.InfoTextOnOtherItemMissing, "[itemname]", missingItemName);
}
inputItem.GetComponent<GeneticMaterial>()?.ModifyDeconstructInfo(this, ref activateButtonText, ref infoText);
activateButton.Text = activateButtonText;
infoArea.Text = infoText;
}
}
activateButton.Enabled = outputsFound || !InputContainer.Inventory.IsEmpty();
activateButton.Text = TextManager.Get(ActivateButtonText);
};
}
@@ -415,7 +424,7 @@ namespace Barotrauma.Items.Components
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
inSufficientPowerWarning.Visible = IsActive && !hasPower;
inSufficientPowerWarning.Visible = IsActive && !HasPower;
}
private bool OnActivateButtonClicked(GUIButton button, object obj)

View File

@@ -10,45 +10,27 @@ using Microsoft.Xna.Framework.Input;
namespace Barotrauma.Items.Components
{
internal readonly struct MiniMapGUIComponent
internal readonly record struct MiniMapGUIComponent(GUIComponent RectComponent, GUIComponent BorderComponent)
{
public readonly GUIComponent RectComponent;
public readonly GUIComponent BorderComponent;
public MiniMapGUIComponent(GUIComponent rectComponent)
public MiniMapGUIComponent(GUIComponent rectComponent) : this(rectComponent, rectComponent)
{
RectComponent = rectComponent;
BorderComponent = rectComponent;
}
public MiniMapGUIComponent(GUIComponent frame, GUIComponent linkedHullComponent)
{
RectComponent = frame;
BorderComponent = linkedHullComponent;
}
public void Deconstruct(out GUIComponent component, out GUIComponent borderComponent)
{
component = RectComponent;
borderComponent = BorderComponent;
}
}
internal readonly struct MiniMapSprite
internal readonly record struct MiniMapSprite(Sprite? Sprite, Color Color)
{
public readonly Sprite? Sprite;
public readonly Color Color;
public MiniMapSprite(JobPrefab prefab)
public MiniMapSprite(JobPrefab prefab) : this(prefab.IconSmall, prefab.UIColor)
{
Sprite = prefab.IconSmall;
Color = prefab.UIColor;
}
public MiniMapSprite(Order order)
public MiniMapSprite(Order order) : this(order.SymbolSprite, order.Color)
{
Sprite = order.SymbolSprite;
Color = order.Color;
}
}
@@ -223,7 +205,27 @@ namespace Barotrauma.Items.Components
NoPowerColor = MiniMapBaseColor * 0.1f,
ElectricalBaseColor = GUIStyle.Orange,
NoPowerElectricalColor = ElectricalBaseColor * 0.1f;
// If this is portable, only allow displaying data in the player sub (not enemy subs, ruins, wrecks or other unknown places)
private bool IsPortableItemAllowed
{
get
{
if (IsUsableOutsidePlayerSub) { return true; }
if (item.Submarine == null) { return false; }
if (item.GetComponent<Pickable>() is not Pickable handheldItem) { return true; }
// This will effectively make sure wherever we are, it belongs to the player
return handheldItem.Picker?.TeamID == item.Submarine.TeamID;
}
}
[Serialize(false, IsPropertySaveable.No, description: "If this item is portable, should it be usable outside the player submarine?")]
public bool IsUsableOutsidePlayerSub
{
get;
set;
}
partial void InitProjSpecific()
{
hullDatas = new Dictionary<Hull, HullData>();
@@ -425,22 +427,25 @@ namespace Barotrauma.Items.Components
return false;
}
private bool VisibleOnItemFinder(Item it)
private bool VisibleOnItemFinder(Item targetItem)
{
if (it?.Submarine == null) { return false; }
if (item.Submarine == null || !item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; }
if (it.NonInteractable || it.IsHidden) { return false; }
if (it.GetComponent<Pickable>() == null) { return false; }
if (targetItem?.Submarine == null || item.Submarine == null) { return false; }
var holdable = it.GetComponent<Holdable>();
if (!IsPortableItemAllowed) { return false; }
if (!item.Submarine.IsEntityFoundOnThisSub(targetItem, includingConnectedSubs: true)) { return false; }
if (targetItem.NonInteractable || targetItem.IsHidden) { return false; }
if (targetItem.GetComponent<Pickable>() == null) { return false; }
var holdable = targetItem.GetComponent<Holdable>();
if (holdable != null && holdable.Attached) { return false; }
var wire = it.GetComponent<Wire>();
var wire = targetItem.GetComponent<Wire>();
if (wire != null && wire.Connections.Any(c => c != null)) { return false; }
if (it.Container?.GetComponent<ItemContainer>() is { DrawInventory: false } or { AllowAccess: false }) { return false; }
if (targetItem.Container?.GetComponent<ItemContainer>() is { DrawInventory: false } or { AllowAccess: false }) { return false; }
if (it.HasTag(Tags.TraitorMissionItem)) { return false; }
if (targetItem.HasTag(Tags.TraitorMissionItem)) { return false; }
return true;
}
@@ -454,18 +459,24 @@ namespace Barotrauma.Items.Components
searchAutoComplete?.AddToGUIUpdateList(order: order + 1);
}
}
private void CreateHUD()
private void ClearHUD()
{
subEntities.Clear();
prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
submarineContainer.ClearChildren();
displayedSubs.Clear();
}
if (item.Submarine is null)
private void RefreshHUD()
{
ClearHUD();
if (item.Submarine is null || !IsPortableItemAllowed)
{
displayedSubs.Clear();
return;
}
prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center));
miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false };
@@ -574,18 +585,25 @@ namespace Barotrauma.Items.Components
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
//recreate HUD if the subs we should display have changed
if (item.Submarine == null && displayedSubs.Count > 0 || // item not inside a sub anymore, but display is still showing subs
// Refresh HUD (including possibly just clearing it away) if the subs we should display have changed
if (item.Submarine == null && displayedSubs.Count > 0 || // item not inside a sub anymore, but display is still showing subs
item.Submarine is { } itemSub &&
(
!displayedSubs.Contains(itemSub) || // current sub not displayed
itemSub.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID).Any(s => !displayedSubs.Contains(s) && itemSub.ConnectedDockingPorts[s].IsLocked) || // some of the docked subs not displayed
displayedSubs.Any(s => s != itemSub && !itemSub.DockedTo.Contains(s)) // displaying a sub that shouldn't be displayed
// current sub not displayed
!displayedSubs.Contains(itemSub) ||
// some of the docked subs not displayed
itemSub.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID).Any(s => !displayedSubs.Contains(s) && itemSub.ConnectedDockingPorts[s].IsLocked) ||
// displaying a sub that shouldn't be displayed
displayedSubs.Any(s => s != itemSub && !itemSub.DockedTo.Contains(s))
) ||
prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight || // resolution changed
!submarineContainer.Children.Any()) // We lack a GUI
// If this item is portable and not in a player sub and using it otherwise is disallowed
!IsPortableItemAllowed ||
// resolution changed
prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight ||
// We lack a GUI
!submarineContainer.Children.Any())
{
CreateHUD();
RefreshHUD();
}
//reset data if we haven't received anything in a while
@@ -737,7 +755,7 @@ namespace Barotrauma.Items.Components
return;
}
if (Voltage < MinVoltage)
if (!HasPower)
{
Vector2 textSize = GUIStyle.Font.MeasureString(noPowerTip);
Vector2 textPos = GuiFrame.Rect.Center.ToVector2();
@@ -747,7 +765,7 @@ namespace Barotrauma.Items.Components
return;
}
if (currentMode == MiniMapMode.HullStatus && item.Submarine != null)
if (currentMode == MiniMapMode.HullStatus && item.Submarine != null && IsPortableItemAllowed)
{
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.End();
@@ -958,19 +976,19 @@ namespace Barotrauma.Items.Components
MiniMapBlips = positions.ToImmutableHashSet();
if (searchAutoComplete is null) { return; }
searchAutoComplete.Visible = false;
HideGUIComponent(searchAutoComplete);
}
private void UpdateHUDBack()
{
if (item.Submarine == null) { return; }
if (hullInfoFrame != null) { hullInfoFrame.Visible = false; }
reportFrame.Visible = false;
searchBarFrame.Visible = false;
electricalFrame.Visible = false;
miniMapFrame.Visible = false;
// Clear up mode-specific elements before checking if drawing should continue, so they'll be gone if not
HideModeSpecificFrames();
if (item.Submarine == null || !IsPortableItemAllowed)
{
ClearHUD();
return;
}
switch (currentMode)
{
@@ -988,7 +1006,24 @@ namespace Barotrauma.Items.Components
break;
}
}
private void HideModeSpecificFrames()
{
HideGUIComponent(hullInfoFrame);
HideGUIComponent(reportFrame);
HideGUIComponent(searchBarFrame);
HideGUIComponent(electricalFrame);
HideGUIComponent(miniMapFrame);
}
private static void HideGUIComponent(GUIComponent? component)
{
if (component != null)
{
component.Visible = false;
}
}
private void UpdateHullStatus()
{
bool canHoverOverHull = true;
@@ -1007,7 +1042,7 @@ namespace Barotrauma.Items.Components
child.Color = child.OutlineColor = NoPowerDoorColor;
}
if (Voltage < MinVoltage) { continue; }
if (!HasPower) { continue; }
child.Color = child.OutlineColor = DoorIndicatorColor;
if (GUI.MouseOn == child)
@@ -1037,7 +1072,7 @@ namespace Barotrauma.Items.Components
}
}
if (Voltage < MinVoltage) { continue; }
if (!HasPower) { continue; }
hullDatas.TryGetValue(hull, out HullData? hullData);
if (hullData is null)
@@ -1187,7 +1222,7 @@ namespace Barotrauma.Items.Components
component.Color = component.OutlineColor = NoPowerElectricalColor;
}
if (Voltage < MinVoltage || !miniMapGuiComponent.RectComponent.Visible) { continue; }
if (!HasPower || !miniMapGuiComponent.RectComponent.Visible) { continue; }
int durability = (int)(it.Condition / (it.MaxCondition / it.MaxRepairConditionMultiplier) * 100f);
Color color = ToolBox.GradientLerp(durability / 100f, GUIStyle.Red, GUIStyle.Orange, GUIStyle.Green, GUIStyle.Green);
@@ -1229,11 +1264,11 @@ namespace Barotrauma.Items.Components
private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container)
{
if (item.Submarine == null) { return; }
if (item.Submarine == null || !IsPortableItemAllowed) { return; }
DrawSubmarine(spriteBatch);
DrawSubmarine(spriteBatch);
if (Voltage < MinVoltage) { return; }
if (!HasPower) { return; }
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);

View File

@@ -144,6 +144,7 @@ namespace Barotrauma.Items.Components
private bool isConnectedToSteering;
private static LocalizedString caveLabel;
private static LocalizedString enemyLabel;
[Serialize(false, IsPropertySaveable.Yes)]
@@ -164,6 +165,8 @@ namespace Barotrauma.Items.Components
TextManager.Get("cave").Fallback(
TextManager.Get("missiontype.nest"));
enemyLabel = TextManager.Get("enemysubmarine");
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -1019,6 +1022,38 @@ namespace Barotrauma.Items.Components
cave.StartPos.ToVector2(), transducerCenter,
DisplayScale, center, DisplayRadius);
}
if (GameMain.NetworkMember is { } networkMember && GameMain.GameSession?.GameMode is PvPMode)
{
if (networkMember.ServerSettings.TrackOpponentInPvP
&& Submarine.MainSubs[0] is { } coalitionSub
&& Submarine.MainSubs[1] is { } separatistSub
&& Character.Controlled is { } player)
{
Submarine whichSubToDraw = player.TeamID switch
{
CharacterTeamType.Team1 => separatistSub,
CharacterTeamType.Team2 => coalitionSub,
_ => null
};
if (whichSubToDraw != null)
{
DrawOffsetMarker(spriteBatch,
enemyLabel.Value,
Tags.Submarine,
Tags.Enemy,
whichSubToDraw.WorldPosition,
transducerCenter,
distanceThresholds: new Range<float>(start: MetersToUnits(150), end: MetersToUnits(1600)),
offset: new Range<float>(start: MetersToUnits(100), end: MetersToUnits(400)),
minOffset: MetersToUnits(10));
static float MetersToUnits(float m)
=> m / Physics.DisplayToRealWorldRatio;
}
}
}
}
int missionIndex = 0;
@@ -1042,7 +1077,8 @@ namespace Barotrauma.Items.Components
}
if (HasMineralScanner && UseMineralScanner && CurrentMode == Mode.Active && MineralClusters != null &&
(item.CurrentHull == null || !DetectSubmarineWalls))
(item.CurrentHull == null || !DetectSubmarineWalls) &&
HasPower)
{
foreach (var c in MineralClusters)
{
@@ -1076,7 +1112,7 @@ namespace Barotrauma.Items.Components
{
if (!sub.ShowSonarMarker) { continue; }
if (connectedSubs.Contains(sub)) { continue; }
if (Level.Loaded != null && sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (sub.IsAboveLevel) { continue; }
if (item.Submarine != null || Character.Controlled != null)
{
@@ -1185,7 +1221,7 @@ namespace Barotrauma.Items.Components
foreach (DockingPort dockingPort in DockingPort.List)
{
if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (dockingPort.Item.Submarine.IsAboveLevel) { continue; }
if (dockingPort.Item.IsHidden) { continue; }
if (dockingPort.Item.Submarine == null) { continue; }
if (dockingPort.Item.Submarine.Info.IsWreck) { continue; }
@@ -1198,8 +1234,8 @@ namespace Barotrauma.Items.Components
//don't show the docking ports of the opposing team on the sonar
if (item.Submarine != null &&
item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
dockingPort.Item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
!item.Submarine.IsRespawnShuttle &&
!dockingPort.Item.Submarine.IsRespawnShuttle &&
!dockingPort.Item.Submarine.Info.IsOutpost &&
!dockingPort.Item.Submarine.Info.IsBeacon)
{
@@ -1792,8 +1828,47 @@ namespace Barotrauma.Items.Components
sonarBlip.Draw(spriteBatch, center + pos, color * 0.5f * blip.Alpha, sonarBlip.Origin, 0, scale, SpriteEffects.None, 0);
}
/// <summary>
/// Used in DrawOffsetMarker to cache the randomized location of the marker
/// </summary>
private readonly Dictionary<Identifier, CachedLocation> cachedLocations = new Dictionary<Identifier, CachedLocation>();
private void DrawOffsetMarker(SpriteBatch spriteBatch, string label, Identifier iconIdentifier, Identifier targetIdentifier, Vector2 worldPosition, Vector2 transducerPosition, Range<float> distanceThresholds, Range<float> offset, float minOffset)
{
Vector2 pos;
if (!cachedLocations.TryGetValue(targetIdentifier, out CachedLocation cachedLocation))
{
cachedLocation = CreateCachedLocation();
cachedLocations.Add(targetIdentifier, cachedLocation);
pos = cachedLocation.Location;
}
else
{
if (Timing.TotalTime > cachedLocation.RecalculationTime)
{
cachedLocation = CreateCachedLocation();
cachedLocations[targetIdentifier] = cachedLocation;
}
pos = cachedLocation.Location;
}
DrawMarker(spriteBatch, label, iconIdentifier, targetIdentifier, pos, transducerPosition, DisplayScale, center, DisplayRadius);
CachedLocation CreateCachedLocation()
{
float distance = Vector2.Distance(worldPosition, transducerPosition);
float maxOffset = MathHelper.Lerp(offset.Start, offset.End, MathHelper.Clamp((distance - distanceThresholds.Start) / (distanceThresholds.End - distanceThresholds.Start), 0.0f, 1.0f));
Vector2 randomPos = Rand.Vector(Rand.Range(minOffset, maxOffset));
return new CachedLocation(worldPosition + randomPos, Timing.TotalTime + Rand.Range(10.0f, 30.0f));
}
}
private void DrawMarker(SpriteBatch spriteBatch, string label, Identifier iconIdentifier, object targetIdentifier, Vector2 worldPosition, Vector2 transducerPosition, float scale, Vector2 center, float radius,
bool onlyShowTextOnMouseOver = false)
bool onlyShowTextOnMouseOver = false)
{
float linearDist = Vector2.Distance(worldPosition, transducerPosition);
float dist = linearDist;

View File

@@ -554,7 +554,7 @@ namespace Barotrauma.Items.Components
int x = rect.X;
int y = rect.Y;
if (Voltage < MinVoltage) { return; }
if (!HasPower) { return; }
Rectangle velRect = new Rectangle(x + 20, y + 20, width - 40, height - 40);
Vector2 steeringOrigin = steerArea.Rect.Center.ToVector2();
@@ -759,7 +759,7 @@ namespace Barotrauma.Items.Components
dockingButton.Text = dockText;
}
if (Voltage < MinVoltage)
if (!HasPower)
{
tipContainer.Visible = true;
tipContainer.Text = noPowerTip;
@@ -829,7 +829,7 @@ namespace Barotrauma.Items.Components
}
if (!AutoPilot && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null)
{
steeringAdjustSpeed = character == null ? DefaultSteeringAdjustSpeed : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f);
steeringAdjustSpeed = character == null ? DefaultSteeringAdjustSpeed : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel(Tags.HelmSkill) / 100.0f);
Vector2 input = Vector2.Zero;
if (PlayerInput.KeyDown(InputType.Left)) { input -= Vector2.UnitX; }
if (PlayerInput.KeyDown(InputType.Right)) { input += Vector2.UnitX; }
@@ -909,7 +909,7 @@ namespace Barotrauma.Items.Components
if (targetPort.Docked || targetPort.Item.Submarine == null) { continue; }
if (targetPort.Item.Submarine == controlledSub || targetPort.IsHorizontal != sourcePort.IsHorizontal) { continue; }
if (targetPort.Item.Submarine.DockedTo?.Contains(sourcePort.Item.Submarine) ?? false) { continue; }
if (Level.Loaded != null && targetPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (targetPort.Item.Submarine.IsAboveLevel) { continue; }
if (sourceDir == targetPort.GetDir()) { continue; }
float dist = Vector2.DistanceSquared(sourcePort.Item.WorldPosition, targetPort.Item.WorldPosition);

View File

@@ -6,6 +6,7 @@ namespace Barotrauma.Items.Components
{
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
base.DrawHUD(spriteBatch, character);
currentTarget?.DrawHUD(spriteBatch, Screen.Selected.Cam, character);
}

View File

@@ -299,6 +299,8 @@ namespace Barotrauma.Items.Components
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
base.DrawHUD(spriteBatch, character);
IsActive = true;
float defaultMaxCondition = (item.MaxCondition / item.MaxRepairConditionMultiplier);

View File

@@ -52,9 +52,11 @@ namespace Barotrauma.Items.Components
Vector2 sourcePos = GetSourcePos();
//need to double the size because this is essentially just the radius, we need the diameter
// + some extra margin to be on the safe side
return new Vector2(
Math.Abs(target.DrawPosition.X - sourcePos.X),
Math.Abs(target.DrawPosition.Y - sourcePos.Y)) * 1.5f;
Math.Abs(target.DrawPosition.Y - sourcePos.Y)) * 2.2f;
}
}
@@ -122,7 +124,7 @@ namespace Barotrauma.Items.Components
{
if (turret.BarrelSprite != null)
{
startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * turret.Item.Scale * BarrelLengthMultiplier;
}
startPos -= turret.GetRecoilOffset();
}

View File

@@ -3,7 +3,6 @@ using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -16,7 +15,7 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(ContentXElement element)
{
terminalButtonStyles = new string[RequiredSignalCount];
terminalButtonStyles = new string[requiredSignalCount];
int i = 0;
foreach (var childElement in element.GetChildElements("TerminalButton"))
{
@@ -38,11 +37,11 @@ namespace Barotrauma.Items.Components
};
paddedFrame.OnAddedToGUIUpdateList += (component) =>
{
bool buttonsEnabled = AllowUsingButtons;
foreach (var child in component.Children)
bool buttonsEnabled = IsActivated;
foreach (GUIComponent child in component.Children)
{
if (!(child is GUIButton)) { continue; }
if (!(child.UserData is int)) { continue; }
if (child is not GUIButton) { continue; }
if (child.UserData is not int) { continue; }
child.Enabled = buttonsEnabled;
child.Children.ForEach(c => c.Enabled = buttonsEnabled);
}
@@ -59,7 +58,7 @@ namespace Barotrauma.Items.Components
containerIndicator.OverrideState = itemsContained ? GUIComponent.ComponentState.Selected : GUIComponent.ComponentState.None;
};
float x = 1.0f / (1 + RequiredSignalCount);
float x = 1.0f / (1 + requiredSignalCount);
float y = Math.Min((x * paddedFrame.Rect.Width) / paddedFrame.Rect.Height, 0.5f);
Vector2 relativeSize = new Vector2(x, y);
@@ -69,7 +68,7 @@ namespace Barotrauma.Items.Components
containerIndicator = new GUIImage(new RectTransform(new Vector2(0.5f, 0.5f * (1.0f - y)), containerSection.RectTransform, anchor: Anchor.BottomCenter),
style: "IndicatorLightRed", scaleToFit: true);
for (int i = 0; i < RequiredSignalCount; i++)
for (int i = 0; i < requiredSignalCount; i++)
{
var button = new GUIButton(new RectTransform(relativeSize, paddedFrame.RectTransform), style: null)
{
@@ -111,7 +110,7 @@ namespace Barotrauma.Items.Components
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
SendSignal(msg.ReadRangedInteger(0, Signals.Length - 1), sender: null, isServerMessage: true);
SendSignal(msg.ReadRangedInteger(0, Signals.Length - 1), sender: null, ignoreState: true);
}
}
}

View File

@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using System;
using System.Collections.Generic;
@@ -129,7 +129,7 @@ namespace Barotrauma.Items.Components
public void RemoveComponents(IReadOnlyCollection<CircuitBoxComponent> node)
{
if (Locked) { return; }
if (IsLocked()) { return; }
var ids = node.Select(static n => n.ID).ToImmutableArray();
if (GameMain.NetworkMember is null)
@@ -146,7 +146,7 @@ namespace Barotrauma.Items.Components
public void AddWire(CircuitBoxConnection one, CircuitBoxConnection two)
{
if (Locked) { return; }
if (IsLocked()) { return; }
if (GameMain.NetworkMember is null)
{
Connect(one, two, static delegate { }, CircuitBoxWire.SelectedWirePrefab);
@@ -160,7 +160,7 @@ namespace Barotrauma.Items.Components
public void RemoveWires(IReadOnlyCollection<CircuitBoxWire> wires)
{
if (Locked) { return; }
if (IsLocked()) { return; }
var ids = wires.Select(static w => w.ID).ToImmutableArray();
if (GameMain.NetworkMember is null)
{
@@ -230,7 +230,7 @@ namespace Barotrauma.Items.Components
public void MoveComponent(Vector2 moveAmount, IReadOnlyCollection<CircuitBoxNode> moveables)
{
if (Locked) { return; }
if (IsLocked()) { return; }
var ids = ImmutableArray.CreateBuilder<ushort>();
var ios = ImmutableArray.CreateBuilder<CircuitBoxInputOutputNode.Type>();
var labelIds = ImmutableArray.CreateBuilder<ushort>();
@@ -265,7 +265,7 @@ namespace Barotrauma.Items.Components
public void AddComponent(ItemPrefab prefab, Vector2 pos)
{
if (Locked) { return; }
if (IsLocked()) { return; }
if (GameMain.NetworkMember is null)
{
ItemPrefab resource;
@@ -292,7 +292,7 @@ namespace Barotrauma.Items.Components
public void RenameLabel(CircuitBoxLabelNode label, Color color, NetLimitedString header, NetLimitedString body)
{
if (Locked) { return; }
if (IsLocked()) { return; }
if (GameMain.NetworkMember is null)
{
label.EditText(header, body);
@@ -316,7 +316,7 @@ namespace Barotrauma.Items.Components
public void ResizeNode(CircuitBoxNode node, CircuitBoxResizeDirection dir, Vector2 amount)
{
if (Locked) { return; }
if (IsLocked()) { return; }
var resize = node.ResizeBy(dir, amount);
if (GameMain.NetworkMember is null)
{
@@ -341,7 +341,7 @@ namespace Barotrauma.Items.Components
public void AddLabel(Vector2 pos)
{
if (Locked) { return; }
if (IsLocked()) { return; }
if (GameMain.NetworkMember is null)
{
AddLabelInternal(ICircuitBoxIdentifiable.FindFreeID(Labels), GUIStyle.Blue, pos, CircuitBoxLabelNode.DefaultHeaderText, NetLimitedString.Empty);
@@ -353,7 +353,7 @@ namespace Barotrauma.Items.Components
public void RemoveLabel(IReadOnlyCollection<CircuitBoxLabelNode> labels)
{
if (Locked) { return; }
if (IsLocked()) { return; }
if (!labels.Any()) { return; }
var ids = labels.Select(static n => n.ID).ToImmutableArray();

View File

@@ -14,6 +14,8 @@ namespace Barotrauma.Items.Components
private bool readingNetworkEvent;
private GUIComponent insufficientPowerWarning;
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
public override bool RecreateGUIOnResolutionChange => true;
@@ -40,7 +42,7 @@ namespace Barotrauma.Items.Components
float elementSize = Math.Min(1.0f / visibleElements.Count(), 1);
foreach (CustomInterfaceElement ciElement in visibleElements)
{
if (ciElement.HasPropertyName)
if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.Number or CustomInterfaceElement.InputTypeOption.Text)
{
var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform), isHorizontal: true)
{
@@ -49,7 +51,7 @@ namespace Barotrauma.Items.Components
};
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform),
TextManager.Get(ciElement.Label).Fallback(ciElement.Label));
if (!ciElement.IsNumberInput)
if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.Text)
{
var textBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), ciElement.Signal, style: "GUITextBoxNoIcon")
{
@@ -149,7 +151,7 @@ namespace Barotrauma.Items.Components
}
}
}
else if (ciElement.ContinuousSignal)
else if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.TickBox)
{
var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform)
{
@@ -175,7 +177,7 @@ namespace Barotrauma.Items.Components
tickBox.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
uiElements.Add(tickBox);
}
else
else if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.Button)
{
var btn = new GUIButton(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform),
TextManager.Get(ciElement.Label).Fallback(ciElement.Label), style: "DeviceButton")
@@ -203,6 +205,16 @@ namespace Barotrauma.Items.Components
uiElements.Add(btn);
}
}
if (ShowInsufficientPowerWarning)
{
insufficientPowerWarning = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), GuiFrame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { MinSize = new Point(0, GUI.IntScale(30)) },
TextManager.Get("SteeringNoPowerTip"), font: GUIStyle.Font, wrap: true, style: "GUIToolTip", textAlignment: Alignment.Center)
{
AutoScaleHorizontal = true,
Visible = false
};
}
}
public override void CreateEditingHUD(SerializableEntityEditor editor)
@@ -253,7 +265,20 @@ namespace Barotrauma.Items.Components
{
if (uiElement.UserData is not CustomInterfaceElement element) { continue; }
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0);
if (visible) { visibleElementCount++; }
if (visible)
{
visibleElementCount++;
if (element.GetValueInterval > 0.0f)
{
element.GetValueTimer -= deltaTime;
if (element.GetValueTimer <= 0.0f)
{
SetSignalToPropertyValue(element);
UpdateSignalProjSpecific(uiElement);
element.GetValueTimer = element.GetValueInterval;
}
}
}
if (uiElement.Visible != visible)
{
uiElement.Visible = visible;
@@ -274,6 +299,11 @@ namespace Barotrauma.Items.Components
GuiFrame.Visible = visibleElementCount > 0;
uiElementContainer.Recalculate();
}
if (insufficientPowerWarning != null)
{
insufficientPowerWarning.Visible = item.GetComponents<Powered>().Any(p => p.PowerConsumption > 0.0f && p.Voltage < p.MinVoltage);
}
}
partial void UpdateLabelsProjSpecific()
@@ -336,22 +366,32 @@ namespace Barotrauma.Items.Components
if (signals == null) { return; }
for (int i = 0; i < signals.Length && i < uiElements.Count; i++)
{
string signal = customInterfaceElementList[i].Signal;
if (uiElements[i] is GUITextBox tb)
UpdateSignalProjSpecific(uiElements[i]);
}
}
private void UpdateSignalProjSpecific(GUIComponent uiElement)
{
if (uiElement.UserData is not CustomInterfaceElement element) { return; }
string signal = element.Signal;
if (uiElement is GUITextBox tb)
{
tb.Text = Screen.Selected is { IsEditor: true } ?
signal :
TextManager.Get(signal).Fallback(signal).Value;
}
else if (uiElement is GUINumberInput ni)
{
if (ni.InputType == NumberType.Int)
{
tb.Text = Screen.Selected is { IsEditor: true } ?
signal :
TextManager.Get(signal).Fallback(signal).Value;
}
else if (uiElements[i] is GUINumberInput ni)
{
if (ni.InputType == NumberType.Int)
{
int.TryParse(signal, out int value);
ni.IntValue = value;
}
int.TryParse(signal, out int value);
ni.IntValue = value;
}
}
else if (uiElement is GUITickBox tickBox)
{
tickBox.Selected = signal.Equals("true", StringComparison.OrdinalIgnoreCase);
}
}
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
@@ -360,14 +400,9 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
var element = customInterfaceElementList[i];
if (element.HasPropertyName)
switch (element.InputType)
{
if (!element.IsNumberInput)
{
msg.WriteString(((GUITextBox)uiElements[i]).Text);
}
else
{
case CustomInterfaceElement.InputTypeOption.Number:
switch (element.NumberType)
{
case NumberType.Float:
@@ -378,15 +413,16 @@ namespace Barotrauma.Items.Components
msg.WriteString(((GUINumberInput)uiElements[i]).IntValue.ToString());
break;
}
}
}
else if (element.ContinuousSignal)
{
msg.WriteBoolean(((GUITickBox)uiElements[i]).Selected);
}
else
{
msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
break;
case CustomInterfaceElement.InputTypeOption.Text:
msg.WriteString(((GUITextBox)uiElements[i]).Text);
break;
case CustomInterfaceElement.InputTypeOption.TickBox:
msg.WriteBoolean(((GUITickBox)uiElements[i]).Selected);
break;
case CustomInterfaceElement.InputTypeOption.Button:
msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
break;
}
}
}
@@ -399,15 +435,10 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
var element = customInterfaceElementList[i];
if (element.HasPropertyName)
switch (element.InputType)
{
string newValue = msg.ReadString();
if (!element.IsNumberInput)
{
TextChanged(element, newValue);
}
else
{
case CustomInterfaceElement.InputTypeOption.Number:
string newValue = msg.ReadString();
switch (element.NumberType)
{
case NumberType.Int when int.TryParse(newValue, out int value):
@@ -417,20 +448,23 @@ namespace Barotrauma.Items.Components
ValueChanged(element, value);
break;
}
}
}
else
{
bool elementState = msg.ReadBoolean();
if (element.ContinuousSignal)
{
((GUITickBox)uiElements[i]).Selected = elementState;
TickBoxToggled(element, elementState);
}
else if (elementState)
{
ButtonClicked(element);
}
break;
case CustomInterfaceElement.InputTypeOption.Text:
string newTextValue = msg.ReadString();
TextChanged(element, newTextValue);
break;
case CustomInterfaceElement.InputTypeOption.TickBox:
bool tickBoxState = msg.ReadBoolean();
((GUITickBox)uiElements[i]).Selected = tickBoxState;
TickBoxToggled(element, tickBoxState);
break;
case CustomInterfaceElement.InputTypeOption.Button:
bool buttonState = msg.ReadBoolean();
if (buttonState)
{
ButtonClicked(element);
}
break;
}
}

View File

@@ -106,7 +106,7 @@ namespace Barotrauma.Items.Components
private static int? selectedNodeIndex;
private static int? highlightedNodeIndex;
[Serialize(0.3f, IsPropertySaveable.No)]
[Serialize(0.3f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f, DecimalCount = 2)]
public float Width
{
get;

View File

@@ -175,6 +175,8 @@ namespace Barotrauma.Items.Components
{
if (character == null) { return; }
base.DrawHUD(spriteBatch, character);
if (OverlayColor.A > 0)
{
GUIStyle.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
@@ -206,44 +208,51 @@ namespace Barotrauma.Items.Components
if (ThermalGoggles)
{
spriteBatch.End();
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.Parameters["blurDistance"].SetValue(0.01f + MathF.Sin(thermalEffectState) * 0.005f);
GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect);
Entity refEntity = equipper;
if (!isEquippable || refEntity == null)
{
refEntity = item;
}
DrawThermalOverlay(spriteBatch, refEntity, character, OverlayColor, Range, thermalEffectState, ShowDeadCharacters);
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 = GUIStyle.UIThermalGlow.Value.Sprite;
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.Mass < 0.5f && limb != c.AnimController.MainLimb) { 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));
}
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
}
}
public static void DrawThermalOverlay(SpriteBatch spriteBatch, Entity refEntity, Character user, Color overlayColor, float range, float effectState, bool showDeadCharacters)
{
spriteBatch.End();
float colorIntensityBase = 0.5f; //Multiplies the overlay color by this amount, the higher the value, the more bright/vibrant the color.
float colorIntensityVariance = 0.05f; //The variance of the pulse effect affecting the color's brightness/vibrance
GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(overlayColor.ToVector4() * (colorIntensityBase + MathF.Sin(effectState) * colorIntensityVariance));
GameMain.LightManager.SolidColorEffect.CurrentTechnique = GameMain.LightManager.SolidColorEffect.Techniques["SolidColorBlur"];
GameMain.LightManager.SolidColorEffect.Parameters["blurDistance"].SetValue(0.01f + MathF.Sin(effectState) * 0.005f);
GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect);
foreach (Character c in Character.CharacterList)
{
if (c == user || !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 = GUIStyle.UIThermalGlow.Value.Sprite;
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.Mass < 0.5f && limb != c.AnimController.MainLimb) { continue; }
float noise1 = PerlinNoise.GetPerlin((effectState + limb.Params.ID + c.ID) * 0.01f, (effectState + limb.Params.ID + c.ID) * 0.02f);
float noise2 = PerlinNoise.GetPerlin((effectState + limb.Params.ID + c.ID) * 0.01f, (effectState + 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));
}
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
}
private void DrawCharacterInfo(SpriteBatch spriteBatch, Character target, float alpha = 1.0f)
{
Vector2 hudPos = GameMain.GameScreen.Cam.WorldToScreen(target.DrawPosition);

View File

@@ -1,12 +1,28 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma.Items.Components
{
partial class TriggerComponent : ItemComponent, IServerSerializable
partial class TriggerComponent : ItemComponent, IServerSerializable, IDrawableComponent
{
public Vector2 DrawSize =>
Vector2.One *
(Radius > 0.0f ? Radius * 2 : Math.Max(Width, Height));
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (editing)
{
PhysicsBody.DebugDraw(spriteBatch, Color.LightGray * 0.7f);
}
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
CurrentForceFluctuation = msg.ReadRangedSingle(0.0f, 1.0f, 8);
}
}
}

View File

@@ -385,17 +385,20 @@ namespace Barotrauma.Items.Components
if (item.Condition > 0.0f || !HideBarrelWhenBroken)
{
railSprite?.Draw(spriteBatch,
var currentRailSprite = item.Condition <= 0.0f && railSpriteBroken != null ? railSpriteBroken : railSprite;
var currentBarrelSprite = item.Condition <= 0.0f && barrelSpriteBroken != null ? barrelSpriteBroken : barrelSprite;
currentRailSprite?.Draw(spriteBatch,
drawPos,
overrideColor ?? item.SpriteColor,
Rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
SpriteEffects.None, item.SpriteDepth + (currentRailSprite.Depth - item.Sprite.Depth));
barrelSprite?.Draw(spriteBatch,
currentBarrelSprite?.Draw(spriteBatch,
drawPos - GetRecoilOffset() * item.Scale,
overrideColor ?? item.SpriteColor,
Rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
SpriteEffects.None, item.SpriteDepth + (currentBarrelSprite.Depth - item.Sprite.Depth));
float chargeRatio = currentChargeTime / MaxChargeTime;
@@ -702,12 +705,14 @@ namespace Barotrauma.Items.Components
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
base.DrawHUD(spriteBatch, character);
if (HudTint.A > 0)
{
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
new Color(HudTint.R, HudTint.G, HudTint.B) * (HudTint.A / 255.0f), true);
}
GetAvailablePower(out float batteryCharge, out float batteryCapacity);
List<Item> availableAmmo = new List<Item>();

View File

@@ -1359,7 +1359,7 @@ namespace Barotrauma
if (selectedInventory.GetItemAt(slotIndex)?.OwnInventory?.Container is { } container &&
container.Inventory.CanBePut(item))
{
if (!container.AllowDragAndDrop || !container.AllowAccess)
if (!container.AllowDragAndDrop || !container.IsAccessible())
{
allowCombine = false;
}

View File

@@ -130,7 +130,7 @@ namespace Barotrauma
}
}
public float GetDrawDepth()
public override float GetDrawDepth()
{
return GetDrawDepth(SpriteDepth + DrawDepthOffset, Sprite);
}
@@ -287,7 +287,7 @@ namespace Barotrauma
}
else
{
int padding = 100;
int padding = 0;
RectangleF boundingBox = GetTransformedQuad().BoundingAxisAlignedRectangle;
Vector2 min = new Vector2(-boundingBox.Width / 2 - padding, -boundingBox.Height / 2 - padding);
@@ -302,11 +302,11 @@ namespace Barotrauma
}
foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites)
{
float scale = decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
min.X = Math.Min(-decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * scale, min.X);
min.Y = Math.Min(-decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale, min.Y);
max.X = Math.Max(decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * scale, max.X);
max.Y = Math.Max(decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale, max.Y);
Vector2 scale = decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
min.X = Math.Min(-decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * scale.X, min.X);
min.Y = Math.Min(-decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale.Y, min.Y);
max.X = Math.Max(decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * scale.X, max.X);
max.Y = Math.Max(decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale.Y, max.Y);
}
cachedVisibleExtents = extents = new Rectangle(min.ToPoint(), max.ToPoint());
}
@@ -316,6 +316,9 @@ namespace Barotrauma
if (worldPosition.X + extents.X > worldView.Right || worldPosition.X + extents.Width < worldView.X) { return false; }
if (worldPosition.Y + extents.Height < worldView.Y - worldView.Height || worldPosition.Y + extents.Y > worldView.Y) { return false; }
if (extents.Width * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
if (extents.Height * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
return true;
}
@@ -490,7 +493,7 @@ namespace Barotrauma
}
}
var head = holdable.Picker.AnimController.GetLimb(LimbType.Head);
if (head != null)
if (head?.Sprite != null)
{
//ensure the holdable item is always drawn in front of the head no matter what the wearables or whatnot do with the sprite depths
depth =
@@ -523,8 +526,8 @@ namespace Barotrauma
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -RotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Origin,
rotation, decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
}
@@ -680,7 +683,7 @@ namespace Barotrauma
offset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
}
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(drawPos.X + offset.X, -(drawPos.Y + offset.Y)), decorativeSpriteColor, origin,
-rotation + spriteRotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffects,
-rotation + spriteRotation, decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
}
@@ -1069,12 +1072,18 @@ namespace Barotrauma
foreach (RelatedItem relatedItem in requiredItems)
{
//TODO: add to localization
var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)),
relatedItem.Type.ToString() + " required", font: GUIStyle.SmallFont)
TextManager.Get($"{relatedItem.Type}.required").Fallback($"{relatedItem.Type} required"), font: GUIStyle.SmallFont)
{
Padding = new Vector4(10.0f, 0.0f, 10.0f, 0.0f)
};
var tooltip = TextManager.Get($"{relatedItem.Type}.required.tooltip").Fallback(LocalizedString.EmptyString);
if (!tooltip.IsNullOrWhiteSpace())
{
textBlock.ToolTip = tooltip;
}
textBlock.RectTransform.IsFixedSize = true;
componentEditor.AddCustomContent(textBlock, 1);
@@ -2074,6 +2083,17 @@ namespace Barotrauma
}
}
break;
case EventType.SwapItem:
ushort newId = msg.ReadUInt16();
uint prefabUintId = msg.ReadUInt32();
ItemPrefab newPrefab = ItemPrefab.Prefabs.FirstOrDefault(p => p.UintIdentifier == prefabUintId);
if (newPrefab is null)
{
DebugConsole.ThrowError($"Error while reading {EventType.SwapItem} message: could not find an item prefab with the hash {prefabUintId}.");
break;
}
ReplaceFromNetwork(newPrefab, newId);
break;
default:
throw new Exception($"Malformed incoming item event: unsupported event type {eventType}");
}
@@ -2171,6 +2191,18 @@ namespace Barotrauma
}
}
//if the item is outside the level, but not in a sub, it implies the item is inside a sub server-side but the client failed to properly move it
// -> let's correct that by finding the correct sub
if (Level.IsPositionAboveLevel(WorldPosition) && Submarine == null)
{
var newSub = Submarine.FindContainingInLocalCoordinates(ConvertUnits.ToDisplayUnits(body.SimPosition), inflate: 0.0f);
if (newSub != null)
{
Submarine = newSub;
FindHull();
}
}
Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition);
rect.X = (int)(displayPos.X - rect.Width / 2.0f);
rect.Y = (int)(displayPos.Y + rect.Height / 2.0f);
@@ -2329,15 +2361,15 @@ namespace Barotrauma
ownerSheetIndex = (x, y);
}
bool tagsChanged = msg.ReadBoolean();
bool tagsChanged = msg.ReadBoolean();
string tags = "";
if (tagsChanged)
{
HashSet<Identifier> addedTags = msg.ReadString().Split(',').ToIdentifiers().ToHashSet();
HashSet<Identifier> removedTags = msg.ReadString().Split(',').ToIdentifiers().ToHashSet();
HashSet<Identifier> addedTags = msg.ReadString().ToIdentifiers().ToHashSet();
HashSet<Identifier> removedTags = msg.ReadString().ToIdentifiers().ToHashSet();
if (itemPrefab != null)
{
tags = string.Join(',',itemPrefab.Tags.Where(t => !removedTags.Contains(t)).Concat(addedTags));
tags = string.Join(',', itemPrefab.Tags.Where(t => !removedTags.Contains(t)).Union(addedTags));
}
}

View File

@@ -20,7 +20,8 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView)
{
return Screen.Selected == GameMain.SubEditorScreen || GameMain.DebugDraw;
if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw) { return false; }
return base.IsVisible(worldView);
}
public override void Draw(SpriteBatch sb, bool editing, bool back = true)

View File

@@ -81,16 +81,12 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView)
{
if (BallastFlora != null) { return true; }
if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw)
{
if (decals.Count == 0 && paintAmount < minimumPaintAmountToDraw) { return false; }
Rectangle worldRect = WorldRect;
if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) { return false; }
if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) { return false; }
}
return true;
return base.IsVisible(worldView);
}
public override bool IsMouseOn(Vector2 position)
@@ -103,12 +99,31 @@ namespace Barotrauma
private GUIComponent CreateEditingHUD(bool inGame = false)
{
int heightScaled = GUI.IntScale(20);
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null)
{
CanTakeKeyBoardFocus = false
};
new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUIStyle.LargeFont);
var hullEditor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUIStyle.LargeFont);
if (!inGame)
{
if (Linkable)
{
var linkText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, heightScaled), isFixedSize: true), TextManager.Get("HoldToLink"), font: GUIStyle.SmallFont);
var hullLinkText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, heightScaled), isFixedSize: true), TextManager.Get("hulllinkinfo"), font: GUIStyle.SmallFont);
var itemsText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, heightScaled), isFixedSize: true), TextManager.Get("AllowedLinks"), font: GUIStyle.SmallFont);
LocalizedString allowedItems = AllowedLinks.None() ? TextManager.Get("None") : string.Join(", ", AllowedLinks);
itemsText.Text = TextManager.AddPunctuation(':', itemsText.Text, allowedItems);
hullEditor.AddCustomContent(linkText, 1);
hullEditor.AddCustomContent(hullLinkText, 2);
hullEditor.AddCustomContent(itemsText, 3);
linkText.TextColor = GUIStyle.Orange;
hullLinkText.TextColor = GUIStyle.Orange;
itemsText.TextColor = GUIStyle.Orange;
}
}
PositionEditingHUD();

View File

@@ -47,62 +47,75 @@ namespace Barotrauma
if (renderer == null) { return; }
renderer.DrawDebugOverlay(spriteBatch, cam);
if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
if (GameMain.DebugDraw)
{
foreach (InterestingPosition pos in PositionsOfInterest)
if (Screen.Selected.Cam.Zoom > 0.1f)
{
Color color = Color.Yellow;
if (pos.PositionType == PositionType.Cave || pos.PositionType == PositionType.AbyssCave)
foreach (InterestingPosition pos in PositionsOfInterest)
{
color = Color.DarkOrange;
}
else if (pos.PositionType == PositionType.Ruin)
{
color = Color.LightGray;
}
if (!pos.IsValid)
{
color = Color.Red;
}
Color color = Color.Yellow;
if (pos.PositionType == PositionType.Cave || pos.PositionType == PositionType.AbyssCave)
{
color = Color.DarkOrange;
}
else if (pos.PositionType == PositionType.Ruin)
{
color = Color.LightGray;
}
if (!pos.IsValid)
{
color = Color.Red;
}
GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true);
GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true);
}
foreach (RuinGeneration.Ruin ruin in Ruins)
{
Rectangle ruinArea = ruin.Area;
ruinArea.Y = -ruinArea.Y - ruinArea.Height;
GUI.DrawRectangle(spriteBatch, ruinArea, Color.DarkSlateBlue, false, 0, 5);
}
}
foreach (RuinGeneration.Ruin ruin in Ruins)
{
Rectangle ruinArea = ruin.Area;
ruinArea.Y = -ruinArea.Y - ruinArea.Height;
GUI.DrawRectangle(spriteBatch, ruinArea, Color.DarkSlateBlue, false, 0, 5);
}
foreach (var positions in wreckPositions.Values)
float zoomFactor = MathHelper.Lerp(20, 1, MathUtils.InverseLerp(Screen.Selected.Cam.MinZoom, Screen.Selected.Cam.DefaultZoom, Screen.Selected.Cam.Zoom));
foreach ((string debugInfo, List<Vector2> positions) in positionHistory)
{
for (int i = 0; i < positions.Count; i++)
{
float t = (i + 1) / (float)positions.Count;
float multiplier = MathHelper.Lerp(0, 1, t);
float multiplier = MathHelper.Lerp(0.1f, 1, t);
Color color = Color.Red * multiplier;
var pos = positions[i];
pos.Y = -pos.Y;
var size = new Vector2(100);
GUI.DrawRectangle(spriteBatch, pos - size / 2, size, color, thickness: 10);
var size = new Vector2(200);
if (i == 0)
{
GUI.DrawRectangle(spriteBatch, pos - size, size * 2, Color.Red, thickness: 2 * zoomFactor);
GUI.DrawString(spriteBatch, pos - new Vector2(10, 20), debugInfo, Color.White, font: GUIStyle.LargeFont, forceUpperCase: ForceUpperCase.Yes);
}
if (i < positions.Count - 1)
{
if (i > 0)
{
GUI.DrawRectangle(spriteBatch, pos - size / 2, size, Color.Red, isFilled: true);
}
var nextPos = positions[i + 1];
nextPos.Y = -nextPos.Y;
GUI.DrawLine(spriteBatch, pos, nextPos, color, width: 10);
GUI.DrawLine(spriteBatch, pos, nextPos, color, width: 4 * zoomFactor);
}
}
}
foreach (var rects in blockedRects.Values)
foreach ((Submarine sub, List<Rectangle> rects) in blockedRects)
{
foreach (var rect in rects)
foreach (Rectangle t in rects)
{
Rectangle newRect = rect;
Rectangle newRect = t;
newRect.Y = -newRect.Y;
GUI.DrawRectangle(spriteBatch, newRect, Color.Red, thickness: 5);
GUI.DrawRectangle(spriteBatch, newRect, Color.Red * 0.1f, isFilled: true);
GUI.DrawString(spriteBatch, newRect.Center.ToVector2(), $"{sub.Info.Name}", Color.White, font: GUIStyle.LargeFont, forceUpperCase: ForceUpperCase.Yes);
}
}
}

View File

@@ -1,4 +1,5 @@
using Barotrauma.Extensions;
using Barotrauma.Extensions;
using Barotrauma.Particles;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -239,7 +240,8 @@ namespace Barotrauma
public void DrawBackground(SpriteBatch spriteBatch, Camera cam,
LevelObjectManager backgroundSpriteManager = null,
BackgroundCreatureManager backgroundCreatureManager = null)
BackgroundCreatureManager backgroundCreatureManager = null,
ParticleManager particleManager = null)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap);
@@ -277,7 +279,7 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null,
cam.Transform);
cam.Transform);
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, cam);
if (cam.Zoom > 0.05f)
@@ -321,6 +323,9 @@ namespace Barotrauma
color: level.GenerationParams.WaterParticleColor * alpha, textureScale: new Vector2(texScale));
}
}
GameMain.ParticleManager?.Draw(spriteBatch, inWater: true, inSub: false, ParticleBlendState.AlphaBlend, background: true);
spriteBatch.End();
RenderWalls(GameMain.Instance.GraphicsDevice, cam);
@@ -465,7 +470,8 @@ namespace Barotrauma
var wallList = i == 0 ? level.ExtraWalls : level.UnsyncedExtraWalls;
foreach (LevelWall wall in wallList)
{
if (!(wall is DestructibleLevelWall destructibleWall) || destructibleWall.Destroyed) { continue; }
if (wall is not DestructibleLevelWall destructibleWall || destructibleWall.Destroyed) { continue; }
if (!wall.IsVisible(cam.WorldView)) { continue; }
wallCenterEffect.Texture = level.GenerationParams.DestructibleWallSprite?.Texture ?? level.GenerationParams.WallSprite.Texture;
wallCenterEffect.World = wall.GetTransform() * transformMatrix;
@@ -521,6 +527,7 @@ namespace Barotrauma
foreach (LevelWall wall in wallList)
{
if (wall is DestructibleLevelWall) { continue; }
if (!wall.IsVisible(cam.WorldView)) { continue; }
//TODO: use LevelWallVertexBuffers for extra walls as well
wallCenterEffect.World = wall.GetTransform() * transformMatrix;
wallCenterEffect.Alpha = wall.Alpha;

View File

@@ -1,8 +1,10 @@
using FarseerPhysics;
using Barotrauma.Extensions;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
@@ -41,5 +43,23 @@ namespace Barotrauma
level.GenerationParams.WallEdgeSprite.Texture,
color);
}
public bool IsVisible(Rectangle worldView)
{
RectangleF worldViewInSimUnits = new RectangleF(
ConvertUnits.ToSimUnits(worldView.Location.ToVector2()),
ConvertUnits.ToSimUnits(worldView.Size.ToVector2()));
foreach (var fixture in Body.FixtureList)
{
fixture.GetAABB(out var aabb, 0);
Vector2 lowerBound = aabb.LowerBound + Body.Position;
if (lowerBound.X > worldViewInSimUnits.Right || lowerBound.Y > worldViewInSimUnits.Y) { continue; }
Vector2 upperBound = aabb.UpperBound + Body.Position;
if (upperBound.X < worldViewInSimUnits.X || upperBound.Y < worldViewInSimUnits.Y - worldViewInSimUnits.Height) { continue; }
return true;
}
return false;
}
}
}

View File

@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
@@ -13,6 +12,7 @@ namespace Barotrauma.Lights
public readonly Submarine Submarine;
public HashSet<ConvexHull> IsHidden = new HashSet<ConvexHull>();
public HashSet<ConvexHull> HasBeenVisible = new HashSet<ConvexHull>();
public readonly List<ConvexHull> List = new List<ConvexHull>();
public ConvexHullList(Submarine submarine)

View File

@@ -174,7 +174,7 @@ namespace Barotrauma.Lights
}
private readonly List<LightSource> activeLights = new List<LightSource>(capacity: 100);
private readonly List<LightSource> activeLightsWithLightVolume = new List<LightSource>(capacity: 100);
private readonly List<LightSource> activeShadowCastingLights = new List<LightSource>(capacity: 100);
public static int ActiveLightCount { get; private set; }
@@ -279,7 +279,7 @@ namespace Barotrauma.Lights
}
//above the top boundary of the level (in an inactive respawn shuttle?)
if (Level.Loaded != null && light.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (Level.IsPositionAboveLevel(light.WorldPosition)) { continue; }
float range = light.LightSourceParams.TextureRange;
if (light.LightSprite != null)
@@ -315,19 +315,20 @@ namespace Barotrauma.Lights
}
//find the lights with an active light volume
activeLightsWithLightVolume.Clear();
activeShadowCastingLights.Clear();
foreach (var activeLight in activeLights)
{
if (!activeLight.CastShadows) { continue; }
if (activeLight.Range < 1.0f || activeLight.Color.A < 1 || activeLight.CurrentBrightness <= 0.0f) { continue; }
activeLightsWithLightVolume.Add(activeLight);
activeShadowCastingLights.Add(activeLight);
}
//remove some lights with a light volume if there's too many of them
if (activeLightsWithLightVolume.Count > GameSettings.CurrentConfig.Graphics.VisibleLightLimit && Screen.Selected is { IsEditor: false })
if (activeShadowCastingLights.Count > GameSettings.CurrentConfig.Graphics.VisibleLightLimit && Screen.Selected is { IsEditor: false })
{
for (int i = GameSettings.CurrentConfig.Graphics.VisibleLightLimit; i < activeLightsWithLightVolume.Count; i++)
for (int i = GameSettings.CurrentConfig.Graphics.VisibleLightLimit; i < activeShadowCastingLights.Count; i++)
{
activeLights.Remove(activeLightsWithLightVolume[i]);
activeLights.Remove(activeShadowCastingLights[i]);
}
}
activeLights.Sort((l1, l2) => l1.LastRecalculationTime.CompareTo(l2.LastRecalculationTime));
@@ -827,7 +828,7 @@ namespace Barotrauma.Lights
public void ClearLights()
{
activeLights.Clear();
activeLightsWithLightVolume.Clear();
activeShadowCastingLights.Clear();
lights.Clear();
}
}

View File

@@ -1,4 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -245,7 +245,7 @@ namespace Barotrauma.Lights
}
set
{
if (!needsRecalculation && value)
if (value)
{
foreach (ConvexHullList chList in convexHullsInRange)
{
@@ -550,7 +550,6 @@ namespace Barotrauma.Lights
chList.List.Clear();
foreach (var convexHull in fullChList.List)
{
if (!convexHull.Enabled) { continue; }
if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, convexHull.BoundingBox)) { continue; }
if (lightSourceParams.Directional)
{
@@ -572,6 +571,7 @@ namespace Barotrauma.Lights
chList.List.Add(convexHull);
}
chList.IsHidden.RemoveWhere(ch => !chList.List.Contains(ch));
chList.HasBeenVisible.RemoveWhere(ch => !chList.List.Contains(ch));
HullsUpToDate.Add(sub);
}
@@ -604,7 +604,8 @@ namespace Barotrauma.Lights
foreach (var ch in chList.List)
{
if (ch.LastVertexChangeTime > LastRecalculationTime && !chList.IsHidden.Contains(ch))
if (ch.LastVertexChangeTime > LastRecalculationTime &&
(!chList.IsHidden.Contains(ch) || chList.HasBeenVisible.Contains(ch)))
{
NeedsRecalculation = true;
break;
@@ -712,8 +713,8 @@ namespace Barotrauma.Lights
{
foreach (ConvexHull hull in chList.List)
{
if (hull.IsInvalid) { continue; }
if (!chList.IsHidden.Contains(hull))
if (hull.IsInvalid || !hull.Enabled) { continue; }
if (!chList.IsHidden.Contains(hull) || chList.HasBeenVisible.Contains(hull))
{
//find convexhull segments that are close enough and facing towards the light source
lock (mutex)
@@ -732,7 +733,17 @@ namespace Barotrauma.Lights
}
foreach (ConvexHull hull in chList.List)
{
chList.IsHidden.Add(hull);
if (!hull.Enabled)
{
//if the hull is not enabled, we cannot determine if it's visible or hidden from the point of view of the light source
//so let's not mark it as hidden, but instead consider it as something that has been visible, so we know to recalculate if/when it becomes enabled again
chList.IsHidden.Remove(hull);
chList.HasBeenVisible.Add(hull);
continue;
}
//mark convex hulls as hidden at this point, they're removed if we find any of the segments to be visible
chList.IsHidden.Add(hull);
}
}
@@ -1411,14 +1422,7 @@ namespace Barotrauma.Lights
{
if (conditionals.None()) { return; }
if (conditionalTarget == null) { return; }
if (logicalOperator == PropertyConditional.LogicalOperatorType.And)
{
Enabled = conditionals.All(c => c.Matches(conditionalTarget));
}
else
{
Enabled = conditionals.Any(c => c.Matches(conditionalTarget));
}
Enabled = PropertyConditional.CheckConditionals(conditionalTarget, conditionals, logicalOperator);
}
public void DebugDrawVertices(SpriteBatch spriteBatch)
@@ -1501,6 +1505,7 @@ namespace Barotrauma.Lights
foreach (var convexHullList in convexHullsInRange)
{
convexHullList.IsHidden.Remove(visibleConvexHull);
convexHullList.HasBeenVisible.Add(visibleConvexHull);
}
}

View File

@@ -923,23 +923,25 @@ namespace Barotrauma
if (GameMain.DebugDraw)
{
Vector2 dPos = pos;
//move the debug texts upwards so they don't go under the info panel that appears when highlighted
Vector2 dPos = pos + new Vector2(15, -100);
if (location == HighlightedLocation)
{
dPos.Y -= 80;
GUI.DrawString(spriteBatch, dPos + new Vector2(15, 32), "Faction: " + (location.Faction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
GUI.DrawString(spriteBatch, dPos + new Vector2(15, 50), "Secondary Faction: " + (location.SecondaryFaction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
dPos.Y += 48;
GUI.DrawString(spriteBatch, dPos, "Faction: " + (location.Faction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
GUI.DrawString(spriteBatch, dPos + new Vector2(0, 18), "Secondary Faction: " + (location.SecondaryFaction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
dPos.Y += 50;
if (PlayerInput.KeyDown(Keys.LeftShift))
{
GUI.DrawString(spriteBatch, new Vector2(150,150), "Dist: " +
GUI.DrawString(spriteBatch, new Vector2(150, 150), "Dist: " +
GetDistanceToClosestLocationOrConnection(CurrentLocation, int.MaxValue, loc => loc == location), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
}
GUI.DrawString(spriteBatch, dPos, $"Difficulty: {location.LevelData.Difficulty.FormatSingleDecimal()}",
ToolBox.GradientLerp(location.LevelData.Difficulty / 100.0f, GUIStyle.Blue, GUIStyle.Yellow, GUIStyle.Red), Color.Black * 0.8f, 4, font: GUIStyle.SmallFont);
dPos.Y += 25;
GUI.DrawString(spriteBatch, dPos, $"Biome: {location.LevelData.Biome.DisplayName} ({location.LevelData.GenerationParams.Identifier})", Color.White, Color.Black, font: GUIStyle.SmallFont);
}
dPos.Y += 48;
GUI.DrawString(spriteBatch, dPos, $"Difficulty: {location.LevelData.Difficulty.FormatSingleDecimal()}", Color.White, Color.Black * 0.8f, 4, font: GUIStyle.SmallFont);
}
}
}
@@ -1196,7 +1198,9 @@ namespace Barotrauma
Vector2 center = rectCenter + (connection.CenterPos + viewOffset) * zoom;
if (viewArea.Contains(center) && connection.Biome != null)
{
GUI.DrawString(spriteBatch, center, (connection.LevelData?.GenerationParams?.Identifier ?? connection.Biome.Identifier) + " (" + connection.Difficulty.FormatSingleDecimal() + ")", Color.White);
GUI.DrawString(spriteBatch, center - Vector2.UnitX * 50,
$"{(connection.LevelData?.GenerationParams?.Identifier ?? connection.Biome.Identifier)} ({connection.Difficulty.FormatSingleDecimal()})",
ToolBox.GradientLerp(connection.Difficulty / 100.0f, GUIStyle.Blue, GUIStyle.Yellow, GUIStyle.Red), backgroundColor: Color.Black * 0.7f, font: GUIStyle.SmallFont);
}
}

View File

@@ -81,6 +81,16 @@ namespace Barotrauma
public virtual bool IsVisible(Rectangle worldView)
{
Rectangle worldRect = WorldRect;
if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) { return false; }
if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) { return false; }
//zoomed extremely far out -> no need to render
if (Screen.Selected.Cam.Zoom < 0.05f) { return false; }
if (worldRect.Width * Screen.Selected.Cam.Zoom < 1.0f ||
worldRect.Height * Screen.Selected.Cam.Zoom < 1.0f)
{
return false;
}
return true;
}
@@ -91,6 +101,8 @@ namespace Barotrauma
public virtual void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) { }
public virtual float GetDrawDepth() { return 0.0f; }
/// <summary>
/// A method that modifies the draw depth to prevent z-fighting between entities with the same sprite depth
/// </summary>
@@ -305,6 +317,15 @@ namespace Barotrauma
if (PlayerInput.IsCtrlDown())
{
HashSet<MapEntity> clones = Clone(SelectedList.ToList()).Where(c => c != null).ToHashSet();
if (clones.Count == 1)
{
if (clones.First() is WayPoint wayPoint && SelectedList.First() is WayPoint originalWaypoint && originalWaypoint.SpawnType == SpawnType.Path)
{
originalWaypoint.ConnectTo(wayPoint);
}
}
SelectedList = clones;
SelectedList.ForEach(c => c.Move(moveAmount));
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(clones), false));
@@ -1068,6 +1089,7 @@ namespace Barotrauma
}
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false, handleInventoryBehavior: false));
if (Screen.Selected is SubEditorScreen subEditor) { subEditor.ReconstructLayers(); }
}
/// <summary>

View File

@@ -73,9 +73,16 @@ namespace Barotrauma
}
Sound? existingSound = null;
if (roundSoundByPath.TryGetValue(filename.FullPath, out RoundSound? rs) && rs.Sound is { Disposed: false })
if (roundSoundByPath.TryGetValue(filename.FullPath, out RoundSound? rs))
{
existingSound = rs.Sound;
if (rs.Sound is { Disposed: false })
{
existingSound = rs.Sound;
}
else
{
roundSoundByPath.Remove(filename.FullPath);
}
}
if (existingSound is null)

View File

@@ -328,11 +328,11 @@ namespace Barotrauma
Vector2 max = new Vector2(worldRect.Right, worldRect.Y + worldRect.Height);
foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites)
{
float scale = decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
min.X = Math.Min(worldPos.X - decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * scale, min.X);
max.X = Math.Max(worldPos.X + decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * scale, max.X);
min.Y = Math.Min(worldPos.Y - decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale, min.Y);
max.Y = Math.Max(worldPos.Y + decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale, max.Y);
Vector2 scale = decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
min.X = Math.Min(worldPos.X - decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * scale.X, min.X);
max.X = Math.Max(worldPos.X + decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * scale.X, max.X);
min.Y = Math.Min(worldPos.Y - decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale.Y, min.Y);
max.Y = Math.Max(worldPos.Y + decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale.Y, max.Y);
}
Vector2 offset = GetCollapseEffectOffset();
min += offset;
@@ -341,6 +341,9 @@ namespace Barotrauma
if (min.X > worldView.Right || max.X < worldView.X) { return false; }
if (min.Y > worldView.Y || max.Y < worldView.Y - worldView.Height) { return false; }
Vector2 extents = max - min;
if (extents.X * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
if (extents.Y * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
return true;
}
@@ -368,7 +371,7 @@ namespace Barotrauma
return SpriteDepthOverrideIsSet ? SpriteOverrideDepth : Prefab.Sprite.Depth;
}
public float GetDrawDepth()
public override float GetDrawDepth()
{
return GetDrawDepth(GetRealDepth(), Prefab.Sprite);
}
@@ -560,7 +563,8 @@ namespace Barotrauma
pos: drawPos.FlipY(),
color: color,
rotate: rotation,
scale: decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale,
origin: decorativeSprite.Sprite.Origin,
scale: decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale,
spriteEffect: Prefab.Sprite.effects ^ SpriteEffects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - Prefab.Sprite.Depth), 0.999f));
}

View File

@@ -656,6 +656,7 @@ namespace Barotrauma
errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning",
("[doorcount]", doorLinks.ToString()),
("[freeconnectioncount]", (item.Connections[i].MaxWires - wireCount).ToString())).Value);
warnings.Add(SubEditorScreen.WarningType.InsufficientFreeConnectionsWarning);
break;
}
}
@@ -836,7 +837,7 @@ namespace Barotrauma
subBody.PositionBuffer.Insert(index, posInfo);
}
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
Identifier layerIdentifier = msg.ReadIdentifier();

View File

@@ -572,8 +572,9 @@ namespace Barotrauma
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX) { offset.X = -offset.X; }
if (flippedY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color,
rotationRad + rot, decorativeSprite.GetScale(0f) * scale, prefab.Sprite.effects,
float throwAway = 0.0f;
decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color, decorativeSprite.Sprite.Origin,
rotationRad + rot, decorativeSprite.GetScale(ref throwAway, 0f) * scale, prefab.Sprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f));
}
}

View File

@@ -15,7 +15,8 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView)
{
return Screen.Selected == GameMain.SubEditorScreen || GameMain.DebugDraw;
if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw) { return false; }
return base.IsVisible(worldView);
}
public override bool SelectableInEditor
@@ -92,6 +93,11 @@ namespace Barotrauma
if (sprite != null)
{
float spriteScale = iconSize / (float)sprite.SourceRect.Width;
if (Ladders == null && ConnectedDoor == null && ConnectedGap != null)
{
clr = Color.White;
spriteScale *= 1.5f;
}
sprite.Draw(spriteBatch, drawPos, clr, origin: sprite.size / 2, scale: spriteScale, depth: 0.001f);
sprite2?.Draw(spriteBatch, drawPos + sprite.size * spriteScale * 0.5f, clr, origin: sprite2.size / 2, scale: spriteScale, depth: 0.001f);
}
@@ -100,27 +106,39 @@ namespace Barotrauma
{
AssignedJob.Icon.Draw(spriteBatch, drawPos, AssignedJob.UIColor, scale: iconSize / (float)AssignedJob.Icon.SourceRect.Width * 0.8f, depth: 0.0f);
}
foreach (MapEntity e in linkedTo)
// alternate line drawing for when cloning the waypoint: line goes from current position to original position, where moving started
if (StartMovingPos != Vector2.Zero && SelectedList.Contains(this) && PlayerInput.IsCtrlDown())
{
GUI.DrawLine(spriteBatch,
drawPos,
new Vector2(e.DrawPosition.X, -e.DrawPosition.Y),
new Vector2(StartMovingPos.X, -StartMovingPos.Y),
(IsTraversable ? GUIStyle.Green : Color.Gray) * 0.7f, width: 5, depth: 0.002f);
}
else
{
foreach (MapEntity e in linkedTo)
{
GUI.DrawLine(spriteBatch,
drawPos,
new Vector2(e.DrawPosition.X, -e.DrawPosition.Y),
(IsTraversable ? GUIStyle.Green : Color.Gray) * 0.7f, width: 5, depth: 0.002f);
}
}
if (ConnectedGap != null)
{
GUI.DrawLine(spriteBatch,
drawPos,
new Vector2(ConnectedGap.DrawPosition.X, -ConnectedGap.DrawPosition.Y),
GUIStyle.Green * 0.5f, width: 1);
Color.White, width: 1);
}
if (Ladders != null)
{
GUI.DrawLine(spriteBatch,
drawPos,
new Vector2(Ladders.Item.DrawPosition.X, -Ladders.Item.DrawPosition.Y),
GUIStyle.Green * 0.5f, width: 1);
Color.White, width: 1);
}
var color = Color.WhiteSmoke;
@@ -419,6 +437,7 @@ namespace Barotrauma
jobDropDown.AddItem(TextManager.Get("Any"), null);
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
if (jobPrefab.HiddenJob) { continue; }
jobDropDown.AddItem(jobPrefab.Name, jobPrefab);
}
jobDropDown.SelectItem(AssignedJob);
@@ -432,7 +451,7 @@ namespace Barotrauma
};
propertyBox.OnTextChanged += (textBox, text) =>
{
tags = text.Split(',').ToIdentifiers().ToHashSet();
tags = text.ToIdentifiers().ToHashSet();
return true;
};
propertyBox.OnEnterPressed += (textBox, text) =>

View File

@@ -260,7 +260,7 @@ namespace Barotrauma.Networking
{
try
{
Directory.CreateDirectory(downloadFolder);
Directory.CreateDirectory(downloadFolder, catchUnauthorizedAccessExceptions: false);
}
catch (Exception e)
{
@@ -572,7 +572,7 @@ namespace Barotrauma.Networking
{
try
{
File.Delete(transfer.FilePath);
File.Delete(transfer.FilePath, catchUnauthorizedAccessExceptions: false);
}
catch (Exception e)
{

View File

@@ -7,9 +7,11 @@ using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.PerkBehaviors;
namespace Barotrauma.Networking
{
@@ -41,8 +43,9 @@ namespace Barotrauma.Networking
nameId++;
}
public void ForceNameAndJobUpdate()
public void ForceNameJobTeamUpdate()
{
// Triggers SendLobbyUpdate() which causes the server to call GameServer.ClientReadLobby()
nameId++;
}
@@ -137,13 +140,14 @@ namespace Barotrauma.Networking
}
}
public Client MyClient => ConnectedClients.FirstOrDefault(c => c.SessionId == SessionId);
public Option<int> Ping
{
get
{
Client selfClient = ConnectedClients.FirstOrDefault(c => c.SessionId == SessionId);
if (selfClient is null || selfClient.Ping == 0) { return Option<int>.None(); }
return Option<int>.Some(selfClient.Ping);
if (MyClient is null || MyClient.Ping == 0) { return Option<int>.None(); }
return Option<int>.Some(MyClient.Ping);
}
}
@@ -485,14 +489,13 @@ namespace Barotrauma.Networking
{
if (VoipCapture.Instance.LastEnqueueAudio > DateTime.Now - new TimeSpan(0, 0, 0, 0, milliseconds: 100))
{
var myClient = ConnectedClients.Find(c => c.SessionId == SessionId);
if (Screen.Selected == GameMain.NetLobbyScreen)
{
GameMain.NetLobbyScreen.SetPlayerSpeaking(myClient);
GameMain.NetLobbyScreen.SetPlayerSpeaking(MyClient);
}
else
{
GameMain.GameSession?.CrewManager?.SetClientSpeaking(myClient);
GameMain.GameSession?.CrewManager?.SetClientSpeaking(MyClient);
}
}
}
@@ -689,6 +692,16 @@ namespace Barotrauma.Networking
string subName = inc.ReadString();
string subHash = inc.ReadString();
bool hasEnemySub = inc.ReadBoolean();
string enemySubName = subName;
string enemySubHash = subHash;
if (hasEnemySub)
{
enemySubName = inc.ReadString();
enemySubHash = inc.ReadString();
}
bool usingShuttle = inc.ReadBoolean();
string shuttleName = inc.ReadString();
string shuttleHash = inc.ReadString();
@@ -709,8 +722,13 @@ namespace Barotrauma.Networking
bool readyToStart;
if (campaign == null && campaignID == 0)
{
readyToStart = GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList) &&
GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox);
readyToStart = GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList) &&
GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox);
if (hasEnemySub && !GameMain.NetLobbyScreen.TrySelectSub(enemySubName, enemySubHash, SelectedSubType.EnemySub, GameMain.NetLobbyScreen.SubList))
{
readyToStart = false;
}
}
else
{
@@ -733,6 +751,19 @@ namespace Barotrauma.Networking
CoroutineManager.StartCoroutine(NetLobbyScreen.WaitForStartRound(startButton: null), "WaitForStartRound");
}
break;
case ServerPacketHeader.WARN_STARTGAME:
DebugConsole.Log("Received WARN_STARTGAME packet.");
RoundStartWarningData warningData = INetSerializableStruct.Read<RoundStartWarningData>(inc);
var team1IncompatiblePerks = ToolBox.UintIdentifierArrayToPrefabCollection(DisembarkPerkPrefab.Prefabs, warningData.Team1IncompatiblePerks);
var team2IncompatiblePerks = ToolBox.UintIdentifierArrayToPrefabCollection(DisembarkPerkPrefab.Prefabs, warningData.Team2IncompatiblePerks);
GameMain.NetLobbyScreen?.ShowStartRoundWarning(SerializableDateTime.UtcNow + TimeSpan.FromSeconds(warningData.RoundStartsAnywaysTimeInSeconds), warningData.Team1Sub, team1IncompatiblePerks, warningData.Team2Sub, team2IncompatiblePerks);
break;
case ServerPacketHeader.CANCEL_STARTGAME:
DebugConsole.Log("Received CANCEL_STARTGAME packet.");
GameMain.NetLobbyScreen?.CloseStartRoundWarning();
break;
case ServerPacketHeader.STARTGAME:
DebugConsole.Log("Received STARTGAME packet.");
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is CampaignMode)
@@ -869,6 +900,9 @@ namespace Barotrauma.Networking
case ServerPacketHeader.EVENTACTION:
GameMain.GameSession?.EventManager.ClientRead(inc);
break;
case ServerPacketHeader.SEND_BACKUP_INDICES:
GameMain.NetLobbyScreen?.CampaignSetupUI?.OnBackupIndicesReceived(inc);
break;
}
}
@@ -952,7 +986,7 @@ namespace Barotrauma.Networking
", server value " + stage + ": " + levelEqualityCheckValues[stage].ToString("X") +
", level value count: " + levelEqualityCheckValues.Count +
", seed: " + Level.Loaded.Seed +
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
", sub: " + (Submarine.MainSub == null ? "null" : (Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")")) +
", mirrored: " + Level.Loaded.Mirrored + "). Round init status: " + roundInitStatus + "." + campaignErrorInfo;
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
@@ -969,9 +1003,24 @@ namespace Barotrauma.Networking
CrewManager.ClientReadActiveOrders(inc);
}
if (inc.ReadBoolean())
{
ApplyDisembarkPerk();
}
roundInitStatus = RoundInitStatus.Started;
}
private void ApplyDisembarkPerk()
{
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
ImmutableArray<Character> team1Characters = characters.Where(static c => c.TeamID is CharacterTeamType.Team1).ToImmutableArray(),
team2Characters = characters.Where(static c => c.TeamID is CharacterTeamType.Team2).ToImmutableArray();
GameSession.GetPerks().ApplyAll(team1Characters, team2Characters);
}
/// <summary>
/// Fires when the ClientPeer gets disconnected from the server. Does not necessarily mean the client is shutting down, we may still be able to reconnect.
/// </summary>
@@ -1396,6 +1445,7 @@ namespace Barotrauma.Networking
ServerSettings.LockAllDefaultWires = inc.ReadBoolean();
ServerSettings.AllowLinkingWifiToChat = inc.ReadBoolean();
ServerSettings.MaximumMoneyTransferRequest = inc.ReadInt32();
ServerSettings.RespawnMode = (RespawnMode)inc.ReadByte();
bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
ServerSettings.ShowEnemyHealthBars = (EnemyHealthBarMode)inc.ReadByte();
@@ -1419,19 +1469,38 @@ namespace Barotrauma.Networking
string subHash = inc.ReadString();
string shuttleName = inc.ReadString();
string shuttleHash = inc.ReadString();
bool hasEnemySub = inc.ReadBoolean();
string enemySubName = subName;
string enemySubHash = subHash;
if (hasEnemySub)
{
enemySubName = inc.ReadString();
enemySubHash = inc.ReadString();
}
List<UInt32> missionHashes = new List<UInt32>();
int missionCount = inc.ReadByte();
for (int i = 0; i < missionCount; i++)
{
missionHashes.Add(inc.ReadUInt32());
}
if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList))
if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList))
{
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Success;
}
if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox))
if (hasEnemySub)
{
if (!GameMain.NetLobbyScreen.TrySelectSub(enemySubName, enemySubHash, SelectedSubType.EnemySub, GameMain.NetLobbyScreen.SubList))
{
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Success;
}
}
if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox))
{
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Success;
@@ -1480,8 +1549,10 @@ namespace Barotrauma.Networking
var selectedMissions = missionHashes.Select(i => MissionPrefab.Prefabs.Find(p => p.UintIdentifier == i));
GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, gameMode, missionPrefabs: selectedMissions);
GameMain.GameSession.StartRound(levelSeed, levelDifficulty);
var selectedEnemySub = hasEnemySub && GameMain.NetLobbyScreen.SelectedEnemySub is { } enemySub ? Option.Some(enemySub) : Option.None;
GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, selectedEnemySub, gameMode, missionPrefabs: selectedMissions);
GameMain.GameSession.StartRound(levelSeed, levelDifficulty, levelGenerationParams: null, forceBiome: ServerSettings.Biome);
}
else
{
@@ -1667,7 +1738,8 @@ namespace Barotrauma.Networking
}
}
if (GameMain.GameSession.Submarine.Info.IsFileCorrupted)
if (GameMain.GameSession.Submarine != null &&
GameMain.GameSession.Submarine.Info.IsFileCorrupted)
{
DebugConsole.ThrowError($"Failed to start a round. Could not load the submarine \"{GameMain.GameSession.Submarine.Info.Name}\".");
yield return CoroutineStatus.Failure;
@@ -1759,12 +1831,15 @@ namespace Barotrauma.Networking
refSub = Submarine.MainSubs[1];
}
// Enable characters near the main sub for the endCinematic
foreach (Character c in Character.CharacterList)
if (refSub != null)
{
if (Vector2.DistanceSquared(refSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance))
// Enable characters near the main sub for the endCinematic
foreach (Character c in Character.CharacterList)
{
c.Enabled = true;
if (Vector2.DistanceSquared(refSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance))
{
c.Enabled = true;
}
}
}
@@ -1851,6 +1926,8 @@ namespace Barotrauma.Networking
{
bool refreshCampaignUI = false;
UInt16 listId = inc.ReadUInt16();
GameMain.NetLobbyScreen.Team1Count = inc.ReadByte();
GameMain.NetLobbyScreen.Team2Count = inc.ReadByte();
List<TempClient> tempClients = new List<TempClient>();
int clientCount = inc.ReadByte();
for (int i = 0; i < clientCount; i++)
@@ -1883,19 +1960,30 @@ namespace Barotrauma.Networking
existingClient.NameId = tc.NameId;
existingClient.PreferredJob = tc.PreferredJob;
existingClient.PreferredTeam = tc.PreferredTeam;
existingClient.TeamID = tc.TeamID;
existingClient.Character = null;
existingClient.Karma = tc.Karma;
existingClient.Muted = tc.Muted;
existingClient.InGame = tc.InGame;
existingClient.IsOwner = tc.IsOwner;
existingClient.IsDownloading = tc.IsDownloading;
GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient);
GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient); // refresh lobby player list in the local UI
if (Screen.Selected != GameMain.NetLobbyScreen && tc.CharacterId > 0)
{
existingClient.CharacterID = tc.CharacterId;
}
if (existingClient.SessionId == SessionId)
{
MultiplayerPreferences.Instance.TeamPreference = existingClient.PreferredTeam;
// If a team is already selected, make sure the UI reflects it
if (MultiplayerPreferences.Instance.TeamPreference != CharacterTeamType.None)
{
GameMain.NetLobbyScreen.TeamPreferenceListBox?.Select(MultiplayerPreferences.Instance.TeamPreference);
}
else
{
GameMain.NetLobbyScreen.RefreshPvpTeamSelectionButtons();
}
existingClient.SetPermissions(permissions, permittedConsoleCommands);
if (!NetIdUtils.IdMoreRecent(nameId, tc.NameId))
{
@@ -1950,6 +2038,7 @@ namespace Barotrauma.Networking
Steam.SteamManager.UpdateLobby(ServerSettings);
}
GameMain.NetLobbyScreen?.UpdateDisembarkPointListFromServerSettings();
}
if (refreshCampaignUI)
@@ -1960,6 +2049,7 @@ namespace Barotrauma.Networking
campaign.CampaignUI?.HRManagerUI?.RefreshUI();
}
}
}
private bool initialUpdateReceived;
@@ -1997,6 +2087,15 @@ namespace Barotrauma.Networking
string selectSubName = inc.ReadString();
string selectSubHash = inc.ReadString();
bool usingEnemySub = inc.ReadBoolean();
string selectEnemySubName = selectSubName;
string selectEnemySubHash = selectSubHash;
if (usingEnemySub)
{
selectEnemySubName = inc.ReadString();
selectEnemySubHash = inc.ReadString();
}
bool usingShuttle = inc.ReadBoolean();
string selectShuttleName = inc.ReadString();
string selectShuttleHash = inc.ReadString();
@@ -2011,7 +2110,13 @@ namespace Barotrauma.Networking
float traitorProbability = inc.ReadSingle();
int traitorDangerLevel = inc.ReadRangedInteger(TraitorEventPrefab.MinDangerLevel, TraitorEventPrefab.MaxDangerLevel);
MissionType missionType = (MissionType)inc.ReadRangedInteger(0, (int)MissionType.All);
List<Identifier> missionTypes = new List<Identifier>();
uint missionTypeCount = inc.ReadVariableUInt32();
for (int i = 0; i < missionTypeCount; i++)
{
missionTypes.Add(inc.ReadIdentifier());
}
int modeIndex = inc.ReadByte();
string levelSeed = inc.ReadString();
@@ -2048,12 +2153,19 @@ namespace Barotrauma.Networking
ServerSettings.ServerLog.ServerName = ServerSettings.ServerName;
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
if (!allowSubVoting || GameMain.NetLobbyScreen.SelectedSub == null) { GameMain.NetLobbyScreen.TrySelectSub(selectSubName, selectSubHash, GameMain.NetLobbyScreen.SubList); }
GameMain.NetLobbyScreen.TrySelectSub(selectShuttleName, selectShuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox);
if (!allowSubVoting || GameMain.NetLobbyScreen.SelectedSub == null)
{
GameMain.NetLobbyScreen.TrySelectSub(selectSubName, selectSubHash, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList);
if (usingEnemySub)
{
GameMain.NetLobbyScreen.TrySelectSub(selectEnemySubName, selectEnemySubHash, SelectedSubType.EnemySub, GameMain.NetLobbyScreen.SubList);
}
}
GameMain.NetLobbyScreen.TrySelectSub(selectShuttleName, selectShuttleHash, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox);
GameMain.NetLobbyScreen.SetTraitorProbability(traitorProbability);
GameMain.NetLobbyScreen.SetTraitorDangerLevel(traitorDangerLevel);
GameMain.NetLobbyScreen.SetMissionType(missionType);
GameMain.NetLobbyScreen.SetMissionTypes(missionTypes);
GameMain.NetLobbyScreen.LevelSeed = levelSeed;
GameMain.NetLobbyScreen.SelectMode(modeIndex);
@@ -2489,14 +2601,20 @@ namespace Barotrauma.Networking
((SubmarineInfo)c.UserData).MD5Hash.StringRepresentation == newSub.MD5Hash.StringRepresentation);
if (subElement == null) { continue; }
Color newSubTextColor = new Color(subElement.GetChild<GUITextBlock>().TextColor, 1.0f);
subElement.GetChild<GUITextBlock>().TextColor = newSubTextColor;
if (subElement.GetChildByUserData("classtext") is GUITextBlock classTextBlock)
//set the dimmed out submarine info back to normal and update texts
if (subElement.FindChild("nametext", recursive: true) is GUITextBlock nameTextBlock)
{
nameTextBlock.TextColor = new Color(nameTextBlock.TextColor, 1.0f);
}
if (subElement.FindChild("classtext", recursive: true) is GUITextBlock classTextBlock)
{
Color newSubClassTextColor = new Color(classTextBlock.TextColor, 0.8f);
classTextBlock.Text = TextManager.Get($"submarineclass.{newSub.SubmarineClass}");
classTextBlock.TextColor = newSubClassTextColor;
classTextBlock.TextColor = new Color(classTextBlock.TextColor, 0.8f);
}
if (subElement.FindChild("pricetext", recursive: true) is GUITextBlock priceTextBlock)
{
priceTextBlock.Text = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", newSub.Price));
priceTextBlock.TextColor = new Color(priceTextBlock.TextColor, 0.8f);
}
subElement.UserData = newSub;
@@ -2507,14 +2625,22 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.FailedSelectedSub.Value.Name == newSub.Name &&
GameMain.NetLobbyScreen.FailedSelectedSub.Value.Hash == newSub.MD5Hash.StringRepresentation)
{
GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, GameMain.NetLobbyScreen.SubList);
GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList);
}
if (GameMain.NetLobbyScreen.FailedSelectedShuttle.HasValue &&
GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Name == newSub.Name &&
GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Hash == newSub.MD5Hash.StringRepresentation)
{
GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, GameMain.NetLobbyScreen.ShuttleList.ListBox);
GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox);
}
if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.PvP &&
GameMain.NetLobbyScreen.FailedSelectedEnemySub.HasValue &&
GameMain.NetLobbyScreen.FailedSelectedEnemySub.Value.Name == newSub.Name &&
GameMain.NetLobbyScreen.FailedSelectedEnemySub.Value.Hash == newSub.MD5Hash.StringRepresentation)
{
GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, SelectedSubType.EnemySub, GameMain.NetLobbyScreen.SubList);
}
NetLobbyScreen.FailedSubInfo failedCampaignSub = GameMain.NetLobbyScreen.FailedCampaignSubs.Find(s => s.Name == newSub.Name && s.Hash == newSub.MD5Hash.StringRepresentation);
@@ -2547,20 +2673,20 @@ namespace Barotrauma.Networking
if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign campaign || campaign.CampaignID != campaignID)
{
string savePath = transfer.FilePath;
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty);
GameMain.GameSession = new GameSession(null, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
}
GameMain.GameSession.SavePath = transfer.FilePath;
GameMain.GameSession.DataPath = CampaignDataPath.CreateRegular(transfer.FilePath);
if (GameMain.GameSession.SubmarineInfo == null || campaign.Map == null)
{
string subPath = Path.Combine(SaveUtil.TempPath, gameSessionDocRoot.GetAttributeString("submarine", "")) + ".sub";
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(subPath, "");
}
campaign.LoadState(GameMain.GameSession.SavePath);
campaign.LoadState(GameMain.GameSession.DataPath.LoadPath);
GameMain.GameSession?.SubmarineInfo?.Reload();
GameMain.GameSession?.SubmarineInfo?.CheckSubsLeftBehind();
@@ -2577,7 +2703,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.Select();
}
DebugConsole.Log("Campaign save received (" + GameMain.GameSession.SavePath + "), save ID " + campaign.LastSaveID);
DebugConsole.Log("Campaign save received (" + GameMain.GameSession.DataPath + "), save ID " + campaign.LastSaveID);
//decrement campaign update IDs so the server will send us the latest data
//(as there may have been campaign updates after the save file was created)
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
@@ -2858,14 +2984,14 @@ namespace Barotrauma.Networking
/// <summary>
/// Tell the server to select a submarine (permission required)
/// </summary>
public void RequestSelectSub(SubmarineInfo sub, bool isShuttle)
public void RequestSelectSub(SubmarineInfo sub, SelectedSubType type)
{
if (!HasPermission(ClientPermissions.SelectSub) || sub == null) { return; }
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
msg.WriteBoolean(isShuttle); msg.WritePadBits();
msg.WriteUInt16((ushort)ClientPermissions.SelectSub);
msg.WriteByte((byte)type);
msg.WriteString(sub.MD5Hash.StringRepresentation);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2909,7 +3035,7 @@ namespace Barotrauma.Networking
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
public void SetupLoadCampaign(string saveName)
public void SetupLoadCampaign(string filePath, Option<uint> backupIndex)
{
if (ClientPeer == null) { return; }
@@ -2920,7 +3046,19 @@ namespace Barotrauma.Networking
msg.WriteByte((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
msg.WriteBoolean(false); msg.WritePadBits();
msg.WriteString(saveName);
msg.WriteString(filePath);
if (backupIndex.TryUnwrap(out uint index))
{
msg.WriteBoolean(true);
msg.WritePadBits();
msg.WriteUInt32(index);
}
else
{
msg.WriteBoolean(false);
msg.WritePadBits();
}
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -3062,7 +3200,8 @@ namespace Barotrauma.Networking
public bool EnterChatMessage(GUITextBox textBox, string message)
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
var messageType = NetLobbyScreen.TeamChatSelected ? ChatMessageType.Team : ChatMessageType.Default;
textBox.TextColor = ChatMessage.MessageColor[(int)messageType];
if (string.IsNullOrWhiteSpace(message))
{
@@ -3070,7 +3209,7 @@ namespace Barotrauma.Networking
return false;
}
chatBox.ChatManager.Store(message);
SendChatMessage(message);
SendChatMessage(message, type: messageType);
if (textBox.DeselectAfterMessage)
{
@@ -3559,9 +3698,9 @@ namespace Barotrauma.Networking
{
errorLines.Add("Submarine: " + GameMain.GameSession.Submarine.Info.Name);
}
if (GameMain.NetworkMember?.RespawnManager?.RespawnShuttle != null)
if (GameMain.NetworkMember?.RespawnManager is { } respawnManager)
{
errorLines.Add("Respawn shuttle: " + GameMain.NetworkMember.RespawnManager.RespawnShuttle.Info.Name);
errorLines.Add("Respawn shuttles: " + string.Join(", ", respawnManager.RespawnShuttles.Select(s => s.Info.Name)));
}
if (Level.Loaded != null)
{

View File

@@ -20,6 +20,8 @@ namespace Barotrauma.Networking
public bool AllowModDownloads { get; private set; } = true;
public string AutomaticallyAttemptedPassword = string.Empty;
public readonly record struct Callbacks(
Callbacks.MessageCallback OnMessageReceived,
Callbacks.DisconnectCallback OnDisconnect,
@@ -102,8 +104,9 @@ namespace Barotrauma.Networking
TaskPool.Add($"{GetType().Name}.{nameof(GetAccountId)}", GetAccountId(), t =>
{
if (GameMain.Client?.ClientPeer is null) { return; }
// FIXME what to do with this?
//if (GameMain.Client?.ClientPeer is null) { return; }
if (!t.TryGetResult(out Option<AccountId> accountId))
{
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
@@ -118,7 +121,7 @@ namespace Barotrauma.Networking
var body = new ClientAuthTicketAndVersionPacket
{
Name = GameMain.Client.Name,
Name = GameMain.Client?.Name ?? "Unknown",
OwnerKey = ownerKey,
AccountId = accountId,
AuthTicket = authTicket,
@@ -177,10 +180,16 @@ namespace Barotrauma.Networking
var passwordPacket = INetSerializableStruct.Read<ServerPeerPasswordPacket>(inc.Message);
if (WaitingForPassword) { return; }
passwordPacket.Salt.TryUnwrap(out passwordSalt);
passwordPacket.RetriesLeft.TryUnwrap(out var retries);
if (!string.IsNullOrWhiteSpace(AutomaticallyAttemptedPassword))
{
SendPassword(AutomaticallyAttemptedPassword);
return;
}
LocalizedString pwMsg = TextManager.Get("PasswordRequired");
passwordMsgBox?.Close();

View File

@@ -224,10 +224,13 @@ namespace Barotrauma.Networking
ToolBox.ThrowIfNull(netPeerConfiguration);
#if DEBUG
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
netPeerConfiguration.SimulatedMinimumLatency = GameMain.Client.SimulatedMinimumLatency;
netPeerConfiguration.SimulatedRandomLatency = GameMain.Client.SimulatedRandomLatency;
netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
if (GameMain.Client != null)
{
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
netPeerConfiguration.SimulatedMinimumLatency = GameMain.Client.SimulatedMinimumLatency;
netPeerConfiguration.SimulatedRandomLatency = GameMain.Client.SimulatedRandomLatency;
netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
}
#endif
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);

View File

@@ -22,6 +22,12 @@ namespace Barotrauma.Networking
get; private set;
}
public DateTime ReturnTime { get; private set; }
public DateTime RespawnTime { get; private set; }
public State CurrentState { get; private set; }
public bool ReturnCountdownStarted { get; private set; }
public bool RespawnCountdownStarted { get; private set; }
public static void ShowDeathPromptIfNeeded(float delay = 1.0f)
{
if (UseDeathPrompt)
@@ -30,13 +36,18 @@ namespace Barotrauma.Networking
}
}
partial void UpdateTransportingProjSpecific(float deltaTime)
partial void UpdateTransportingProjSpecific(TeamSpecificState teamSpecificState, float deltaTime)
{
if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; }
if (!ReturnCountdownStarted) { return; }
if (GameMain.Client?.Character == null ||
GameMain.Client.Character.Submarine is not { IsRespawnShuttle: true } ||
GameMain.Client.Character.TeamID != teamSpecificState.TeamID)
{
return;
}
if (!teamSpecificState.ReturnCountdownStarted) { return; }
//show a warning when there's 20 seconds until the shuttle leaves
if ((ReturnTime - DateTime.Now).TotalSeconds < 20.0f &&
if ((teamSpecificState.ReturnTime - DateTime.Now).TotalSeconds < 20.0f &&
(DateTime.Now - lastShuttleLeavingWarningTime).TotalSeconds > 30.0f)
{
lastShuttleLeavingWarningTime = DateTime.Now;
@@ -46,43 +57,56 @@ namespace Barotrauma.Networking
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
bool respawnPromptPending = false;
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
switch (newState)
var myTeamId = (CharacterTeamType)msg.ReadByte();
foreach (var teamSpecificState in teamSpecificStates.Values)
{
case State.Transporting:
ReturnCountdownStarted = msg.ReadBoolean();
maxTransportTime = msg.ReadSingle();
float transportTimeLeft = msg.ReadSingle();
var teamId = (CharacterTeamType)msg.ReadByte();
ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(transportTimeLeft * 1000.0f));
RespawnCountdownStarted = false;
if (CurrentState != newState)
{
CoroutineManager.StopCoroutines("forcepos");
}
break;
case State.Waiting:
PendingRespawnCount = msg.ReadUInt16();
RequiredRespawnCount = msg.ReadUInt16();
respawnPromptPending = msg.ReadBoolean();
RespawnCountdownStarted = msg.ReadBoolean();
ResetShuttle();
float newRespawnTime = msg.ReadSingle();
RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
break;
case State.Returning:
RespawnCountdownStarted = false;
break;
bool respawnPromptPending = false;
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
switch (newState)
{
case State.Transporting:
teamSpecificState.ReturnCountdownStarted = msg.ReadBoolean();
maxTransportTime = msg.ReadSingle();
float transportTimeLeft = msg.ReadSingle();
teamSpecificState.ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(transportTimeLeft * 1000.0f));
teamSpecificState.RespawnCountdownStarted = false;
break;
case State.Waiting:
teamSpecificState.PendingRespawnCount = msg.ReadUInt16();
teamSpecificState.RequiredRespawnCount = msg.ReadUInt16();
respawnPromptPending = msg.ReadBoolean();
teamSpecificState.RespawnCountdownStarted = msg.ReadBoolean();
ResetShuttle(teamSpecificState);
float newRespawnTime = msg.ReadSingle();
teamSpecificState.RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
break;
case State.Returning:
teamSpecificState.RespawnCountdownStarted = false;
break;
}
teamSpecificState.CurrentState = newState;
if (respawnPromptPending)
{
GameMain.Client.HasSpawned = true;
DeathPrompt.Create(delay: 1.0f);
}
if (teamId == myTeamId)
{
PendingRespawnCount = teamSpecificState.PendingRespawnCount;
RequiredRespawnCount = teamSpecificState.RequiredRespawnCount;
ReturnTime = teamSpecificState.ReturnTime;
RespawnTime = teamSpecificState.RespawnTime;
CurrentState = teamSpecificState.CurrentState;
ReturnCountdownStarted = teamSpecificState.ReturnCountdownStarted;
RespawnCountdownStarted = teamSpecificState.RespawnCountdownStarted;
}
}
CurrentState = newState;
if (respawnPromptPending)
{
GameMain.Client.HasSpawned = true;
DeathPrompt.Create(delay: 1.0f);
}
msg.ReadPadBits();
}
}

View File

@@ -144,9 +144,9 @@ namespace Barotrauma.Networking
var pingLocation = NetPingLocation.TryParseFromString(pingLocationStr);
if (pingLocation.HasValue && Steamworks.SteamNetworkingUtils.LocalPingLocation.HasValue)
if (pingLocation.HasValue)
{
int ping = Steamworks.SteamNetworkingUtils.LocalPingLocation.Value.EstimatePingTo(pingLocation.Value);
int ping = Steamworks.SteamNetworkingUtils.EstimatePingTo(pingLocation.Value);
if (ping < 0) { return Result.Failure(SteamLobbyPingError.PingEstimationFailed); }
return Result.Success(ping);
}

View File

@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
@@ -178,6 +179,11 @@ namespace Barotrauma.Networking
extraCargoPanel.Visible = true;
}
}
if (ReadPerks(incMsg))
{
GameMain.NetLobbyScreen?.UpdateDisembarkPointListFromServerSettings();
}
}
if (requiredFlags.HasFlag(NetFlags.HiddenSubs))
@@ -194,10 +200,41 @@ namespace Barotrauma.Networking
}
}
public static bool HasPermissionToChangePerks()
{
if (GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return true; }
bool isPvP = GameMain.NetLobbyScreen?.SelectedMode == GameModePreset.PvP;
bool hasSelectedTeam = MultiplayerPreferences.Instance.TeamPreference is CharacterTeamType.Team1 or CharacterTeamType.Team2;
var otherClients = GameMain.Client?.ConnectedClients.Where(static c => c.SessionId != GameMain.Client.SessionId).ToImmutableArray() ?? ImmutableArray<Client>.Empty;
if (isPvP)
{
if (!hasSelectedTeam) { return false; }
return !otherClients
.Where(static c => c.PreferredTeam == MultiplayerPreferences.Instance.TeamPreference)
.Any(static c => c.HasPermission(Networking.ClientPermissions.ManageSettings));
}
else
{
return !otherClients.Any(static c => c.HasPermission(Networking.ClientPermissions.ManageSettings));
}
}
public void ClientAdminWritePerks()
{
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteByte((byte)ClientPacketHeader.SERVER_SETTINGS_PERKS);
WritePerks(outMsg);
GameMain.Client?.ClientPeer?.Send(outMsg, DeliveryMethod.Reliable);
}
public void ClientAdminWrite(
NetFlags dataToSend,
int? missionTypeOr = null,
int? missionTypeAnd = null,
Identifier addedMissionType = default,
Identifier removedMissionType = default,
int traitorDangerLevel = 0)
{
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return; }
@@ -220,7 +257,7 @@ namespace Barotrauma.Networking
outMsg.WriteUInt32(count);
foreach (KeyValuePair<UInt32, NetPropertyData> prop in changedProperties)
{
DebugConsole.NewMessage(prop.Value.Name.Value, Color.Lime);
DebugConsole.NewMessage($"Changed {prop.Value.Name.Value} to {prop.Value.GUIComponentValue}", Color.Lime);
outMsg.WriteUInt32(prop.Key);
prop.Value.Write(outMsg, prop.Value.GUIComponentValue);
}
@@ -237,8 +274,8 @@ namespace Barotrauma.Networking
if (dataToSend.HasFlag(NetFlags.Misc))
{
outMsg.WriteRangedInteger(missionTypeOr ?? (int)Barotrauma.MissionType.None, 0, (int)Barotrauma.MissionType.All);
outMsg.WriteRangedInteger(missionTypeAnd ?? (int)Barotrauma.MissionType.All, 0, (int)Barotrauma.MissionType.All);
outMsg.WriteIdentifier(addedMissionType);
outMsg.WriteIdentifier(removedMissionType);
outMsg.WriteByte((byte)(traitorDangerLevel + 1));
outMsg.WritePadBits();
}

View File

@@ -418,8 +418,44 @@ namespace Barotrauma.Networking
var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed"));
AssignGUIComponent(nameof(RandomizeSeed), randomizeLevelBox);
//***********************************************
// ******* PVP ********************************
NetLobbyScreen.CreateSubHeader("gamemode.pvp", listBox.Content);
var teamSelectModeLabel = new GUITextBlock(
new RectTransform(new Vector2(1.0f, 0.05f),
listBox.Content.RectTransform),
TextManager.Get("TeamSelectionMode"));
teamSelectModeLabel.ToolTip = TextManager.Get("TeamSelectionMode.tooltip");
var teamSelectionMode = new GUISelectionCarousel<PvpTeamSelectionMode>(
new RectTransform(new Vector2(0.5f, 0.6f),
teamSelectModeLabel.RectTransform,
Anchor.CenterRight));
foreach (PvpTeamSelectionMode teamSelectionModeOption in Enum.GetValues(typeof(PvpTeamSelectionMode)))
{
var optionName = teamSelectionModeOption.ToString();
teamSelectionMode.AddElement(teamSelectionModeOption,
TextManager.Get($"TeamSelectionMode.{optionName}"),
TextManager.Get($"TeamSelectionMode.{optionName}.tooltip"));
}
AssignGUIComponent(nameof(PvpTeamSelectionMode), teamSelectionMode);
var autoBalanceThresholdLabel = new GUITextBlock(
new RectTransform(new Vector2(1.0f, 0.05f),
listBox.Content.RectTransform),
TextManager.Get("AutoBalanceThreshold"));
var autoBalanceThresholdTooltip = TextManager.Get("AutoBalanceThreshold.tooltip");
autoBalanceThresholdLabel.ToolTip = autoBalanceThresholdTooltip;
var autoBalanceThreshold = new GUISelectionCarousel<int>(
new RectTransform(new Vector2(0.5f, 0.6f),
autoBalanceThresholdLabel.RectTransform,
Anchor.CenterRight));
autoBalanceThreshold.AddElement(0, TextManager.Get($"AutoBalanceThreshold.Off"), autoBalanceThresholdTooltip);
autoBalanceThreshold.AddElement(1, "1", autoBalanceThresholdTooltip);
autoBalanceThreshold.AddElement(2, "2", autoBalanceThresholdTooltip);
autoBalanceThreshold.AddElement(3, "3", autoBalanceThresholdTooltip);
AssignGUIComponent(nameof(PvpAutoBalanceThreshold), autoBalanceThreshold);
// ******* GAMEPLAY ***************************
NetLobbyScreen.CreateSubHeader("serversettingsroundstab", listBox.Content);
var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
@@ -485,6 +521,9 @@ namespace Barotrauma.Networking
AssignGUIComponent(nameof(NewCampaignDefaultSalary), defaultSalarySlider);
defaultSalarySlider.OnMoved(defaultSalarySlider, defaultSalarySlider.BarScroll);
var pvpDisembarkPoints = NetLobbyScreen.CreateLabeledNumberInput(listBox.Content, "serversettingsdisembarkpoints", 0, 100, "serversettingsdisembarkpointstooltip");
AssignGUIComponent(nameof(DisembarkPointAllowance), pvpDisembarkPoints);
//--------------------------------------------------------------------------------
// game settings
//--------------------------------------------------------------------------------

View File

@@ -320,7 +320,7 @@ namespace Barotrauma.Networking
private Sound overrideSound;
private int overridePos;
private short[] overrideBuf = new short[VoipConfig.BUFFER_SIZE];
private readonly short[] overrideBuf = new short[VoipConfig.BUFFER_SIZE];
private void FillBuffer()
{
@@ -331,13 +331,13 @@ namespace Barotrauma.Networking
{
int sampleCount = overrideSound.FillStreamBuffer(overridePos, overrideBuf);
overridePos += sampleCount * 2;
Array.Copy(overrideBuf, 0, uncompressedBuffer, totalSampleCount, sampleCount);
Array.Copy(overrideBuf, 0, uncompressedBuffer, totalSampleCount, Math.Min(sampleCount, uncompressedBuffer.Length - totalSampleCount));
totalSampleCount += sampleCount;
if (sampleCount == 0)
{
overridePos = 0;
}
}
}
int sleepMs = VoipConfig.BUFFER_SIZE * 800 / VoipConfig.FREQUENCY;
Thread.Sleep(sleepMs - 1);
@@ -382,7 +382,14 @@ namespace Barotrauma.Networking
}
else
{
overrideSound = GameMain.SoundManager.LoadSound(fileName, true);
try
{
overrideSound = GameMain.SoundManager.LoadSound(fileName, true);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to load the sound {fileName}.", e);
}
}
}

View File

@@ -59,22 +59,77 @@ namespace Barotrauma
switch (voteType)
{
case VoteType.Sub:
case VoteType.Mode:
GUIListBox listBox = (voteType == VoteType.Sub) ?
GameMain.NetLobbyScreen.SubList : GameMain.NetLobbyScreen.ModeList;
var subList = GameMain.NetLobbyScreen.SubList;
foreach (GUIComponent comp in listBox.Content.Children)
foreach (GUIComponent comp in subList.Content.Children)
{
if (comp.FindChild("votes") is GUITextBlock voteText) { comp.RemoveChild(voteText); }
TryRemoveVoteText(comp);
var container = comp.GetChild<GUILayoutGroup>();
var imageFrame = container.GetChild<GUIFrame>();
var coalIcon = imageFrame.GetChildByUserData(NetLobbyScreen.CoalitionIconUserData);
var sepIcon = imageFrame.GetChildByUserData(NetLobbyScreen.SeparatistsIconUserData);
coalIcon.Enabled = false;
sepIcon.Enabled = false;
TryRemoveVoteText(coalIcon);
TryRemoveVoteText(sepIcon);
static void TryRemoveVoteText(GUIComponent component)
{
if (component.FindChild("votes") is GUITextBlock foundText)
{
component.RemoveChild(foundText);
}
}
}
if (clients == null) { return; }
IReadOnlyDictionary<object, int> voteList = GetVoteCounts<object>(voteType, clients);
foreach (KeyValuePair<object, int> votable in voteList)
bool isPvP = GameMain.NetLobbyScreen?.SelectedMode == GameModePreset.PvP;
if (isPvP)
{
SetVoteText(listBox, votable.Key, votable.Value);
}
var coalitionVoteList = GetVoteCounts<SubmarineInfo>(voteType, clients.Where(static c => c.PreferredTeam is CharacterTeamType.Team1));
var separatistVoteList = GetVoteCounts<SubmarineInfo>(voteType, clients.Where(static c => c.PreferredTeam is CharacterTeamType.Team2));
foreach (var (subInfo, amount) in coalitionVoteList)
{
SetSubVoteText(subList, subInfo, amount, CharacterTeamType.Team1);
}
foreach (var (subInfo, amount) in separatistVoteList)
{
SetSubVoteText(subList, subInfo, amount, CharacterTeamType.Team2);
}
}
else
{
var subVoteList = GetVoteCounts<SubmarineInfo>(voteType, clients);
foreach (var (subInfo, amount) in subVoteList)
{
SetSubVoteText(subList, subInfo, amount, CharacterTeamType.None);
}
}
break;
case VoteType.Mode:
var modeList = GameMain.NetLobbyScreen.ModeList;
foreach (GUIComponent comp in modeList.Content.Children)
{
if (comp.FindChild("votes") is GUITextBlock voteText)
{
comp.RemoveChild(voteText);
}
}
if (clients == null) { return; }
var modeVoteList = GetVoteCounts<GameModePreset>(voteType, clients);
foreach (var (preset, amount) in modeVoteList)
{
SetVoteText(modeList, preset, amount);
}
break;
case VoteType.StartRound:
if (clients == null) { return; }
@@ -90,12 +145,70 @@ namespace Barotrauma
}
}
private void SetSubVoteText(GUIListBox subListBox, SubmarineInfo userData, int votes, CharacterTeamType type)
{
GUIComponent subElement = subListBox.Content.GetChildByUserData(userData);
if (subElement is null)
{
DebugConsole.ThrowError("Failed to find the submarine element in the listbox");
return;
}
var (coalIcon, sepIcon) = GetPvPIcons(subElement);
switch (type)
{
case CharacterTeamType.None:
{
SetVoteText(subListBox, userData, votes);
break;
}
case CharacterTeamType.Team1:
{
coalIcon.Enabled = votes > 0;
CreateSubmarineVoteText(coalIcon, votes);
break;
}
case CharacterTeamType.Team2:
{
sepIcon.Enabled = votes > 0;
CreateSubmarineVoteText(sepIcon, votes);
break;
}
default:
return;
}
static void CreateSubmarineVoteText(GUIComponent parent, int votes)
{
if (parent is null) { return; }
var voteText = new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopLeft), $"{votes}", textAlignment: Alignment.Center)
{
Padding = Vector4.Zero,
UserData = "votes",
Shadow = true
};
voteText.RectTransform.RelativeOffset = new Vector2(0.33f, 0.33f);
}
static (GUIComponent CoalitionIcon, GUIComponent SeparatistsIcon) GetPvPIcons(GUIComponent child)
{
var container = child.GetChild<GUILayoutGroup>();
var imageFrame = container.GetChild<GUIFrame>();
var coalIcon = imageFrame.GetChildByUserData(NetLobbyScreen.CoalitionIconUserData);
var sepIcon = imageFrame.GetChildByUserData(NetLobbyScreen.SeparatistsIconUserData);
return (CoalitionIcon: coalIcon, SeparatistsIcon: sepIcon);
}
}
private void SetVoteText(GUIListBox listBox, object userData, int votes)
{
if (userData == null) { return; }
foreach (GUIComponent comp in listBox.Content.Children)
{
if (comp.UserData != userData) { continue; }
if (comp.FindChild("votes") is not GUITextBlock voteText)
{
voteText = new GUITextBlock(new RectTransform(new Point(GUI.IntScale(30), comp.Rect.Height), comp.RectTransform, Anchor.CenterRight),
@@ -197,28 +310,48 @@ namespace Barotrauma
msg.WritePadBits();
return true;
}
public void ClientRead(IReadMessage inc)
{
GameMain.Client.ServerSettings.AllowSubVoting = inc.ReadBoolean();
if (GameMain.Client.ServerSettings.AllowSubVoting)
{
UpdateVoteTexts(null, VoteType.Sub);
bool isMultiSub = inc.ReadBoolean();
int votableCount = inc.ReadByte();
List<SubmarineInfo> serversubs = new List<SubmarineInfo>();
if (GameMain.NetLobbyScreen?.SubList?.Content != null)
{
foreach (GUIComponent item in GameMain.NetLobbyScreen.SubList.Content.Children)
{
if (item.UserData is SubmarineInfo info)
{
serversubs.Add(info);
}
}
}
for (int i = 0; i < votableCount; i++)
{
int votes = inc.ReadByte();
string subName = inc.ReadString();
List<SubmarineInfo> serversubs = new List<SubmarineInfo>();
if (GameMain.NetLobbyScreen?.SubList?.Content != null)
{
foreach (GUIComponent item in GameMain.NetLobbyScreen.SubList.Content.Children)
{
if (item.UserData != null && item.UserData is SubmarineInfo) { serversubs.Add(item.UserData as SubmarineInfo); }
}
}
SubmarineInfo sub = serversubs.FirstOrDefault(s => s.Name == subName);
SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes);
SetSubVoteText(GameMain.NetLobbyScreen.SubList, sub, votes, isMultiSub ? CharacterTeamType.Team1 : CharacterTeamType.None);
}
if (isMultiSub)
{
int separatistsCount = inc.ReadByte();
for (int i = 0; i < separatistsCount; i++)
{
int votes = inc.ReadByte();
string subName = inc.ReadString();
SubmarineInfo sub = serversubs.FirstOrDefault(s => s.Name == subName);
SetSubVoteText(GameMain.NetLobbyScreen.SubList, sub, votes, CharacterTeamType.Team2);
}
}
}
GameMain.Client.ServerSettings.AllowModeVoting = inc.ReadBoolean();

View File

@@ -72,7 +72,7 @@ namespace Barotrauma.Particles
public float VelocityChangeMultiplier;
public bool DrawOnTop { get; private set; }
public ParticleDrawOrder DrawOrder { get; private set; }
public ParticlePrefab.DrawTargetType DrawTarget
{
@@ -110,7 +110,7 @@ namespace Barotrauma.Particles
{
return debugName;
}
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, ParticleDrawOrder drawOrder = ParticleDrawOrder.Default, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
{
this.prefab = prefab;
#if DEBUG
@@ -205,7 +205,7 @@ namespace Barotrauma.Particles
prevRotation = rotation;
}
DrawOnTop = drawOnTop;
DrawOrder = drawOrder;
this.collisionIgnoreTimer = collisionIgnoreTimer;
}

View File

@@ -8,6 +8,7 @@ namespace Barotrauma.Particles
{
class ParticleEmitterProperties : ISerializableEntity
{
private const float MinValue = int.MinValue,
MaxValue = int.MaxValue;
@@ -98,8 +99,8 @@ namespace Barotrauma.Particles
[Editable, Serialize(1f, IsPropertySaveable.Yes)]
public float LifeTimeMultiplier { get; set; }
[Editable, Serialize(false, IsPropertySaveable.Yes)]
public bool DrawOnTop { get; set; }
[Editable, Serialize(ParticleDrawOrder.Default, IsPropertySaveable.Yes)]
public ParticleDrawOrder DrawOrder { get; set; }
[Serialize(0f, IsPropertySaveable.Yes)]
public float Angle
@@ -127,6 +128,12 @@ namespace Barotrauma.Particles
public ParticleEmitterProperties(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
//backwards compatibility
if (element.GetAttributeBool("drawontop", false))
{
DrawOrder = ParticleDrawOrder.Foreground;
}
}
}
@@ -215,7 +222,9 @@ namespace Barotrauma.Particles
position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax);
}
var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, particlePrefab.DrawOnTop || Prefab.DrawOnTop, lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: tracerPoints);
var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess,
particlePrefab.DrawOrder != ParticleDrawOrder.Default ? particlePrefab.DrawOrder : Prefab.DrawOrder,
lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: tracerPoints);
if (particle != null)
{
@@ -286,7 +295,9 @@ namespace Barotrauma.Particles
public readonly ContentPackage? ContentPackage;
public bool DrawOnTop => Properties.DrawOnTop || ParticlePrefab is { DrawOnTop: true };
public ParticleDrawOrder DrawOrder => Properties.DrawOrder != ParticleDrawOrder.Default ?
Properties.DrawOrder :
(ParticlePrefab?.DrawOrder ?? ParticleDrawOrder.Default);
public ParticleEmitterPrefab(ContentXElement element)
{

View File

@@ -11,6 +11,13 @@ namespace Barotrauma.Particles
AlphaBlend, Additive//, Distortion
}
enum ParticleDrawOrder
{
Default,
Foreground,
Background
}
class ParticleManager
{
private const int MaxOutOfViewDist = 500;
@@ -91,7 +98,7 @@ namespace Barotrauma.Particles
return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer, tracerPoints:tracerPoints);
}
public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, ParticleDrawOrder drawOrder = ParticleDrawOrder.Default, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
{
if (prefab == null || prefab.Sprites.Count == 0) { return null; }
if (particleCount >= MaxParticles)
@@ -134,7 +141,7 @@ namespace Barotrauma.Particles
if (particles[particleCount] == null) { particles[particleCount] = new Particle(); }
Particle particle = particles[particleCount];
particle.Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, lifeTimeMultiplier, tracerPoints: tracerPoints);
particle.Init(prefab, position, velocity, rotation, hullGuess, drawOrder, collisionIgnoreTimer, lifeTimeMultiplier, tracerPoints: tracerPoints);
particleCount++;
particlesInCreationOrder.AddFirst(particle);
@@ -213,7 +220,7 @@ namespace Barotrauma.Particles
return activeParticles;
}
public void Draw(SpriteBatch spriteBatch, bool inWater, bool? inSub, ParticleBlendState blendState)
public void Draw(SpriteBatch spriteBatch, bool inWater, bool? inSub, ParticleBlendState blendState, bool? background = false)
{
ParticlePrefab.DrawTargetType drawTarget = inWater ? ParticlePrefab.DrawTargetType.Water : ParticlePrefab.DrawTargetType.Air;
@@ -225,12 +232,16 @@ namespace Barotrauma.Particles
if (inSub.HasValue)
{
bool isOutside = particle.CurrentHull == null;
if (!particle.DrawOnTop && isOutside == inSub.Value)
if (particle.DrawOrder != ParticleDrawOrder.Foreground && isOutside == inSub.Value)
{
continue;
}
}
if (background.HasValue)
{
bool isBackgroundParticle = particle.DrawOrder == ParticleDrawOrder.Background;
if (background.Value != isBackgroundParticle) { continue; }
}
particle.Draw(spriteBatch);
}
}

View File

@@ -151,7 +151,7 @@ namespace Barotrauma.Particles
public float Friction { get; private set; }
[Editable(0.0f, 1.0f)]
[Serialize(0.5f, IsPropertySaveable.No, description: "How much of the particle's velocity is conserved when it collides with something, i.e. the \"bounciness\" of the particle. (1.0 = the particle stops completely).")]
[Serialize(0.5f, IsPropertySaveable.No, description: "How much of the particle's velocity is conserved when it collides with something, i.e. the \"bounciness\" of the particle. (0.0 = the particle stops completely).")]
public float Restitution { get; private set; }
//size -----------------------------------------
@@ -188,8 +188,8 @@ namespace Barotrauma.Particles
[Editable, Serialize(DrawTargetType.Air, IsPropertySaveable.No, description: "Should the particle be rendered in air, water or both.")]
public DrawTargetType DrawTarget { get; private set; }
[Editable, Serialize(false, IsPropertySaveable.No, description: "Should the particle be always rendered on top of entities?")]
public bool DrawOnTop { get; private set; }
[Editable, Serialize(ParticleDrawOrder.Default, IsPropertySaveable.No, description: "Should the particle be always forced to render on top of entities or behind everything?")]
public ParticleDrawOrder DrawOrder { get; private set; }
[Editable, Serialize(false, IsPropertySaveable.No, description: "Draw the particle even when it's calculated to be outside of view (the formula doesn't take scales into account). ")]
public bool DrawAlways { get; private set; }
@@ -226,6 +226,16 @@ namespace Barotrauma.Particles
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
//backwards compatibility
if (element.GetAttributeBool("drawontop", false))
{
DrawOrder = ParticleDrawOrder.Foreground;
}
if (BlendState == ParticleBlendState.Additive && DrawOrder == ParticleDrawOrder.Background)
{
DebugConsole.AddWarning($"Error in particle prefab {Identifier}: additive particles cannot be rendered in the background.");
}
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())

View File

@@ -9,14 +9,6 @@ namespace Barotrauma
{
partial class PhysicsBody
{
private float bodyShapeTextureScale;
private Texture2D bodyShapeTexture;
public Texture2D BodyShapeTexture
{
get { return bodyShapeTexture; }
}
public void Draw(DeformableSprite deformSprite, Camera cam, Vector2 scale, Color color, bool invert = false)
{
if (!Enabled) { return; }
@@ -79,78 +71,30 @@ namespace Barotrauma
new Vector2(DrawPosition.X, -DrawPosition.Y),
Color.Cyan, 0, 5);
}
if (bodyShapeTexture == null && IsValidShape(Radius, Height, Width))
if (IsValidShape(Radius, Height, Width))
{
float radius = ConvertUnits.ToDisplayUnits(Radius);
float height = ConvertUnits.ToDisplayUnits(Height);
float width = ConvertUnits.ToDisplayUnits(Width);
switch (BodyShape)
{
case Shape.Rectangle:
{
float maxSize = Math.Max(ConvertUnits.ToDisplayUnits(Width), ConvertUnits.ToDisplayUnits(Height));
if (maxSize > 128.0f)
{
bodyShapeTextureScale = 128.0f / maxSize;
}
else
{
bodyShapeTextureScale = 1.0f;
}
bodyShapeTexture = GUI.CreateRectangle(
(int)ConvertUnits.ToDisplayUnits(Width * bodyShapeTextureScale),
(int)ConvertUnits.ToDisplayUnits(Height * bodyShapeTextureScale));
break;
}
GUI.DrawRectangle(spriteBatch, DrawPosition.FlipY(), new Vector2(width, height), new Vector2(width, height) / 2, -DrawRotation, color);
break;
case Shape.Capsule:
GUI.DrawCapsule(spriteBatch, DrawPosition.FlipY(), height, radius, -DrawRotation - MathHelper.PiOver2, color);
break;
case Shape.HorizontalCapsule:
{
float maxSize = Math.Max(ConvertUnits.ToDisplayUnits(Radius), ConvertUnits.ToDisplayUnits(Math.Max(Height, Width)));
if (maxSize > 128.0f)
{
bodyShapeTextureScale = 128.0f / maxSize;
}
else
{
bodyShapeTextureScale = 1.0f;
}
bodyShapeTexture = GUI.CreateCapsule(
(int)ConvertUnits.ToDisplayUnits(Radius * bodyShapeTextureScale),
(int)ConvertUnits.ToDisplayUnits(Math.Max(Height, Width) * bodyShapeTextureScale));
break;
}
GUI.DrawCapsule(spriteBatch, DrawPosition.FlipY(), width, radius, -DrawRotation, color);
break;
case Shape.Circle:
if (ConvertUnits.ToDisplayUnits(Radius) > 128.0f)
{
bodyShapeTextureScale = 128.0f / ConvertUnits.ToDisplayUnits(Radius);
}
else
{
bodyShapeTextureScale = 1.0f;
}
bodyShapeTexture = GUI.CreateCircle((int)ConvertUnits.ToDisplayUnits(Radius * bodyShapeTextureScale));
GUI.DrawDonutSection(spriteBatch, DrawPosition.FlipY(), new Range<float>(radius - 0.5f, radius + 0.5f), MathHelper.TwoPi, color, 0, -DrawRotation);
break;
default:
throw new NotImplementedException();
}
}
float rot = -DrawRotation;
if (bodyShape == Shape.HorizontalCapsule)
{
rot -= MathHelper.PiOver2;
}
if (bodyShapeTexture != null)
{
spriteBatch.Draw(
bodyShapeTexture,
new Vector2(DrawPosition.X, -DrawPosition.Y),
null,
color,
rot,
new Vector2(bodyShapeTexture.Width / 2, bodyShapeTexture.Height / 2),
1.0f / bodyShapeTextureScale, SpriteEffects.None, 0.0f);
}
}
public PosInfo ClientRead(IReadMessage msg, float sendingTime, string parentDebugName)
@@ -210,14 +154,5 @@ namespace Barotrauma
null :
new PosInfo(newPosition, newRotation, newVelocity, newAngularVelocity, sendingTime);
}
partial void DisposeProjSpecific()
{
if (bodyShapeTexture != null)
{
bodyShapeTexture.Dispose();
bodyShapeTexture = null;
}
}
}
}

View File

@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
@@ -19,7 +20,9 @@ namespace Barotrauma
protected GUIButton loadGameButton;
public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame;
public Action<string> LoadGame;
public delegate void LoadGameDelegate(string loadPath, Option<uint> backupIndex);
public LoadGameDelegate LoadGame;
protected enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 }
protected CategoryFilter subFilter = CategoryFilter.All;
@@ -821,5 +824,119 @@ namespace Barotrauma
return true;
}
protected void CreateBackupMenu(IEnumerable<SaveUtil.BackupIndexData> indexData, Action<SaveUtil.BackupIndexData> loadBackup)
{
var backupPopup = new GUIMessageBox("", "", new[] { TextManager.Get("Load"), TextManager.Get("Cancel") }, new Vector2(0.3f, 0.5f), minSize: new Point(500, 500));
GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.8f), backupPopup.Content.RectTransform, Anchor.TopCenter));
GUIListBox backupList = new GUIListBox(new RectTransform(Vector2.One, campaignSettingContent.RectTransform));
bool isIronman = GameMain.NetworkMember?.ServerSettings is { IronmanModeActive: true };
if (!indexData.Any() || isIronman)
{
LocalizedString errorMsg = isIronman
? TextManager.Get("ironmanmodebackupdisclaimer")
: TextManager.Get("nobackups");
var errorBlock = new GUITextBlock(new RectTransform(Vector2.One, campaignSettingContent.RectTransform), errorMsg, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center)
{
TextColor = GUIStyle.Red,
IgnoreLayoutGroups = true
};
if (errorBlock.Font.MeasureString(errorMsg).X > campaignSettingContent.Rect.Width)
{
errorBlock.Wrap = true;
errorBlock.SetTextPos();
}
}
if (!isIronman)
{
foreach (var data in indexData.OrderByDescending(static i => i.SaveTime))
{
GUIFrame indexFrame = new GUIFrame(
new RectTransform(new Vector2(1.0f, 1f / SaveUtil.MaxBackupCount), backupList.Content.RectTransform), style: "ListBoxElement")
{
UserData = data
};
GUILayoutGroup indexLayout = new GUILayoutGroup(
new RectTransform(Vector2.One, indexFrame.RectTransform),
isHorizontal: true,
childAnchor: Anchor.CenterLeft)
{
RelativeSpacing = 0.05f,
Stretch = true
};
GUILayoutGroup leftLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.8f), indexLayout.RectTransform), childAnchor: Anchor.TopCenter)
{
RelativeSpacing = 0.05f,
Stretch = true
};
LocalizedString locationName = data.LocationType.IsEmpty || data.LocationNameIdentifier.IsEmpty ?
TextManager.Get("unknown") :
Location.GetName(data.LocationType, data.LocationNameFormatIndex, data.LocationNameIdentifier);
var locationNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), leftLayout.RectTransform), locationName, textAlignment: Alignment.CenterLeft)
{
TextColor = Color.White
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), leftLayout.RectTransform), TextManager.Get($"savestate.{data.LevelType}"), textAlignment: Alignment.CenterLeft);
GUILayoutGroup rightLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.8f), indexLayout.RectTransform), childAnchor: Anchor.TopCenter)
{
RelativeSpacing = 0.05f,
Stretch = true
};
TimeSpan difference = SerializableDateTime.UtcNow - data.SaveTime;
double totalMinutes = difference.TotalMinutes;
LocalizedString timeFormat = totalMinutes switch
{
< 1 => TextManager.Get("subeditor.savedjustnow"),
> 60 => TextManager.GetWithVariable("saveagehours", "[hours]", ((int)Math.Floor(difference.TotalHours)).ToString()),
_ => TextManager.GetWithVariable("subeditor.saveageminutes", "[minutes]", difference.Minutes.ToString())
};
new GUITextBlock(new RectTransform(Vector2.One, rightLayout.RectTransform), timeFormat, textAlignment: Alignment.CenterRight);
locationNameBlock.Text = ToolBox.LimitString(locationName, locationNameBlock.Font, locationNameBlock.Rect.Width);
}
}
backupList.AfterSelected = (selected, _) =>
{
// to my understanding, there's no way to unselect an item in a GUIListBox
// so no need to check if selected is null
backupPopup.Buttons[0].Enabled = true;
return true;
};
backupPopup.Buttons[1].OnClicked += (button, o) =>
{
backupPopup.Close();
return true;
};
backupPopup.Buttons[0].Enabled = false;
backupPopup.Buttons[0].OnClicked += (button, o) =>
{
if (backupList.SelectedComponent?.UserData is not SaveUtil.BackupIndexData selectedIndexData) { return false; }
backupPopup.Close();
loadBackup?.Invoke(selectedIndexData);
return true;
};
}
}
}

View File

@@ -1,12 +1,16 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
class MultiPlayerCampaignSetupUI : CampaignSetupUI
{
private GUIButton rollbackSaveButton;
private GUIButton deleteMpSaveButton;
private int prevInitialMoney;
@@ -232,34 +236,114 @@ namespace Barotrauma
{
if (saveList.SelectedData is not CampaignMode.SaveInfo saveInfo) { return false; }
if (string.IsNullOrWhiteSpace(saveInfo.FilePath)) { return false; }
LoadGame?.Invoke(saveInfo.FilePath);
LoadGame?.Invoke(saveInfo.FilePath, backupIndex: Option.None);
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
return true;
},
Enabled = false
};
deleteMpSaveButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomLeft),
TextManager.Get("Delete"), style: "GUIButtonSmall")
GUILayoutGroup leftButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.15f), loadGameContainer.RectTransform, Anchor.BottomLeft))
{
RelativeSpacing = 0.05f,
Stretch = true
};
rollbackSaveButton = new GUIButton(new RectTransform(new Vector2(1f, 0.5f), leftButtonContainer.RectTransform), TextManager.Get("rollbackbutton"), style: "GUIButtonSmallFreeScale")
{
Visible = false,
ToolTip = TextManager.Get("backuptooltip"),
OnClicked = ViewBackupSaveMenu
};
deleteMpSaveButton = new GUIButton(new RectTransform(new Vector2(1f, 0.5f), leftButtonContainer.RectTransform),
TextManager.Get("Delete"), style: "GUIButtonSmallFreeScale")
{
OnClicked = DeleteSave,
Visible = false
};
}
}
private bool ViewBackupSaveMenu(GUIButton button, object obj)
{
if (obj is not CampaignMode.SaveInfo saveInfo) { return false; }
if (string.IsNullOrWhiteSpace(saveInfo.FilePath)) { return false; }
if (GameMain.Client.IsServerOwner)
{
CreateBackupMenu(SaveUtil.GetIndexData(saveInfo.FilePath), index =>
{
LoadGame(saveInfo.FilePath, backupIndex: Option.Some(index.Index));
});
}
else
{
RequestBackupIndexData(saveInfo.FilePath);
}
return true;
}
private const string PleaseWaitUserData = "PleaseWaitPopup";
private void RequestBackupIndexData(string savePath)
{
if (GameMain.Client == null) { return; }
GUI.SetCursorWaiting();
var msgBox = new GUIMessageBox(TextManager.Get("CampaignStartingPleaseWait"), TextManager.Get("CampaignStarting"), new[] { TextManager.Get("Cancel") })
{
UserData = PleaseWaitUserData
};
msgBox.Buttons[0].OnClicked = (btn, obj) =>
{
GUI.ClearCursorWait();
return true;
};
IWriteMessage msg = new WriteOnlyMessage().WithHeader(ClientPacketHeader.REQUEST_BACKUP_INDICES);
msg.WriteString(savePath);
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
public void OnBackupIndicesReceived(IReadMessage message)
{
GUI.ClearCursorWait();
foreach (GUIComponent component in GUIMessageBox.MessageBoxes.Where(static mb => mb.UserData is PleaseWaitUserData).ToArray())
{
if (component is GUIMessageBox msgBox)
{
msgBox.Close();
}
}
string path = message.ReadString();
var indexData = INetSerializableStruct.Read<NetCollection<SaveUtil.BackupIndexData>>(message);
CreateBackupMenu(indexData, selectedIndex =>
{
LoadGame?.Invoke(path, backupIndex: Option.Some(selectedIndex.Index));
});
}
private bool SelectSaveFile(GUIComponent component, object obj)
{
if (obj is not CampaignMode.SaveInfo saveInfo) { return true; }
string fileName = saveInfo.FilePath;
loadGameButton.Enabled = true;
rollbackSaveButton.Visible = true;
deleteMpSaveButton.Visible = deleteMpSaveButton.Enabled = GameMain.Client.IsServerOwner;
deleteMpSaveButton.Enabled = GameMain.GameSession?.SavePath != fileName;
rollbackSaveButton.Enabled = deleteMpSaveButton.Enabled = GameMain.GameSession?.DataPath.LoadPath != fileName;
if (deleteMpSaveButton.Visible)
{
deleteMpSaveButton.UserData = saveInfo;
}
if (rollbackSaveButton.Visible)
{
rollbackSaveButton.UserData = saveInfo;
}
return true;
}
}

View File

@@ -653,8 +653,7 @@ namespace Barotrauma
{
if (saveList.SelectedData is not CampaignMode.SaveInfo saveInfo) { return false; }
if (string.IsNullOrWhiteSpace(saveInfo.FilePath)) { return false; }
LoadGame?.Invoke(saveInfo.FilePath);
LoadGame?.Invoke(saveInfo.FilePath, backupIndex: Option.None);
return true;
},
Enabled = false
@@ -685,6 +684,15 @@ namespace Barotrauma
string mapseed = docRoot.GetAttributeString("mapseed", "unknown");
Identifier locationNameIdentifier = docRoot.GetAttributeIdentifier("currentlocation", Identifier.Empty);
int locationNameFormatIndex = docRoot.GetAttributeInt("currentlocationnameformatindex", -1);
Identifier locationType = docRoot.GetAttributeIdentifier("locationtype", Identifier.Empty);
LevelData.LevelType levelType = docRoot.GetAttributeEnum("nextleveltype", LevelData.LevelType.LocationConnection);
LocalizedString locationName = locationType.IsEmpty || locationNameIdentifier.IsEmpty ?
LocalizedString.EmptyString :
Location.GetName(locationType, locationNameFormatIndex, locationNameIdentifier);
var saveFileFrame = new GUIFrame(
new RectTransform(new Vector2(0.45f, 0.6f), loadGameContainer.RectTransform, Anchor.TopRight)
{
@@ -708,6 +716,15 @@ namespace Barotrauma
RelativeOffset = new Vector2(0, 0.1f)
});
if (!locationName.IsNullOrEmpty())
{
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
locationName, font: GUIStyle.SmallFont);
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
TextManager.Get($"savestate.{levelType}"), font: GUIStyle.SmallFont);
//spacing
new GUIFrame(new RectTransform(new Vector2(0.0f, 0.05f), layoutGroup.RectTransform), style: null);
}
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
$"{TextManager.Get("Submarine")} : {subName}", font: GUIStyle.SmallFont);
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
@@ -715,15 +732,40 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
$"{TextManager.Get("MapSeed")} : {mapseed}", font: GUIStyle.SmallFont);
new GUIButton(new RectTransform(new Vector2(0.4f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter)
GUILayoutGroup buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.85f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter)
{
RelativeOffset = new Vector2(0, 0.1f)
}, TextManager.Get("Delete"), style: "GUIButtonSmall")
}, isHorizontal: true)
{
RelativeSpacing = 0.05f,
Stretch = true
};
new GUIButton(new RectTransform(new Vector2(0.5f, 1f), buttonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("Delete"), style: "GUIButtonSmall")
{
UserData = saveInfo,
OnClicked = DeleteSave
};
new GUIButton(new RectTransform(new Vector2(0.5f, 1f), buttonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("rollbackbutton"), style: "GUIButtonSmall")
{
UserData = saveInfo,
ToolTip = TextManager.Get("backuptooltip"),
OnClicked = ViewBackupMenu
};
return true;
}
private bool ViewBackupMenu(GUIButton btn, object obj)
{
if (obj is not CampaignMode.SaveInfo saveInfo) { return false; }
var indexData = SaveUtil.GetIndexData(saveInfo.FilePath);
CreateBackupMenu(indexData, index =>
{
LoadGame(saveInfo.FilePath, Option.Some(index.Index));
});
return true;
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.Sounds;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
#if DEBUG
@@ -115,7 +116,7 @@ namespace Barotrauma.CharacterEditor
{
base.Select();
GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", 0.0f, 0);
GameMain.SoundManager.SetCategoryGainMultiplier(SoundManager.SoundCategoryWaterAmbience, 0.0f, 0);
GUI.ForceMouseOn(null);
if (Submarine.MainSub == null)
@@ -236,7 +237,7 @@ namespace Barotrauma.CharacterEditor
protected override void DeselectEditorSpecific()
{
SoundPlayer.OverrideMusicType = Identifier.Empty;
GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", GameSettings.CurrentConfig.Audio.SoundVolume, 0);
GameMain.SoundManager.SetCategoryGainMultiplier(SoundManager.SoundCategoryWaterAmbience, GameSettings.CurrentConfig.Audio.SoundVolume, 0);
GUI.ForceMouseOn(null);
if (isEndlessRunner)
{
@@ -746,6 +747,12 @@ namespace Barotrauma.CharacterEditor
minorModesToggle?.UpdateOpenState((float)deltaTime, new Vector2(-minorModesPanel.Rect.Width - leftArea.RectTransform.AbsoluteOffset.X, 0), minorModesPanel.RectTransform);
modesToggle?.UpdateOpenState((float)deltaTime, new Vector2(-modesPanel.Rect.Width - leftArea.RectTransform.AbsoluteOffset.X, 0), modesPanel.RectTransform);
buttonsPanelToggle?.UpdateOpenState((float)deltaTime, new Vector2(-buttonsPanel.Rect.Width - leftArea.RectTransform.AbsoluteOffset.X, 0), buttonsPanel.RectTransform);
totalMassText.Text = GetTotalMassText();
}
private LocalizedString GetTotalMassText()
{
return TextManager.GetWithVariable($"{screenTextTag}totalmass", "[mass]", character?.AnimController?.Mass.FormatZeroDecimal() ?? "0");
}
public CursorState GetMouseCursorState()
@@ -1089,9 +1096,12 @@ namespace Barotrauma.CharacterEditor
}
else if (PlayerInput.PrimaryMouseButtonClicked())
{
jointStartLimb = GetClosestLimbOnSpritesheet(PlayerInput.MousePosition, l => selectedLimbs.Contains(l) && !l.Hidden);
anchor1Pos = GetLimbSpritesheetRect(jointStartLimb).Center.ToVector2() - PlayerInput.MousePosition;
jointCreationMode = JointCreationMode.Create;
jointStartLimb = GetClosestLimbOnSpritesheet(PlayerInput.MousePosition, l => selectedLimbs.Contains(l));
if (jointStartLimb != null)
{
anchor1Pos = GetLimbSpritesheetRect(jointStartLimb).Center.ToVector2() - PlayerInput.MousePosition;
jointCreationMode = JointCreationMode.Create;
}
}
}
else
@@ -1110,8 +1120,11 @@ namespace Barotrauma.CharacterEditor
else if (PlayerInput.PrimaryMouseButtonClicked())
{
jointStartLimb = GetClosestLimbOnRagdoll(PlayerInput.MousePosition, l => selectedLimbs.Contains(l) && !l.Hidden);
anchor1Pos = ConvertUnits.ToDisplayUnits(jointStartLimb.body.FarseerBody.GetLocalPoint(ScreenToSim(PlayerInput.MousePosition)));
jointCreationMode = JointCreationMode.Create;
if (jointStartLimb != null)
{
anchor1Pos = ConvertUnits.ToDisplayUnits(jointStartLimb.body.FarseerBody.GetLocalPoint(ScreenToSim(PlayerInput.MousePosition)));
jointCreationMode = JointCreationMode.Create;
}
}
}
}
@@ -1494,10 +1507,10 @@ namespace Barotrauma.CharacterEditor
{
DebugConsole.NewMessage(GetCharacterEditorTranslation("TryingToSpawnCharacter").Replace("[config]", speciesName.ToString()), Color.HotPink);
OnPreSpawn();
bool dontFollowCursor = true;
bool followCursor = false;
if (character != null)
{
dontFollowCursor = character.dontFollowCursor;
followCursor = character.FollowCursor;
RagdollParams.ClearHistory();
CurrentAnimation.ClearHistory();
if (!character.Removed)
@@ -1510,7 +1523,7 @@ namespace Barotrauma.CharacterEditor
{
var characterInfo = new CharacterInfo(speciesName, jobOrJobPrefab: JobPrefab.Prefabs[selectedJob.Value]);
character = Character.Create(speciesName, spawnPosition, ToolBox.RandomSeed(8), characterInfo, hasAi: false, ragdoll: ragdoll);
character.GiveJobItems();
character.GiveJobItems(isPvPMode: false);
HideWearables();
if (displayWearables)
{
@@ -1525,7 +1538,7 @@ namespace Barotrauma.CharacterEditor
}
if (character != null)
{
character.dontFollowCursor = dontFollowCursor;
character.FollowCursor = followCursor;
}
if (character == null)
{
@@ -1693,8 +1706,8 @@ namespace Barotrauma.CharacterEditor
}
else
{
config.SetAttributeValue("speciesname", name, StringComparison.OrdinalIgnoreCase);
config.SetAttributeValue("humanoid", isHumanoid, StringComparison.OrdinalIgnoreCase);
config.TrySetAttributeValue("speciesname", name);
config.TrySetAttributeValue("humanoid", isHumanoid);
var ragdollElement = config.GetChildElement("ragdolls");
if (ragdollElement == null)
{
@@ -1845,6 +1858,7 @@ namespace Barotrauma.CharacterEditor
private GUILayoutGroup rightArea, leftArea;
private GUIFrame centerArea;
private GUITextBlock totalMassText;
private GUIFrame characterSelectionPanel;
private GUIFrame fileEditPanel;
private GUIFrame modesPanel;
@@ -1920,16 +1934,13 @@ namespace Barotrauma.CharacterEditor
centerArea = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.95f), parent: Frame.RectTransform, anchor: Anchor.TopRight)
{
AbsoluteOffset = new Point((int)(rightArea.RectTransform.ScaledSize.X + rightArea.RectTransform.RelativeOffset.X * rightArea.RectTransform.Parent.ScaledSize.X + (int)(20 * GUI.xScale)), (int)(20 * GUI.yScale))
}, style: null)
{ CanBeFocused = false };
leftArea = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 0.95f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft), childAnchor: Anchor.BottomLeft)
{
RelativeSpacing = 0.02f
};
Vector2 toggleSize = new Vector2(1.0f, 0.03f);
CreateFileEditPanel();
CreateOptionsPanel(toggleSize);
CreateCharacterSelectionPanel();
@@ -1941,12 +1952,11 @@ namespace Barotrauma.CharacterEditor
optionsPanel.RectTransform.MinSize = new Point(0, (int)(optionsPanel.GetChild<GUILayoutGroup>().RectTransform.Children.Sum(c => c.Rect.Height) / innerScale.Y));
rightArea.Recalculate();
}
CreateButtonsPanel();
CreateModesPanel(toggleSize);
CreateMinorModesPanel(toggleSize);
CreateContextualControls();
totalMassText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.01f), rightArea.RectTransform), GetTotalMassText());
}
private void CreateMinorModesPanel(Vector2 toggleSize)
@@ -2209,10 +2219,10 @@ namespace Barotrauma.CharacterEditor
};
new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("FollowCursor"))
{
Selected = !character.dontFollowCursor,
Selected = character.FollowCursor,
OnSelected = box =>
{
character.dontFollowCursor = !box.Selected;
character.FollowCursor = box.Selected;
return true;
}
};
@@ -2710,7 +2720,7 @@ namespace Barotrauma.CharacterEditor
}, elementCount: 8, style: null);
jobDropDown.ListBox.Color = new Color(jobDropDown.ListBox.Color.R, jobDropDown.ListBox.Color.G, jobDropDown.ListBox.Color.B, byte.MaxValue);
jobDropDown.AddItem("None");
JobPrefab.Prefabs.ForEach(j => jobDropDown.AddItem(j.Name, j.Identifier));
JobPrefab.Prefabs.Where(j => !j.HiddenJob).ForEach(j => jobDropDown.AddItem(j.Name, j.Identifier));
jobDropDown.SelectItem(selectedJob);
jobDropDown.OnSelected = (component, data) =>
{
@@ -3112,7 +3122,7 @@ namespace Barotrauma.CharacterEditor
{
CharacterParams.Reset(true);
AnimParams.ForEach(p => p.Reset(true));
character.AnimController.ResetRagdoll(forceReload: true);
character.AnimController.ResetRagdoll();
RecreateRagdoll();
jointCreationMode = JointCreationMode.None;
isDrawingLimb = false;
@@ -3566,20 +3576,23 @@ namespace Barotrauma.CharacterEditor
int offsetX = spriteSheetOffsetX;
int offsetY = spriteSheetOffsetY;
Rectangle rect = Rectangle.Empty;
for (int i = 0; i < Textures.Count; i++)
if (Textures != null)
{
if (limb.ActiveSprite.FilePath != texturePaths[i])
for (int i = 0; i < Textures.Count; i++)
{
offsetY += (int)(Textures[i].Height * spriteSheetZoom);
}
else
{
rect = limb.ActiveSprite.SourceRect;
rect.Size = rect.MultiplySize(spriteSheetZoom);
rect.Location = rect.Location.Multiply(spriteSheetZoom);
rect.X += offsetX;
rect.Y += offsetY;
break;
if (limb.ActiveSprite.FilePath != texturePaths[i])
{
offsetY += (int)(Textures[i].Height * spriteSheetZoom);
}
else
{
rect = limb.ActiveSprite.SourceRect;
rect.Size = rect.MultiplySize(spriteSheetZoom);
rect.Location = rect.Location.Multiply(spriteSheetZoom);
rect.X += offsetX;
rect.Y += offsetY;
break;
}
}
}
return rect;

View File

@@ -300,7 +300,7 @@ namespace Barotrauma.CharacterEditor
string destinationDir = Path.GetDirectoryName(destinationPath);
if (!Directory.Exists(destinationDir))
{
Directory.CreateDirectory(destinationDir);
Directory.CreateDirectory(destinationDir, catchUnauthorizedAccessExceptions: true);
}
if (!File.Exists(destinationPath))

View File

@@ -198,7 +198,7 @@ namespace Barotrauma
msgBox.Close();
string path = Path.Combine(exportPath, $"{nameInput.Text}.xml");
File.WriteAllText(path, save.ToString());
File.WriteAllText(path, save.ToString(), catchUnauthorizedAccessExceptions: false);
AskForConfirmation(TextManager.Get("EventEditor.OpenTextHeader"), TextManager.Get("EventEditor.OpenTextBody"), () =>
{
ToolBox.OpenFileWithShell(path);
@@ -942,7 +942,7 @@ namespace Barotrauma
return false;
}
GameSession gameSession = new GameSession(subInfo, "", GameModePreset.TestMode, CampaignSettings.Empty, null);
GameSession gameSession = new GameSession(subInfo, Option.None, CampaignDataPath.Empty, GameModePreset.TestMode, CampaignSettings.Empty, null);
TestGameMode gameMode = ((TestGameMode?)gameSession.GameMode) ?? throw new InvalidCastException();
gameMode.SpawnOutpost = true;

View File

@@ -133,19 +133,7 @@ namespace Barotrauma
if (Character.Controlled == null && !GUI.DisableHUD)
{
for (int i = 0; i < Submarine.MainSubs.Length; i++)
{
if (Submarine.MainSubs[i] == null) continue;
if (Level.Loaded != null && Submarine.MainSubs[i].WorldPosition.Y < Level.MaxEntityDepth) { continue; }
Vector2 position = Submarine.MainSubs[i].SubBody != null ? Submarine.MainSubs[i].WorldPosition : Submarine.MainSubs[i].HiddenSubPosition;
Color indicatorColor = i == 0 ? Color.LightBlue * 0.5f : GUIStyle.Red * 0.5f;
GUI.DrawIndicator(
spriteBatch, position, cam,
Math.Max(Submarine.MainSub.Borders.Width, Submarine.MainSub.Borders.Height),
GUIStyle.SubmarineLocationIcon.Value.Sprite, indicatorColor);
}
DrawPositionIndicators(spriteBatch);
}
if (!GUI.DisableHUD)
@@ -164,7 +152,118 @@ namespace Barotrauma
GameMain.PerformanceCounter.AddElapsedTicks("Draw:HUD", sw.ElapsedTicks);
sw.Restart();
}
private void DrawPositionIndicators(SpriteBatch spriteBatch)
{
Sprite subLocationSprite = GUIStyle.SubLocationIcon.Value?.Sprite;
Sprite shuttleSprite = GUIStyle.ShuttleIcon.Value?.Sprite;
Sprite wreckSprite = GUIStyle.WreckIcon.Value?.Sprite;
Sprite caveSprite = GUIStyle.CaveIcon.Value?.Sprite;
Sprite outpostSprite = GUIStyle.OutpostIcon.Value?.Sprite;
Sprite ruinSprite = GUIStyle.RuinIcon.Value?.Sprite;
Sprite enemySprite = GUIStyle.EnemyIcon.Value?.Sprite;
Sprite corpseSprite = GUIStyle.CorpseIcon.Value?.Sprite;
Sprite beaconSprite = GUIStyle.BeaconIcon.Value?.Sprite;
for (int i = 0; i < Submarine.MainSubs.Length; i++)
{
if (Submarine.MainSubs[i] == null) { continue; }
if (Level.Loaded != null && Submarine.MainSubs[i].WorldPosition.Y < Level.MaxEntityDepth) { continue; }
Vector2 position = Submarine.MainSubs[i].SubBody != null ? Submarine.MainSubs[i].WorldPosition : Submarine.MainSubs[i].HiddenSubPosition;
Color indicatorColor = i == 0 ? Color.LightBlue * 0.5f : GUIStyle.Red * 0.5f;
Sprite displaySprite = Submarine.MainSubs[i].Info.HasTag(SubmarineTag.Shuttle) ? shuttleSprite : subLocationSprite;
if (displaySprite != null)
{
GUI.DrawIndicator(
spriteBatch, position, cam,
Math.Max(Submarine.MainSubs[i].Borders.Width, Submarine.MainSubs[i].Borders.Height),
displaySprite, indicatorColor);
}
}
if (!GameMain.DevMode) { return;}
if (Level.Loaded != null)
{
foreach (Level.Cave cave in Level.Loaded.Caves)
{
Vector2 position = cave.StartPos.ToVector2();
Color indicatorColor = Color.Yellow * 0.5f;
if (caveSprite != null)
{
GUI.DrawIndicator(
spriteBatch, position, cam, hideDist: 3000f,
caveSprite, indicatorColor);
}
}
}
foreach (Submarine submarine in Submarine.Loaded)
{
if (Submarine.MainSubs.Contains(submarine)) { continue; }
Vector2 position = submarine.WorldPosition;
Color teamColorIndicator = submarine.TeamID switch
{
CharacterTeamType.Team1 => Color.LightBlue * 0.5f,
CharacterTeamType.Team2 => GUIStyle.Red * 0.5f,
CharacterTeamType.FriendlyNPC => GUIStyle.Yellow * 0.5f,
_ => Color.Green * 0.5f
};
Color indicatorColor = submarine.Info.Type switch
{
SubmarineType.Outpost => Color.LightGreen,
SubmarineType.Wreck => Color.SaddleBrown,
SubmarineType.BeaconStation => Color.Azure,
SubmarineType.Ruin => Color.Purple,
_ => teamColorIndicator
};
Sprite displaySprite = submarine.Info.Type switch
{
SubmarineType.Outpost => outpostSprite,
SubmarineType.Wreck => wreckSprite,
SubmarineType.BeaconStation => beaconSprite,
SubmarineType.Ruin => ruinSprite,
_ => subLocationSprite
};
// use a little dimmer color for transports
if (submarine.Info.SubmarineClass == SubmarineClass.Transport) { indicatorColor *= 0.75f; }
if (displaySprite != null)
{
GUI.DrawIndicator(
spriteBatch, position, cam, hideDist: Math.Max(submarine.Borders.Width, submarine.Borders.Height),
displaySprite, indicatorColor);
}
}
// markers for all enemies and corpses
foreach (Character character in Character.CharacterList)
{
Vector2 position = character.WorldPosition;
Color indicatorColor = Color.DarkRed * 0.5f;
if (character.IsDead) { indicatorColor = Color.DarkGray * 0.5f; }
if (character.TeamID != CharacterTeamType.None) { continue;}
Sprite displaySprite = character.IsDead ? corpseSprite : enemySprite;
if (displaySprite != null)
{
GUI.DrawIndicator(
spriteBatch, position, cam, hideDist: 3000f,
displaySprite, indicatorColor);
}
}
}
public void DrawMap(GraphicsDevice graphics, SpriteBatch spriteBatch, double deltaTime)
{
foreach (Submarine sub in Submarine.Loaded)

View File

@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Xna.Framework.Input;
#if DEBUG
using System.IO;
using System.Xml;
@@ -20,23 +21,26 @@ namespace Barotrauma
{
public override Camera Cam { get; }
private readonly GUIFrame leftPanel, rightPanel, bottomPanel, topPanel;
private GUIFrame leftPanel, rightPanel, bottomPanel, topPanel;
private LevelGenerationParams selectedParams;
private RuinGenerationParams selectedRuinGenerationParams;
private OutpostGenerationParams selectedOutpostGenerationParams;
private LevelObjectPrefab selectedLevelObject;
private readonly GUIListBox paramsList, ruinParamsList, caveParamsList, outpostParamsList, levelObjectList;
private readonly GUIListBox editorContainer;
private GUIListBox paramsList, ruinParamsList, caveParamsList, outpostParamsList, levelObjectList;
private GUIListBox editorContainer;
private readonly GUIButton spriteEditDoneButton;
private GUIButton spriteEditDoneButton;
private readonly GUITextBox seedBox;
private GUITextBox seedBox;
private readonly GUITickBox lightingEnabled, cursorLightEnabled, allowInvalidOutpost, mirrorLevel;
private GUITickBox lightingEnabled, cursorLightEnabled, allowInvalidOutpost, mirrorLevel;
private readonly GUIDropDown selectedSubDropDown;
private readonly GUIDropDown selectedBeaconStationDropdown;
private readonly GUIDropDown selectedWreckDropdown;
private GUIDropDown selectedSubDropDown;
private GUIDropDown selectedBeaconStationDropdown;
private GUIDropDown selectedWreckDropdown;
private GUINumberInput forceDifficultyInput;
private Sprite editingSprite;
@@ -45,15 +49,32 @@ namespace Barotrauma
private readonly Color[] tunnelDebugColors = new Color[] { Color.White, Color.Cyan, Color.LightGreen, Color.Red, Color.LightYellow, Color.LightSeaGreen };
private LevelData currentLevelData;
public LevelEditorScreen()
private void RefreshUI(bool forceCreate = false)
{
Cam = new Camera()
if (forceCreate)
{
MinZoom = 0.01f,
MaxZoom = 1.0f
};
CreateUI();
}
GUI.PreventPauseMenuToggle = false;
pointerLightSource = new LightSource(Vector2.Zero, 1000.0f, Color.White, submarine: null);
GameMain.LightManager.AddLight(pointerLightSource);
topPanel.ClearChildren();
new SerializableEntityEditor(topPanel.RectTransform, pointerLightSource.LightSourceParams, false, true);
editingSprite = null;
UpdateParamsList();
UpdateRuinParamsList();
UpdateCaveParamsList();
UpdateOutpostParamsList();
UpdateLevelObjectsList();
}
private void CreateUI()
{
leftPanel?.ClearChildren();
rightPanel?.ClearChildren();
leftPanel = new GUIFrame(new RectTransform(new Vector2(0.125f, 0.8f), Frame.RectTransform) { MinSize = new Point(150, 0) });
var paddedLeftPanel = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), leftPanel.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.02f, 0.0f) })
{
@@ -72,6 +93,7 @@ namespace Barotrauma
editorContainer.ClearChildren();
SortLevelObjectsList(currentLevelData);
new SerializableEntityEditor(editorContainer.Content.RectTransform, selectedParams, inGame: false, showName: true, elementHeight: 20, titleFont: GUIStyle.LargeFont);
forceDifficultyInput.FloatValue = (selectedParams.MinLevelDifficulty + selectedParams.MaxLevelDifficulty) / 2f;
return true;
};
@@ -83,7 +105,30 @@ namespace Barotrauma
};
ruinParamsList.OnSelected += (GUIComponent component, object obj) =>
{
CreateOutpostGenerationParamsEditor(obj as OutpostGenerationParams);
if (selectedRuinGenerationParams == obj)
{
// need to wait a frame before deselecting or the highlight on the list item gets left on
CoroutineManager.StartCoroutine(DeselectRuinParams());
IEnumerable<CoroutineStatus> DeselectRuinParams()
{
if (Screen.Selected != this)
{
yield break;
}
yield return null;
selectedRuinGenerationParams = null;
CreateOutpostGenerationParamsEditor(null);
ruinParamsList.Deselect();
}
}
else
{
selectedRuinGenerationParams = obj as RuinGenerationParams;
CreateOutpostGenerationParamsEditor(selectedRuinGenerationParams);
}
return true;
};
@@ -108,7 +153,8 @@ namespace Barotrauma
};
outpostParamsList.OnSelected += (GUIComponent component, object obj) =>
{
CreateOutpostGenerationParamsEditor(obj as OutpostGenerationParams);
selectedOutpostGenerationParams = obj as OutpostGenerationParams;
CreateOutpostGenerationParamsEditor(selectedOutpostGenerationParams);
return true;
};
@@ -218,15 +264,31 @@ namespace Barotrauma
selectedWreckDropdown.AddItem(wreck.DisplayName, userData: wreck);
}
wreckDropDownContainer.RectTransform.MinSize = new Point(0, selectedWreckDropdown.RectTransform.MinSize.Y);
var forceDifficultyContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), forceDifficultyContainer.RectTransform), TextManager.Get("leveldifficulty"));
forceDifficultyInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1.0f), forceDifficultyContainer.RectTransform), NumberType.Float)
{
MinValueFloat = 0,
MaxValueFloat = 100,
FloatValue = Level.ForcedDifficulty ?? selectedParams?.MinLevelDifficulty ?? 0f,
OnValueChanged = (numberInput) =>
{
if (Level.ForcedDifficulty == null) { return; }
Level.ForcedDifficulty = numberInput.FloatValue;
}
};
forceDifficultyContainer.RectTransform.MinSize = new Point(0, forceDifficultyInput.RectTransform.MinSize.Y);
var tickBoxContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), paddedRightPanel.RectTransform), isHorizontal: true);
mirrorLevel = new GUITickBox(new RectTransform(new Vector2(0.5f, 0.02f), tickBoxContainer.RectTransform), TextManager.Get("mirrorentityx"));
mirrorLevel = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), TextManager.Get("mirrorentityx"));
allowInvalidOutpost = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.025f), paddedRightPanel.RectTransform),
allowInvalidOutpost = new GUITickBox(new RectTransform(new Vector2(0.5f, 0.025f), tickBoxContainer.RectTransform),
TextManager.Get("leveleditor.allowinvalidoutpost"))
{
ToolTip = TextManager.Get("leveleditor.allowinvalidoutpost.tooltip")
};
new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedRightPanel.RectTransform),
TextManager.Get("leveleditor.generate"))
{
@@ -240,10 +302,11 @@ namespace Barotrauma
Submarine.MainSub = new Submarine(subInfo);
}
GameMain.LightManager.ClearLights();
currentLevelData = LevelData.CreateRandom(seedBox.Text, generationParams: selectedParams);
currentLevelData = LevelData.CreateRandom(seedBox.Text, difficulty: forceDifficultyInput.FloatValue, generationParams: selectedParams);
currentLevelData.ForceOutpostGenerationParams = outpostParamsList.SelectedData as OutpostGenerationParams;
currentLevelData.ForceBeaconStation = selectedBeaconStationDropdown.SelectedData as SubmarineInfo;
currentLevelData.ForceWreck = selectedWreckDropdown.SelectedData as SubmarineInfo;
currentLevelData.ForceRuinGenerationParams = selectedRuinGenerationParams;
currentLevelData.AllowInvalidOutpost = allowInvalidOutpost.Selected;
var dummyLocations = GameSession.CreateDummyLocations(currentLevelData);
Level.Generate(currentLevelData, mirror: mirrorLevel.Selected, startLocation: dummyLocations[0], endLocation: dummyLocations[1]);
@@ -272,7 +335,6 @@ namespace Barotrauma
}
};
new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedRightPanel.RectTransform),
TextManager.Get("leveleditor.test"))
{
@@ -300,7 +362,7 @@ namespace Barotrauma
subInfo ??= SubmarineInfo.SavedSubmarines.GetRandomUnsynced(s =>
s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) &&
!nonPlayerFiles.Any(f => f.Path == s.FilePath));
GameSession gameSession = new GameSession(subInfo, "", GameModePreset.TestMode, CampaignSettings.Empty, null);
GameSession gameSession = new GameSession(subInfo, Option.None, CampaignDataPath.Empty, GameModePreset.TestMode, CampaignSettings.Empty, null);
gameSession.StartRound(Level.Loaded.LevelData);
(gameSession.GameMode as TestGameMode).OnRoundEnd = () =>
{
@@ -350,6 +412,17 @@ namespace Barotrauma
topPanel = new GUIFrame(new RectTransform(new Point(400, 100), GUI.Canvas)
{ RelativeOffset = new Vector2(leftPanel.RectTransform.RelativeSize.X * 2, 0.0f) }, style: "GUIFrameTop");
}
public LevelEditorScreen()
{
Cam = new Camera()
{
MinZoom = 0.01f,
MaxZoom = 1.0f
};
RefreshUI(forceCreate: true);
}
public void TestLevelGenerationForErrors(int amountOfLevelsToGenerate)
{
@@ -394,28 +467,13 @@ namespace Barotrauma
yield return CoroutineStatus.Running;
}
}
}
public override void Select()
{
base.Select();
GUI.PreventPauseMenuToggle = false;
pointerLightSource = new LightSource(Vector2.Zero, 1000.0f, Color.White, submarine: null);
GameMain.LightManager.AddLight(pointerLightSource);
topPanel.ClearChildren();
new SerializableEntityEditor(topPanel.RectTransform, pointerLightSource.LightSourceParams, false, true);
editingSprite = null;
UpdateParamsList();
UpdateRuinParamsList();
UpdateCaveParamsList();
UpdateOutpostParamsList();
UpdateLevelObjectsList();
RefreshUI(forceCreate: false);
}
protected override void DeselectEditorSpecific()
@@ -464,7 +522,7 @@ namespace Barotrauma
foreach (RuinGenerationParams genParams in RuinGenerationParams.RuinParams.OrderBy(p => p.Identifier))
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), ruinParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
genParams.Name)
genParams.Identifier.Value)
{
Padding = Vector4.Zero,
UserData = genParams
@@ -556,6 +614,7 @@ namespace Barotrauma
private void CreateOutpostGenerationParamsEditor(OutpostGenerationParams outpostGenerationParams)
{
editorContainer.ClearChildren();
if (outpostGenerationParams == null) { return; }
var outpostParamsEditor = new SerializableEntityEditor(editorContainer.Content.RectTransform, outpostGenerationParams, false, true, elementHeight: 20);
// location type -------------------------
@@ -584,7 +643,7 @@ namespace Barotrauma
locationTypeDropDown.SelectItem("any");
}
locationTypeDropDown.OnSelected += (_, __) =>
locationTypeDropDown.AfterSelected += (_, __) =>
{
outpostGenerationParams.SetAllowedLocationTypes(locationTypeDropDown.SelectedDataMultiple.Cast<Identifier>());
locationTypeDropDown.Text = ToolBox.LimitString(locationTypeDropDown.Text, locationTypeDropDown.Font, locationTypeDropDown.Rect.Width);
@@ -596,29 +655,29 @@ namespace Barotrauma
// module count -------------------------
var moduleLabel = new GUITextBlock(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(70 * GUI.Scale))), TextManager.Get("submarinetype.outpostmodules"), font: GUIStyle.SubHeadingFont);
outpostParamsEditor.AddCustomContent(moduleLabel, 100);
foreach (var moduleCount in outpostGenerationParams.ModuleCounts)
{
var moduleCountGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(25 * GUI.Scale))), isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), moduleCountGroup.RectTransform), TextManager.Capitalize(moduleCount.Identifier.Value), textAlignment: Alignment.CenterLeft);
new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), moduleCountGroup.RectTransform), NumberType.Int)
var editor = new SerializableEntityEditor(editorContainer.Content.RectTransform, moduleCount, inGame: false, showName: true, elementHeight: 20, titleFont: GUIStyle.Font);
foreach (var componentList in editor.Fields.Values)
{
MinValueInt = 0,
MaxValueInt = 100,
IntValue = moduleCount.Count,
OnValueChanged = (numInput) =>
foreach (var component in componentList)
{
outpostGenerationParams.SetModuleCount(moduleCount.Identifier, numInput.IntValue);
if (numInput.IntValue == 0)
if (component is GUINumberInput numberInput)
{
outpostParamsList.Select(outpostParamsList.SelectedData);
numberInput.OnValueChanged += (numInput) =>
{
if (moduleCount.Count == 0)
{
//refresh to remove this module count from the editor
outpostParamsList.Select(outpostParamsList.SelectedData);
}
};
}
}
};
moduleCountGroup.RectTransform.MinSize = new Point(moduleCountGroup.Rect.Width, moduleCountGroup.RectTransform.Children.Max(c => c.MinSize.Y));
outpostParamsEditor.AddCustomContent(moduleCountGroup, 100);
}
editor.RectTransform.MaxSize = new Point(int.MaxValue, editor.Rect.Height);
outpostParamsEditor.AddCustomContent(editor, 100);
editor.Recalculate();
}
// add module count -------------------------
@@ -648,7 +707,7 @@ namespace Barotrauma
};
addModuleCountGroup.RectTransform.MinSize = new Point(addModuleCountGroup.Rect.Width, addModuleCountGroup.RectTransform.Children.Max(c => c.MinSize.Y));
outpostParamsEditor.AddCustomContent(addModuleCountGroup, 100);
outpostParamsEditor.Recalculate();
}
private void CreateLevelObjectEditor(LevelObjectPrefab levelObjectPrefab)
@@ -736,7 +795,7 @@ namespace Barotrauma
dropdown.AddItem(objPrefab.Name, objPrefab);
if (childObj.AllowedNames.Contains(objPrefab.Name)) { dropdown.SelectItem(objPrefab); }
}
dropdown.OnSelected = (selected, obj) =>
dropdown.AfterSelected = (selected, obj) =>
{
childObj.AllowedNames = dropdown.SelectedDataMultiple.Select(d => ((LevelObjectPrefab)d).Name).ToList();
return true;
@@ -965,11 +1024,17 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
if (Level.Loaded != null)
{
float crushDepthScreen = Cam.WorldToScreen(new Vector2(0.0f, -Level.Loaded.CrushDepth)).Y;
if (crushDepthScreen > 0.0f && crushDepthScreen < GameMain.GraphicsHeight)
float hullUpgradePrcIncrease = UpgradePrefab.CrushDepthUpgradePrc / 100f;
for (int upgradeLevel = 0; upgradeLevel <= UpgradePrefab.IncreaseWallHealthMaxLevel; upgradeLevel++)
{
GUI.DrawLine(spriteBatch, new Vector2(0, crushDepthScreen), new Vector2(GameMain.GraphicsWidth, crushDepthScreen), GUIStyle.Red * 0.25f, width: 5);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, crushDepthScreen), "Crush depth", GUIStyle.Red, backgroundColor: Color.Black);
float upgradeLevelCrushDepth = Level.DefaultRealWorldCrushDepth + (Level.DefaultRealWorldCrushDepth * upgradeLevel * hullUpgradePrcIncrease);
float subCrushDepth = (upgradeLevelCrushDepth / Physics.DisplayToRealWorldRatio) - Level.Loaded.LevelData.InitialDepth;
string labelText = $"Crush depth (upgrade level {upgradeLevel})";
if (upgradeLevel == 0)
{
labelText = $"Crush depth (no upgrade)";
}
DrawCrushDepth(subCrushDepth, labelText, Color.Red);
}
float abyssStartScreen = Cam.WorldToScreen(new Vector2(0.0f, Level.Loaded.AbyssArea.Bottom)).Y;
@@ -987,9 +1052,18 @@ namespace Barotrauma
}
GUI.Draw(Cam, spriteBatch);
spriteBatch.End();
void DrawCrushDepth(float crushDepth, string labelText, Color color)
{
float crushDepthScreen = Cam.WorldToScreen(new Vector2(0.0f, -crushDepth)).Y;
if (crushDepthScreen > 0.0f && crushDepthScreen < GameMain.GraphicsHeight)
{
GUI.DrawLine(spriteBatch, new Vector2(0, crushDepthScreen), new Vector2(GameMain.GraphicsWidth, crushDepthScreen), color * 0.25f, width: 5);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, crushDepthScreen), labelText, GUIStyle.Red, backgroundColor: Color.Black);
}
}
}
public override void Update(double deltaTime)
{
if (lightingEnabled.Selected)
@@ -1016,6 +1090,19 @@ namespace Barotrauma
{
GameMain.SpriteEditorScreen.Update(deltaTime);
}
// in case forced difficulty was changed by console command or such
if (Level.ForcedDifficulty != null && MathHelper.Distance((float)Level.ForcedDifficulty, forceDifficultyInput.FloatValue) > 0.001f)
{
forceDifficultyInput.FloatValue = (float)Level.ForcedDifficulty;
}
#if DEBUG
if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.R))
{
RefreshUI(forceCreate: true);
}
#endif
}
private void SerializeAll()

View File

@@ -906,6 +906,7 @@ namespace Barotrauma
}
var gamesession = new GameSession(
selectedSub,
Option.None,
GameModePreset.DevSandbox,
missionPrefabs: null);
//(gamesession.GameMode as SinglePlayerCampaign).GenerateMap(ToolBox.RandomSeed(8));
@@ -1251,12 +1252,12 @@ namespace Barotrauma
if (!Directory.Exists(SaveUtil.TempPath))
{
Directory.CreateDirectory(SaveUtil.TempPath);
Directory.CreateDirectory(SaveUtil.TempPath, catchUnauthorizedAccessExceptions: true);
}
try
{
File.Copy(selectedSub.FilePath, Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), true);
File.Copy(selectedSub.FilePath, Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), overwrite: true, catchUnauthorizedAccessExceptions: false);
}
catch (System.IO.IOException e)
{
@@ -1270,7 +1271,7 @@ namespace Barotrauma
selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"));
GameMain.GameSession = new GameSession(selectedSub, savePath, GameModePreset.SinglePlayerCampaign, settings, mapSeed);
GameMain.GameSession = new GameSession(selectedSub, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.SinglePlayerCampaign, settings, mapSeed);
GameMain.GameSession.CrewManager.ClearCharacterInfos();
foreach (var characterInfo in campaignSetupUI.CharacterMenus.Select(m => m.CharacterInfo))
{
@@ -1279,17 +1280,22 @@ namespace Barotrauma
((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel();
}
private void LoadGame(string saveFile)
private void LoadGame(string path, Option<uint> backupIndex)
{
if (string.IsNullOrWhiteSpace(saveFile)) return;
if (string.IsNullOrWhiteSpace(path)) return;
try
{
SaveUtil.LoadGame(saveFile);
CampaignDataPath dataPath =
backupIndex.TryUnwrap(out uint index)
? new CampaignDataPath(loadPath: SaveUtil.GetBackupPath(path, index), path)
: CampaignDataPath.CreateRegular(path);
SaveUtil.LoadGame(dataPath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Loading save \"" + saveFile + "\" failed", e);
DebugConsole.ThrowError("Loading save \"" + path + "\" failed", e);
return;
}

Some files were not shown because too many files have changed in this diff Show More