v0.14.6.0

This commit is contained in:
Joonas Rikkonen
2021-06-17 17:54:52 +03:00
parent 3f324b14e8
commit c27e2ea5ab
348 changed files with 13156 additions and 4266 deletions

6
.gitignore vendored
View File

@@ -40,9 +40,5 @@ Libraries/webm_mem_playback/opus_x64_linux/
# Win
desktop.ini
# Merge script
#Merge script
temp.txt
# Private assets
Barotrauma/BarotraumaShared/Content/*
.github/ISSUE_TEMPLATE/release-checklist.md

View File

@@ -9,7 +9,7 @@ namespace Barotrauma
{
public override void DebugDraw(SpriteBatch spriteBatch)
{
if (Character.IsDead) return;
if (Character.IsUnconscious || !Character.Enabled || !Enabled) { return; }
Vector2 pos = Character.WorldPosition;
pos.Y = -pos.Y;
@@ -38,7 +38,7 @@ namespace Barotrauma
}
targetPos.Y = -targetPos.Y;
GUI.DrawLine(spriteBatch, pos, targetPos, GUI.Style.Red * 0.5f, 0, 4);
if (wallTarget != null && (State == AIState.Attack || State == AIState.Aggressive || State == AIState.PassiveAggressive))
if (wallTarget != null)
{
Vector2 wallTargetPos = wallTarget.Position;
if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.Position; }

View File

@@ -0,0 +1,32 @@
namespace Barotrauma
{
abstract partial class AIObjective
{
public static Sprite GetSprite(string identifier, string option, Entity targetEntity)
{
if (string.IsNullOrEmpty(identifier))
{
return null;
}
identifier = identifier.RemoveWhitespace();
if (Order.Prefabs.TryGetValue(identifier, out Order orderPrefab))
{
if (!string.IsNullOrEmpty(option) && orderPrefab.OptionSprites.TryGetValue(option, out var optionSprite))
{
return optionSprite;
}
if (targetEntity is Item targetItem && targetItem.Prefab.MinimapIcon != null)
{
return targetItem.Prefab.MinimapIcon;
}
return orderPrefab.SymbolSprite;
}
return GUI.Style.GetComponentStyle($"{identifier}objectiveicon")?.GetDefaultSprite();
}
public Sprite GetSprite()
{
return GetSprite(Identifier, Option, (this as AIObjectiveOperateItem)?.OperateTarget);
}
}
}

View File

@@ -39,10 +39,7 @@ namespace Barotrauma
partial void DamageParticles(float deltaTime, Vector2 worldPosition)
{
if (particleEmitter != null)
{
particleEmitter.Emit(deltaTime, worldPosition);
}
particleEmitter?.Emit(deltaTime, worldPosition);
if (sound != null)
{

View File

@@ -102,11 +102,13 @@ namespace Barotrauma
set { chromaticAberrationStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public Color GrainColor { get; set; }
private float grainStrength;
public float GrainStrength
{
get => grainStrength;
set => grainStrength = MathHelper.Clamp(value, 0.0f, 1.0f);
set => grainStrength = Math.Max(0, value);
}
private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
@@ -199,7 +201,7 @@ namespace Barotrauma
/// </summary>
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
{
if (DisableControls || GUI.PauseMenuOpen || GUI.SettingsMenuOpen)
if (DisableControls || GUI.InputBlockingMenuOpen)
{
foreach (Key key in keys)
{
@@ -321,7 +323,7 @@ namespace Barotrauma
DoInteractionUpdate(deltaTime, mouseSimPos);
}
if (!GUI.PauseMenuOpen && !GUI.SettingsMenuOpen)
if (!GUI.InputBlockingMenuOpen)
{
if (SelectedConstruction != null &&
(SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
@@ -505,14 +507,16 @@ namespace Barotrauma
{
continue;
}
if (item.body != null && !item.body.Enabled) continue;
if (item.ParentInventory != null) continue;
if (ignoredItems != null && ignoredItems.Contains(item)) continue;
if (item.body != null && !item.body.Enabled) { continue; }
if (item.ParentInventory != null) { continue; }
if (ignoredItems != null && ignoredItems.Contains(item)) { continue; }
if (item.Prefab.RequireCampaignInteract && item.CampaignInteractionType == CampaignMode.InteractionType.None) { continue; }
if (Screen.Selected is SubEditorScreen editor && editor.WiringMode && item.GetComponent<ConnectionPanel>() == null) { continue; }
if (draggingItemToWorld)
{
if (item.OwnInventory == null ||
!item.OwnInventory.Container.AllowDragAndDrop ||
!item.OwnInventory.CanBePut(CharacterInventory.DraggingItems.First()) ||
!CanAccessInventory(item.OwnInventory))
{
@@ -677,7 +681,7 @@ namespace Barotrauma
else
{
//Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm
hudInfoVisible = controlled.CanSeeCharacter(this, controlled.ViewTarget == null ? controlled.WorldPosition : controlled.ViewTarget.WorldPosition);
hudInfoVisible = controlled.CanSeeTarget(this, controlled.ViewTarget);
}
hudInfoTimer = Rand.Range(0.5f, 1.0f);
}
@@ -859,7 +863,14 @@ namespace Barotrauma
Color nameColor = Color.White;
if (Controlled != null && TeamID != Controlled.TeamID)
{
nameColor = TeamID == CharacterTeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
if (TeamID == CharacterTeamType.FriendlyNPC)
{
nameColor = UniqueNameColor ?? Color.SkyBlue;
}
else
{
nameColor = GUI.Style.Red;
}
}
if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract)
{

View File

@@ -296,7 +296,7 @@ namespace Barotrauma
float alpha = GetDistanceBasedIconAlpha(brokenItem);
if (alpha <= 0.0f) continue;
GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUI.BrokenIcon,
Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
}
float GetDistanceBasedIconAlpha(ISpatialEntity target, float maxDistance = 1000.0f)
@@ -341,7 +341,7 @@ namespace Barotrauma
if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
{
bool shiftDown = PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift);
bool shiftDown = PlayerInput.IsShiftDown();
if (shouldRecreateHudTexts || heldDownShiftWhenGotHudTexts != shiftDown)
{
shouldRecreateHudTexts = true;
@@ -391,7 +391,16 @@ namespace Barotrauma
if (npc.CampaignInteractionType == CampaignMode.InteractionType.None || npc.Submarine != character.Submarine || npc.IsDead || npc.IsIncapacitated) { continue; }
var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + npc.CampaignInteractionType);
GUI.DrawIndicator(spriteBatch, npc.WorldPosition, cam, 500.0f, iconStyle.GetDefaultSprite(), iconStyle.Color);
GUI.DrawIndicator(spriteBatch, npc.WorldPosition, cam, npc.CurrentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, iconStyle.GetDefaultSprite(), iconStyle.Color);
}
foreach (Item item in Item.ItemList)
{
if (item.IconStyle is null || item.Submarine != character.Submarine) { continue; }
if (Vector2.DistanceSquared(character.Position, item.Position) > 500f*500f) { continue; }
var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true);
if (body != null && body.UserData as Item != item) { continue; }
GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Vector2(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false);
}
}

View File

@@ -291,8 +291,7 @@ namespace Barotrauma
break;
case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 6);
int eventType = msg.ReadRangedInteger(0, 9);
switch (eventType)
{
case 0: //NetEntityEvent.Type.InventoryState
@@ -303,9 +302,9 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
//read anyway to prevent messing up reading the rest of the message
UInt16 lastEventID = msg.ReadUInt16();
byte itemCount = msg.ReadByte();
for (int i = 0; i < itemCount; i++)
_ = msg.ReadUInt16();
byte inventoryItemCount = msg.ReadByte();
for (int i = 0; i < inventoryItemCount; i++)
{
msg.ReadUInt16();
}
@@ -354,56 +353,102 @@ namespace Barotrauma
info?.SetSkillLevel(skillIdentifier, skillLevel, Position + Vector2.UnitY * 150.0f);
}
break;
case 4: //NetEntityEvent.Type.ExecuteAttack
case 4: // NetEntityEvent.Type.SetAttackTarget
case 5: //NetEntityEvent.Type.ExecuteAttack
int attackLimbIndex = msg.ReadByte();
UInt16 targetEntityID = msg.ReadUInt16();
int targetLimbIndex = msg.ReadByte();
Vector2 targetSimPos = new Vector2(msg.ReadSingle(), msg.ReadSingle());
//255 = entity already removed, no need to do anything
if (attackLimbIndex == 255 || Removed) { break; }
if (attackLimbIndex >= AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})");
DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})");
break;
}
Limb attackLimb = AnimController.Limbs[attackLimbIndex];
Limb targetLimb = null;
if (!(FindEntityByID(targetEntityID) is IDamageable targetEntity))
{
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target entity not found (ID {targetEntityID})");
DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Target entity not found (ID {targetEntityID})");
break;
}
if (targetEntity is Character targetCharacter)
{
if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
break;
}
targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
}
if (attackLimb?.attack != null)
{
attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
if (eventType == 4)
{
SetAttackTarget(attackLimb, targetEntity, targetSimPos);
}
else
{
attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
}
}
break;
case 5: //NetEntityEvent.Type.AssignCampaignInteraction
case 6: //NetEntityEvent.Type.AssignCampaignInteraction
byte campaignInteractionType = msg.ReadByte();
bool requireConsciousness = msg.ReadBoolean();
(GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType);
RequireConsciousnessForCustomInteract = requireConsciousness;
break;
case 6: //NetEntityEvent.Type.ObjectiveManagerOrderState
bool properData = msg.ReadBoolean();
if (!properData) { break; }
int orderIndex = msg.ReadRangedInteger(0, Order.PrefabList.Count);
var orderPrefab = Order.PrefabList[orderIndex];
string option = null;
if (orderPrefab.HasOptions)
case 7: //NetEntityEvent.Type.ObjectiveManagerState
// 1 = order, 2 = objective
int msgType = msg.ReadRangedInteger(0, 2);
if (msgType == 0) { break; }
bool validData = msg.ReadBoolean();
if (!validData) { break; }
if (msgType == 1)
{
int optionIndex = msg.ReadRangedInteger(0, orderPrefab.Options.Length);
option = orderPrefab.Options[optionIndex];
int orderIndex = msg.ReadRangedInteger(0, Order.PrefabList.Count);
var orderPrefab = Order.PrefabList[orderIndex];
string option = null;
if (orderPrefab.HasOptions)
{
int optionIndex = msg.ReadRangedInteger(-1, orderPrefab.AllOptions.Length);
if (optionIndex > -1)
{
option = orderPrefab.AllOptions[optionIndex];
}
}
GameMain.GameSession?.CrewManager?.SetOrderHighlight(this, orderPrefab.Identifier, option);
}
else if (msgType == 2)
{
string identifier = msg.ReadString();
string option = msg.ReadString();
ushort objectiveTargetEntityId = msg.ReadUInt16();
var objectiveTargetEntity = FindEntityByID(objectiveTargetEntityId);
GameMain.GameSession?.CrewManager?.CreateObjectiveIcon(this, identifier, option, objectiveTargetEntity);
}
break;
case 8: //NetEntityEvent.Type.TeamChange
byte newTeamId = msg.ReadByte();
ChangeTeam((CharacterTeamType)newTeamId);
break;
case 9: //NetEntityEvent.Type.AddToCrew
GameMain.GameSession.CrewManager.AddCharacter(this);
CharacterTeamType teamID = (CharacterTeamType)msg.ReadByte();
ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++)
{
ushort itemID = msg.ReadUInt16();
if (!(Entity.FindEntityByID(itemID) is Item item)) { continue; }
item.AllowStealing = true;
var wifiComponent = item.GetComponent<Items.Components.WifiComponent>();
if (wifiComponent != null)
{
wifiComponent.TeamID = teamID;
}
}
GameMain.GameSession.CrewManager.SetHighlightedOrderIcon(this, orderPrefab.Identifier, option);
break;
}
msg.ReadPadBits();
@@ -415,7 +460,7 @@ namespace Barotrauma
{
DebugConsole.Log("Reading character spawn data");
if (GameMain.Client == null) return null;
if (GameMain.Client == null) { return null; }
bool noInfo = inc.ReadBoolean();
ushort id = inc.ReadUInt16();
@@ -431,7 +476,15 @@ namespace Barotrauma
Character character = null;
if (noInfo)
{
character = Create(speciesName, position, seed, characterInfo: null, id: id, isRemotePlayer: false);
try
{
character = Create(speciesName, position, seed, characterInfo: null, id: id, isRemotePlayer: false);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to spawn character {speciesName}", e);
throw;
}
bool containsStatusData = inc.ReadBoolean();
if (containsStatusData)
{
@@ -447,8 +500,15 @@ namespace Barotrauma
string infoSpeciesName = inc.ReadString();
CharacterInfo info = CharacterInfo.ClientRead(infoSpeciesName, inc);
character = Create(speciesName, position, seed, characterInfo: info, id: id, isRemotePlayer: ownerId > 0 && GameMain.Client.ID != ownerId, hasAi: hasAi);
try
{
character = Create(speciesName, position, seed, characterInfo: info, id: id, isRemotePlayer: ownerId > 0 && GameMain.Client.ID != ownerId, hasAi: hasAi);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to spawn character {speciesName}", e);
throw;
}
character.TeamID = (CharacterTeamType)teamID;
character.CampaignInteractionType = (CampaignMode.InteractionType)inc.ReadByte();
if (character.CampaignInteractionType != CampaignMode.InteractionType.None)
@@ -471,7 +531,7 @@ namespace Barotrauma
var x = inc.ReadSingle();
var y = inc.ReadSingle();
var hull = FindEntityByID(inc.ReadUInt16()) as Hull;
targetPosition = new OrderTarget(new Vector2(x, y), hull, true);
targetPosition = new OrderTarget(new Vector2(x, y), hull, creatingFromExistingData: true);
}
if (orderPrefabIndex >= 0 && orderPrefabIndex < Order.PrefabList.Count)
@@ -485,7 +545,7 @@ namespace Barotrauma
new Order(orderPrefab, targetPosition, orderGiver: orderGiver);
character.SetOrder(order,
orderOptionIndex >= 0 && orderOptionIndex < orderPrefab.Options.Length ? orderPrefab.Options[orderOptionIndex] : null,
orderPriority, orderGiver, speak: false);
orderPriority, orderGiver, speak: false, force: true);
}
else
{

View File

@@ -63,6 +63,8 @@ namespace Barotrauma
private GUIListBox afflictionTooltip;
private static readonly Color oxygenLowGrainColor = new Color(0.1f, 0.1f, 0.1f, 1f);
private struct HeartratePosition
{
public float Time;
@@ -671,17 +673,19 @@ namespace Barotrauma
bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f);
if (bloodParticleTimer <= 0.0f)
{
var emitter = Character.BloodEmitters.FirstOrDefault();
float particleMinScale = emitter != null ? emitter.Prefab.ScaleMin : 0.5f;
float particleMaxScale = emitter != null ? emitter.Prefab.ScaleMax : 1;
bool inWater = Character.AnimController.InWater;
var drawTarget = inWater ? Particles.ParticlePrefab.DrawTargetType.Water : Particles.ParticlePrefab.DrawTargetType.Air;
var emitter = Character.BloodEmitters.FirstOrDefault(e => e.Prefab.ParticlePrefab.DrawTarget == drawTarget || e.Prefab.ParticlePrefab.DrawTarget == Particles.ParticlePrefab.DrawTargetType.Both);
float particleMinScale = emitter?.Prefab.Properties.ScaleMin ?? 0.5f;
float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1;
float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1);
float bloodParticleSize = MathHelper.Lerp(particleMinScale, particleMaxScale, severity);
bool inWater = Character.AnimController.InWater;
if (!inWater)
{
bloodParticleSize *= 2.0f;
}
// TODO: use the blood emitter?
var blood = GameMain.ParticleManager.CreateParticle(
inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir,
targetLimb.WorldPosition, Rand.Vector(affliction.Strength), 0.0f, Character.AnimController.CurrentHull);
@@ -694,6 +698,12 @@ namespace Barotrauma
}
}
public static bool IsMouseOnHealthBar()
{
if (Character.Controlled?.CharacterHealth == null) { return false; }
return Character.Controlled.CharacterHealth.healthBar.State == GUIComponent.ComponentState.Hover;
}
public void UpdateHUD(float deltaTime)
{
if (GUI.DisableHUD) return;
@@ -742,7 +752,9 @@ namespace Barotrauma
float radialDistortStrength = 0.0f;
float chromaticAberrationStrength = 0.0f;
float grainStrength = 0.0f;
Color grainColor = Color.Transparent;
float oxygenLowStrength = 0.0f;
if (Character.IsUnconscious)
{
blurStrength = 1.0f;
@@ -750,10 +762,14 @@ namespace Barotrauma
}
else if (OxygenAmount < 100.0f)
{
blurStrength = MathHelper.Lerp(0.5f, 1.0f, 1.0f - Vitality / MaxVitality);
distortStrength = blurStrength;
distortSpeed = (blurStrength + 1.0f);
oxygenLowStrength = Math.Min(1.0f - (OxygenAmount - LowOxygenThreshold) / LowOxygenThreshold, 1.0f);
blurStrength = MathHelper.Lerp(0.5f, 1.0f, 1.0f - Vitality / MaxVitality) * oxygenLowStrength;
distortStrength = blurStrength * oxygenLowStrength;
distortSpeed = blurStrength + 1.0f;
distortSpeed *= distortSpeed * distortSpeed * distortSpeed;
grainStrength = MathHelper.Lerp(0.5f, 10.0f, oxygenLowStrength);
grainColor = oxygenLowGrainColor;
}
foreach (Affliction affliction in afflictions)
@@ -762,7 +778,12 @@ namespace Barotrauma
blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength());
grainStrength = Math.Max(grainStrength, affliction.GetScreenGrainStrength());
float afflictionGrainStrength = affliction.GetScreenGrainStrength();
if (afflictionGrainStrength > 0.0f)
{
grainStrength = Math.Max(grainStrength, affliction.GetScreenGrainStrength());
grainColor = Color.Lerp(grainColor, Color.White, (float)Math.Pow(1.0f - oxygenLowStrength, 2));
}
}
foreach (LimbHealth limbHealth in limbHealths)
{
@@ -778,6 +799,7 @@ namespace Barotrauma
Character.RadialDistortStrength = radialDistortStrength;
Character.ChromaticAberrationStrength = chromaticAberrationStrength;
Character.GrainStrength = grainStrength;
Character.GrainColor = grainColor;
if (blurStrength > 0.0f)
{
distortTimer = (distortTimer + deltaTime * distortSpeed) % MathHelper.TwoPi;
@@ -955,11 +977,9 @@ namespace Barotrauma
highlightedLimbIndex = -1;
}
Rectangle hoverArea = Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea);
healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud();
if (Character.AllowInput && UseHealthWindow && healthBar.Enabled && healthBar.CanBeFocused &&
hoverArea.Contains(PlayerInput.MousePosition) && Inventory.SelectedSlot == null)
(GUI.IsMouseOn(healthBar) || highlightedAfflictionIcon != null) && Inventory.SelectedSlot == null)
{
healthBar.State = GUIComponent.ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonClicked())
@@ -1076,8 +1096,11 @@ namespace Barotrauma
DrawStatusHUD(spriteBatch);
}
private Pair<Affliction, string> highlightedAfflictionIcon = null;
public void DrawStatusHUD(SpriteBatch spriteBatch)
{
highlightedAfflictionIcon = null;
//Rectangle interactArea = healthBar.Rect;
if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null)
{
@@ -1092,7 +1115,6 @@ namespace Barotrauma
statusIcons.Add(new Pair<Affliction, string>(affliction, affliction.Prefab.Name));
}
Pair<Affliction, string> highlightedIcon = null;
Vector2 highlightedIconPos = Vector2.Zero;
Rectangle afflictionArea = HUDLayoutSettings.AfflictionAreaLeft;
@@ -1113,9 +1135,9 @@ namespace Barotrauma
AfflictionPrefab afflictionPrefab = affliction.Prefab;
Rectangle afflictionIconRect = new Rectangle(pos, new Point(iconSize));
if (afflictionIconRect.Contains(PlayerInput.MousePosition) && !Character.ShouldLockHud())
if (afflictionIconRect.Contains(PlayerInput.MousePosition) && !Character.ShouldLockHud() && GUI.MouseOn == null)
{
highlightedIcon = statusIcon;
highlightedAfflictionIcon = statusIcon;
highlightedIconPos = afflictionIconRect.Location.ToVector2();
}
@@ -1135,7 +1157,7 @@ namespace Barotrauma
highlightedIcon == statusIcon ? slot.HoverColor : slot.Color);*/
float alphaMultiplier = highlightedIcon == statusIcon ? 1f : 0.8f;
float alphaMultiplier = highlightedAfflictionIcon == statusIcon ? 1f : 0.8f;
afflictionPrefab.Icon?.Draw(spriteBatch,
pos.ToVector2(),
@@ -1150,9 +1172,9 @@ namespace Barotrauma
pos.Y += iconSize + (int)(5 * GUI.Scale);
}
if (highlightedIcon != null)
if (highlightedAfflictionIcon != null)
{
string nameTooltip = highlightedIcon.Second;
string nameTooltip = highlightedAfflictionIcon.Second;
Vector2 offset = GUI.Font.MeasureString(nameTooltip);
GUI.DrawString(spriteBatch,
@@ -1315,7 +1337,7 @@ namespace Barotrauma
child.Recalculate();
}
if (buttonToSelect != null) { buttonToSelect.OnClicked(buttonToSelect, "selectaffliction"); }
buttonToSelect?.OnClicked(buttonToSelect, "selectaffliction");
afflictionIconContainer.RecalculateChildren();
@@ -2004,7 +2026,7 @@ namespace Barotrauma
existingAffliction.PeriodicEffectTimers[periodicEffect.First] = periodicEffect.Second;
foreach (StatusEffect effect in periodicEffect.First.StatusEffects)
{
existingAffliction.ApplyStatusEffect(effect, deltaTime: 1.0f, this, targetLimb: null);
existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: null);
}
}
}
@@ -2071,7 +2093,7 @@ namespace Barotrauma
foreach (StatusEffect effect in periodicEffect.First.StatusEffects)
{
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(newAffliction.First));
existingAffliction.ApplyStatusEffect(effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
}
}
}

View File

@@ -997,7 +997,7 @@ namespace Barotrauma
}
wearableColor = wearableItemComponent.Item.GetSpriteColor();
}
float textureScale = wearable.InheritTextureScale ? TextureScale : 1;
float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale;
wearable.Sprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),

View File

@@ -15,6 +15,7 @@ using Barotrauma.Extensions;
using Barotrauma.Steam;
using System.Threading.Tasks;
using Barotrauma.MapCreatures.Behavior;
using static Barotrauma.FabricationRecipe;
namespace Barotrauma
{
@@ -240,6 +241,7 @@ namespace Barotrauma
case "toggleupperhud":
case "togglecharacternames":
case "fpscounter":
case "showperf":
case "dumptofile":
case "findentityids":
case "setfreecamspeed":
@@ -1367,6 +1369,241 @@ namespace Barotrauma
}
}, isCheat: false));
commands.Add(new Command("analyzeitem", "analyzeitem: Analyzes one item for exploits.", (string[] args) =>
{
if (args.Length < 1) return;
List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>();
foreach (ItemPrefab iPrefab in ItemPrefab.Prefabs)
{
fabricableItems.AddRange(iPrefab.FabricationRecipes);
}
string itemNameOrId = args[0].ToLowerInvariant();
ItemPrefab itemPrefab =
(MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab;
if (itemPrefab == null)
{
NewMessage("Item not found for analyzing.");
return;
}
NewMessage("Analyzing item " + itemPrefab.Name + " with base cost " + itemPrefab.DefaultPrice.Price);
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab);
// omega nesting incoming
if (fabricationRecipe != null)
{
foreach (KeyValuePair<string, PriceInfo> itemLocationPrice in itemPrefab.GetSellPricesOver(0))
{
NewMessage(" If bought at " + itemLocationPrice.Key + " it costs " + itemLocationPrice.Value.Price);
int totalPrice = 0;
int? totalBestPrice = 0;
foreach (var ingredient in fabricationRecipe.RequiredItems)
{
foreach (ItemPrefab ingredientItemPrefab in ingredient.ItemPrefabs)
{
NewMessage(" Its ingredient " + ingredientItemPrefab.Name + " has base cost " + ingredientItemPrefab.DefaultPrice.Price);
totalPrice += ingredientItemPrefab.DefaultPrice.Price;
totalBestPrice += ingredientItemPrefab.GetMinPrice();
int basePrice = ingredientItemPrefab.DefaultPrice.Price;
foreach (KeyValuePair<string, PriceInfo> ingredientItemLocationPrice in ingredientItemPrefab.GetBuyPricesUnder())
{
if (basePrice > ingredientItemLocationPrice.Value.Price)
{
NewMessage(" Location " + ingredientItemLocationPrice.Key + " sells ingredient " + ingredientItemPrefab.Name + " for cheaper, " + ingredientItemLocationPrice.Value.Price, Color.Yellow);
}
else
{
NewMessage(" Location " + ingredientItemLocationPrice.Key + " sells ingredient " + ingredientItemPrefab.Name + " for more, " + ingredientItemLocationPrice.Value.Price, Color.Teal);
}
}
}
}
int costDifference = itemPrefab.DefaultPrice.Price - totalPrice;
NewMessage(" Constructing the item from store-bought items provides " + costDifference + " profit with default values.");
if (totalBestPrice.HasValue)
{
int? bestDifference = itemLocationPrice.Value.Price - totalBestPrice;
NewMessage(" Constructing the item from store-bought items provides " + bestDifference + " profit with best-case scenario values.");
}
}
}
},
() =>
{
return new string[][] { ItemPrefab.Prefabs.SelectMany(p => p.Aliases).Concat(ItemPrefab.Prefabs.Select(p => p.Identifier)).ToArray() };
}, isCheat: false));
commands.Add(new Command("checkcraftingexploits", "checkcraftingexploits: Finds outright item exploits created by buying store-bought ingredients and constructing them into sellable items.", (string[] args) =>
{
List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>();
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
{
fabricableItems.AddRange(itemPrefab.FabricationRecipes);
}
List<Tuple<string, int>> costDifferences = new List<Tuple<string, int>>();
int maximumAllowedCost = 5;
if (args.Length > 0)
{
Int32.TryParse(args[0], out maximumAllowedCost);
}
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
{
int? defaultCost = itemPrefab.DefaultPrice?.Price;
int? fabricationCostStore = null;
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab);
if (fabricationRecipe == null)
{
continue;
}
bool canBeBought = true;
foreach (var ingredient in fabricationRecipe.RequiredItems)
{
int? ingredientPrice = ingredient.ItemPrefabs.Where(p => p.CanBeBought).Min(ip => ip.DefaultPrice?.Price);
if (ingredientPrice.HasValue)
{
if (!fabricationCostStore.HasValue) { fabricationCostStore = 0; }
float useAmount = ingredient.UseCondition ? ingredient.MinCondition : 1.0f;
fabricationCostStore += (int)(ingredientPrice.Value * ingredient.Amount * useAmount);
}
else
{
canBeBought = false;
}
}
if (fabricationCostStore.HasValue && defaultCost.HasValue && canBeBought)
{
int costDifference = defaultCost.Value - fabricationCostStore.Value;
if (costDifference > maximumAllowedCost || costDifference < 0f)
{
float ratio = (float)fabricationCostStore.Value / defaultCost.Value;
string message = "Fabricating \"" + itemPrefab.Name + "\" costs " + (int)(ratio * 100) + "% of the price of the item, or " + costDifference + " more. Item price: " + defaultCost.Value + ", ingredient prices: " + fabricationCostStore.Value;
costDifferences.Add(new Tuple<string, int>(message, costDifference));
}
}
}
costDifferences.Sort((x, y) => x.Item2.CompareTo(y.Item2));
foreach (Tuple<string, int> costDifference in costDifferences)
{
Color color = Color.Yellow;
NewMessage(costDifference.Item1, color);
}
}, isCheat: false));
commands.Add(new Command("adjustprice", "adjustprice: Recursively prints out expected price adjustments for items derived from this item.", (string[] args) =>
{
List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>();
foreach (ItemPrefab iP in ItemPrefab.Prefabs)
{
fabricableItems.AddRange(iP.FabricationRecipes);
}
if (args.Length < 2)
{
NewMessage("Item or value not defined.");
return;
}
string itemNameOrId = args[0].ToLowerInvariant();
ItemPrefab materialPrefab =
(MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab;
if (materialPrefab == null)
{
NewMessage("Item not found for price adjustment.");
return;
}
AdjustItemTypes adjustItemType = AdjustItemTypes.NoAdjustment;
if (args.Length > 2)
{
switch (args[2].ToLowerInvariant())
{
case "add":
adjustItemType = AdjustItemTypes.Additive;
break;
case "mult":
adjustItemType = AdjustItemTypes.Multiplicative;
break;
}
}
if (Int32.TryParse(args[1].ToLowerInvariant(), out int newPrice))
{
Dictionary<ItemPrefab, int> newPrices = new Dictionary<ItemPrefab, int>();
PrintItemCosts(newPrices, materialPrefab, fabricableItems, newPrice, true, adjustItemType: adjustItemType);
PrintItemCosts(newPrices, materialPrefab, fabricableItems, newPrice, false, adjustItemType: adjustItemType);
}
}, isCheat: false));
commands.Add(new Command("deconstructvalue", "deconstructvalue: Views and compares deconstructed component prices for this item.", (string[] args) =>
{
List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>();
foreach (ItemPrefab iP in ItemPrefab.Prefabs)
{
fabricableItems.AddRange(iP.FabricationRecipes);
}
if (args.Length < 1)
{
NewMessage("Item not defined.");
return;
}
string itemNameOrId = args[0].ToLowerInvariant();
ItemPrefab parentItem =
(MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab;
if (parentItem == null)
{
NewMessage("Item not found for price adjustment.");
return;
}
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == parentItem);
int totalValue = 0;
NewMessage(parentItem.Name + " has the price " + parentItem.DefaultPrice.Price);
if (fabricationRecipe != null)
{
NewMessage(" It constructs from:");
foreach (RequiredItem requiredItem in fabricationRecipe.RequiredItems)
{
foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs)
{
NewMessage(" " + itemPrefab.Name + " has the price " + itemPrefab.DefaultPrice.Price);
totalValue += itemPrefab.DefaultPrice.Price;
}
}
NewMessage("Its total value was: " + totalValue);
totalValue = 0;
}
NewMessage(" The item deconstructs into:");
foreach (DeconstructItem deconstructItem in parentItem.DeconstructItems)
{
ItemPrefab itemPrefab =
(MapEntityPrefab.Find(deconstructItem.ItemIdentifier, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab;
NewMessage(" " + itemPrefab.Name + " has the price " + itemPrefab.DefaultPrice.Price);
totalValue += itemPrefab.DefaultPrice.Price;
}
NewMessage("Its deconstruct value was: " + totalValue);
}, isCheat: false));
commands.Add(new Command("setentityproperties", "setentityproperties [property name] [value]: Sets the value of some property on all selected items/structures in the sub editor.", (string[] args) =>
{
if (args.Length != 2 || Screen.Selected != GameMain.SubEditorScreen) { return; }
@@ -2935,5 +3172,155 @@ namespace Barotrauma
return false;
}
}
private enum AdjustItemTypes
{
NoAdjustment,
Additive,
Multiplicative
}
private static void PrintItemCosts(Dictionary<ItemPrefab, int> newPrices, ItemPrefab materialPrefab, List<FabricationRecipe> fabricableItems, int newPrice, bool adjustDown, string depth = "", AdjustItemTypes adjustItemType = AdjustItemTypes.NoAdjustment)
{
if (newPrice < 1)
{
NewMessage(depth + materialPrefab.Name + " cannot be adjusted to this price, because it would become less than 1.");
return;
}
depth += " ";
if (newPrice > 0)
{
newPrices.TryAdd(materialPrefab, newPrice);
}
int componentCost = 0;
int newComponentCost = 0;
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == materialPrefab);
if (fabricationRecipe != null)
{
foreach (RequiredItem requiredItem in fabricationRecipe.RequiredItems)
{
foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs)
{
GetAdjustedPrice(itemPrefab, ref componentCost, ref newComponentCost, newPrices);
}
}
}
string componentCostMultiplier = "";
if (componentCost > 0)
{
componentCostMultiplier = $" (Relative difference to component cost {GetComponentCostDifference(materialPrefab.DefaultPrice.Price, componentCost)} => {GetComponentCostDifference(newPrice, newComponentCost)}, or flat profit {(int)(materialPrefab.DefaultPrice.Price - (int)componentCost)} => {newPrice - newComponentCost})";
}
string priceAdjustment = "";
if (newPrice != materialPrefab.DefaultPrice.Price)
{
priceAdjustment = ", Suggested price adjustment is " + materialPrefab.DefaultPrice.Price + " => " + newPrice;
}
NewMessage(depth + materialPrefab.Name + "(" + materialPrefab.DefaultPrice.Price + ") " + priceAdjustment + componentCostMultiplier);
if (adjustDown)
{
if (componentCost > 0)
{
double newPriceMult = (double)newPrice / (double)(materialPrefab.DefaultPrice.Price);
int newPriceDiff = componentCost + newPrice - materialPrefab.DefaultPrice.Price;
switch (adjustItemType)
{
case AdjustItemTypes.Additive:
NewMessage(depth + materialPrefab.Name + "'s components should be adjusted " + componentCost + " => " + newPriceDiff);
break;
case AdjustItemTypes.Multiplicative:
NewMessage(depth + materialPrefab.Name + "'s components should be adjusted " + componentCost + " => " + Math.Round(newPriceMult * componentCost));
break;
}
if (fabricationRecipe != null)
{
foreach (RequiredItem requiredItem in fabricationRecipe.RequiredItems)
{
foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs)
{
if (itemPrefab.DefaultPrice != null)
{
switch (adjustItemType)
{
case AdjustItemTypes.NoAdjustment:
PrintItemCosts(newPrices, itemPrefab, fabricableItems, itemPrefab.DefaultPrice.Price, adjustDown, depth, adjustItemType);
break;
case AdjustItemTypes.Additive:
PrintItemCosts(newPrices, itemPrefab, fabricableItems, itemPrefab.DefaultPrice.Price + (int)((newPrice - materialPrefab.DefaultPrice.Price) / (double)fabricationRecipe.RequiredItems.Count), adjustDown, depth, adjustItemType);
break;
case AdjustItemTypes.Multiplicative:
PrintItemCosts(newPrices, itemPrefab, fabricableItems, (int)(itemPrefab.DefaultPrice.Price * newPriceMult), adjustDown, depth, adjustItemType);
break;
}
}
}
}
}
}
}
else
{
var fabricationRecipes = fabricableItems.Where(f => f.RequiredItems.Any(x => x.ItemPrefabs.Contains(materialPrefab)));
foreach (FabricationRecipe fabricationRecipeParent in fabricationRecipes)
{
if (fabricationRecipeParent.TargetItem.DefaultPrice != null)
{
int targetComponentCost = 0;
int newTargetComponentCost = 0;
foreach (RequiredItem requiredItem in fabricationRecipeParent.RequiredItems)
{
foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs)
{
GetAdjustedPrice(itemPrefab, ref targetComponentCost, ref newTargetComponentCost, newPrices);
}
}
switch (adjustItemType)
{
case AdjustItemTypes.NoAdjustment:
PrintItemCosts(newPrices, fabricationRecipeParent.TargetItem, fabricableItems, fabricationRecipeParent.TargetItem.DefaultPrice.Price, adjustDown, depth, adjustItemType);
break;
case AdjustItemTypes.Additive:
PrintItemCosts(newPrices, fabricationRecipeParent.TargetItem, fabricableItems, fabricationRecipeParent.TargetItem.DefaultPrice.Price + newPrice - materialPrefab.DefaultPrice.Price, adjustDown, depth, adjustItemType);
break;
case AdjustItemTypes.Multiplicative:
double maintainedMultiplier = GetComponentCostDifference(fabricationRecipeParent.TargetItem.DefaultPrice.Price, targetComponentCost);
PrintItemCosts(newPrices, fabricationRecipeParent.TargetItem, fabricableItems, (int)(newTargetComponentCost * maintainedMultiplier), adjustDown, depth, adjustItemType);
break;
}
}
}
}
}
private static double GetComponentCostDifference(int itemCost, int componentCost)
{
return Math.Round((double)(itemCost / (double)componentCost), 2);
}
private static void GetAdjustedPrice(ItemPrefab itemPrefab, ref int componentCost, ref int newComponentCost, Dictionary<ItemPrefab, int> newPrices)
{
if (newPrices.TryGetValue(itemPrefab, out int newPrice))
{
newComponentCost += newPrice;
}
else if (itemPrefab.DefaultPrice != null)
{
newComponentCost += itemPrefab.DefaultPrice.Price;
}
if (itemPrefab.DefaultPrice != null)
{
componentCost += itemPrefab.DefaultPrice.Price;
}
}
}
}

View File

@@ -10,6 +10,8 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Hull hull, float depth)
{
if (Sprite.Texture == null) { return; }
Vector2 drawPos = position + hull.Rect.Location.ToVector2();
if (hull.Submarine != null) { drawPos += hull.Submarine.DrawPosition; }
drawPos.Y = -drawPos.Y;

View File

@@ -55,6 +55,12 @@ namespace Barotrauma
{
Debug.Assert(actionInstance == null || actionId == null);
if (GUI.InputBlockingMenuOpen)
{
if (actionId.HasValue) { SendIgnore(actionId.Value); }
return;
}
shouldFadeToBlack = fadeToBlack;
if (lastMessageBox != null && !lastMessageBox.Closed && GUIMessageBox.MessageBoxes.Contains(lastMessageBox))
@@ -368,6 +374,15 @@ namespace Barotrauma
GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable);
}
private static void SendIgnore(UInt16 actionId)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE);
outmsg.Write(actionId);
outmsg.Write(byte.MaxValue);
GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable);
}
// Too broken, left it here if I ever want to come back to it
private static List<RichTextData> GetQuoteHighlights(string text, Color color)
{

View File

@@ -52,6 +52,7 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, new Vector2(15, y + 95), "FloodingAmount: " + (int)Math.Round(floodingAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, floodingAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 110), "FireAmount: " + (int)Math.Round(fireAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, fireAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 125), "EnemyDanger: " + (int)Math.Round(enemyDanger * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, enemyDanger), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 140), "MonsterTotalStrength: " + (int)Math.Round(monsterTotalStrength), Color.Lerp(GUI.Style.Green, GUI.Style.Red, monsterTotalStrength / 5000f), Color.Black * 0.6f, 0, GUI.SmallFont);
#if DEBUG
if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) &&
@@ -75,7 +76,7 @@ namespace Barotrauma
lastIntensityUpdate = (float) Timing.TotalTime;
}
Rectangle graphRect = new Rectangle(15, y + 150, 150, 50);
Rectangle graphRect = new Rectangle(15, y + 165, 150, 50);
GUI.DrawRectangle(spriteBatch, graphRect, Color.Black * 0.5f, true);
intensityGraph.Draw(spriteBatch, graphRect, 1.0f, 0.0f, Color.Lerp(Color.White, GUI.Style.Red, currentIntensity));

View File

@@ -22,6 +22,13 @@ namespace Barotrauma
public override void ClientReadInitial(IReadMessage msg)
{
ushort targetItemCount = msg.ReadUInt16();
for (int i = 0; i < targetItemCount; i++)
{
var item = Item.ReadSpawnData(msg);
items.Add(item);
}
byte characterCount = msg.ReadByte();
for (int i = 0; i < characterCount; i++)

View File

@@ -1,9 +1,28 @@
using Barotrauma.Networking;
using System.Globalization;
namespace Barotrauma
{
partial class CargoMission : Mission
{
public override string GetMissionRewardText(Submarine sub)
{
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub)));
if (rewardPerCrate.HasValue)
{
string rewardPerCrateText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", rewardPerCrate.Value));
return TextManager.GetWithVariables("missionrewardcargopercrate",
new string[] { "[rewardpercrate]", "[itemcount]", "[maxitemcount]", "[totalreward]" },
new string[] { rewardPerCrateText, itemsToSpawn.Count.ToString(), maxItemCount.ToString(), $"‖color:gui.orange‖{rewardText}‖end‖" });
}
else
{
return TextManager.GetWithVariables("missionrewardcargo",
new string[] { "[totalreward]", "[itemcount]", "[maxitemcount]" },
new string[] { $"‖color:gui.orange‖{rewardText}‖end‖", itemsToSpawn.Count.ToString(), maxItemCount.ToString() });
}
}
public override void ClientReadInitial(IReadMessage msg)
{
items.Clear();

View File

@@ -0,0 +1,37 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class EscortMission : Mission
{
public override void ClientReadInitial(IReadMessage msg)
{
byte characterCount = msg.ReadByte();
for (int i = 0; i < characterCount; i++)
{
Character character = Character.ReadSpawnData(msg);
characters.Add(character);
if (msg.ReadBoolean())
{
terroristCharacters.Add(character);
}
ushort itemCount = msg.ReadUInt16();
for (int j = 0; j < itemCount; j++)
{
Item.ReadSpawnData(msg);
}
}
if (characters.Contains(null))
{
throw new System.Exception("Error in EscortMission.ClientReadInitial: character list contains null (mission: " + Prefab.Identifier + ")");
}
if (characters.Count != characterCount)
{
throw new System.Exception("Error in EscortMission.ClientReadInitial: character count does not match the server count (" + characterCount + " != " + characters.Count + "mission: " + Prefab.Identifier + ")");
}
InitCharacters();
}
}
}

View File

@@ -21,9 +21,9 @@ namespace Barotrauma
return ToolBox.GradientLerp(t, GUI.Style.Green, GUI.Style.Orange, GUI.Style.Red);
}
public string GetMissionRewardText()
public virtual string GetMissionRewardText(Submarine sub)
{
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", Reward));
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub)));
return TextManager.GetWithVariable("missionreward", "[reward]", $"‖color:gui.orange‖{rewardText}‖end‖");
}

View File

@@ -1,18 +0,0 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class OutpostDestroyMission : AbandonedOutpostMission
{
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++)
{
var item = Item.ReadSpawnData(msg);
items.Add(item);
}
}
}
}

View File

@@ -0,0 +1,32 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class PirateMission : Mission
{
public override void ClientReadInitial(IReadMessage msg)
{
// duplicate code from escortmission, should possibly be combined, though additional loot items might be added so maybe not
byte characterCount = msg.ReadByte();
for (int i = 0; i < characterCount; i++)
{
characters.Add(Character.ReadSpawnData(msg));
ushort itemCount = msg.ReadUInt16();
for (int j = 0; j < itemCount; j++)
{
Item.ReadSpawnData(msg);
}
}
if (characters.Contains(null))
{
throw new System.Exception("Error in PirateMission.ClientReadInitial: character list contains null (mission: " + Prefab.Identifier + ")");
}
if (characters.Count != characterCount)
{
throw new System.Exception("Error in PirateMission.ClientReadInitial: character count does not match the server count (" + characterCount + " != " + characters.Count + "mission: " + Prefab.Identifier + ")");
}
}
}
}

View File

@@ -208,6 +208,19 @@ namespace Barotrauma
get { return pauseMenuOpen; }
}
public static bool InputBlockingMenuOpen
{
get
{
return PauseMenuOpen ||
SettingsMenuOpen ||
DebugConsole.IsOpen ||
GameSession.IsTabMenuOpen ||
(GameMain.GameSession?.GameMode?.Paused ?? false) ||
CharacterHUD.IsCampaignInterfaceOpen;
}
}
public static bool PreventPauseMenuToggle = false;
public static Color ScreenOverlayColor
@@ -226,6 +239,9 @@ namespace Barotrauma
private static SavingIndicatorState savingIndicatorState = SavingIndicatorState.None;
private static float? timeUntilSavingIndicatorDisabled;
private static string loadedSpritesText;
private static DateTime loadedSpritesUpdateTime;
private enum SavingIndicatorState
{
None,
@@ -441,9 +457,12 @@ namespace Barotrauma
"Particle count: " + GameMain.ParticleManager.ParticleCount + "/" + GameMain.ParticleManager.MaxParticles,
Color.Lerp(GUI.Style.Green, GUI.Style.Red, (GameMain.ParticleManager.ParticleCount / (float)GameMain.ParticleManager.MaxParticles)), Color.Black * 0.5f, 0, SmallFont);
DrawString(spriteBatch, new Vector2(10, 115),
"Loaded sprites: " + Sprite.LoadedSprites.Count() + "\n(" + Sprite.LoadedSprites.Select(s => s.FilePath).Distinct().Count() + " unique textures)",
Color.White, Color.Black * 0.5f, 0, SmallFont);
if (loadedSpritesText == null || DateTime.Now > loadedSpritesUpdateTime)
{
loadedSpritesText = "Loaded sprites: " + Sprite.LoadedSprites.Count() + "\n(" + Sprite.LoadedSprites.Select(s => s.FilePath).Distinct().Count() + " unique textures)";
loadedSpritesUpdateTime = DateTime.Now + new TimeSpan(0, 0, seconds: 5);
}
DrawString(spriteBatch, new Vector2(10, 115), loadedSpritesText, Color.White, Color.Black * 0.5f, 0, SmallFont);
if (debugDrawSounds)
{
@@ -978,8 +997,7 @@ namespace Barotrauma
return editor.GetMouseCursorState();
// Portrait area during gameplay
case GameScreen _ when !(Character.Controlled?.ShouldLockHud() ?? true):
if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) ||
Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea).Contains(PlayerInput.MousePosition))
if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) || CharacterHealth.IsMouseOnHealthBar())
{
return CursorState.Hand;
}
@@ -1331,7 +1349,7 @@ namespace Barotrauma
/// <param name="createOffset">Should the indicator move based on the camera position?</param>
/// <param name="overrideAlpha">Override the distance-based alpha value with the specified alpha value</param>
public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam, float hideDist, Sprite sprite, Color color,
public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Vector2 visibleRange, Sprite sprite, in Color color,
bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null)
{
Vector2 diff = worldPosition - cam.WorldViewCenter;
@@ -1339,9 +1357,9 @@ namespace Barotrauma
float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier * Scale;
if (overrideAlpha.HasValue || dist > hideDist)
if (overrideAlpha.HasValue || (dist > visibleRange.X && dist < visibleRange.Y))
{
float alpha = overrideAlpha ?? Math.Min((dist - hideDist) / 100.0f, 1.0f);
float alpha = overrideAlpha ?? MathUtils.Min((dist - visibleRange.X) / 100.0f, 1.0f - ((dist - visibleRange.Y + 100f) / 100.0f), 1.0f);
Vector2 targetScreenPos = cam.WorldToScreen(worldPosition);
if (!createOffset)
@@ -1352,8 +1370,9 @@ namespace Barotrauma
float screenDist = Vector2.Distance(cam.WorldToScreen(cam.WorldViewCenter), targetScreenPos);
float angle = MathUtils.VectorToAngle(diff);
float originalAngle = angle;
float minAngleDiff = 0.05f;
const float minAngleDiff = 0.05f;
bool overlapFound = true;
int iterations = 0;
while (overlapFound && iterations < 10)
@@ -1375,18 +1394,24 @@ namespace Barotrauma
usedIndicatorAngles.Add(angle);
Vector2 unclampedDiff = new Vector2(
(float)Math.Cos(angle) * screenDist,
(float)-Math.Sin(angle) * screenDist);
Vector2 iconDiff = new Vector2(
(float)Math.Cos(angle) * Math.Min(GameMain.GraphicsWidth * 0.4f, screenDist + 10),
(float)-Math.Sin(angle) * Math.Min(GameMain.GraphicsHeight * 0.4f, screenDist + 10));
angle = MathHelper.Lerp(originalAngle, angle, MathHelper.Clamp(((screenDist + 10f) - iconDiff.Length()) / 10f, 0f, 1f));
/*Vector2 unclampedDiff = new Vector2(
(float)Math.Cos(angle) * screenDist,
(float)-Math.Sin(angle) * screenDist);*/
iconDiff = new Vector2(
(float)Math.Cos(angle) * Math.Min(GameMain.GraphicsWidth * 0.4f, screenDist),
(float)-Math.Sin(angle) * Math.Min(GameMain.GraphicsHeight * 0.4f, screenDist));
Vector2 iconPos = cam.WorldToScreen(cam.WorldViewCenter) + iconDiff;
sprite.Draw(spriteBatch, iconPos, color * alpha, rotate: 0.0f, scale: symbolScale);
if (unclampedDiff.Length() - 10 > iconDiff.Length())
if (/*unclampedDiff.Length()*/ screenDist - 10 > iconDiff.Length())
{
Vector2 normalizedDiff = Vector2.Normalize(targetScreenPos - iconPos);
Vector2 arrowOffset = normalizedDiff * sprite.size.X * symbolScale * 0.7f;
@@ -1395,6 +1420,12 @@ namespace Barotrauma
}
}
public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam, float hideDist, Sprite sprite, Color color,
bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null)
{
DrawIndicator(spriteBatch, worldPosition, cam, new Vector2(hideDist, float.PositiveInfinity), sprite, color, createOffset, scaleMultiplier, overrideAlpha);
}
public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, float width = 1)
{
DrawLine(sb, t, start, end, clr, depth, (int)width);
@@ -1495,6 +1526,22 @@ namespace Barotrauma
}
}
public static void DrawFilledRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, float depth = 0.0f)
{
if (size.X < 0)
{
start.X += size.X;
size.X = -size.X;
}
if (size.Y < 0)
{
start.Y += size.Y;
size.Y = -size.Y;
}
sb.Draw(t, start, null, clr, 0f, Vector2.Zero, size, SpriteEffects.None, depth);
}
public static void DrawRectangle(SpriteBatch sb, Vector2 center, float width, float height, float rotation, Color clr, float depth = 0.0f, float thickness = 1)
{
Matrix rotate = Matrix.CreateRotationZ(rotation);
@@ -1971,6 +2018,30 @@ namespace Barotrauma
}
return frame;
}
public static GUIMessageBox AskForConfirmation(string header, string body, Action onConfirm, Action onDeny = null)
{
string[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") };
GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
// Cancel button
msgBox.Buttons[1].OnClicked = delegate
{
onDeny?.Invoke();
msgBox.Close();
return true;
};
// Ok button
msgBox.Buttons[0].OnClicked = delegate
{
onConfirm.Invoke();
msgBox.Close();
return true;
};
return msgBox;
}
#endregion
#region Element positioning
@@ -2098,7 +2169,7 @@ namespace Barotrauma
for (int j = i + 1; j < elements.Count; j++)
{
Rectangle rect2 = elements[j].Rect;
if (!rect1.Intersects(rect2)) continue;
if (!rect1.Intersects(rect2)) { continue; }
intersections = true;
Point centerDiff = rect1.Center - rect2.Center;
@@ -2127,10 +2198,10 @@ namespace Barotrauma
elements[j].RectTransform.ScreenSpaceOffset += moveAmount2.ToPoint();
}
if (disallowedAreas == null) continue;
if (disallowedAreas == null) { continue; }
foreach (Rectangle rect2 in disallowedAreas)
{
if (!rect1.Intersects(rect2)) continue;
if (!rect1.Intersects(rect2)) { continue; }
intersections = true;
Point centerDiff = rect1.Center - rect2.Center;
@@ -2148,7 +2219,7 @@ namespace Barotrauma
iterations++;
}
Vector2 ClampMoveAmount(Rectangle Rect, Rectangle clampTo, Vector2 moveAmount)
static Vector2 ClampMoveAmount(Rectangle Rect, Rectangle clampTo, Vector2 moveAmount)
{
if (Rect.Y < clampTo.Y)
{
@@ -2221,6 +2292,7 @@ namespace Barotrauma
}
};
bool IsOutpostLevel() => GameMain.GameSession != null && Level.IsLoadedOutpost;
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession != null)
{
if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode)
@@ -2253,45 +2325,22 @@ namespace Barotrauma
};
return true;
};
var saveAndQuitButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit"))
if (IsOutpostLevel())
{
UserData = "save"
};
saveAndQuitButton.OnClicked += (btn, userdata) =>
{
//Only allow saving mid-round in outpost levels. Quitting in the middle of a mission reset progress to the start of the round.
if (GameMain.GameSession == null)
var saveAndQuitButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit"))
{
pauseMenuOpen = false;
}
else if (GameMain.GameSession?.Campaign == null || Level.IsLoadedOutpost)
{
pauseMenuOpen = false;
GameMain.QuitToMainMenu(save: true);
}
else
{
var msgBox = new GUIMessageBox("", TextManager.Get("PauseMenuSaveAndQuitVerification", fallBackTag: "pausemenuquitverification"), new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") })
{
UserData = "verificationprompt"
};
msgBox.Buttons[0].OnClicked = (_, userdata) =>
UserData = "save",
OnClicked = (btn, userData) =>
{
pauseMenuOpen = false;
GameMain.QuitToMainMenu(save: false);
if (IsOutpostLevel())
{
GameMain.QuitToMainMenu(save: true);
}
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = (_, userdata) =>
{
pauseMenuOpen = false;
msgBox.Close();
return true;
};
}
return true;
};
}
};
}
}
else if (GameMain.GameSession.GameMode is TestGameMode)
{
@@ -2313,7 +2362,7 @@ namespace Barotrauma
OnClicked = (btn, userdata) =>
{
if (!GameMain.Client.HasPermission(ClientPermissions.ManageRound)) { return false; }
if (GameMain.GameSession.GameMode is CampaignMode || (!Submarine.MainSub.AtStartExit && !Submarine.MainSub.AtEndExit))
if (GameMain.GameSession.GameMode is CampaignMode && !IsOutpostLevel() || (!Submarine.MainSub.AtStartExit && !Submarine.MainSub.AtEndExit))
{
var msgBox = new GUIMessageBox("",
TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd"),

View File

@@ -201,7 +201,7 @@ namespace Barotrauma
{
base.ApplyStyle(style);
if (frame != null) { frame.ApplyStyle(style); }
frame?.ApplyStyle(style);
}
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null)

View File

@@ -59,9 +59,7 @@ namespace Barotrauma
private bool useGridLayout;
private float targetScroll;
private GUIComponent pendingScroll;
private GUIComponent scrollToElement;
public bool AllowMouseWheelScroll { get; set; } = true;
@@ -238,8 +236,6 @@ namespace Barotrauma
public GUIComponent DraggedElement => draggedElement;
private bool scheduledScroll = false;
private readonly bool isHorizontal;
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
@@ -429,7 +425,14 @@ namespace Barotrauma
int index = children.IndexOf(component);
if (index < 0) { return; }
targetScroll = MathHelper.Clamp(MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, (children.Count - 0.9f), index)), ScrollBar.MinValue, ScrollBar.MaxValue);
if (!Content.Children.Contains(component) || !component.Visible)
{
scrollToElement = null;
}
else
{
scrollToElement = component;
}
}
public void ScrollToEnd(float duration)
@@ -533,7 +536,7 @@ namespace Barotrauma
}
}
if (SelectTop && Content.Children.Any() && pendingScroll == null)
if (SelectTop && Content.Children.Any() && scrollToElement == null)
{
GUIComponent component = Content.Children.FirstOrDefault(c => (c.Rect.Y - Content.Rect.Y) / (float)c.Rect.Height > -0.1f);
@@ -563,7 +566,6 @@ namespace Barotrauma
{
if (SelectTop)
{
pendingScroll = child;
ScrollToElement(child);
Select(i, autoScroll: false, takeKeyBoardFocus: true);
}
@@ -728,25 +730,29 @@ namespace Barotrauma
}
}
}
if (SmoothScroll)
{
if (targetScroll > -1)
{
float distance = Math.Abs(targetScroll - BarScroll);
float speed = Math.Max(distance * BarSize, 0.1f);
BarScroll = (1.0f - speed) * BarScroll + speed * targetScroll;
if (MathUtils.NearlyEqual(BarScroll, targetScroll) || GUIScrollBar.DraggingBar != null)
{
targetScroll = -1;
pendingScroll = null;
}
}
}
if (scrollToElement != null)
{
if (!scrollToElement.Visible || !Content.Children.Contains(scrollToElement))
{
scrollToElement = null;
}
else
{
float diff = isHorizontal ? scrollToElement.Rect.X - Content.Rect.X : scrollToElement.Rect.Y - Content.Rect.Y;
float speed = MathHelper.Clamp(Math.Abs(diff) * 0.1f, 5.0f, 100.0f);
System.Diagnostics.Debug.WriteLine(speed);
if (Math.Abs(diff) < speed || GUIScrollBar.DraggingBar != null)
{
speed = Math.Abs(diff);
scrollToElement = null;
}
BarScroll += speed * Math.Sign(diff) / TotalSize;
}
}
if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && AllowMouseWheelScroll && PlayerInput.ScrollWheelSpeed != 0)
{
float speed = PlayerInput.ScrollWheelSpeed / 500.0f * BarSize;
if (SmoothScroll)
{
if (ClampScrollToElements)
@@ -762,13 +768,6 @@ namespace Barotrauma
SelectNext(takeKeyBoardFocus: true);
}
}
else
{
pendingScroll = null;
if (targetScroll < 0) { targetScroll = BarScroll; }
targetScroll -= speed;
targetScroll = Math.Clamp(targetScroll, ScrollBar.MinValue, ScrollBar.MaxValue);
}
}
else
{
@@ -799,7 +798,6 @@ namespace Barotrauma
Select(index, force, !SmoothScroll && autoScroll, takeKeyBoardFocus: takeKeyBoardFocus);
if (SmoothScroll)
{
pendingScroll = child;
ScrollToElement(child);
}
break;
@@ -819,7 +817,6 @@ namespace Barotrauma
Select(index, force, !SmoothScroll && autoScroll, takeKeyBoardFocus: takeKeyBoardFocus);
if (SmoothScroll)
{
pendingScroll = child;
ScrollToElement(child);
}
break;

View File

@@ -551,7 +551,7 @@ namespace Barotrauma
}
if (openState >= 2.0f)
{
if (Parent != null) { Parent.RemoveChild(this); }
Parent?.RemoveChild(this);
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
}
}
@@ -604,7 +604,7 @@ namespace Barotrauma
}
else
{
if (Parent != null) { Parent.RemoveChild(this); }
Parent?.RemoveChild(this);
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
}

View File

@@ -140,6 +140,7 @@ namespace Barotrauma
{
if (value == intValue) { return; }
intValue = value;
ClampIntValue();
UpdateText();
}
}

View File

@@ -658,10 +658,7 @@ namespace Barotrauma
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData);
}
if (Strikethrough != null)
{
Strikethrough.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X, ForceUpperCase ? pos.Y : pos.Y + GUI.Scale * 2f);
}
Strikethrough?.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X, ForceUpperCase ? pos.Y : pos.Y + GUI.Scale * 2f);
}
if (overflowClipActive)

View File

@@ -49,8 +49,11 @@ namespace Barotrauma
get { return _caretIndex; }
set
{
_caretIndex = value;
caretPosDirty = true;
if (value >= 0)
{
_caretIndex = value;
caretPosDirty = true;
}
}
}
private bool caretPosDirty;
@@ -454,7 +457,7 @@ namespace Barotrauma
}
if (!isSelecting)
{
isSelecting = PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift);
isSelecting = PlayerInput.IsShiftDown();
}
if (mouseHeldInside && !PlayerInput.PrimaryMouseButtonHeld())
@@ -879,15 +882,22 @@ namespace Barotrauma
selectionEndIndex = Math.Min(CaretIndex, textDrawn.Length);
selectionEndPos = caretPos;
selectedCharacters = Math.Abs(selectionStartIndex - selectionEndIndex);
if (IsLeftToRight)
try
{
selectedText = Text.Substring(selectionStartIndex, selectedCharacters);
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionStartIndex, selectedCharacters)) * TextBlock.TextScale;
if (IsLeftToRight)
{
selectedText = Text.Substring(selectionStartIndex, Math.Min(selectedCharacters, Text.Length));
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionStartIndex, Math.Min(selectedCharacters, textDrawn.Length))) * TextBlock.TextScale;
}
else
{
selectedText = Text.Substring(selectionEndIndex, Math.Min(selectedCharacters, Text.Length));
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionEndIndex, Math.Min(selectedCharacters, textDrawn.Length))) * TextBlock.TextScale;
}
}
else
catch (ArgumentOutOfRangeException exception)
{
selectedText = Text.Substring(selectionEndIndex, Math.Min(selectedCharacters, textDrawn.Length - selectionEndIndex));
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionEndIndex, selectedCharacters)) * TextBlock.TextScale;
DebugConsole.ThrowError($"GUITextBox: Invalid selection: ({exception})");
}
}
}

View File

@@ -17,47 +17,63 @@ namespace Barotrauma
private readonly Dictionary<StoreTab, GUIListBox> tabLists = new Dictionary<StoreTab, GUIListBox>();
private readonly Dictionary<StoreTab, SortingMethod> tabSortingMethods = new Dictionary<StoreTab, SortingMethod>();
private readonly List<PurchasedItem> itemsToSell = new List<PurchasedItem>();
private readonly List<PurchasedItem> itemsToSellFromSub = new List<PurchasedItem>();
private StoreTab activeTab = StoreTab.Buy;
private MapEntityCategory? selectedItemCategory;
private bool suppressBuySell;
private int buyTotal, sellTotal;
private int buyTotal, sellTotal, sellFromSubTotal;
private GUITextBlock merchantBalanceBlock;
private GUITextBlock currentSellValueBlock, newSellValueBlock;
private GUIImage sellValueChangeArrow;
private GUIDropDown sortingDropDown;
private GUITextBox searchBox;
private GUIListBox storeBuyList, storeSellList;
private GUIListBox storeBuyList, storeSellList, storeSellFromSubList;
/// <summary>
/// Can be null when there are no deals at the current location
/// </summary>
private GUILayoutGroup storeDailySpecialsGroup, storeRequestedGoodGroup;
private GUILayoutGroup storeDailySpecialsGroup, storeRequestedGoodGroup, storeRequestedSubGoodGroup;
private Color storeSpecialColor;
private GUIListBox shoppingCrateBuyList, shoppingCrateSellList;
private GUIListBox shoppingCrateBuyList, shoppingCrateSellList, shoppingCrateSellFromSubList;
private GUITextBlock shoppingCrateTotal;
private GUIButton clearAllButton, confirmButton;
private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh;
private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh, needsSellingFromSubRefresh, needsItemsToSellFromSubRefresh;
private Point resolutionWhenCreated;
private bool hadPermissions;
private Dictionary<ItemPrefab, int> OwnedItems { get; } = new Dictionary<ItemPrefab, int>();
private Dictionary<ItemPrefab, int> OwnedItems { get; } = new Dictionary<ItemPrefab, int>();
private CargoManager CargoManager => campaignUI.Campaign.CargoManager;
private Location CurrentLocation => campaignUI.Campaign.Map?.CurrentLocation;
private int PlayerMoney => campaignUI.Campaign.Money;
private bool HasPermissions => campaignUI.Campaign.AllowedToManageCampaign();
private bool IsBuying => activeTab != StoreTab.Sell;
private bool IsSelling => activeTab == StoreTab.Sell;
private GUIListBox ActiveShoppingCrateList => IsBuying ? shoppingCrateBuyList : shoppingCrateSellList;
private bool IsBuying => activeTab switch
{
StoreTab.Buy => true,
StoreTab.Sell => false,
StoreTab.SellFromSub => false,
_ => throw new NotImplementedException()
};
private bool IsSelling => !IsBuying;
private GUIListBox ActiveShoppingCrateList => activeTab switch
{
StoreTab.Buy => shoppingCrateBuyList,
StoreTab.Sell => shoppingCrateSellList,
StoreTab.SellFromSub => shoppingCrateSellFromSubList,
_ => throw new NotImplementedException()
};
private enum StoreTab
private bool IsTabUnavailable(StoreTab tab) => !tabLists.ContainsKey(tab);
public enum StoreTab
{
Buy,
Sell
Sell,
SellFromSub
}
private enum SortingMethod
@@ -73,11 +89,8 @@ namespace Barotrauma
{
this.campaignUI = campaignUI;
this.parentComponent = parentComponent;
hadPermissions = HasPermissions;
CreateUI();
campaignUI.Campaign.Map.OnLocationChanged += UpdateLocation;
if (CurrentLocation?.Reputation != null)
{
@@ -89,8 +102,10 @@ namespace Barotrauma
campaignUI.Campaign.CargoManager.OnSoldItemsChanged += () =>
{
needsItemsToSellRefresh = true;
needsItemsToSellFromSubRefresh = true;
needsRefresh = true;
};
campaignUI.Campaign.CargoManager.OnItemsInSellFromSubCrateChanged += () => { needsSellingFromSubRefresh = true; };
}
public void Refresh(bool updateOwned = true)
@@ -99,6 +114,7 @@ namespace Barotrauma
if (updateOwned) { UpdateOwnedItems(); }
RefreshBuying(updateOwned: false);
RefreshSelling(updateOwned: false);
RefreshSellingFromSub(updateOwned: false);
needsRefresh = false;
}
@@ -124,6 +140,20 @@ namespace Barotrauma
needsSellingRefresh = false;
}
private void RefreshSellingFromSub(bool updateOwned = true, bool updateItemsToSellFromSub = true)
{
if (IsTabUnavailable(StoreTab.SellFromSub)) { return; }
if (updateOwned) { UpdateOwnedItems(); }
if (updateItemsToSellFromSub) RefreshItemsToSellFromSub();
RefreshShoppingCrateSellFromSubList();
RefreshStoreSellFromSubList();
// TODO: Separate permissions from regular campaign permissions
var hasPermissions = HasPermissions;
storeSellFromSubList.Enabled = hasPermissions;
shoppingCrateSellFromSubList.Enabled = hasPermissions;
needsSellingFromSubRefresh = false;
}
private void CreateUI()
{
if (parentComponent.FindChild(c => c.UserData as string == "glow") is GUIComponent glowChild)
@@ -236,9 +266,13 @@ namespace Barotrauma
{
if (CurrentLocation != null)
{
int balanceAfterTransaction = IsBuying ?
CurrentLocation.StoreCurrentBalance + buyTotal :
CurrentLocation.StoreCurrentBalance - sellTotal;
int balanceAfterTransaction = activeTab switch
{
StoreTab.Buy => CurrentLocation.StoreCurrentBalance + buyTotal,
StoreTab.Sell => CurrentLocation.StoreCurrentBalance - sellTotal,
StoreTab.SellFromSub => CurrentLocation.StoreCurrentBalance - sellFromSubTotal,
_ => throw new NotImplementedException(),
};
if (balanceAfterTransaction != CurrentLocation.StoreCurrentBalance)
{
var newStatus = Location.GetStoreBalanceStatus(balanceAfterTransaction);
@@ -300,8 +334,14 @@ namespace Barotrauma
tabSortingMethods.Clear();
foreach (StoreTab tab in tabs)
{
if (tab == StoreTab.SellFromSub && GameMain.IsMultiplayer) { continue; }
string text = tab switch
{
StoreTab.SellFromSub => TextManager.Get("submarine"),
_ => TextManager.Get("campaignstoretab." + tab)
};
var tabButton = new GUIButton(new RectTransform(new Vector2(1.0f / (tabs.Length + 1), 1.0f), modeButtonContainer.RectTransform),
text: TextManager.Get("campaignstoretab." + tab), style: "GUITabButton")
text: text, style: "GUITabButton")
{
UserData = tab,
OnClicked = (button, userData) =>
@@ -416,6 +456,17 @@ namespace Barotrauma
storeRequestedGoodGroup = CreateDealsGroup(storeSellList);
tabLists.Add(StoreTab.Sell, storeSellList);
if (GameMain.IsSingleplayer)
{
storeSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, storeItemListContainer.RectTransform))
{
AutoHideScrollBar = false,
Visible = false
};
storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList);
tabLists.Add(StoreTab.SellFromSub, storeSellFromSubList);
}
// Shopping Crate ------------------------------------------------------------------------------------------------------------------------------------------
var shoppingCrateContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor: Anchor.TopRight)
@@ -475,6 +526,10 @@ namespace Barotrauma
var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), shoppingCrateInventoryContainer.RectTransform), style: null);
shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
if (GameMain.IsSingleplayer)
{
shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
}
var totalContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true)
{
@@ -504,7 +559,13 @@ namespace Barotrauma
OnClicked = (button, userData) =>
{
if (!HasPermissions) { return false; }
var itemsToRemove = new List<PurchasedItem>(IsBuying ? CargoManager.ItemsInBuyCrate : CargoManager.ItemsInSellCrate);
var itemsToRemove = activeTab switch
{
StoreTab.Buy => new List<PurchasedItem>(CargoManager.ItemsInBuyCrate),
StoreTab.Sell => new List<PurchasedItem>(CargoManager.ItemsInSellCrate),
StoreTab.SellFromSub => new List<PurchasedItem>(CargoManager.ItemsInSellFromSubCrate),
_ => throw new NotImplementedException(),
};
itemsToRemove.ForEach(i => ClearFromShoppingCrate(i));
return true;
}
@@ -560,6 +621,7 @@ namespace Barotrauma
private void ChangeStoreTab(StoreTab tab)
{
if (IsTabUnavailable(tab)) { return; }
activeTab = tab;
foreach (GUIButton tabButton in storeTabButtons)
{
@@ -571,24 +633,56 @@ namespace Barotrauma
SetConfirmButtonBehavior();
SetConfirmButtonStatus();
FilterStoreItems();
if (tab == StoreTab.Buy)
switch (tab)
{
storeSellList.Visible = false;
storeBuyList.Visible = true;
shoppingCrateSellList.Visible = false;
shoppingCrateBuyList.Visible = true;
}
else if (tab == StoreTab.Sell)
{
storeBuyList.Visible = false;
storeSellList.Visible = true;
shoppingCrateBuyList.Visible = false;
shoppingCrateSellList.Visible = true;
case StoreTab.Buy:
storeSellList.Visible = false;
if (storeSellFromSubList != null)
{
storeSellFromSubList.Visible = false;
}
storeBuyList.Visible = true;
shoppingCrateSellList.Visible = false;
if (shoppingCrateSellFromSubList != null)
{
shoppingCrateSellFromSubList.Visible = false;
}
shoppingCrateBuyList.Visible = true;
break;
case StoreTab.Sell:
storeBuyList.Visible = false;
if (storeSellFromSubList != null)
{
storeSellFromSubList.Visible = false;
}
storeSellList.Visible = true;
shoppingCrateBuyList.Visible = false;
if (shoppingCrateSellFromSubList != null)
{
shoppingCrateSellFromSubList.Visible = false;
}
shoppingCrateSellList.Visible = true;
break;
case StoreTab.SellFromSub:
storeBuyList.Visible = false;
storeSellList.Visible = false;
if (storeSellFromSubList != null)
{
storeSellFromSubList.Visible = true;
}
shoppingCrateBuyList.Visible = false;
shoppingCrateSellList.Visible = false;
if (shoppingCrateSellFromSubList != null)
{
shoppingCrateSellFromSubList.Visible = true;
}
break;
}
}
private void FilterStoreItems(MapEntityCategory? category, string filter)
{
if (IsTabUnavailable(activeTab)) { return; }
selectedItemCategory = category;
var list = tabLists[activeTab];
filter = filter?.ToLower();
@@ -668,7 +762,7 @@ namespace Barotrauma
if (itemFrame == null)
{
var parentComponent = isDailySpecial ? storeDailySpecialsGroup : storeBuyList as GUIComponent;
itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, quantity), priceInfo, parentComponent, forceDisable: !hasPermissions);
itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, quantity), parentComponent, StoreTab.Buy, forceDisable: !hasPermissions);
}
else
{
@@ -688,7 +782,7 @@ namespace Barotrauma
removedItemFrames.AddRange(storeDailySpecialsGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
}
removedItemFrames.ForEach(f => f.RectTransform.Parent = null);
if (IsBuying) { FilterStoreItems(); }
if (activeTab == StoreTab.Buy) { FilterStoreItems(); }
SortItems(StoreTab.Buy);
storeBuyList.BarScroll = prevBuyListScroll;
@@ -743,7 +837,7 @@ namespace Barotrauma
if (itemFrame == null)
{
var parentComponent = isRequestedGood ? storeRequestedGoodGroup : storeSellList as GUIComponent;
itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, itemQuantity), priceInfo, parentComponent, forceDisable: !hasPermissions);
itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, itemQuantity), parentComponent, StoreTab.Sell, forceDisable: !hasPermissions);
}
else
{
@@ -766,13 +860,91 @@ namespace Barotrauma
removedItemFrames.AddRange(storeRequestedGoodGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
}
removedItemFrames.ForEach(f => f.RectTransform.Parent = null);
if (IsSelling) { FilterStoreItems(); }
if (activeTab == StoreTab.Sell) { FilterStoreItems(); }
SortItems(StoreTab.Sell);
storeSellList.BarScroll = prevSellListScroll;
shoppingCrateSellList.BarScroll = prevShoppingCrateScroll;
}
private void RefreshStoreSellFromSubList()
{
float prevSellListScroll = storeSellFromSubList.BarScroll;
float prevShoppingCrateScroll = shoppingCrateSellFromSubList.BarScroll;
bool hasPermissions = HasPermissions;
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
if ((storeRequestedSubGoodGroup != null) != CurrentLocation.RequestedGoods.Any())
{
if (storeRequestedSubGoodGroup == null)
{
storeRequestedSubGoodGroup = CreateDealsGroup(storeSellList);
storeRequestedSubGoodGroup.Parent.SetAsFirstChild();
}
else
{
storeSellFromSubList.RemoveChild(storeRequestedSubGoodGroup.Parent);
storeRequestedSubGoodGroup = null;
}
storeSellFromSubList.RecalculateChildren();
}
foreach (PurchasedItem item in itemsToSellFromSub)
{
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
}
foreach (var requestedGood in CurrentLocation.RequestedGoods)
{
if (itemsToSellFromSub.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
CreateOrUpdateItemFrame(requestedGood, 0);
}
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
{
PriceInfo priceInfo = itemPrefab.GetPriceInfo(CurrentLocation);
if (priceInfo == null) { return; }
var isRequestedGood = CurrentLocation.RequestedGoods.Contains(itemPrefab);
var itemFrame = isRequestedGood ?
storeRequestedSubGoodGroup.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) :
storeSellFromSubList.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab);
if (CargoManager.ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem itemInSellFromSubCrate)
{
itemQuantity = Math.Max(itemQuantity - itemInSellFromSubCrate.Quantity, 0);
}
if (itemFrame == null)
{
var parentComponent = isRequestedGood ? storeRequestedSubGoodGroup : storeSellFromSubList as GUIComponent;
itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, itemQuantity), parentComponent, StoreTab.SellFromSub, forceDisable: !hasPermissions);
}
else
{
(itemFrame.UserData as PurchasedItem).Quantity = itemQuantity;
SetQuantityLabelText(StoreTab.SellFromSub, itemFrame);
SetOwnedLabelText(itemFrame);
SetPriceGetters(itemFrame, false);
}
SetItemFrameStatus(itemFrame, hasPermissions && itemQuantity > 0);
if (itemQuantity < 1 && !isRequestedGood)
{
itemFrame.Visible = false;
}
existingItemFrames.Add(itemFrame);
}
var removedItemFrames = storeSellFromSubList.Content.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList();
if (storeRequestedSubGoodGroup != null)
{
removedItemFrames.AddRange(storeRequestedSubGoodGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
}
removedItemFrames.ForEach(f => f.RectTransform.Parent = null);
if (activeTab == StoreTab.SellFromSub) { FilterStoreItems(); }
SortItems(StoreTab.SellFromSub);
storeSellFromSubList.BarScroll = prevSellListScroll;
shoppingCrateSellFromSubList.BarScroll = prevShoppingCrateScroll;
}
private void SetPriceGetters(GUIComponent itemFrame, bool buying)
{
if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; }
@@ -834,7 +1006,38 @@ namespace Barotrauma
needsItemsToSellRefresh = false;
}
private void RefreshShoppingCrateList(List<PurchasedItem> items, GUIListBox listBox)
public void RefreshItemsToSellFromSub()
{
itemsToSellFromSub.Clear();
var subItems = CargoManager.GetSellableItemsFromSub();
foreach (Item subItem in subItems)
{
if (itemsToSellFromSub.FirstOrDefault(i => i.ItemPrefab == subItem.Prefab) is PurchasedItem item)
{
item.Quantity += 1;
}
else if (subItem.Prefab.GetPriceInfo(CurrentLocation) != null)
{
itemsToSellFromSub.Add(new PurchasedItem(subItem.Prefab, 1));
}
}
// Remove items from sell crate if they aren't on the sub anymore
var itemsInCrate = new List<PurchasedItem>(CargoManager.ItemsInSellFromSubCrate);
foreach (PurchasedItem crateItem in itemsInCrate)
{
var subItem = itemsToSellFromSub.Find(i => i.ItemPrefab == crateItem.ItemPrefab);
var subItemQuantity = subItem != null ? subItem.Quantity : 0;
if (crateItem.Quantity > subItemQuantity)
{
CargoManager.ModifyItemQuantityInSellFromSubCrate(crateItem.ItemPrefab, subItemQuantity - crateItem.Quantity);
}
}
sellableItemsFromSubUpdateTimer = 0.0f;
needsItemsToSellFromSubRefresh = false;
}
private void RefreshShoppingCrateList(List<PurchasedItem> items, GUIListBox listBox, StoreTab tab)
{
bool hasPermissions = HasPermissions;
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
@@ -848,7 +1051,7 @@ namespace Barotrauma
GUINumberInput numInput = null;
if (itemFrame == null)
{
itemFrame = CreateItemFrame(item, priceInfo, listBox, forceDisable: !hasPermissions);
itemFrame = CreateItemFrame(item, listBox, tab, forceDisable: !hasPermissions);
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
}
else
@@ -859,6 +1062,7 @@ namespace Barotrauma
{
numInput.UserData = item;
numInput.Enabled = hasPermissions;
numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, tab);
}
SetOwnedLabelText(itemFrame);
SetItemFrameStatus(itemFrame, hasPermissions);
@@ -873,7 +1077,7 @@ namespace Barotrauma
}
suppressBuySell = false;
var price = listBox == shoppingCrateBuyList ?
var price = tab == StoreTab.Buy ?
CurrentLocation.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo) :
CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo);
totalPrice += item.Quantity * price;
@@ -883,24 +1087,32 @@ namespace Barotrauma
removedItemFrames.ForEach(f => listBox.Content.RemoveChild(f));
SortItems(listBox, SortingMethod.CategoryAsc);
listBox.UpdateScrollBarSize();
if (listBox == shoppingCrateBuyList)
listBox.UpdateScrollBarSize();
switch (tab)
{
buyTotal = totalPrice;
if (IsBuying) { SetShoppingCrateTotalText(); }
case StoreTab.Buy:
buyTotal = totalPrice;
break;
case StoreTab.Sell:
sellTotal = totalPrice;
break;
case StoreTab.SellFromSub:
sellFromSubTotal = totalPrice;
break;
}
else
if (activeTab == tab)
{
sellTotal = totalPrice;
if(IsSelling) { SetShoppingCrateTotalText(); }
SetShoppingCrateTotalText();
}
SetClearAllButtonStatus();
SetConfirmButtonStatus();
}
private void RefreshShoppingCrateBuyList() => RefreshShoppingCrateList(CargoManager.ItemsInBuyCrate, shoppingCrateBuyList);
private void RefreshShoppingCrateBuyList() => RefreshShoppingCrateList(CargoManager.ItemsInBuyCrate, shoppingCrateBuyList, StoreTab.Buy);
private void RefreshShoppingCrateSellList() => RefreshShoppingCrateList(CargoManager.ItemsInSellCrate, shoppingCrateSellList);
private void RefreshShoppingCrateSellList() => RefreshShoppingCrateList(CargoManager.ItemsInSellCrate, shoppingCrateSellList, StoreTab.Sell);
private void RefreshShoppingCrateSellFromSubList() => RefreshShoppingCrateList(CargoManager.ItemsInSellFromSubCrate, shoppingCrateSellFromSubList, StoreTab.SellFromSub);
private void SortItems(GUIListBox list, SortingMethod sortingMethod)
{
@@ -932,7 +1144,7 @@ namespace Barotrauma
else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc)
{
SortItems(list, SortingMethod.AlphabeticalAsc);
if (list == storeSellList || list == shoppingCrateSellList)
if (list != storeBuyList && list != shoppingCrateBuyList)
{
list.Content.RectTransform.SortChildren(CompareBySellPrice);
if (GetSpecialsGroup() is GUILayoutGroup specialsGroup)
@@ -1014,6 +1226,10 @@ namespace Barotrauma
{
return storeRequestedGoodGroup;
}
else if (list == storeSellFromSubList)
{
return storeRequestedSubGoodGroup;
}
else
{
return null;
@@ -1045,15 +1261,20 @@ namespace Barotrauma
private void SortItems(StoreTab tab, SortingMethod sortingMethod)
{
if (IsTabUnavailable(tab)) { return; }
tabSortingMethods[tab] = sortingMethod;
SortItems(tabLists[tab], sortingMethod);
}
private void SortItems(StoreTab tab) => SortItems(tab, tabSortingMethods[tab]);
private void SortItems(StoreTab tab)
{
if (IsTabUnavailable(tab)) { return; }
SortItems(tab, tabSortingMethods[tab]);
}
private void SortActiveTabItems(SortingMethod sortingMethod) => SortItems(activeTab, sortingMethod);
private GUIComponent CreateItemFrame(PurchasedItem pi, PriceInfo priceInfo, GUIComponent parentComponent, bool forceDisable = false)
private GUIComponent CreateItemFrame(PurchasedItem pi, GUIComponent parentComponent, StoreTab containingTab, bool forceDisable = false)
{
var tooltip = pi.ItemPrefab.Name;
if (!string.IsNullOrWhiteSpace(pi.ItemPrefab.Description))
@@ -1114,8 +1335,8 @@ namespace Barotrauma
CanBeFocused = false,
Stretch = true
};
var isSellingRelatedList = parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup || parentComponent == shoppingCrateSellList;
var locationHasDealOnItem = isSellingRelatedList ?
bool isSellingRelatedList = containingTab != StoreTab.Buy;
bool locationHasDealOnItem = isSellingRelatedList ?
CurrentLocation.RequestedGoods.Contains(pi.ItemPrefab) : CurrentLocation.DailySpecials.Contains(pi.ItemPrefab);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), nameAndQuantityGroup.RectTransform),
pi.ItemPrefab.Name, font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft)
@@ -1140,14 +1361,15 @@ namespace Barotrauma
};
dealIcon.SetAsFirstChild();
}
var isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup ||
parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup;
bool isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup ||
parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup ||
parentComponent == storeSellFromSubList || parentComponent == storeRequestedSubGoodGroup;
GUILayoutGroup shoppingCrateAmountGroup = null;
GUINumberInput amountInput = null;
if (isParentOnLeftSideOfInterface)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), nameAndQuantityGroup.RectTransform),
CreateQuantityLabelText(isSellingRelatedList ? StoreTab.Sell : StoreTab.Buy, pi.Quantity), font: GUI.Font, textAlignment: Alignment.BottomLeft)
CreateQuantityLabelText(containingTab, pi.Quantity), font: GUI.Font, textAlignment: Alignment.BottomLeft)
{
CanBeFocused = false,
Shadow = locationHasDealOnItem,
@@ -1156,7 +1378,7 @@ namespace Barotrauma
UserData = "quantitylabel"
};
}
else if (!isParentOnLeftSideOfInterface)
else
{
var relativePadding = nameBlock.Padding.X / nameBlock.Rect.Width;
shoppingCrateAmountGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - relativePadding, 0.6f), nameAndQuantityGroup.RectTransform) { RelativeOffset = new Vector2(relativePadding, 0) },
@@ -1167,7 +1389,7 @@ namespace Barotrauma
amountInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), shoppingCrateAmountGroup.RectTransform), GUINumberInput.NumberType.Int)
{
MinValueInt = 0,
MaxValueInt = GetMaxAvailable(pi.ItemPrefab, isSellingRelatedList ? StoreTab.Sell : StoreTab.Buy),
MaxValueInt = GetMaxAvailable(pi.ItemPrefab, containingTab),
UserData = pi,
IntValue = pi.Quantity
};
@@ -1394,7 +1616,7 @@ namespace Barotrauma
}
}
private string CreateQuantityLabelText(StoreTab mode, int quantity) => mode == StoreTab.Sell ?
private string CreateQuantityLabelText(StoreTab mode, int quantity) => mode != StoreTab.Buy ?
TextManager.GetWithVariable("campaignstore.quantity", "[amount]", quantity.ToString()) :
TextManager.GetWithVariable("campaignstore.instock", "[amount]", quantity.ToString());
@@ -1417,10 +1639,16 @@ namespace Barotrauma
private int GetMaxAvailable(ItemPrefab itemPrefab, StoreTab mode)
{
var list = mode == StoreTab.Sell ? itemsToSell : CurrentLocation.StoreStock;
var list = mode switch
{
StoreTab.Buy => CurrentLocation.StoreStock,
StoreTab.Sell => itemsToSell,
StoreTab.SellFromSub => itemsToSellFromSub,
_ => throw new NotImplementedException()
};
if (list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item)
{
if (mode != StoreTab.Sell)
if (mode == StoreTab.Buy)
{
var purchasedItem = CargoManager.PurchasedItems.Find(i => i.ItemPrefab == item.ItemPrefab);
if (purchasedItem != null) { return Math.Max(item.Quantity - purchasedItem.Quantity, 0); }
@@ -1469,11 +1697,37 @@ namespace Barotrauma
return false;
}
private bool AddToShoppingCrate(PurchasedItem item, int quantity = 1) => IsBuying ?
ModifyBuyQuantity(item, quantity) : ModifySellQuantity(item, quantity);
private bool ModifySellFromSubQuantity(PurchasedItem item, int quantity)
{
if (item == null || item.ItemPrefab == null) { return false; }
if (!HasPermissions) { return false; }
if (quantity > 0)
{
// Make sure there's enough available to sell
var itemToSell = CargoManager.ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == item.ItemPrefab);
var totalQuantityToSell = itemToSell != null ? itemToSell.Quantity + quantity : quantity;
if (totalQuantityToSell > GetMaxAvailable(item.ItemPrefab, StoreTab.SellFromSub)) { return false; }
}
CargoManager.ModifyItemQuantityInSellFromSubCrate(item.ItemPrefab, quantity);
// TODO: GameMain.Client?.SendCampaignState();
return false;
}
private bool ClearFromShoppingCrate(PurchasedItem item) => IsBuying ?
ModifyBuyQuantity(item, -item.Quantity) : ModifySellQuantity(item, -item.Quantity);
private bool AddToShoppingCrate(PurchasedItem item, int quantity = 1) => activeTab switch
{
StoreTab.Buy => ModifyBuyQuantity(item, quantity),
StoreTab.Sell => ModifySellQuantity(item, quantity),
StoreTab.SellFromSub => ModifySellFromSubQuantity(item, quantity),
_ => throw new NotImplementedException(),
};
private bool ClearFromShoppingCrate(PurchasedItem item) => activeTab switch
{
StoreTab.Buy => ModifyBuyQuantity(item, -item.Quantity),
StoreTab.Sell => ModifySellQuantity(item, -item.Quantity),
StoreTab.SellFromSub => ModifySellFromSubQuantity(item, -item.Quantity),
_ => throw new NotImplementedException(),
};
private bool BuyItems()
{
@@ -1510,18 +1764,17 @@ namespace Barotrauma
private bool SellItems()
{
if (!HasPermissions) { return false; }
var itemsToSell = new List<PurchasedItem>(CargoManager.ItemsInSellCrate);
var itemsToSell = activeTab switch
{
StoreTab.Sell => new List<PurchasedItem>(CargoManager.ItemsInSellCrate),
StoreTab.SellFromSub => new List<PurchasedItem>(CargoManager.ItemsInSellFromSubCrate),
_ => throw new NotImplementedException()
};
var itemsToRemove = new List<PurchasedItem>();
var totalValue = 0;
foreach (PurchasedItem item in itemsToSell)
{
if (item?.ItemPrefab == null)
{
itemsToRemove.Add(item);
continue;
}
if (item.ItemPrefab.GetPriceInfo(CurrentLocation) is PriceInfo priceInfo)
if (item?.ItemPrefab?.GetPriceInfo(CurrentLocation) is PriceInfo priceInfo)
{
totalValue += item.Quantity * CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo);
}
@@ -1531,12 +1784,13 @@ namespace Barotrauma
}
}
itemsToRemove.ForEach(i => itemsToSell.Remove(i));
if (itemsToSell.None() || totalValue > CurrentLocation.StoreCurrentBalance) { return false; }
CargoManager.SellItems(itemsToSell);
GameMain.Client?.SendCampaignState();
CargoManager.SellItems(itemsToSell, activeTab);
if (activeTab == StoreTab.Sell)
{
// TODO: Implement selling sub items in multiplayer
GameMain.Client?.SendCampaignState();
}
return false;
}
@@ -1549,8 +1803,14 @@ namespace Barotrauma
}
else
{
shoppingCrateTotal.Text = GetCurrencyFormatted(sellTotal);
shoppingCrateTotal.TextColor = CurrentLocation != null && sellTotal > CurrentLocation.StoreCurrentBalance ? Color.Red : Color.White;
int total = activeTab switch
{
StoreTab.Sell => sellTotal,
StoreTab.SellFromSub => sellFromSubTotal,
_ => throw new NotImplementedException(),
};
shoppingCrateTotal.Text = GetCurrencyFormatted(total);
shoppingCrateTotal.TextColor = CurrentLocation != null && total > CurrentLocation.StoreCurrentBalance ? Color.Red : Color.White;
}
}
@@ -1580,13 +1840,19 @@ namespace Barotrauma
private void SetConfirmButtonStatus() => confirmButton.Enabled =
HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any() &&
((IsBuying && buyTotal <= PlayerMoney) || (IsSelling && CurrentLocation != null && sellTotal <= CurrentLocation.StoreCurrentBalance));
activeTab switch
{
StoreTab.Buy => buyTotal <= PlayerMoney,
StoreTab.Sell => CurrentLocation != null && sellTotal <= CurrentLocation.StoreCurrentBalance,
StoreTab.SellFromSub => CurrentLocation != null && sellFromSubTotal <= CurrentLocation.StoreCurrentBalance,
_ => throw new NotImplementedException(),
};
private void SetClearAllButtonStatus() => clearAllButton.Enabled =
HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any();
private float ownedItemsUpdateTimer = 0.0f;
private readonly float ownedItemsUpdateInterval = 1.5f;
private float ownedItemsUpdateTimer = 0.0f, sellableItemsFromSubUpdateTimer = 0.0f;
private readonly float timerUpdateInterval = 1.5f;
public void Update(float deltaTime)
{
@@ -1598,7 +1864,7 @@ namespace Barotrauma
{
// Update the owned items at short intervals and check if the interface should be refreshed
ownedItemsUpdateTimer += deltaTime;
if (ownedItemsUpdateTimer >= ownedItemsUpdateInterval)
if (ownedItemsUpdateTimer >= timerUpdateInterval)
{
var prevOwnedItems = new Dictionary<ItemPrefab, int>(OwnedItems);
UpdateOwnedItems();
@@ -1612,12 +1878,21 @@ namespace Barotrauma
needsRefresh = true;
}
}
// Update the sellable sub items at short intervals and check if the interface should be refreshed
sellableItemsFromSubUpdateTimer += deltaTime;
if (sellableItemsFromSubUpdateTimer >= timerUpdateInterval)
{
needsItemsToSellFromSubRefresh = true;
needsRefresh = true;
}
}
if (needsItemsToSellRefresh) { RefreshItemsToSell(); }
if (needsItemsToSellFromSubRefresh) { RefreshItemsToSellFromSub(); }
if (needsRefresh || hadPermissions != HasPermissions) { Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f); }
if (needsBuyingRefresh) { RefreshBuying(); }
if (needsSellingRefresh) { RefreshSelling(); }
if (needsSellingFromSubRefresh) { RefreshSellingFromSub(updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f); }
}
}
}

View File

@@ -12,7 +12,8 @@ namespace Barotrauma
private const int submarinesPerPage = 4;
private int currentPage = 1;
private int pageCount;
private bool transferService, purchaseService, initialized;
private readonly bool transferService, purchaseService;
private bool initialized;
private int deliveryFee;
private string deliveryLocationName;
@@ -27,12 +28,12 @@ namespace Barotrauma
private int selectionIndicatorThickness;
private GUIImage listBackground;
private List<SubmarineInfo> subsToShow;
private SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage];
private readonly List<SubmarineInfo> subsToShow;
private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage];
private SubmarineInfo selectedSubmarine = null;
private string purchaseAndSwitchText, purchaseOnlyText, deliveryText, currentSubText, deliveryFeeText, priceText, switchText, missingPreviewText, currencyShorthandText, currencyLongText;
private RectTransform parent;
private Action closeAction;
private readonly RectTransform parent;
private readonly Action closeAction;
private Sprite pageIndicator;
public static readonly string[] DeliveryTextVariables = new string[] { "[submarinename1]", "[location1]", "[location2]", "[submarinename2]", "[amount]", "[currencyname]" };
@@ -42,7 +43,7 @@ namespace Barotrauma
private static readonly string[] notEnoughCreditsDeliveryTextVariables = new string[] { "[currencyname]", "[submarinename]", "[location1]", "[location2]" };
private static readonly string[] notEnoughCreditsPurchaseTextVariables = new string[] { "[currencyname]", "[submarinename]" };
private string[] messageBoxOptions;
private readonly string[] messageBoxOptions;
public const int DeliveryFeePerDistanceTravelled = 1000;
public static bool ContentRefreshRequired = false;
@@ -65,7 +66,7 @@ namespace Barotrauma
public SubmarineSelection(bool transfer, Action closeAction, RectTransform parent)
{
if (GameMain.GameSession.Campaign == null) return;
if (GameMain.GameSession.Campaign == null) { return; }
transferService = transfer;
purchaseService = !transfer;
@@ -83,7 +84,7 @@ namespace Barotrauma
messageBoxOptions = new string[2] { TextManager.Get("Yes") + " " + TextManager.Get("initiatevoting"), TextManager.Get("Cancel") };
}
if (Submarine.MainSub?.Info == null) return;
if (Submarine.MainSub?.Info == null) { return; }
Initialize();
}
@@ -184,8 +185,10 @@ namespace Barotrauma
for (int i = 0; i < submarineDisplays.Length; i++)
{
SubmarineDisplayContent submarineDisplayElement = new SubmarineDisplayContent();
submarineDisplayElement.background = new GUIFrame(new RectTransform(new Vector2(1f / submarinesPerPage, 1f), submarineHorizontalGroup.RectTransform), style: null, new Color(8, 13, 19));
SubmarineDisplayContent submarineDisplayElement = new SubmarineDisplayContent
{
background = new GUIFrame(new RectTransform(new Vector2(1f / submarinesPerPage, 1f), submarineHorizontalGroup.RectTransform), style: null, new Color(8, 13, 19))
};
submarineDisplayElement.submarineImage = new GUIImage(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), null, true);
submarineDisplayElement.middleTextBlock = new GUITextBlock(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), string.Empty, textAlignment: Alignment.Center);
submarineDisplayElement.submarineName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUI.SubHeadingFont);
@@ -433,7 +436,7 @@ namespace Barotrauma
private SubmarineInfo GetSubToDisplay(int index)
{
if (subsToShow.Count <= index || index < 0) return null;
if (subsToShow.Count <= index || index < 0) { return null; }
return subsToShow[index];
}
@@ -626,7 +629,6 @@ namespace Barotrauma
if (GameMain.Client == null)
{
SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, deliveryFee);
GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(newSub);
RefreshSubmarineDisplay(true);
}
else
@@ -661,7 +663,6 @@ namespace Barotrauma
{
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, 0);
GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(newSub);
RefreshSubmarineDisplay(true);
}
else

View File

@@ -228,7 +228,10 @@ namespace Barotrauma
var crewButton = createTabButton(InfoFrameTab.Crew, "crew");
var missionButton = createTabButton(InfoFrameTab.Mission, "mission");
if (!(GameMain.GameSession?.GameMode is TestGameMode))
{
createTabButton(InfoFrameTab.Mission, "mission");
}
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
{
@@ -903,51 +906,68 @@ namespace Barotrauma
infoFrame.ClearChildren();
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = (int)(0.0245f * missionFrame.Rect.Height);
Location location = GameMain.GameSession.EndLocation != null ? GameMain.GameSession.EndLocation : GameMain.GameSession.StartLocation;
GUIFrame missionFrameContent = new GUIFrame(new RectTransform(new Point(missionFrame.Rect.Width - padding * 2, missionFrame.Rect.Height - padding * 2), infoFrame.RectTransform, Anchor.Center), style: null);
Location location = GameMain.GameSession.EndLocation ?? GameMain.GameSession.StartLocation;
GUILayoutGroup locationInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), missionFrameContent.RectTransform))
{
AbsoluteSpacing = GUI.IntScale(10)
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Name, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Type.Name, font: GUI.SubHeadingFont);
var biomeLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), locationInfoContainer.RectTransform),
TextManager.Get("Biome", fallBackTag: "location"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), biomeLabel.RectTransform), Level.Loaded.LevelData.Biome.DisplayName, textAlignment: Alignment.CenterRight);
var difficultyLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), locationInfoContainer.RectTransform),
TextManager.Get("LevelDifficulty"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), ((int)Level.Loaded.LevelData.Difficulty) + " %", textAlignment: Alignment.CenterRight);
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), missionFrameContent.RectTransform) { AbsoluteOffset = new Point(0, locationInfoContainer.Rect.Height + padding) }, style: "HorizontalLine")
{
CanBeFocused = false
};
int locationInfoYOffset = locationInfoContainer.Rect.Height + padding * 2;
Sprite portrait = location.Type.GetPortrait(location.PortraitId);
bool hasPortrait = portrait != null && portrait.SourceRect.Width > 0 && portrait.SourceRect.Height > 0;
int contentWidth = hasPortrait ? (int)(missionFrame.Rect.Width * 0.951f) : missionFrame.Rect.Width - padding * 2;
Vector2 locationNameSize = GUI.LargeFont.MeasureString(location.Name);
Vector2 locationTypeSize = GUI.SubHeadingFont.MeasureString(location.Name);
GUITextBlock locationNameText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationNameSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, padding) }, location.Name, font: GUI.LargeFont);
GUITextBlock locationTypeText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationTypeSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationNameText.Rect.Height + padding) }, location.Type.Name, font: GUI.SubHeadingFont);
int locationInfoYOffset = locationNameText.Rect.Height + locationTypeText.Rect.Height + padding * 2;
GUIListBox missionList;
int contentWidth = missionFrameContent.Rect.Width;
if (hasPortrait)
{
GUIFrame portraitHolder = new GUIFrame(new RectTransform(new Point(contentWidth, (int)(missionFrame.Rect.Height * 0.588f)), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) });
float portraitAspectRatio = portrait.SourceRect.Width / portrait.SourceRect.Height;
GUIImage portraitImage = new GUIImage(new RectTransform(new Vector2(1.0f, 1f), portraitHolder.RectTransform), portrait, scaleToFit: true);
portraitHolder.RectTransform.NonScaledSize = new Point(portraitImage.Rect.Size.X, (int)(portraitImage.Rect.Size.X / portraitAspectRatio));
GUIImage portraitImage = new GUIImage(new RectTransform(new Vector2(0.5f, 1f), locationInfoContainer.RectTransform, Anchor.CenterRight), portrait, scaleToFit: true)
{
IgnoreLayoutGroups = true
};
locationInfoContainer.Recalculate();
portraitImage.RectTransform.NonScaledSize = new Point(Math.Min((int)(portraitImage.Rect.Size.Y * portraitAspectRatio), portraitImage.Rect.Width), portraitImage.Rect.Size.Y);
}
missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrame.Rect.Bottom - portraitHolder.Rect.Bottom - padding), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, portraitHolder.RectTransform.AbsoluteOffset.Y + portraitHolder.Rect.Height + padding) });
}
else
{
missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrame.Rect.Height - locationInfoYOffset - padding), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) });
}
GUIListBox missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrameContent.Rect.Height - locationInfoYOffset), missionFrameContent.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) });
missionList.ContentBackground.Color = Color.Transparent;
missionList.Spacing = GUI.IntScale(15);
if (GameMain.GameSession?.Missions != null)
{
int spacing = GUI.IntScale(5);
int iconSize = (int)(GUI.LargeFont.MeasureChar('T').Y + GUI.Font.MeasureChar('T').Y * 4 + spacing * 4);
foreach (Mission mission in GameMain.GameSession.Missions)
{
GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null);
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.225f, 0f) }, false, childAnchor: Anchor.TopLeft)
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(iconSize + spacing, 0) }, false, childAnchor: Anchor.TopLeft)
{
AbsoluteSpacing = GUI.IntScale(5)
AbsoluteSpacing = spacing
};
string descriptionText = mission.Description;
foreach (string missionMessage in mission.ShownMessages)
{
descriptionText += "\n\n" + missionMessage;
}
string rewardText = mission.GetMissionRewardText();
string rewardText = mission.GetMissionRewardText(Submarine.MainSub);
string reputationText = mission.GetReputationRewardText(mission.Locations[0]);
var missionNameRichTextData = RichTextData.GetRichTextData(mission.Name, out string missionNameString);
@@ -974,12 +994,12 @@ namespace Barotrauma
if (mission.Prefab.Icon != null)
{
float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height;
/*float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height;
int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X);
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio));
Point iconSize = new Point(iconWidth, iconHeight);
Point iconSize = new Point(iconWidth, iconHeight);*/
new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true)
new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true)
{
Color = mission.Prefab.IconColor,
HoverColor = mission.Prefab.IconColor,

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@ namespace Barotrauma
public static bool ShowFPS = false;
public static bool ShowPerf = false;
public static bool DebugDraw;
public static bool IsSingleplayer => NetworkMember == null;
public static bool IsMultiplayer => NetworkMember != null;
public static PerformanceCounter PerformanceCounter;
@@ -245,6 +246,23 @@ namespace Barotrauma
FarseerPhysics.Settings.PositionIterations = 1;
MainThread = Thread.CurrentThread;
Window.FileDropped += OnFileDropped;
}
public static void OnFileDropped(object sender, FileDropEventArgs args)
{
if (!(Screen.Selected is { } screen)) { return; }
string filePath = args.FilePath;
if (string.IsNullOrWhiteSpace(filePath)) { return; }
string extension = Path.GetExtension(filePath).ToLower();
System.IO.FileInfo info = new System.IO.FileInfo(args.FilePath);
if (!info.Exists) { return; }
screen.OnFileDropped(filePath, extension);
}
public void ApplyGraphicsSettings()
@@ -933,10 +951,7 @@ namespace Barotrauma
Screen.Selected.AddToGUIUpdateList();
if (Client != null)
{
Client.AddToGUIUpdateList();
}
Client?.AddToGUIUpdateList();
SubmarinePreview.AddToGUIUpdateList();
@@ -966,10 +981,7 @@ namespace Barotrauma
}
}
if (NetworkMember != null)
{
NetworkMember.Update((float)Timing.Step);
}
NetworkMember?.Update((float)Timing.Step);
GUI.Update((float)Timing.Step);
}

View File

@@ -1,4 +1,5 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
@@ -42,10 +43,7 @@ namespace Barotrauma
public IEnumerable<Item> GetSellableItems(Character character)
{
if (character == null) { return new List<Item>(); }
// Only consider items which have been:
// a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or
// b) sold locally in multiplayer (SellStatus.Local), but the client has not received a campaing state update yet after selling them
var confirmedSoldEntities = SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed);
var confirmedSoldEntities = GetConfirmedSoldEntities();
// The bag slot is intentionally left out since we want to be able to sell items from there
var equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card };
return character.Inventory.FindAllItems(item =>
@@ -72,6 +70,43 @@ namespace Barotrauma
}
}
public IEnumerable<Item> GetSellableItemsFromSub()
{
if (Submarine.MainSub == null) { return new List<Item>(); }
var confirmedSoldEntities = GetConfirmedSoldEntities();
return Submarine.MainSub.GetItems(true).FindAll(item =>
{
if (!item.Prefab.CanBeSold) { return false; }
if (item.SpawnedInOutpost) { return false; }
if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; }
if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; }
if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; }
if (!ItemAndAllContainersInteractable(item)) { return false; }
if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; }
// There must be no contained items or the contained items must be confirmed as sold
if (!item.ContainedItems.All(it => confirmedSoldEntities.Any(se => se.Item == it))) { return false; }
return true;
}).Distinct();
static bool ItemAndAllContainersInteractable(Item item)
{
do
{
if (!item.IsPlayerTeamInteractable) { return false; }
item = item.Container;
} while (item != null);
return true;
}
}
private IEnumerable<SoldEntity> GetConfirmedSoldEntities()
{
// Only consider items which have been:
// a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or
// b) sold locally in multiplayer (SellStatus.Local), but the client has not received a campaing state update yet after selling them
return SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed);
}
public void SetItemsInBuyCrate(List<PurchasedItem> items)
{
ItemsInBuyCrate.Clear();
@@ -119,10 +154,34 @@ namespace Barotrauma
OnItemsInSellCrateChanged?.Invoke();
}
public void SellItems(List<PurchasedItem> itemsToSell)
public void ModifyItemQuantityInSellFromSubCrate(ItemPrefab itemPrefab, int changeInQuantity)
{
var itemsInInventory = GetSellableItems(Character.Controlled);
var canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null;
var itemToSell = ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == itemPrefab);
if (itemToSell != null)
{
itemToSell.Quantity += changeInQuantity;
if (itemToSell.Quantity < 1)
{
ItemsInSellFromSubCrate.Remove(itemToSell);
}
}
else if (changeInQuantity > 0)
{
itemToSell = new PurchasedItem(itemPrefab, changeInQuantity);
ItemsInSellFromSubCrate.Add(itemToSell);
}
OnItemsInSellFromSubCrateChanged?.Invoke();
}
public void SellItems(List<PurchasedItem> itemsToSell, Store.StoreTab sellingMode)
{
var sellableItems = sellingMode switch
{
Store.StoreTab.Sell => GetSellableItems(Character.Controlled),
Store.StoreTab.SellFromSub => GetSellableItemsFromSub(),
_ => throw new System.NotImplementedException(),
};
bool canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null;
var sellerId = GameMain.Client?.ID ?? 0;
// Check all the prices before starting the transaction
@@ -137,7 +196,7 @@ namespace Barotrauma
if (Location.StoreCurrentBalance < itemValue) { continue; }
// TODO: Write logic for prioritizing certain items over others (e.g. lone Battery Cell should be preferred over one inside a Stun Baton)
var matchingItems = itemsInInventory.Where(i => i.Prefab == item.ItemPrefab);
var matchingItems = sellableItems.Where(i => i.Prefab == item.ItemPrefab);
if (matchingItems.Count() <= item.Quantity)
{
foreach (Item i in matchingItems)
@@ -163,12 +222,21 @@ namespace Barotrauma
campaign.Money += itemValue;
// Remove from the sell crate
if (ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } itemToSell)
// TODO: Simplify duplicate logic?
if (sellingMode == Store.StoreTab.Sell && ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } inventoryItem)
{
itemToSell.Quantity -= item.Quantity;
if (itemToSell.Quantity < 1)
inventoryItem.Quantity -= item.Quantity;
if (inventoryItem.Quantity < 1)
{
ItemsInSellCrate.Remove(itemToSell);
ItemsInSellCrate.Remove(inventoryItem);
}
}
else if(sellingMode == Store.StoreTab.SellFromSub && ItemsInSellFromSubCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } subItem)
{
subItem.Quantity -= item.Quantity;
if (subItem.Quantity < 1)
{
ItemsInSellFromSubCrate.Remove(subItem);
}
}
}

View File

@@ -71,6 +71,7 @@ namespace Barotrauma
: this(isSinglePlayer)
{
AddCharacterElements(element);
ActiveOrdersElement = element.GetChildElement("activeorders");
}
partial void InitProjectSpecific()
@@ -192,7 +193,8 @@ namespace Barotrauma
{
AbsoluteSpacing = (int)(5 * GUI.Scale),
UserData = "reportbuttons",
CanBeFocused = false
CanBeFocused = false,
Visible = false
};
ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height);
@@ -433,13 +435,20 @@ namespace Barotrauma
Stretch = false
};
var soundIcons = new GUIFrame(new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform), style: null)
var extraIconFrame = new GUIFrame(new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "soundicons"
UserData = "extraicons"
};
var soundIconParent = new GUIFrame(new RectTransform(Vector2.One, extraIconFrame.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "soundicons",
Visible = character.IsPlayer
};
new GUIImage(
new RectTransform(Vector2.One, soundIcons.RectTransform),
new RectTransform(Vector2.One, soundIconParent.RectTransform),
GUI.Style.GetComponentStyle("GUISoundIcon").GetDefaultSprite(),
scaleToFit: true)
{
@@ -448,7 +457,7 @@ namespace Barotrauma
Visible = true
};
new GUIImage(
new RectTransform(Vector2.One, soundIcons.RectTransform),
new RectTransform(Vector2.One, soundIconParent.RectTransform),
"GUISoundIconDisabled",
scaleToFit: true)
{
@@ -457,6 +466,16 @@ namespace Barotrauma
Visible = false
};
if (character.IsBot)
{
new GUIFrame(new RectTransform(Vector2.One, extraIconFrame.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "objectiveicon",
Visible = false
};
}
new GUIButton(new RectTransform(new Point((int)commandButtonAbsoluteHeight), background.RectTransform), style: "CrewListCommandButton")
{
ToolTip = TextManager.Get("inputtype.command"),
@@ -624,9 +643,7 @@ namespace Barotrauma
{
if (client?.Character == null) { return; }
if (crewList.Content.GetChildByUserData(client.Character)?
.FindChild(c => c is GUILayoutGroup)?
.GetChildByUserData("soundicons") is GUIComponent soundIcons)
if (GetSoundIconParent(client.Character) is GUIComponent soundIcons)
{
var soundIcon = soundIcons.FindChild(c => c.UserData is Pair<string, float> pair && pair.First == "soundicon");
var soundIconDisabled = soundIcons.FindChild("soundicondisabled");
@@ -638,15 +655,17 @@ namespace Barotrauma
public void SetClientSpeaking(Client client)
{
if (client?.Character != null) { SetCharacterSpeaking(client.Character); }
if (client?.Character != null)
{
SetCharacterSpeaking(client.Character);
}
}
public void SetCharacterSpeaking(Character character)
{
if (crewList.Content.GetChildByUserData(character)?
.FindChild(c => c is GUILayoutGroup)?
.GetChildByUserData("soundicons")?
.FindChild(c => c.UserData is Pair<string, float> pair && pair.First == "soundicon") is GUIComponent soundIcon)
if (character == null || character.IsBot) { return; }
if (GetSoundIconParent(character)?.FindChild(c => c.UserData is Pair<string, float> pair && pair.First == "soundicon") is GUIComponent soundIcon)
{
soundIcon.Color = Color.White;
Pair<string, float> userdata = soundIcon.UserData as Pair<string, float>;
@@ -654,6 +673,19 @@ namespace Barotrauma
}
}
private GUIComponent GetSoundIconParent(GUIComponent characterComponent)
{
return characterComponent?
.FindChild(c => c is GUILayoutGroup)?
.GetChildByUserData("extraicons")?
.GetChildByUserData("soundicons");
}
private GUIComponent GetSoundIconParent(Character character)
{
return GetSoundIconParent(crewList?.Content.GetChildByUserData(character));
}
#endregion
#region Crew List Order Displayment
@@ -770,6 +802,7 @@ namespace Barotrauma
if (icon is GUIImage image)
{
image.Sprite = GetOrderIconSprite(order, option);
image.ToolTip = CreateOrderTooltip(order, option);
}
updatedExistingIcon = true;
}
@@ -835,7 +868,7 @@ namespace Barotrauma
Visible = false
};
int hierarchyIndex = GetOrderIconHierarchyIndex(priority);
int hierarchyIndex = Math.Clamp(CharacterInfo.HighestManualOrderPriority - priority, 0, Math.Max(currentOrderIconList.Content.CountChildren - 1, 0));
if (hierarchyIndex != currentOrderIconList.Content.GetChildIndex(nodeIcon))
{
nodeIcon.RectTransform.RepositionChildInHierarchy(hierarchyIndex);
@@ -878,11 +911,6 @@ namespace Barotrauma
}
}
}
static int GetOrderIconHierarchyIndex(int priority)
{
return CharacterInfo.HighestManualOrderPriority - priority;
}
}
public void AddCurrentOrderIcon(Character character, OrderInfo? orderInfo)
@@ -1020,29 +1048,36 @@ namespace Barotrauma
SetCharacterOrder(character, orderInfo.Order, orderInfo.OrderOption, priority, Character.Controlled);
}
private string CreateOrderTooltip(Order order, string option)
private string CreateOrderTooltip(Order orderPrefab, string option, Entity targetEntity)
{
if (order == null) { return ""; }
if (orderPrefab == null) { return ""; }
if (!string.IsNullOrEmpty(option))
{
return TextManager.GetWithVariables("crewlistordericontooltip",
new string[2] { "[ordername]", "[orderoption]" },
new string[2] { order.Name, order.GetOptionName(option) });
new string[2] { orderPrefab.Name, orderPrefab.GetOptionName(option) });
}
else if (order.TargetEntity is Item targetItem && order.MinimapIcons.ContainsKey(targetItem.Prefab.Identifier))
else if (targetEntity is Item targetItem && targetItem.Prefab.MinimapIcon != null)
{
return TextManager.GetWithVariables("crewlistordericontooltip",
new string[2] { "[ordername]", "[orderoption]" },
new string[2] { order.Name, targetItem.Name });
new string[2] { orderPrefab.Name, targetItem.Name });
}
else
{
return order.Name;
return orderPrefab.Name;
}
}
private string CreateOrderTooltip(OrderInfo orderInfo) =>
CreateOrderTooltip(orderInfo.Order, orderInfo.OrderOption);
private string CreateOrderTooltip(Order order, string option)
{
return CreateOrderTooltip(order?.Prefab ?? order, option, order?.TargetEntity);
}
private string CreateOrderTooltip(OrderInfo orderInfo)
{
return CreateOrderTooltip(orderInfo.Order?.Prefab ?? orderInfo.Order, orderInfo.OrderOption, orderInfo.Order?.TargetEntity);
}
private Sprite GetOrderIconSprite(Order order, string option)
{
@@ -1052,9 +1087,9 @@ namespace Barotrauma
{
order.Prefab.OptionSprites.TryGetValue(option, out sprite);
}
if (sprite == null && order.TargetEntity is Item targetItem && order.MinimapIcons.Any())
if (sprite == null && order.TargetEntity is Item targetItem && targetItem.Prefab.MinimapIcon != null)
{
order.MinimapIcons.TryGetValue(targetItem.Prefab.Identifier, out sprite);
sprite = targetItem.Prefab.MinimapIcon;
}
return sprite ?? order.SymbolSprite;
}
@@ -1244,10 +1279,7 @@ namespace Barotrauma
//make the previously selected character wait in place for some time
//(so they don't immediately start idling and walking away from their station)
var aiController = Character.Controlled?.AIController;
if (aiController != null)
{
aiController.Reset();
}
aiController?.Reset();
DisableCommandUI();
Character.Controlled = character;
HintManager.OnChangeCharacter();
@@ -1255,24 +1287,34 @@ namespace Barotrauma
private int TryAdjustIndex(int amount)
{
int index = Character.Controlled == null ? 0 :
crewList.Content.GetChildIndex(crewList.Content.GetChildByUserData(Character.Controlled)) + amount;
if (Character.Controlled == null) { return 0; }
int currentIndex = crewList.Content.GetChildIndex(crewList.Content.GetChildByUserData(Character.Controlled));
if (currentIndex == -1) { return 0; }
int lastIndex = crewList.Content.CountChildren - 1;
if (index > lastIndex)
int index = currentIndex + amount;
for (int i = 0; i < crewList.Content.CountChildren; i++)
{
index = 0;
if (index > lastIndex) { index = 0; }
if (index < 0) { index = lastIndex; }
if ((crewList.Content.GetChild(index)?.UserData as Character)?.IsOnPlayerTeam ?? false)
{
return index;
}
index += amount;
}
if (index < 0)
{
index = lastIndex;
}
return index;
return 0;
}
partial void UpdateProjectSpecific(float deltaTime)
{
// Quick selection
if (!GameMain.IsMultiplayer && GUI.KeyboardDispatcher.Subscriber == null)
if (GameMain.IsSingleplayer && GUI.KeyboardDispatcher.Subscriber == null)
{
if (PlayerInput.KeyHit(InputType.SelectNextCharacter))
{
@@ -1294,7 +1336,7 @@ namespace Barotrauma
(GUI.KeyboardDispatcher.Subscriber == null || (GUI.KeyboardDispatcher.Subscriber is GUIComponent component && (component == crewList || component.IsChildOf(crewList)))) &&
commandFrame == null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ?? false))
{
if (PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift))
if (PlayerInput.IsShiftDown())
{
CreateCommandUI(FindEntityContext(), true);
}
@@ -1521,19 +1563,44 @@ namespace Barotrauma
{
crewList.Select(character, force: true);
}
if (character.AIController is HumanAIController controller)
if (GameMain.IsSingleplayer && character.IsBot && character.AIController is HumanAIController controller &&
controller.ObjectiveManager is AIObjectiveManager objectiveManager)
{
OrderInfo? currentOrderInfo = controller.ObjectiveManager?.GetCurrentOrderInfo();
if (currentOrderInfo.HasValue)
// In multiplayer, these are set through character networking (the server lets the clients now when these are updated)
if (objectiveManager.CurrentObjective is AIObjective currentObjective)
{
SetHighlightedOrderIcon(characterComponent, currentOrderInfo.Value.Order?.Identifier, currentOrderInfo.Value.OrderOption);
if (objectiveManager.IsOrder(currentObjective))
{
var orderInfo = objectiveManager.CurrentOrders.FirstOrDefault(o => o.Objective == currentObjective);
SetOrderHighlight(characterComponent, orderInfo.Order?.Identifier, orderInfo.OrderOption);
}
else
{
CreateObjectiveIcon(characterComponent, currentObjective);
}
}
}
if (characterComponent.GetChild<GUILayoutGroup>().GetChildByUserData("soundicons") is GUIComponent soundIconParent)
// Order highlighting and objective icons are intended to communicate bot behavior so they should be disabled for player characters
if (character.IsPlayer)
{
DisableOrderHighlight(characterComponent);
RemoveObjectiveIcon(characterComponent);
}
if (GetSoundIconParent(characterComponent) is GUIComponent soundIconParent)
{
if (soundIconParent.FindChild(c => c.UserData is Pair<string, float> pair && pair.First == "soundicon") is GUIImage soundIcon)
{
VoipClient.UpdateVoiceIndicator(soundIcon, 0.0f, deltaTime);
if (character.IsPlayer)
{
soundIconParent.Visible = true;
VoipClient.UpdateVoiceIndicator(soundIcon, 0.0f, deltaTime);
}
else if(soundIcon.Visible)
{
var userdata = soundIcon.UserData as Pair<string, float>;
userdata.Second = 0.0f;
soundIconParent.Visible = soundIcon.Visible = false;
}
}
}
}
@@ -1559,31 +1626,127 @@ namespace Barotrauma
UpdateReports();
}
private void SetHighlightedOrderIcon(GUIComponent characterComponent, string orderIdentifier, string orderOption)
private void SetOrderHighlight(GUIComponent characterComponent, string orderIdentifier, string orderOption)
{
var currentOrderIconList = GetCurrentOrderIconList(characterComponent);
if (currentOrderIconList == null) { return; }
bool foundMatch = false;
foreach (var orderIcon in currentOrderIconList.Content.Children)
if (characterComponent == null) { return; }
RemoveObjectiveIcon(characterComponent);
if (GetCurrentOrderIconList(characterComponent) is GUIListBox currentOrderIconList)
{
var glowComponent = orderIcon.GetChildByUserData("glow");
if (glowComponent == null) { continue; }
if (foundMatch)
bool foundMatch = false;
foreach (var orderIcon in currentOrderIconList.Content.Children)
{
glowComponent.Visible = false;
continue;
var glowComponent = orderIcon.GetChildByUserData("glow");
if (glowComponent == null) { continue; }
if (foundMatch)
{
glowComponent.Visible = false;
continue;
}
var orderInfo = (OrderInfo)orderIcon.UserData;
foundMatch = orderInfo.MatchesOrder(orderIdentifier, orderOption);
glowComponent.Visible = foundMatch;
}
var orderInfo = (OrderInfo)orderIcon.UserData;
foundMatch = orderInfo.MatchesOrder(orderIdentifier, orderOption);
glowComponent.Visible = foundMatch;
}
}
public void SetHighlightedOrderIcon(Character character, string orderIdentifier, string orderOption)
public void SetOrderHighlight(Character character, string orderIdentifier, string orderOption)
{
if (crewList == null) { return; }
var characterComponent = crewList.Content.GetChildByUserData(character);
SetHighlightedOrderIcon(characterComponent, orderIdentifier, orderOption);
SetOrderHighlight(characterComponent, orderIdentifier, orderOption);
}
private void DisableOrderHighlight(GUIComponent characterComponent)
{
if (GetCurrentOrderIconList(characterComponent) is GUIListBox currentOrderIconList)
{
foreach (var orderIcon in currentOrderIconList.Content.Children)
{
var glowComponent = orderIcon.GetChildByUserData("glow");
if (glowComponent == null) { continue; }
glowComponent.Visible = false;
}
}
}
private void CreateObjectiveIcon(GUIComponent characterComponent, Sprite sprite, string tooltip)
{
if (characterComponent == null || !(characterComponent.UserData is Character character) || character.IsPlayer) { return; }
DisableOrderHighlight(characterComponent);
if (GetObjectiveIconParent(characterComponent) is GUIFrame objectiveIconFrame)
{
var existingObjectiveIcon = objectiveIconFrame.GetChild<GUIImage>();
if (existingObjectiveIcon == null || existingObjectiveIcon.Sprite != sprite || existingObjectiveIcon.ToolTip != tooltip)
{
objectiveIconFrame.ClearChildren();
if (sprite != null)
{
var objectiveIcon = CreateNodeIcon(Vector2.One, objectiveIconFrame.RectTransform, sprite, Color.LightGray, tooltip: tooltip);
new GUIFrame(new RectTransform(new Vector2(1.5f), objectiveIcon.RectTransform, anchor: Anchor.Center), style: "OuterGlowCircular")
{
CanBeFocused = false,
Color = Color.LightGray
};
objectiveIconFrame.Visible = true;
}
else
{
objectiveIconFrame.Visible = false;
}
}
}
}
public void CreateObjectiveIcon(Character character, string identifier, string option, Entity targetEntity)
{
CreateObjectiveIcon(crewList?.Content.GetChildByUserData(character),
AIObjective.GetSprite(identifier, option, targetEntity),
GetObjectiveIconTooltip(identifier, option, targetEntity));
}
private void CreateObjectiveIcon(GUIComponent characterComponent, AIObjective objective)
{
CreateObjectiveIcon(characterComponent,
objective?.GetSprite(),
GetObjectiveIconTooltip(objective));
}
private string GetObjectiveIconTooltip(string identifier, string option, Entity targetEntity)
{
string variableValue;
identifier = identifier.RemoveWhitespace();
if (Order.Prefabs.TryGetValue(identifier, out Order orderPrefab))
{
variableValue = CreateOrderTooltip(orderPrefab, option, targetEntity);
}
else
{
variableValue = TextManager.Get($"objective.{identifier}", returnNull: true) ?? "";
}
return string.IsNullOrEmpty(variableValue) ? variableValue : TextManager.GetWithVariable("crewlistobjectivetooltip", "[objective]", variableValue);
}
private string GetObjectiveIconTooltip(AIObjective objective)
{
return objective == null ? "" :
GetObjectiveIconTooltip(objective.Identifier, objective.Option, (objective as AIObjectiveOperateItem)?.OperateTarget);
}
private GUIComponent GetObjectiveIconParent(GUIComponent characterComponent)
{
return characterComponent?
.GetChild<GUILayoutGroup>()?
.GetChildByUserData("extraicons")?
.GetChildByUserData("objectiveicon");
}
private void RemoveObjectiveIcon(GUIComponent characterComponent)
{
if (GetObjectiveIconParent(characterComponent) is GUIFrame objectiveIconFrame)
{
objectiveIconFrame.ClearChildren();
objectiveIconFrame.Visible = false;
}
}
#endregion
@@ -1853,7 +2016,10 @@ namespace Barotrauma
{
nodeConnectors = new GUICustomComponent(
new RectTransform(Vector2.One, commandFrame.RectTransform),
onDraw: DrawNodeConnectors);
onDraw: DrawNodeConnectors)
{
CanBeFocused = false
};
nodeConnectors.SetAsFirstChild();
background.SetAsFirstChild();
}
@@ -1862,10 +2028,27 @@ namespace Barotrauma
{
if (centerNode == null || optionNodes == null) { return; }
var startNodePos = centerNode.Rect.Center.ToVector2();
// Don't draw connectors for mini map options or assignment nodes
if ((targetFrame == null || !targetFrame.Visible) && !(optionNodes.FirstOrDefault()?.Item1.UserData is Character))
// Don't draw connectors for assignment nodes
if (!(optionNodes.FirstOrDefault()?.Item1.UserData is Character))
{
optionNodes.ForEach(n => DrawNodeConnector(startNodePos, centerNodeMargin, n.Item1, optionNodeMargin, spriteBatch));
// Regular option nodes
if (targetFrame == null || !targetFrame.Visible)
{
optionNodes.ForEach(n => DrawNodeConnector(startNodePos, centerNodeMargin, n.Item1, optionNodeMargin, spriteBatch));
}
// Minimap item nodes for single-option orders
else if(optionNodes.FirstOrDefault()?.Item1?.UserData is Tuple<Order, string> userData && string.IsNullOrEmpty(userData.Item2))
{
foreach (var node in optionNodes)
{
float iconRadius = 0.5f * optionNodeMargin;
Vector2 itemPosition = node.Item1.Parent.Rect.Center.ToVector2();
if (Vector2.Distance(node.Item1.Center, itemPosition) <= iconRadius) { continue; }
DrawNodeConnector(itemPosition, 0.0f, node.Item1, iconRadius, spriteBatch, widthMultiplier: 0.5f);
GUI.DrawFilledRectangle(spriteBatch, itemPosition - Vector2.One, new Vector2(3),
node.Item1.GetChildByUserData("colorsource")?.Color ?? Color.White);
}
}
}
DrawNodeConnector(startNodePos, centerNodeMargin, returnNode, returnNodeMargin, spriteBatch);
if (shortcutCenterNode == null || !shortcutCenterNode.Visible) { return; }
@@ -1874,7 +2057,7 @@ namespace Barotrauma
shortcutNodes.ForEach(n => DrawNodeConnector(startNodePos, shortcutCenterNodeMargin, n, shortcutNodeMargin, spriteBatch));
}
private void DrawNodeConnector(Vector2 startNodePos, float startNodeMargin, GUIComponent endNode, float endNodeMargin, SpriteBatch spriteBatch)
private void DrawNodeConnector(Vector2 startNodePos, float startNodeMargin, GUIComponent endNode, float endNodeMargin, SpriteBatch spriteBatch, float widthMultiplier = 1.0f)
{
if (endNode == null || !endNode.Visible) { return; }
var endNodePos = endNode.Rect.Center.ToVector2();
@@ -1885,11 +2068,11 @@ namespace Barotrauma
if ((selectedNode == null && endNode != shortcutCenterNode && GUI.IsMouseOn(endNode)) ||
(isSelectionHighlighted && (endNode == selectedNode || (endNode == shortcutCenterNode && shortcutNodes.Any(n => GUI.IsMouseOn(n))))))
{
GUI.DrawLine(spriteBatch, start, end, colorSource != null ? colorSource.HoverColor : Color.White, width: 4);
GUI.DrawLine(spriteBatch, start, end, colorSource?.HoverColor ?? Color.White, width: Math.Max(widthMultiplier * 4.0f, 1.0f));
}
else
{
GUI.DrawLine(spriteBatch, start, end, colorSource != null ? colorSource.Color : Color.White * nodeColorMultiplier, width: 2);
GUI.DrawLine(spriteBatch, start, end, colorSource?.Color ?? Color.White * nodeColorMultiplier, width: Math.Max(widthMultiplier * 2.0f, 1.0f));
}
}
@@ -1966,7 +2149,12 @@ namespace Barotrauma
{
if (commandFrame == null) { return false; }
RemoveOptionNodes();
if (targetFrame != null) { targetFrame.Visible = false; }
if (targetFrame != null)
{
targetFrame.Visible = false;
nodeConnectors.RectTransform.Parent = commandFrame.RectTransform;
nodeConnectors.RectTransform.RepositionChildInHierarchy(1);
}
// TODO: Center node could move to option node instead of being removed
commandFrame.RemoveChild(centerNode);
SetCenterNode(node);
@@ -2126,6 +2314,8 @@ namespace Barotrauma
private void CreateShortcutNodes()
{
bool HasAppropriateJob(Character c, string jobId) => c.Info?.Job != null && c.Info.Job.Prefab.AppropriateOrders.Contains(jobId);
Submarine sub = GetTargetSubmarine();
if (sub == null) { return; }
@@ -2138,7 +2328,7 @@ namespace Barotrauma
var reactorOutput = -reactor.CurrPowerConsumption;
// If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor
// ---> Create shortcut node for "Operate Reactor" order's "Power Up" option
if ((Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) &&
if ((Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "operatereactor")) &&
reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item))
{
var order = new Order(Order.GetPrefab("operatereactor"), reactor.Item, reactor, Character.Controlled);
@@ -2151,7 +2341,7 @@ namespace Barotrauma
// TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it
// If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up
// --> Create shortcut node for Steer order
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("captain")) &&
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "steer")) &&
sub.GetItems(false).Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
{
@@ -2161,8 +2351,8 @@ namespace Barotrauma
// If player is not a security officer AND invaders are reported
// --> Create shorcut node for Fight Intruders order
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("securityofficer")) &&
(Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders)))
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "fightintruders")) &&
Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders))
{
shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fightintruders"), -1));
@@ -2170,25 +2360,43 @@ namespace Barotrauma
// If player is not a mechanic AND a breach has been reported
// --> Create shorcut node for Fix Leaks order
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("mechanic")) &&
(Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach)))
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "fixleaks")) &&
Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach))
{
shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fixleaks"), -1));
}
// If player is not an engineer AND broken devices have been reported
// --> Create shortcut node for Repair Damaged Systems order
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) &&
(Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices)))
// --> Create shortcut nodes for the Repair orders
if (shortcutNodes.Count < maxShortCutNodeCount && Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices))
{
shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("repairsystems"), -1));
// TODO: Doesn't work for player issued reports, because they don't have a target.
int repairNodes = 0;
string tag = "repairelectrical";
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, tag)) && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices && o.First.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "electrical")))
{
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab(tag), -1));
repairNodes++;
}
tag = "repairmechanical";
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, tag)) && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices && o.First.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "mechanical")))
{
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab(tag), -1));
repairNodes++;
}
if (repairNodes == 0 && shortcutNodes.Count < maxShortCutNodeCount)
{
tag = "repairsystems";
if (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, tag))
{
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab(tag), -1));
}
}
}
// If fire is reported
// --> Create shortcut node for Extinguish Fires order
if (shortcutNodes.Count < maxShortCutNodeCount && ActiveOrders.Any(o=> o.First.Prefab == Order.GetPrefab("reportfire")))
if (shortcutNodes.Count < maxShortCutNodeCount && ActiveOrders.Any(o => o.First.Prefab == Order.GetPrefab("reportfire")))
{
shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("extinguishfires"), -1));
@@ -2555,20 +2763,22 @@ namespace Barotrauma
if (itemTargetFrame == null) { continue; }
var anchor = Anchor.TopLeft;
if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f && itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f)
if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f)
{
anchor = Anchor.BottomRight;
if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f)
{
anchor = Anchor.BottomRight;
}
else
{
anchor = Anchor.TopRight;
}
}
else if (itemTargetFrame.RectTransform.RelativeOffset.X > 0.5f && itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f)
else if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f)
{
anchor = Anchor.BottomLeft;
}
else if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f && itemTargetFrame.RectTransform.RelativeOffset.Y > 0.5f)
{
anchor = Anchor.TopRight;
}
GUIComponent optionElement;
if (order.Options.Length > 1)
{
@@ -2631,7 +2841,6 @@ namespace Barotrauma
{
UserData = userData,
Font = GUI.SmallFont,
ToolTip = item?.Name ?? GetOrderNameBasedOnContextuality(order),
OnClicked = (_, userData) =>
{
if (!CanIssueOrders) { return false; }
@@ -2645,21 +2854,31 @@ namespace Barotrauma
{
optionElement.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button);
}
Sprite icon = null;
order.MinimapIcons?.TryGetValue(item.Prefab.Identifier, out icon);
if (item.Prefab.MinimapIcon != null)
{
icon = item.Prefab.MinimapIcon;
}
var colorMultiplier = characters.Any(c => c.CurrentOrders.Any(o => o.Order != null &&
o.Order.Identifier == userData.Item1.Identifier &&
o.Order.TargetEntity == userData.Item1.TargetEntity)) ? 0.5f : 1f;
CreateNodeIcon(Vector2.One, optionElement.RectTransform, icon ?? order.SymbolSprite, order.Color * colorMultiplier);
CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.SymbolSprite, order.Color * colorMultiplier, tooltip: item.Name);
optionNodes.Add(new Tuple<GUIComponent, Keys>(optionElement, Keys.None));
}
optionElements.Add(optionElement);
}
GUI.PreventElementOverlap(optionElements, clampArea: new Rectangle(10, 10, GameMain.GraphicsWidth - 20, GameMain.GraphicsHeight - 20));
Rectangle clampArea = new Rectangle(10, 10, GameMain.GraphicsWidth - 20, GameMain.GraphicsHeight - 20);
if (order.Identifier == "operateweapons")
{
Rectangle disallowedArea = targetFrame.GetChild<GUIFrame>().Rect;
Point originalSize = disallowedArea.Size;
disallowedArea.Size = disallowedArea.MultiplySize(0.9f);
disallowedArea.X += (originalSize.X - disallowedArea.Size.X) / 2;
disallowedArea.Y += (originalSize.Y - disallowedArea.Size.Y) / 2;
GUI.PreventElementOverlap(optionElements, new List<Rectangle>() { disallowedArea }, clampArea);
nodeConnectors.RectTransform.Parent = targetFrame.RectTransform;
nodeConnectors.RectTransform.SetAsFirstChild();
}
else
{
GUI.PreventElementOverlap(optionElements, clampArea: clampArea);
}
var shadow = new GUIFrame(
new RectTransform(targetFrame.Rect.Size + new Point((int)(200 * GUI.Scale)), targetFrame.RectTransform, anchor: Anchor.Center),
@@ -2731,6 +2950,12 @@ namespace Barotrauma
private bool CreateAssignmentNodes(GUIComponent node)
{
if (centerNode == null)
{
DisableCommandUI();
return false;
}
var order = (node.UserData is Order) ?
new Tuple<Order, string>(node.UserData as Order, null) :
node.UserData as Tuple<Order, string>;
@@ -2777,6 +3002,8 @@ namespace Barotrauma
node = null;
}
targetFrame.Visible = false;
nodeConnectors.RectTransform.Parent = commandFrame.RectTransform;
nodeConnectors.RectTransform.RepositionChildInHierarchy(1);
}
if (shortcutCenterNode != null)
{
@@ -3182,16 +3409,7 @@ namespace Barotrauma
private Character GetCharacterForQuickAssignment(Order order)
{
var controllingCharacter = Character.Controlled != null;
#if !DEBUG
if (!controllingCharacter) { return null; }
#endif
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter) &&
(!controllingCharacter || operatingCharacter.CanHearCharacter(Character.Controlled)))
{
return operatingCharacter;
}
return GetCharactersSortedForOrder(order, false).FirstOrDefault(c => !controllingCharacter || c.CanHearCharacter(Character.Controlled)) ?? Character.Controlled;
return GetCharacterForQuickAssignment(order, Character.Controlled, characters);
}
private List<Character> GetCharactersForManualAssignment(Order order)
@@ -3203,34 +3421,15 @@ namespace Barotrauma
{
return characters.Union(GetOrderableFriendlyNPCs()).Where(c => !c.IsDismissed).OrderBy(c => c.Info.DisplayName).ToList();
}
return GetCharactersSortedForOrder(order, order.Identifier != "follow").ToList();
}
private IEnumerable<Character> GetCharactersSortedForOrder(Order order, bool includeSelf)
{
return characters.Where(c => Character.Controlled == null || ((includeSelf || c != Character.Controlled) && c.TeamID == Character.Controlled.TeamID)).Union(GetOrderableFriendlyNPCs())
// 1. Prioritize those who are on the same submarine than the controlled character
.OrderByDescending(c => Character.Controlled == null || c.Submarine == Character.Controlled.Submarine)
// 2. Prioritize those who have been given the same maintenance or operate order as now issued
.ThenByDescending(c => c.CurrentOrders.Any(o =>
o.Order != null && o.Order.Identifier == order.Identifier &&
(order.Category == OrderCategory.Maintenance || order.Category == OrderCategory.Operate)))
// 3. Prioritize those with the appropriate job for the order
.ThenByDescending(c => order.HasAppropriateJob(c))
// 4. Prioritize bots over player controlled characters
.ThenByDescending(c => c.IsBot)
// 5. Use the priority value of the current objective
.ThenBy(c => c.AIController is HumanAIController humanAI ? humanAI.ObjectiveManager.CurrentObjective?.Priority : 0)
// 6. Prioritize those with the best skill for the order
.ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill));
return GetCharactersSortedForOrder(order, characters, Character.Controlled, order.Identifier != "follow", extraCharacters: GetOrderableFriendlyNPCs()).ToList();
}
private IEnumerable<Character> GetOrderableFriendlyNPCs()
{
// TODO: change this so that we can get the data without having to rely on ui elements.
return crewList.Content.Children.Where(c => c.UserData is Character character && character.TeamID == CharacterTeamType.FriendlyNPC).Select(c => (Character)c.UserData);
}
#endregion
#endregion
@@ -3325,15 +3524,76 @@ namespace Barotrauma
public void Save(XElement parentElement)
{
XElement element = new XElement("crew");
foreach (CharacterInfo ci in characterInfos)
{
var infoElement = ci.Save(element);
if (ci.InventoryData != null) { infoElement.Add(ci.InventoryData); }
if (ci.HealthData != null) { infoElement.Add(ci.HealthData); }
if (ci.OrderData != null) { infoElement.Add(ci.OrderData); }
if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); }
}
SaveActiveOrders(element);
parentElement.Add(element);
}
public static void ClientReadActiveOrders(IReadMessage inc)
{
ushort count = inc.ReadUInt16();
if (count < 1) { return; }
var activeOrders = new List<(Order, float?)>();
for (ushort i = 0; i < count; i++)
{
var orderMessageInfo = OrderChatMessage.ReadOrder(inc);
Character orderGiver = null;
if (inc.ReadBoolean())
{
ushort orderGiverId = inc.ReadUInt16();
orderGiver = orderGiverId != Entity.NullEntityID ? Entity.FindEntityByID(orderGiverId) as Character : null;
}
if (orderMessageInfo.OrderIndex < 0 || orderMessageInfo.OrderIndex >= Order.PrefabList.Count)
{
DebugConsole.ThrowError("Invalid active order - order index out of bounds.");
continue;
}
Order orderPrefab = orderMessageInfo.OrderPrefab ?? Order.PrefabList[orderMessageInfo.OrderIndex];
Order order = orderMessageInfo.TargetType switch
{
Order.OrderTargetType.Entity =>
new Order(orderPrefab, orderMessageInfo.TargetEntity, orderPrefab.GetTargetItemComponent(orderMessageInfo.TargetEntity as Item), orderGiver: orderGiver),
Order.OrderTargetType.Position =>
new Order(orderPrefab, orderMessageInfo.TargetPosition, orderGiver: orderGiver),
Order.OrderTargetType.WallSection =>
new Order(orderPrefab, orderMessageInfo.TargetEntity as Structure, orderMessageInfo.WallSectionIndex, orderGiver: orderGiver),
_ => throw new NotImplementedException()
};
if (order != null && order.TargetAllCharacters)
{
var fadeOutTime = !orderPrefab.IsIgnoreOrder ? (float?)orderPrefab.FadeOutTime : null;
activeOrders.Add((order, fadeOutTime));
}
}
foreach (var (order, fadeOutTime) in activeOrders)
{
if (order.IsIgnoreOrder)
{
switch (order.TargetType)
{
case Order.OrderTargetType.Entity:
if (!(order.TargetEntity is IIgnorable ignorableEntity)) { break; }
ignorableEntity.OrderedToBeIgnored = order.Identifier == "ignorethis";
break;
case Order.OrderTargetType.Position:
throw new NotImplementedException();
case Order.OrderTargetType.WallSection:
if (!order.WallSectionIndex.HasValue) { break; }
if (!(order.TargetEntity is Structure s)) { break; }
if (!(s.GetSection(order.WallSectionIndex.Value) is IIgnorable ignorableWall)) { break; }
ignorableWall.OrderedToBeIgnored = order.Identifier == "ignorethis";
break;
}
}
GameMain.GameSession?.CrewManager?.AddOrder(order, fadeOutTime);
}
}
}
}

View File

@@ -189,7 +189,7 @@ namespace Barotrauma
case TransitionType.None:
default:
if (Level.Loaded.Type == LevelData.LevelType.Outpost &&
(Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false)))
(Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags.Contains("airlock") ?? false)))
{
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
@@ -287,6 +287,7 @@ namespace Barotrauma
{
case InteractionType.None:
case InteractionType.Talk:
case InteractionType.Examine:
return;
case InteractionType.Upgrade when !UpgradeManager.CanUpgradeSub():
UpgradeManager.CreateUpgradeErrorMessage(TextManager.Get("Dialog.CantUpgrade"), IsSinglePlayer, npc);

View File

@@ -211,10 +211,7 @@ namespace Barotrauma
{
Character.Controlled = null;
if (prevControlled != null)
{
prevControlled.ClearInputs();
}
prevControlled?.ClearInputs();
overlayColor = Color.LightGray;
overlaySprite = Map.CurrentLocation.Type.GetPortrait(Map.CurrentLocation.PortraitId);
@@ -326,7 +323,6 @@ namespace Barotrauma
Level prevLevel = Level.Loaded;
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
GUI.SetSavingIndicatorState(success);
crewDead = false;
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
@@ -484,6 +480,8 @@ namespace Barotrauma
{
IsFirstRound = false;
CoroutineManager.StartCoroutine(DoLevelTransition(), "LevelTransition");
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
GUI.SetSavingIndicatorState(success && (Level.IsLoadedOutpost || transitionType != TransitionType.None));
}
}
@@ -534,7 +532,13 @@ namespace Barotrauma
msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex);
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex);
var selectedMissionIndices = map.GetSelectedMissionIndices();
msg.Write((byte)selectedMissionIndices.Count());
foreach (int selectedMissionIndex in selectedMissionIndices)
{
msg.Write((byte)selectedMissionIndex);
}
msg.Write(PurchasedHullRepairs);
msg.Write(PurchasedItemRepairs);
msg.Write(PurchasedLostShuttles);
@@ -569,6 +573,13 @@ namespace Barotrauma
msg.Write(category.Identifier);
msg.Write((byte)level);
}
msg.Write((ushort)UpgradeManager.PurchasedItemSwaps.Count);
foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps)
{
msg.Write(itemSwap.ItemToRemove.ID);
msg.Write(itemSwap.ItemToInstall?.Identifier ?? string.Empty);
}
}
//static because we may need to instantiate the campaign if it hasn't been done yet
@@ -581,8 +592,15 @@ namespace Barotrauma
string mapSeed = msg.ReadString();
UInt16 currentLocIndex = msg.ReadUInt16();
UInt16 selectedLocIndex = msg.ReadUInt16();
byte selectedMissionIndex = msg.ReadByte();
bool allowDebugTeleport = msg.ReadBoolean();
byte selectedMissionCount = msg.ReadByte();
List<int> selectedMissionIndices = new List<int>();
for (int i = 0; i < selectedMissionCount; i++)
{
selectedMissionIndices.Add(msg.ReadByte());
}
bool allowDebugTeleport = msg.ReadBoolean();
float? reputation = null;
if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); }
@@ -657,6 +675,21 @@ namespace Barotrauma
pendingUpgrades.Add(new PurchasedUpgrade(prefab, category, upgradeLevel));
}
ushort purchasedItemSwapCount = msg.ReadUInt16();
List<PurchasedItemSwap> purchasedItemSwaps = new List<PurchasedItemSwap>();
for (int i = 0; i < purchasedItemSwapCount; i++)
{
UInt16 itemToRemoveID = msg.ReadUInt16();
Item itemToRemove = Entity.FindEntityByID(itemToRemoveID) as Item;
string itemToInstallIdentifier = msg.ReadString();
ItemPrefab itemToInstall = string.IsNullOrEmpty(itemToInstallIdentifier) ? null : ItemPrefab.Find(string.Empty, itemToInstallIdentifier);
if (itemToRemove == null) { continue; }
purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall));
}
bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null;
if (hasCharacterData)
@@ -694,7 +727,7 @@ namespace Barotrauma
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
campaign.Map.SelectMission(selectedMissionIndex);
campaign.Map.SelectMission(selectedMissionIndices);
campaign.Map.AllowDebugTeleport = allowDebugTeleport;
campaign.CargoManager.SetItemsInBuyCrate(buyCrateItems);
campaign.CargoManager.SetPurchasedItems(purchasedItems);
@@ -703,6 +736,26 @@ namespace Barotrauma
campaign.UpgradeManager.SetPendingUpgrades(pendingUpgrades);
campaign.UpgradeManager.PurchasedUpgrades.Clear();
campaign.UpgradeManager.PurchasedUpgrades.Clear();
foreach (var purchasedItemSwap in purchasedItemSwaps)
{
if (purchasedItemSwap.ItemToInstall == null)
{
campaign.UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove, force: true);
}
else
{
campaign.UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, force: true);
}
}
foreach (Item item in Item.ItemList)
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
item.PendingItemSwap = null;
}
}
foreach (var (identifier, rep) in factionReps)
{
Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));

View File

@@ -101,7 +101,8 @@ namespace Barotrauma
case "cargo":
CargoManager.LoadPurchasedItems(subElement);
break;
case "pendingupgrades":
case "pendingupgrades": //backwards compatibility
case "upgrademanager":
UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: true);
break;
case "pets":
@@ -229,6 +230,7 @@ namespace Barotrauma
{
PetBehavior.LoadPets(petsElement);
}
CrewManager.LoadActiveOrders();
GUI.DisableSavingIndicatorDelayed();
}
@@ -264,10 +266,7 @@ namespace Barotrauma
prevControlled.AIController.Enabled = false;
}
Character.Controlled = null;
if (prevControlled != null)
{
prevControlled.ClearInputs();
}
prevControlled?.ClearInputs();
GUI.DisableHUD = true;
while (GameMain.Instance.LoadingScreenOpen)
@@ -303,7 +302,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success;
}
overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration);
timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration);
timer = Math.Min(timer + CoroutineManager.UnscaledDeltaTime, textDuration);
yield return CoroutineStatus.Running;
}
var outpost = GameMain.GameSession.Level.StartOutpost;
@@ -331,7 +330,7 @@ namespace Barotrauma
while (timer < fadeInDuration)
{
overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration);
timer += CoroutineManager.DeltaTime;
timer += CoroutineManager.UnscaledDeltaTime;
yield return CoroutineStatus.Running;
}
overlayColor = Color.Transparent;
@@ -446,9 +445,13 @@ namespace Barotrauma
{
Submarine.MainSub = leavingSub;
GameMain.GameSession.Submarine = leavingSub;
GameMain.GameSession.SubmarineInfo = leavingSub.Info;
leavingSub.Info.FilePath = System.IO.Path.Combine(SaveUtil.TempPath, leavingSub.Info.Name + ".sub");
var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
GameMain.GameSession.OwnedSubmarines.Add(leavingSub.Info);
foreach (Submarine sub in subsToLeaveBehind)
{
GameMain.GameSession.OwnedSubmarines.RemoveAll(s => s != leavingSub.Info && s.Name == sub.Info.Name);
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
LinkedSubmarine.CreateDummy(leavingSub, sub);
}
@@ -480,6 +483,8 @@ namespace Barotrauma
EnableRoundSummaryGameOverState();
}
CrewManager?.ClearCurrentOrders();
//--------------------------------------
SelectSummaryScreen(roundSummary, newLevel, mirror, () =>
@@ -558,7 +563,7 @@ namespace Barotrauma
}
#if DEBUG
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.R))
if (GUI.KeyboardDispatcher.Subscriber == null && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.M))
{
if (GUIMessageBox.MessageBoxes.Any()) { GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.MessageBoxes.Last()); }
@@ -735,9 +740,10 @@ namespace Barotrauma
if (c.Inventory != null)
{
c.Info.InventoryData = new XElement("inventory");
c.SaveInventory(c.Inventory, c.Info.InventoryData);
c.SaveInventory();
c.Inventory?.DeleteAllItems();
}
c.Info.SaveOrderData();
}
petsElement = new XElement("pets");
@@ -748,7 +754,7 @@ namespace Barotrauma
CampaignMetadata.Save(modeElement);
Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement);
UpgradeManager?.SavePendingUpgrades(modeElement, UpgradeManager?.PendingUpgrades);
UpgradeManager?.Save(modeElement);
element.Add(modeElement);
}
}

View File

@@ -123,17 +123,17 @@ namespace Barotrauma.Tutorials
SetDoorAccess(tutorial_lockedDoor_2, null, false);
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("mechanic"));
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic.TeamID = CharacterTeamType.Team1;
captain_mechanic.GiveJobItems();
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer"));
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security.TeamID = CharacterTeamType.Team1;
captain_security.GiveJobItems();
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer.TeamID = CharacterTeamType.Team1;
captain_engineer.GiveJobItems();

View File

@@ -94,19 +94,19 @@ namespace Barotrauma.Tutorials
patient2.AIController.Enabled = false;
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3");
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
subPatient1.TeamID = CharacterTeamType.Team1;
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient1);
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer"));
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3");
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
subPatient2.TeamID = CharacterTeamType.Team1;
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient2);
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3");
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
subPatient3.TeamID = CharacterTeamType.Team1;
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient3);

View File

@@ -531,7 +531,7 @@ namespace Barotrauma.Tutorials
}
}
yield return null;
} while (!mechanic.HasEquippedItem("divingsuit"));
} while (!mechanic.HasEquippedItem("divingsuit", slotType: InvSlotType.OuterClothes));
SetHighlight(mechanic_divingSuitContainer.Item, false);
RemoveCompletedObjective(segments[8]);
SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true);

View File

@@ -177,7 +177,7 @@ namespace Barotrauma.Tutorials
}
}
return WayPoint.GetRandom(spawnPointType, charInfo.Job, spawnSub);
return WayPoint.GetRandom(spawnPointType, charInfo.Job?.Prefab, spawnSub);
}
protected bool HasOrder(Character character, string identifier, string option = null)

View File

@@ -224,10 +224,7 @@ namespace Barotrauma.Tutorials
public virtual void Update(float deltaTime)
{
if (videoPlayer != null)
{
videoPlayer.Update();
}
videoPlayer?.Update();
if (activeObjectives != null)
{

View File

@@ -524,7 +524,7 @@ namespace Barotrauma
private static void CheckIfDivingGearOutOfOxygen()
{
if (!CanDisplayHints()) { return; }
var divingGear = Character.Controlled.GetEquippedItem("diving");
var divingGear = Character.Controlled.GetEquippedItem("diving", InvSlotType.OuterClothes);
if (divingGear?.OwnInventory == null) { return; }
if (divingGear.GetContainedItemConditionPercentage() > 0.0f) { return; }
DisplayHint("ondivinggearoutofoxygen", onUpdate: () =>

View File

@@ -218,12 +218,15 @@ namespace Barotrauma
};
List<Mission> missionsToDisplay = new List<Mission>(selectedMissions);
if (!selectedMissions.Any() && startLocation?.SelectedMission != null)
{
if (startLocation.SelectedMission.Locations[0] == startLocation.SelectedMission.Locations[1] ||
startLocation.SelectedMission.Locations.Contains(campaignMode?.Map.SelectedLocation))
if (!selectedMissions.Any() && startLocation != null)
{
foreach (Mission mission in startLocation.SelectedMissions)
{
missionsToDisplay.Add(startLocation.SelectedMission);
if (mission.Locations[0] == mission.Locations[1] ||
mission.Locations.Contains(campaignMode?.Map.SelectedLocation))
{
missionsToDisplay.Add(mission);
}
}
}
@@ -284,10 +287,11 @@ namespace Barotrauma
new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), displayedMission.Completed ? "MissionCompletedIcon" : "MissionFailedIcon", scaleToFit: true);
}
var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), missionContentHorizontal.RectTransform))
var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), missionContentHorizontal.RectTransform))
{
RelativeSpacing = 0.05f
AbsoluteSpacing = GUI.IntScale(5)
};
missionContentHorizontal.Recalculate();
var missionNameTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
displayedMission.Name, font: GUI.SubHeadingFont);
if (displayedMission.Difficulty.HasValue)
@@ -309,12 +313,13 @@ namespace Barotrauma
};
}
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
var missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
missionMessage, wrap: true, parseRichText: true);
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && displayedMission.Reward > 0)
int reward = displayedMission.GetReward(Submarine.MainSub);
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && reward > 0)
{
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", displayedMission.Reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), displayedMission.GetMissionRewardText(), parseRichText: true);
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), displayedMission.GetMissionRewardText(Submarine.MainSub), parseRichText: true);
}
if (displayedMission != missionsToDisplay.Last())
@@ -322,6 +327,13 @@ namespace Barotrauma
var spacing = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), missionList.Content.RectTransform) { MaxSize = new Point(int.MaxValue, GUI.IntScale(15)) }, style: null);
new GUIFrame(new RectTransform(new Vector2(0.8f, 1.0f), spacing.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.1f, 0.0f) }, "HorizontalLine");
}
foreach (GUIComponent child in missionTextContent.Children)
{
child.RectTransform.IsFixedSize = true;
}
missionTextContent.RectTransform.MinSize = new Point(0, missionTextContent.Children.Sum(c => c.Rect.Height + missionTextContent.AbsoluteSpacing));
missionContentHorizontal.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Rect.Height / missionTextContent.RectTransform.RelativeSize.Y));
}
if (!missionsToDisplay.Any())

View File

@@ -39,43 +39,5 @@ namespace Barotrauma
}
}
}
/// <summary>
/// Server has notified us that upgrades were reset.
/// </summary>
/// <param name="inc"></param>
/// <see cref="UpgradeManager.SendUpgradeResetMessage"/>
public void ClientRead(IReadMessage inc)
{
bool shouldReset = inc.ReadBoolean();
int money = inc.ReadInt32();
// uint length = inc.ReadUInt32();
//
// for (int i = 0; i < length; i++)
// {
// string key = inc.ReadString();
// byte value = inc.ReadByte();
// Metadata.SetValue(key, value);
// }
Campaign.Money = money;
if (shouldReset)
{
ResetUpgrades();
}
// spentMoney is local, so this message box should only appear for those who have spent money on upgrades
if (spentMoney > 0)
{
GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("UpgradeRefundTitle"), TextManager.Get("UpgradeRefundBody"), new [] { TextManager.Get("Ok") });
msgBox.Buttons[0].OnClicked += msgBox.Close;
}
spentMoney = 0;
PendingUpgrades.Clear();
PurchasedUpgrades.Clear();
CanUpgrade = false;
}
}
}

View File

@@ -1077,8 +1077,8 @@ namespace Barotrauma
new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice"), font: GUI.SubHeadingFont);
var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.Voice))
var voiceKeybindLabel = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice"), font: GUI.SubHeadingFont);
var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.3f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.Voice))
{
SelectedColor = Color.Gold * 0.3f,
UserData = InputType.Voice
@@ -1089,14 +1089,16 @@ namespace Barotrauma
new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), localVoiceInputContainer.RectTransform), TextManager.Get("InputType.LocalVoice"), font: GUI.SubHeadingFont);
var localVoiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), localVoiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.LocalVoice))
var localVoiceKeybindLabel = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), localVoiceInputContainer.RectTransform), TextManager.Get("InputType.LocalVoice"), font: GUI.SubHeadingFont);
var localVoiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.3f, 1.0f), localVoiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.LocalVoice))
{
SelectedColor = Color.Gold * 0.3f,
UserData = InputType.LocalVoice
};
localVoiceKeyBox.OnSelected += KeyBoxSelected;
voiceKeybindLabel.RectTransform.SizeChanged += () => { GUITextBlock.AutoScaleAndNormalize(voiceKeybindLabel, localVoiceKeybindLabel); };
var cutoffPreventionText = new GUITextBlock(new RectTransform(textBlockScale, voiceChatContent.RectTransform), TextManager.Get("CutoffPrevention"), font: GUI.SubHeadingFont)
{
ToolTip = TextManager.Get("CutoffPreventionTooltip")
@@ -1326,24 +1328,41 @@ namespace Barotrauma
}
};
GUITickBox disableInGameHintsBox = new GUITickBox(new RectTransform(tickBoxScale, gameplaySettingsGroup.RectTransform),
TextManager.Get("DisableInGameHints"))
new GUITickBox(new RectTransform(tickBoxScale, gameplaySettingsGroup.RectTransform), TextManager.Get("DisableInGameHints"))
{
Selected = DisableInGameHints,
ToolTip = TextManager.Get("DisableInGameHintsToolTip"),
OnSelected = (tickBox) =>
{
DisableInGameHints = tickBox.Selected;
if (!DisableInGameHints && GameMain.Config?.IgnoredHints != null)
{
// Reset the ignored hints when the hints are re-enabled (to-be-replaced by a separate button)
GameMain.Config.IgnoredHints.Clear();
}
UnsavedSettings = true;
return true;
}
};
new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform),
text: TextManager.Get("ResetInGameHints"),
style: "GUIButtonSmall")
{
OnClicked = (button, userData) =>
{
var msgBox = new GUIMessageBox(TextManager.Get("ResetInGameHints"),
TextManager.Get("ResetInGameHintsTooltip"),
new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") })
{
UserData = "verificationprompt"
};
msgBox.Buttons[0].OnClicked = (button, userData) =>
{
GameMain.Config.IgnoredHints.Clear();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = msgBox.Close;
return false;
}
};
GUITextBlock HUDScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), TextManager.Get("HUDScale"), font: GUI.SubHeadingFont, wrap: true);
GUIScrollBar HUDScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform),
style: "GUISlider", barSize: 0.1f)

View File

@@ -297,11 +297,11 @@ namespace Barotrauma
SlotSize = !isFourByThree ? (SlotSpriteSmall.size * UIScale).ToPoint() : (SlotSpriteSmall.size * UIScale * .925f).ToPoint();
int bottomOffset = SlotSize.Y + Spacing * 2 + ContainedIndicatorHeight;
hideButton.Visible = false;
if (visualSlots == null) { CreateSlots(); }
if (visualSlots.None()) { return; }
hideButton.Visible = false;
switch (layout)
{
case Layout.Default:
@@ -537,9 +537,12 @@ namespace Barotrauma
}
List<SlotReference> hideSubInventories = new List<SlotReference>();
//remove highlighted subinventory slots that can no longer be accessed
highlightedSubInventorySlots.RemoveWhere(s =>
s.ParentInventory == this &&
((s.SlotIndex < 0 || s.SlotIndex >= slots.Length || slots[s.SlotIndex] == null) || (Character.Controlled != null && !Character.Controlled.CanAccessInventory(s.Inventory))));
//remove highlighted subinventory slots that refer to items no longer in this inventory
highlightedSubInventorySlots.RemoveWhere(s => s.Item != null && s.ParentInventory == this && s.Item.ParentInventory != this);
foreach (var highlightedSubInventorySlot in highlightedSubInventorySlots)
{
if (highlightedSubInventorySlot.ParentInventory == this)
@@ -910,7 +913,7 @@ namespace Barotrauma
else if (allowEquip) //doubleclicked and no other inventory is selected
{
//not equipped -> attempt to equip
if (!character.HasEquippedItem(item))
if (!character.HasEquippedItem(item) || item.GetComponents<Pickable>().Count() > 1)
{
return QuickUseAction.Equip;
}
@@ -1100,6 +1103,7 @@ namespace Barotrauma
public void DrawOwn(SpriteBatch spriteBatch)
{
if (!AccessibleWhenAlive && !character.IsDead) { return; }
if (capacity == 0) { return; }
if (visualSlots == null) { CreateSlots(); }
if (GameMain.GraphicsWidth != screenResolution.X ||
GameMain.GraphicsHeight != screenResolution.Y ||

View File

@@ -281,10 +281,10 @@ namespace Barotrauma.Items.Components
foreach (ParticleEmitter particleEmitter in particleEmitters)
{
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
float particleRange = particleEmitter.Prefab.VelocityMax * particleEmitter.Prefab.ParticlePrefab.LifeTime;
float particleRange = particleEmitter.Prefab.Properties.VelocityMax * particleEmitter.Prefab.ParticlePrefab.LifeTime;
particleEmitter.Emit(
deltaTime, particleStartPos,
item.CurrentHull, particleAngle, particleEmitter.Prefab.CopyEntityAngle ? -particleAngle : 0, velocityMultiplier: dist / particleRange * 1.5f,
item.CurrentHull, particleAngle, particleEmitter.Prefab.Properties.CopyEntityAngle ? -particleAngle : 0, velocityMultiplier: dist / particleRange * 1.5f,
colorMultiplier: new Color(color.R, color.G, color.B, (byte)255));
}
}

View File

@@ -611,5 +611,6 @@ namespace Barotrauma.Items.Components
}
OnResolutionChanged();
}
public virtual void AddTooltipInfo(ref string description) { }
}
}

View File

@@ -14,6 +14,11 @@ namespace Barotrauma.Items.Components
private GUICustomComponent guiCustomComponent;
/// <summary>
/// Can be used to set the sprite depth individually for each contained item
/// </summary>
private float[] containedSpriteDepths;
public Sprite InventoryTopSprite
{
get { return inventoryTopSprite; }
@@ -153,6 +158,8 @@ namespace Barotrauma.Items.Components
//if a GUIFrame has been defined, draw the inventory inside it
CreateGUI();
}
containedSpriteDepths = element.GetAttributeFloatArray("containedspritedepths", new float[0]);
}
protected override void CreateGUI()
@@ -316,6 +323,10 @@ namespace Barotrauma.Items.Components
if (item.FlippedY) { origin.Y = containedItem.Sprite.SourceRect.Height - origin.Y; }
float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? containedItem.Sprite.Depth : ContainedSpriteDepth;
if (i < containedSpriteDepths.Length)
{
containedSpriteDepth = containedSpriteDepths[i];
}
containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f;
containedItem.Sprite.Draw(

View File

@@ -16,44 +16,43 @@ namespace Barotrauma.Items.Components
public Vector2 DrawSize
{
get { return new Vector2(light.Range * 2, light.Range * 2); }
get { return new Vector2(Light.Range * 2, Light.Range * 2); }
}
private LightSource light;
public LightSource Light
{
get { return light; }
}
public LightSource Light { get; }
public override void OnScaleChanged()
{
light.SpriteScale = Vector2.One * item.Scale;
light.Position = ParentBody != null ? ParentBody.Position : item.Position;
Light.SpriteScale = Vector2.One * item.Scale;
Light.Position = ParentBody != null ? ParentBody.Position : item.Position;
}
partial void SetLightSourceState(bool enabled, float brightness)
{
if (light == null) { return; }
light.Enabled = enabled;
light.Color = LightColor.Multiply(brightness);
if (Light == null) { return; }
Light.Enabled = enabled;
if (enabled)
{
Light.Color = LightColor.Multiply(brightness);
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{
if (light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn)
if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn)
{
Vector2 origin = light.LightSprite.Origin;
if ((light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = light.LightSprite.SourceRect.Width - origin.X; }
if ((light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = light.LightSprite.SourceRect.Height - origin.Y; }
light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, origin, -light.Rotation, item.Scale, light.LightSpriteEffect, itemDepth - 0.0001f);
Vector2 origin = Light.LightSprite.Origin;
if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; }
if ((Light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = Light.LightSprite.SourceRect.Height - origin.Y; }
Light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, origin, -Light.Rotation, item.Scale, Light.LightSpriteEffect, itemDepth - 0.0001f);
}
}
public override void FlipX(bool relativeToSub)
{
if (light?.LightSprite != null && item.Prefab.CanSpriteFlipX && item.body == null)
if (Light?.LightSprite != null && item.Prefab.CanSpriteFlipX && item.body == null)
{
light.LightSpriteEffect = light.LightSpriteEffect == SpriteEffects.None ?
Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ?
SpriteEffects.FlipHorizontally : SpriteEffects.None;
}
}
@@ -93,7 +92,7 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
light.Remove();
Light.Remove();
}
}
}

View File

@@ -178,26 +178,26 @@ namespace Barotrauma.Items.Components
ToolTip = fi.TargetItem.Description
};
var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform),
childAnchor: Anchor.CenterLeft, isHorizontal: true) { RelativeSpacing = 0.02f };
var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform),
childAnchor: Anchor.CenterLeft, isHorizontal: true) { RelativeSpacing = 0.02f };
var itemIcon = fi.TargetItem.InventoryIcon ?? fi.TargetItem.sprite;
if (itemIcon != null)
{
new GUIImage(new RectTransform(new Point(frame.Rect.Height,frame.Rect.Height), container.RectTransform),
itemIcon, scaleToFit: true)
{
Color = fi.TargetItem.InventoryIconColor,
ToolTip = fi.TargetItem.Description
};
}
var itemIcon = fi.TargetItem.InventoryIcon ?? fi.TargetItem.sprite;
if (itemIcon != null)
{
new GUIImage(new RectTransform(new Point(frame.Rect.Height,frame.Rect.Height), container.RectTransform),
itemIcon, scaleToFit: true)
{
Color = fi.TargetItem.InventoryIconColor,
ToolTip = fi.TargetItem.Description
};
}
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform), GetRecipeNameAndAmount(fi))
{
Padding = Vector4.Zero,
AutoScaleVertical = true,
ToolTip = fi.TargetItem.Description
};
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform), GetRecipeNameAndAmount(fi))
{
Padding = Vector4.Zero,
AutoScaleVertical = true,
ToolTip = fi.TargetItem.Description
};
}
}
@@ -367,6 +367,10 @@ namespace Barotrauma.Items.Components
{
toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%";
}
else if (requiredItem.MaxCondition <= 0.0f)
{
toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText);
}
if (!string.IsNullOrEmpty(requiredItem.ItemPrefabs.First().Description))
{
toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description;

View File

@@ -199,13 +199,9 @@ namespace Barotrauma.Items.Components
}
Color neutralColor = Color.DarkCyan;
if (hull.RoomName != null)
if (hull.IsWetRoom)
{
if (hull.RoomName.Contains("ballast") || hull.RoomName.Contains("Ballast") ||
hull.RoomName.Contains("airlock") || hull.RoomName.Contains("Airlock"))
{
neutralColor = new Color(9, 80, 159);
}
neutralColor = new Color(9, 80, 159);
}
if (hullData.Distort)

View File

@@ -33,10 +33,7 @@ namespace Barotrauma.Items.Components
base.Update(deltaTime, cam);
if (selectionUI != null)
{
selectionUI.Update();
}
selectionUI?.Update();
}
}
}

View File

@@ -16,8 +16,8 @@ namespace Barotrauma.Items.Components
private GUITickBox powerLight;
private GUITickBox autoControlIndicator;
private List<Pair<Vector2, ParticleEmitter>> pumpOutEmitters = new List<Pair<Vector2, ParticleEmitter>>();
private List<Pair<Vector2, ParticleEmitter>> pumpInEmitters = new List<Pair<Vector2, ParticleEmitter>>();
private readonly List<(Vector2 position, ParticleEmitter emitter)> pumpOutEmitters = new List<(Vector2 position, ParticleEmitter emitter)>();
private readonly List<(Vector2 position, ParticleEmitter emitter)> pumpInEmitters = new List<(Vector2 position, ParticleEmitter emitter)>();
public float CurrentBrokenVolume
{
@@ -35,14 +35,10 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "pumpoutemitter":
pumpOutEmitters.Add(new Pair<Vector2, ParticleEmitter>(
subElement.GetAttributeVector2("position", Vector2.Zero),
new ParticleEmitter(subElement)));
pumpOutEmitters.Add((subElement.GetAttributeVector2("position", Vector2.Zero), new ParticleEmitter(subElement)));
break;
case "pumpinemitter":
pumpInEmitters.Add(new Pair<Vector2, ParticleEmitter>(
subElement.GetAttributeVector2("position", Vector2.Zero),
new ParticleEmitter(subElement)));
pumpInEmitters.Add((subElement.GetAttributeVector2("position", Vector2.Zero), new ParticleEmitter(subElement)));
break;
}
}
@@ -148,21 +144,43 @@ namespace Barotrauma.Items.Components
{
if (FlowPercentage < 0.0f)
{
foreach (Pair<Vector2, ParticleEmitter> pumpOutEmitter in pumpOutEmitters)
foreach (var (position, emitter) in pumpOutEmitters)
{
//only emit "pump out" particles when underwater
Vector2 particlePos = item.Rect.Location.ToVector2() + pumpOutEmitter.First;
if (item.CurrentHull != null && item.CurrentHull.Surface < particlePos.Y) continue;
if (item.CurrentHull != null && item.CurrentHull.Surface < item.Rect.Location.Y + position.Y) { continue; }
pumpOutEmitter.Second.Emit(deltaTime, item.WorldRect.Location.ToVector2() + pumpOutEmitter.First * item.Scale, item.CurrentHull,
//only emit "pump out" particles when underwater
Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition;
float angle = 0.0f;
if (item.FlippedX)
{
relativeParticlePos.X = -relativeParticlePos.X;
angle = MathHelper.Pi;
}
if (item.FlippedY)
{
relativeParticlePos.Y = -relativeParticlePos.Y;
}
emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle,
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, -FlowPercentage / 100.0f));
}
}
else if (FlowPercentage > 0.0f)
{
foreach (Pair<Vector2, ParticleEmitter> pumpInEmitter in pumpInEmitters)
foreach (var (position, emitter) in pumpInEmitters)
{
pumpInEmitter.Second.Emit(deltaTime, item.WorldRect.Location.ToVector2() + pumpInEmitter.First * item.Scale, item.CurrentHull,
Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition;
float angle = 0.0f;
if (item.FlippedX)
{
relativeParticlePos.X = -relativeParticlePos.X;
angle = MathHelper.Pi;
}
if (item.FlippedY)
{
relativeParticlePos.Y = -relativeParticlePos.Y;
}
emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle,
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, FlowPercentage / 100.0f));
}
}

View File

@@ -53,6 +53,8 @@ namespace Barotrauma.Items.Components
private GUIFrame inventoryContainer;
private GUILayoutGroup paddedFrame;
private readonly Dictionary<string, GUIButton> warningButtons = new Dictionary<string, GUIButton>();
private static readonly string[] warningTexts = new string[]
@@ -74,7 +76,7 @@ namespace Barotrauma.Items.Components
tempRangeIndicator = new Sprite(element.GetChildElement("temprangeindicator")?.GetChildElement("sprite"));
graphLine = new Sprite(element.GetChildElement("graphline")?.GetChildElement("sprite"));
var paddedFrame = new GUILayoutGroup(new RectTransform(
paddedFrame = new GUILayoutGroup(new RectTransform(
GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
{ AbsoluteOffset = GUIStyle.ItemFrameOffset },
isHorizontal: true)
@@ -128,27 +130,27 @@ namespace Barotrauma.Items.Components
Point maxIndicatorSize = new Point(int.MaxValue, (int)(40 * GUI.Scale));
criticalHeatWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
criticalHeatWarning = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{
Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorHeatTip")
};
lowTemperatureWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalLowTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{
Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorTempTip")
};
criticalOutputWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
criticalOutputWarning = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalOutput"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{
Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorOutputTip")
};
lowTemperatureWarning = new GUITickBox(new RectTransform(new Vector2(0.4f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalLowTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{
Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorTempTip")
};
List<GUITickBox> indicatorLights = new List<GUITickBox>() { criticalHeatWarning, lowTemperatureWarning, criticalOutputWarning };
indicatorLights.ForEach(l => l.TextBlock.OverrideTextColor(GUI.Style.TextColor));
topLeftArea.Recalculate();
@@ -334,8 +336,9 @@ namespace Barotrauma.Items.Components
};
topRightArea.Recalculate();
autoTempLight.TextBlock.Wrap = true;
indicatorLights.Add(autoTempLight);
autoTempLight.TextBlock.Padding = new Vector4(autoTempLight.TextBlock.Padding.X, 0.0f, 0.0f, 0.0f);
autoTempLight.TextBlock.Text = autoTempLight.TextBlock.Text.Replace(' ', '\n');
autoTempLight.TextBlock.AutoScaleHorizontal = true;
GUITextBlock.AutoScaleAndNormalize(indicatorLights.Select(l => l.TextBlock));
// right bottom (graph area) -----------------------
@@ -533,8 +536,7 @@ namespace Barotrauma.Items.Components
warningButtons["ReactorWarningMeltdown"].Selected = meltDownTimer > MeltdownDelay * 0.5f || item.Condition == 0.0f && lightOn;
warningButtons["ReactorWarningSCRAM"].Selected = temperature > 0.1f && !PowerOn;
if ((FissionRateScrollBar.Rect.Contains(PlayerInput.MousePosition) || FissionRateScrollBar.Children.Contains(GUIScrollBar.DraggingBar) ||
TurbineOutputScrollBar.Rect.Contains(PlayerInput.MousePosition) || TurbineOutputScrollBar.Children.Contains(GUIScrollBar.DraggingBar)) &&
if (paddedFrame.Rect.Contains(PlayerInput.MousePosition) &&
!PlayerInput.KeyDown(InputType.Deselect) && !PlayerInput.KeyHit(InputType.Deselect))
{
Character.DisableControls = true;

View File

@@ -87,21 +87,6 @@ namespace Barotrauma.Items.Components
//float = strength of the disruption, between 0-1
private readonly List<Pair<Vector2, float>> disruptedDirections = new List<Pair<Vector2, float>>();
class CachedDistance
{
public readonly Vector2 TransducerWorldPos;
public readonly Vector2 WorldPos;
public readonly float Distance;
public double RecalculationTime;
public CachedDistance(Vector2 transducerWorldPos, Vector2 worldPos, float dist)
{
TransducerWorldPos = transducerWorldPos;
WorldPos = worldPos;
Distance = dist;
}
}
private readonly Dictionary<object, CachedDistance> markerDistances = new Dictionary<object, CachedDistance>();
private readonly Color positiveColor = Color.Green;
@@ -451,7 +436,9 @@ namespace Barotrauma.Items.Components
connectedSubUpdateTimer = ConnectedSubUpdateInterval;
}
if (sonarView.Rect.Contains(PlayerInput.MousePosition))
Steering steering = item.GetComponent<Steering>();
if (sonarView.Rect.Contains(PlayerInput.MousePosition) &&
(GUI.MouseOn == null || GUI.MouseOn == sonarView || sonarView.IsParentOf(GUI.MouseOn) || GUI.MouseOn == steering?.GuiFrame || (steering?.GuiFrame?.IsParentOf(GUI.MouseOn) ?? false)))
{
float scrollSpeed = PlayerInput.ScrollWheelSpeed / 1000.0f;
if (Math.Abs(scrollSpeed) > 0.0001f)
@@ -459,8 +446,13 @@ namespace Barotrauma.Items.Components
zoomSlider.BarScroll += PlayerInput.ScrollWheelSpeed / 1000.0f;
zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll);
}
if (PlayerInput.KeyHit(InputType.Run))
{
SonarModeSwitch.OnClicked(SonarModeSwitch, null);
}
}
float distort = 1.0f - item.Condition / item.MaxCondition;
for (int i = sonarBlips.Count - 1; i >= 0; i--)
{
@@ -623,7 +615,6 @@ namespace Barotrauma.Items.Components
}
}
Steering steering = item.GetComponent<Steering>();
if (steering != null && steering.DockingModeEnabled && steering.ActiveDockingSource != null)
{
float dockingDist = Vector2.Distance(steering.ActiveDockingSource.Item.WorldPosition, steering.DockingTarget.Item.WorldPosition);
@@ -790,10 +781,7 @@ namespace Barotrauma.Items.Components
DisplayRadius = (rect.Width / 2.0f) * (1.0f - displayBorderSize);
displayScale = DisplayRadius / range * zoom;
if (screenBackground != null)
{
screenBackground.Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.size.X);
}
screenBackground?.Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.size.X);
if (useDirectionalPing)
{
@@ -881,10 +869,7 @@ namespace Barotrauma.Items.Components
GUI.DrawString(spriteBatch, rect.Location.ToVector2(), sonarBlips.Count.ToString(), Color.White);
}
if (screenOverlay != null)
{
screenOverlay.Draw(spriteBatch, center, 0.0f, rect.Width / screenOverlay.size.X);
}
screenOverlay?.Draw(spriteBatch, center, 0.0f, rect.Width / screenOverlay.size.X);
if (signalStrength <= 0.5f)
{
@@ -947,21 +932,25 @@ namespace Barotrauma.Items.Components
cave.StartPos.ToVector2(), transducerCenter,
displayScale, center, DisplayRadius);
}
int missionIndex = 0;
foreach (Mission mission in GameMain.GameSession.Missions)
{
if (!string.IsNullOrWhiteSpace(mission.SonarLabel))
{
int i = 0;
foreach (Vector2 sonarPosition in mission.SonarPositions)
{
DrawMarker(spriteBatch,
mission.SonarLabel,
mission.SonarIconIdentifier,
mission,
sonarPosition, transducerCenter,
"mission" + missionIndex + ":" + i,
sonarPosition, transducerCenter,
displayScale, center, DisplayRadius * 0.95f);
i++;
}
}
missionIndex++;
}
if (AllowUsingMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null)
@@ -974,7 +963,7 @@ namespace Barotrauma.Items.Components
var i = unobtainedMinerals.FirstOrDefault();
if (i == null) { continue; }
DrawMarker(spriteBatch,
i.Name, "mineral", i,
i.Name, "mineral", "mineralcluster" + i,
c.center, transducerCenter,
displayScale, center, DisplayRadius * 0.95f,
onlyShowTextOnMouseOver: true);
@@ -987,14 +976,14 @@ namespace Barotrauma.Items.Components
if (connectedSubs.Contains(sub)) { continue; }
if (sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (item.Submarine != null)
if (item.Submarine != null || Character.Controlled != null)
{
//hide enemy team
if (sub.TeamID == CharacterTeamType.Team1 && (item.Submarine.TeamID == CharacterTeamType.Team2 || Character.Controlled?.TeamID == CharacterTeamType.Team2))
if (sub.TeamID == CharacterTeamType.Team1 && (item.Submarine?.TeamID == CharacterTeamType.Team2 || Character.Controlled?.TeamID == CharacterTeamType.Team2))
{
continue;
}
else if (sub.TeamID == CharacterTeamType.Team2 && (item.Submarine.TeamID == CharacterTeamType.Team1 || Character.Controlled?.TeamID == CharacterTeamType.Team1))
else if (sub.TeamID == CharacterTeamType.Team2 && (item.Submarine?.TeamID == CharacterTeamType.Team1 || Character.Controlled?.TeamID == CharacterTeamType.Team1))
{
continue;
}
@@ -1101,19 +1090,17 @@ namespace Barotrauma.Items.Components
if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (dockingPort.Item.Submarine == null) { continue; }
if (dockingPort.Item.Submarine.Info.IsWreck) { continue; }
if (!dockingPort.Item.Submarine.ShowSonarMarker && !dockingPort.Item.Submarine.Info.IsOutpost) { continue; }
// docking ports should be shown even if defined as not, if the submarine is the same as the sonar's
if (!dockingPort.Item.Submarine.ShowSonarMarker && dockingPort.Item.Submarine != item.Submarine && !dockingPort.Item.Submarine.Info.IsOutpost) { continue; }
//don't show the docking ports of the opposing team on the sonar
if (item.Submarine != null)
if (item.Submarine != null &&
item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
dockingPort.Item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
dockingPort.Item.Submarine.Info.Type != SubmarineType.Outpost)
{
if (dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team1 && (item.Submarine.TeamID == CharacterTeamType.Team2 || Character.Controlled?.TeamID == CharacterTeamType.Team2))
{
continue;
}
else if (dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team2 && (item.Submarine.TeamID == CharacterTeamType.Team1 || Character.Controlled?.TeamID == CharacterTeamType.Team1))
{
continue;
}
// specifically checking for friendlyNPC seems more logical here
if (dockingPort.Item.Submarine.TeamID != item.Submarine.TeamID && dockingPort.Item.Submarine.TeamID != CharacterTeamType.FriendlyNPC) { continue; }
}
Vector2 offset = (dockingPort.Item.WorldPosition - transducerCenter) * scale;
@@ -1629,9 +1616,7 @@ namespace Barotrauma.Items.Components
{
if (markerDistances.TryGetValue(targetIdentifier, out CachedDistance cachedDistance))
{
if (Timing.TotalTime > cachedDistance.RecalculationTime &&
(Vector2.DistanceSquared(cachedDistance.TransducerWorldPos, transducerPosition) > 500 * 500 ||
Vector2.DistanceSquared(cachedDistance.WorldPos, worldPosition) > 500 * 500))
if (cachedDistance.ShouldUpdateDistance(transducerPosition, worldPosition))
{
markerDistances.Remove(targetIdentifier);
CalculateDistance();
@@ -1653,10 +1638,7 @@ namespace Barotrauma.Items.Components
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition));
if (!path.Unreachable)
{
var cachedDistance = new CachedDistance(transducerPosition, worldPosition, path.TotalLength)
{
RecalculationTime = Timing.TotalTime + Rand.Range(1.0f, 5.0f)
};
var cachedDistance = new CachedDistance(transducerPosition, worldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f));
markerDistances.Add(targetIdentifier, cachedDistance);
dist = path.TotalLength;
}

View File

@@ -1,12 +1,18 @@
using Barotrauma.Networking;
using Barotrauma.Particles;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Projectile : ItemComponent
{
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
bool launch = msg.ReadBoolean();
@@ -96,5 +102,30 @@ namespace Barotrauma.Items.Components
Unstick();
}
}
partial void LaunchProjSpecific(Vector2 startLocation, Vector2 endLocation)
{
Vector2 particlePos = item.WorldPosition;
float rotation = -item.body.Rotation;
if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; }
Tuple<Vector2, Vector2> tracerPoints = new Tuple<Vector2, Vector2>(startLocation, endLocation);
foreach (ParticleEmitter emitter in particleEmitters)
{
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: rotation, particleRotation: rotation, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier, tracerPoints: tracerPoints);
}
}
partial void InitProjSpecific(XElement element)
{
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
break;
}
}
}
}
}

View File

@@ -73,7 +73,7 @@ namespace Barotrauma.Items.Components
}
particleEmitter.Emit(
deltaTime, ConvertUnits.ToDisplayUnits(raystart),
item.CurrentHull, particleAngle, particleEmitter.Prefab.CopyEntityAngle ? -particleAngle : 0);
item.CurrentHull, particleAngle, particleEmitter.Prefab.Properties.CopyEntityAngle ? -particleAngle : 0);
}
}

View File

@@ -36,12 +36,30 @@ namespace Barotrauma.Items.Components
get
{
if (target == null || source == null) { return Vector2.Zero; }
Vector2 sourcePos = GetSourcePos();
return new Vector2(
Math.Abs(target.DrawPosition.X - source.DrawPosition.X),
Math.Abs(target.DrawPosition.Y - source.DrawPosition.Y)) * 1.5f;
Math.Abs(target.DrawPosition.X - sourcePos.X),
Math.Abs(target.DrawPosition.Y - sourcePos.Y)) * 1.5f;
}
}
private Vector2 GetSourcePos()
{
Vector2 sourcePos = source.WorldPosition;
if (source is Item sourceItem)
{
sourcePos = sourceItem.DrawPosition;
}
else if (source is Limb sourceLimb && sourceLimb.body != null)
{
sourcePos = sourceLimb.body.DrawPosition;
}
return sourcePos;
}
partial void InitProjSpecific(XElement element)
{
foreach (XElement subElement in element.Elements())
@@ -65,14 +83,18 @@ namespace Barotrauma.Items.Components
{
if (target == null) { return; }
Vector2 startPos = new Vector2(source.DrawPosition.X, -source.DrawPosition.Y);
var turret = source?.GetComponent<Turret>();
if (turret != null)
Vector2 startPos = GetSourcePos();
startPos.Y = -startPos.Y;
if (source is Item sourceItem)
{
startPos = new Vector2(source.WorldRect.X + turret.TransformedBarrelPos.X, -(source.WorldRect.Y - turret.TransformedBarrelPos.Y));
if (turret.BarrelSprite != null)
var turret = sourceItem?.GetComponent<Turret>();
if (turret != 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(sourceItem.WorldRect.X + turret.TransformedBarrelPos.X, -(sourceItem.WorldRect.Y - turret.TransformedBarrelPos.Y));
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;
}
}
}
Vector2 endPos = new Vector2(target.DrawPosition.X, -target.DrawPosition.Y);
@@ -80,7 +102,7 @@ namespace Barotrauma.Items.Components
if (Snapped)
{
float snapState = 1.0f - snapTimer / SnapAnimDuration;
Vector2 diff = target.DrawPosition - source.DrawPosition;
Vector2 diff = target.DrawPosition - new Vector2(startPos.X, -startPos.Y);
diff.Y = -diff.Y;
int width = (int)(SpriteWidth * snapState);

View File

@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
Wire equippedWire = null;
bool allowRewiring = GameMain.NetworkMember?.ServerSettings == null || GameMain.NetworkMember.ServerSettings.AllowRewiring || panel.AlwaysAllowRewiring;
if (allowRewiring && (!panel.Locked || Screen.Selected == GameMain.SubEditorScreen))
if (allowRewiring && (!panel.Locked && !panel.TemporarilyLocked || Screen.Selected == GameMain.SubEditorScreen))
{
//if the Character using the panel has a wire item equipped
//and the wire hasn't been connected yet, draw it on the panel
@@ -365,7 +365,7 @@ namespace Barotrauma.Items.Components
ConnectionPanel.HighlightedWire = wire;
bool allowRewiring = GameMain.NetworkMember?.ServerSettings == null || GameMain.NetworkMember.ServerSettings.AllowRewiring || panel.AlwaysAllowRewiring;
if (allowRewiring && (!wire.Locked && !panel.Locked || Screen.Selected == GameMain.SubEditorScreen))
if (allowRewiring && (!wire.Locked && !panel.Locked && !panel.TemporarilyLocked || Screen.Selected == GameMain.SubEditorScreen))
{
//start dragging the wire
if (PlayerInput.PrimaryMouseButtonHeld()) { DraggingConnected = wire; }

View File

@@ -367,9 +367,9 @@ namespace Barotrauma.Items.Components
//a wire has been selected -> check if we should start dragging one of the nodes
float nodeSelectDist = 10, sectionSelectDist = 5;
highlightedNodeIndex = null;
if (MapEntity.SelectedList.Count == 1 && MapEntity.SelectedList[0] is Item)
if (MapEntity.SelectedList.Count == 1 && MapEntity.SelectedList.FirstOrDefault() is Item selectedItem)
{
Wire selectedWire = ((Item)MapEntity.SelectedList[0]).GetComponent<Wire>();
Wire selectedWire = selectedItem.GetComponent<Wire>();
if (selectedWire != null)
{

View File

@@ -17,15 +17,6 @@ namespace Barotrauma.Items.Components
TextManager.Get("CatastrophicBleeding")
};
private static readonly string[] HealthTexts =
{
TextManager.Get("NoInjuries"),
TextManager.Get("MinorInjuries"),
TextManager.Get("Injuries"),
TextManager.Get("MajorInjuries"),
TextManager.Get("CriticalInjuries")
};
private static readonly string[] OxygenTexts =
{
TextManager.Get("OxygenNormal"),
@@ -55,6 +46,8 @@ namespace Barotrauma.Items.Components
private Character equipper;
private bool isEquippable;
public IEnumerable<Character> VisibleCharacters
{
get
@@ -64,14 +57,28 @@ namespace Barotrauma.Items.Components
}
}
public override void OnItemLoaded()
{
isEquippable = item.GetComponent<Pickable>() != null;
if (!isEquippable) { IsActive = true; }
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
if (equipper == null || equipper.Removed)
Entity refEntity = equipper;
if (isEquippable)
{
IsActive = false;
return;
if (equipper == null || equipper.Removed)
{
IsActive = false;
return;
}
}
else
{
refEntity = item;
}
if (updateTimer > 0.0f)
@@ -85,11 +92,11 @@ namespace Barotrauma.Items.Components
{
if (c == equipper || !c.Enabled || c.Removed) { continue; }
float dist = Vector2.DistanceSquared(equipper.WorldPosition, c.WorldPosition);
float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
if (dist < Range * Range)
{
Vector2 diff = c.WorldPosition - equipper.WorldPosition;
if (Submarine.CheckVisibility(equipper.SimPosition, equipper.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
Vector2 diff = c.WorldPosition - refEntity.WorldPosition;
if (Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
{
visibleCharacters.Add(c);
}
@@ -147,9 +154,13 @@ namespace Barotrauma.Items.Components
List<string> texts = new List<string>();
List<Color> textColors = new List<Color>();
texts.Add(target.Info == null ? target.DisplayName : target.Info.DisplayName);
textColors.Add(GUI.Style.TextColor);
Color nameColor = GUI.Style.TextColor;
if (Character.Controlled != null && target.TeamID != Character.Controlled.TeamID)
{
nameColor = target.TeamID == CharacterTeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
}
textColors.Add(nameColor);
if (target.IsDead)
{

View File

@@ -34,7 +34,9 @@ namespace Barotrauma.Items.Components
private RoundSound startMoveSound, endMoveSound, moveSound;
private SoundChannel moveSoundChannel;
private RoundSound chargeSound;
private SoundChannel moveSoundChannel, chargeSoundChannel;
private Vector2 oldRotation = Vector2.Zero;
private Vector2 crosshairPos, crosshairPointerPos;
@@ -43,11 +45,16 @@ namespace Barotrauma.Items.Components
private float prevAngle;
private bool flashLowPower;
private bool flashNoAmmo;
private bool flashNoAmmo, flashLoaderBroken;
private float flashTimer;
private float flashLength = 1;
private readonly float flashLength = 1;
private const float MaxCircle = 360f;
private const float HalfCircle = 180f;
private const float QuarterCircle = 90f;
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private readonly List<ParticleEmitter> particleEmitterCharges = new List<ParticleEmitter>();
[Editable, Serialize("0,0,0,0", true, description: "Optional screen tint color when the item is being operated (R,G,B,A).")]
public Color HudTint
@@ -77,6 +84,21 @@ namespace Barotrauma.Items.Components
private set;
}
[Serialize(0.0f, false, description: "The distance in which the spinning barrels rotate. Only used if spinning barrels are created.")]
public float SpinningBarrelDistance
{
get;
private set;
}
[Serialize(false, false, description: "Use firing offset for muzzleflash? This field shouldn't be needed but I'm using it for prototyping")]
public bool UseFiringOffsetForMuzzleFlash
{
get;
private set;
}
public Vector2 DrawSize
{
get
@@ -124,9 +146,15 @@ namespace Barotrauma.Items.Components
case "movesound":
moveSound = Submarine.LoadRoundSound(subElement, false);
break;
case "chargesound":
chargeSound = Submarine.LoadRoundSound(subElement, false);
break;
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
break;
case "particleemittercharge":
particleEmitterCharges.Add(new ParticleEmitter(subElement));
break;
}
}
@@ -150,7 +178,7 @@ namespace Barotrauma.Items.Components
{
recoilTimer = RetractionTime;
PlaySound(ActionType.OnUse);
Vector2 particlePos = new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y);
Vector2 particlePos = GetRelativeFiringPosition(UseFiringOffsetForMuzzleFlash);
foreach (ParticleEmitter emitter in particleEmitters)
{
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation);
@@ -215,12 +243,55 @@ namespace Barotrauma.Items.Components
}
}
float chargeRatio = currentChargeTime / MaxChargeTime;
currentBarrelSpin = (currentBarrelSpin + MaxCircle * chargeRatio * deltaTime * 3f) % MaxCircle;
switch (currentChargingState)
{
case ChargingState.WindingUp:
Vector2 particlePos = GetRelativeFiringPosition();
float sizeMultiplier = Math.Clamp(chargeRatio, 0.1f, 1f);
foreach (ParticleEmitter emitter in particleEmitterCharges)
{
// color is currently not connected to ammo type, should be updated when ammo is changed
emitter.Emit(deltaTime, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier);
}
if (chargeSoundChannel == null || !chargeSoundChannel.IsPlaying)
{
if (chargeSound != null)
{
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling);
if (chargeSoundChannel != null) chargeSoundChannel.Looping = true;
}
}
else if (chargeSoundChannel != null)
{
chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio);
}
break;
default:
if (chargeSoundChannel != null)
{
if (chargeSoundChannel.IsPlaying)
{
chargeSoundChannel.FadeOutAndDispose();
chargeSoundChannel.Looping = false;
}
else
{
chargeSoundChannel = null;
}
}
break;
}
if (moveSoundChannel != null && moveSoundChannel.IsPlaying)
{
moveSoundChannel.Gain = MathHelper.Clamp(Math.Abs(angularVelocity), 0.5f, 1.0f);
}
if (flashLowPower || flashNoAmmo)
if (flashLowPower || flashNoAmmo || flashLoaderBroken)
{
flashTimer += deltaTime;
if (flashTimer >= flashLength)
@@ -228,6 +299,7 @@ namespace Barotrauma.Items.Components
flashTimer = 0;
flashLowPower = false;
flashNoAmmo = false;
flashLoaderBroken = false;
}
}
}
@@ -289,6 +361,42 @@ namespace Barotrauma.Items.Components
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
float chargeRatio = currentChargeTime / MaxChargeTime;
foreach ((Sprite chargeSprite, Vector2 position) in chargeSprites)
{
chargeSprite?.Draw(spriteBatch,
drawPos - MathUtils.RotatePoint(new Vector2(position.X * chargeRatio, position.Y * chargeRatio) * item.Scale, rotation + MathHelper.PiOver2),
item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (chargeSprite.Depth - item.Sprite.Depth));
}
int spinningBarrelCount = spinningBarrelSprites.Count;
for (int i = 0; i < spinningBarrelCount; i++)
{
// this block is messy since I was debugging it with a bunch of values, should be cleaned up / optimized if prototype is accepted
Sprite spinningBarrel = spinningBarrelSprites[i];
float barrelCirclePosition = (MaxCircle * i / spinningBarrelCount + currentBarrelSpin) % MaxCircle;
float newDepth = item.SpriteDepth + (spinningBarrel.Depth - item.Sprite.Depth) + (barrelCirclePosition > HalfCircle ? 0.0f : 0.001f);
float barrelColorPosition = (barrelCirclePosition + QuarterCircle) % MaxCircle;
float colorOffset = Math.Abs(barrelColorPosition - HalfCircle) / HalfCircle;
Color newColorModifier = Color.Lerp(Color.Black, Color.Gray, colorOffset);
float barrelHalfCirclePosition = Math.Abs(barrelCirclePosition - HalfCircle);
float barrelPositionModifier = MathUtils.SmoothStep(barrelHalfCirclePosition / HalfCircle);
float newPositionOffset = barrelPositionModifier * SpinningBarrelDistance;
spinningBarrel.Draw(spriteBatch,
drawPos - MathUtils.RotatePoint(new Vector2(newPositionOffset, 0f) * item.Scale, rotation + MathHelper.PiOver2),
Color.Lerp(item.SpriteColor, newColorModifier, 0.8f),
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, newDepth);
}
if (!editing || GUI.DisableHUD || !item.IsSelected) { return; }
const float widgetRadius = 60.0f;
@@ -552,14 +660,20 @@ namespace Barotrauma.Items.Components
new VisualSlot(new Rectangle(invSlotPos + new Point((i % slotsPerRow) * (slotSize.X + spacing.X), (int)Math.Floor(i / (float)slotsPerRow) * (slotSize.Y + spacing.Y)), slotSize)),
availableAmmo[i], -1, true);
}
Rectangle rect = new Rectangle(invSlotPos.X, invSlotPos.Y, totalWidth, slotSize.Y);
float inflate = MathHelper.Lerp(3, 8, (float)Math.Abs(Math.Sin(flashTimer * 5)));
rect.Inflate(inflate, inflate);
Color color = GUI.Style.Red * Math.Max(0.5f, (float)Math.Sin(flashTimer * 12));
if (flashNoAmmo)
{
Rectangle rect = new Rectangle(invSlotPos.X, invSlotPos.Y, totalWidth, slotSize.Y);
float inflate = MathHelper.Lerp(3, 8, (float)Math.Abs(1 * Math.Sin(flashTimer * 5)));
rect.Inflate(inflate, inflate);
Color color = GUI.Style.Red * MathHelper.Max(0.5f, (float)Math.Sin(flashTimer * 12));
GUI.DrawRectangle(spriteBatch, rect, color, thickness: 3);
}
else if (flashLoaderBroken)
{
GUI.DrawRectangle(spriteBatch, rect, color, thickness: 3);
GUI.BrokenIcon.Draw(spriteBatch, rect.Center.ToVector2(), color, scale: rect.Height / GUI.BrokenIcon.size.Y);
GUIComponent.DrawToolTip(spriteBatch, TextManager.Get("turretloaderbroken"), new Rectangle(invSlotPos.X + totalWidth + GUI.IntScale(10), invSlotPos.Y + slotSize.Y / 2 - GUI.IntScale(9), 0, 0));
}
}
float zoom = cam == null ? 1.0f : (float)Math.Sqrt(cam.Zoom);

View File

@@ -0,0 +1,54 @@
using System;
using System.Linq;
namespace Barotrauma.Items.Components
{
partial class Wearable
{
private void GetDamageModifierText(ref string description, float damageMultiplier, string afflictionIdentifier)
{
int roundedValue = (int)Math.Round((1 - damageMultiplier) * 100);
if (roundedValue == 0) { return; }
string colorStr = XMLExtensions.ColorToString(GUI.Style.Green);
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ?? afflictionIdentifier}";
}
public override void AddTooltipInfo(ref string description)
{
if (damageModifiers.Any(d => !MathUtils.NearlyEqual(d.DamageMultiplier, 1f)) || SkillModifiers.Any())
{
description += "\n";
}
if (damageModifiers.Any())
{
foreach (DamageModifier damageModifier in damageModifiers)
{
if (MathUtils.NearlyEqual(damageModifier.DamageMultiplier, 1f))
{
continue;
}
foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionIdentifiers)
{
GetDamageModifierText(ref description, damageModifier.DamageMultiplier, afflictionIdentifier);
}
foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionTypes)
{
GetDamageModifierText(ref description, damageModifier.DamageMultiplier, afflictionIdentifier);
}
}
}
if (SkillModifiers.Any())
{
foreach (var skillModifier in SkillModifiers)
{
string colorStr = XMLExtensions.ColorToString(GUI.Style.Green);
int roundedValue = (int)Math.Round(skillModifier.Value);
if (roundedValue == 0) { continue; }
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("+0;-#")}‖color:end‖ {TextManager.Get("SkillName." + skillModifier.Key, true) ?? skillModifier.Key}";
}
}
}
}
}

View File

@@ -296,6 +296,12 @@ namespace Barotrauma
}
}
}
foreach (ItemComponent component in item.Components)
{
component.AddTooltipInfo(ref description);
}
if (item.Prefab.ShowContentsInTooltip && item.OwnInventory != null)
{
foreach (string itemName in item.OwnInventory.AllItems.Select(it => it.Name).Distinct())
@@ -320,6 +326,11 @@ namespace Barotrauma
toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
}
if (!string.IsNullOrEmpty(description)) { toolTip += '\n' + description; }
if (item.prefab.ContentPackage != GameMain.VanillaContent && item.prefab.ContentPackage != null)
{
colorStr = XMLExtensions.ColorToString(Color.MediumPurple);
toolTip += $"\n‖color:{colorStr}‖{item.prefab.ContentPackage.Name}‖color:end‖";
}
}
if (itemsInSlot.Count() > 1)
{
@@ -945,6 +956,15 @@ namespace Barotrauma
{
return CursorState.Hand;
}
var container = item?.GetComponent<ItemContainer>();
if (container == null) { continue; }
if (container.Inventory.visualSlots != null)
{
if (container.Inventory.visualSlots.Any(slot => slot.IsHighlighted))
{
return CursorState.Hand;
}
}
}
}
@@ -1120,9 +1140,10 @@ namespace Barotrauma
if (selectedSlot == null)
{
if (DraggingItemToWorld &&
Character.Controlled.FocusedItem?.OwnInventory != null &&
(Character.Controlled.FocusedItem.GetComponent<ItemContainer>()?.HasRequiredItems(Character.Controlled, addMessage: false) ?? false) &&
Character.Controlled.FocusedItem.OwnInventory.CanBePut(DraggingItems.FirstOrDefault()))
Character.Controlled.FocusedItem is { OwnInventory: { } inventory } item && item.GetComponent<ItemContainer>() is { } container &&
container.HasRequiredItems(Character.Controlled, addMessage: false) &&
container.AllowDragAndDrop &&
inventory.CanBePut(DraggingItems.FirstOrDefault()))
{
bool anySuccess = false;
foreach (Item it in DraggingItems)
@@ -1575,7 +1596,7 @@ namespace Barotrauma
}
sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale);
if (!item.AllowStealing && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
if ((!item.AllowStealing || (inventory != null && inventory.slots[slotIndex].Items.Any(it => !it.AllowStealing))) && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
{
var stealIcon = CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand];
Vector2 iconSize = new Vector2(25 * GUI.Scale);

View File

@@ -22,6 +22,19 @@ namespace Barotrauma
private readonly List<ItemComponent> activeHUDs = new List<ItemComponent>();
public GUIComponentStyle IconStyle { get; private set; } = null;
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType)
{
if (interactionType == CampaignMode.InteractionType.None)
{
IconStyle = null;
}
else
{
IconStyle = GUI.Style.GetComponentStyle($"CampaignInteractionIcon.{interactionType}");
}
}
public IEnumerable<ItemComponent> ActiveHUDs => activeHUDs;
public float LastImpactSoundTime;
@@ -252,7 +265,8 @@ namespace Barotrauma
else if (!ShowItems) { return; }
}
Color color = IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUI.Style.Orange : GetSpriteColor();
Color color = IsIncludedInSelection && editing ? GUI.Style.Blue : IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUI.Style.Orange * Math.Max(GetSpriteColor().A / (float) byte.MaxValue, 0.1f) : GetSpriteColor();
//if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f);
bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null;
@@ -305,24 +319,27 @@ namespace Barotrauma
if (prefab.ResizeHorizontal || prefab.ResizeVertical)
{
Vector2 size = new Vector2(rect.Width, rect.Height);
activeSprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + drawOffset,
size, color: color,
textureScale: Vector2.One * Scale,
depth: depth);
fadeInBrokenSprite?.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + fadeInBrokenSprite.Offset.ToVector2() * Scale, size, color: color * fadeInBrokenSpriteAlpha,
textureScale: Vector2.One * Scale,
depth: depth - 0.000001f);
foreach (var decorativeSprite in Prefab.DecorativeSprites)
if (color.A > 0)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
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.DrawTiled(spriteBatch,
new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)),
activeSprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + drawOffset,
size, color: color,
textureScale: Vector2.One * Scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
depth: depth);
fadeInBrokenSprite?.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + fadeInBrokenSprite.Offset.ToVector2() * Scale, size, color: color * fadeInBrokenSpriteAlpha,
textureScale: Vector2.One * Scale,
depth: depth - 0.000001f);
foreach (var decorativeSprite in Prefab.DecorativeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
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.DrawTiled(spriteBatch,
new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)),
size, color: color,
textureScale: Vector2.One * Scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
}
}
}
else
@@ -336,8 +353,11 @@ namespace Barotrauma
{
origin.Y = activeSprite.SourceRect.Height - origin.Y;
}
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, rotationRad, Scale, activeSprite.effects, depth);
fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, depth - 0.000001f);
if (color.A > 0)
{
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, rotationRad, Scale, activeSprite.effects, depth);
fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, depth - 0.000001f);
}
if (Infector != null && (Infector.ParentBallastFlora.HasBrokenThrough || BallastFloraBehavior.AlwaysShowBallastFloraSprite))
{
Prefab.InfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, Prefab.InfectedSprite.Origin, rotationRad, Scale, activeSprite.effects, depth - 0.001f);
@@ -365,7 +385,7 @@ namespace Barotrauma
float depthStep = 0.000001f;
if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == this)
{
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightHand);
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightArm);
if (holdLimb?.ActiveSprite != null)
{
depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() + depthStep * 2;
@@ -377,7 +397,7 @@ namespace Barotrauma
}
else if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == this)
{
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftHand);
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftArm);
if (holdLimb?.ActiveSprite != null)
{
depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() - depthStep * 2;
@@ -442,10 +462,7 @@ namespace Barotrauma
if (GameMain.DebugDraw)
{
if (body != null)
{
body.DebugDraw(spriteBatch, Color.White);
}
body?.DebugDraw(spriteBatch, Color.White);
}
if (editing && IsSelected && PlayerInput.KeyDown(Keys.Space))
@@ -463,7 +480,10 @@ namespace Barotrauma
if (IsSelected || IsHighlighted)
{
GUI.DrawRectangle(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)), new Vector2(rect.Width, rect.Height),
Vector2 drawPos = new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2));
Vector2 drawSize = new Vector2(MathF.Ceiling(rect.Width + Math.Abs(drawPos.X - (int)drawPos.X)), MathF.Ceiling(rect.Height + Math.Abs(drawPos.Y - (int)drawPos.Y)));
drawPos = new Vector2(MathF.Floor(drawPos.X), MathF.Floor(drawPos.Y));
GUI.DrawRectangle(spriteBatch, drawPos, drawSize,
Color.White, false, 0, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
foreach (Rectangle t in Prefab.Triggers)
@@ -675,7 +695,11 @@ namespace Barotrauma
ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) =>
{
FlipX(relativeToSub: false);
foreach (MapEntity me in SelectedList)
{
me.FlipX(relativeToSub: false);
}
if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); }
return true;
}
};
@@ -684,7 +708,11 @@ namespace Barotrauma
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
{
FlipY(relativeToSub: false);
foreach (MapEntity me in SelectedList)
{
me.FlipY(relativeToSub: false);
}
if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); }
return true;
}
};
@@ -694,7 +722,7 @@ namespace Barotrauma
reloadTextureButton.OnClicked += (button, data) =>
{
Sprite.ReloadXML();
Sprite.ReloadTexture();
Sprite.ReloadTexture(updateAllSprites: true);
return true;
};
}
@@ -702,7 +730,12 @@ namespace Barotrauma
{
OnClicked = (button, data) =>
{
Reset();
foreach (MapEntity me in SelectedList)
{
(me as Item)?.Reset();
(me as Structure)?.Reset();
}
if (!SelectedList.Contains(this)) { Reset(); }
CreateEditingHUD();
return true;
}
@@ -734,7 +767,11 @@ namespace Barotrauma
if (inGame)
{
if (!ic.AllowInGameEditing) { continue; }
if (SerializableProperty.GetProperties<InGameEditable>(ic).Count == 0) { continue; }
if (SerializableProperty.GetProperties<InGameEditable>(ic).Count == 0 &&
!SerializableProperty.GetProperties<ConditionallyEditable>(ic).Any(p => p.GetAttribute<ConditionallyEditable>().IsEditable(ic)))
{
continue;
}
}
else
{
@@ -1093,29 +1130,39 @@ namespace Barotrauma
nameText += $" ({idName})";
}
}
texts.Add(new ColoredText(nameText, GUI.Style.TextColor, false, false));
texts.Add(new ColoredText(nameText, GUI.Style.TextColor, false, false));
foreach (ItemComponent ic in components)
bool noComponentText = true;
if (CampaignInteractionType != CampaignMode.InteractionType.None)
{
if (string.IsNullOrEmpty(ic.DisplayMsg)) { continue; }
if (!ic.CanBePicked && !ic.CanBeSelected) { continue; }
if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; }
Color color = Color.Gray;
if (ic.HasRequiredItems(character, false))
{
if (ic is Repairable)
{
if (!IsFullCondition) { color = Color.Cyan; }
}
else
{
color = Color.Cyan;
}
}
texts.Add(new ColoredText(ic.DisplayMsg, color, false, false));
texts.Add(new ColoredText(TextManager.GetWithVariable($"CampaignInteraction.{CampaignInteractionType}", "[key]", GameMain.Config.KeyBindText(InputType.Use)), Color.Cyan, false, false));
}
if ((PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift)) && CrewManager.DoesItemHaveContextualOrders(this))
else
{
foreach (ItemComponent ic in components)
{
if (string.IsNullOrEmpty(ic.DisplayMsg)) { continue; }
if (!ic.CanBePicked && !ic.CanBeSelected) { continue; }
if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; }
Color color = Color.Gray;
if (ic.HasRequiredItems(character, false))
{
if (ic is Repairable)
{
if (!IsFullCondition) { color = Color.Cyan; }
}
else
{
color = Color.Cyan;
}
}
texts.Add(new ColoredText(ic.DisplayMsg, color, false, false));
noComponentText = false;
}
}
if (PlayerInput.IsShiftDown() && CrewManager.DoesItemHaveContextualOrders(this))
{
texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")), Color.Cyan, false, false));
}
@@ -1213,6 +1260,9 @@ namespace Barotrauma
}
SetActiveSprite();
break;
case NetEntityEvent.Type.AssignCampaignInteraction:
CampaignInteractionType = (CampaignMode.InteractionType)msg.ReadByte();
break;
case NetEntityEvent.Type.ApplyStatusEffect:
{
ActionType actionType = (ActionType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1);
@@ -1327,6 +1377,11 @@ namespace Barotrauma
isActive = true;
if (positionBuffer.Count > 0)
{
transformDirty = true;
}
body.CorrectPosition(positionBuffer, out Vector2 newPosition, out Vector2 newVelocity, out float newRotation, out float newAngularVelocity);
body.LinearVelocity = newVelocity;
body.AngularVelocity = newAngularVelocity;
@@ -1534,11 +1589,20 @@ namespace Barotrauma
}
}
var item = new Item(itemPrefab, pos, sub, id: itemId)
Item item = null;
try
{
SpawnedInOutpost = spawnedInOutpost,
AllowStealing = allowStealing
};
item = new Item(itemPrefab, pos, sub, id: itemId)
{
SpawnedInOutpost = spawnedInOutpost,
AllowStealing = allowStealing
};
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to spawn item {itemPrefab.Name}", e);
throw;
}
if (item.body != null)
{

View File

@@ -63,9 +63,12 @@ namespace Barotrauma
public Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
public Sprite InventoryIcon;
public Sprite MinimapIcon;
public Sprite UpgradePreviewSprite;
public Sprite InfectedSprite;
public Sprite DamagedInfectedSprite;
public float UpgradePreviewScale = 1.0f;
//only used to display correct color in the sub editor, item instances have their own property that can be edited on a per-item basis
[Serialize("1.0,1.0,1.0,1.0", false)]
public Color InventoryIconColor

View File

@@ -71,15 +71,15 @@ namespace Barotrauma
if (sparks)
{
GameMain.ParticleManager.CreateParticle("spark", worldPosition,
Rand.Vector(Rand.Range(500.0f, 800.0f)), 0.0f, hull);
Rand.Vector(Rand.Range(1200.0f, 2400.0f)), 0.0f, hull);
}
}
if (flash)
{
float displayRange = flashRange.HasValue ? flashRange.Value : Attack.Range;
float displayRange = flashRange ?? Attack.Range;
if (displayRange < 0.1f) { return; }
var light = new LightSource(worldPosition, displayRange, Color.LightYellow, null);
var light = new LightSource(worldPosition, displayRange, flashColor, null);
CoroutineManager.StartCoroutine(DimLight(light));
}
}

View File

@@ -169,40 +169,43 @@ namespace Barotrauma
}
}
if (EditWater)
if (!IdFreed)
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (Submarine.RectContains(WorldRect, position))
if (EditWater)
{
if (PlayerInput.PrimaryMouseButtonHeld())
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (Submarine.RectContains(WorldRect, position))
{
WaterVolume += 1500.0f;
networkUpdatePending = true;
serverUpdateDelay = 0.5f;
if (PlayerInput.PrimaryMouseButtonHeld())
{
WaterVolume += 1500.0f;
networkUpdatePending = true;
serverUpdateDelay = 0.5f;
}
else if (PlayerInput.SecondaryMouseButtonHeld())
{
WaterVolume -= 1500.0f;
networkUpdatePending = true;
serverUpdateDelay = 0.5f;
}
}
else if (PlayerInput.SecondaryMouseButtonHeld())
}
else if (EditFire)
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (Submarine.RectContains(WorldRect, position))
{
WaterVolume -= 1500.0f;
networkUpdatePending = true;
serverUpdateDelay = 0.5f;
if (PlayerInput.PrimaryMouseButtonClicked())
{
new FireSource(position, this, isNetworkMessage: true);
networkUpdatePending = true;
serverUpdateDelay = 0.5f;
}
}
}
}
else if (EditFire)
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (Submarine.RectContains(WorldRect, position))
{
if (PlayerInput.PrimaryMouseButtonClicked())
{
new FireSource(position, this, isNetworkMessage: true);
networkUpdatePending = true;
serverUpdateDelay = 0.5f;
}
}
}
if (waterVolume < 1.0f) return;
if (waterVolume < 1.0f) { return; }
for (int i = 1; i < waveY.Length - 1; i++)
{
float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i]));
@@ -629,9 +632,9 @@ namespace Barotrauma
PowerConsumptionTimer = message.ReadSingle()
};
}
else if (BallastFlora != null)
else
{
BallastFlora.ClientRead(message, header);
BallastFlora?.ClientRead(message, header);
}
return;
}

View File

@@ -210,7 +210,7 @@ namespace Barotrauma
if (ParticleEmitterTriggers[i] != null && !ParticleEmitterTriggers[i].IsTriggered) { continue; }
Vector2 emitterPos = LocalToWorld(Prefab.EmitterPositions[i]);
ParticleEmitters[i].Emit(deltaTime, emitterPos, hullGuess: null,
angle: ParticleEmitters[i].Prefab.CopyEntityAngle ? -CurrentRotation + MathHelper.PiOver2 : 0.0f);
angle: ParticleEmitters[i].Prefab.Properties.CopyEntityAngle ? -CurrentRotation + MathHelper.Pi : 0.0f);
}
}
@@ -293,6 +293,12 @@ namespace Barotrauma
public void ClientRead(IReadMessage msg)
{
if (Triggers == null) { return; }
if (Prefab.TakeLevelWallDamage)
{
float newHealth = msg.ReadRangedSingle(0.0f, Prefab.Health, 8);
AddDamage(Health - newHealth, 1.0f, null, isNetworkEvent: true);
}
for (int i = 0; i < Triggers.Count; i++)
{
if (!Triggers[i].UseNetworkSyncing) { continue; }

View File

@@ -20,6 +20,8 @@ namespace Barotrauma
const int MaxVisibleObjects = 500;
private Rectangle currentGridIndices;
public bool ForceRefreshVisibleObjects;
partial void UpdateProjSpecific(float deltaTime)
{
@@ -60,6 +62,8 @@ namespace Barotrauma
if (objectGrid[x, y] == null) { continue; }
foreach (LevelObject obj in objectGrid[x, y])
{
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
if (zoom < 0.05f)
{
//hide if the sprite is very small when zoomed this far out
@@ -154,9 +158,10 @@ namespace Barotrauma
indices.Height = Math.Min(indices.Height, objectGrid.GetLength(1) - 1);
float z = 0.0f;
if (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime)
if (ForceRefreshVisibleObjects || (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime))
{
RefreshVisibleObjects(indices, cam.Zoom);
ForceRefreshVisibleObjects = false;
if (cam.Zoom < 0.1f)
{
//when zoomed very far out, refresh a little less often

View File

@@ -361,6 +361,7 @@ namespace Barotrauma.Lights
void DrawHalo(Character character)
{
if (character == null || character.Removed) { return; }
Vector2 haloDrawPos = character.DrawPosition;
haloDrawPos.Y = -haloDrawPos.Y;
@@ -404,7 +405,7 @@ namespace Barotrauma.Lights
}
foreach (Item item in Item.ItemList)
{
if (item.IsHighlighted && !highlightedEntities.Contains(item))
if ((item.IsHighlighted || item.IconStyle != null) && !highlightedEntities.Contains(item))
{
highlightedEntities.Add(item);
}
@@ -425,7 +426,14 @@ namespace Barotrauma.Lights
{
if (highlighted is Item item)
{
item.Draw(spriteBatch, false, true);
if (item.IconStyle != null && (item != Character.Controlled.FocusedItem || Character.Controlled.FocusedItem == null))
{
//wait until next pass
}
else
{
item.Draw(spriteBatch, false, true);
}
}
else if (highlighted is Character character)
{
@@ -434,6 +442,22 @@ namespace Barotrauma.Lights
}
spriteBatch.End();
//draw items with iconstyles in the style's color
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
foreach (Entity highlighted in highlightedEntities)
{
if (highlighted is Item item)
{
if (item.IconStyle != null && (item != Character.Controlled.FocusedItem || Character.Controlled.FocusedItem == null))
{
SolidColorEffect.Parameters["color"].SetValue(item.IconStyle.Color.ToVector4());
SolidColorEffect.CurrentTechnique.Passes[0].Apply();
item.Draw(spriteBatch, false, true);
}
}
}
spriteBatch.End();
//draw characters in black with a bit of blur, leaving the white edges visible
float phase = (float)(Math.Sin(Timing.TotalTime * 3.0f) + 1.0f) / 2.0f; //phase oscillates between 0 and 1
Vector4 overlayColor = Color.Black.ToVector4() * MathHelper.Lerp(0.5f, 0.9f, phase);

View File

@@ -607,15 +607,18 @@ namespace Barotrauma
tooltip = (new Rectangle(typeChangeIconPos.ToPoint(), new Point(30)), location.LastTypeChangeMessage);
}
}
if (location != CurrentLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location)) && generationParams.MissionIcon != null)
if (location != CurrentLocation && generationParams.MissionIcon != null)
{
Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom;
float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width;
generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom);
if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos))
if ((CurrentLocation == currentDisplayLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location))) || location.AvailableMissions.Any(m => m.Prefab.Type == MissionType.GoTo))
{
var availableMissions = CurrentLocation.AvailableMissions.Where(m => m.Locations.Contains(location));
tooltip = (new Rectangle(missionIconPos.ToPoint(), new Point(30)), TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name)));
Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom;
float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width;
generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom);
if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos))
{
var availableMissions = CurrentLocation.AvailableMissions.Where(m => m.Locations.Contains(location)).Concat(location.AvailableMissions.Where(m => m.Prefab.Type == MissionType.GoTo)).Distinct();
tooltip = (new Rectangle(missionIconPos.ToPoint(), new Point(30)), TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name)));
}
}
}

View File

@@ -29,20 +29,14 @@ namespace Barotrauma
public static bool SelectionChanged;
//which entities have been selected for editing
private static List<MapEntity> selectedList = new List<MapEntity>();
public static List<MapEntity> SelectedList
{
get
{
return selectedList;
}
}
private static List<MapEntity> copiedList = new List<MapEntity>();
public static HashSet<MapEntity> SelectedList { get; private set; } = new HashSet<MapEntity>();
public static List<MapEntity> CopiedList = new List<MapEntity>();
private static List<MapEntity> highlightedList = new List<MapEntity>();
// Test feature. Not yet saved.
public static Dictionary<MapEntity, List<MapEntity>> SelectionGroups { get; private set; } = new Dictionary<MapEntity, List<MapEntity>>();
public static Dictionary<MapEntity, HashSet<MapEntity>> SelectionGroups { get; private set; } = new Dictionary<MapEntity, HashSet<MapEntity>>();
private static float highlightTimer;
@@ -78,25 +72,13 @@ namespace Barotrauma
}
}
public virtual bool SelectableInEditor
{
get { return true; }
}
public virtual bool SelectableInEditor => true;
public static bool SelectedAny
{
get { return selectedList.Count > 0; }
}
public static bool SelectedAny => SelectedList.Count > 0;
public static IEnumerable<MapEntity> CopiedList
{
get { return copiedList; }
}
public bool IsSelected => SelectedList.Contains(this);
public bool IsSelected
{
get { return selectedList.Contains(this); }
}
public bool IsIncludedInSelection { get; set; }
public virtual bool IsVisible(Rectangle worldView)
{
@@ -129,7 +111,10 @@ namespace Barotrauma
{
if (resizing)
{
if (selectedList.Count == 0) resizing = false;
if (!SelectedAny)
{
resizing = false;
}
return;
}
@@ -157,19 +142,19 @@ namespace Barotrauma
if (MapEntityPrefab.Selected != null)
{
selectionPos = Vector2.Zero;
selectedList.Clear();
SelectedList.Clear();
return;
}
if (GUI.KeyboardDispatcher.Subscriber == null)
{
if (PlayerInput.KeyHit(Keys.Delete))
{
if (selectedList.Any())
if (SelectedAny)
{
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(selectedList, true));
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(SelectedList), true));
}
selectedList.ForEach(e => { if (!e.Removed) { e.Remove(); } });
selectedList.Clear();
SelectedList.ForEach(e => { if (!e.Removed) { e.Remove(); } });
SelectedList.Clear();
}
if (PlayerInput.IsCtrlDown())
@@ -178,7 +163,7 @@ namespace Barotrauma
if (PlayerInput.KeyHit(Keys.D))
{
bool terminate = false;
foreach (MapEntity entity in selectedList)
foreach (MapEntity entity in SelectedList)
{
if (entity is Item item && item.GetComponent<Planter>() is { } planter)
{
@@ -201,11 +186,11 @@ namespace Barotrauma
#endif
if (PlayerInput.KeyHit(Keys.C))
{
Copy(selectedList);
Copy(SelectedList.ToList());
}
else if (PlayerInput.KeyHit(Keys.X))
{
Cut(selectedList);
Cut(SelectedList.ToList());
}
else if (PlayerInput.KeyHit(Keys.V))
{
@@ -213,21 +198,21 @@ namespace Barotrauma
}
else if (PlayerInput.KeyHit(Keys.G))
{
if (selectedList.Any())
if (SelectedList.Any())
{
if (SelectionGroups.ContainsKey(selectedList.Last()))
if (SelectionGroups.ContainsKey(SelectedList.Last()))
{
// Ungroup all selected
selectedList.ForEach(e => SelectionGroups.Remove(e));
SelectedList.ForEach(e => SelectionGroups.Remove(e));
}
else
{
foreach (var entity in selectedList)
foreach (var entity in SelectedList)
{
// Remove the old group, if any
SelectionGroups.Remove(entity);
// Create a group that can be accessed with any member
SelectionGroups.Add(entity, selectedList);
SelectionGroups.Add(entity, SelectedList);
}
}
}
@@ -277,7 +262,7 @@ namespace Barotrauma
Vector2 nudge = GetNudgeAmount();
if (nudge != Vector2.Zero)
{
foreach (MapEntity entityToNudge in selectedList) { entityToNudge.Move(nudge); }
foreach (MapEntity entityToNudge in SelectedList) { entityToNudge.Move(nudge); }
}
}
else
@@ -290,7 +275,7 @@ namespace Barotrauma
//started moving selected entities
if (startMovingPos != Vector2.Zero)
{
Item targetContainer = GetPotentialContainer(position, selectedList);
Item targetContainer = GetPotentialContainer(position, SelectedList);
if (targetContainer != null) { targetContainer.IsHighlighted = true; }
@@ -313,16 +298,16 @@ namespace Barotrauma
//clone
if (PlayerInput.IsCtrlDown())
{
var clones = Clone(selectedList).Where(c => c != null).ToList();
selectedList = clones;
selectedList.ForEach(c => c.Move(moveAmount));
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false));
HashSet<MapEntity> clones = Clone(SelectedList.ToList()).Where(c => c != null).ToHashSet();
SelectedList = clones;
SelectedList.ForEach(c => c.Move(moveAmount));
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(clones), false));
}
else // move
{
var oldRects = selectedList.Select(e => e.Rect).ToList();
var oldRects = SelectedList.Select(e => e.Rect).ToList();
List<MapEntity> deposited = new List<MapEntity>();
foreach (MapEntity e in selectedList)
foreach (MapEntity e in SelectedList)
{
e.Move(moveAmount);
@@ -340,14 +325,14 @@ namespace Barotrauma
}
}
SubEditorScreen.StoreCommand(new TransformCommand(new List<MapEntity>(selectedList),selectedList.Select(entity => entity.Rect).ToList(), oldRects, false));
SubEditorScreen.StoreCommand(new TransformCommand(new List<MapEntity>(SelectedList),SelectedList.Select(entity => entity.Rect).ToList(), oldRects, false));
if (deposited.Any() && deposited.Any(entity => entity is Item))
{
var depositedItems = deposited.Where(entity => entity is Item).Cast<Item>().ToList();
SubEditorScreen.StoreCommand(new InventoryPlaceCommand(targetContainer.OwnInventory, depositedItems, false));
}
deposited.ForEach(entity => { selectedList.Remove(entity); });
deposited.ForEach(entity => { SelectedList.Remove(entity); });
}
}
startMovingPos = Vector2.Zero;
@@ -360,7 +345,12 @@ namespace Barotrauma
selectionSize.X = position.X - selectionPos.X;
selectionSize.Y = selectionPos.Y - position.Y;
List<MapEntity> newSelection = new List<MapEntity>();// FindSelectedEntities(selectionPos, selectionSize);
foreach (MapEntity entity in mapEntityList)
{
entity.IsIncludedInSelection = false;
}
HashSet<MapEntity> newSelection = new HashSet<MapEntity>();// FindSelectedEntities(selectionPos, selectionSize);
if (Math.Abs(selectionSize.X) > Submarine.GridSize.X || Math.Abs(selectionSize.Y) > Submarine.GridSize.Y)
{
newSelection = FindSelectedEntities(selectionPos, selectionSize);
@@ -369,13 +359,22 @@ namespace Barotrauma
{
if (highLightedEntity != null)
{
if (SelectionGroups.TryGetValue(highLightedEntity, out List<MapEntity> group))
if (SelectionGroups.TryGetValue(highLightedEntity, out HashSet<MapEntity> group))
{
newSelection.AddRange(group);
foreach (MapEntity entity in group.Where(e => !newSelection.Contains(e)))
{
newSelection.Add(entity);
}
foreach (MapEntity entity in group)
{
entity.IsIncludedInSelection = true;
}
}
else
{
newSelection.Add(highLightedEntity);
highLightedEntity.IsIncludedInSelection = true;
}
}
}
@@ -386,7 +385,7 @@ namespace Barotrauma
{
foreach (MapEntity e in newSelection)
{
if (selectedList.Contains(e))
if (SelectedList.Contains(e))
{
RemoveSelection(e);
}
@@ -398,7 +397,7 @@ namespace Barotrauma
}
else
{
selectedList = new List<MapEntity>(newSelection);
SelectedList = new HashSet<MapEntity>(newSelection);
//selectedList.Clear();
//newSelection.ForEach(e => AddSelection(e));
foreach (var entity in newSelection)
@@ -407,23 +406,23 @@ namespace Barotrauma
onGapFound: (door, gap) =>
{
door.RefreshLinkedGap();
if (!selectedList.Contains(gap))
if (!SelectedList.Contains(gap))
{
selectedList.Add(gap);
SelectedList.Add(gap);
}
},
onDoorFound: (door, gap) =>
{
if (!selectedList.Contains(door.Item))
if (!SelectedList.Contains(door.Item))
{
selectedList.Add(door.Item);
SelectedList.Add(door.Item);
}
});
}
}
//select wire if both items it's connected to are selected
var selectedItems = selectedList.Where(e => e is Item).Cast<Item>().ToList();
var selectedItems = SelectedList.Where(e => e is Item).Cast<Item>().ToList();
foreach (Item item in selectedItems)
{
if (item.Connections == null) continue;
@@ -431,11 +430,11 @@ namespace Barotrauma
{
foreach (Wire w in c.Wires)
{
if (w == null || selectedList.Contains(w.Item)) continue;
if (w == null || SelectedList.Contains(w.Item)) continue;
if (w.OtherConnection(c) != null && selectedList.Contains(w.OtherConnection(c).Item))
if (w.OtherConnection(c) != null && SelectedList.Contains(w.OtherConnection(c).Item))
{
selectedList.Add(w.Item);
SelectedList.Add(w.Item);
}
}
}
@@ -443,6 +442,10 @@ namespace Barotrauma
selectionPos = Vector2.Zero;
selectionSize = Vector2.Zero;
foreach (MapEntity entity in mapEntityList)
{
entity.IsIncludedInSelection = false;
}
}
}
//default, not doing anything specific yet
@@ -455,7 +458,7 @@ namespace Barotrauma
(highlightedListBox == null || (GUI.MouseOn != highlightedListBox && !highlightedListBox.IsParentOf(GUI.MouseOn))))
{
//if clicking a selected entity, start moving it
foreach (MapEntity e in selectedList)
foreach (MapEntity e in SelectedList)
{
if (e.IsMouseOn(position)) startMovingPos = position;
}
@@ -503,7 +506,7 @@ namespace Barotrauma
return ReplacedBy?.GetReplacementOrThis() ?? this;
}
public static Item GetPotentialContainer(Vector2 position, List<MapEntity> entities = null)
public static Item GetPotentialContainer(Vector2 position, HashSet<MapEntity> entities = null)
{
Item targetContainer = null;
bool isShiftDown = PlayerInput.IsShiftDown();
@@ -638,7 +641,7 @@ namespace Barotrauma
if (PlayerInput.IsCtrlDown() && !wiringMode)
{
if (selectedList.Contains(entity))
if (SelectedList.Contains(entity))
{
RemoveSelection(entity);
}
@@ -657,56 +660,60 @@ namespace Barotrauma
public static void AddSelection(MapEntity entity)
{
if (selectedList.Contains(entity)) { return; }
selectedList.Add(entity);
if (SelectedList.Contains(entity)) { return; }
SelectedList.Add(entity);
HandleDoorGapLinks(entity,
onGapFound: (door, gap) =>
{
door.RefreshLinkedGap();
if (!selectedList.Contains(gap))
if (!SelectedList.Contains(gap))
{
selectedList.Add(gap);
SelectedList.Add(gap);
}
},
onDoorFound: (door, gap) =>
{
if (!selectedList.Contains(door.Item))
if (!SelectedList.Contains(door.Item))
{
selectedList.Add(door.Item);
SelectedList.Add(door.Item);
}
});
}
private static void HandleDoorGapLinks(MapEntity entity, Action<Door, Gap> onGapFound, Action<Door, Gap> onDoorFound)
{
if (entity is Item i)
switch (entity)
{
var door = i.GetComponent<Door>();
if (door != null)
case Item i:
{
var gap = door.LinkedGap;
var door = i.GetComponent<Door>();
var gap = door?.LinkedGap;
if (gap != null)
{
onGapFound(door, gap);
}
break;
}
}
else if (entity is Gap gap)
{
var door = gap.ConnectedDoor;
if (door != null)
case Gap gap:
{
onDoorFound(door, gap);
var door = gap.ConnectedDoor;
if (door != null)
{
onDoorFound(door, gap);
}
break;
}
}
}
public static void RemoveSelection(MapEntity entity)
{
selectedList.Remove(entity);
SelectedList.Remove(entity);
HandleDoorGapLinks(entity,
onGapFound: (door, gap) => selectedList.Remove(gap),
onDoorFound: (door, gap) => selectedList.Remove(door.Item));
onGapFound: (door, gap) => SelectedList.Remove(gap),
onDoorFound: (door, gap) => SelectedList.Remove(door.Item));
}
static partial void UpdateAllProjSpecific(float deltaTime)
@@ -751,7 +758,7 @@ namespace Barotrauma
//started moving the selected entities
if (Math.Abs(moveAmount.X) >= Submarine.GridSize.X || Math.Abs(moveAmount.Y) >= Submarine.GridSize.Y || isShiftDown)
{
foreach (MapEntity e in selectedList)
foreach (MapEntity e in SelectedList)
{
SpriteEffects spriteEffects = SpriteEffects.None;
switch (e)
@@ -800,7 +807,32 @@ namespace Barotrauma
}
if (selectionPos != null && selectionPos != Vector2.Zero)
{
GUI.DrawRectangle(spriteBatch, new Vector2(selectionPos.X, -selectionPos.Y), selectionSize, Color.DarkRed, false, 0, 2f / GameScreen.Selected.Cam.Zoom);
var (sizeX, sizeY) = selectionSize;
var (posX, posY) = selectionPos;
posY = -posY;
Vector2[] corners =
{
new Vector2(posX, posY),
new Vector2(posX + sizeX, posY),
new Vector2(posX + sizeX, posY + sizeY),
new Vector2(posX, posY + sizeY)
};
Color selectionColor = GUI.Style.Blue;
float thickness = Math.Max(2f, 2f / Screen.Selected.Cam.Zoom);
GUI.DrawFilledRectangle(spriteBatch, corners[0], selectionSize, selectionColor * 0.1f);
Vector2 offset = new Vector2(0f, thickness / 2f);
if (sizeY < 0) { offset.Y = -offset.Y; }
spriteBatch.DrawLine(corners[0], corners[1], selectionColor, thickness);
spriteBatch.DrawLine(corners[1] - offset, corners[2] + offset, selectionColor, thickness);
spriteBatch.DrawLine(corners[2], corners[3], selectionColor, thickness);
spriteBatch.DrawLine(corners[3] + offset, corners[0] - offset, selectionColor, thickness);
}
}
@@ -824,8 +856,8 @@ namespace Barotrauma
}
}
FilteredSelectedList.Clear();
if (selectedList.Count == 0) return;
foreach (var e in selectedList)
if (SelectedList.Count == 0) return;
foreach (var e in SelectedList)
{
if (e is Gap gap && gap.ConnectedDoor != null) { continue; }
FilteredSelectedList.Add(e);
@@ -844,15 +876,19 @@ namespace Barotrauma
{
if (PlayerInput.KeyHit(Keys.N))
{
float minX = selectedList[0].WorldRect.X, maxX = selectedList[0].WorldRect.Right;
for (int i = 0; i < selectedList.Count; i++)
MapEntity firstSelected = SelectedList.First();
float minX = firstSelected.WorldRect.X,
maxX = firstSelected.WorldRect.Right;
foreach (MapEntity entity in SelectedList)
{
minX = Math.Min(minX, selectedList[i].WorldRect.X);
maxX = Math.Max(maxX, selectedList[i].WorldRect.Right);
minX = Math.Min(minX, entity.WorldRect.X);
maxX = Math.Max(maxX, entity.WorldRect.Right);
}
float centerX = (minX + maxX) / 2.0f;
foreach (MapEntity me in selectedList)
foreach (MapEntity me in SelectedList)
{
me.FlipX(false);
me.Move(new Vector2((centerX - me.WorldPosition.X) * 2.0f, 0.0f));
@@ -860,15 +896,20 @@ namespace Barotrauma
}
else if (PlayerInput.KeyHit(Keys.M))
{
float minY = selectedList[0].WorldRect.Y - selectedList[0].WorldRect.Height, maxY = selectedList[0].WorldRect.Y;
for (int i = 0; i < selectedList.Count; i++)
MapEntity firstSelected = SelectedList.First();
float minY = firstSelected.WorldRect.Y - firstSelected.WorldRect.Height,
maxY = firstSelected.WorldRect.Y;
foreach (MapEntity entity in SelectedList)
{
minY = Math.Min(minY, selectedList[i].WorldRect.Y - selectedList[i].WorldRect.Height);
maxY = Math.Max(maxY, selectedList[i].WorldRect.Y);
minY = Math.Min(minY, entity.WorldRect.Y - entity.WorldRect.Height);
maxY = Math.Max(maxY, entity.WorldRect.Y);
}
float centerY = (minY + maxY) / 2.0f;
foreach (MapEntity me in selectedList)
foreach (MapEntity me in SelectedList)
{
me.FlipY(false);
me.Move(new Vector2(0.0f, (centerY - me.WorldPosition.Y) * 2.0f));
@@ -879,19 +920,20 @@ namespace Barotrauma
public static void DrawEditor(SpriteBatch spriteBatch, Camera cam)
{
if (selectedList.Count == 1)
if (SelectedList.Count == 1)
{
selectedList[0].DrawEditing(spriteBatch, cam);
if (selectedList[0].ResizeHorizontal || selectedList[0].ResizeVertical)
MapEntity firstSelected = SelectedList.First();
firstSelected.DrawEditing(spriteBatch, cam);
if (firstSelected.ResizeHorizontal || firstSelected.ResizeVertical)
{
selectedList[0].DrawResizing(spriteBatch, cam);
firstSelected.DrawResizing(spriteBatch, cam);
}
}
}
public static void DeselectAll()
{
selectedList.Clear();
SelectedList.Clear();
}
public static void SelectEntity(MapEntity entity)
@@ -926,10 +968,10 @@ namespace Barotrauma
public static void Paste(Vector2 position)
{
if (copiedList.Count == 0) { return; }
if (CopiedList.Count == 0) { return; }
List<MapEntity> prevEntities = new List<MapEntity>(mapEntityList);
Clone(copiedList);
Clone(CopiedList);
var clones = mapEntityList.Except(prevEntities).ToList();
var nonWireClones = clones.Where(c => !(c is Item item) || item.GetComponent<Wire>() == null);
@@ -941,8 +983,8 @@ namespace Barotrauma
Vector2 moveAmount = Submarine.VectorToWorldGrid(position - center);
selectedList = new List<MapEntity>(clones);
foreach (MapEntity clone in selectedList)
SelectedList = new HashSet<MapEntity>(clones);
foreach (MapEntity clone in SelectedList)
{
clone.Move(moveAmount);
clone.Submarine = Submarine.MainSub;
@@ -958,7 +1000,7 @@ namespace Barotrauma
{
List<MapEntity> prevEntities = new List<MapEntity>(mapEntityList);
copiedList = Clone(entities);
CopiedList = Clone(entities);
//find all new entities created during cloning
var newEntities = mapEntityList.Except(prevEntities).ToList();
@@ -1131,9 +1173,9 @@ namespace Barotrauma
/// <summary>
/// Find entities whose rect intersects with the "selection rect"
/// </summary>
public static List<MapEntity> FindSelectedEntities(Vector2 pos, Vector2 size)
public static HashSet<MapEntity> FindSelectedEntities(Vector2 pos, Vector2 size)
{
List<MapEntity> foundEntities = new List<MapEntity>();
HashSet<MapEntity> foundEntities = new HashSet<MapEntity>();
Rectangle selectionRect = Submarine.AbsRect(pos, size);
@@ -1141,7 +1183,11 @@ namespace Barotrauma
{
if (!e.SelectableInEditor) continue;
if (Submarine.RectsOverlap(selectionRect, e.rect)) foundEntities.Add(e);
if (Submarine.RectsOverlap(selectionRect, e.rect))
{
foundEntities.Add(e);
e.IsIncludedInSelection = true;
}
}
return foundEntities;

View File

@@ -127,7 +127,11 @@ namespace Barotrauma
ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) =>
{
FlipX(relativeToSub: false);
foreach (MapEntity me in SelectedList)
{
me.FlipX(relativeToSub: false);
}
if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); }
return true;
}
};
@@ -136,7 +140,11 @@ namespace Barotrauma
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
{
FlipY(relativeToSub: false);
foreach (MapEntity me in SelectedList)
{
me.FlipY(relativeToSub: false);
}
if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); }
return true;
}
};
@@ -145,7 +153,7 @@ namespace Barotrauma
OnClicked = (button, data) =>
{
Sprite.ReloadXML();
Sprite.ReloadTexture();
Sprite.ReloadTexture(updateAllSprites: true);
return true;
}
};
@@ -153,7 +161,12 @@ namespace Barotrauma
{
OnClicked = (button, data) =>
{
Reset();
foreach (MapEntity me in SelectedList)
{
(me as Item)?.Reset();
(me as Structure)?.Reset();
}
if (!SelectedList.Contains(this)) { Reset(); }
CreateEditingHUD();
return true;
}
@@ -247,7 +260,7 @@ namespace Barotrauma
}
else if (HiddenInGame) { return; }
Color color = IsHighlighted ? GUI.Style.Orange : spriteColor;
Color color = IsIncludedInSelection && editing ? GUI.Style.Blue : IsHighlighted ? GUI.Style.Orange * Math.Max(spriteColor.A / (float) byte.MaxValue, 0.1f) : spriteColor;
if (IsSelected && editing)
{

View File

@@ -114,7 +114,6 @@ namespace Barotrauma
if (realWorldDimensions != Vector2.Zero)
{
string dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", new string[2] { "[width]", "[height]" }, new string[2] { ((int)realWorldDimensions.X).ToString(), ((int)realWorldDimensions.Y).ToString() });
var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
@@ -124,6 +123,15 @@ namespace Barotrauma
dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height);
}
string cargoCapacityStr = CargoCapacity < 0 ? TextManager.Get("unknown") : TextManager.GetWithVariables("cargocapacityformat", new string[1] { "[cratecount]" }, new string[1] {CargoCapacity.ToString() });
var cargoCapacityText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("cargocapacity"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), cargoCapacityText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
cargoCapacityStr, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
cargoCapacityText.RectTransform.MinSize = new Point(0, cargoCapacityText.Children.First().Rect.Height);
if (RecommendedCrewSizeMax > 0)
{
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),

View File

@@ -46,7 +46,10 @@ namespace Barotrauma
if (IsHighlighted || IsHighlighted) { clr = Color.Lerp(clr, Color.White, 0.8f); }
int iconSize = spawnType == SpawnType.Path ? WaypointSize : SpawnPointSize;
if (ConnectedDoor != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path) { iconSize = (int)(iconSize * 1.5f); }
if (ConnectedDoor != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path)
{
iconSize = (int)(iconSize * 1.5f);
}
if (IsSelected || IsHighlighted)
{
@@ -98,10 +101,32 @@ namespace Barotrauma
GUI.Style.Green * 0.5f, width: 1);
}
var color = Color.WhiteSmoke;
if (spawnType == SpawnType.Path)
{
if (linkedTo.Count < 2)
{
if (linkedTo.Count == 0)
{
color = Color.Red;
}
else
{
if (CurrentHull == null)
{
color = Ladders == null ? Color.Red : Color.Yellow;
}
else
{
color = Color.Yellow;
}
}
}
}
GUI.SmallFont.DrawString(spriteBatch,
ID.ToString(),
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30),
Color.WhiteSmoke);
color);
}
public override bool IsMouseOn(Vector2 position)

View File

@@ -53,100 +53,32 @@ namespace Barotrauma.Networking
case ChatMessageType.Default:
break;
case ChatMessageType.Order:
int orderIndex = msg.ReadByte();
UInt16 targetCharacterID = msg.ReadUInt16();
Character targetCharacter = Entity.FindEntityByID(targetCharacterID) as Character;
Entity targetEntity = Entity.FindEntityByID(msg.ReadUInt16());
Order orderPrefab = null;
int? optionIndex = null;
string orderOption = null;
// The option of a Dismiss order is written differently so we know what order we target
// now that the game supports multiple current orders simultaneously
if (orderIndex >= 0 && orderIndex < Order.PrefabList.Count)
{
orderPrefab = Order.PrefabList[orderIndex];
if (orderPrefab.Identifier != "dismissed")
{
optionIndex = msg.ReadByte();
}
// Does the dismiss order have a specified target?
else if (msg.ReadBoolean())
{
int identifierCount = msg.ReadByte();
if (identifierCount > 0)
{
int dismissedOrderIndex = msg.ReadByte();
Order dismissedOrderPrefab = null;
if (dismissedOrderIndex >= 0 && dismissedOrderIndex < Order.PrefabList.Count)
{
dismissedOrderPrefab = Order.PrefabList[dismissedOrderIndex];
orderOption = dismissedOrderPrefab.Identifier;
}
if (identifierCount > 1)
{
int dismissedOrderOptionIndex = msg.ReadByte();
if (dismissedOrderPrefab != null)
{
var options = dismissedOrderPrefab.Options;
if (options != null && dismissedOrderOptionIndex >= 0 && dismissedOrderOptionIndex < options.Length)
{
orderOption += $".{options[dismissedOrderOptionIndex]}";
}
}
}
}
}
}
else
{
optionIndex = msg.ReadByte();
}
int orderPriority = msg.ReadByte();
OrderTarget orderTargetPosition = null;
Order.OrderTargetType orderTargetType = (Order.OrderTargetType)msg.ReadByte();
int wallSectionIndex = 0;
if (msg.ReadBoolean())
{
var x = msg.ReadSingle();
var y = msg.ReadSingle();
var hull = Entity.FindEntityByID(msg.ReadUInt16()) as Hull;
orderTargetPosition = new OrderTarget(new Vector2(x, y), hull, creatingFromExistingData: true);
}
else if(orderTargetType == Order.OrderTargetType.WallSection)
{
wallSectionIndex = msg.ReadByte();
}
if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count)
var orderMessageInfo = OrderChatMessage.ReadOrder(msg);
if (orderMessageInfo.OrderIndex < 0 || orderMessageInfo.OrderIndex >= Order.PrefabList.Count)
{
DebugConsole.ThrowError("Invalid order message - order index out of bounds.");
if (NetIdUtils.IdMoreRecent(id, LastID)) { LastID = id; }
return;
}
else
{
orderPrefab ??= Order.PrefabList[orderIndex];
}
orderOption ??= optionIndex.HasValue && optionIndex >= 0 && optionIndex < orderPrefab.Options.Length ? orderPrefab.Options[optionIndex.Value] : "";
txt = orderPrefab.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption);
var orderPrefab = orderMessageInfo.OrderPrefab ?? Order.PrefabList[orderMessageInfo.OrderIndex];
string orderOption = orderMessageInfo.OrderOption;
orderOption ??= orderMessageInfo.OrderOptionIndex.HasValue && orderMessageInfo.OrderOptionIndex >= 0 && orderMessageInfo.OrderOptionIndex < orderPrefab.Options.Length ?
orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value] : "";
txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter, orderOption: orderOption);
if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen)
{
Order order = null;
switch (orderTargetType)
switch (orderMessageInfo.TargetType)
{
case Order.OrderTargetType.Entity:
order = new Order(orderPrefab, targetEntity, orderPrefab.GetTargetItemComponent(targetEntity as Item), orderGiver: senderCharacter);
order = new Order(orderPrefab, orderMessageInfo.TargetEntity, orderPrefab.GetTargetItemComponent(orderMessageInfo.TargetEntity as Item), orderGiver: senderCharacter);
break;
case Order.OrderTargetType.Position:
order = new Order(orderPrefab, orderTargetPosition, orderGiver: senderCharacter);
order = new Order(orderPrefab, orderMessageInfo.TargetPosition, orderGiver: senderCharacter);
break;
case Order.OrderTargetType.WallSection:
order = new Order(orderPrefab, targetEntity as Structure, wallSectionIndex, orderGiver: senderCharacter);
order = new Order(orderPrefab, orderMessageInfo.TargetEntity as Structure, orderMessageInfo.WallSectionIndex, orderGiver: senderCharacter);
break;
}
@@ -157,9 +89,9 @@ namespace Barotrauma.Networking
var fadeOutTime = !orderPrefab.IsIgnoreOrder ? (float?)orderPrefab.FadeOutTime : null;
GameMain.GameSession?.CrewManager?.AddOrder(order, fadeOutTime);
}
else if (targetCharacter != null)
else
{
targetCharacter.SetOrder(order, orderOption, orderPriority, senderCharacter);
orderMessageInfo.TargetCharacter?.SetOrder(order, orderOption, orderMessageInfo.Priority, senderCharacter);
}
}
}
@@ -167,7 +99,7 @@ namespace Barotrauma.Networking
if (NetIdUtils.IdMoreRecent(id, LastID))
{
GameMain.Client.AddChatMessage(
new OrderChatMessage(orderPrefab, orderOption, orderPriority, txt, orderTargetPosition ?? targetEntity as ISpatialEntity, targetCharacter, senderCharacter));
new OrderChatMessage(orderPrefab, orderOption, orderMessageInfo.Priority, txt, orderMessageInfo.TargetPosition ?? orderMessageInfo.TargetEntity as ISpatialEntity, orderMessageInfo.TargetCharacter, senderCharacter));
LastID = id;
}
return;

View File

@@ -527,7 +527,7 @@ namespace Barotrauma.Networking
string pwMsg = TextManager.Get("PasswordRequired");
var msgBox = new GUIMessageBox(pwMsg, "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") },
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, (int)(170 * Math.Max(1.0f, GUI.Scale))));
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, GUI.IntScale(170)));
var passwordHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), msgBox.Content.RectTransform), childAnchor: Anchor.TopCenter);
var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1f), passwordHolder.RectTransform) { MinSize = new Point(0, 20) })
{
@@ -537,7 +537,8 @@ namespace Barotrauma.Networking
if (wrongPassword)
{
new GUITextBlock(new RectTransform(new Vector2(1f, 0.33f), passwordHolder.RectTransform), TextManager.Language == "English" ? TextManager.Get("incorrectpassword") : "Incorrect password", GUI.Style.Red, GUI.Font, textAlignment: Alignment.Center);
var incorrectPasswordText = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), passwordHolder.RectTransform), TextManager.Get("incorrectpassword"), GUI.Style.Red, GUI.Font, textAlignment: Alignment.Center);
incorrectPasswordText.RectTransform.MinSize = new Point(0, (int)incorrectPasswordText.TextSize.Y);
passwordHolder.Recalculate();
}
@@ -643,7 +644,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Error while reading a message from server.", e);
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", new string[2] { "[message]", "[targetsite]" }, new string[2] { e.Message, e.TargetSite.ToString() }));
Disconnect();
GameMain.MainMenuScreen.Select();
GameMain.ServerListScreen.Select();
return;
}
@@ -659,10 +660,7 @@ namespace Barotrauma.Networking
{
EndVoteTickBox.Visible = serverSettings.Voting.AllowEndVoting && HasSpawned && !(GameMain.GameSession?.GameMode is CampaignMode);
if (respawnManager != null)
{
respawnManager.Update(deltaTime);
}
respawnManager?.Update(deltaTime);
if (updateTimer <= DateTime.Now)
{
@@ -936,9 +934,6 @@ namespace Barotrauma.Networking
}
}
break;
case ServerPacketHeader.RESET_UPGRADES:
campaign?.UpgradeManager.ClientRead(inc);
break;
case ServerPacketHeader.CREW:
campaign?.ClientReadCrew(inc);
break;
@@ -993,15 +988,21 @@ namespace Barotrauma.Networking
string errorMsg = $"Mission equality check failed. Mission count doesn't match the server (server: {missionCount}, client: {GameMain.GameSession.Missions.Count()})";
throw new Exception(errorMsg);
}
foreach (Mission mission in GameMain.GameSession.Missions)
List<string> serverMissionIdentifiers = new List<string>();
for (int i = 0; i < missionCount; i++)
{
string missionIdentifier = inc.ReadString() ?? "";
if (missionIdentifier != mission.Prefab.Identifier)
serverMissionIdentifiers.Add(inc.ReadString() ?? "");
}
if (missionCount > 0)
{
if (!GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id)))
{
string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {missionIdentifier ?? "null"}, client: {mission.Prefab.Identifier})";
string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {string.Join(", ", serverMissionIdentifiers)}, client: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
GameMain.GameSession.EnforceMissionOrder(serverMissionIdentifiers);
}
byte equalityCheckValueCount = inc.ReadByte();
@@ -1046,6 +1047,11 @@ namespace Barotrauma.Networking
mission.ClientReadInitial(inc);
}
if (inc.ReadBoolean())
{
CrewManager.ClientReadActiveOrders(inc);
}
roundInitStatus = RoundInitStatus.Started;
}
@@ -1308,10 +1314,7 @@ namespace Barotrauma.Networking
Client.ReadPermissions(inc, out permissions, out permittedCommands);
Client targetClient = ConnectedClients.Find(c => c.ID == clientID);
if (targetClient != null)
{
targetClient.SetPermissions(permissions, permittedCommands);
}
targetClient?.SetPermissions(permissions, permittedCommands);
if (clientID == myID)
{
SetMyPermissions(permissions, permittedCommands.Select(command => command.names[0]));
@@ -1422,7 +1425,7 @@ namespace Barotrauma.Networking
while (CoroutineManager.IsCoroutineRunning("EndGame"))
{
if (EndCinematic != null) { EndCinematic.Stop(); }
EndCinematic?.Stop();
yield return CoroutineStatus.Running;
}
@@ -1609,6 +1612,9 @@ namespace Barotrauma.Networking
DateTime? timeOut = null;
DateTime requestFinalizeTime = DateTime.Now;
TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2);
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
clientPeer.Send(msg, DeliveryMethod.Unreliable);
while (true)
{
@@ -1618,7 +1624,7 @@ namespace Barotrauma.Networking
{
if (DateTime.Now > requestFinalizeTime)
{
IWriteMessage msg = new WriteOnlyMessage();
msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
clientPeer.Send(msg, DeliveryMethod.Unreliable);
requestFinalizeTime = DateTime.Now + requestFinalizeInterval;
@@ -1648,12 +1654,11 @@ namespace Barotrauma.Networking
break;
}
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize)
{
break;
}
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize) { break; }
clientPeer.Update((float)Timing.Step);
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize) { break; }
}
catch (Exception e)
{
@@ -2089,6 +2094,7 @@ namespace Barotrauma.Networking
float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f;
bool radiationEnabled = inc.ReadBoolean();
byte maxMissionCount = inc.ReadByte();
//ignore the message if we already a more up-to-date one
//or if we're still waiting for the initial update
@@ -2154,6 +2160,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetRadiationEnabled(radiationEnabled);
GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode);
GameMain.NetLobbyScreen.SetBotCount(botCount);
GameMain.NetLobbyScreen.SetMaxMissionCount(maxMissionCount);
GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer);
serverSettings.VoiceChatEnabled = voiceChatEnabled;
@@ -2752,6 +2759,8 @@ namespace Barotrauma.Networking
public void Vote(VoteType voteType, object data)
{
if (clientPeer == null) return;
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ClientNetObject.VOTE);
@@ -3289,16 +3298,24 @@ namespace Barotrauma.Networking
{
string respawnText = string.Empty;
Color textColor = Color.White;
bool canChooseRespawn =
GameMain.GameSession.GameMode is CampaignMode &&
Character.Controlled == null &&
bool canChooseRespawn =
GameMain.GameSession.GameMode is CampaignMode &&
Character.Controlled == null &&
Level.Loaded?.Type != LevelData.LevelType.Outpost &&
(characterInfo == null || HasSpawned);
if (respawnManager.CurrentState == RespawnManager.State.Waiting &&
respawnManager.RespawnCountdownStarted)
if (respawnManager.CurrentState == RespawnManager.State.Waiting)
{
float timeLeft = (float)(respawnManager.RespawnTime - DateTime.Now).TotalSeconds;
respawnText = TextManager.GetWithVariable(respawnManager.UsingShuttle ? "RespawnShuttleDispatching" : "RespawningIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft));
if (respawnManager.RespawnCountdownStarted)
{
float timeLeft = (float)(respawnManager.RespawnTime - DateTime.Now).TotalSeconds;
respawnText = TextManager.GetWithVariable(respawnManager.UsingShuttle ? "RespawnShuttleDispatching" : "RespawningIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft));
}
else if (respawnManager.PendingRespawnCount > 0)
{
respawnText = TextManager.GetWithVariables("RespawnWaitingForMoreDeadPlayers",
new string[] { "[deadplayers]", "[requireddeadplayers]" },
new string[] { respawnManager.PendingRespawnCount.ToString(), respawnManager.RequiredRespawnCount.ToString() });
}
}
else if (respawnManager.CurrentState == RespawnManager.State.Transporting &&
respawnManager.ReturnCountdownStarted)

View File

@@ -1,6 +1,4 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System;
namespace Barotrauma.Networking
{
@@ -8,6 +6,17 @@ namespace Barotrauma.Networking
{
private DateTime lastShuttleLeavingWarningTime;
public int PendingRespawnCount
{
get; private set;
}
public int RequiredRespawnCount
{
get; private set;
}
partial void UpdateTransportingProjSpecific(float deltaTime)
{
if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; }
@@ -41,6 +50,8 @@ namespace Barotrauma.Networking
}
break;
case State.Waiting:
PendingRespawnCount = msg.ReadUInt16();
RequiredRespawnCount = msg.ReadUInt16();
RespawnCountdownStarted = msg.ReadBoolean();
ResetShuttle();
float newRespawnTime = msg.ReadSingle();

View File

@@ -151,7 +151,7 @@ namespace Barotrauma.Networking
}
}
public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? radiationEnabled = null, bool? useRespawnShuttle = null)
public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? radiationEnabled = null, bool? useRespawnShuttle = null, int maxMissionCount = 0)
{
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
@@ -217,6 +217,8 @@ namespace Barotrauma.Networking
outMsg.Write(autoRestart != null);
outMsg.Write(autoRestart ?? false);
outMsg.Write(radiationEnabled ?? RadiationEnabled);
outMsg.Write((byte)maxMissionCount + 1);
outMsg.WritePadBits();
}
@@ -745,6 +747,10 @@ namespace Barotrauma.Networking
TextManager.Get("ServerSettingsAllowRewiring"));
GetPropertyData("AllowRewiring").AssignGUIComponent(allowRewiring);
var allowWifiChatter = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowWifiChat"));
GetPropertyData("AllowLinkingWifiToChat").AssignGUIComponent(allowWifiChatter);
var allowDisguises = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowDisguises"));
GetPropertyData("AllowDisguises").AssignGUIComponent(allowDisguises);

View File

@@ -26,8 +26,6 @@ namespace Barotrauma.Particles
private float angularVelocity;
private Vector2 dragVec = Vector2.Zero;
private float dragWait = 0;
private float collisionIgnoreTimer = 0;
private Vector2 size;
@@ -35,6 +33,7 @@ namespace Barotrauma.Particles
private Color color;
private bool changeColor;
private bool UseMiddleColor;
private int spriteIndex;
@@ -66,7 +65,7 @@ namespace Barotrauma.Particles
public Vector4 ColorMultiplier;
public bool DrawOnTop { get; private set; }
public ParticlePrefab.DrawTargetType DrawTarget
{
get { return prefab.DrawTarget; }
@@ -103,8 +102,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)
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null)
{
this.prefab = prefab;
debugName = $"Particle ({prefab.Name})";
@@ -113,11 +111,19 @@ namespace Barotrauma.Particles
animState = 0;
animFrame = 0;
dragWait = 0;
dragVec = Vector2.Zero;
currentHull = Hull.FindHull(position, hullGuess);
size = prefab.StartSizeMin + (prefab.StartSizeMax - prefab.StartSizeMin) * Rand.Range(0.0f, 1.0f);
if (tracerPoints != null)
{
size = new Vector2(Vector2.Distance(tracerPoints.Item1, tracerPoints.Item2), size.Y);
position = (tracerPoints.Item1 + tracerPoints.Item2) / 2;
}
sizeChange = prefab.SizeChangeMin + (prefab.SizeChangeMax - prefab.SizeChangeMin) * Rand.Range(0.0f, 1.0f);
this.position = position;
prevPosition = position;
@@ -135,14 +141,21 @@ namespace Barotrauma.Particles
angularVelocity = Rand.Range(prefab.AngularVelocityMinRad, prefab.AngularVelocityMaxRad);
totalLifeTime = prefab.LifeTime;
lifeTime = prefab.LifeTime;
if (prefab.LifeTimeMin <= 0.0f)
{
totalLifeTime = prefab.LifeTime;
lifeTime = prefab.LifeTime;
}
else
{
totalLifeTime = Rand.Range(prefab.LifeTimeMin, prefab.LifeTime);
lifeTime = totalLifeTime;
}
startDelay = Rand.Range(prefab.StartDelayMin, prefab.StartDelayMax);
size = prefab.StartSizeMin + (prefab.StartSizeMax - prefab.StartSizeMin) * Rand.Range(0.0f, 1.0f);
sizeChange = prefab.SizeChangeMin + (prefab.SizeChangeMax - prefab.SizeChangeMin) * Rand.Range(0.0f, 1.0f);
UseMiddleColor = prefab.UseMiddleColor;
color = prefab.StartColor;
changeColor = prefab.StartColor != prefab.EndColor;
ColorMultiplier = Vector4.One;
@@ -238,13 +251,27 @@ namespace Barotrauma.Particles
}
size.X += sizeChange.X * deltaTime;
size.Y += sizeChange.Y * deltaTime;
size.Y += sizeChange.Y * deltaTime;
if (changeColor)
if (UseMiddleColor)
{
color = Color.Lerp(prefab.EndColor, prefab.StartColor, lifeTime / prefab.LifeTime);
if (lifeTime > totalLifeTime * 0.5f)
{
color = Color.Lerp(prefab.MiddleColor, prefab.StartColor, (lifeTime / totalLifeTime - 0.5f) * 2.0f);
}
else
{
color = Color.Lerp(prefab.EndColor, prefab.MiddleColor, lifeTime / totalLifeTime * 2.0f);
}
}
else
{
if (changeColor)
{
color = Color.Lerp(prefab.EndColor, prefab.StartColor, lifeTime / totalLifeTime);
}
}
if (prefab.Sprites[spriteIndex] is SpriteSheet)
{
animState += deltaTime;
@@ -394,29 +421,35 @@ namespace Barotrauma.Particles
private void ApplyDrag(float dragCoefficient, float deltaTime)
{
if (velocity.LengthSquared() < dragVec.LengthSquared())
Vector2 relativeVel = velocity;
if (currentHull?.Submarine != null)
{
velocity = Vector2.Zero;
return;
}
if (Math.Abs(velocity.X) < 0.0001f && Math.Abs(velocity.Y) < 0.0001f) return;
//TODO: some better way to handle particle drag
//this doesn't work that well because the drag vector is only updated every 0.5 seconds, allowing the particle to accelerate way more than it should
//(e.g. a falling particle can freely accelerate for 0.5 seconds before the drag takes effect)
dragWait-=deltaTime;
if (dragWait <= 0f)
{
dragWait = 0.5f;
float speed = velocity.Length();
dragVec = (velocity / speed) * Math.Min(speed * speed * dragCoefficient * deltaTime, 1.0f);
relativeVel = velocity - ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
}
velocity -= dragVec;
float speed = relativeVel.Length();
relativeVel /= speed;
float drag = speed * speed * dragCoefficient * 0.01f * deltaTime;
if (drag > speed)
{
relativeVel = Vector2.Zero;
}
else
{
speed -= drag;
relativeVel *= speed;
}
velocity = relativeVel;
if (currentHull?.Submarine != null)
{
velocity += ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
}
}
private void OnWallCollisionInside(Hull prevHull, Vector2 collisionNormal)
{
if (prevHull == null) { return; }

View File

@@ -1,14 +1,127 @@
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Particles
{
class ParticleEmitterProperties : ISerializableEntity
{
private const float MinValue = int.MinValue,
MaxValue = int.MaxValue;
public string Name => nameof(ParticleEmitter);
private float angleMin, angleMax;
public float AngleMinRad { get; private set; }
public float AngleMaxRad { get; private set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 360, MinValueFloat = -360f), Serialize(0f, true)]
public float AngleMin
{
get => angleMin;
set
{
angleMin = value;
AngleMinRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f));
}
}
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 360, MinValueFloat = -360f), Serialize(0f, true)]
public float AngleMax
{
get => angleMax;
set
{
angleMax = value;
AngleMaxRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f));
}
}
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, true)]
public float DistanceMin { get; set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, true)]
public float DistanceMax { get; set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, true)]
public float VelocityMin { get; set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, true)]
public float VelocityMax { get; set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(1f, true)]
public float ScaleMin { get; set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(1f, true)]
public float ScaleMax { get; set; }
[Editable(), Serialize("1,1", true)]
public Vector2 ScaleMultiplier { get; set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(0f, true)]
public float EmitInterval { get; set; }
[Editable(ValueStep = 1, MinValueInt = 0, MaxValueInt = 1000), Serialize(0, true, description: "The number of particles to spawn per frame, or every x seconds if EmitInterval is set.")]
public int ParticleAmount { get; set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 1000.0f, MinValueFloat = 0.0f), Serialize(0f, true)]
public float ParticlesPerSecond { get; set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 10.0f, MinValueFloat = 0.0f), Serialize(0f, true, description: "If larger than 0, a particle is spawned every x pixels across the ray cast by a hitscan weapon.")]
public float EmitAcrossRayInterval { get; set; }
[Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(0f, true, description: "Delay before the emitter becomes active after being created.")]
public float InitialDelay { get; set; }
[Editable, Serialize(false, true)]
public bool HighQualityCollisionDetection { get; set; }
[Editable, Serialize(false, true)]
public bool CopyEntityAngle { get; set; }
[Editable, Serialize("1,1,1,1", true)]
public Color ColorMultiplier { get; set; }
[Editable, Serialize(false, true)]
public bool DrawOnTop { get; set; }
[Serialize(0f, true)]
public float Angle
{
get => AngleMin;
set => AngleMin = AngleMax = value;
}
[Serialize(0f, true)]
public float Distance
{
get => DistanceMin;
set => DistanceMin = DistanceMax = value;
}
[Serialize(0f, true)]
public float Velocity
{
get => VelocityMin;
set => VelocityMin = VelocityMax = value;
}
public Dictionary<string, SerializableProperty> SerializableProperties { get; }
public ParticleEmitterProperties(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
}
}
class ParticleEmitter
{
private float emitTimer;
private float burstEmitTimer;
private float initialDelay;
public readonly ParticleEmitterPrefab Prefab;
@@ -23,51 +136,76 @@ namespace Barotrauma.Particles
Prefab = prefab;
}
public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null)
public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple<Vector2, Vector2> tracerPoints = null)
{
if (initialDelay < Prefab.Properties.InitialDelay)
{
initialDelay += deltaTime;
return;
}
emitTimer += deltaTime * amountMultiplier;
burstEmitTimer -= deltaTime;
if (Prefab.ParticlesPerSecond > 0)
if (Prefab.Properties.EmitAcrossRayInterval > 0.0f && tracerPoints != null)
{
float emitInterval = 1.0f / Prefab.ParticlesPerSecond;
Vector2 dir = tracerPoints.Item2 - tracerPoints.Item1;
if (dir.LengthSquared() > 0.001f)
{
float dist = dir.Length();
dir /= dist;
for (float z = 0.0f; z < dist; z += Prefab.Properties.EmitAcrossRayInterval)
{
Vector2 pos = tracerPoints.Item1 + dir * z;
Emit(pos, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: null);
}
}
}
if (Prefab.Properties.ParticlesPerSecond > 0)
{
float emitInterval = 1.0f / Prefab.Properties.ParticlesPerSecond;
while (emitTimer > emitInterval)
{
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle);
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints);
emitTimer -= emitInterval;
}
}
if (burstEmitTimer > 0.0f) { return; }
burstEmitTimer = Prefab.EmitInterval;
for (int i = 0; i < Prefab.ParticleAmount * amountMultiplier; i++)
burstEmitTimer = Prefab.Properties.EmitInterval;
for (int i = 0; i < Prefab.Properties.ParticleAmount * amountMultiplier; i++)
{
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle);
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints);
}
}
private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null)
private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple<Vector2, Vector2> tracerPoints = null)
{
angle += Rand.Range(Prefab.AngleMin, Prefab.AngleMax);
var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab;
if (particlePrefab == null) { return; }
angle += Rand.Range(Prefab.Properties.AngleMinRad, Prefab.Properties.AngleMaxRad);
Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
Vector2 velocity = dir * Rand.Range(Prefab.VelocityMin, Prefab.VelocityMax) * velocityMultiplier;
position += dir * Rand.Range(Prefab.DistanceMin, Prefab.DistanceMax);
Vector2 velocity = dir * Rand.Range(Prefab.Properties.VelocityMin, Prefab.Properties.VelocityMax) * velocityMultiplier;
position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax);
var particle = GameMain.ParticleManager.CreateParticle(overrideParticle ?? Prefab.ParticlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop);
var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, tracerPoints: tracerPoints);
if (particle != null)
{
particle.Size *= Rand.Range(Prefab.ScaleMin, Prefab.ScaleMax) * sizeMultiplier;
particle.HighQualityCollisionDetection = Prefab.HighQualityCollisionDetection;
if (colorMultiplier.HasValue)
{
particle.ColorMultiplier = colorMultiplier.Value.ToVector4();
}
else if (Prefab.ColorMultiplier != Color.White)
particle.Size *= Rand.Range(Prefab.Properties.ScaleMin, Prefab.Properties.ScaleMax) * sizeMultiplier;
particle.Size *= Prefab.Properties.ScaleMultiplier;
particle.HighQualityCollisionDetection = Prefab.Properties.HighQualityCollisionDetection;
if (colorMultiplier.HasValue)
{
particle.ColorMultiplier = Prefab.ColorMultiplier.ToVector4();
particle.ColorMultiplier = colorMultiplier.Value.ToVector4();
}
else if (Prefab.Properties.ColorMultiplier != Color.White)
{
particle.ColorMultiplier = Prefab.Properties.ColorMultiplier.ToVector4();
}
}
}
@@ -76,9 +214,9 @@ namespace Barotrauma.Particles
{
Rectangle bounds = new Rectangle((int)startPosition.X, (int)startPosition.Y, (int)startPosition.X, (int)startPosition.Y);
for (float angle = Prefab.AngleMin; angle <= Prefab.AngleMax; angle += 0.1f)
for (float angle = Prefab.Properties.AngleMinRad; angle <= Prefab.Properties.AngleMaxRad; angle += 0.1f)
{
Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Prefab.VelocityMax;
Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Prefab.Properties.VelocityMax;
Vector2 endPosition = Prefab.ParticlePrefab.CalculateEndPosition(startPosition, velocity);
Vector2 endSize = Prefab.ParticlePrefab.CalculateEndSize();
@@ -91,15 +229,15 @@ namespace Barotrauma.Particles
}
else
{
spriteExtent = Math.Max(spriteExtent, Math.Max(sprite.size.X * endSize.X, sprite.size.Y * endSize.Y));
spriteExtent = Math.Max(spriteExtent, Math.Max(sprite.size.X * endSize.X, sprite.size.Y * endSize.Y));
}
}
bounds = new Rectangle(
(int)Math.Min(bounds.X, endPosition.X - Prefab.DistanceMax - spriteExtent / 2),
(int)Math.Min(bounds.Y, endPosition.Y - Prefab.DistanceMax - spriteExtent / 2),
(int)Math.Max(bounds.X, endPosition.X + Prefab.DistanceMax + spriteExtent / 2),
(int)Math.Max(bounds.Y, endPosition.Y + Prefab.DistanceMax + spriteExtent / 2));
(int)Math.Min(bounds.X, endPosition.X - Prefab.Properties.DistanceMax - spriteExtent / 2),
(int)Math.Min(bounds.Y, endPosition.Y - Prefab.Properties.DistanceMax - spriteExtent / 2),
(int)Math.Max(bounds.X, endPosition.X + Prefab.Properties.DistanceMax + spriteExtent / 2),
(int)Math.Max(bounds.Y, endPosition.Y + Prefab.Properties.DistanceMax + spriteExtent / 2));
}
bounds = new Rectangle(bounds.X, bounds.Y, bounds.Width - bounds.X, bounds.Height - bounds.Y);
@@ -109,9 +247,7 @@ namespace Barotrauma.Particles
}
class ParticleEmitterPrefab
{
public readonly string Name;
{
private string particlePrefabName;
private ParticlePrefab particlePrefab;
@@ -122,103 +258,30 @@ namespace Barotrauma.Particles
if (particlePrefab == null && particlePrefabName != null)
{
particlePrefab = GameMain.ParticleManager?.FindPrefab(particlePrefabName);
if (particlePrefab == null) { particlePrefabName = null; }
if (particlePrefab == null)
{
DebugConsole.ThrowError($"Failed to find particle prefab \"{particlePrefabName}\".");
particlePrefabName = null;
}
}
return particlePrefab;
}
}
public readonly float AngleMin, AngleMax;
public readonly ParticleEmitterProperties Properties;
public readonly float DistanceMin, DistanceMax;
public readonly float VelocityMin, VelocityMax;
public readonly float ScaleMin, ScaleMax;
public readonly float EmitInterval;
public readonly int ParticleAmount;
public readonly float ParticlesPerSecond;
public readonly bool HighQualityCollisionDetection;
public readonly bool CopyEntityAngle;
public readonly Color ColorMultiplier;
public bool DrawOnTop => forceDrawOnTop || ParticlePrefab.DrawOnTop;
private readonly bool forceDrawOnTop;
public bool DrawOnTop => Properties.DrawOnTop || ParticlePrefab.DrawOnTop;
public ParticleEmitterPrefab(XElement element)
{
Name = element.Name.ToString();
Properties = new ParticleEmitterProperties(element);
particlePrefabName = element.GetAttributeString("particle", "");
}
if (element.Attribute("startrotation") == null)
{
AngleMin = element.GetAttributeFloat("anglemin", 0.0f);
AngleMax = element.GetAttributeFloat("anglemax", 0.0f);
}
else
{
AngleMin = element.GetAttributeFloat("angle", 0.0f);
AngleMax = AngleMin;
}
AngleMin = MathHelper.ToRadians(MathHelper.Clamp(AngleMin, -360.0f, 360.0f));
AngleMax = MathHelper.ToRadians(MathHelper.Clamp(AngleMax, -360.0f, 360.0f));
if (element.Attribute("scalemin") == null)
{
ScaleMin = 1.0f;
ScaleMax = 1.0f;
}
else
{
ScaleMin = element.GetAttributeFloat("scalemin", 1.0f);
ScaleMax = Math.Max(ScaleMin, element.GetAttributeFloat("scalemax", 1.0f));
}
if (element.Attribute("distance") == null)
{
DistanceMin = element.GetAttributeFloat("distancemin", 0.0f);
DistanceMax = element.GetAttributeFloat("distancemax", 0.0f);
}
else
{
DistanceMin = DistanceMax = element.GetAttributeFloat("distance", 0.0f);
}
if (DistanceMax < DistanceMin)
{
var temp = DistanceMin;
DistanceMin = DistanceMax;
DistanceMax = temp;
}
if (element.Attribute("velocity") == null)
{
VelocityMin = element.GetAttributeFloat("velocitymin", 0.0f);
VelocityMax = element.GetAttributeFloat("velocitymax", 0.0f);
}
else
{
VelocityMin = VelocityMax = element.GetAttributeFloat("velocity", 0.0f);
}
if (VelocityMax < VelocityMin)
{
var temp = VelocityMin;
VelocityMin = VelocityMax;
VelocityMax = temp;
}
EmitInterval = element.GetAttributeFloat("emitinterval", 0.0f);
ParticlesPerSecond = element.GetAttributeFloat("particlespersecond", 0);
ParticleAmount = element.GetAttributeInt("particleamount", 0);
HighQualityCollisionDetection = element.GetAttributeBool("highqualitycollisiondetection", false);
CopyEntityAngle = element.GetAttributeBool("copyentityangle", false);
forceDrawOnTop = element.GetAttributeBool("drawontop", false);
ColorMultiplier = element.GetAttributeColor("colormultiplier", Color.White);
public ParticleEmitterPrefab(ParticlePrefab prefab, ParticleEmitterProperties properties)
{
Properties = properties;
particlePrefab = prefab;
}
}
}

View File

@@ -114,13 +114,12 @@ namespace Barotrauma.Particles
{
Prefabs.RemoveByFile(configFile);
}
public Particle CreateParticle(string prefabName, Vector2 position, float angle, float speed, Hull hullGuess = null, float collisionIgnoreTimer = 0f)
public Particle CreateParticle(string prefabName, Vector2 position, float angle, float speed, Hull hullGuess = null, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null)
{
return CreateParticle(prefabName, position, new Vector2((float)Math.Cos(angle), (float)-Math.Sin(angle)) * speed, angle, hullGuess, collisionIgnoreTimer);
return CreateParticle(prefabName, position, new Vector2((float)Math.Cos(angle), (float)-Math.Sin(angle)) * speed, angle, hullGuess, collisionIgnoreTimer, tracerPoints: tracerPoints);
}
public Particle CreateParticle(string prefabName, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, float collisionIgnoreTimer = 0f)
public Particle CreateParticle(string prefabName, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null)
{
ParticlePrefab prefab = FindPrefab(prefabName);
@@ -129,27 +128,30 @@ namespace Barotrauma.Particles
DebugConsole.ThrowError("Particle prefab \"" + prefabName + "\" not found!");
return null;
}
return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer);
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)
public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null)
{
if (particleCount >= MaxParticles || prefab == null || prefab.Sprites.Count == 0) { return null; }
// this should be optimized for tracers after prototyping
if (tracerPoints == null)
{
Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity);
Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity);
Vector2 minPos = new Vector2(Math.Min(position.X, particleEndPos.X), Math.Min(position.Y, particleEndPos.Y));
Vector2 maxPos = new Vector2(Math.Max(position.X, particleEndPos.X), Math.Max(position.Y, particleEndPos.Y));
Vector2 minPos = new Vector2(Math.Min(position.X, particleEndPos.X), Math.Min(position.Y, particleEndPos.Y));
Vector2 maxPos = new Vector2(Math.Max(position.X, particleEndPos.X), Math.Max(position.Y, particleEndPos.Y));
Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist);
Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist);
if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; }
if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; }
if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; }
if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; }
}
if (particles[particleCount] == null) particles[particleCount] = new Particle();
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer);
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, tracerPoints: tracerPoints);
particleCount++;

View File

@@ -53,6 +53,10 @@ namespace Barotrauma.Particles
[Editable(0.0f, float.MaxValue), Serialize(5.0f, false, description: "How many seconds the particle remains alive.")]
public float LifeTime { get; private set; }
[Editable(0.0f, float.MaxValue), Serialize(0.0f, false, description: "Will randomize lifetime value between lifetime and lifetimeMin. If left to 0 will use only lifetime value.")]
public float LifeTimeMin { get; private set; }
[Editable, Serialize(0.0f, false, description: "How long it takes for the particle to appear after spawning it.")]
public float StartDelayMin { get; private set; }
[Editable, Serialize(0.0f, false, description: "How long it takes for the particle to appear after spawning it.")]
@@ -118,10 +122,10 @@ namespace Barotrauma.Particles
[Editable, Serialize(false, false, description: "Should the particle face the direction it's moving towards.")]
public bool RotateToDirection { get; private set; }
[Editable, Serialize(0.0f, false, description: "Drag applied to the particle when it's moving through air.")]
[Editable(0.0f, float.MaxValue, DecimalCount = 3), Serialize(0.0f, false, description: "Drag applied to the particle when it's moving through air.")]
public float Drag { get; private set; }
[Editable, Serialize(0.0f, false, description: "Drag applied to the particle when it's moving through water.")]
[Editable(0.0f, float.MaxValue, DecimalCount = 3), Serialize(0.0f, false, description: "Drag applied to the particle when it's moving through water.")]
public float WaterDrag { get; private set; }
private Vector2 velocityChange;
@@ -193,8 +197,14 @@ namespace Barotrauma.Particles
[Editable, Serialize("1.0,1.0,1.0,1.0", false, description: "The initial color of the particle.")]
public Color StartColor { get; private set; }
[Editable, Serialize("1.0,1.0,1.0,1.0", false, description: "The initial color of the particle.")]
public Color MiddleColor { get; private set; }
[Editable, Serialize("1.0,1.0,1.0,1.0", false, description: "The color of the particle at the end of its lifetime.")]
public Color EndColor { get; private set; }
[Editable, Serialize(false, false, description: "If true the color will go from StartColor to EndcColor and back to StartColor.")]
public bool UseMiddleColor { get; private set; }
[Editable, Serialize(DrawTargetType.Air, false, description: "Should the particle be rendered in air, water or both.")]
public DrawTargetType DrawTarget { get; private set; }
@@ -289,7 +299,7 @@ namespace Barotrauma.Particles
StartRotationMin = element.GetAttributeFloat("startrotation", 0.0f);
StartRotationMax = StartRotationMin;
}
if (CollisionRadius <= 0.0f) CollisionRadius = Sprites.Count > 0 ? 1 : Sprites[0].SourceRect.Width / 2.0f;
}

View File

@@ -443,7 +443,7 @@ namespace Barotrauma
public static bool KeyHit(Keys button)
{
return (AllowInput && oldKeyboardState.IsKeyDown(button) && keyboardState.IsKeyUp(button));
return AllowInput && oldKeyboardState.IsKeyUp(button) && keyboardState.IsKeyDown(button);
}
public static bool InventoryKeyHit(int index)
@@ -454,7 +454,7 @@ namespace Barotrauma
public static bool KeyDown(Keys button)
{
return (AllowInput && keyboardState.IsKeyDown(button));
return AllowInput && keyboardState.IsKeyDown(button);
}
public static bool KeyUp(Keys button)

View File

@@ -6,6 +6,7 @@ using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using System.Globalization;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -42,6 +43,12 @@ namespace Barotrauma
}
public GUITickBox EnableRadiationToggle { get; set; }
public GUILayoutGroup CampaignSettingsContent { get; set; }
public GUIButton CampaignCustomizeButton { get; set; }
public GUIMessageBox CampaignCustomizeSettings { get; set; }
public GUITextBlock MaxMissionCountText;
private readonly bool isMultiplayer;
@@ -177,10 +184,19 @@ namespace Barotrauma
if (isMultiplayer)
{
settings.RadiationEnabled = GameMain.NetLobbyScreen.IsRadiationEnabled();
settings.MaxMissionCount = GameMain.NetLobbyScreen.GetMaxMissionCount();
}
else
{
settings.RadiationEnabled = EnableRadiationToggle?.Selected ?? false;
if (MaxMissionCountText != null && Int32.TryParse(MaxMissionCountText.Text, out int missionCount))
{
settings.MaxMissionCount = missionCount;
}
else
{
settings.MaxMissionCount = CampaignSettings.DefaultMaxMissionCount;
}
}
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
@@ -265,14 +281,14 @@ namespace Barotrauma
if (!isMultiplayer)
{
if (MapGenerationParams.Instance.RadiationParams != null)
CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), buttonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton"))
{
EnableRadiationToggle = new GUITickBox(new RectTransform(new Vector2(0.3f, 1f), buttonContainer.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font)
OnClicked = (tb, userdata) =>
{
Selected = true,
ToolTip = TextManager.Get("campaignoption.enableradiation.tooltip")
};
}
CreateCustomizeWindow();
return true;
}
};
var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton")
{
@@ -290,6 +306,52 @@ namespace Barotrauma
UpdateLoadMenu(saveFiles);
}
private void CreateCustomizeWindow()
{
CampaignCustomizeSettings = new GUIMessageBox("", "", new string[] { TextManager.Get("OK") }, new Vector2(0.2f, 0.2f));
CampaignCustomizeSettings.Buttons[0].OnClicked += CampaignCustomizeSettings.Close;
CampaignSettingsContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter))
{
RelativeSpacing = 0.1f
};
if (MapGenerationParams.Instance.RadiationParams != null)
{
EnableRadiationToggle = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.3f), CampaignSettingsContent.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font)
{
Selected = true,
ToolTip = TextManager.Get("campaignoption.enableradiation.tooltip")
};
}
var maxMissionCountSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), CampaignSettingsContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
ToolTip = TextManager.Get("maxmissioncounttooltip")
};
var maxMissionCountDescription = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), maxMissionCountSettingHolder.RectTransform), TextManager.Get("maxmissioncount", fallBackTag: "missions"), wrap: true);
var maxMissionCountContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), maxMissionCountSettingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, Stretch = true };
var maxMissionCountButtons = new GUIButton[2];
maxMissionCountButtons[0] = new GUIButton(new RectTransform(new Vector2(0.15f, 0.8f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleLeft")
{
OnClicked = (button, obj) =>
{
MaxMissionCountText.Text = Math.Clamp(Int32.Parse(MaxMissionCountText.Text) - 1, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit).ToString();
return true;
}
};
MaxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), CampaignSettings.DefaultMaxMissionCount.ToString(), textAlignment: Alignment.Center, style: "GUITextBox");
maxMissionCountButtons[1] = new GUIButton(new RectTransform(new Vector2(0.15f, 0.8f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleRight")
{
OnClicked = (button, obj) =>
{
MaxMissionCountText.Text = Math.Clamp(Int32.Parse(MaxMissionCountText.Text) + 1, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit).ToString();
return true;
}
};
maxMissionCountContainer.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip);
}
private void CreateMultiplayerCampaignSubList(RectTransform parent)
{
GUILayoutGroup subHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.725f), parent))

View File

@@ -21,6 +21,9 @@ namespace Barotrauma
private GUIComponent locationInfoPanel;
private GUIListBox missionList;
private readonly List<GUITickBox> missionTickBoxes = new List<GUITickBox>();
private bool hasMaxMissions;
private GUIButton repairHullsButton, replaceShuttlesButton, repairItemsButton;
@@ -51,9 +54,18 @@ namespace Barotrauma
CreateUI(container);
campaign.Map.OnLocationSelected += SelectLocation;
campaign.Map.OnMissionSelected += (connection, mission) =>
campaign.Map.OnMissionsSelected += (connection, missions) =>
{
missionList?.Select(mission);
if (missionList?.Content != null)
{
foreach (GUIComponent missionElement in missionList.Content.Children)
{
if (missionElement.FindChild(c => c is GUITickBox, recursive: true) is GUITickBox tickBox)
{
tickBox.Selected = missions.Contains(tickBox.UserData as Mission);
}
}
}
};
}
@@ -311,6 +323,20 @@ namespace Barotrauma
map.SelectLocation(-1);
}
map.Update(deltaTime, mapContainer);
foreach (GUITickBox tickBox in missionTickBoxes)
{
bool disable = hasMaxMissions && !tickBox.Selected;
tickBox.Enabled = Campaign.AllowedToManageCampaign() && !disable;
tickBox.Box.DisabledColor = disable ? tickBox.Box.Color * 0.5f : tickBox.Box.Color * 0.8f;
foreach (GUIComponent child in tickBox.Parent.Parent.Children)
{
if (child is GUITextBlock textBlock)
{
textBlock.SelectedTextColor = textBlock.HoverTextColor = textBlock.TextColor =
disable ? new Color(textBlock.TextColor, 0.5f) : new Color(textBlock.TextColor, 1.0f);
}
}
}
}
public void Update(float deltaTime)
@@ -341,6 +367,7 @@ namespace Barotrauma
public void SelectLocation(Location location, LocationConnection connection)
{
missionTickBoxes.Clear();
locationInfoPanel.ClearChildren();
//don't select the map panel if we're looking at some other tab
if (selectedTab == CampaignMode.InteractionType.Map)
@@ -436,6 +463,19 @@ namespace Barotrauma
{
Spacing = (int)(5 * GUI.yScale)
};
missionList.OnSelected = (GUIComponent selected, object userdata) =>
{
var tickBox = selected.FindChild(c => c is GUITickBox, recursive: true) as GUITickBox;
if (GUI.MouseOn == tickBox) { return false; }
if (tickBox != null)
{
if (Campaign.AllowedToManageCampaign() && tickBox.Enabled)
{
tickBox.Selected = !tickBox.Selected;
}
}
return true;
};
SelectedLevel = connection?.LevelData;
Location currentDisplayLocation = Campaign.GetCurrentDisplayLocation();
@@ -444,9 +484,6 @@ namespace Barotrauma
List<Mission> availableMissions = currentDisplayLocation.GetMissionsInConnection(connection).ToList();
if (!availableMissions.Contains(null)) { availableMissions.Insert(0, null); }
Mission selectedMission = currentDisplayLocation.SelectedMission != null && availableMissions.Contains(currentDisplayLocation.SelectedMission) ?
currentDisplayLocation.SelectedMission : null;
missionList.Content.ClearChildren();
foreach (Mission mission in availableMissions)
@@ -458,67 +495,93 @@ namespace Barotrauma
var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), missionPanel.RectTransform, Anchor.Center))
{
Stretch = true,
CanBeFocused = true
CanBeFocused = true,
AbsoluteSpacing = GUI.IntScale(5)
};
var missionName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission?.Name ?? TextManager.Get("NoMission"), font: GUI.SubHeadingFont, wrap: true);
// missionName.RectTransform.MinSize = new Point(0, (int)(missionName.Rect.Height * 1.5f));
if (mission != null)
{
if (MapGenerationParams.Instance?.MissionIcon != null)
{
var tickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.X, 0) }, label: string.Empty)
{
var icon = new GUIImage(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.X, 0) },
MapGenerationParams.Instance.MissionIcon, scaleToFit: true)
{
Color = MapGenerationParams.Instance.IndicatorColor * 0.5f,
SelectedColor = MapGenerationParams.Instance.IndicatorColor,
HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f)
};
icon.RectTransform.IsFixedSize = true;
GUILayoutGroup difficultyIndicatorGroup = null;
if (mission.Difficulty.HasValue)
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) },
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
AbsoluteSpacing = 1,
UserData = "difficulty"
};
var difficultyColor = mission.GetDifficultyColor();
for (int i = 0; i < mission.Difficulty; i++)
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true)
{
Color = difficultyColor * 0.5f,
SelectedColor = difficultyColor,
HoverColor = Color.Lerp(difficultyColor, Color.White, 0.5f)
};
}
}
UserData = mission,
Selected = Campaign.Map.CurrentLocation?.SelectedMissions.Contains(mission) ?? false
};
tickBox.RectTransform.MinSize = new Point(tickBox.Rect.Height, 0);
tickBox.RectTransform.IsFixedSize = true;
tickBox.Enabled = Campaign.AllowedToManageCampaign();
tickBox.OnSelected += (GUITickBox tb) =>
{
if (!Campaign.AllowedToManageCampaign()) { return false; }
float extraPadding = 0.5f * icon.Rect.Width;
float extraZPadding = difficultyIndicatorGroup != null ? mission.Difficulty.Value * (difficultyIndicatorGroup.Children.First().Rect.Width + difficultyIndicatorGroup.AbsoluteSpacing) : 0;
missionName.Padding = new Vector4(missionName.Padding.X + icon.Rect.Width + extraPadding,
missionName.Padding.Y,
missionName.Padding.Z + extraZPadding + extraPadding,
missionName.Padding.W);
missionName.CalculateHeightFromText();
if (tb.Selected)
{
Campaign.Map.CurrentLocation.SelectMission(mission);
}
else
{
Campaign.Map.CurrentLocation.DeselectMission(mission);
}
UpdateMaxMissions(connection.OtherLocation(currentDisplayLocation));
if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending &&
Campaign.AllowedToManageCampaign())
{
GameMain.Client?.SendCampaignState();
}
return true;
};
missionTickBoxes.Add(tickBox);
GUILayoutGroup difficultyIndicatorGroup = null;
if (mission.Difficulty.HasValue)
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) },
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
AbsoluteSpacing = 1,
UserData = "difficulty"
};
var difficultyColor = mission.GetDifficultyColor();
for (int i = 0; i < mission.Difficulty; i++)
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true)
{
Color = difficultyColor,
SelectedColor = difficultyColor,
HoverColor = difficultyColor
};
}
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.GetMissionRewardText(), wrap: true, parseRichText: true);
float extraPadding = 0;// 0.8f * tickBox.Rect.Width;
float extraZPadding = difficultyIndicatorGroup != null ? mission.Difficulty.Value * (difficultyIndicatorGroup.Children.First().Rect.Width + difficultyIndicatorGroup.AbsoluteSpacing) : 0;
missionName.Padding = new Vector4(missionName.Padding.X + tickBox.Rect.Width * 1.2f + extraPadding,
missionName.Padding.Y,
missionName.Padding.Z + extraZPadding + extraPadding,
missionName.Padding.W);
missionName.CalculateHeightFromText();
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform) { MinSize = new Point(0, GUI.IntScale(10)) }, style: null);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.GetMissionRewardText(Submarine.MainSub), wrap: true, parseRichText: true);
string reputationText = mission.GetReputationRewardText(mission.Locations[0]);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText, wrap: true, parseRichText: true);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.Description, wrap: true, parseRichText: true);
}
missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(20));
missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height + missionTextContent.AbsoluteSpacing) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(0));
foreach (GUIComponent child in missionTextContent.Children)
{
var textBlock = child as GUITextBlock;
textBlock.Color = textBlock.SelectedColor = textBlock.HoverColor = Color.Transparent;
textBlock.HoverTextColor = textBlock.TextColor;
textBlock.TextColor *= 0.5f;
if (child is GUITextBlock textBlock)
{
textBlock.Color = textBlock.SelectedColor = textBlock.HoverColor = Color.Transparent;
textBlock.SelectedTextColor = textBlock.HoverTextColor = textBlock.TextColor;
}
}
missionPanel.OnAddedToGUIUpdateList = (c) =>
{
@@ -537,36 +600,33 @@ namespace Barotrauma
};
}
}
missionList.Select(selectedMission);
if (prevSelectedLocation == selectedLocation)
{
missionList.BarScroll = prevMissionListScroll;
}
if (Campaign.AllowedToManageCampaign())
{
missionList.OnSelected = (component, userdata) =>
{
Mission mission = userdata as Mission;
if (Campaign.Map.CurrentLocation.SelectedMission == mission) { return false; }
Campaign.Map.CurrentLocation.SelectedMission = mission;
//RefreshMissionInfo(mission);
if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending &&
Campaign.AllowedToManageCampaign())
{
GameMain.Client?.SendCampaignState();
}
return true;
};
missionList.UpdateDimensions();
missionList.UpdateScrollBarSize();
}
}
var destination = connection.OtherLocation(currentDisplayLocation);
UpdateMaxMissions(destination);
StartButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), content.RectTransform),
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), content.RectTransform), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), buttonArea.RectTransform), "", font: GUI.Style.SubHeadingFont)
{
TextGetter = () =>
{
return TextManager.AddPunctuation(':', TextManager.Get("Missions"), $"{Campaign.NumberOfMissionsAtLocation(destination)}/{Campaign.Settings.MaxMissionCount}");
}
};
StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonArea.RectTransform),
TextManager.Get("StartCampaignButton"), style: "GUIButtonLarge")
{
OnClicked = (GUIButton btn, object obj) =>
{
if (missionList.Content.Children.Any(c => c.UserData is Mission) && !(missionList.SelectedData is Mission))
if (missionList.Content.FindChild(c => c is GUITickBox tickBox && tickBox.Selected, recursive: true) == null &&
missionList.Content.Children.Any(c => c.UserData is Mission))
{
var noMissionVerification = new GUIMessageBox(string.Empty, TextManager.Get("nomissionprompt"), new string[] { TextManager.Get("yes"), TextManager.Get("no") });
noMissionVerification.Buttons[0].OnClicked = (btn, userdata) =>
@@ -587,6 +647,8 @@ namespace Barotrauma
Visible = Campaign.AllowedToEndRound()
};
buttonArea.RectTransform.MinSize = new Point(0, StartButton.RectTransform.MinSize.Y);
if (Level.Loaded != null &&
connection?.LevelData == Level.Loaded.LevelData &&
currentDisplayLocation == Campaign.Map?.CurrentLocation)
@@ -594,6 +656,7 @@ namespace Barotrauma
StartButton.Visible = false;
missionList.Enabled = false;
}
//locationInfoPanel?.UpdateAuto(1.0f);
}
public void SelectTab(CampaignMode.InteractionType tab)
@@ -657,5 +720,10 @@ namespace Barotrauma
{
return TextManager.GetWithVariable("PlayerCredits", "[credits]", (GameMain.GameSession?.Campaign == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", GameMain.GameSession.Campaign.Money));
}
private void UpdateMaxMissions(Location location)
{
hasMaxMissions = Campaign.NumberOfMissionsAtLocation(location) >= Campaign.Settings.MaxMissionCount;
}
}
}

View File

@@ -4731,7 +4731,7 @@ namespace Barotrauma.CharacterEditor
rotation: 0,
origin: orig,
sourceRectangle: wearable.InheritSourceRect ? limb.ActiveSprite.SourceRect : wearable.Sprite.SourceRect,
scale: (wearable.InheritTextureScale ? 1 : 1 / RagdollParams.TextureScale) * spriteSheetZoom,
scale: (wearable.InheritTextureScale ? 1 : wearable.Scale / RagdollParams.TextureScale) * spriteSheetZoom,
effects: SpriteEffects.None,
color: Color.White,
layerDepth: 0);

View File

@@ -305,7 +305,7 @@ namespace Barotrauma.CharacterEditor
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), TextManager.Get("ContentPackage"));
var rightContainer = new GUIFrame(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), style: null);
contentPackageDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.5f), rightContainer.RectTransform, Anchor.TopRight));
foreach (ContentPackage cp in ContentPackage.AllPackages)
foreach (ContentPackage cp in GameMain.Config.AllEnabledPackages)
{
#if !DEBUG
if (cp == GameMain.VanillaContent) { continue; }

View File

@@ -0,0 +1,57 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class EditorScreen : Screen
{
public static Color BackgroundColor = GameSettings.SubEditorBackgroundColor;
public void CreateBackgroundColorPicker()
{
var msgBox = new GUIMessageBox(TextManager.Get("CharacterEditor.EditBackgroundColor"), "", new[] { TextManager.Get("Reset"), TextManager.Get("OK")}, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
var rgbLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal: true);
// Generate R,G,B labels and parent elements
var layoutParents = new GUILayoutGroup[3];
for (int i = 0; i < 3; i++)
{
var colorContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1), rgbLayout.RectTransform), isHorizontal: true) { Stretch = true };
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), colorContainer.RectTransform, Anchor.CenterLeft) { MinSize = new Point(15, 0) }, GUI.colorComponentLabels[i], font: GUI.SmallFont, textAlignment: Alignment.Center);
layoutParents[i] = colorContainer;
}
// attach number inputs to our generated parent elements
var rInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[0].RectTransform), GUINumberInput.NumberType.Int) { IntValue = BackgroundColor.R };
var gInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[1].RectTransform), GUINumberInput.NumberType.Int) { IntValue = BackgroundColor.G };
var bInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[2].RectTransform), GUINumberInput.NumberType.Int) { IntValue = BackgroundColor.B };
rInput.MinValueInt = gInput.MinValueInt = bInput.MinValueInt = 0;
rInput.MaxValueInt = gInput.MaxValueInt = bInput.MaxValueInt = 255;
rInput.OnValueChanged = gInput.OnValueChanged = bInput.OnValueChanged = delegate
{
var color = new Color(rInput.IntValue, gInput.IntValue, bInput.IntValue);
BackgroundColor = color;
GameSettings.SubEditorBackgroundColor = color;
};
// Reset button
msgBox.Buttons[0].OnClicked = (button, o) =>
{
rInput.IntValue = 13;
gInput.IntValue = 37;
bInput.IntValue = 69;
return true;
};
// Ok button
msgBox.Buttons[1].OnClicked = (button, o) =>
{
msgBox.Close();
GameMain.Config.SaveNewPlayerConfig();
return true;
};
}
}
}

View File

@@ -227,7 +227,7 @@ namespace Barotrauma
public static GUIMessageBox AskForConfirmation(string header, string body, Func<bool> onConfirm)
{
string[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") };
GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons);
// Cancel button
msgBox.Buttons[1].OnClicked = delegate

View File

@@ -364,13 +364,15 @@ namespace Barotrauma
Quad.Render();
}
float grainStrength = Character.Controlled?.GrainStrength ?? 0;
if (grainStrength > 0)
if (Character.Controlled is { } character)
{
float grainStrength = character.GrainStrength;
Rectangle screenRect = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: GrainEffect);
GUI.DrawRectangle(spriteBatch, screenRect, Color.White * grainStrength, isFilled: true);
GUI.DrawRectangle(spriteBatch, screenRect, Color.White, isFilled: true);
GrainEffect.Parameters["seed"].SetValue(Rand.Range(0f, 1f, Rand.RandSync.Unsynced));
GrainEffect.Parameters["intensity"].SetValue(grainStrength);
GrainEffect.Parameters["grainColor"].SetValue(character.GrainColor.ToVector4());
spriteBatch.End();
}

View File

@@ -469,18 +469,17 @@ namespace Barotrauma
this.game = game;
menuTabs[(int)Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[(int)Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
menuTabs[(int)Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null)
{
CanBeFocused = false
};
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[(int)Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker")
{
CanBeFocused = false
};
var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f);
creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
new GUIButton(new RectTransform(new Vector2(0.1f, 0.05f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.BottomLeft) { RelativeOffset = new Vector2(0.25f, 0.02f) },
TextManager.Get("Back"), style: "GUIButtonLarge")
{
OnClicked = SelectTab
};
}
#endregion
@@ -867,9 +866,7 @@ namespace Barotrauma
{
int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers + (int)button.UserData, 1, NetConfig.MaxPlayers);
maxPlayersBox.Text = currMaxPlayers.ToString();
return true;
}
@@ -1167,15 +1164,18 @@ namespace Barotrauma
StartNewGame = StartGame
};
var startButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), innerNewGame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.BottomRight);
var startButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), innerNewGame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.BottomRight)
{
RelativeSpacing = 0.05f
};
campaignSetupUI.StartButton.RectTransform.Parent = startButtonContainer.RectTransform;
campaignSetupUI.StartButton.RectTransform.MinSize = new Point(
(int)(campaignSetupUI.StartButton.TextBlock.TextSize.X * 1.5f),
campaignSetupUI.StartButton.RectTransform.MinSize.Y);
startButtonContainer.RectTransform.MinSize = new Point(0, campaignSetupUI.StartButton.RectTransform.MinSize.Y);
if (campaignSetupUI.EnableRadiationToggle != null)
if (campaignSetupUI.CampaignCustomizeButton != null)
{
campaignSetupUI.EnableRadiationToggle.RectTransform.Parent = startButtonContainer.RectTransform;
campaignSetupUI.CampaignCustomizeButton.RectTransform.Parent = startButtonContainer.RectTransform;
}
campaignSetupUI.InitialMoneyText.RectTransform.Parent = startButtonContainer.RectTransform;
}
@@ -1322,8 +1322,18 @@ namespace Barotrauma
};
maxPlayersBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform), textAlignment: Alignment.Center)
{
Text = maxPlayers.ToString(),
CanBeFocused = false
Text = maxPlayers.ToString()
};
maxPlayersBox.OnEnterPressed += (GUITextBox sender, string text) =>
{
maxPlayersBox.Deselect();
return true;
};
maxPlayersBox.OnDeselected += (GUITextBox sender, Microsoft.Xna.Framework.Input.Keys key) =>
{
int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers, 1, NetConfig.MaxPlayers);
maxPlayersBox.Text = currMaxPlayers.ToString();
};
new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton", textAlignment: Alignment.Center)
{

View File

@@ -45,6 +45,10 @@ namespace Barotrauma
private readonly GUITickBox radiationEnabledTickBox;
private readonly GUIButton[] maxMissionCountButtons;
private readonly GUITextBlock maxMissionCountText;
private readonly GUITextBlock maxMissionCountDescription;
private readonly GUIButton[] traitorProbabilityButtons;
private readonly GUITextBlock traitorProbabilityText;
@@ -929,7 +933,7 @@ namespace Barotrauma
}
};
QuitCampaignButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform),
TextManager.Get("pausemenusavequit"), textAlignment: Alignment.Center)
TextManager.Get("quitbutton"), textAlignment: Alignment.Center)
{
OnClicked = (_, __) =>
{
@@ -969,6 +973,16 @@ namespace Barotrauma
UserData = missionType,
};
if (MissionPrefab.HiddenMissionClasses.Contains(missionType))
{
missionTypeTickBoxes[index] = new GUITickBox(new RectTransform(Vector2.One, frame.RectTransform), string.Empty)
{
UserData = (int)missionType,
Visible = false,
CanBeFocused = false
};
continue;
}
missionTypeTickBoxes[index] = new GUITickBox(new RectTransform(Vector2.One, frame.RectTransform),
TextManager.Get("MissionType." + missionType.ToString()))
{
@@ -1148,6 +1162,31 @@ namespace Barotrauma
};
}
var maxMissionCountSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), settingsContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
maxMissionCountDescription = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), maxMissionCountSettingHolder.RectTransform), TextManager.Get("maxmissioncount", fallBackTag: "missions"), wrap: true)
{
ToolTip = TextManager.Get("maxmissioncounttooltip")
};
var maxMissionCountContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), maxMissionCountSettingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, Stretch = true };
maxMissionCountButtons = new GUIButton[2];
maxMissionCountButtons[0] = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleLeft")
{
OnClicked = (button, obj) =>
{
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, maxMissionCount: -1);
return true;
}
};
maxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), "0", textAlignment: Alignment.Center, style: "GUITextBox");
maxMissionCountButtons[1] = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleRight")
{
OnClicked = (button, obj) =>
{
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, maxMissionCount: 1);
return true;
}
};
maxMissionCountSettingHolder.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip);
List<GUIComponent> settingsElements = settingsContent.Children.ToList();
for (int i = 0; i < settingsElements.Count; i++)
@@ -1301,6 +1340,13 @@ namespace Barotrauma
{
radiationEnabledTickBox.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
}
maxMissionCountDescription.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
maxMissionCountText.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
foreach (var button in maxMissionCountButtons)
{
button.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
}
traitorProbabilityButtons[0].Enabled = traitorProbabilityButtons[1].Enabled = traitorProbabilityText.Enabled =
!CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
botCountButtons[0].Enabled = botCountButtons[1].Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
@@ -1322,7 +1368,7 @@ namespace Barotrauma
roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible);
roundControlsHolder.Recalculate();
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted && SelectedMode != GameModePreset.MultiPlayerCampaign;
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted;
RefreshGameModeContent();
}
@@ -3214,7 +3260,12 @@ namespace Barotrauma
{
for (int i = 0; i < missionTypeTickBoxes.Length; i++)
{
MissionType missionType = (MissionType)((int)missionTypeTickBoxes[i].UserData);
MissionType missionType = (MissionType)(int)missionTypeTickBoxes[i].UserData;
if (MissionPrefab.HiddenMissionClasses.Contains(missionType))
{
missionTypeTickBoxes[i].Parent.Visible = false;
continue;
}
if (SelectedMode == GameModePreset.Mission)
{
missionTypeTickBoxes[i].Parent.Visible = MissionPrefab.CoOpMissionClasses.ContainsKey(missionType);
@@ -3269,7 +3320,7 @@ namespace Barotrauma
CampaignFrame.Visible = CampaignSetupFrame.Visible = false;
}
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted && SelectedMode != GameModePreset.MultiPlayerCampaign;
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted;
StartButton.Visible =
GameMain.Client.HasPermission(ClientPermissions.ManageRound) &&

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Xml.Linq;
using System.Text;
using Barotrauma.Extensions;
using FarseerPhysics;
#if DEBUG
using System.IO;
using System.Xml;
@@ -15,57 +16,8 @@ using Barotrauma.IO;
namespace Barotrauma
{
class ParticleEditorScreen : Screen
class ParticleEditorScreen : EditorScreen
{
class Emitter : ISerializableEntity
{
public float EmitTimer;
public float BurstTimer;
[Editable(), Serialize("0.0,360.0", false)]
public Vector2 AngleRange { get; private set; }
[Editable(), Serialize("0.0,0.0", false)]
public Vector2 VelocityRange { get; private set; }
[Editable(), Serialize("1.0,1.0", false)]
public Vector2 ScaleRange { get; private set; }
[Editable(), Serialize(0, false)]
public int ParticleBurstAmount { get; private set; }
[Editable(), Serialize(1.0f, false)]
public float ParticleBurstInterval { get; private set; }
[Editable(), Serialize(1.0f, false)]
public float ParticlesPerSecond { get; private set; }
public string Name
{
get
{
return TextManager.Get("particleeditor.emitter");
}
}
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
}
public Emitter()
{
ScaleRange = Vector2.One;
AngleRange = new Vector2(0.0f, 360.0f);
ParticleBurstAmount = 1;
ParticleBurstInterval = 1.0f;
SerializableProperties = SerializableProperty.GetProperties(this);
}
}
private GUIComponent rightPanel, leftPanel;
private GUIListBox prefabList;
private GUITextBox filterBox;
@@ -73,23 +25,38 @@ namespace Barotrauma
private ParticlePrefab selectedPrefab;
private Emitter emitter;
private readonly ParticleEmitterProperties emitterProperties = new ParticleEmitterProperties(null)
{
ScaleMax = 1f,
ScaleMin = 1f,
AngleMax = 360f,
AngleMin = 0,
ParticlesPerSecond = 1f
};
private ParticleEmitterPrefab emitterPrefab;
private ParticleEmitter emitter;
private readonly Camera cam;
public override Camera Cam
{
get
{
return cam;
}
}
public override Camera Cam => cam;
private const string sizeRefFilePath = "Content/size_reference.png";
private readonly Texture2D sizeReference;
private Vector2 sizeRefPosition = Vector2.Zero;
private readonly Vector2 sizeRefOrigin;
private bool sizeRefEnabled;
public ParticleEditorScreen()
{
cam = new Camera();
GameMain.Instance.ResolutionChanged += CreateUI;
CreateUI();
if (File.Exists(sizeRefFilePath))
{
sizeReference = TextureLoader.FromFile(sizeRefFilePath, compress: false);
sizeRefOrigin = new Vector2(sizeReference.Width / 2f, sizeReference.Height / 2f);
}
}
private void CreateUI()
@@ -122,8 +89,8 @@ namespace Barotrauma
}
};
var serializeToClipBoardButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.03f), paddedRightPanel.RectTransform),
TextManager.Get("editor.copytoclipboard"))
new GUIButton(new RectTransform(new Vector2(1.0f, 0.03f), paddedRightPanel.RectTransform),
TextManager.Get("ParticleEditor.CopyPrefabToClipboard"))
{
OnClicked = (btn, obj) =>
{
@@ -132,11 +99,18 @@ namespace Barotrauma
}
};
emitter = new Emitter();
var emitterEditorContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), paddedRightPanel.RectTransform), style: null);
var emitterEditor = new SerializableEntityEditor(emitterEditorContainer.RectTransform, emitter, false, true, elementHeight: 20, titleFont: GUI.SubHeadingFont);
emitterEditor.RectTransform.RelativeSize = Vector2.One;
emitterEditorContainer.RectTransform.Resize(new Point(emitterEditorContainer.RectTransform.NonScaledSize.X, emitterEditor.ContentHeight), false);
new GUIButton(new RectTransform(new Vector2(1.0f, 0.03f), paddedRightPanel.RectTransform),
TextManager.Get("ParticleEditor.CopyEmitterToClipboard"))
{
OnClicked = (btn, obj) =>
{
SerializeEmitterToClipboard();
return true;
}
};
var emitterListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.25f), paddedRightPanel.RectTransform));
new SerializableEntityEditor(emitterListBox.Content.RectTransform, emitterProperties, false, true, elementHeight: 20, titleFont: GUI.SubHeadingFont);
var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.6f), paddedRightPanel.RectTransform));
@@ -157,7 +131,10 @@ namespace Barotrauma
prefabList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), paddedLeftPanel.RectTransform));
prefabList.OnSelected += (GUIComponent component, object obj) =>
{
cam.Position = Vector2.Zero;
selectedPrefab = obj as ParticlePrefab;
emitterPrefab = new ParticleEmitterPrefab(selectedPrefab, emitterProperties);
emitter = new ParticleEmitter(emitterPrefab);
listBox.ClearChildren();
new SerializableEntityEditor(listBox.Content.RectTransform, selectedPrefab, false, true, elementHeight: 20, titleFont: GUI.SubHeadingFont);
//listBox.Content.RectTransform.NonScaledSize = particlePrefabEditor.RectTransform.NonScaledSize;
@@ -198,19 +175,6 @@ namespace Barotrauma
}
}
private void Emit(Vector2 position)
{
float angle = MathHelper.ToRadians(Rand.Range(emitter.AngleRange.X, emitter.AngleRange.Y));
Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Rand.Range(emitter.VelocityRange.X, emitter.VelocityRange.Y);
var particle = GameMain.ParticleManager.CreateParticle(selectedPrefab, position, velocity, 0.0f);
if (particle != null)
{
particle.Size *= Rand.Range(emitter.ScaleRange.X, emitter.ScaleRange.Y);
}
}
private void FilterEmitters(string text)
{
if (string.IsNullOrWhiteSpace(text))
@@ -263,9 +227,34 @@ namespace Barotrauma
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
}
private void SerializeEmitterToClipboard()
{
XElement element = new XElement(nameof(ParticleEmitter));
if (selectedPrefab is { } prefab)
{
element.Add(new XAttribute("particle", prefab.Identifier));
}
SerializableProperty.SerializeProperties(emitterProperties, element, saveIfDefault: false, ignoreEditable: true);
StringBuilder sb = new StringBuilder();
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
{
OmitXmlDeclaration = true
};
using (var writer = System.Xml.XmlWriter.Create(sb, settings))
{
element.WriteTo(writer);
writer.Flush();
}
Clipboard.SetText(sb.ToString());
}
private void SerializeToClipboard(ParticlePrefab prefab)
{
#if WINDOWS
if (prefab == null) { return; }
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
@@ -308,43 +297,39 @@ namespace Barotrauma
}
Clipboard.SetText(sb.ToString());
#endif
}
public override void Update(double deltaTime)
{
cam.MoveCamera((float)deltaTime, allowMove: true, allowZoom: GUI.MouseOn == null);
if (selectedPrefab != null)
if (GUI.MouseOn is null && PlayerInput.PrimaryMouseButtonHeld())
{
emitter.EmitTimer += (float)deltaTime;
emitter.BurstTimer += (float)deltaTime;
sizeRefPosition = cam.ScreenToWorld(PlayerInput.MousePosition);
}
if (PlayerInput.SecondaryMouseButtonClicked())
{
CreateContextMenu();
}
if (emitter.ParticlesPerSecond > 0)
{
float emitInterval = 1.0f / emitter.ParticlesPerSecond;
while (emitter.EmitTimer > emitInterval)
{
Emit(Vector2.Zero);
emitter.EmitTimer -= emitInterval;
}
}
if (emitter.BurstTimer > emitter.ParticleBurstInterval)
{
for (int i = 0; i < emitter.ParticleBurstAmount; i++)
{
Emit(Vector2.Zero);
}
emitter.BurstTimer = 0.0f;
}
if (selectedPrefab != null && emitter != null)
{
emitter.Emit((float) deltaTime, Vector2.Zero);
}
GameMain.ParticleManager.Update((float)deltaTime);
}
private void CreateContextMenu()
{
GUIContextMenu.CreateContextMenu
(
new ContextMenuOption("subeditor.editbackgroundcolor", true, CreateBackgroundColorPicker),
new ContextMenuOption("editor.togglereferencecharacter", true, delegate { sizeRefEnabled = !sizeRefEnabled; })
);
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{
cam.UpdateTransform();
@@ -357,7 +342,7 @@ namespace Barotrauma
null, null, null, null,
cam.Transform);
graphics.Clear(new Color(0.051f, 0.149f, 0.271f, 1.0f));
graphics.Clear(BackgroundColor);
GameMain.ParticleManager.Draw(spriteBatch, false, false, ParticleBlendState.AlphaBlend);
GameMain.ParticleManager.Draw(spriteBatch, true, false, ParticleBlendState.AlphaBlend);
@@ -374,6 +359,20 @@ namespace Barotrauma
spriteBatch.End();
if (sizeRefEnabled && !(sizeReference is null))
{
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
null, null, null, null,
cam.Transform);
Vector2 pos = sizeRefPosition;
pos.Y = -pos.Y;
spriteBatch.Draw(sizeReference, pos, null, Color.White, 0f, sizeRefOrigin, new Vector2(0.4f), SpriteEffects.None, 0f);
spriteBatch.End();
}
//-------------------------------------------------------
spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);

View File

@@ -66,6 +66,8 @@ namespace Barotrauma
yield return CoroutineStatus.Success;
}
public virtual void OnFileDropped(string filePath, string extension) { }
public virtual void Release()
{
frame.RectTransform.Parent = null;

View File

@@ -363,7 +363,10 @@ namespace Barotrauma
"VineSprite",
"LeafSprite",
"FlowerSprite",
"DecorativeSprite"
"DecorativeSprite",
"BarrelSprite",
"RailSprite",
"SchematicSprite"
};
foreach (string spriteElementName in spriteElementNames)

View File

@@ -26,6 +26,8 @@ namespace Barotrauma
//listbox that shows the files included in the item being created
private GUIListBox createItemFileList;
private GUIImage previewIcon;
private System.IO.FileSystemWatcher createItemWatcher;
private readonly List<GUIButton> tabButtons = new List<GUIButton>();
@@ -277,6 +279,24 @@ namespace Barotrauma
SelectTab(Tab.Mods);
}
public override void OnFileDropped(string filePath, string extension)
{
switch (extension)
{
case ".png": // workshop preview
case ".jpg":
case ".jpeg":
if (previewIcon == null || itemContentPackage == null) { break; }
OnPreviewImageSelected(previewIcon, filePath);
break;
default:
DebugConsole.ThrowError($"Could not drag and drop the file. \"{extension}\" is not a valid file extension! (expected .png, .jpg or .jpeg)");
break;
}
}
private void OnItemInstalled(ulong itemId)
{
RefreshSubscribedItems();
@@ -1263,7 +1283,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), topLeftColumn.RectTransform), TextManager.Get("WorkshopItemPreviewImage"), font: GUI.SubHeadingFont);
var previewIcon = new GUIImage(new RectTransform(new Vector2(1.0f, 0.7f), topLeftColumn.RectTransform), SteamManager.DefaultPreviewImage, scaleToFit: true);
previewIcon = new GUIImage(new RectTransform(new Vector2(1.0f, 0.7f), topLeftColumn.RectTransform), SteamManager.DefaultPreviewImage, scaleToFit: true);
new GUIButton(new RectTransform(new Vector2(1.0f, 0.2f), topLeftColumn.RectTransform), TextManager.Get("WorkshopItemBrowse"), style: "GUIButtonSmall")
{
OnClicked = (btn, userdata) =>

View File

@@ -19,7 +19,7 @@ using Barotrauma.IO;
namespace Barotrauma
{
class SubEditorScreen : Screen
class SubEditorScreen : EditorScreen
{
private static readonly string[] crewExperienceLevels =
{
@@ -177,8 +177,6 @@ namespace Barotrauma
private Mode mode;
private Color backgroundColor = GameSettings.SubEditorBackgroundColor;
private Vector2 MeasurePositionStart = Vector2.Zero;
// Prevent the mode from changing
@@ -1007,6 +1005,12 @@ namespace Barotrauma
string name = legacy ? TextManager.GetWithVariable("legacyitemformat", "[name]", ep.Name) : ep.Name;
frame.ToolTip = string.IsNullOrEmpty(ep.Description) ? name : name + '\n' + ep.Description;
if (ep.ContentPackage != GameMain.VanillaContent && ep.ContentPackage != null)
{
frame.Color = Color.Magenta;
string colorStr = XMLExtensions.ColorToString(Color.MediumPurple);
frame.ToolTip += $"\n‖color:{colorStr}‖{ep.ContentPackage?.Name}‖color:end‖";
}
if (ep.HideInMenus)
{
frame.Color = Color.Red;
@@ -1225,6 +1229,50 @@ namespace Barotrauma
}
}
public override void OnFileDropped(string filePath, string extension)
{
switch (extension)
{
case ".sub": // Submarine
SubmarineInfo info = new SubmarineInfo(filePath);
if (info.IsFileCorrupted)
{
DebugConsole.ThrowError($"Could not drag and drop the file. File \"{filePath}\" is corrupted!");
info.Dispose();
return;
}
string body = TextManager.GetWithVariable("SubEditor.LoadConfirmBody", "[submarine]", info.Name);
GUI.AskForConfirmation(TextManager.Get("Load"), body, onConfirm: () => LoadSub(info), onDeny: () => info.Dispose());
break;
case ".xml": // Item Assembly
string text = File.ReadAllText(filePath);
// PlayerInput.MousePosition doesn't update while the window is not active so we need to use this method
Vector2 mousePos = Mouse.GetState().Position.ToVector2();
PasteAssembly(text, cam.ScreenToWorld(mousePos));
break;
case ".png": // submarine preview
case ".jpg":
case ".jpeg":
if (saveFrame == null) { break; }
Texture2D texture = Sprite.LoadTexture(filePath);
previewImage.Sprite = new Sprite(texture, null, null);
if (Submarine.MainSub != null)
{
Submarine.MainSub.Info.PreviewImage = previewImage.Sprite;
}
break;
default:
DebugConsole.ThrowError($"Could not drag and drop the file. \"{extension}\" is not a valid file extension! (expected .xml, .sub, .png or .jpg)");
break;
}
}
/// <summary>
/// Coroutine that waits 5 minutes and then runs itself recursively again to save the submarine into a temporary file
/// </summary>
@@ -1466,6 +1514,9 @@ namespace Barotrauma
case SubmarineType.Wreck:
contentType = ContentType.Wreck;
break;
case SubmarineType.EnemySubmarine:
contentType = ContentType.EnemySubmarine;
break;
}
if (contentType != ContentType.Submarine)
{
@@ -1504,8 +1555,13 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(specialSavePath) &&
(string.IsNullOrEmpty(Submarine.MainSub?.Info.FilePath) || Path.GetFileNameWithoutExtension(Submarine.MainSub.Info.Name) != nameBox.Text || Path.GetDirectoryName(Submarine.MainSub?.Info.FilePath) != specialSavePath))
{
string submarineTypeTag = "SubmarineType." + Submarine.MainSub.Info.Type;
if (Submarine.MainSub.Info.Type == SubmarineType.EnemySubmarine && !TextManager.ContainsTag(submarineTypeTag))
{
submarineTypeTag = "MissionType.Pirate";
}
var msgBox = new GUIMessageBox("", TextManager.GetWithVariables("savesubtospecialfolderprompt",
new string[] { "[type]", "[outpostpath]" }, new string[] { TextManager.Get("submarinetype." + Submarine.MainSub.Info.Type), specialSavePath }),
new string[] { "[type]", "[outpostpath]" }, new string[] { TextManager.Get(submarineTypeTag), specialSavePath }),
new string[] { TextManager.Get("yes"), TextManager.Get("no") });
msgBox.Buttons[0].OnClicked = (bt, userdata) =>
{
@@ -1781,7 +1837,12 @@ namespace Barotrauma
subTypeContainer.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
foreach (SubmarineType subType in Enum.GetValues(typeof(SubmarineType)))
{
subTypeDropdown.AddItem(TextManager.Get("submarinetype."+subType.ToString().ToLowerInvariant()), subType);
string textTag = "SubmarineType." + subType;
if (subType == SubmarineType.EnemySubmarine && !TextManager.ContainsTag(textTag))
{
textTag = "MissionType.Pirate";
}
subTypeDropdown.AddItem(TextManager.Get(textTag), subType);
}
//---------------------------------------
@@ -2020,30 +2081,27 @@ namespace Barotrauma
Submarine.MainSub.Info.Price = Math.Max(Submarine.MainSub.Info.Price, basePrice);
}
if (!Submarine.MainSub.Info.HasTag(SubmarineTag.Shuttle))
var classGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
{
var classGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
{
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), classGroup.RectTransform),
TextManager.Get("submarineclass"), textAlignment: Alignment.CenterLeft, wrap: true);
GUIDropDown classDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), classGroup.RectTransform));
classDropDown.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
classDropDown.AddItem(TextManager.Get("submarineclass.undefined"), SubmarineClass.Undefined);
classDropDown.AddItem(TextManager.Get("submarineclass.scout"), SubmarineClass.Scout);
classDropDown.AddItem(TextManager.Get("submarineclass.attack"), SubmarineClass.Attack);
classDropDown.AddItem(TextManager.Get("submarineclass.transport"), SubmarineClass.Transport);
classDropDown.AddItem(TextManager.Get("submarineclass.deepdiver"), SubmarineClass.DeepDiver);
classDropDown.OnSelected += (selected, userdata) =>
{
SubmarineClass submarineClass = (SubmarineClass)userdata;
Submarine.MainSub.Info.SubmarineClass = submarineClass;
return true;
};
classDropDown.SelectItem(Submarine.MainSub.Info.SubmarineClass);
}
Stretch = true
};
var classText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), classGroup.RectTransform),
TextManager.Get("submarineclass"), textAlignment: Alignment.CenterLeft, wrap: true);
GUIDropDown classDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), classGroup.RectTransform));
classDropDown.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
classDropDown.AddItem(TextManager.Get("submarineclass.undefined"), SubmarineClass.Undefined);
classDropDown.AddItem(TextManager.Get("submarineclass.scout"), SubmarineClass.Scout);
classDropDown.AddItem(TextManager.Get("submarineclass.attack"), SubmarineClass.Attack);
classDropDown.AddItem(TextManager.Get("submarineclass.transport"), SubmarineClass.Transport);
classDropDown.AddItem(TextManager.Get("submarineclass.deepdiver"), SubmarineClass.DeepDiver);
classDropDown.OnSelected += (selected, userdata) =>
{
SubmarineClass submarineClass = (SubmarineClass)userdata;
Submarine.MainSub.Info.SubmarineClass = submarineClass;
return true;
};
classDropDown.SelectItem(Submarine.MainSub.Info.SubmarineClass);
classText.Enabled = classDropDown.ButtonEnabled = !Submarine.MainSub.Info.HasTag(SubmarineTag.Shuttle);
var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
{
@@ -2216,17 +2274,29 @@ namespace Barotrauma
{
Selected = Submarine.MainSub != null && Submarine.MainSub.Info.HasTag(tag),
UserData = tag,
OnSelected = (GUITickBox tickBox) =>
{
if (Submarine.MainSub == null) return false;
SubmarineTag tag = (SubmarineTag)tickBox.UserData;
if (tag == SubmarineTag.Shuttle)
{
if (tickBox.Selected)
{
classDropDown.SelectItem(SubmarineClass.Undefined);
}
else
{
classDropDown.SelectItem(Submarine.MainSub.Info.SubmarineClass);
}
classText.Enabled = classDropDown.ButtonEnabled = !tickBox.Selected;
}
if (tickBox.Selected)
{
Submarine.MainSub.Info.AddTag((SubmarineTag)tickBox.UserData);
Submarine.MainSub.Info.AddTag(tag);
}
else
{
Submarine.MainSub.Info.RemoveTag((SubmarineTag)tickBox.UserData);
Submarine.MainSub.Info.RemoveTag(tag);
}
return true;
}
@@ -2305,7 +2375,6 @@ namespace Barotrauma
if (quickSave) { SaveSub(saveButton, saveButton.UserData); }
}
private void CreateSaveAssemblyScreen()
{
SetMode(Mode.Default);
@@ -2317,10 +2386,10 @@ namespace Barotrauma
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, saveFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.25f, 0.3f), saveFrame.RectTransform, Anchor.Center) { MinSize = new Point(400, 300) });
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.25f, 0.35f), saveFrame.RectTransform, Anchor.Center) { MinSize = new Point(400, 350) });
GUILayoutGroup paddedSaveFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), innerFrame.RectTransform, Anchor.Center))
{
AbsoluteSpacing = 5,
AbsoluteSpacing = GUI.IntScale(5),
Stretch = true
};
@@ -2337,15 +2406,22 @@ namespace Barotrauma
};
#endif
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedSaveFrame.RectTransform),
TextManager.Get("SaveItemAssemblyDialogDescription"));
descriptionBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), paddedSaveFrame.RectTransform))
var descriptionContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), paddedSaveFrame.RectTransform));
descriptionBox = new GUITextBox(new RectTransform(Vector2.One, descriptionContainer.Content.RectTransform, Anchor.TopLeft),
font: GUI.SmallFont, style: "GUITextBoxNoBorder", wrap: true, textAlignment: Alignment.TopLeft)
{
UserData = "description",
Wrap = true,
Text = ""
Padding = new Vector4(10 * GUI.Scale)
};
descriptionBox.OnTextChanged += (textBox, text) =>
{
Vector2 textSize = textBox.Font.MeasureString(descriptionBox.WrappedText);
textBox.RectTransform.NonScaledSize = new Point(textBox.RectTransform.NonScaledSize.X, Math.Max(descriptionContainer.Content.Rect.Height, (int)textSize.Y + 10));
descriptionContainer.UpdateScrollBarSize();
descriptionContainer.BarScroll = 1.0f;
return true;
};
var buttonArea = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), paddedSaveFrame.RectTransform), style: null);
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform, Anchor.BottomLeft),
TextManager.Get("Cancel"))
@@ -2361,6 +2437,7 @@ namespace Barotrauma
{
OnClicked = SaveAssembly
};
buttonArea.RectTransform.MinSize = new Point(0, buttonArea.Children.First().RectTransform.MinSize.Y);
}
/// <summary>
@@ -2404,7 +2481,7 @@ namespace Barotrauma
}
}
bool hideInMenus = !(nameBox.Parent.GetChildByUserData("hideinmenus") is GUITickBox hideInMenusTickBox) ? false : hideInMenusTickBox.Selected;
bool hideInMenus = nameBox.Parent.GetChildByUserData("hideinmenus") is GUITickBox hideInMenusTickBox && hideInMenusTickBox.Selected;
#if DEBUG
string saveFolder = ItemAssemblyPrefab.VanillaSaveFolder;
#else
@@ -2423,7 +2500,6 @@ namespace Barotrauma
}
#endif
string filePath = Path.Combine(saveFolder, nameBox.Text + ".xml");
if (File.Exists(filePath))
{
var msgBox = new GUIMessageBox(TextManager.Get("Warning"), TextManager.Get("ItemAssemblyFileExistsWarning"), new[] { TextManager.Get("Yes"), TextManager.Get("No") });
@@ -2438,18 +2514,27 @@ namespace Barotrauma
}
else
{
Save();
var identifier = nameBox.Text.ToLowerInvariant().Replace(" ", "");
var existingPrefab = MapEntityPrefab.Find(null, identifier, showErrorMessages: false);
if (existingPrefab != null && System.IO.Path.GetDirectoryName(existingPrefab.FilePath) == ItemAssemblyPrefab.VanillaSaveFolder)
{
var msgBox = new GUIMessageBox(TextManager.Get("Warning"), TextManager.Get("ItemAssemblyVanillaFileExistsWarning"));
}
else
{
Save();
}
}
void Save()
{
XDocument doc = new XDocument(ItemAssemblyPrefab.Save(MapEntity.SelectedList, nameBox.Text, descriptionBox.Text, hideInMenus));
XDocument doc = new XDocument(ItemAssemblyPrefab.Save(MapEntity.SelectedList.ToList(), nameBox.Text, descriptionBox.Text, hideInMenus));
#if DEBUG
doc.Save(filePath);
#else
doc.SaveSafe(filePath);
#endif
new ItemAssemblyPrefab(filePath);
new ItemAssemblyPrefab(filePath, allowOverwrite: true);
UpdateEntityList();
}
@@ -2519,8 +2604,13 @@ namespace Barotrauma
{
if (prevSub == null || prevSub.Type != sub.Type)
{
string textTag = "SubmarineType." + sub.Type;
if (sub.Type == SubmarineType.EnemySubmarine && !TextManager.ContainsTag(textTag))
{
textTag = "MissionType.Pirate";
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), subList.Content.RectTransform) { MinSize = new Point(0, 35) },
TextManager.Get("SubmarineType." + sub.Type), font: GUI.LargeFont, textAlignment: Alignment.Center, style: "ListBoxElement")
TextManager.Get(textTag), font: GUI.LargeFont, textAlignment: Alignment.Center, style: "ListBoxElement")
{
CanBeFocused = false
};
@@ -2712,8 +2802,15 @@ namespace Barotrauma
if (subList.SelectedComponent == null) { return false; }
if (!(subList.SelectedComponent.UserData is SubmarineInfo selectedSubInfo)) { return false; }
LoadSub(selectedSubInfo);
return true;
}
public void LoadSub(SubmarineInfo info)
{
Submarine.Unload();
var selectedSub = new Submarine(selectedSubInfo);
var selectedSub = new Submarine(info);
Submarine.MainSub = selectedSub;
Submarine.MainSub.UpdateTransform(interpolate: false);
ClearUndoBuffer();
@@ -2744,8 +2841,6 @@ namespace Barotrauma
};
adjustLightsPrompt.Buttons[1].OnClicked += adjustLightsPrompt.Close;
}
return true;
}
private void TryDeleteSub(SubmarineInfo sub)
@@ -2822,7 +2917,7 @@ namespace Barotrauma
}
}
if (!string.IsNullOrEmpty(entityFilterBox.Text) || dummyCharacter?.SelectedConstruction?.OwnInventory != null)
if (!string.IsNullOrEmpty(entityFilterBox.Text))
{
FilterEntities(entityFilterBox.Text);
}
@@ -2835,7 +2930,7 @@ namespace Barotrauma
private void FilterEntities(string filter)
{
if (string.IsNullOrWhiteSpace(filter) && dummyCharacter?.SelectedConstruction?.OwnInventory == null)
if (string.IsNullOrWhiteSpace(filter))
{
allEntityList.Visible = false;
categorizedEntityList.Visible = true;
@@ -2862,11 +2957,7 @@ namespace Barotrauma
{
child.Visible =
(!selectedCategory.HasValue || ((MapEntityPrefab)child.UserData).Category.HasFlag(selectedCategory)) &&
((MapEntityPrefab)child.UserData).Name.ToLower().Contains(filter); ;
if (child.Visible && dummyCharacter?.SelectedConstruction?.OwnInventory != null)
{
child.Visible = child.UserData is MapEntityPrefab item && IsItemPrefab(item);
}
((MapEntityPrefab)child.UserData).Name.ToLower().Contains(filter);
}
allEntityList.UpdateScrollBarSize();
allEntityList.BarScroll = 0.0f;
@@ -2942,11 +3033,14 @@ namespace Barotrauma
new ContextMenuOption("SubEditor.EditBackgroundColor", isEnabled: true, onSelected: CreateBackgroundColorPicker),
new ContextMenuOption("SubEditor.ToggleTransparency", isEnabled: true, onSelected: () => TransparentWiringMode = !TransparentWiringMode),
new ContextMenuOption("SubEditor.ToggleGrid", isEnabled: true, onSelected: () => ShouldDrawGrid = !ShouldDrawGrid),
new ContextMenuOption("SubEditor.PasteAssembly", isEnabled: true, PasteAssembly),
new ContextMenuOption("SubEditor.PasteAssembly", isEnabled: true, () => PasteAssembly()),
new ContextMenuOption("Editor.SelectSame", isEnabled: targets.Count > 0, onSelected: delegate
{
IEnumerable<MapEntity> matching = MapEntity.mapEntityList.Where(e => e.prefab != null && targets.Any(t => t.prefab?.Identifier == e.prefab.Identifier) && !MapEntity.SelectedList.Contains(e));
MapEntity.SelectedList.AddRange(matching);
foreach (MapEntity match in MapEntity.mapEntityList.Where(e => e.prefab != null && targets.Any(t => t.prefab?.Identifier == e.prefab.Identifier) && !MapEntity.SelectedList.Contains(e)))
{
if (MapEntity.SelectedList.Contains(match)) { continue; }
MapEntity.SelectedList.Add(match);
}
}),
new ContextMenuOption("SubEditor.AddImage", isEnabled: true, onSelected: ImageManager.CreateImageWizard),
new ContextMenuOption("SubEditor.ToggleImageEditing", isEnabled: true, onSelected: delegate
@@ -2973,10 +3067,11 @@ namespace Barotrauma
}
}
private void PasteAssembly()
private void PasteAssembly(string text = null, Vector2? pos = null)
{
string clipboard = Clipboard.GetText();
if (string.IsNullOrWhiteSpace(clipboard))
pos ??= cam.ScreenToWorld(PlayerInput.MousePosition);
text ??= Clipboard.GetText();
if (string.IsNullOrWhiteSpace(text))
{
DebugConsole.ThrowError("Unable to paste assembly: Clipboard content is empty.");
return;
@@ -2986,7 +3081,7 @@ namespace Barotrauma
try
{
element = XDocument.Parse(clipboard).Root;
element = XDocument.Parse(text).Root;
}
catch (Exception) { /* ignored */ }
@@ -2996,12 +3091,11 @@ namespace Barotrauma
return;
}
Vector2 pos = cam.ScreenToWorld(PlayerInput.MousePosition);
Submarine sub = Submarine.MainSub;
List<MapEntity> entities;
try
{
entities = ItemAssemblyPrefab.PasteEntities(pos, sub, element, selectInstance: true);
entities = ItemAssemblyPrefab.PasteEntities(pos.Value, sub, element, selectInstance: true);
}
catch (Exception e)
{
@@ -3143,6 +3237,8 @@ namespace Barotrauma
Color newColor = SetColor(null);
if (!IsSubEditor()) { return true; }
Dictionary<object, List<ISerializableEntity>> oldProperties = new Dictionary<object, List<ISerializableEntity>>();
foreach (var (sEntity, color, _) in entities)
@@ -3274,57 +3370,6 @@ namespace Barotrauma
static string ColorToHex(Color color) => $"#{(color.R << 16 | color.G << 8 | color.B):X6}";
}
/// <summary>
/// Creates a color picker that can be used to change the submarine editor's background color
/// </summary>
private void CreateBackgroundColorPicker()
{
var msgBox = new GUIMessageBox(TextManager.Get("CharacterEditor.EditBackgroundColor"), "", new[] { TextManager.Get("Reset"), TextManager.Get("OK")}, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
var rgbLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal: true);
// Generate R,G,B labels and parent elements
var layoutParents = new GUILayoutGroup[3];
for (int i = 0; i < 3; i++)
{
var colorContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1), rgbLayout.RectTransform), isHorizontal: true) { Stretch = true };
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), colorContainer.RectTransform, Anchor.CenterLeft) { MinSize = new Point(15, 0) }, GUI.colorComponentLabels[i], font: GUI.SmallFont, textAlignment: Alignment.Center);
layoutParents[i] = colorContainer;
}
// attach number inputs to our generated parent elements
var rInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[0].RectTransform), GUINumberInput.NumberType.Int) { IntValue = backgroundColor.R };
var gInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[1].RectTransform), GUINumberInput.NumberType.Int) { IntValue = backgroundColor.G };
var bInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[2].RectTransform), GUINumberInput.NumberType.Int) { IntValue = backgroundColor.B };
rInput.MinValueInt = gInput.MinValueInt = bInput.MinValueInt = 0;
rInput.MaxValueInt = gInput.MaxValueInt = bInput.MaxValueInt = 255;
rInput.OnValueChanged = gInput.OnValueChanged = bInput.OnValueChanged = delegate
{
var color = new Color(rInput.IntValue, gInput.IntValue, bInput.IntValue);
backgroundColor = color;
GameSettings.SubEditorBackgroundColor = color;
};
// Reset button
msgBox.Buttons[0].OnClicked = (button, o) =>
{
rInput.IntValue = 13;
gInput.IntValue = 37;
bInput.IntValue = 69;
return true;
};
// Ok button
msgBox.Buttons[1].OnClicked = (button, o) =>
{
msgBox.Close();
GameMain.Config.SaveNewPlayerConfig();
return true;
};
}
private GUIFrame CreateWiringPanel()
{
@@ -3495,27 +3540,7 @@ namespace Barotrauma
submarineDescriptionCharacterCount.Text = text.Length + " / " + submarineDescriptionLimit;
}
/// <summary>
/// Checks if the prefab is an item or if it only consists of items
/// </summary>
/// <param name="mapPrefab">The prefab to check</param>
/// <returns>True if the the prefab is an item or it contains only items</returns>
private bool IsItemPrefab(MapEntityPrefab mapPrefab)
{
if (dummyCharacter?.SelectedConstruction == null)
{
return false;
}
return mapPrefab switch
{
ItemPrefab iPrefab => true,
ItemAssemblyPrefab aPrefab => aPrefab.DisplayEntities.All(pair => pair.First is ItemPrefab),
_ => false
};
}
private bool SelectPrefab(GUIComponent component, object obj)
{
allEntityList.Deselect();
@@ -3983,9 +4008,9 @@ namespace Barotrauma
{
loadFrame.AddToGUIUpdateList();
}
else if (saveFrame != null)
else
{
saveFrame.AddToGUIUpdateList();
saveFrame?.AddToGUIUpdateList();
}
}
@@ -4728,9 +4753,9 @@ namespace Barotrauma
CloseItem();
}
}
else if (MapEntity.SelectedList.Count == 1 && WiringMode)
else if (MapEntity.SelectedList.Count == 1 && WiringMode && MapEntity.SelectedList.FirstOrDefault() is Item item)
{
(MapEntity.SelectedList[0] as Item)?.UpdateHUD(cam, dummyCharacter, (float)deltaTime);
item.UpdateHUD(cam, dummyCharacter, (float)deltaTime);
}
CharacterHUD.Update((float)deltaTime, dummyCharacter, cam);
@@ -4753,7 +4778,7 @@ namespace Barotrauma
sub.UpdateTransform();
}
graphics.Clear(backgroundColor);
graphics.Clear(BackgroundColor);
ImageManager.Draw(spriteBatch, cam);

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