Unstable 0.1400.1.0
This commit is contained in:
@@ -257,5 +257,7 @@
|
||||
<OutpostModule file="Content/Map/Outposts/MineModule_04.sub" />
|
||||
<OutpostModule file="Content/Map/Outposts/HallModuleHorizontal_Abandoned.sub" />
|
||||
<OutpostModule file="Content/Map/Outposts/HallModuleVertical_Abandoned.sub" />
|
||||
<EnemySubmarine file="Content/Map/EnemySubmarines/DugongPirate.sub"/>
|
||||
<EnemySubmarine file="Content/Map/EnemySubmarines/HumpbackPirate.sub"/>
|
||||
<EnemySubmarine file="Content/Map/EnemySubmarines/Typhon2Pirate.sub"/>
|
||||
</contentpackage>
|
||||
@@ -66,7 +66,7 @@ namespace Barotrauma
|
||||
|
||||
private IEnumerable<object> Update(ISpatialEntity targetEntity, Camera cam)
|
||||
{
|
||||
if (targetEntity == null) { yield return CoroutineStatus.Success; }
|
||||
if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; }
|
||||
|
||||
prevControlled = Character.Controlled;
|
||||
if (RemoveControlFromCharacter)
|
||||
|
||||
@@ -1475,7 +1475,9 @@ namespace Barotrauma
|
||||
Character thief = character;
|
||||
bool someoneSpoke = false;
|
||||
|
||||
if (item.SpawnedInOutpost && !item.AllowStealing && thief.TeamID != CharacterTeamType.FriendlyNPC && !item.HasTag("handlocker"))
|
||||
bool stolenItemsInside = item.OwnInventory?.FindAllItems(it => it.SpawnedInOutpost && !it.AllowStealing, recursive: true).Any() ?? false;
|
||||
|
||||
if ((item.SpawnedInOutpost && !item.AllowStealing || stolenItemsInside) && thief.TeamID != CharacterTeamType.FriendlyNPC && !item.HasTag("handlocker"))
|
||||
{
|
||||
foreach (Character otherCharacter in Character.CharacterList)
|
||||
{
|
||||
|
||||
@@ -56,8 +56,23 @@ namespace Barotrauma
|
||||
public float BasePriority { get; set; }
|
||||
public float PriorityModifier { get; private set; } = 1;
|
||||
|
||||
// For forcing the highest priority temporarily. Will reset after each priority calculation, so it will need to be kept alive by something.
|
||||
public bool ForceHighestPriority { get; set; }
|
||||
private float resetPriorityTimer;
|
||||
private readonly float resetPriorityTime = 1;
|
||||
private bool _forceHighestPriority;
|
||||
// For forcing the highest priority temporarily. Will reset automatically after one second, unless kept alive by something.
|
||||
public bool ForceHighestPriority
|
||||
{
|
||||
get { return _forceHighestPriority; }
|
||||
set
|
||||
{
|
||||
if (_forceHighestPriority == value) { return; }
|
||||
_forceHighestPriority = value;
|
||||
if (_forceHighestPriority)
|
||||
{
|
||||
resetPriorityTimer = resetPriorityTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For temporarily forcing walking. Will reset after each priority calculation, so it will need to be kept alive by something.
|
||||
// The intention of this boolean to allow walking even when the priority is higher than AIObjectiveManager.RunPriority.
|
||||
@@ -171,7 +186,6 @@ namespace Barotrauma
|
||||
Act(deltaTime);
|
||||
}
|
||||
|
||||
// TODO: check turret aioperate
|
||||
public void AddSubObjective(AIObjective objective, bool addFirst = false)
|
||||
{
|
||||
var type = objective.GetType();
|
||||
@@ -294,6 +308,14 @@ namespace Barotrauma
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
if (resetPriorityTimer > 0)
|
||||
{
|
||||
resetPriorityTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
ForceHighestPriority = false;
|
||||
}
|
||||
if (!objectiveManager.IsOrder(this) && objectiveManager.WaitTimer <= 0)
|
||||
{
|
||||
UpdateDevotion(deltaTime);
|
||||
|
||||
+5
-2
@@ -91,7 +91,7 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (container == null || (container.Item != null && container.Item.IsThisOrAnyContainerIgnoredByAI()))
|
||||
if (container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI())
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
@@ -146,7 +146,10 @@ namespace Barotrauma
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = container.Item.Name,
|
||||
AbortCondition = obj => !ItemToContain.IsOwnedBy(character),
|
||||
AbortCondition = obj =>
|
||||
container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI() ||
|
||||
ItemToContain == null || ItemToContain.Removed ||
|
||||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>()
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
|
||||
+5
-1
@@ -614,7 +614,11 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
|
||||
/// </summary>
|
||||
public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
|
||||
public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective
|
||||
{
|
||||
if (CurrentObjective == null) { return Enumerable.Empty<T>(); }
|
||||
return CurrentObjective.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
|
||||
}
|
||||
|
||||
public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ namespace Barotrauma
|
||||
Abandon = !isOrder;
|
||||
return Priority;
|
||||
}
|
||||
if (component.Item.ConditionPercentage <= 0)
|
||||
if (!isOrder && component.Item.ConditionPercentage <= 0)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
|
||||
+15
-2
@@ -1,6 +1,7 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
@@ -43,7 +44,6 @@ namespace Barotrauma
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
// TODO: priority list?
|
||||
// Ignore items that are being repaired by someone else.
|
||||
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character))
|
||||
{
|
||||
@@ -66,7 +66,20 @@ namespace Barotrauma
|
||||
float devotion = (CumulatedDevotion + selectedBonus) / 100;
|
||||
float reduction = isPriority ? 1 : isSelected ? 2 : 3;
|
||||
float max = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
float highestWeight = -1;
|
||||
foreach (string tag in Item.Prefab.Tags)
|
||||
{
|
||||
if (JobPrefab.ItemRepairPriorities.TryGetValue(tag, out float weight) && weight > highestWeight)
|
||||
{
|
||||
highestWeight = weight;
|
||||
}
|
||||
}
|
||||
if (highestWeight == -1)
|
||||
{
|
||||
// Predefined weight not found.
|
||||
highestWeight = 1;
|
||||
}
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * highestWeight * PriorityModifier), 0, 1));
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,8 @@ namespace Barotrauma
|
||||
//legacy support
|
||||
public readonly string[] AppropriateJobs;
|
||||
public readonly string[] Options;
|
||||
public readonly string[] HiddenOptions;
|
||||
public readonly string[] AllOptions;
|
||||
private readonly Dictionary<string, string> OptionNames;
|
||||
|
||||
public readonly Dictionary<string, Sprite> OptionSprites;
|
||||
@@ -310,6 +312,8 @@ namespace Barotrauma
|
||||
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
|
||||
AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]);
|
||||
Options = orderElement.GetAttributeStringArray("options", new string[0]);
|
||||
HiddenOptions = orderElement.GetAttributeStringArray("hiddenoptions", new string[0]);
|
||||
AllOptions = Options.Concat(HiddenOptions).ToArray();
|
||||
var category = orderElement.GetAttributeString("category", null);
|
||||
if (!string.IsNullOrWhiteSpace(category)) { this.Category = (OrderCategory)Enum.Parse(typeof(OrderCategory), category, true); }
|
||||
MustSetTarget = orderElement.GetAttributeBool("mustsettarget", false);
|
||||
|
||||
@@ -400,7 +400,7 @@ namespace Barotrauma
|
||||
spawnPos = spawnPoint?.WorldPosition ?? Submarine.MainSub.WorldPosition;
|
||||
}
|
||||
var pet = Character.Create(speciesName, spawnPos, seed);
|
||||
var petBehavior = (pet.AIController as EnemyAIController)?.PetBehavior;
|
||||
var petBehavior = (pet?.AIController as EnemyAIController)?.PetBehavior;
|
||||
if (petBehavior != null)
|
||||
{
|
||||
petBehavior.Owner = owner;
|
||||
|
||||
+2
-2
@@ -113,7 +113,7 @@ namespace Barotrauma
|
||||
if (CurrentOrder != null && OrderedCharacter.GetCurrentOrderWithTopPriority()?.Order != CurrentOrder)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Order {CurrentOrder.Name} did not match current order for character {OrderedCharacter} in {this}");
|
||||
ShipCommandManager.ShipCommandLog($"Order {CurrentOrder.Name} did not match current order for character {OrderedCharacter} in {this}");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -121,7 +121,7 @@ namespace Barotrauma
|
||||
if (!shipCommandManager.AbleToTakeOrder(OrderedCharacter))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage(OrderedCharacter + " was unable to perform assigned order in " + this);
|
||||
ShipCommandManager.ShipCommandLog(OrderedCharacter + " was unable to perform assigned order in " + this);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
static void ShipCommandLog(string text)
|
||||
public static void ShipCommandLog(string text)
|
||||
{
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasMultipleLimbsOfSameType => Limbs.Length > limbDictionary.Count;
|
||||
public bool HasMultipleLimbsOfSameType => limbs == null ? false : Limbs.Length > limbDictionary.Count;
|
||||
|
||||
private bool frozen;
|
||||
public bool Frozen
|
||||
@@ -416,10 +416,7 @@ namespace Barotrauma
|
||||
|
||||
protected void CreateColliders()
|
||||
{
|
||||
if (collider != null)
|
||||
{
|
||||
collider.ForEach(c => c.Remove());
|
||||
}
|
||||
collider?.ForEach(c => c.Remove());
|
||||
DebugConsole.Log($"Creating colliders from {RagdollParams.Name}.");
|
||||
collider = new List<PhysicsBody>();
|
||||
foreach (var cParams in RagdollParams.Colliders)
|
||||
@@ -479,10 +476,7 @@ namespace Barotrauma
|
||||
|
||||
protected void CreateLimbs()
|
||||
{
|
||||
if (limbs != null)
|
||||
{
|
||||
limbs.ForEach(l => l.Remove());
|
||||
}
|
||||
limbs?.ForEach(l => l.Remove());
|
||||
DebugConsole.Log($"Creating limbs from {RagdollParams.Name}.");
|
||||
limbDictionary = new Dictionary<LimbType, Limb>();
|
||||
limbs = new Limb[RagdollParams.Limbs.Count];
|
||||
|
||||
@@ -395,14 +395,12 @@ namespace Barotrauma
|
||||
Affliction affliction;
|
||||
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
|
||||
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, System.StringComparison.OrdinalIgnoreCase));
|
||||
if (afflictionPrefab != null)
|
||||
if (afflictionPrefab == null)
|
||||
{
|
||||
affliction = afflictionPrefab.Instantiate(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
affliction = new Affliction(null, 0);
|
||||
DebugConsole.ThrowError($"Couldn't find the affliction with the identifier {afflictionIdentifier} referenced in {element.Document.ParseContentPathFromUri()}");
|
||||
continue;
|
||||
}
|
||||
affliction = afflictionPrefab.Instantiate(0.0f);
|
||||
affliction.Deserialize(subElement);
|
||||
//backwards compatibility
|
||||
if (subElement.Attribute("amount") != null && subElement.Attribute("strength") == null)
|
||||
|
||||
@@ -1523,6 +1523,16 @@ namespace Barotrauma
|
||||
greatestNegativeHealthMultiplier = 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to modify a character's health for runtime session. Change with AddHealthMultiplier
|
||||
/// </summary>
|
||||
public float StaticHealthMultiplier { get; private set; } = 1;
|
||||
|
||||
public void AddStaticHealthMultiplier(float newMultiplier)
|
||||
{
|
||||
StaticHealthMultiplier *= newMultiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Speed reduction from the current limb specific damage. Min 0, max 1.
|
||||
/// </summary>
|
||||
@@ -1916,19 +1926,19 @@ namespace Barotrauma
|
||||
return AnimController.GetLimb(LimbType.Head) ?? AnimController.GetLimb(LimbType.Torso) ?? AnimController.MainLimb;
|
||||
}
|
||||
|
||||
public bool CanSeeTarget(ISpatialEntity target, Limb seeingLimb = null)
|
||||
public bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity = null)
|
||||
{
|
||||
seeingLimb ??= GetSeeingLimb();
|
||||
if (seeingLimb == null) { return false; }
|
||||
ISpatialEntity seeingEntity = AnimController.SimplePhysicsEnabled ? this : seeingLimb as ISpatialEntity;
|
||||
seeingEntity ??= AnimController.SimplePhysicsEnabled ? this as ISpatialEntity : GetSeeingLimb() as ISpatialEntity;
|
||||
if (seeingEntity == null) { return false; }
|
||||
ISpatialEntity sourceEntity = seeingEntity ;
|
||||
// TODO: Could we just use the method below? If not, let's refactor it so that we can.
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - seeingEntity.WorldPosition);
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - sourceEntity.WorldPosition);
|
||||
Body closestBody;
|
||||
//both inside the same sub (or both outside)
|
||||
//OR the we're inside, the other character outside
|
||||
if (target.Submarine == Submarine || target.Submarine == null)
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff);
|
||||
closestBody = Submarine.CheckVisibility(sourceEntity.SimPosition, sourceEntity.SimPosition + diff);
|
||||
}
|
||||
//we're outside, the other character inside
|
||||
else if (Submarine == null)
|
||||
@@ -1938,7 +1948,7 @@ namespace Barotrauma
|
||||
//both inside different subs
|
||||
else
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff);
|
||||
closestBody = Submarine.CheckVisibility(sourceEntity.SimPosition, sourceEntity.SimPosition + diff);
|
||||
if (!IsBlocking(closestBody))
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff);
|
||||
@@ -1966,29 +1976,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: ensure that works. CheckVisibility takes positions in sim space, but this method uses world positions
|
||||
/// </summary>
|
||||
public bool CanSeeCharacter(Character target, Vector2 sourceWorldPos)
|
||||
{
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - sourceWorldPos);
|
||||
Body closestBody;
|
||||
if (target.Submarine == null)
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(sourceWorldPos, sourceWorldPos + diff);
|
||||
if (closestBody == null) { return true; }
|
||||
}
|
||||
else
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(target.WorldPosition, target.WorldPosition - diff);
|
||||
if (closestBody == null) { return true; }
|
||||
}
|
||||
Structure wall = closestBody.UserData as Structure;
|
||||
Item item = closestBody.UserData as Item;
|
||||
Door door = item?.GetComponent<Door>();
|
||||
return (wall == null || !wall.CastShadow) && (door == null || door.CanBeTraversed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple check if the character Dir is towards the target or not. Uses the world coordinates.
|
||||
/// </summary>
|
||||
@@ -2233,7 +2220,10 @@ namespace Barotrauma
|
||||
Rectangle itemDisplayRect = new Rectangle(item.InteractionRect.X, item.InteractionRect.Y - item.InteractionRect.Height, item.InteractionRect.Width, item.InteractionRect.Height);
|
||||
|
||||
// Get the point along the line between lowerBodyPosition and upperBodyPosition which is closest to the center of itemDisplayRect
|
||||
Vector2 playerDistanceCheckPosition = Vector2.Clamp(itemDisplayRect.Center.ToVector2(), lowerBodyPosition, upperBodyPosition);
|
||||
Vector2 playerDistanceCheckPosition =
|
||||
lowerBodyPosition.Y < upperBodyPosition.Y ?
|
||||
Vector2.Clamp(itemDisplayRect.Center.ToVector2(), lowerBodyPosition, upperBodyPosition) :
|
||||
Vector2.Clamp(itemDisplayRect.Center.ToVector2(), upperBodyPosition, lowerBodyPosition);
|
||||
|
||||
// If playerDistanceCheckPosition is inside the itemDisplayRect then we consider the character to within 0 distance of the item
|
||||
if (itemDisplayRect.Contains(playerDistanceCheckPosition))
|
||||
@@ -2271,7 +2261,7 @@ namespace Barotrauma
|
||||
itemPosition -= Submarine.SimPosition;
|
||||
}
|
||||
var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true);
|
||||
if (body != null && body.UserData as Item != item) { return false; }
|
||||
if (body != null && body.UserData as Item != item && Submarine.LastPickedFixture?.UserData as Item != item) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -3657,7 +3647,11 @@ namespace Barotrauma
|
||||
// OnDamaged is called only for the limb that is hit.
|
||||
AnimController.Limbs.ForEach(l => l.ApplyStatusEffects(actionType, deltaTime));
|
||||
}
|
||||
CharacterHealth.ApplyAfflictionStatusEffects(actionType);
|
||||
//OnActive effects are handled by the afflictions themselves
|
||||
if (actionType != ActionType.OnActive)
|
||||
{
|
||||
CharacterHealth.ApplyAfflictionStatusEffects(actionType);
|
||||
}
|
||||
}
|
||||
|
||||
private void Implode(bool isNetworkMessage = false)
|
||||
@@ -3801,10 +3795,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
if (aiTarget != null)
|
||||
{
|
||||
aiTarget.Remove();
|
||||
}
|
||||
aiTarget?.Remove();
|
||||
|
||||
aiTarget = new AITarget(this);
|
||||
CharacterHealth.RemoveAllAfflictions();
|
||||
|
||||
@@ -456,7 +456,7 @@ namespace Barotrauma
|
||||
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
|
||||
|
||||
// Used for creating the data
|
||||
public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced, string npcIdentifier = "")
|
||||
{
|
||||
if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -485,8 +485,12 @@ namespace Barotrauma
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
else
|
||||
else if (!string.IsNullOrEmpty(npcIdentifier) && TextManager.Get("npctitle." + npcIdentifier, true) is string npcTitle)
|
||||
{
|
||||
Name = npcTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = "";
|
||||
if (CharacterConfigElement.Element("name") != null)
|
||||
{
|
||||
|
||||
@@ -150,12 +150,9 @@ namespace Barotrauma
|
||||
{
|
||||
max += Character.Info.Job.Prefab.VitalityModifier;
|
||||
}
|
||||
max *= Character.StaticHealthMultiplier;
|
||||
return max * Character.HealthMultiplier;
|
||||
}
|
||||
set
|
||||
{
|
||||
maxVitality = Math.Max(0, value);
|
||||
}
|
||||
}
|
||||
|
||||
public float MinVitality
|
||||
|
||||
@@ -121,11 +121,12 @@ namespace Barotrauma
|
||||
|
||||
public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null)
|
||||
{
|
||||
npc.CharacterHealth.MaxVitality *= HealthMultiplier;
|
||||
npc.AddStaticHealthMultiplier(HealthMultiplier);
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
npc.CharacterHealth.MaxVitality *= HealthMultiplierInMultiplayer;
|
||||
npc.AddStaticHealthMultiplier(HealthMultiplierInMultiplayer);
|
||||
}
|
||||
|
||||
var humanAI = npc.AIController as HumanAIController;
|
||||
if (humanAI != null)
|
||||
{
|
||||
@@ -227,10 +228,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||
if (idCardComponent != null)
|
||||
{
|
||||
idCardComponent.Initialize(character.Info);
|
||||
}
|
||||
idCardComponent?.Initialize(character.Info);
|
||||
|
||||
var idCardTags = itemElement.GetAttributeStringArray("tags", new string[0]);
|
||||
foreach (string tag in idCardTags)
|
||||
@@ -243,10 +241,7 @@ namespace Barotrauma
|
||||
{
|
||||
wifiComponent.TeamID = character.TeamID;
|
||||
}
|
||||
if (parentItem != null)
|
||||
{
|
||||
parentItem.Combine(item, user: null);
|
||||
}
|
||||
parentItem?.Combine(item, user: null);
|
||||
foreach (XElement childItemElement in itemElement.Elements())
|
||||
{
|
||||
InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents);
|
||||
|
||||
@@ -204,10 +204,7 @@ namespace Barotrauma
|
||||
item.AddTag("job:" + Name);
|
||||
|
||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||
if (idCardComponent != null)
|
||||
{
|
||||
idCardComponent.Initialize(character.Info);
|
||||
}
|
||||
idCardComponent?.Initialize(character.Info);
|
||||
}
|
||||
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -43,6 +43,12 @@ namespace Barotrauma
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, float> _itemRepairPriorities = new Dictionary<string, float>();
|
||||
/// <summary>
|
||||
/// Tag -> priority.
|
||||
/// </summary>
|
||||
public static IReadOnlyDictionary<string, float> ItemRepairPriorities => _itemRepairPriorities;
|
||||
|
||||
public static XElement NoJobElement;
|
||||
public static JobPrefab Get(string identifier)
|
||||
{
|
||||
@@ -294,7 +300,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
if (element.Name.ToString().Equals("nojob", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (!element.Name.ToString().Equals("job", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var job = new JobPrefab(element.FirstElement(), file.Path)
|
||||
@@ -312,8 +318,31 @@ namespace Barotrauma
|
||||
Prefabs.Add(job, false);
|
||||
}
|
||||
}
|
||||
NoJobElement = NoJobElement ?? mainElement.Element("NoJob");
|
||||
NoJobElement = NoJobElement ?? mainElement.Element("nojob");
|
||||
NoJobElement ??= mainElement.GetChildElement("nojob");
|
||||
var itemRepairPrioritiesElement = mainElement.GetChildElement("ItemRepairPriorities");
|
||||
if (itemRepairPrioritiesElement != null)
|
||||
{
|
||||
foreach (var subElement in itemRepairPrioritiesElement.Elements())
|
||||
{
|
||||
string tag = subElement.GetAttributeString("tag", null);
|
||||
if (tag != null)
|
||||
{
|
||||
float priority = subElement.GetAttributeFloat("priority", -1f);
|
||||
if (priority >= 0)
|
||||
{
|
||||
_itemRepairPriorities.TryAdd(tag, priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"The 'priority' attribute is missing from the the item repair priorities definition in {subElement} of {file.Path}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"The 'tag' attribute is missing from the the item repair priorities definition in {subElement} of {file.Path}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveByFile(string filePath)
|
||||
|
||||
@@ -837,10 +837,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (attack != null)
|
||||
{
|
||||
attack.UpdateCoolDown(deltaTime);
|
||||
}
|
||||
attack?.UpdateCoolDown(deltaTime);
|
||||
}
|
||||
|
||||
private float reEnableTimer = -1;
|
||||
|
||||
@@ -194,13 +194,13 @@ namespace Barotrauma
|
||||
isCorePackage = value;
|
||||
if (isCorePackage && regularPackages.Contains(this))
|
||||
{
|
||||
corePackages.Add(this);
|
||||
regularPackages.Remove(this);
|
||||
corePackages.AddOnMainThread(this);
|
||||
regularPackages.RemoveOnMainThread(this);
|
||||
}
|
||||
else if (!isCorePackage && corePackages.Contains(this))
|
||||
{
|
||||
regularPackages.Add(this);
|
||||
corePackages.Remove(this);
|
||||
regularPackages.AddOnMainThread(this);
|
||||
corePackages.RemoveOnMainThread(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,6 +411,7 @@ namespace Barotrauma
|
||||
case ContentType.Submarine:
|
||||
case ContentType.Wreck:
|
||||
case ContentType.BeaconStation:
|
||||
case ContentType.EnemySubmarine:
|
||||
break;
|
||||
default:
|
||||
try
|
||||
@@ -528,7 +529,7 @@ namespace Barotrauma
|
||||
{
|
||||
refreshFiles = true;
|
||||
}
|
||||
corePackages.Remove(p);
|
||||
corePackages.RemoveOnMainThread(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -536,16 +537,16 @@ namespace Barotrauma
|
||||
{
|
||||
refreshFiles = true;
|
||||
}
|
||||
regularPackages.Remove(p);
|
||||
regularPackages.RemoveOnMainThread(p);
|
||||
}
|
||||
}
|
||||
if (IsCorePackage)
|
||||
{
|
||||
corePackages.Add(this);
|
||||
corePackages.AddOnMainThread(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
regularPackages.Add(this);
|
||||
regularPackages.AddOnMainThread(this);
|
||||
}
|
||||
|
||||
if (refreshFiles)
|
||||
@@ -743,18 +744,18 @@ namespace Barotrauma
|
||||
}
|
||||
if (newPackage.IsCorePackage)
|
||||
{
|
||||
corePackages.Add(newPackage);
|
||||
corePackages.AddOnMainThread(newPackage);
|
||||
}
|
||||
else
|
||||
{
|
||||
regularPackages.Add(newPackage);
|
||||
regularPackages.AddOnMainThread(newPackage);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemovePackage(ContentPackage package)
|
||||
{
|
||||
if (package.IsCorePackage) { corePackages.Remove(package); }
|
||||
else { regularPackages.Remove(package); }
|
||||
if (package.IsCorePackage) { corePackages.RemoveOnMainThread(package); }
|
||||
else { regularPackages.RemoveOnMainThread(package); }
|
||||
}
|
||||
|
||||
public static void LoadAll()
|
||||
@@ -775,9 +776,9 @@ namespace Barotrauma
|
||||
|
||||
IEnumerable<string> files = Directory.GetFiles(folder, "*.xml");
|
||||
|
||||
corePackages.Clear();
|
||||
corePackages.ClearOnMainThread();
|
||||
var prevRegularPackages = regularPackages.Select(p => p.Name.ToLowerInvariant()).ToList();
|
||||
regularPackages.Clear();
|
||||
regularPackages.ClearOnMainThread();
|
||||
|
||||
foreach (string filePath in files)
|
||||
{
|
||||
@@ -815,7 +816,7 @@ namespace Barotrauma
|
||||
.OrderBy(p => order(p))
|
||||
.ThenBy(p => regularPackages.IndexOf(p))
|
||||
.ToList();
|
||||
regularPackages.Clear(); regularPackages.AddRange(ordered);
|
||||
regularPackages.ClearOnMainThread(); regularPackages.AddRangeOnMainThread(ordered);
|
||||
(config ?? GameMain.Config)?.SortContentPackages(refreshAll);
|
||||
}
|
||||
|
||||
@@ -825,12 +826,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (IsCorePackage)
|
||||
{
|
||||
corePackages.Remove(this);
|
||||
corePackages.RemoveOnMainThread(this);
|
||||
if (GameMain.Config.CurrentCorePackage == this) { GameMain.Config.AutoSelectCorePackage(null); }
|
||||
}
|
||||
else
|
||||
{
|
||||
regularPackages.Remove(this);
|
||||
regularPackages.RemoveOnMainThread(this);
|
||||
if (GameMain.Config.EnabledRegularPackages.Contains(this)) { GameMain.Config.DisableRegularPackage(this); }
|
||||
}
|
||||
GameMain.Config.SaveNewPlayerConfig();
|
||||
|
||||
@@ -303,9 +303,15 @@ namespace Barotrauma
|
||||
|
||||
private bool IsValidTarget(Entity e)
|
||||
{
|
||||
return
|
||||
e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
|
||||
bool isValid = e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
|
||||
(e == Character.Controlled || character.IsRemotePlayer);
|
||||
#if SERVER
|
||||
UpdateIgnoredClients();
|
||||
isValid &= !ignoredClients.Keys.Any(c => c.Character == e);
|
||||
#elif CLIENT
|
||||
isValid &= (e != Character.Controlled || !GUI.InputBlockingMenuOpen);
|
||||
#endif
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private void TryStartConversation(Character speaker, Character targetCharacter = null)
|
||||
@@ -348,7 +354,7 @@ namespace Barotrauma
|
||||
{
|
||||
ParentEvent.AddTarget(InvokerTag, targetCharacter);
|
||||
}
|
||||
|
||||
|
||||
ShowDialog(speaker, targetCharacter);
|
||||
|
||||
dialogOpened = true;
|
||||
|
||||
@@ -14,6 +14,18 @@ namespace Barotrauma
|
||||
[Serialize("", true)]
|
||||
public string MissionTag { get; set; }
|
||||
|
||||
[Serialize("", true, description: "The type of the location the mission will be unlocked in (if empty, any location can be selected).")]
|
||||
public string LocationType { get; set; }
|
||||
|
||||
[Serialize(0, true, description: "Minimum distance to the location the mission is unlocked in (1 = one path between locations).")]
|
||||
public int MinLocationDistance { get; set; }
|
||||
|
||||
[Serialize(true, true, description: "If true, the mission has to be unlocked in a location further on the campaign map.")]
|
||||
public bool UnlockFurtherOnMap { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "If true, a suitable location is forced on the map if one isn't found.")]
|
||||
public bool CreateLocationIfNotFound { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public MissionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
@@ -44,33 +56,82 @@ namespace Barotrauma
|
||||
if (GameMain.GameSession.GameMode is CampaignMode campaign)
|
||||
{
|
||||
MissionPrefab prefab = null;
|
||||
if (!string.IsNullOrEmpty(MissionIdentifier))
|
||||
var unlockLocation = FindUnlockLocation();
|
||||
if (unlockLocation == null && CreateLocationIfNotFound)
|
||||
{
|
||||
prefab = campaign.Map.CurrentLocation.UnlockMissionByIdentifier(MissionIdentifier);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(MissionTag))
|
||||
{
|
||||
prefab = campaign.Map.CurrentLocation.UnlockMissionByTag(MissionTag);
|
||||
}
|
||||
if (campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
mpCampaign.LastUpdateID++;
|
||||
//find an empty location at least 3 steps away, further on the map
|
||||
var emptyLocation = FindUnlockLocationRecursive(campaign.Map.CurrentLocation, Math.Max(MinLocationDistance, 3), "none", true, new HashSet<Location>());
|
||||
if (emptyLocation != null)
|
||||
{
|
||||
emptyLocation.ChangeType(Barotrauma.LocationType.List.Find(lt => lt.Identifier.Equals(LocationType, StringComparison.OrdinalIgnoreCase)));
|
||||
unlockLocation = emptyLocation;
|
||||
}
|
||||
}
|
||||
|
||||
if (prefab != null)
|
||||
if (unlockLocation != null)
|
||||
{
|
||||
#if CLIENT
|
||||
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name),
|
||||
new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
|
||||
if (!string.IsNullOrEmpty(MissionIdentifier))
|
||||
{
|
||||
IconColor = prefab.IconColor
|
||||
};
|
||||
#else
|
||||
NotifyMissionUnlock(prefab);
|
||||
#endif
|
||||
prefab = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(MissionTag))
|
||||
{
|
||||
prefab = unlockLocation.UnlockMissionByTag(MissionTag);
|
||||
}
|
||||
if (campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
mpCampaign.LastUpdateID++;
|
||||
}
|
||||
if (prefab != null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Unlocked mission \"{prefab.Name}\" in the location \"{unlockLocation.Name}\".");
|
||||
#if CLIENT
|
||||
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name),
|
||||
new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
|
||||
{
|
||||
IconColor = prefab.IconColor
|
||||
};
|
||||
#else
|
||||
NotifyMissionUnlock(prefab);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to find a suitable location to unlock a mission in (LocationType: {LocationType}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})");
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
private Location FindUnlockLocation()
|
||||
{
|
||||
var campaign = GameMain.GameSession.GameMode as CampaignMode;
|
||||
if (string.IsNullOrEmpty(LocationType) && MinLocationDistance <= 1)
|
||||
{
|
||||
return campaign.Map.CurrentLocation;
|
||||
}
|
||||
|
||||
return FindUnlockLocationRecursive(campaign.Map.CurrentLocation, 0, LocationType, UnlockFurtherOnMap, new HashSet<Location>());
|
||||
}
|
||||
|
||||
private Location FindUnlockLocationRecursive(Location currLocation, int currDistance, string locationType, bool unlockFurtherOnMap, HashSet<Location> checkedLocations)
|
||||
{
|
||||
var campaign = GameMain.GameSession.GameMode as CampaignMode;
|
||||
if (currLocation.Type.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase) && currDistance >= MinLocationDistance &&
|
||||
(!unlockFurtherOnMap || currLocation.MapPosition.X > campaign.Map.CurrentLocation.MapPosition.X))
|
||||
{
|
||||
return currLocation;
|
||||
}
|
||||
checkedLocations.Add(currLocation);
|
||||
foreach (LocationConnection connection in currLocation.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(currLocation);
|
||||
if (checkedLocations.Contains(otherLocation)) { continue; }
|
||||
var unlockLocation = FindUnlockLocationRecursive(otherLocation, ++currDistance, locationType, unlockFurtherOnMap, checkedLocations);
|
||||
if (unlockLocation != null) { return unlockLocation; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
@@ -84,8 +145,8 @@ namespace Barotrauma
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.Write((byte) ServerPacketHeader.EVENTACTION);
|
||||
outmsg.Write((byte) EventManager.NetworkEventType.MISSION);
|
||||
outmsg.Write((byte)ServerPacketHeader.EVENTACTION);
|
||||
outmsg.Write((byte)EventManager.NetworkEventType.MISSION);
|
||||
outmsg.Write(prefab.Identifier);
|
||||
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
@@ -41,9 +41,14 @@ namespace Barotrauma
|
||||
foreach (Item item in npc.Inventory.AllItems)
|
||||
{
|
||||
item.AllowStealing = true;
|
||||
var wifiComponent = item.GetComponent<Items.Components.WifiComponent>();
|
||||
if (wifiComponent != null)
|
||||
{
|
||||
wifiComponent.TeamID = newTeam;
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew });
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew, newTeam, npc.Inventory.AllItems.Select(it => it.ID).ToArray() });
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace Barotrauma
|
||||
Outpost,
|
||||
MainPath,
|
||||
Ruin,
|
||||
Wreck
|
||||
Wreck,
|
||||
BeaconStation
|
||||
}
|
||||
|
||||
[Serialize("", true, description: "Species name of the character to spawn.")]
|
||||
@@ -225,6 +226,7 @@ namespace Barotrauma
|
||||
SpawnLocationType.Outpost => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsOutpost),
|
||||
SpawnLocationType.Wreck => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsWreck),
|
||||
SpawnLocationType.Ruin => Item.ItemList.FindAll(it => it.ParentRuin != null),
|
||||
SpawnLocationType.BeaconStation => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsBeacon),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
@@ -250,6 +252,7 @@ namespace Barotrauma
|
||||
SpawnLocationType.Outpost => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.CurrentHull != null && wp.Submarine.Info.IsOutpost),
|
||||
SpawnLocationType.Wreck => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsWreck),
|
||||
SpawnLocationType.Ruin => WayPoint.WayPointList.FindAll(wp => wp.ParentRuin != null),
|
||||
SpawnLocationType.BeaconStation => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsBeacon),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
|
||||
@@ -51,7 +51,14 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var target in targets)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity);
|
||||
if (target is Item targetItem)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, target, targetItem.AllPropertyObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Barotrauma
|
||||
npc.CampaignInteractionType = CampaignMode.InteractionType.Examine;
|
||||
#if CLIENT
|
||||
npc.SetCustomInteract(
|
||||
Trigger,
|
||||
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
|
||||
TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameMain.Config.KeyBindText(InputType.Use)));
|
||||
#else
|
||||
npc.SetCustomInteract(
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace Barotrauma
|
||||
totalPathLength = 0.0f;
|
||||
if (level != null)
|
||||
{
|
||||
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(Level.Loaded.StartPosition), ConvertUnits.ToSimUnits(Level.Loaded.EndPosition));
|
||||
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(level.StartPosition), ConvertUnits.ToSimUnits(level.EndPosition));
|
||||
totalPathLength = steeringPath.TotalLength;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Barotrauma
|
||||
}
|
||||
MTRandom rand = new MTRandom(seed);
|
||||
|
||||
var initialEventSet = SelectRandomEvents(EventSet.List);
|
||||
var initialEventSet = SelectRandomEvents(EventSet.List, rand);
|
||||
if (initialEventSet != null)
|
||||
{
|
||||
pendingEventSets.Add(initialEventSet);
|
||||
@@ -386,21 +386,25 @@ namespace Barotrauma
|
||||
{
|
||||
if (level == null) { return; }
|
||||
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
|
||||
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Loading event set {eventSet.DebugIdentifier}", Color.LightBlue);
|
||||
#else
|
||||
DebugConsole.Log($"Loading event set {eventSet.DebugIdentifier}");
|
||||
#endif
|
||||
int applyCount = 1;
|
||||
List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>();
|
||||
if (eventSet.PerRuin)
|
||||
{
|
||||
applyCount = Level.Loaded.Ruins.Count();
|
||||
foreach (var ruin in Level.Loaded.Ruins)
|
||||
applyCount = level.Ruins.Count();
|
||||
foreach (var ruin in level.Ruins)
|
||||
{
|
||||
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Ruin == ruin; });
|
||||
}
|
||||
}
|
||||
else if (eventSet.PerCave)
|
||||
{
|
||||
applyCount = Level.Loaded.Caves.Count();
|
||||
foreach (var cave in Level.Loaded.Caves)
|
||||
applyCount = level.Caves.Count();
|
||||
foreach (var cave in level.Caves)
|
||||
{
|
||||
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Cave == cave; });
|
||||
}
|
||||
@@ -417,7 +421,8 @@ namespace Barotrauma
|
||||
|
||||
var suitablePrefabs = eventSet.EventPrefabs.FindAll(e =>
|
||||
string.IsNullOrEmpty(e.First.BiomeIdentifier) ||
|
||||
e.First.BiomeIdentifier.Equals(Level.Loaded.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
e.First.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
for (int i = 0; i < applyCount; i++)
|
||||
{
|
||||
if (eventSet.ChooseRandom)
|
||||
@@ -435,7 +440,11 @@ namespace Barotrauma
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
||||
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}");
|
||||
#else
|
||||
DebugConsole.Log($"Initialized event {newEvent}");
|
||||
#endif
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
selectedEvents.Add(eventSet, new List<Event>());
|
||||
@@ -447,8 +456,11 @@ namespace Barotrauma
|
||||
}
|
||||
if (eventSet.ChildSets.Count > 0)
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
|
||||
if (newEventSet != null) { CreateEvents(newEventSet, rand); }
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets, rand);
|
||||
if (newEventSet != null)
|
||||
{
|
||||
CreateEvents(newEventSet, rand);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -458,7 +470,11 @@ namespace Barotrauma
|
||||
var newEvent = eventPrefab.First.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}");
|
||||
#else
|
||||
DebugConsole.Log($"Initialized event {newEvent}");
|
||||
#endif
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
selectedEvents.Add(eventSet, new List<Event>());
|
||||
@@ -474,10 +490,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private EventSet SelectRandomEvents(List<EventSet> eventSets)
|
||||
private EventSet SelectRandomEvents(List<EventSet> eventSets, Random random = null)
|
||||
{
|
||||
if (level == null) { return null; }
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
Random rand = random ?? new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
|
||||
var allowedEventSets =
|
||||
eventSets.Where(es =>
|
||||
@@ -496,7 +512,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
|
||||
float randomNumber = (float)rand.NextDouble() * totalCommonness;
|
||||
float randomNumber = (float)rand.NextDouble();
|
||||
randomNumber *= totalCommonness;
|
||||
foreach (EventSet eventSet in allowedEventSets)
|
||||
{
|
||||
float commonness = eventSet.GetCommonness(level);
|
||||
@@ -835,7 +852,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (level == null) { return 0.0f; }
|
||||
var refEntity = GetRefEntity();
|
||||
Vector2 target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition);
|
||||
Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition);
|
||||
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target);
|
||||
if (steeringPath.Unreachable || float.IsPositiveInfinity(totalPathLength))
|
||||
{
|
||||
@@ -953,15 +970,15 @@ namespace Barotrauma
|
||||
|
||||
const int maxDist = 1000;
|
||||
|
||||
if (Level.Loaded != null)
|
||||
if (level != null)
|
||||
{
|
||||
foreach (var ruin in Level.Loaded.Ruins)
|
||||
foreach (var ruin in level.Ruins)
|
||||
{
|
||||
Rectangle area = ruin.Area;
|
||||
area.Inflate(maxDist, maxDist);
|
||||
if (area.Contains(character.WorldPosition)) { return true; }
|
||||
}
|
||||
foreach (var cave in Level.Loaded.Caves)
|
||||
foreach (var cave in level.Caves)
|
||||
{
|
||||
Rectangle area = cave.Area;
|
||||
area.Inflate(maxDist, maxDist);
|
||||
|
||||
+138
-35
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -15,6 +16,10 @@ namespace Barotrauma
|
||||
protected readonly HashSet<Character> requireKill = new HashSet<Character>();
|
||||
protected readonly HashSet<Character> requireRescue = new HashSet<Character>();
|
||||
|
||||
private readonly string itemTag;
|
||||
private readonly XElement itemConfig;
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
|
||||
protected const int HostagesKilledState = 5;
|
||||
|
||||
private readonly string hostagesKilledMessage;
|
||||
@@ -33,6 +38,43 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Targets.Select(t => t.WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Entity> Targets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Entity>();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (items.Any())
|
||||
{
|
||||
return items.Where(it => !it.Removed && it.Condition > 0.0f).Cast<Entity>().Concat(requireKill.Where(c => !c.Removed && !c.IsDead)).Concat(requireRescue);
|
||||
}
|
||||
else
|
||||
{
|
||||
return requireKill.Concat(requireRescue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected bool wasDocked;
|
||||
|
||||
public AbandonedOutpostMission(MissionPrefab prefab, Location[] locations, Submarine sub) :
|
||||
@@ -42,6 +84,9 @@ namespace Barotrauma
|
||||
|
||||
string msgTag = prefab.ConfigElement.GetAttributeString("hostageskilledmessage", "");
|
||||
hostagesKilledMessage = TextManager.Get(msgTag, returnNull: true) ?? msgTag;
|
||||
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
itemTag = prefab.ConfigElement.GetAttributeString("targetitem", "");
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
@@ -52,8 +97,13 @@ namespace Barotrauma
|
||||
characterItems.Clear();
|
||||
requireKill.Clear();
|
||||
requireRescue.Clear();
|
||||
items.Clear();
|
||||
#if SERVER
|
||||
spawnedItems.Clear();
|
||||
#endif
|
||||
|
||||
var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub;
|
||||
InitItems(submarine);
|
||||
if (!IsClient)
|
||||
{
|
||||
InitCharacters(submarine);
|
||||
@@ -62,49 +112,101 @@ namespace Barotrauma
|
||||
wasDocked = Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost);
|
||||
}
|
||||
|
||||
private void InitItems(Submarine submarine)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(itemTag))
|
||||
{
|
||||
var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
|
||||
if (!itemsToDestroy.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
items.AddRange(itemsToDestroy);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemConfig != null && !IsClient)
|
||||
{
|
||||
foreach (XElement element in itemConfig.Elements())
|
||||
{
|
||||
string itemIdentifier = element.GetAttributeString("identifier", "");
|
||||
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null);
|
||||
string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null);
|
||||
ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos(
|
||||
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human | SpawnType.Enemy,
|
||||
moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false));
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
spawnPoint = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
|
||||
}
|
||||
Vector2 spawnPos = spawnPoint.WorldPosition;
|
||||
if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100)
|
||||
{
|
||||
spawnPos = new Vector2(
|
||||
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50),
|
||||
wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f);
|
||||
}
|
||||
var item = new Item(itemPrefab, spawnPos, null);
|
||||
items.Add(item);
|
||||
#if SERVER
|
||||
spawnedItems.Add(item);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitCharacters(Submarine submarine)
|
||||
{
|
||||
characters.Clear();
|
||||
characterItems.Clear();
|
||||
|
||||
if (characterConfig == null) { return; }
|
||||
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false)) { continue; }
|
||||
|
||||
int defaultCount = element.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
if (characterConfig != null)
|
||||
{
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
defaultCount = element.GetAttributeInt("amount", 1);
|
||||
}
|
||||
int min = Math.Min(element.GetAttributeInt("min", defaultCount), 255);
|
||||
int max = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255);
|
||||
int count = Rand.Range(min, max + 1);
|
||||
if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false)) { continue; }
|
||||
|
||||
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
|
||||
{
|
||||
HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element);
|
||||
for (int i = 0; i < count; i++)
|
||||
int defaultCount = element.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
{
|
||||
LoadHuman(humanPrefab, element, submarine);
|
||||
defaultCount = element.GetAttributeInt("amount", 1);
|
||||
}
|
||||
int min = Math.Min(element.GetAttributeInt("min", defaultCount), 255);
|
||||
int max = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255);
|
||||
int count = Rand.Range(min, max + 1);
|
||||
|
||||
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
|
||||
{
|
||||
HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadHuman(humanPrefab, element, submarine);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string speciesName = element.GetAttributeString("character", element.GetAttributeString("identifier", ""));
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (characterPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + speciesName + "\" not found");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadMonster(characterPrefab, element, submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string speciesName = element.GetAttributeString("character", element.GetAttributeString("identifier", ""));
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (characterPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + speciesName + "\" not found");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadMonster(characterPrefab, element, submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadHuman(HumanPrefab humanPrefab, XElement element, Submarine submarine)
|
||||
@@ -175,7 +277,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (State != HostagesKilledState)
|
||||
{
|
||||
@@ -203,7 +305,8 @@ namespace Barotrauma
|
||||
{
|
||||
case 0:
|
||||
|
||||
if (requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
if (items.All(it => it.Removed || it.Condition <= 0.0f) &&
|
||||
requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
|
||||
{
|
||||
State = 1;
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
if (!swarmSpawned && level.CheckBeaconActive())
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma
|
||||
private readonly Dictionary<Item, UInt16> parentInventoryIDs = new Dictionary<Item, UInt16>();
|
||||
private readonly Dictionary<Item, byte> parentItemContainerIndices = new Dictionary<Item, byte>();
|
||||
|
||||
private int requiredDeliveryAmount;
|
||||
private float requiredDeliveryAmount;
|
||||
|
||||
private readonly List<(XElement element, ItemContainer container)> itemsToSpawn = new List<(XElement element, ItemContainer container)>();
|
||||
private int? rewardPerCrate;
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma
|
||||
{
|
||||
this.sub = sub;
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
requiredDeliveryAmount = prefab.ConfigElement.GetAttributeInt("requireddeliveryamount", 0);
|
||||
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.9f), 1.0f);
|
||||
DetermineCargo();
|
||||
}
|
||||
|
||||
@@ -123,12 +123,7 @@ namespace Barotrauma
|
||||
LoadItemAsChild(element, container?.Item);
|
||||
}
|
||||
|
||||
if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; }
|
||||
if (requiredDeliveryAmount > items.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Error in mission \"{Prefab.Identifier}\". Required delivery amount is {requiredDeliveryAmount} but there's only {items.Count} items to deliver.");
|
||||
requiredDeliveryAmount = items.Count;
|
||||
}
|
||||
if (requiredDeliveryAmount <= 0.0f) { requiredDeliveryAmount = 1.0f; }
|
||||
}
|
||||
|
||||
private ItemPrefab FindItemPrefab(XElement element)
|
||||
@@ -220,7 +215,7 @@ namespace Barotrauma
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit)
|
||||
{
|
||||
int deliveredItemCount = items.Count(i => i.CurrentHull != null && !i.Removed && i.Condition > 0.0f);
|
||||
if (deliveredItemCount >= requiredDeliveryAmount)
|
||||
if (deliveredItemCount / (float)items.Count >= requiredDeliveryAmount)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
|
||||
@@ -39,13 +39,10 @@ namespace Barotrauma
|
||||
itemConfig = prefab.ConfigElement.Element("TerroristItems");
|
||||
}
|
||||
|
||||
public override int Reward
|
||||
public override int GetReward(Submarine sub)
|
||||
{
|
||||
get
|
||||
{
|
||||
int multiplier = CalculateScalingEscortedCharacterCount();
|
||||
return Prefab.Reward * multiplier;
|
||||
}
|
||||
int multiplier = CalculateScalingEscortedCharacterCount();
|
||||
return Prefab.Reward * multiplier;
|
||||
}
|
||||
|
||||
int CalculateScalingEscortedCharacterCount(bool inMission = false)
|
||||
@@ -58,7 +55,7 @@ namespace Barotrauma
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return (int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * Submarine.MainSub.Info.RecommendedCrewSizeMin);
|
||||
return (int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * (Submarine.MainSub.Info.RecommendedCrewSizeMin + Submarine.MainSub.Info.RecommendedCrewSizeMax) / 2);
|
||||
}
|
||||
|
||||
private void InitEscort()
|
||||
@@ -88,6 +85,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (terroristChance > 0f)
|
||||
{
|
||||
int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
|
||||
terroristCount = Math.Clamp(terroristCount, 1, characters.Count);
|
||||
|
||||
terroristCharacters.Clear();
|
||||
characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c));
|
||||
|
||||
terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f);
|
||||
|
||||
#if DEBUG
|
||||
DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared));
|
||||
foreach (Character character in terroristCharacters)
|
||||
{
|
||||
DebugConsole.AddWarning(character.Name + " is a terrorist.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void InitCharacters()
|
||||
@@ -120,26 +136,6 @@ namespace Barotrauma
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!IsClient && terroristChance > 0f)
|
||||
{
|
||||
int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
|
||||
terroristCount = Math.Clamp(terroristCount, 1, characters.Count);
|
||||
|
||||
terroristCharacters.Clear();
|
||||
characters.Shuffle();
|
||||
characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c));
|
||||
|
||||
terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f);
|
||||
|
||||
#if DEBUG
|
||||
DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared));
|
||||
foreach (Character character in terroristCharacters)
|
||||
{
|
||||
DebugConsole.AddWarning(character.Name + " is a terrorist.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
@@ -207,10 +203,10 @@ namespace Barotrauma
|
||||
|
||||
bool NonTerroristsStillAlive(IEnumerable<Character> characterList)
|
||||
{
|
||||
return characterList.Any(c => !terroristCharacters.Contains(c) && IsAlive(c));
|
||||
return characterList.All(c => terroristCharacters.Contains(c) || IsAlive(c));
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (!IsClient)
|
||||
{
|
||||
@@ -261,9 +257,10 @@ namespace Barotrauma
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit)
|
||||
{
|
||||
bool terroristsSurvived = terroristCharacters.Any(c => Survived(c) && !IsCaptured(c));
|
||||
bool friendliesSurvived = characters.Except(terroristCharacters).Any(c => Survived(c));
|
||||
bool friendliesSurvived = characters.Except(terroristCharacters).All(c => Survived(c));
|
||||
bool vipDied = false;
|
||||
|
||||
// this logic is currently irrelevant, as the mission is failed regardless of who dies
|
||||
if (vipCharacter != null)
|
||||
{
|
||||
vipDied = !Survived(vipCharacter);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class GoToMission : Mission
|
||||
{
|
||||
public GoToMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
}
|
||||
#elif SERVER
|
||||
|
||||
public override void ServerWriteInitial(IWriteMessage msg, Client c)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ namespace Barotrauma
|
||||
FindRelevantLevelResources();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
switch (State)
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Barotrauma
|
||||
if (state != value)
|
||||
{
|
||||
state = value;
|
||||
TryTriggerEvents(state);
|
||||
#if SERVER
|
||||
GameMain.Server?.UpdateMissionState(this, state);
|
||||
#endif
|
||||
@@ -128,6 +129,20 @@ namespace Barotrauma
|
||||
{
|
||||
get { return Prefab.Difficulty; }
|
||||
}
|
||||
|
||||
private class DelayedTriggerEvent
|
||||
{
|
||||
public readonly MissionPrefab.TriggerEvent TriggerEvent;
|
||||
public float Delay;
|
||||
|
||||
public DelayedTriggerEvent(MissionPrefab.TriggerEvent triggerEvent, float delay)
|
||||
{
|
||||
TriggerEvent = triggerEvent;
|
||||
Delay = delay;
|
||||
}
|
||||
}
|
||||
|
||||
private List<DelayedTriggerEvent> delayedTriggerEvents = new List<DelayedTriggerEvent>();
|
||||
|
||||
public Mission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
{
|
||||
@@ -167,6 +182,9 @@ namespace Barotrauma
|
||||
Messages[m] = Messages[m].Replace("[reward]", rewardText);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetDifficulty(float difficulty) { }
|
||||
|
||||
public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false)
|
||||
{
|
||||
return LoadRandom(locations, new MTRandom(ToolBox.StringToInt(seed)), requireCorrectLocationType, missionType, isSinglePlayer);
|
||||
@@ -216,9 +234,11 @@ namespace Barotrauma
|
||||
|
||||
public void Start(Level level)
|
||||
{
|
||||
state = 0;
|
||||
#if CLIENT
|
||||
shownMessages.Clear();
|
||||
#endif
|
||||
delayedTriggerEvents.Clear();
|
||||
foreach (string categoryToShow in Prefab.UnhideEntitySubCategories)
|
||||
{
|
||||
foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab?.HasSubCategory(categoryToShow) ?? false))
|
||||
@@ -227,12 +247,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
this.level = level;
|
||||
TryTriggerEvents(0);
|
||||
StartMissionSpecific(level);
|
||||
}
|
||||
|
||||
protected virtual void StartMissionSpecific(Level level) { }
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
for (int i = delayedTriggerEvents.Count - 1; i>=0;i--)
|
||||
{
|
||||
delayedTriggerEvents[i].Delay -= deltaTime;
|
||||
if (delayedTriggerEvents[i].Delay <= 0.0f)
|
||||
{
|
||||
TriggerEvent(delayedTriggerEvents[i].TriggerEvent);
|
||||
delayedTriggerEvents.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
UpdateMissionSpecific(deltaTime);
|
||||
}
|
||||
|
||||
protected virtual void UpdateMissionSpecific(float deltaTime) { }
|
||||
|
||||
protected void ShowMessage(int missionState)
|
||||
{
|
||||
@@ -241,6 +276,55 @@ namespace Barotrauma
|
||||
|
||||
partial void ShowMessageProjSpecific(int missionState);
|
||||
|
||||
|
||||
private void TryTriggerEvents(int state)
|
||||
{
|
||||
foreach (var triggerEvent in Prefab.TriggerEvents)
|
||||
{
|
||||
if (triggerEvent.State == state)
|
||||
{
|
||||
TryTriggerEvent(triggerEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the event or adds it to the delayedTriggerEvents it if it has a delay
|
||||
/// </summary>
|
||||
private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger)
|
||||
{
|
||||
if (trigger.Delay > 0)
|
||||
{
|
||||
if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger))
|
||||
{
|
||||
delayedTriggerEvents.Add(new DelayedTriggerEvent(trigger, trigger.Delay));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TriggerEvent(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the event immediately, ignoring any delays
|
||||
/// </summary>
|
||||
private void TriggerEvent(MissionPrefab.TriggerEvent trigger)
|
||||
{
|
||||
var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier.Equals(trigger.EventIdentifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (eventPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Mission \"{Name}\" failed to trigger an event (couldn't find an event with the identifier \"{trigger.EventIdentifier}\").");
|
||||
return;
|
||||
}
|
||||
if (GameMain.GameSession?.EventManager != null)
|
||||
{
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent);
|
||||
newEvent.Init(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End the mission and give a reward if it was completed successfully
|
||||
/// </summary>
|
||||
@@ -344,7 +428,7 @@ namespace Barotrauma
|
||||
{
|
||||
positionToStayIn = WayPoint.GetRandom(SpawnType.Human, null, submarine);
|
||||
}
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync);
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, npcIdentifier: humanPrefab.Identifier, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync);
|
||||
characterInfo.TeamID = teamType;
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace Barotrauma
|
||||
Nest = 0x10,
|
||||
Mineral = 0x20,
|
||||
Combat = 0x40,
|
||||
OutpostDestroy = 0x80,
|
||||
OutpostRescue = 0x100,
|
||||
Escort = 0x200,
|
||||
Pirate = 0x400,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | OutpostDestroy | OutpostRescue | Escort | Pirate
|
||||
AbandonedOutpost = 0x80,
|
||||
Escort = 0x100,
|
||||
Pirate = 0x200,
|
||||
GoTo = 0x400,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo
|
||||
}
|
||||
|
||||
partial class MissionPrefab
|
||||
@@ -37,15 +37,17 @@ namespace Barotrauma
|
||||
{ MissionType.Beacon, typeof(BeaconMission) },
|
||||
{ MissionType.Nest, typeof(NestMission) },
|
||||
{ MissionType.Mineral, typeof(MineralMission) },
|
||||
{ MissionType.OutpostDestroy, typeof(OutpostDestroyMission) },
|
||||
{ MissionType.OutpostRescue, typeof(AbandonedOutpostMission) },
|
||||
{ MissionType.AbandonedOutpost, typeof(AbandonedOutpostMission) },
|
||||
{ MissionType.Escort, typeof(EscortMission) },
|
||||
{ MissionType.Pirate, typeof(PirateMission) }
|
||||
{ MissionType.Pirate, typeof(PirateMission) },
|
||||
{ MissionType.GoTo, typeof(GoToMission) }
|
||||
};
|
||||
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
{ MissionType.Combat, typeof(CombatMission) }
|
||||
};
|
||||
|
||||
public static readonly HashSet<MissionType> HiddenMissionClasses = new HashSet<MissionType>() { MissionType.GoTo };
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
|
||||
@@ -87,6 +89,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly bool IsSideObjective;
|
||||
|
||||
public readonly bool RequireWreck;
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only be received when travelling from Pair.First to Pair.Second
|
||||
/// </summary>
|
||||
@@ -102,6 +106,25 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly List<string> UnhideEntitySubCategories = new List<string>();
|
||||
|
||||
public class TriggerEvent
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string EventIdentifier { get; private set; }
|
||||
|
||||
[Serialize(0, true)]
|
||||
public int State { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float Delay { get; private set; }
|
||||
|
||||
public TriggerEvent(XElement element)
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<TriggerEvent> TriggerEvents = new List<TriggerEvent>();
|
||||
|
||||
public LocationTypeChange LocationTypeChangeOnCompleted;
|
||||
|
||||
public readonly XElement ConfigElement;
|
||||
@@ -160,6 +183,7 @@ namespace Barotrauma
|
||||
Reward = element.GetAttributeInt("reward", 1);
|
||||
AllowRetry = element.GetAttributeBool("allowretry", false);
|
||||
IsSideObjective = element.GetAttributeBool("sideobjective", false);
|
||||
RequireWreck = element.GetAttributeBool("requirewreck", false);
|
||||
Commonness = element.GetAttributeInt("commonness", 1);
|
||||
if (element.GetAttribute("difficulty") != null)
|
||||
{
|
||||
@@ -272,10 +296,19 @@ namespace Barotrauma
|
||||
DataRewards.Add(Tuple.Create(identifier, value, operation));
|
||||
}
|
||||
break;
|
||||
case "triggerevent":
|
||||
TriggerEvents.Add(new TriggerEvent(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string missionTypeName = element.GetAttributeString("type", "");
|
||||
//backwards compatibility
|
||||
if (missionTypeName.Equals("outpostdestroy", StringComparison.OrdinalIgnoreCase) || missionTypeName.Equals("outpostrescue", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
missionTypeName = "AbandonedOutpost";
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(missionTypeName, out Type))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - \"" + missionTypeName + "\" is not a valid mission type.");
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Barotrauma
|
||||
level.LevelObjectManager.PlaceNestObjects(level, cave, nestPosition, nestObjectRadius, nestObjectAmount);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (IsClient)
|
||||
{
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class OutpostDestroyMission : AbandonedOutpostMission
|
||||
{
|
||||
private readonly string itemTag;
|
||||
private readonly XElement itemConfig;
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Targets.Select(t => t.WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Entity> Targets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Entity>();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (items.Any())
|
||||
{
|
||||
return items.Where(it => !it.Removed && it.Condition > 0.0f).Cast<Entity>().Concat(requireKill.Where(c => !c.Removed && !c.IsDead)).Concat(requireRescue);
|
||||
}
|
||||
else
|
||||
{
|
||||
return requireKill.Concat(requireRescue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OutpostDestroyMission(MissionPrefab prefab, Location[] locations, Submarine sub) :
|
||||
base(prefab, locations, sub)
|
||||
{
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
itemTag = prefab.ConfigElement.GetAttributeString("targetitem", "");
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
items.Clear();
|
||||
#if SERVER
|
||||
spawnedItems.Clear();
|
||||
#endif
|
||||
if (!string.IsNullOrEmpty(itemTag))
|
||||
{
|
||||
var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
|
||||
if (!itemsToDestroy.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
items.AddRange(itemsToDestroy);
|
||||
}
|
||||
}
|
||||
if (itemConfig != null && !IsClient)
|
||||
{
|
||||
foreach (XElement element in itemConfig.Elements())
|
||||
{
|
||||
string itemIdentifier = element.GetAttributeString("identifier", "");
|
||||
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null);
|
||||
string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null);
|
||||
ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos(
|
||||
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human | SpawnType.Enemy,
|
||||
moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false));
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub;
|
||||
spawnPoint = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
|
||||
}
|
||||
Vector2 spawnPos = spawnPoint.WorldPosition;
|
||||
if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100)
|
||||
{
|
||||
spawnPos = new Vector2(
|
||||
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50),
|
||||
wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f);
|
||||
}
|
||||
var item = new Item(itemPrefab, spawnPos, null);
|
||||
items.Add(item);
|
||||
#if SERVER
|
||||
spawnedItems.Add(item);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
base.StartMissionSpecific(level);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (requireRescue.Any(r => r.Removed || r.IsDead))
|
||||
{
|
||||
#if SERVER
|
||||
if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.EndGame();
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
if (items.Any())
|
||||
{
|
||||
if (items.All(it => it.Removed || it.Condition <= 0.0f) &&
|
||||
requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#if SERVER
|
||||
case 1:
|
||||
if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null)
|
||||
{
|
||||
if (!Submarine.MainSub.AtStartExit || (wasDocked && !Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost)))
|
||||
{
|
||||
GameMain.Server.EndGame();
|
||||
State = 2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,15 @@ namespace Barotrauma
|
||||
{
|
||||
partial class PirateMission : Mission
|
||||
{
|
||||
private readonly XElement submarineTypeConfig;
|
||||
private readonly XElement characterConfig;
|
||||
private readonly XElement submarineConfig;
|
||||
private readonly XElement characterTypeConfig;
|
||||
private readonly float addedMissionDifficultyPerPlayer;
|
||||
|
||||
private float missionDifficulty;
|
||||
private int alternateReward;
|
||||
|
||||
private Submarine enemySub;
|
||||
private Item reactorItem;
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
private readonly Dictionary<Character, List<Item>> characterDictionary = new Dictionary<Character, List<Item>>();
|
||||
|
||||
@@ -51,17 +55,55 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetReward(Submarine sub)
|
||||
{
|
||||
return alternateReward;
|
||||
}
|
||||
|
||||
private SubmarineInfo submarineInfo;
|
||||
|
||||
public override SubmarineInfo EnemySubmarineInfo => submarineInfo;
|
||||
public override SubmarineInfo EnemySubmarineInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return submarineInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// these values could also be defined within the mission XML
|
||||
private const float RandomnessModifier = 25;
|
||||
private const float ShipRandomnessModifier = 15;
|
||||
|
||||
private const float MaxDifficulty = 100;
|
||||
|
||||
public PirateMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
||||
{
|
||||
submarineConfig = prefab.ConfigElement.Element("Submarine");
|
||||
submarineTypeConfig = prefab.ConfigElement.Element("SubmarineTypes");
|
||||
characterConfig = prefab.ConfigElement.Element("Characters");
|
||||
characterTypeConfig = prefab.ConfigElement.Element("CharacterTypes");
|
||||
addedMissionDifficultyPerPlayer = prefab.ConfigElement.GetAttributeFloat("addedmissiondifficultyperplayer", 0);
|
||||
|
||||
// for campaign missions, set difficulty at construction
|
||||
LevelData levelData = locations[0].Connections.Where(c => c.Locations.Contains(locations[1])).FirstOrDefault()?.LevelData ?? locations[0]?.LevelData;
|
||||
|
||||
SetDifficulty(levelData?.Difficulty ?? Level.Loaded?.Difficulty ?? 0f);
|
||||
}
|
||||
|
||||
public override void SetDifficulty(float difficulty)
|
||||
{
|
||||
if (missionDifficulty > 0f)
|
||||
{
|
||||
// difficulty already set
|
||||
return;
|
||||
}
|
||||
|
||||
missionDifficulty = difficulty;
|
||||
|
||||
XElement submarineConfig = GetRandomDifficultyModifiedElement(submarineTypeConfig, missionDifficulty, ShipRandomnessModifier);
|
||||
|
||||
alternateReward = submarineConfig.GetAttributeInt("alternatereward", Reward);
|
||||
|
||||
string submarineIdentifier = submarineConfig.GetAttributeString("identifier", string.Empty);
|
||||
|
||||
if (submarineIdentifier == string.Empty)
|
||||
{
|
||||
DebugConsole.ThrowError("No identifier used for submarine for pirate mission!");
|
||||
@@ -74,9 +116,36 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("No submarine file found with the identifier!");
|
||||
return;
|
||||
}
|
||||
|
||||
submarineInfo = new SubmarineInfo(contentFile.Path);
|
||||
}
|
||||
|
||||
private float GetDifficultyModifiedValue(float preferredDifficulty, float levelDifficulty, float randomnessModifier)
|
||||
{
|
||||
return Math.Abs(levelDifficulty - preferredDifficulty + (Rand.Range(-randomnessModifier, randomnessModifier, Rand.RandSync.Server)));
|
||||
}
|
||||
private int GetDifficultyModifiedAmount(int minAmount, int maxAmount, float levelDifficulty)
|
||||
{
|
||||
return Math.Max((int)Math.Round(minAmount + (maxAmount - minAmount) * ((levelDifficulty + Rand.Range(-RandomnessModifier, RandomnessModifier, Rand.RandSync.Server)) / MaxDifficulty)), minAmount);
|
||||
}
|
||||
|
||||
private XElement GetRandomDifficultyModifiedElement(XElement parentElement, float levelDifficulty, float randomnessModifier)
|
||||
{
|
||||
// look for the element that is closest to our difficulty, with some randomness
|
||||
XElement bestElement = null;
|
||||
float bestValue = float.MaxValue;
|
||||
foreach (XElement element in parentElement.Elements())
|
||||
{
|
||||
float applicabilityValue = GetDifficultyModifiedValue(element.GetAttributeFloat(0f, "preferreddifficulty"), levelDifficulty, randomnessModifier);
|
||||
if (applicabilityValue < bestValue)
|
||||
{
|
||||
bestElement = element;
|
||||
bestValue = applicabilityValue;
|
||||
}
|
||||
}
|
||||
return bestElement;
|
||||
}
|
||||
|
||||
private void CreateMissionPositions(out Vector2 preferredSpawnPos)
|
||||
{
|
||||
Vector2 patrolPos = enemySub.WorldPosition;
|
||||
@@ -116,7 +185,6 @@ namespace Barotrauma
|
||||
if (enemySub.GetItems(alsoFromConnectedSubs: false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
|
||||
{
|
||||
reactor.PowerUpImmediately();
|
||||
reactorItem = reactor.Item;
|
||||
}
|
||||
enemySub.EnableMaintainPosition();
|
||||
enemySub.SetPosition(spawnPos);
|
||||
@@ -134,21 +202,45 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
int playerCount = 1;
|
||||
|
||||
#if SERVER
|
||||
playerCount = GameMain.Server.ConnectedClients.Where(c => !c.SpectateOnly || !GameMain.Server.ServerSettings.AllowSpectating).Count();
|
||||
#endif
|
||||
|
||||
float enemyCreationDifficulty = missionDifficulty + playerCount * addedMissionDifficultyPerPlayer;
|
||||
|
||||
bool commanderAssigned = false;
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterDictionary, enemySub, CharacterTeamType.None, null);
|
||||
if (!commanderAssigned)
|
||||
// it is possible to get more than the "max" amount of characters if the modified difficulty is high enough; this is intentional
|
||||
// if necessary, another "hard max" value could be used to clamp the value for performance/gameplay concerns
|
||||
int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt("minamount", 0), element.GetAttributeInt("maxamount", 0), enemyCreationDifficulty);
|
||||
for (int i = 0; i < amountCreated; i++)
|
||||
{
|
||||
bool isCommander = element.GetAttributeBool("iscommander", false);
|
||||
if (isCommander && spawnedCharacter.AIController is HumanAIController humanAIController)
|
||||
XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeString("typeidentifier", string.Empty) == element.GetAttributeString("typeidentifier", string.Empty)).FirstOrDefault();
|
||||
|
||||
if (characterType == null)
|
||||
{
|
||||
humanAIController.InitShipCommandManager();
|
||||
foreach (var patrolPos in patrolPositions)
|
||||
DebugConsole.ThrowError("No character types defined in CharacterTypes for a declared type identifier in mission file " + this);
|
||||
return;
|
||||
}
|
||||
|
||||
XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier);
|
||||
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(variantElement), characters, characterDictionary, enemySub, CharacterTeamType.None, null);
|
||||
if (!commanderAssigned)
|
||||
{
|
||||
bool isCommander = variantElement.GetAttributeBool("iscommander", false);
|
||||
if (isCommander && spawnedCharacter.AIController is HumanAIController humanAIController)
|
||||
{
|
||||
humanAIController.ShipCommandManager.patrolPositions.Add(patrolPos);
|
||||
humanAIController.InitShipCommandManager();
|
||||
foreach (var patrolPos in patrolPositions)
|
||||
{
|
||||
humanAIController.ShipCommandManager.patrolPositions.Add(patrolPos);
|
||||
}
|
||||
commanderAssigned = true;
|
||||
}
|
||||
commanderAssigned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +309,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
int newState = State;
|
||||
float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange);
|
||||
@@ -268,7 +360,7 @@ namespace Barotrauma
|
||||
State = newState;
|
||||
}
|
||||
|
||||
private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m)) || reactorItem.Condition <= 0f);
|
||||
private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m)));
|
||||
|
||||
private bool Survived(Character character)
|
||||
{
|
||||
|
||||
@@ -205,7 +205,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
|
||||
@@ -442,7 +442,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!spawnPosType.HasFlag(Level.PositionType.MainPath))
|
||||
{
|
||||
scatterAmount = 100;
|
||||
scatterAmount = 0;
|
||||
}
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
@@ -455,7 +455,7 @@ namespace Barotrauma
|
||||
System.Diagnostics.Debug.Assert(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer, "Clients should not create monster events.");
|
||||
|
||||
Vector2 pos = spawnPos.Value + Rand.Vector(scatterAmount);
|
||||
if (scatterAmount > 100)
|
||||
if (scatterAmount > 0)
|
||||
{
|
||||
if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(pos)))
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Barotrauma
|
||||
//duration of the camera transition at the end of a round
|
||||
protected const float EndTransitionDuration = 5.0f;
|
||||
//there can be no events before this time has passed during the 1st campaign round
|
||||
const float FirstRoundEventDelay = 30.0f;
|
||||
const float FirstRoundEventDelay = 0.0f;
|
||||
|
||||
public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub }
|
||||
|
||||
@@ -113,12 +113,15 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Map.CurrentLocation?.SelectedMission != null)
|
||||
if (Map.CurrentLocation != null)
|
||||
{
|
||||
if (Map.CurrentLocation.SelectedMission.Locations[0] == Map.CurrentLocation.SelectedMission.Locations[1] ||
|
||||
Map.CurrentLocation.SelectedMission.Locations.Contains(Map.SelectedLocation))
|
||||
foreach (Mission mission in map.CurrentLocation.SelectedMissions)
|
||||
{
|
||||
yield return Map.CurrentLocation.SelectedMission;
|
||||
if (mission.Locations[0] == mission.Locations[1] ||
|
||||
mission.Locations.Contains(Map.SelectedLocation))
|
||||
{
|
||||
yield return mission;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Mission mission in extraMissions)
|
||||
@@ -240,23 +243,24 @@ namespace Barotrauma
|
||||
if (levelData.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
//if there's an available mission that takes place in the outpost, select it
|
||||
var availableMissionsInLocation = currentLocation.AvailableMissions.Where(m => m.Locations[0] == currentLocation && m.Locations[1] == currentLocation);
|
||||
if (availableMissionsInLocation.Any())
|
||||
foreach (var availableMission in currentLocation.AvailableMissions)
|
||||
{
|
||||
currentLocation.SelectedMission = availableMissionsInLocation.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentLocation.SelectedMission = null;
|
||||
if (availableMission.Locations[0] == currentLocation && availableMission.Locations[1] == currentLocation)
|
||||
{
|
||||
currentLocation.SelectMission(availableMission);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//if we had selected a mission that takes place in the outpost, deselect it when leaving the outpost
|
||||
if (currentLocation.SelectedMission?.Locations[0] == currentLocation &&
|
||||
currentLocation.SelectedMission?.Locations[1] == currentLocation)
|
||||
foreach (Mission mission in currentLocation.SelectedMissions.ToList())
|
||||
{
|
||||
currentLocation.SelectedMission = null;
|
||||
//if we had selected a mission that takes place in the outpost, deselect it when leaving the outpost
|
||||
if (mission.Locations[0] == currentLocation &&
|
||||
mission.Locations[1] == currentLocation)
|
||||
{
|
||||
currentLocation.DeselectMission(mission);
|
||||
}
|
||||
}
|
||||
|
||||
if (levelData.HasBeaconStation && !levelData.IsBeaconActive)
|
||||
@@ -851,11 +855,14 @@ namespace Barotrauma
|
||||
DebugConsole.NewMessage(" " + i + ". " + destination.Name, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
if (map.CurrentLocation?.SelectedMission != null)
|
||||
|
||||
if (map.CurrentLocation != null)
|
||||
{
|
||||
DebugConsole.NewMessage(" Selected mission: " + map.CurrentLocation.SelectedMission.Name, Color.White);
|
||||
DebugConsole.NewMessage("\n" + map.CurrentLocation.SelectedMission.Description, Color.White);
|
||||
foreach (Mission mission in map.CurrentLocation.SelectedMissions)
|
||||
{
|
||||
DebugConsole.NewMessage(" Selected mission: " + mission.Name, Color.White);
|
||||
DebugConsole.NewMessage("\n" + mission.Description, Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,18 @@ namespace Barotrauma
|
||||
|
||||
private GameMode InstantiateGameMode(GameModePreset gameModePreset, string seed, SubmarineInfo selectedSub, CampaignSettings settings, IEnumerable<MissionPrefab> missionPrefabs = null, MissionType missionType = MissionType.None)
|
||||
{
|
||||
if (gameModePreset.GameModeType == typeof(CoOpMode) || gameModePreset.GameModeType == typeof(PvPMode))
|
||||
{
|
||||
//don't allow hidden mission types (e.g. GoTo) in single mission modes
|
||||
var missionTypes = (MissionType[])Enum.GetValues(typeof(MissionType));
|
||||
for (int i = 0; i < missionTypes.Length; i++)
|
||||
{
|
||||
if (MissionPrefab.HiddenMissionClasses.Contains(missionTypes[i]))
|
||||
{
|
||||
missionType &= ~missionTypes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gameModePreset.GameModeType == typeof(CoOpMode))
|
||||
{
|
||||
return missionPrefabs != null ?
|
||||
@@ -354,6 +366,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Mission mission in GameMode.Missions)
|
||||
{
|
||||
// setting difficulty for missions that may involve difficulty-related submarine creation
|
||||
mission.SetDifficulty(levelData?.Difficulty ?? 0f);
|
||||
}
|
||||
|
||||
if (Submarine.MainSubs[1] == null)
|
||||
{
|
||||
var enemySubmarineInfo = GameMode is PvPMode ? SubmarineInfo : GameMode.Missions.FirstOrDefault(m => m.EnemySubmarineInfo != null)?.EnemySubmarineInfo;
|
||||
|
||||
@@ -91,7 +91,6 @@ namespace Barotrauma
|
||||
|
||||
private CampaignMetadata Metadata => Campaign.CampaignMetadata;
|
||||
private readonly CampaignMode Campaign;
|
||||
private int spentMoney;
|
||||
|
||||
public event Action? OnUpgradesChanged;
|
||||
|
||||
@@ -125,7 +124,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public int DetermineItemSwapCost(Item item, ItemPrefab replacement)
|
||||
public int DetermineItemSwapCost(Item item, ItemPrefab? replacement)
|
||||
{
|
||||
if (replacement == null)
|
||||
{
|
||||
@@ -226,7 +225,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Campaign.Money -= price;
|
||||
spentMoney += price;
|
||||
|
||||
PurchasedUpgrade? upgrade = FindMatchingUpgrade(prefab, category);
|
||||
|
||||
@@ -270,17 +268,22 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Cannot swap null item!");
|
||||
return;
|
||||
}
|
||||
if (itemToRemove.HiddenInGame)
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot swap item \"{itemToRemove.Name}\" because it's set to be hidden in-game.");
|
||||
return;
|
||||
}
|
||||
if (!itemToRemove.AllowSwapping)
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot swap item \"{itemToRemove.Name}\" because it's configured to be non-swappable.");
|
||||
return;
|
||||
}
|
||||
if (!UpgradeCategory.Categories.Any(c => c.ItemTags.Any(t => itemToRemove.HasTag(t)) && c.ItemTags.Any(t => itemToInstall.Tags.Contains(t))))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to swap item \"{itemToRemove.Name}\" with \"{itemToInstall.Name}\" (not in the same upgrade category).");
|
||||
return;
|
||||
}
|
||||
|
||||
/*if (itemToRemove.PendingItemSwap != null)
|
||||
{
|
||||
CancelItemSwap(itemToRemove);
|
||||
}
|
||||
else */
|
||||
if (itemToRemove.prefab == itemToInstall)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to swap item \"{itemToRemove.Name}\" (trying to swap with the same item!).");
|
||||
@@ -318,7 +321,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Campaign.Money -= price;
|
||||
spentMoney += price;
|
||||
|
||||
itemToRemove.AvailableSwaps.Add(itemToRemove.Prefab);
|
||||
if (itemToInstall != null) { itemToRemove.AvailableSwaps.Add(itemToInstall); }
|
||||
@@ -436,36 +438,12 @@ namespace Barotrauma
|
||||
{
|
||||
int newLevel = BuyUpgrade(prefab, category, Submarine.MainSub, level);
|
||||
DebugConsole.Log($" - {category.Identifier}.{prefab.Identifier} lvl. {level}, new: ({newLevel})");
|
||||
if (newLevel > 0)
|
||||
{
|
||||
SetUpgradeLevel(prefab, category, Math.Clamp(newLevel, 0, prefab.MaxLevel));
|
||||
}
|
||||
SetUpgradeLevel(prefab, category, Math.Clamp(level, 0, prefab.MaxLevel));
|
||||
}
|
||||
|
||||
PendingUpgrades.Clear();
|
||||
loadedUpgrades?.Clear();
|
||||
loadedUpgrades = null;
|
||||
spentMoney = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the pending upgrades and refunds the money spent
|
||||
/// </summary>
|
||||
private void RefundUpgrades()
|
||||
{
|
||||
DebugConsole.Log($"Refunded {spentMoney} marks in pending upgrades.");
|
||||
if (spentMoney > 0)
|
||||
{
|
||||
#if CLIENT
|
||||
GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("UpgradeRefundTitle"), TextManager.Get("UpgradeRefundBody"), new[] { TextManager.Get("Ok") });
|
||||
msgBox.Buttons[0].OnClicked += msgBox.Close;
|
||||
#endif
|
||||
}
|
||||
|
||||
Campaign.Money += spentMoney;
|
||||
spentMoney = 0;
|
||||
PendingUpgrades.Clear();
|
||||
PurchasedUpgrades.Clear();
|
||||
}
|
||||
|
||||
public void CreateUpgradeErrorMessage(string text, bool isSinglePlayer, Character character)
|
||||
@@ -523,7 +501,7 @@ namespace Barotrauma
|
||||
|
||||
if (upgrade == null || upgrade.Level != level || isOverMax)
|
||||
{
|
||||
DebugConsole.AddWarning($"{wall.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}. Fixing...");
|
||||
DebugLog($"{wall.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}. Fixing...");
|
||||
FixUpgradeOnItem(wall, prefab, level);
|
||||
}
|
||||
}
|
||||
@@ -552,7 +530,7 @@ namespace Barotrauma
|
||||
|
||||
if (upgrade == null || upgrade.Level != level || isOverMax)
|
||||
{
|
||||
DebugConsole.AddWarning($"{item.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}{(isOverMax ? " (Over max level!)" : string.Empty)}. Fixing...");
|
||||
DebugLog($"{item.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}{(isOverMax ? " (Over max level!)" : string.Empty)}. Fixing...");
|
||||
FixUpgradeOnItem(item, prefab, level);
|
||||
}
|
||||
}
|
||||
@@ -695,112 +673,6 @@ namespace Barotrauma
|
||||
return Campaign.PendingSubmarineSwitch == null;
|
||||
}
|
||||
|
||||
public void RefundResetAndReload(SubmarineInfo newSubmarine, bool notifyClients = false)
|
||||
{
|
||||
RefundUpgrades();
|
||||
ResetUpgrades();
|
||||
Dictionary<string, int> newUpgrades = ReloadUpgradeValues(newSubmarine);
|
||||
#if SERVER
|
||||
if (notifyClients)
|
||||
{
|
||||
SendUpgradeResetMessage(newUpgrades);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a SubmarineInfo and sets the store values accordingly.
|
||||
/// Used when reloading a previously saved submarine.
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
private Dictionary<string, int> ReloadUpgradeValues(SubmarineInfo info)
|
||||
{
|
||||
Dictionary<string, int> newValues = new Dictionary<string, int>();
|
||||
IEnumerable<XElement> linkedSubElements = info.SubmarineElement.Elements().Where(element => element.Name.ToString().Equals("LinkedSubmarine", StringComparison.OrdinalIgnoreCase)).SelectMany(element => element.Elements());
|
||||
IEnumerable<XElement> mainSubElements = info.SubmarineElement.Elements().Where(Predicate);
|
||||
List<XElement> elements = mainSubElements.Concat(linkedSubElements.Where(Predicate)).ToList();
|
||||
foreach (UpgradeCategory category in UpgradeCategory.Categories)
|
||||
{
|
||||
foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs)
|
||||
{
|
||||
if (!prefab.UpgradeCategories.Contains(category)) { continue; }
|
||||
|
||||
List<int> levels = GetUpgradeFromXML(elements, category, prefab);
|
||||
if (levels.Any())
|
||||
{
|
||||
int level = (int) levels.Average(i => i);
|
||||
newValues.Add(FormatIdentifier(prefab, category), level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (dataIdentifier, level) in newValues)
|
||||
{
|
||||
Campaign.CampaignMetadata.SetValue(dataIdentifier, level);
|
||||
}
|
||||
|
||||
return newValues;
|
||||
|
||||
static List<int> GetUpgradeFromXML(List<XElement> elements, UpgradeCategory category, UpgradePrefab prefab)
|
||||
{
|
||||
List<int> levels = new List<int>();
|
||||
foreach (XElement subElement in elements)
|
||||
{
|
||||
if (!category.CanBeApplied(subElement, prefab)) { continue; }
|
||||
|
||||
foreach (XElement component in subElement.Elements())
|
||||
{
|
||||
if (string.Equals(component.Name.ToString(), "upgrade", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string identifier = component.GetAttributeString("identifier", string.Empty);
|
||||
int level = component.GetAttributeInt("level", -1);
|
||||
if (string.IsNullOrWhiteSpace(identifier) || level <= 0) { continue; }
|
||||
|
||||
UpgradePrefab? matchingPrefab = UpgradePrefab.Find(identifier);
|
||||
if (matchingPrefab == null || matchingPrefab != prefab) { continue; }
|
||||
|
||||
if (matchingPrefab.UpgradeCategories.Contains(category)) { levels.Add(level); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return levels;
|
||||
}
|
||||
|
||||
static bool Predicate(XElement element) => element.HasElements && element.Elements().Any(e => e.Name.ToString().Equals("upgrade", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resets our upgrade progress and prices.
|
||||
/// This does not actually remove the upgrades from the submarine but resets the store interface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method works by iterating thru all upgrade categories and prefabs and checking if they have a
|
||||
/// valid key stored in the metadata, if they do set it to 0, upgrades without a key stored are always
|
||||
/// assumed to be 0 so they don't need to be reset.
|
||||
///
|
||||
/// Should initially be called server side as we can't trust clients with such a simple notification.
|
||||
/// </remarks>
|
||||
private void ResetUpgrades()
|
||||
{
|
||||
foreach (UpgradeCategory category in UpgradeCategory.Categories)
|
||||
{
|
||||
foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs)
|
||||
{
|
||||
if (!prefab.UpgradeCategories.Contains(category)) { continue; }
|
||||
|
||||
string dataIdentifier = FormatIdentifier(prefab, category);
|
||||
if (Metadata.HasKey(dataIdentifier))
|
||||
{
|
||||
Metadata.SetValue(dataIdentifier, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnUpgradesChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void Save(XElement? parent)
|
||||
{
|
||||
if (parent == null) { return; }
|
||||
@@ -877,34 +749,10 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError(error.TrimEnd('\n'), e);
|
||||
}
|
||||
|
||||
public static Dictionary<string, int> GetMetadataLevels(CampaignMetadata? metadata)
|
||||
{
|
||||
Dictionary<string, int> values = new Dictionary<string, int>();
|
||||
|
||||
if (metadata == null) { return values; }
|
||||
|
||||
foreach (UpgradeCategory category in UpgradeCategory.Categories)
|
||||
{
|
||||
foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs)
|
||||
{
|
||||
string identifier = FormatIdentifier(prefab, category);
|
||||
if (metadata.HasKey(identifier) && !values.ContainsKey(identifier))
|
||||
{
|
||||
values.Add(identifier, metadata.GetInt(identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to sync the pending upgrades list in multiplayer.
|
||||
/// </summary>
|
||||
/// <param name="upgrades"></param>
|
||||
/// <remarks>
|
||||
/// In singleplayer this is not used and should not be.
|
||||
/// </remarks>
|
||||
public void SetPendingUpgrades(List<PurchasedUpgrade> upgrades)
|
||||
{
|
||||
PendingUpgrades.Clear();
|
||||
|
||||
@@ -887,10 +887,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null)
|
||||
{
|
||||
wire.Drop(null);
|
||||
}
|
||||
wire?.Drop(null);
|
||||
|
||||
if (joint != null)
|
||||
{
|
||||
@@ -991,7 +988,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (DockingTarget.Door != null && doorBody != null)
|
||||
{
|
||||
doorBody.Enabled = DockingTarget.Door.Body.Enabled;
|
||||
doorBody.Enabled = DockingTarget.Door.Body.Enabled && !(DockingTarget.Door.Body.FarseerBody.FixtureList.FirstOrDefault()?.IsSensor ?? false);
|
||||
}
|
||||
dockingState = MathHelper.Lerp(dockingState, 1.0f, deltaTime * 10.0f);
|
||||
}
|
||||
|
||||
@@ -259,6 +259,10 @@ namespace Barotrauma.Items.Components
|
||||
Body.SetTransformIgnoreContacts(
|
||||
ConvertUnits.ToSimUnits(new Vector2(doorRect.Center.X, doorRect.Y - doorRect.Height / 2)),
|
||||
0.0f);
|
||||
if (isBroken)
|
||||
{
|
||||
DisableBody();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
|
||||
@@ -440,7 +440,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (item.CurrentHull == null)
|
||||
{
|
||||
return attachTargetCell != null && Structure.GetAttachTarget(item.WorldPosition) != null;
|
||||
return attachTargetCell != null || Structure.GetAttachTarget(item.WorldPosition) != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -548,6 +548,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is LevelObject levelObject && levelObject.Prefab.TakeLevelWallDamage)
|
||||
{
|
||||
levelObject.AddDamage(-LevelWallFixAmount, deltaTime, item);
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter.Removed) { return false; }
|
||||
|
||||
@@ -290,10 +290,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
for (int i = 0; i < ingredient.Amount; i++)
|
||||
{
|
||||
var availableItem = availableIngredients.FirstOrDefault(it => it != null && ingredient.ItemPrefabs.Contains(it.Prefab) && it.ConditionPercentage >= ingredient.MinCondition * 100.0f);
|
||||
var availableItem = availableIngredients.FirstOrDefault(it =>
|
||||
it != null && ingredient.ItemPrefabs.Contains(it.Prefab) &&
|
||||
it.ConditionPercentage >= ingredient.MinCondition * 100.0f &&
|
||||
it.ConditionPercentage <= ingredient.MaxCondition * 100.0f);
|
||||
if (availableItem == null) { continue; }
|
||||
|
||||
//Item4 = use condition bool
|
||||
|
||||
if (ingredient.UseCondition && availableItem.ConditionPercentage - ingredient.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
|
||||
{
|
||||
availableItem.Condition -= availableItem.Prefab.Health * ingredient.MinCondition;
|
||||
@@ -493,7 +495,8 @@ namespace Barotrauma.Items.Components
|
||||
return
|
||||
item != null &&
|
||||
requiredItem.ItemPrefabs.Contains(item.prefab) &&
|
||||
item.Condition / item.Prefab.Health >= requiredItem.MinCondition;
|
||||
item.Condition / item.Prefab.Health >= requiredItem.MinCondition &&
|
||||
item.Condition / item.Prefab.Health <= requiredItem.MaxCondition;
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
|
||||
@@ -242,7 +242,7 @@ namespace Barotrauma.Items.Components
|
||||
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
|
||||
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
|
||||
|
||||
optimalTemperature = Vector2.Lerp(new Vector2(40.0f, 60.0f), new Vector2(30.0f, 70.0f), degreeOfSuccess);
|
||||
allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess);
|
||||
|
||||
@@ -314,6 +314,33 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadQueue.Any() && PowerOn)
|
||||
{
|
||||
//loadQueue is empty, round must've just started
|
||||
//reset the fission rate, turbine output and
|
||||
//temperature to optimal levels to prevent fires
|
||||
//at the start of the round
|
||||
correctTurbineOutput = currentLoad / MaxPowerOutput * 100.0f;
|
||||
tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
|
||||
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
|
||||
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
|
||||
float desiredTurbineOutput = MathHelper.Clamp(correctTurbineOutput, 0.0f, 100.0f);
|
||||
DebugConsole.Log($"Turbine output reset: {targetTurbineOutput}, {turbineOutput} -> {desiredTurbineOutput}");
|
||||
targetTurbineOutput = desiredTurbineOutput;
|
||||
turbineOutput = desiredTurbineOutput;
|
||||
|
||||
float desiredFissionRate = (optimalFissionRate.X + optimalFissionRate.Y) / 2.0f;
|
||||
DebugConsole.Log($"Fission rate reset: {targetFissionRate}, {fissionRate} -> {desiredFissionRate}");
|
||||
targetFissionRate = desiredFissionRate;
|
||||
fissionRate = desiredFissionRate;
|
||||
|
||||
float desiredTemperature = (optimalTemperature.X + optimalTemperature.Y) / 2.0f;
|
||||
DebugConsole.Log($"Temperature reset: {temperature} -> {desiredTemperature}");
|
||||
temperature = desiredTemperature;
|
||||
}
|
||||
|
||||
loadQueue.Enqueue(currentLoad);
|
||||
while (loadQueue.Count() > 60.0f)
|
||||
{
|
||||
@@ -619,6 +646,18 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(Item, character))
|
||||
{
|
||||
if (Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
|
||||
{
|
||||
objective.AddSubObjective(new AIObjectiveRepairItem(character, Item, objective.objectiveManager, isPriority: true));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogReactorIsBroken"), identifier: "reactorisbroken", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
}
|
||||
if (TooMuchFuel())
|
||||
{
|
||||
DropFuel(minCondition: 0.1f, maxCondition: 100);
|
||||
|
||||
@@ -705,6 +705,20 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
user = character;
|
||||
|
||||
if (Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(Item, character))
|
||||
{
|
||||
if (Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
|
||||
{
|
||||
objective.AddSubObjective(new AIObjectiveRepairItem(character, Item, objective.objectiveManager, isPriority: true));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNavTerminalIsBroken"), identifier: "navterminalisbroken", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!AutoPilot)
|
||||
{
|
||||
unsentChanges = true;
|
||||
|
||||
@@ -171,6 +171,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Override random spread with static spread; hitscan are launched with an equal amount of angle between them. Only applies when firing multiple hitscan.")]
|
||||
public bool StaticSpread
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Body StickTarget
|
||||
{
|
||||
get;
|
||||
@@ -267,9 +274,24 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character != null && !characterUsable) { return false; }
|
||||
|
||||
|
||||
for (int i = 0; i < HitScanCount; i++)
|
||||
{
|
||||
float launchAngle = item.body.Rotation + MathHelper.ToRadians(Spread * Rand.Range(-0.5f, 0.5f));
|
||||
float launchAngle = 0f;
|
||||
|
||||
if (StaticSpread)
|
||||
{
|
||||
float staticSpread = Spread / (HitScanCount - 1);
|
||||
// because the position of the item changes as hitscan are fired, we will set an
|
||||
// initial offset on the first hitscan and then increase the item's angle by a set amount as hitscan are fired
|
||||
float offset = i == 0 ? -staticSpread * (HitScanCount -1) : 0f;
|
||||
launchAngle = item.body.Rotation + MathHelper.ToRadians(staticSpread + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
launchAngle = item.body.Rotation + MathHelper.ToRadians(Spread * Rand.Range(-0.5f, 0.5f));
|
||||
}
|
||||
|
||||
Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle));
|
||||
if (Hitscan)
|
||||
{
|
||||
@@ -430,8 +452,15 @@ namespace Barotrauma.Items.Components
|
||||
var aabb = new FarseerPhysics.Collision.AABB(rayStart - Vector2.One * 0.001f, rayStart + Vector2.One * 0.001f);
|
||||
GameMain.World.QueryAABB((fixture) =>
|
||||
{
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return true; }
|
||||
if (fixture?.Body.UserData is LevelObject levelObj)
|
||||
{
|
||||
if (!levelObj.Prefab.TakeLevelWallDamage) { return true; }
|
||||
}
|
||||
else if (fixture?.Body == null || fixture.IsSensor)
|
||||
{
|
||||
//ignore sensors and items
|
||||
return true;
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return true; }
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; }
|
||||
if (fixture.Body.UserData as string == "ruinroom") { return true; }
|
||||
@@ -439,6 +468,7 @@ namespace Barotrauma.Items.Components
|
||||
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
|
||||
if (submarine != null)
|
||||
{
|
||||
if (fixture.Body.UserData is VoronoiCell) { return true; }
|
||||
if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return true; }
|
||||
}
|
||||
|
||||
@@ -459,7 +489,15 @@ namespace Barotrauma.Items.Components
|
||||
GameMain.World.RayCast((fixture, point, normal, fraction) =>
|
||||
{
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return -1; }
|
||||
if (fixture?.Body.UserData is LevelObject levelObj)
|
||||
{
|
||||
if (!levelObj.Prefab.TakeLevelWallDamage) { return -1; }
|
||||
}
|
||||
else if (fixture?.Body == null || fixture.IsSensor)
|
||||
{
|
||||
//ignore sensors and items
|
||||
return -1;
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return -1; }
|
||||
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
|
||||
@@ -473,21 +511,40 @@ namespace Barotrauma.Items.Components
|
||||
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
|
||||
if (submarine != null)
|
||||
{
|
||||
if (fixture.Body.UserData is VoronoiCell) { return -1; }
|
||||
if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return -1; }
|
||||
}
|
||||
|
||||
//ignore level cells if the item and the point of impact are inside a sub
|
||||
if (fixture.Body.UserData is VoronoiCell && this.item.Submarine != null)
|
||||
if (fixture.Body.UserData is VoronoiCell)
|
||||
{
|
||||
if (Hull.FindHull(ConvertUnits.ToDisplayUnits(point), this.item.CurrentHull) != null)
|
||||
if (Hull.FindHull(ConvertUnits.ToDisplayUnits(point), this.item.CurrentHull) != null && this.item.Submarine != null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (hits.Count > 50)
|
||||
{
|
||||
float furthestHit = 0.0f;
|
||||
int furthestHitIndex = -1;
|
||||
for (int i = 0; i < hits.Count; i++)
|
||||
{
|
||||
if (hits[i].Fraction > furthestHit)
|
||||
{
|
||||
furthestHitIndex = i;
|
||||
furthestHit = hits[i].Fraction;
|
||||
}
|
||||
}
|
||||
if (furthestHitIndex > -1)
|
||||
{
|
||||
hits.RemoveAt(furthestHitIndex);
|
||||
}
|
||||
}
|
||||
|
||||
hits.Add(new HitscanResult(fixture, point, normal, fraction));
|
||||
|
||||
return hits.Count < 25 ? 1 : 0;
|
||||
return 1;
|
||||
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel);
|
||||
|
||||
return hits;
|
||||
@@ -590,11 +647,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (target.Body.UserData is Limb limb)
|
||||
{
|
||||
//severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive)
|
||||
if (limb.IsSevered)
|
||||
{
|
||||
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
|
||||
return true;
|
||||
//push the severed limb around a bit, but let the projectile pass through it
|
||||
limb.body?.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass * 0.1f, item.SimPosition);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (target.Body.UserData is Item item)
|
||||
@@ -654,9 +711,7 @@ namespace Barotrauma.Items.Components
|
||||
projectileNewSpeed = 1f;
|
||||
projectileDeflectedNewSpeed = 0.8f;
|
||||
}
|
||||
//severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive)
|
||||
if (limb.IsSevered) { return true; }
|
||||
if (limb.character == null || limb.character.Removed) { return false; }
|
||||
if (limb.IsSevered || limb.character == null || limb.character.Removed) { return false; }
|
||||
|
||||
limb.character.LastDamageSource = item;
|
||||
if (Attack != null) { attackResult = Attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f); }
|
||||
@@ -674,7 +729,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (Attack != null) { attackResult = Attack.DoDamage(User, damageable, item.WorldPosition, 1.0f); }
|
||||
}
|
||||
else if (target.Body.UserData is VoronoiCell voronoiCell && voronoiCell.IsDestructible && Attack != null && Math.Abs(Attack.StructureDamage) > 0.0f)
|
||||
else if (target.Body.UserData is VoronoiCell voronoiCell && voronoiCell.IsDestructible && Attack != null && Math.Abs(Attack.LevelWallDamage) > 0.0f)
|
||||
{
|
||||
if (Level.Loaded?.ExtraWalls.Find(w => w.Body == target.Body) is DestructibleLevelWall destructibleWall)
|
||||
{
|
||||
|
||||
@@ -197,19 +197,13 @@ namespace Barotrauma.Items.Components
|
||||
if (Math.Abs(SourcePullForce) > 0.001f)
|
||||
{
|
||||
var sourceBody = GetBodyToPull(source);
|
||||
if (sourceBody != null)
|
||||
{
|
||||
sourceBody.ApplyForce(forceDir * SourcePullForce);
|
||||
}
|
||||
sourceBody?.ApplyForce(forceDir * SourcePullForce);
|
||||
}
|
||||
|
||||
if (Math.Abs(TargetPullForce) > 0.001f)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
if (targetBody != null)
|
||||
{
|
||||
targetBody.ApplyForce(-forceDir * TargetPullForce);
|
||||
}
|
||||
targetBody?.ApplyForce(-forceDir * TargetPullForce);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
public bool TemporarilyLocked
|
||||
{
|
||||
get { return Level.IsLoadedOutpost && item.GetComponent<DockingPort>() != null; }
|
||||
}
|
||||
|
||||
//connection panels can't be deactivated externally (by signals or status effects)
|
||||
public override bool IsActive
|
||||
{
|
||||
|
||||
+24
-24
@@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components
|
||||
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
if (light != null) { light.Range = range; }
|
||||
if (Light != null) { Light.Range = range; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
castShadows = value;
|
||||
#if CLIENT
|
||||
if (light != null) light.CastShadows = value;
|
||||
if (Light != null) Light.CastShadows = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
drawBehindSubs = value;
|
||||
#if CLIENT
|
||||
if (light != null) light.IsBackground = drawBehindSubs;
|
||||
if (Light != null) Light.IsBackground = drawBehindSubs;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
flicker = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.Flicker = flicker; }
|
||||
if (Light != null) { Light.LightSourceParams.Flicker = flicker; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
flickerSpeed = value;
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.FlickerSpeed = flickerSpeed; }
|
||||
if (Light != null) { Light.LightSourceParams.FlickerSpeed = flickerSpeed; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
pulseFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.PulseFrequency = pulseFrequency; }
|
||||
if (Light != null) { Light.LightSourceParams.PulseFrequency = pulseFrequency; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
pulseAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.PulseAmount = pulseAmount; }
|
||||
if (Light != null) { Light.LightSourceParams.PulseAmount = pulseAmount; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.BlinkFrequency = blinkFrequency; }
|
||||
if (Light != null) { Light.LightSourceParams.BlinkFrequency = blinkFrequency; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
lightColor = value;
|
||||
#if CLIENT
|
||||
if (light != null) light.Color = IsActive ? lightColor : Color.Transparent;
|
||||
if (Light != null) Light.Color = IsActive ? lightColor : Color.Transparent;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ namespace Barotrauma.Items.Components
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
#if CLIENT
|
||||
light.Position += amount;
|
||||
Light.Position += amount;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace Barotrauma.Items.Components
|
||||
: base(item, element)
|
||||
{
|
||||
#if CLIENT
|
||||
light = new LightSource(element)
|
||||
Light = new LightSource(element)
|
||||
{
|
||||
ParentSub = item.CurrentHull?.Submarine,
|
||||
Position = item.Position,
|
||||
@@ -206,11 +206,11 @@ namespace Barotrauma.Items.Components
|
||||
SpriteScale = Vector2.One * item.Scale,
|
||||
Range = range
|
||||
};
|
||||
light.LightSourceParams.Flicker = flicker;
|
||||
light.LightSourceParams.FlickerSpeed = FlickerSpeed;
|
||||
light.LightSourceParams.PulseAmount = pulseAmount;
|
||||
light.LightSourceParams.PulseFrequency = pulseFrequency;
|
||||
light.LightSourceParams.BlinkFrequency = blinkFrequency;
|
||||
Light.LightSourceParams.Flicker = flicker;
|
||||
Light.LightSourceParams.FlickerSpeed = FlickerSpeed;
|
||||
Light.LightSourceParams.PulseAmount = pulseAmount;
|
||||
Light.LightSourceParams.PulseFrequency = pulseFrequency;
|
||||
Light.LightSourceParams.BlinkFrequency = blinkFrequency;
|
||||
#endif
|
||||
|
||||
IsActive = IsOn;
|
||||
@@ -233,7 +233,7 @@ namespace Barotrauma.Items.Components
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
#if CLIENT
|
||||
light.ParentSub = item.Submarine;
|
||||
Light.ParentSub = item.Submarine;
|
||||
#endif
|
||||
if (item.Container != null)
|
||||
{
|
||||
@@ -243,23 +243,23 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (ParentBody != null)
|
||||
{
|
||||
light.Position = ParentBody.Position;
|
||||
Light.Position = ParentBody.Position;
|
||||
}
|
||||
else if (turret != null)
|
||||
{
|
||||
light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
|
||||
Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
light.Position = item.Position;
|
||||
Light.Position = item.Position;
|
||||
}
|
||||
#endif
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if (body != null)
|
||||
{
|
||||
#if CLIENT
|
||||
light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
|
||||
light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
|
||||
Light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
|
||||
Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
|
||||
#endif
|
||||
if (!body.Enabled)
|
||||
{
|
||||
@@ -270,8 +270,8 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
light.LightSpriteEffect = item.SpriteEffects;
|
||||
Light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
Light.LightSpriteEffect = item.SpriteEffects;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Barotrauma.Items.Components
|
||||
get
|
||||
{
|
||||
if (GameMain.NetworkMember?.ServerSettings != null && !GameMain.NetworkMember.ServerSettings.AllowRewiring) { return false; }
|
||||
return locked || connections.Any(c => c != null && c.ConnectionPanel.Locked);
|
||||
return locked || connections.Any(c => c != null && (c.ConnectionPanel.Locked || c.ConnectionPanel.TemporarilyLocked));
|
||||
}
|
||||
set { locked = value; }
|
||||
}
|
||||
|
||||
@@ -920,14 +920,14 @@ namespace Barotrauma.Items.Components
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
|
||||
previousTarget.IsDead)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTurretTargetDead"), null, 0.0f, "killedtarget" + previousTarget.ID, 10.0f);
|
||||
character.Speak(TextManager.Get("DialogTurretTargetDead"), identifier: "killedtarget" + previousTarget.ID, minDurationBetweenSimilar: 10.0f);
|
||||
character.AIController.SelectTarget(null);
|
||||
}
|
||||
|
||||
bool canShoot = true;
|
||||
if (!HasPowerToShoot())
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
|
||||
float lowestCharge = 0.0f;
|
||||
PowerContainer batteryToLoad = null;
|
||||
foreach (PowerContainer battery in batteries)
|
||||
@@ -938,15 +938,31 @@ namespace Barotrauma.Items.Components
|
||||
batteryToLoad = battery;
|
||||
lowestCharge = battery.Charge;
|
||||
}
|
||||
if (battery.Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(battery.Item, character))
|
||||
{
|
||||
if (battery.Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
|
||||
{
|
||||
objective.AddSubObjective(new AIObjectiveRepairItem(character, battery.Item, objective.objectiveManager, isPriority: true));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogSupercapacitorIsBroken"), identifier: "supercapacitorisbroken", minDurationBetweenSimilar: 30.0f);
|
||||
canShoot = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (batteryToLoad == null) return true;
|
||||
|
||||
if (batteryToLoad == null) { return true; }
|
||||
if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f)
|
||||
{
|
||||
objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, objective.objectiveManager, option: "", requireEquip: false));
|
||||
return false;
|
||||
}
|
||||
if (lowestCharge <= 0 && batteryToLoad.Item.ConditionPercentage > 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTurretHasNoPower"), identifier: "turrethasnopower", minDurationBetweenSimilar: 30.0f);
|
||||
canShoot = false;
|
||||
}
|
||||
}
|
||||
|
||||
int usableProjectileCount = 0;
|
||||
@@ -983,7 +999,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "cannotloadturret", 30.0f);
|
||||
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "cannotloadturret", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -993,7 +1009,7 @@ namespace Barotrauma.Items.Components
|
||||
loadItemsObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "loadturret", 30.0f);
|
||||
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "loadturret", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
loadItemsObjective.Abandoned += CheckRemainingAmmo;
|
||||
loadItemsObjective.Completed += CheckRemainingAmmo;
|
||||
@@ -1007,11 +1023,11 @@ namespace Barotrauma.Items.Components
|
||||
int remainingAmmo = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(ammoType) && i.Condition > 1);
|
||||
if (remainingAmmo == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get($"DialogOutOf{ammoType}", fallBackTag: "DialogOutOfTurretAmmo"), null, 0.0f, "outofammo", 30.0f);
|
||||
character.Speak(TextManager.Get($"DialogOutOf{ammoType}", fallBackTag: "DialogOutOfTurretAmmo"), identifier: "outofammo", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
else if (remainingAmmo < 3)
|
||||
{
|
||||
character.Speak(TextManager.Get($"DialogLowOn{ammoType}"), null, 0.0f, "outofammo", 30.0f);
|
||||
character.Speak(TextManager.Get($"DialogLowOn{ammoType}"), identifier: "outofammo", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1253,12 +1269,15 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (character.IsOnPlayerTeam)
|
||||
if (canShoot)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f);
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f);
|
||||
}
|
||||
character.SetInput(InputType.Shoot, true, true);
|
||||
}
|
||||
aiTargetingGraceTimer = 5f;
|
||||
character.SetInput(InputType.Shoot, true, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -164,10 +164,7 @@ namespace Barotrauma
|
||||
if (IsInitialized) { return; }
|
||||
_gender = UnassignedSpritePath.Contains("[GENDER]") ? gender : Gender.None;
|
||||
ParsePath(false);
|
||||
if (Sprite != null)
|
||||
{
|
||||
Sprite.Remove();
|
||||
}
|
||||
Sprite?.Remove();
|
||||
Sprite = new Sprite(SourceElement, file: SpritePath);
|
||||
Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true);
|
||||
HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
|
||||
|
||||
@@ -125,8 +125,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!component.AllowInGameEditing) { continue; }
|
||||
if (component.SerializableProperties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any())
|
||||
|| component.SerializableProperties.Values.Any(p => p.Attributes.OfType<ConditionallyEditable>().Any(a => a.IsEditable()))
|
||||
)
|
||||
|| component.SerializableProperties.Values.Any(p => p.Attributes.OfType<ConditionallyEditable>().Any(a => a.IsEditable(this))))
|
||||
{
|
||||
hasInGameEditableProperties = true;
|
||||
break;
|
||||
@@ -198,6 +197,13 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.IsSwappableItem), Serialize(true, true, alwaysUseInstanceValues: true)]
|
||||
public bool AllowSwapping
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks both <see cref="NonInteractable"/> and <see cref="NonPlayerTeamInteractable"/>
|
||||
/// </summary>
|
||||
@@ -1084,10 +1090,7 @@ namespace Barotrauma
|
||||
|
||||
public void RemoveContained(Item contained)
|
||||
{
|
||||
if (ownInventory != null)
|
||||
{
|
||||
ownInventory.RemoveItem(contained);
|
||||
}
|
||||
ownInventory?.RemoveItem(contained);
|
||||
|
||||
contained.Container = null;
|
||||
}
|
||||
@@ -2043,6 +2046,8 @@ namespace Barotrauma
|
||||
|
||||
public bool TryInteract(Character picker, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceActionKey = false)
|
||||
{
|
||||
if (CampaignInteractionType != CampaignMode.InteractionType.None) { return false; }
|
||||
|
||||
bool picked = false, selected = false;
|
||||
#if CLIENT
|
||||
bool hasRequiredSkills = true;
|
||||
@@ -2467,7 +2472,7 @@ namespace Barotrauma
|
||||
private List<Pair<object, SerializableProperty>> GetInGameEditableProperties()
|
||||
{
|
||||
return GetProperties<ConditionallyEditable>()
|
||||
.Where(ce => ce.Second.GetAttribute<ConditionallyEditable>().IsEditable())
|
||||
.Where(ce => ce.Second.GetAttribute<ConditionallyEditable>().IsEditable(this))
|
||||
.Union(GetProperties<InGameEditable>()).ToList();
|
||||
}
|
||||
|
||||
@@ -2746,7 +2751,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (usePrefabValues)
|
||||
if (usePrefabValues && appliedSwap == null)
|
||||
{
|
||||
//use prefab scale when overriding a non-overridden item or vice versa
|
||||
item.Scale = prefab.ConfigElement.GetAttributeFloat(item.scale, "scale", "Scale");
|
||||
@@ -2764,22 +2769,34 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
float prevRotation = item.Rotation;
|
||||
if (element.GetAttributeBool("flippedx", false)) { item.FlipX(false); }
|
||||
if (element.GetAttributeBool("flippedy", false)) { item.FlipY(false); }
|
||||
item.Rotation = prevRotation;
|
||||
|
||||
if (appliedSwap != null && oldPrefab.SwappableItem != null && prefab.SwappableItem != null)
|
||||
if (appliedSwap != null)
|
||||
{
|
||||
Vector2 oldRelativeOrigin = (oldPrefab.SwappableItem.SwapOrigin - oldPrefab.Size / 2) * element.GetAttributeFloat(item.scale, "scale", "Scale");
|
||||
oldRelativeOrigin.Y = -oldRelativeOrigin.Y;
|
||||
oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, item.rotationRad);
|
||||
Vector2 oldOrigin = centerPos + oldRelativeOrigin;
|
||||
item.SpriteDepth = element.GetAttributeFloat("spritedepth", item.SpriteDepth);
|
||||
item.SpriteColor = element.GetAttributeColor("spritecolor", item.SpriteColor);
|
||||
item.Rotation = element.GetAttributeFloat("rotation", item.Rotation);
|
||||
|
||||
Vector2 relativeOrigin = (prefab.SwappableItem.SwapOrigin - prefab.Size / 2) * item.Scale;
|
||||
relativeOrigin.Y = -relativeOrigin.Y;
|
||||
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, item.rotationRad);
|
||||
Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin;
|
||||
float scaleRelativeToPrefab = element.GetAttributeFloat(item.scale, "scale", "Scale") / oldPrefab.Scale;
|
||||
item.Scale *= scaleRelativeToPrefab;
|
||||
|
||||
item.rect.Location -= (origin - oldOrigin).ToPoint();
|
||||
if (oldPrefab.SwappableItem != null && prefab.SwappableItem != null)
|
||||
{
|
||||
Vector2 oldRelativeOrigin = (oldPrefab.SwappableItem.SwapOrigin - oldPrefab.Size / 2) * element.GetAttributeFloat(item.scale, "scale", "Scale");
|
||||
oldRelativeOrigin.Y = -oldRelativeOrigin.Y;
|
||||
oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, item.rotationRad);
|
||||
Vector2 oldOrigin = centerPos + oldRelativeOrigin;
|
||||
|
||||
Vector2 relativeOrigin = (prefab.SwappableItem.SwapOrigin - prefab.Size / 2) * item.Scale;
|
||||
relativeOrigin.Y = -relativeOrigin.Y;
|
||||
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, item.rotationRad);
|
||||
Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin;
|
||||
|
||||
item.rect.Location -= (origin - oldOrigin).ToPoint();
|
||||
}
|
||||
}
|
||||
|
||||
float condition = element.GetAttributeFloat("condition", item.MaxCondition);
|
||||
|
||||
@@ -3,6 +3,7 @@ using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -39,21 +40,24 @@ namespace Barotrauma
|
||||
public readonly List<ItemPrefab> ItemPrefabs;
|
||||
public int Amount;
|
||||
public readonly float MinCondition;
|
||||
public readonly float MaxCondition;
|
||||
public readonly bool UseCondition;
|
||||
|
||||
public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, bool useCondition)
|
||||
public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition)
|
||||
{
|
||||
ItemPrefabs = new List<ItemPrefab>() { itemPrefab };
|
||||
Amount = amount;
|
||||
MinCondition = minCondition;
|
||||
MaxCondition = maxCondition;
|
||||
UseCondition = useCondition;
|
||||
}
|
||||
|
||||
public RequiredItem(IEnumerable<ItemPrefab> itemPrefabs, int amount, float minCondition, bool useCondition)
|
||||
public RequiredItem(IEnumerable<ItemPrefab> itemPrefabs, int amount, float minCondition, float maxCondition, bool useCondition)
|
||||
{
|
||||
ItemPrefabs = new List<ItemPrefab>(itemPrefabs);
|
||||
Amount = amount;
|
||||
MinCondition = minCondition;
|
||||
MaxCondition = maxCondition;
|
||||
UseCondition = useCondition;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +75,7 @@ namespace Barotrauma
|
||||
{
|
||||
TargetItem = itemPrefab;
|
||||
string displayName = element.GetAttributeString("displayname", "");
|
||||
DisplayName = string.IsNullOrEmpty(displayName) ? itemPrefab.Name : TextManager.Get($"DisplayName.{displayName}");
|
||||
DisplayName = string.IsNullOrEmpty(displayName) ? itemPrefab.Name : TextManager.GetWithVariable($"DisplayName.{displayName}", "[itemname]", itemPrefab.Name);
|
||||
|
||||
SuitableFabricatorIdentifiers = element.GetAttributeStringArray("suitablefabricators", new string[0]);
|
||||
|
||||
@@ -107,6 +111,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f);
|
||||
float maxCondition = subElement.GetAttributeFloat("maxcondition", 1.0f);
|
||||
//Substract mincondition from required item's condition or delete it regardless?
|
||||
bool useCondition = subElement.GetAttributeBool("usecondition", true);
|
||||
int count = subElement.GetAttributeInt("count", 1);
|
||||
@@ -119,10 +124,12 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
var existing = RequiredItems.Find(r => r.ItemPrefabs.Count == 1 && r.ItemPrefabs[0] == requiredItem && MathUtils.NearlyEqual(r.MinCondition, minCondition));
|
||||
var existing = RequiredItems.Find(r =>
|
||||
r.ItemPrefabs.Count == 1 && r.ItemPrefabs[0] == requiredItem &&
|
||||
MathUtils.NearlyEqual(r.MinCondition, minCondition) && MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
|
||||
if (existing == null)
|
||||
{
|
||||
RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition));
|
||||
RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, maxCondition, useCondition));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -138,10 +145,13 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
var existing = RequiredItems.Find(r => r.ItemPrefabs.SequenceEqual(matchingItems) && MathUtils.NearlyEqual(r.MinCondition, minCondition));
|
||||
var existing = RequiredItems.Find(r =>
|
||||
r.ItemPrefabs.SequenceEqual(matchingItems) &&
|
||||
MathUtils.NearlyEqual(r.MinCondition, minCondition) &&
|
||||
MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
|
||||
if (existing == null)
|
||||
{
|
||||
RequiredItems.Add(new RequiredItem(matchingItems, count, minCondition, useCondition));
|
||||
RequiredItems.Add(new RequiredItem(matchingItems, count, minCondition, maxCondition, useCondition));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -204,6 +214,8 @@ namespace Barotrauma
|
||||
|
||||
public List<(string requiredTag, string swapTo)> ConnectedItemsToSwap = new List<(string requiredTag, string swapTo)>();
|
||||
|
||||
public readonly Sprite SchematicSprite;
|
||||
|
||||
public int GetPrice(Location location = null)
|
||||
{
|
||||
int price = BasePrice;
|
||||
@@ -221,6 +233,9 @@ namespace Barotrauma
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "schematicsprite":
|
||||
SchematicSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "swapconnecteditem":
|
||||
ConnectedItemsToSwap.Add(
|
||||
(subElement.GetAttributeString("tag", ""),
|
||||
@@ -1203,9 +1218,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public List<Tuple<string, PriceInfo>> GetBuyPricesUnder(int maxCost = 0)
|
||||
public ImmutableDictionary<string, PriceInfo> GetBuyPricesUnder(int maxCost = 0)
|
||||
{
|
||||
List<Tuple<string, PriceInfo>> priceLocations = new List<Tuple<string, PriceInfo>>();
|
||||
Dictionary<string, PriceInfo> priceLocations = new Dictionary<string, PriceInfo>();
|
||||
foreach (KeyValuePair<string, PriceInfo> locationPrice in locationPrices)
|
||||
{
|
||||
PriceInfo priceInfo = locationPrice.Value;
|
||||
@@ -1220,19 +1235,19 @@ namespace Barotrauma
|
||||
}
|
||||
if (priceInfo.Price < maxCost || maxCost == 0)
|
||||
{
|
||||
priceLocations.Add(new Tuple<string, PriceInfo>(locationPrice.Key, priceInfo));
|
||||
priceLocations.Add(locationPrice.Key, priceInfo);
|
||||
}
|
||||
}
|
||||
return priceLocations;
|
||||
return priceLocations.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public List<Tuple<string, PriceInfo>> GetSellPricesOver(int minCost = 0, bool sellingImportant = true)
|
||||
public ImmutableDictionary<string, PriceInfo> GetSellPricesOver(int minCost = 0, bool sellingImportant = true)
|
||||
{
|
||||
List<Tuple<string, PriceInfo>> priceLocations = new List<Tuple<string, PriceInfo>>();
|
||||
Dictionary<string, PriceInfo> priceLocations = new Dictionary<string, PriceInfo>();
|
||||
|
||||
if (!CanBeSold && sellingImportant)
|
||||
{
|
||||
return priceLocations;
|
||||
return priceLocations.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, PriceInfo> locationPrice in locationPrices)
|
||||
@@ -1245,10 +1260,10 @@ namespace Barotrauma
|
||||
}
|
||||
if (priceInfo.Price > minCost)
|
||||
{
|
||||
priceLocations.Add(new Tuple<string, PriceInfo>(locationPrice.Key, priceInfo));
|
||||
priceLocations.Add(locationPrice.Key, priceInfo);
|
||||
}
|
||||
}
|
||||
return priceLocations;
|
||||
return priceLocations.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary)
|
||||
|
||||
@@ -25,12 +25,12 @@ namespace Barotrauma
|
||||
private readonly float screenColorRange, screenColorDuration;
|
||||
|
||||
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
|
||||
private bool playTinnitus;
|
||||
private bool applyFireEffects;
|
||||
private string[] ignoreFireEffectsForTags;
|
||||
private bool ignoreCover;
|
||||
private bool onlyInside;
|
||||
private bool onlyOutside;
|
||||
private readonly Color flashColor;
|
||||
private readonly bool playTinnitus;
|
||||
private readonly bool applyFireEffects;
|
||||
private readonly string[] ignoreFireEffectsForTags;
|
||||
private readonly bool ignoreCover;
|
||||
private readonly bool onlyInside,onlyOutside;
|
||||
private readonly float flashDuration;
|
||||
private readonly float? flashRange;
|
||||
private readonly string decal;
|
||||
@@ -81,6 +81,7 @@ namespace Barotrauma
|
||||
flash = element.GetAttributeBool("flash", true);
|
||||
flashDuration = element.GetAttributeFloat("flashduration", 0.05f);
|
||||
if (element.Attribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); }
|
||||
flashColor = element.GetAttributeColor("flashcolor", Color.LightYellow);
|
||||
|
||||
EmpStrength = element.GetAttributeFloat("empstrength", 0.0f);
|
||||
BallastFloraDamage = element.GetAttributeFloat("ballastfloradamage", 0.0f);
|
||||
@@ -395,7 +396,7 @@ namespace Barotrauma
|
||||
for (int i = 0; i < structure.SectionCount; i++)
|
||||
{
|
||||
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
|
||||
if (distFactor <= 0.0f) continue;
|
||||
if (distFactor <= 0.0f) { continue; }
|
||||
|
||||
structure.AddDamage(i, damage * distFactor, attacker);
|
||||
|
||||
@@ -412,6 +413,19 @@ namespace Barotrauma
|
||||
|
||||
if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f))
|
||||
{
|
||||
if (Level.Loaded?.LevelObjectManager != null)
|
||||
{
|
||||
foreach (var levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(worldPosition, worldRange))
|
||||
{
|
||||
if (levelObject.Prefab.TakeLevelWallDamage)
|
||||
{
|
||||
float distFactor = 1.0f - (Vector2.Distance(levelObject.WorldPosition, worldPosition) / worldRange);
|
||||
if (distFactor <= 0.0f) { continue; }
|
||||
levelObject.AddDamage(levelWallDamage * distFactor, 1.0f, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; }
|
||||
|
||||
@@ -745,19 +745,22 @@ namespace Barotrauma
|
||||
|
||||
Oxygen -= OxygenDeteriorationSpeed * deltaTime;
|
||||
|
||||
if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
|
||||
if (FakeFireSources.Count > 0)
|
||||
{
|
||||
for (int i = FakeFireSources.Count - 1; i >= 0; i--)
|
||||
if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
|
||||
{
|
||||
if (FakeFireSources[i].CausedByPsychosis)
|
||||
for (int i = FakeFireSources.Count - 1; i >= 0; i--)
|
||||
{
|
||||
FakeFireSources[i].Remove();
|
||||
if (FakeFireSources[i].CausedByPsychosis)
|
||||
{
|
||||
FakeFireSources[i].Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
FireSource.UpdateAll(FakeFireSources, deltaTime);
|
||||
}
|
||||
|
||||
FireSource.UpdateAll(FireSources, deltaTime);
|
||||
FireSource.UpdateAll(FakeFireSources, deltaTime);
|
||||
|
||||
foreach (Decal decal in decals)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Barotrauma
|
||||
Cave = 0x4,
|
||||
Ruin = 0x8,
|
||||
Wreck = 0x10,
|
||||
BeaconStation = 0x20,
|
||||
BeaconStation = 0x20, // Not used anywhere
|
||||
Abyss = 0x40,
|
||||
AbyssCave = 0x80
|
||||
}
|
||||
@@ -389,7 +389,7 @@ namespace Barotrauma
|
||||
|
||||
private void Generate(bool mirror)
|
||||
{
|
||||
if (Loaded != null) { Loaded.Remove(); }
|
||||
Loaded?.Remove();
|
||||
Loaded = this;
|
||||
Generating = true;
|
||||
|
||||
@@ -1154,7 +1154,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
CreateWrecks();
|
||||
CreateBeaconStation(cells);
|
||||
CreateBeaconStation();
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
|
||||
|
||||
@@ -3007,20 +3007,30 @@ namespace Barotrauma
|
||||
return originalTag + "_" + shortSeed;
|
||||
}
|
||||
|
||||
public bool IsCloseToStart(Vector2 position, float minDist) => IsCloseToStart(position.ToPoint(), minDist);
|
||||
public bool IsCloseToEnd(Vector2 position, float minDist) => IsCloseToEnd(position.ToPoint(), minDist);
|
||||
|
||||
public bool IsCloseToStart(Point position, float minDist)
|
||||
{
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(StartPosition.ToPoint(), StartExitPosition.ToPoint(), position) < minDist * minDist;
|
||||
}
|
||||
|
||||
public bool IsCloseToEnd(Point position, float minDist)
|
||||
{
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(EndPosition.ToPoint(), EndExitPosition.ToPoint(), position) < minDist * minDist;
|
||||
}
|
||||
|
||||
private Submarine SpawnSubOnPath(string subName, ContentFile contentFile, SubmarineType type)
|
||||
{
|
||||
var tempSW = new Stopwatch();
|
||||
|
||||
// Min distance between a sub and the start/end/other sub.
|
||||
float minDistance = Sonar.DefaultSonarRange;
|
||||
float squaredMinDistance = minDistance * minDistance;
|
||||
Vector2 start = startPosition.ToVector2();
|
||||
Vector2 end = endPosition.ToVector2();
|
||||
var waypoints = WayPoint.WayPointList.Where(wp =>
|
||||
wp.Submarine == null &&
|
||||
wp.SpawnType == SpawnType.Path &&
|
||||
Vector2.DistanceSquared(wp.WorldPosition, start) > squaredMinDistance &&
|
||||
Vector2.DistanceSquared(wp.WorldPosition, end) > squaredMinDistance).ToList();
|
||||
!IsCloseToStart(wp.WorldPosition, minDistance) &&
|
||||
!IsCloseToEnd(wp.WorldPosition, minDistance)).ToList();
|
||||
|
||||
var subDoc = SubmarineInfo.OpenFile(contentFile.Path);
|
||||
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
|
||||
@@ -3163,7 +3173,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
var sp = spawnPoint;
|
||||
if (Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < squaredMinDistance))
|
||||
if (Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < minDistance * minDistance))
|
||||
{
|
||||
Debug.WriteLine($"Invalid position {spawnPoint}. Too close to other wreck(s).");
|
||||
return false;
|
||||
@@ -3321,7 +3331,13 @@ namespace Barotrauma
|
||||
|
||||
int minWreckCount = Math.Min(Loaded.GenerationParams.MinWreckCount, wreckFiles.Count);
|
||||
int maxWreckCount = Math.Min(Loaded.GenerationParams.MaxWreckCount, wreckFiles.Count);
|
||||
int wreckCount = Rand.Range(minWreckCount, maxWreckCount, Rand.RandSync.Server);
|
||||
int wreckCount = Rand.Range(minWreckCount, maxWreckCount + 1, Rand.RandSync.Server);
|
||||
|
||||
if (GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireWreck) ?? false)
|
||||
{
|
||||
wreckCount = Math.Max(wreckCount, 1);
|
||||
}
|
||||
|
||||
Wrecks = new List<Submarine>(wreckCount);
|
||||
for (int i = 0; i < wreckCount; i++)
|
||||
{
|
||||
@@ -3379,7 +3395,7 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (GameMain.GameSession.GameMode is PvPMode) { continue; }
|
||||
if (GameMain.GameSession?.GameMode is PvPMode) { continue; }
|
||||
|
||||
bool isStart = (i == 0) == !Mirrored;
|
||||
if (isStart)
|
||||
@@ -3561,7 +3577,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateBeaconStation(List<VoronoiCell> mainPath)
|
||||
private void CreateBeaconStation()
|
||||
{
|
||||
if (!LevelData.HasBeaconStation) { return; }
|
||||
var beaconStationFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.BeaconStation).ToList();
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelObject : ISpatialEntity
|
||||
partial class LevelObject : ISpatialEntity, IDamageable, ISerializableEntity
|
||||
{
|
||||
public readonly LevelObjectPrefab Prefab;
|
||||
public Vector3 Position;
|
||||
@@ -21,6 +21,8 @@ namespace Barotrauma
|
||||
|
||||
private int spriteIndex;
|
||||
|
||||
protected bool tookDamage;
|
||||
|
||||
public LevelObjectPrefab ActivePrefab;
|
||||
|
||||
public PhysicsBody PhysicsBody
|
||||
@@ -37,11 +39,15 @@ namespace Barotrauma
|
||||
|
||||
public bool NeedsNetworkSyncing
|
||||
{
|
||||
get { return Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing); }
|
||||
get
|
||||
{
|
||||
return tookDamage || (Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing));
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Triggers == null) { return; }
|
||||
Triggers.ForEach(t => t.NeedsNetworkSyncing = false);
|
||||
Triggers.ForEach(t => t.NeedsNetworkSyncing = false);
|
||||
tookDamage = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +56,12 @@ namespace Barotrauma
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public float Health
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get
|
||||
@@ -67,6 +79,10 @@ namespace Barotrauma
|
||||
|
||||
public Submarine Submarine => null;
|
||||
|
||||
public string Name => Prefab?.Name ?? "LevelObject (null)";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; } = new Dictionary<string, SerializableProperty>();
|
||||
|
||||
public Level.Cave ParentCave;
|
||||
|
||||
public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
|
||||
@@ -75,6 +91,7 @@ namespace Barotrauma
|
||||
Position = position;
|
||||
Scale = scale;
|
||||
Rotation = rotation;
|
||||
Health = prefab.Health;
|
||||
|
||||
spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1;
|
||||
|
||||
@@ -89,10 +106,13 @@ namespace Barotrauma
|
||||
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
PhysicsBody.UserData = this;
|
||||
PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition, -Rotation);
|
||||
PhysicsBody.BodyType = BodyType.Static;
|
||||
PhysicsBody.CollisionCategories = Physics.CollisionLevel;
|
||||
PhysicsBody.CollidesWith = Physics.CollisionWall | Physics.CollisionCharacter;
|
||||
PhysicsBody.CollidesWith = Prefab.TakeLevelWallDamage?
|
||||
Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionProjectile :
|
||||
Physics.CollisionWall | Physics.CollisionCharacter;
|
||||
}
|
||||
|
||||
foreach (XElement triggerElement in prefab.LevelTriggerElements)
|
||||
@@ -111,6 +131,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var newTrigger = new LevelTrigger(triggerElement, new Vector2(position.X, position.Y) + triggerPosition, -rotation, scale, prefab.Name);
|
||||
if (newTrigger.PhysicsBody != null)
|
||||
{
|
||||
newTrigger.PhysicsBody.UserData = this;
|
||||
}
|
||||
int parentTriggerIndex = prefab.LevelTriggerElements.IndexOf(triggerElement.Parent);
|
||||
if (parentTriggerIndex > -1) { newTrigger.ParentTrigger = Triggers[parentTriggerIndex]; }
|
||||
Triggers.Add(newTrigger);
|
||||
@@ -135,7 +159,54 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
|
||||
{
|
||||
if (Health <= 0.0f) { return new AttackResult(0.0f); }
|
||||
|
||||
float damage = 0.0f;
|
||||
if (Prefab.TakeLevelWallDamage)
|
||||
{
|
||||
damage += attack.GetLevelWallDamage(deltaTime);
|
||||
}
|
||||
damage = Math.Max(Health, damage);
|
||||
AddDamage(damage, deltaTime, attacker);
|
||||
return new AttackResult(damage);
|
||||
}
|
||||
|
||||
public void AddDamage(float damage, float deltaTime, Entity attacker, bool isNetworkEvent = false)
|
||||
{
|
||||
if (Health <= 0.0f) { return; }
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
tookDamage |= !MathUtils.NearlyEqual(damage, 0.0f);
|
||||
Health -= damage;
|
||||
if (Health <= 0.0f)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.Level?.LevelObjectManager != null)
|
||||
{
|
||||
GameMain.GameSession.Level.LevelObjectManager.ForceRefreshVisibleObjects = true;
|
||||
}
|
||||
#endif
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
PhysicsBody.Enabled = false;
|
||||
}
|
||||
foreach (LevelTrigger trigger in Triggers)
|
||||
{
|
||||
trigger.PhysicsBody.Enabled = false;
|
||||
foreach (StatusEffect effect in trigger.StatusEffects)
|
||||
{
|
||||
if (effect.type != ActionType.OnBroken) { continue; }
|
||||
effect.Apply(effect.type, deltaTime, attacker, this, worldPosition: WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 LocalToWorld(Vector2 localPosition, float swingState = 0.0f)
|
||||
{
|
||||
Vector2 emitterPos = localPosition * Scale;
|
||||
@@ -169,6 +240,10 @@ namespace Barotrauma
|
||||
public void ServerWrite(IWriteMessage msg, Client c)
|
||||
{
|
||||
if (Triggers == null) { return; }
|
||||
if (Prefab.TakeLevelWallDamage)
|
||||
{
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(Health, 0.0f, Prefab.Health), 0.0f, Prefab.Health, 8);
|
||||
}
|
||||
for (int j = 0; j < Triggers.Count; j++)
|
||||
{
|
||||
if (!Triggers[j].UseNetworkSyncing) { continue; }
|
||||
|
||||
+8
-15
@@ -136,27 +136,18 @@ namespace Barotrauma
|
||||
if (prefab == null) { continue; }
|
||||
if (!suitableSpawnPositions.ContainsKey(prefab))
|
||||
{
|
||||
float minDistance = level.Size.X * 0.2f;
|
||||
|
||||
suitableSpawnPositions.Add(prefab,
|
||||
availableSpawnPositions.Where(sp =>
|
||||
sp.SpawnPosTypes.Any(type => prefab.SpawnPos.HasFlag(type)) &&
|
||||
sp.Length >= prefab.MinSurfaceWidth &&
|
||||
(prefab.AllowAtStart || !closeToStart(sp.GraphEdge.Center)) &&
|
||||
(prefab.AllowAtEnd || !closeToEnd(sp.GraphEdge.Center)) &&
|
||||
(prefab.AllowAtStart || !level.IsCloseToStart(sp.GraphEdge.Center, minDistance)) &&
|
||||
(prefab.AllowAtEnd || !level.IsCloseToEnd(sp.GraphEdge.Center, minDistance)) &&
|
||||
(sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
|
||||
|
||||
spawnPositionWeights.Add(prefab,
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
|
||||
bool closeToStart(Vector2 position)
|
||||
{
|
||||
float minDist = level.Size.X * 0.2f;
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(level.StartPosition.ToPoint(), level.StartExitPosition.ToPoint(), position.ToPoint()) < minDist * minDist;
|
||||
}
|
||||
bool closeToEnd(Vector2 position)
|
||||
{
|
||||
float minDist = level.Size.X * 0.2f;
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(level.EndPosition.ToPoint(), level.EndExitPosition.ToPoint(), position.ToPoint()) < minDist * minDist;
|
||||
}
|
||||
}
|
||||
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
@@ -442,10 +433,10 @@ namespace Barotrauma
|
||||
public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
|
||||
{
|
||||
var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
|
||||
if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) return Enumerable.Empty<LevelObject>();
|
||||
if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) { return Enumerable.Empty<LevelObject>(); }
|
||||
|
||||
var maxIndices = GetGridIndices(worldPosition + Vector2.One * radius);
|
||||
if (maxIndices.X < 0 || maxIndices.Y < 0) return Enumerable.Empty<LevelObject>();
|
||||
if (maxIndices.X < 0 || maxIndices.Y < 0) { return Enumerable.Empty<LevelObject>(); }
|
||||
|
||||
minIndices.X = Math.Max(0, minIndices.X);
|
||||
minIndices.Y = Math.Max(0, minIndices.Y);
|
||||
@@ -460,6 +451,7 @@ namespace Barotrauma
|
||||
if (objectGrid[x, y] == null) { continue; }
|
||||
foreach (LevelObject obj in objectGrid[x, y])
|
||||
{
|
||||
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
|
||||
objectsInRange.Add(obj);
|
||||
}
|
||||
}
|
||||
@@ -522,6 +514,7 @@ namespace Barotrauma
|
||||
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
|
||||
}
|
||||
}
|
||||
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
|
||||
|
||||
if (obj.Triggers != null)
|
||||
{
|
||||
|
||||
@@ -264,6 +264,27 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, true, description: "Can the object take damage from weapons/attacks that damage level walls."), Editable]
|
||||
public bool TakeLevelWallDamage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool HideWhenBroken
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(100.0f, true), Editable]
|
||||
public float Health
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Identifier
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -38,6 +38,10 @@ namespace Barotrauma
|
||||
/// Effects applied to entities that are inside the trigger
|
||||
/// </summary>
|
||||
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
public IEnumerable<StatusEffect> StatusEffects
|
||||
{
|
||||
get { return statusEffects; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attacks applied to entities that are inside the trigger
|
||||
@@ -205,7 +209,7 @@ namespace Barotrauma
|
||||
PhysicsBody = new PhysicsBody(element, scale)
|
||||
{
|
||||
CollisionCategories = Physics.CollisionLevel,
|
||||
CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall
|
||||
CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall,
|
||||
};
|
||||
PhysicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
|
||||
PhysicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
|
||||
@@ -523,6 +527,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
if (effect.type == ActionType.OnBroken) { continue; }
|
||||
Vector2? position = null;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = WorldPosition; }
|
||||
if (triggerer is Character character)
|
||||
|
||||
@@ -181,27 +181,50 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Mission SelectedMission
|
||||
private readonly List<Mission> selectedMissions = new List<Mission>();
|
||||
public IEnumerable<Mission> SelectedMissions
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get { return selectedMissions; }
|
||||
}
|
||||
|
||||
public int SelectedMissionIndex
|
||||
public void SelectMission(Mission mission)
|
||||
{
|
||||
get
|
||||
if (!SelectedMissions.Contains(mission) && mission != null)
|
||||
{
|
||||
if (SelectedMission == null) { return -1; }
|
||||
return availableMissions.IndexOf(SelectedMission);
|
||||
selectedMissions.Add(mission);
|
||||
}
|
||||
set
|
||||
}
|
||||
|
||||
public void DeselectMission(Mission mission)
|
||||
{
|
||||
selectedMissions.Remove(mission);
|
||||
}
|
||||
|
||||
|
||||
public List<int> GetSelectedMissionIndices()
|
||||
{
|
||||
List<int> selectedMissionIndices = new List<int>();
|
||||
foreach (Mission mission in SelectedMissions)
|
||||
{
|
||||
if (value < 0 || value >= AvailableMissions.Count())
|
||||
if (availableMissions.Contains(mission))
|
||||
{
|
||||
SelectedMission = null;
|
||||
return;
|
||||
selectedMissionIndices.Add(availableMissions.IndexOf(mission));
|
||||
}
|
||||
SelectedMission = availableMissions[value];
|
||||
}
|
||||
return selectedMissionIndices;
|
||||
}
|
||||
|
||||
public void SetSelectedMissionIndices(IEnumerable<int> missionIndices)
|
||||
{
|
||||
selectedMissions.Clear();
|
||||
foreach (int missionIndex in missionIndices)
|
||||
{
|
||||
if (missionIndex < 0 || missionIndex >= availableMissions.Count)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to select a mission in location \"{Name}\". Mission index out of bounds ({missionIndex}, available missions: {availableMissions.Count})");
|
||||
break;
|
||||
}
|
||||
selectedMissions.Add(availableMissions[missionIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,6 +438,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (newType == Type) { return; }
|
||||
|
||||
if (newType == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{Name}\" to null.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
|
||||
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
|
||||
|
||||
Type = newType;
|
||||
@@ -573,6 +602,7 @@ namespace Barotrauma
|
||||
public void InstantiateLoadedMissions(Map map)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
selectedMissions.Clear();
|
||||
if (loadedMissions != null && loadedMissions.Any())
|
||||
{
|
||||
foreach (LoadedMission loadedMission in loadedMissions)
|
||||
@@ -588,7 +618,7 @@ namespace Barotrauma
|
||||
}
|
||||
var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination }, Submarine.MainSub);
|
||||
availableMissions.Add(mission);
|
||||
if (loadedMission.SelectedMission) { SelectedMission = mission; }
|
||||
if (loadedMission.SelectedMission) { selectedMissions.Add(mission); }
|
||||
}
|
||||
loadedMissions = null;
|
||||
}
|
||||
@@ -612,7 +642,7 @@ namespace Barotrauma
|
||||
public void ClearMissions()
|
||||
{
|
||||
availableMissions.Clear();
|
||||
SelectedMissionIndex = -1;
|
||||
selectedMissions.Clear();
|
||||
}
|
||||
|
||||
public bool HasOutpost()
|
||||
@@ -1180,7 +1210,7 @@ namespace Barotrauma
|
||||
missionsElement.Add(new XElement("mission",
|
||||
new XAttribute("prefabid", mission.Prefab.Identifier),
|
||||
new XAttribute("destinationindex", i),
|
||||
new XAttribute("selected", mission == SelectedMission)));
|
||||
new XAttribute("selected", selectedMissions.Contains(mission))));
|
||||
}
|
||||
locationElement.Add(missionsElement);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
/// From -> To
|
||||
/// </summary>
|
||||
public Action<Location, Location> OnLocationChanged;
|
||||
public Action<LocationConnection, Mission> OnMissionSelected;
|
||||
public Action<LocationConnection, IEnumerable<Mission>> OnMissionsSelected;
|
||||
|
||||
public Location EndLocation { get; private set; }
|
||||
|
||||
@@ -44,9 +44,9 @@ namespace Barotrauma
|
||||
get { return Locations.IndexOf(SelectedLocation); }
|
||||
}
|
||||
|
||||
public int SelectedMissionIndex
|
||||
public IEnumerable<int> GetSelectedMissionIndices()
|
||||
{
|
||||
get { return SelectedConnection == null ? -1 : CurrentLocation.SelectedMissionIndex; }
|
||||
return SelectedConnection == null ? Enumerable.Empty<int>() : CurrentLocation.GetSelectedMissionIndices();
|
||||
}
|
||||
|
||||
public LocationConnection SelectedConnection { get; private set; }
|
||||
@@ -388,8 +388,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
LocationConnection[] connectionsBetweenZones = new LocationConnection[generationParams.DifficultyZones];
|
||||
foreach (var connection in Connections)
|
||||
List<LocationConnection>[] connectionsBetweenZones = new List<LocationConnection>[generationParams.DifficultyZones];
|
||||
for (int i = 0; i < generationParams.DifficultyZones; i++)
|
||||
{
|
||||
connectionsBetweenZones[i] = new List<LocationConnection>();
|
||||
}
|
||||
var shuffledConnections = Connections.ToList();
|
||||
shuffledConnections.Shuffle(Rand.RandSync.Server);
|
||||
foreach (var connection in shuffledConnections)
|
||||
{
|
||||
int zone1 = GetZoneIndex(connection.Locations[0].MapPosition.X);
|
||||
int zone2 = GetZoneIndex(connection.Locations[1].MapPosition.X);
|
||||
@@ -401,17 +407,25 @@ namespace Barotrauma
|
||||
zone1 = temp;
|
||||
}
|
||||
|
||||
if (connectionsBetweenZones[zone1] == null)
|
||||
if (generationParams.GateCount[zone1] == 0) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones[zone1].Any())
|
||||
{
|
||||
connectionsBetweenZones[zone1] = connection;
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
else
|
||||
else if (generationParams.GateCount[zone1] == 1)
|
||||
{
|
||||
if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].CenterPos.Y - Height / 2))
|
||||
//if there's only one connection, place it at the center of the map
|
||||
if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].First().CenterPos.Y - Height / 2))
|
||||
{
|
||||
connectionsBetweenZones[zone1] = connection;
|
||||
connectionsBetweenZones[zone1].Clear();
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
}
|
||||
else if (connectionsBetweenZones[zone1].Count() < generationParams.GateCount[zone1])
|
||||
{
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = Connections.Count - 1; i >= 0; i--)
|
||||
@@ -421,7 +435,9 @@ namespace Barotrauma
|
||||
if (zone1 == zone2) { continue; }
|
||||
if (zone1 == generationParams.DifficultyZones || zone2 == generationParams.DifficultyZones) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones.Contains(Connections[i]))
|
||||
if (generationParams.GateCount[Math.Min(zone1, zone2)] == 0) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones[Math.Min(zone1, zone2)].Contains(Connections[i]))
|
||||
{
|
||||
Connections.RemoveAt(i);
|
||||
}
|
||||
@@ -756,7 +772,7 @@ namespace Barotrauma
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
|
||||
public void SelectMission(int missionIndex)
|
||||
public void SelectMission(IEnumerable<int> missionIndices)
|
||||
{
|
||||
if (CurrentLocation == null)
|
||||
{
|
||||
@@ -765,23 +781,24 @@ namespace Barotrauma
|
||||
GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
CurrentLocation.SelectedMissionIndex = missionIndex;
|
||||
|
||||
if (CurrentLocation.SelectedMission == null) { return; }
|
||||
CurrentLocation.SetSelectedMissionIndices(missionIndices);
|
||||
|
||||
if (CurrentLocation.SelectedMission.Locations[0] != CurrentLocation ||
|
||||
CurrentLocation.SelectedMission.Locations[1] != CurrentLocation)
|
||||
foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList())
|
||||
{
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (CurrentLocation.SelectedMission != null &&
|
||||
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
|
||||
if (selectedMission.Locations[0] != CurrentLocation ||
|
||||
selectedMission.Locations[1] != CurrentLocation)
|
||||
{
|
||||
CurrentLocation.SelectedMissionIndex = -1;
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (selectedMission.Locations[1] != SelectedLocation)
|
||||
{
|
||||
CurrentLocation.DeselectMission(selectedMission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission);
|
||||
OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions);
|
||||
}
|
||||
|
||||
public void SelectRandomLocation(bool preferUndiscovered)
|
||||
@@ -977,6 +994,12 @@ namespace Barotrauma
|
||||
string prevName = location.Name;
|
||||
|
||||
var newType = LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase));
|
||||
if (newType == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{location.Name}\". Location type \"{change.ChangeToType}\" not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newType.OutpostTeam != location.Type.OutpostTeam ||
|
||||
newType.HasOutpost != location.Type.HasOutpost)
|
||||
{
|
||||
|
||||
@@ -67,6 +67,8 @@ namespace Barotrauma
|
||||
[Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)]
|
||||
public float ConnectionIndicatorDisplacementMultiplier { get; set; }
|
||||
|
||||
public int[] GateCount { get; private set; }
|
||||
|
||||
#if CLIENT
|
||||
|
||||
[Serialize(0.75f, true), Editable(DecimalCount = 2)]
|
||||
@@ -201,6 +203,16 @@ namespace Barotrauma
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
GateCount = element.GetAttributeIntArray("gatecount", null) ?? element.GetAttributeIntArray("GateCount", null);
|
||||
if (GateCount == null)
|
||||
{
|
||||
GateCount = new int[DifficultyZones];
|
||||
for (int i = 0; i < DifficultyZones; i++)
|
||||
{
|
||||
GateCount[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
|
||||
@@ -420,7 +420,7 @@ namespace Barotrauma
|
||||
if (cloneItem == null) { continue; }
|
||||
|
||||
var door = cloneItem.GetComponent<Door>();
|
||||
if (door != null) { door.RefreshLinkedGap(); }
|
||||
door?.RefreshLinkedGap();
|
||||
|
||||
var cloneWire = cloneItem.GetComponent<Wire>();
|
||||
if (cloneWire == null) continue;
|
||||
|
||||
@@ -1447,7 +1447,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
npc.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier;
|
||||
npc.AddStaticHealthMultiplier(humanPrefab.HealthMultiplier);
|
||||
}
|
||||
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.Server);
|
||||
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
|
||||
|
||||
@@ -353,10 +353,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (convexHulls!=null)
|
||||
{
|
||||
convexHulls.ForEach(x => x.Move(amount));
|
||||
}
|
||||
convexHulls?.ForEach(x => x.Move(amount));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ namespace Barotrauma
|
||||
|
||||
private static Vector2 lastPickedPosition;
|
||||
private static float lastPickedFraction;
|
||||
private static Fixture lastPickedFixture;
|
||||
private static Vector2 lastPickedNormal;
|
||||
|
||||
private Vector2 prevPosition;
|
||||
@@ -99,6 +100,11 @@ namespace Barotrauma
|
||||
get { return lastPickedFraction; }
|
||||
}
|
||||
|
||||
public static Fixture LastPickedFixture
|
||||
{
|
||||
get { return lastPickedFixture; }
|
||||
}
|
||||
|
||||
public static Vector2 LastPickedNormal
|
||||
{
|
||||
get { return lastPickedNormal; }
|
||||
@@ -669,6 +675,7 @@ namespace Barotrauma
|
||||
|
||||
float closestFraction = 1.0f;
|
||||
Vector2 closestNormal = Vector2.Zero;
|
||||
Fixture closestFixture = null;
|
||||
Body closestBody = null;
|
||||
if (allowInsideFixture)
|
||||
{
|
||||
@@ -682,13 +689,15 @@ namespace Barotrauma
|
||||
|
||||
closestFraction = 0.0f;
|
||||
closestNormal = Vector2.Normalize(rayEnd - rayStart);
|
||||
if (fixture.Body != null) closestBody = fixture.Body;
|
||||
closestFixture = fixture;
|
||||
if (fixture.Body != null) { closestBody = fixture.Body; }
|
||||
return false;
|
||||
}, ref aabb);
|
||||
if (closestFraction <= 0.0f)
|
||||
{
|
||||
lastPickedPosition = rayStart;
|
||||
lastPickedFraction = closestFraction;
|
||||
lastPickedFixture = closestFixture;
|
||||
lastPickedNormal = closestNormal;
|
||||
return closestBody;
|
||||
}
|
||||
@@ -702,6 +711,7 @@ namespace Barotrauma
|
||||
{
|
||||
closestFraction = fraction;
|
||||
closestNormal = normal;
|
||||
closestFixture = fixture;
|
||||
if (fixture.Body != null) closestBody = fixture.Body;
|
||||
}
|
||||
return fraction;
|
||||
@@ -709,6 +719,7 @@ namespace Barotrauma
|
||||
|
||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||
lastPickedFraction = closestFraction;
|
||||
lastPickedFixture = closestFixture;
|
||||
lastPickedNormal = closestNormal;
|
||||
|
||||
return closestBody;
|
||||
@@ -752,6 +763,7 @@ namespace Barotrauma
|
||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction;
|
||||
lastPickedFraction = fraction;
|
||||
lastPickedNormal = normal;
|
||||
lastPickedFixture = fixture;
|
||||
}
|
||||
//continue
|
||||
return -1;
|
||||
@@ -772,6 +784,7 @@ namespace Barotrauma
|
||||
lastPickedPosition = rayStart;
|
||||
lastPickedFraction = 0.0f;
|
||||
lastPickedNormal = Vector2.Normalize(rayEnd - rayStart);
|
||||
lastPickedFixture = fixture;
|
||||
bodies.Add(fixture.Body);
|
||||
bodyDist[fixture.Body] = 0.0f;
|
||||
return false;
|
||||
@@ -828,6 +841,7 @@ namespace Barotrauma
|
||||
{
|
||||
Body closestBody = null;
|
||||
float closestFraction = 1.0f;
|
||||
Fixture closestFixture = null;
|
||||
Vector2 closestNormal = Vector2.Zero;
|
||||
|
||||
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f)
|
||||
@@ -847,6 +861,8 @@ namespace Barotrauma
|
||||
if (ignoreSubs && fixture.Body.UserData is Submarine) { return -1; }
|
||||
if (ignoreBranches && fixture.Body.UserData is VineTile) { return -1; }
|
||||
if (fixture.Body.UserData as string == "ruinroom") { return -1; }
|
||||
//the hulls have solid fixtures in the submarine's world space collider, ignore them
|
||||
if (fixture.UserData is Hull) { return -1; }
|
||||
if (fixture.Body.UserData is Structure structure)
|
||||
{
|
||||
if (structure.IsPlatform || structure.StairDirection != Direction.None) { return -1; }
|
||||
@@ -861,6 +877,7 @@ namespace Barotrauma
|
||||
{
|
||||
closestBody = fixture.Body;
|
||||
closestFraction = fraction;
|
||||
closestFixture = fixture;
|
||||
closestNormal = normal;
|
||||
}
|
||||
return closestFraction;
|
||||
@@ -870,6 +887,7 @@ namespace Barotrauma
|
||||
|
||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||
lastPickedFraction = closestFraction;
|
||||
lastPickedFixture = closestFixture;
|
||||
lastPickedNormal = closestNormal;
|
||||
return closestBody;
|
||||
}
|
||||
@@ -944,19 +962,23 @@ namespace Barotrauma
|
||||
mapEntity.Move(HiddenSubPosition);
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (bodyItems.Contains(item))
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
item.Submarine = this;
|
||||
if (Position == Vector2.Zero) item.Move(-HiddenSubPosition);
|
||||
//two passes: flip docking ports on the 2nd pass because the doors need to be correctly flipped for the port's orientation to be determined correctly
|
||||
if ((item.GetComponent<DockingPort>() != null) == (i == 0)) { continue; }
|
||||
if (bodyItems.Contains(item))
|
||||
{
|
||||
item.Submarine = this;
|
||||
if (Position == Vector2.Zero) { item.Move(-HiddenSubPosition); }
|
||||
}
|
||||
else if (item.Submarine != this)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
item.FlipX(true);
|
||||
}
|
||||
else if (item.Submarine != this)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
item.FlipX(true);
|
||||
}
|
||||
|
||||
Item.UpdateHulls();
|
||||
|
||||
@@ -79,7 +79,6 @@ namespace Barotrauma.Networking
|
||||
TRAITOR_MESSAGE,
|
||||
MISSION,
|
||||
EVENTACTION,
|
||||
RESET_UPGRADES, //inform the clients that the upgrades on the submarine have been reset
|
||||
CREW, //anything related to managing bots in multiplayer
|
||||
READY_CHECK //start, end and update a ready check
|
||||
}
|
||||
|
||||
@@ -186,11 +186,7 @@ namespace Barotrauma.Networking
|
||||
if (updateReturnTimer > 1.0f)
|
||||
{
|
||||
updateReturnTimer = 0.0f;
|
||||
|
||||
if (shuttleSteering != null)
|
||||
{
|
||||
shuttleSteering.SetDestinationLevelStart();
|
||||
}
|
||||
shuttleSteering?.SetDestinationLevelStart();
|
||||
UpdateReturningProjSpecific(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,21 +69,24 @@ namespace Barotrauma
|
||||
this.conditionType = conditionType;
|
||||
}
|
||||
|
||||
private ConditionType conditionType;
|
||||
private readonly ConditionType conditionType;
|
||||
|
||||
public enum ConditionType
|
||||
{
|
||||
//These need to exist at compile time, so it is a little awkward
|
||||
//I would love to see a better way to do this
|
||||
AllowLinkingWifiToChat
|
||||
AllowLinkingWifiToChat,
|
||||
IsSwappableItem
|
||||
}
|
||||
|
||||
public bool IsEditable()
|
||||
public bool IsEditable(ISerializableEntity entity)
|
||||
{
|
||||
switch (conditionType)
|
||||
{
|
||||
case ConditionType.AllowLinkingWifiToChat:
|
||||
return GameMain.NetworkMember?.ServerSettings?.AllowLinkingWifiToChat ?? true;
|
||||
case ConditionType.IsSwappableItem:
|
||||
return entity is Item item && item.Prefab.SwappableItem != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -766,6 +769,10 @@ namespace Barotrauma
|
||||
if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property))
|
||||
{
|
||||
FixValue(property, entity, attribute);
|
||||
if (property.Name == nameof(ItemComponent.Msg) && entity is ItemComponent component)
|
||||
{
|
||||
component.ParseMsg();
|
||||
}
|
||||
}
|
||||
else if (entity is Item item1)
|
||||
{
|
||||
@@ -774,6 +781,10 @@ namespace Barotrauma
|
||||
if (component.SerializableProperties.TryGetValue(attributeName, out SerializableProperty componentProperty))
|
||||
{
|
||||
FixValue(componentProperty, component, attribute);
|
||||
if (componentProperty.Name == nameof(ItemComponent.Msg))
|
||||
{
|
||||
((ItemComponent)component).ParseMsg();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Barotrauma
|
||||
return spr;
|
||||
}
|
||||
return null;
|
||||
}).Where(s => s!=null).ToList();
|
||||
}).Where(s => s != null).ToList();
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ namespace Barotrauma
|
||||
{
|
||||
var value = (float) OriginalValue;
|
||||
|
||||
if (level == 0) { return value; }
|
||||
|
||||
if (Multiplier[^1] != '%')
|
||||
{
|
||||
float multiplier = ParseValue();
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class CrossThread
|
||||
{
|
||||
public delegate void TaskDelegate();
|
||||
|
||||
private class Task
|
||||
{
|
||||
public TaskDelegate Deleg;
|
||||
public ManualResetEvent Mre;
|
||||
public bool Done;
|
||||
|
||||
public Task(TaskDelegate d)
|
||||
{
|
||||
Deleg = d;
|
||||
Mre = new ManualResetEvent(false);
|
||||
Done = false;
|
||||
}
|
||||
|
||||
public void PerformWait()
|
||||
{
|
||||
if (!Done) { Mre.WaitOne(); }
|
||||
}
|
||||
}
|
||||
private static List<Task> enqueuedTasks;
|
||||
|
||||
static CrossThread() { enqueuedTasks = new List<Task>(); }
|
||||
|
||||
public static void ProcessTasks()
|
||||
{
|
||||
lock (enqueuedTasks)
|
||||
{
|
||||
foreach (var task in enqueuedTasks)
|
||||
{
|
||||
task.Deleg();
|
||||
task.Mre.Set();
|
||||
task.Done = true;
|
||||
}
|
||||
enqueuedTasks.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RequestExecutionOnMainThread(TaskDelegate deleg)
|
||||
{
|
||||
if (GameMain.MainThread == null || Thread.CurrentThread == GameMain.MainThread)
|
||||
{
|
||||
deleg();
|
||||
}
|
||||
else
|
||||
{
|
||||
Task newTask = new Task(deleg);
|
||||
lock (enqueuedTasks)
|
||||
{
|
||||
enqueuedTasks.Add(newTask);
|
||||
}
|
||||
newTask.PerformWait();
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddOnMainThread<T>(this List<T> list, T element)
|
||||
{
|
||||
RequestExecutionOnMainThread(() => { list.Add(element); });
|
||||
}
|
||||
|
||||
public static void AddRangeOnMainThread<T>(this List<T> list, IEnumerable<T> elements)
|
||||
{
|
||||
RequestExecutionOnMainThread(() => { list.AddRange(elements); });
|
||||
}
|
||||
|
||||
public static void RemoveOnMainThread<T>(this List<T> list, T element)
|
||||
{
|
||||
RequestExecutionOnMainThread(() => { list.Remove(element); });
|
||||
}
|
||||
|
||||
public static void ClearOnMainThread<T>(this List<T> list)
|
||||
{
|
||||
RequestExecutionOnMainThread(() => { list.Clear(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,78 @@
|
||||
------------------------------------------------------------------------------------------------------
|
||||
v0.1400.1.0 (unstable)
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Changes:
|
||||
- Improvements/balancing to the pulse laser and chaingun.
|
||||
- Option to choose multiple missions per level.
|
||||
- Added some hardpoints to the vanilla subs.
|
||||
- Replaced the old railgun loaders with the new smaller versions.
|
||||
- Improvements to the weapon customization menu.
|
||||
- Docking ports can't be rewired in outpost levels because it can be abused to force to submarine to depart from the outpost.
|
||||
- The ice shards and exploding mushrooms in caves can be destroyed with weapons and explosives.
|
||||
- The pirate missions can no longer be completed by exploding the reactor: all the pirates need to be killed for the mission to be successful.
|
||||
- Changed the way diving suits are picked up: now LMB equips the suit like it did before, and the suits can be picked up with E.
|
||||
- Taking items that contain stolen items counts as stealing, so you can't for example put a toolbox inside an outpost cabinet, load it full of items and then take it.
|
||||
- Added a button for resetting in-game hints to the settings.
|
||||
- Mention diving suits' depth limits in their descriptions.
|
||||
- Modified the locked path tooltips to mention the officer in the outpost.
|
||||
- Reset reactor fission rate, turbine output and temperature to optimal levels at the start of a round. Prevents the reactor from catching fire at the start of a round if it was being operated at a high fission rate at the end of the previous round.
|
||||
- Increased canister shell pellet count from 7 to 10, increased pellet damage from 22 to 25, reduced spread from 25 to 20.
|
||||
- Option to disable swapping specific weapons in the sub editor.
|
||||
- Items that are set to be hidden in-game can't be swapped.
|
||||
- Removed ability to drag and drop seeds into planters.
|
||||
- Added ability to load .sub, .xml, .png and .jpeg files in sub editor by dragging and dropping them onto the game window.
|
||||
- Added new biome-specific background noise tracks.
|
||||
- Made upgrades affect all submarines. Old purchased submarines will have their upgrades overridden by the currently loaded submarine's upgrades.
|
||||
- Improved the command interface minimap view by adding connector lines between icons and item positions to better visualize which item each icon is linked to.
|
||||
- Projectiles go through severed limbs.
|
||||
|
||||
Fixes:
|
||||
- Fixed rotation of mirrored items getting messed up when saving and reloading a sub.
|
||||
- Fixed inability to detach items attached outside the sub.
|
||||
- Removed colliders from decorative pirate submarine pieces.
|
||||
- Fixed pirate bandana sprite.
|
||||
- Fixed depth decoy sprite.
|
||||
- Fixed items that are inside a hull being difficult to target from outside the sub (docking ports/hatches in particular).
|
||||
- Fixed crashing when trying to load a campaign save that contains pets that can't be found (e.g. if you've saved while using a mod that adds custom pets and try to load the save in the vanilla game).
|
||||
- Fixed crashing when trying to change a location's type to a type that can't be found (e.g. if a mod includes custom location types which are configured incorrectly).
|
||||
- Fixed "are you sure you want to depart without a mission" prompt popping up even if there's no missions available.
|
||||
- Fixed pirate missions not ending automatically when docking with the destination outpost in mission mode.
|
||||
- Fixed client-side "index out of range" errors when a pirate gets assigned the "navigatetactical" order.
|
||||
- Fixed health bars showing up through walls.
|
||||
- Fixed main sub's docking ports not showing up on the respawn shuttle's sonar.
|
||||
- Fixed abandoned outposts' docking ports not showing up on sonar.
|
||||
- Fixed handheld sonar showing the enemy sub in PvP mode when used outside the main sub.
|
||||
- Fixed how weapons are displayed on the outpost display shelves.
|
||||
- Fixed location portrait overlapping with the texts in the tab menu's mission tab.
|
||||
- Fixed horizontal docking ports sometimes docking from the wrong side in mirrored subs.
|
||||
- Fixed mouse wheel zooming the nav terminal view when the server log (or any other UI element) is blocking it.
|
||||
- Fixed chainguns not damaging items (e.g. eggs, piezo crystals).
|
||||
- Fixed Watcher always spawning at the start when the difficulty is between 10 and 20.
|
||||
- Fixed wrecks never spawning in certain biomes, even if there's a wreck mission selected.
|
||||
- Fixed legacy railgun loaders exploding the shells.
|
||||
- Fixed campaign's initial text popup getting stuck if the sub automatically undocks at the start of a round.
|
||||
- Swapped weapons inherit the scale, rotation, sprite depth and sprite color from the previously installed item.
|
||||
- Non-empty items (ammo boxes, fuel rods, etc) can't be recycled.
|
||||
- Fixed afflictions applying their status effects multiple times, causing afflictions that apply other afflictions way faster than they should (e.g. opiate withdrawal caused by opiate addiction, burns caused by radiation sickness).
|
||||
- Fixed all sonar markers of a given mission displaying the same distance reading.
|
||||
- Fixed command interface minimap tooltips not being displayed.
|
||||
- Fixed bot orders not being saved in multiplayer.
|
||||
- Disabled the tab menu missions tab in the test game modes (e.g. sub editor) to fix a related crash.
|
||||
- Made airlock door assembly behave a bit more reliably. The circuit in the assembly simply toggles the state of both doors, meaning that one of the doors needs to always be closed and the other open for the logic to work correctly. If using the assembly in a respawn shuttle, it'd break when the shuttle leaves and it's doors are forced to close.
|
||||
- Fixed inability to go through docking ports when the door/hatch at the other side is broken.
|
||||
- Fixed other entities' sprites disappearing when reloading a sprite in the sub editor.
|
||||
- Fixed Remora drone's docking hatch being repairable with a welding tool instead of a wrench.
|
||||
- Fixed some of Remora drone's walls being transparent.
|
||||
|
||||
Modding:
|
||||
- Made it possible to use StatusHUD components in non-equippable items like turrets.
|
||||
- Fixed scripted event's StatusEffectAction not being able to target ItemComponents.
|
||||
- Added support for making triggering scripted events.
|
||||
- Option to configure the color of an explosion's flash.
|
||||
- Option to configure the number of gates between biomes (see MapGenerationParameters.xml).
|
||||
- Fixed "publishing [mod] in the steam workshop failed" error when trying to publish a mod that contains enemy subs.
|
||||
|
||||
------------------------------------------------------------------------------------------------------
|
||||
v0.1400.0.0 (unstable)
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user