(d9829ac) v0.9.4.0
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum AIState { Idle, Attack, Escape, Eat }
|
||||
|
||||
abstract partial class AIController : ISteerable
|
||||
{
|
||||
public enum AIState { Idle, Attack, GoTo, Escape, Eat }
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
public readonly Character Character;
|
||||
|
||||
@@ -21,7 +21,9 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// How long does it take for the ai target to fade out if not kept alive.
|
||||
/// </summary>
|
||||
public float FadeOutTime { get; private set; } = 3;
|
||||
public float FadeOutTime { get; private set; }
|
||||
|
||||
public bool Static { get; private set; }
|
||||
|
||||
public float SoundRange
|
||||
{
|
||||
@@ -128,6 +130,19 @@ namespace Barotrauma
|
||||
MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange);
|
||||
MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange);
|
||||
FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime);
|
||||
Static = element.GetAttributeBool("static", Static);
|
||||
if (Static)
|
||||
{
|
||||
SightRange = MaxSightRange;
|
||||
SoundRange = MaxSoundRange;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-static ai targets must be kept alive by a custom logic (e.g. item components)
|
||||
SightRange = MinSightRange;
|
||||
SoundRange = MinSoundRange;
|
||||
}
|
||||
|
||||
SonarDisruption = element.GetAttributeFloat("sonardisruption", 0.0f);
|
||||
SonarLabel = element.GetAttributeString("sonarlabel", "");
|
||||
string typeString = element.GetAttributeString("type", "Any");
|
||||
@@ -143,6 +158,16 @@ namespace Barotrauma
|
||||
List.Add(this);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (!Static && FadeOutTime > 0)
|
||||
{
|
||||
// The aitarget goes silent/invisible if the components don't keep it active
|
||||
SightRange -= deltaTime * (MaxSightRange / FadeOutTime);
|
||||
SoundRange -= deltaTime * (MaxSoundRange / FadeOutTime);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsWithinSector(Vector2 worldPosition)
|
||||
{
|
||||
if (sectorRad >= MathHelper.TwoPi) return true;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,10 @@ namespace Barotrauma
|
||||
|
||||
public HumanAIController(Character c) : base(c)
|
||||
{
|
||||
if (!c.IsHuman)
|
||||
{
|
||||
throw new System.Exception($"Tried to create a human ai controller for a non-human: {c.SpeciesName}!");
|
||||
}
|
||||
insideSteering = new IndoorsSteeringManager(this, true, false);
|
||||
outsideSteering = new SteeringManager(this);
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
@@ -324,7 +328,7 @@ namespace Barotrauma
|
||||
AddTargets<AIObjectiveFightIntruders, Character>(Character, c);
|
||||
if (newOrder == null)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
|
||||
var orderPrefab = Order.GetPrefab("reportintruders");
|
||||
newOrder = new Order(orderPrefab, c.CurrentHull, null, orderGiver: Character);
|
||||
}
|
||||
}
|
||||
@@ -334,7 +338,7 @@ namespace Barotrauma
|
||||
AddTargets<AIObjectiveExtinguishFires, Hull>(Character, hull);
|
||||
if (newOrder == null)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire");
|
||||
var orderPrefab = Order.GetPrefab("reportfire");
|
||||
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
|
||||
}
|
||||
}
|
||||
@@ -347,7 +351,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (newOrder == null)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid");
|
||||
var orderPrefab = Order.GetPrefab("requestfirstaid");
|
||||
newOrder = new Order(orderPrefab, c.CurrentHull, null, orderGiver: Character);
|
||||
}
|
||||
}
|
||||
@@ -360,7 +364,7 @@ namespace Barotrauma
|
||||
AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap);
|
||||
if (newOrder == null && !gap.IsRoomToRoom)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach");
|
||||
var orderPrefab = Order.GetPrefab("reportbreach");
|
||||
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
|
||||
}
|
||||
}
|
||||
@@ -374,7 +378,7 @@ namespace Barotrauma
|
||||
AddTargets<AIObjectiveRepairItems, Item>(Character, item);
|
||||
if (newOrder == null)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbrokendevices");
|
||||
var orderPrefab = Order.GetPrefab("reportbrokendevices");
|
||||
newOrder = new Order(orderPrefab, item.CurrentHull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
|
||||
}
|
||||
}
|
||||
@@ -518,11 +522,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (ObjectiveManager.CurrentOrder is AIObjectiveRescueAll rescueAll && rescueAll.Targets.None())
|
||||
{
|
||||
//TODO: re-enable on all languages after DialogNoRescueTargets has been translated
|
||||
if (TextManager.Language == "English")
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
|
||||
}
|
||||
Character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
|
||||
}
|
||||
else if (ObjectiveManager.CurrentOrder is AIObjectivePumpWater pumpWater && pumpWater.Targets.None())
|
||||
{
|
||||
@@ -620,7 +620,7 @@ namespace Barotrauma
|
||||
|
||||
public static void RefreshTargets(Character character, Order order, Hull hull)
|
||||
{
|
||||
switch (order.AITag)
|
||||
switch (order.Identifier)
|
||||
{
|
||||
case "reportfire":
|
||||
AddTargets<AIObjectiveExtinguishFires, Hull>(character, hull);
|
||||
@@ -667,7 +667,7 @@ namespace Barotrauma
|
||||
break;
|
||||
default:
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(order.AITag + " not implemented!");
|
||||
DebugConsole.ThrowError(order.Identifier + " not implemented!");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -765,6 +765,9 @@ namespace Barotrauma
|
||||
|
||||
public bool IsFriendly(Character other) => IsFriendly(Character, other);
|
||||
|
||||
public static bool IsFriendly(Character me, Character other) => (other.TeamID == me.TeamID || other.TeamID == Character.TeamType.FriendlyNPC || me.TeamID == Character.TeamType.FriendlyNPC) && other.SpeciesName == me.SpeciesName;
|
||||
public static bool IsFriendly(Character me, Character other) =>
|
||||
(other.TeamID == me.TeamID ||
|
||||
other.TeamID == Character.TeamType.FriendlyNPC ||
|
||||
me.TeamID == Character.TeamType.FriendlyNPC) && (other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Barotrauma
|
||||
|
||||
switch (enemyAI.State)
|
||||
{
|
||||
case AIController.AIState.Idle:
|
||||
case AIState.Idle:
|
||||
if (attachToWalls && character.Submarine == null && Level.Loaded != null)
|
||||
{
|
||||
raycastTimer -= deltaTime;
|
||||
@@ -187,7 +187,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AIController.AIState.Attack:
|
||||
case AIState.Attack:
|
||||
if (enemyAI.AttackingLimb != null)
|
||||
{
|
||||
if (attachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Path.GetExtension(filePath) == ".csv") continue; // .csv files are not supported
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null || doc.Root == null) continue;
|
||||
if (doc == null) { continue; }
|
||||
string language = doc.Root.GetAttributeString("Language", "English");
|
||||
string identifier = doc.Root.GetAttributeString("Identifier", "unknown");
|
||||
contentPackageFiles.Add(new Tuple<string, string, string>(language, identifier, filePath));
|
||||
@@ -44,7 +44,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Path.GetExtension(filePath) == ".csv") continue; // .csv files are not supported
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null || doc.Root == null) continue;
|
||||
if (doc == null) { continue; }
|
||||
string language = doc.Root.GetAttributeString("Language", "English");
|
||||
string identifier = doc.Root.GetAttributeString("Identifier", "unknown");
|
||||
translationFiles.Add(new Tuple<string, string, string>(language, identifier, filePath));
|
||||
@@ -73,7 +73,7 @@ namespace Barotrauma
|
||||
private static void Load(string file)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
if (doc == null) { return; }
|
||||
|
||||
string language = doc.Root.GetAttributeString("Language", "English");
|
||||
if (language != TextManager.Language) return;
|
||||
@@ -102,8 +102,10 @@ namespace Barotrauma
|
||||
string allowedJobsStr = element.GetAttributeString("allowedjobs", "");
|
||||
foreach (string allowedJobIdentifier in allowedJobsStr.Split(','))
|
||||
{
|
||||
var jobPrefab = JobPrefab.List.Find(jp => jp.Identifier.ToLowerInvariant() == allowedJobIdentifier.ToLowerInvariant());
|
||||
if (jobPrefab != null) AllowedJobs.Add(jobPrefab);
|
||||
if (JobPrefab.List.TryGetValue(allowedJobIdentifier.ToLowerInvariant(), out JobPrefab jobPrefab))
|
||||
{
|
||||
AllowedJobs.Add(jobPrefab);
|
||||
}
|
||||
}
|
||||
|
||||
Flags = new List<string>(element.GetAttributeStringArray("flags", new string[0]));
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
|
||||
return;
|
||||
}
|
||||
container.Combine(itemToContain);
|
||||
container.Combine(itemToContain, character);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,13 +68,13 @@ namespace Barotrauma
|
||||
public void CreateAutonomousObjectives()
|
||||
{
|
||||
Objectives.Clear();
|
||||
AddObjective(new AIObjectiveFindSafety(character, this), delay: Rand.Value() / 2);
|
||||
AddObjective(new AIObjectiveIdle(character, this), delay: Rand.Value() / 2);
|
||||
AddObjective(new AIObjectiveFindSafety(character, this));
|
||||
AddObjective(new AIObjectiveIdle(character, this));
|
||||
int objectiveCount = Objectives.Count;
|
||||
foreach (var automaticOrder in character.Info.Job.Prefab.AutomaticOrders)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == automaticOrder.aiTag);
|
||||
if (orderPrefab == null) { throw new Exception("Could not find a matching prefab by ai tag: " + automaticOrder.aiTag); }
|
||||
var orderPrefab = Order.GetPrefab(automaticOrder.identifier);
|
||||
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{automaticOrder.identifier}'"); }
|
||||
// TODO: Similar code is used in CrewManager:815-> DRY
|
||||
var matchingItems = orderPrefab.ItemIdentifiers.Any() ?
|
||||
Item.ItemList.FindAll(it => orderPrefab.ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(orderPrefab.ItemIdentifiers)) :
|
||||
@@ -144,7 +144,7 @@ namespace Barotrauma
|
||||
if (previousObjective != CurrentObjective)
|
||||
{
|
||||
CurrentObjective?.OnSelected();
|
||||
GetObjective<AIObjectiveIdle>()?.SetRandom();
|
||||
GetObjective<AIObjectiveIdle>().SetRandom();
|
||||
}
|
||||
return CurrentObjective;
|
||||
}
|
||||
@@ -231,7 +231,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (order == null) { return null; }
|
||||
AIObjective newObjective;
|
||||
switch (order.AITag.ToLowerInvariant())
|
||||
switch (order.Identifier.ToLowerInvariant())
|
||||
{
|
||||
case "follow":
|
||||
if (orderGiver == null) { return null; }
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (target.Bleeding < 1 && target.Vitality / target.MaxVitality > vitalityThreshold) { return false; }
|
||||
}
|
||||
if (target.Submarine == null) { return false; }
|
||||
if (target.Submarine == null || character.Submarine == null) { return false; }
|
||||
if (target.Submarine.TeamID != character.Submarine.TeamID) { return false; }
|
||||
if (target.CurrentHull == null) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
||||
|
||||
@@ -10,9 +10,16 @@ namespace Barotrauma
|
||||
{
|
||||
class Order
|
||||
{
|
||||
private static string ConfigFile = Path.Combine("Content", "Orders.xml");
|
||||
|
||||
public static List<Order> PrefabList;
|
||||
public static Dictionary<string, Order> Prefabs { get; private set; }
|
||||
public static List<Order> PrefabList { get; private set; }
|
||||
public static Order GetPrefab(string identifier)
|
||||
{
|
||||
if (!Prefabs.TryGetValue(identifier, out Order order))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find an order with the identifier '{identifier}'!");
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
public Order Prefab
|
||||
{
|
||||
@@ -27,7 +34,7 @@ namespace Barotrauma
|
||||
public readonly Type ItemComponentType;
|
||||
public readonly string[] ItemIdentifiers;
|
||||
|
||||
public readonly string AITag;
|
||||
public readonly string Identifier;
|
||||
|
||||
public readonly Color Color;
|
||||
|
||||
@@ -43,30 +50,64 @@ namespace Barotrauma
|
||||
|
||||
public Character OrderGiver;
|
||||
|
||||
//legacy support
|
||||
public readonly string[] AppropriateJobs;
|
||||
public readonly string[] Options;
|
||||
public readonly string[] OptionNames;
|
||||
|
||||
static Order()
|
||||
{
|
||||
PrefabList = new List<Order>();
|
||||
Prefabs = new Dictionary<string, Order>();
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(ConfigFile);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement orderElement in doc.Root.Elements())
|
||||
foreach (string file in GameMain.Instance.GetFilesOfType(ContentType.Orders))
|
||||
{
|
||||
if (orderElement.Name.ToString().ToLowerInvariant() != "order") continue;
|
||||
var newOrder = new Order(orderElement);
|
||||
newOrder.Prefab = newOrder;
|
||||
PrefabList.Add(newOrder);
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
bool allowOverriding = false;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
allowOverriding = true;
|
||||
}
|
||||
foreach (XElement sourceElement in mainElement.Elements())
|
||||
{
|
||||
var orderElement = sourceElement.IsOverride() ? sourceElement.FirstElement() : sourceElement;
|
||||
string name = orderElement.Name.ToString();
|
||||
if (name.Equals("order", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string identifier = orderElement.GetAttributeString("identifier", null);
|
||||
if (string.IsNullOrWhiteSpace(identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in file {file}: The order element '{name}' does not have an identifier! All orders must have a unique identifier.");
|
||||
continue;
|
||||
}
|
||||
if (Prefabs.TryGetValue(identifier, out Order duplicate))
|
||||
{
|
||||
if (allowOverriding || sourceElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding an existing order '{identifier}' with another one defined in '{file}'", Color.Yellow);
|
||||
Prefabs.Remove(identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in file {file}: Duplicate element with the idenfitier '{identifier}' found in '{file}'! All orders must have a unique identifier. Use <override></override> tags to override an order with the same identifier.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var newOrder = new Order(orderElement);
|
||||
newOrder.Prefab = newOrder;
|
||||
Prefabs.Add(identifier, newOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
PrefabList = new List<Order>(Prefabs.Values);
|
||||
}
|
||||
|
||||
private Order(XElement orderElement)
|
||||
{
|
||||
AITag = orderElement.GetAttributeString("aitag", "");
|
||||
Name = TextManager.Get("OrderName." + AITag, true) ?? "Name not found";
|
||||
Identifier = orderElement.GetAttributeString("identifier", "");
|
||||
Name = TextManager.Get("OrderName." + Identifier, true) ?? "Name not found";
|
||||
|
||||
string targetItemType = orderElement.GetAttributeString("targetitemtype", "");
|
||||
if (!string.IsNullOrWhiteSpace(targetItemType))
|
||||
@@ -78,7 +119,7 @@ namespace Barotrauma
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + ConfigFile + ", item component type " + targetItemType + " not found", e);
|
||||
DebugConsole.ThrowError("Error in the order definitions: item component type " + targetItemType + " not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +131,7 @@ namespace Barotrauma
|
||||
AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]);
|
||||
Options = orderElement.GetAttributeStringArray("options", new string[0]);
|
||||
|
||||
string translatedOptionNames = TextManager.Get("OrderOptions." + AITag, true);
|
||||
string translatedOptionNames = TextManager.Get("OrderOptions." + Identifier, true);
|
||||
if (translatedOptionNames == null)
|
||||
{
|
||||
OptionNames = orderElement.GetAttributeStringArray("optionnames", new string[0]);
|
||||
@@ -116,7 +157,7 @@ namespace Barotrauma
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
SymbolSprite = new Sprite(subElement);
|
||||
SymbolSprite = new Sprite(subElement, lazyLoad: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -127,7 +168,7 @@ namespace Barotrauma
|
||||
Prefab = prefab;
|
||||
|
||||
Name = prefab.Name;
|
||||
AITag = prefab.AITag;
|
||||
Identifier = prefab.Identifier;
|
||||
ItemComponentType = prefab.ItemComponentType;
|
||||
Options = prefab.Options;
|
||||
SymbolSprite = prefab.SymbolSprite;
|
||||
@@ -155,8 +196,14 @@ namespace Barotrauma
|
||||
|
||||
public bool HasAppropriateJob(Character character)
|
||||
{
|
||||
if (AppropriateJobs == null || AppropriateJobs.Length == 0) return true;
|
||||
if (character.Info == null || character.Info.Job == null) return false;
|
||||
if (character.Info == null || character.Info.Job == null) { return false; }
|
||||
if (character.Info.Job.Prefab.AppropriateOrders.Any(appropriateOrderId => Identifier == appropriateOrderId)) { return true; }
|
||||
|
||||
if (!JobPrefab.List.Values.Any(jp => jp.AppropriateOrders.Contains(Identifier)) &&
|
||||
(AppropriateJobs == null || AppropriateJobs.Length == 0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < AppropriateJobs.Length; i++)
|
||||
{
|
||||
if (character.Info.Job.Prefab.Identifier.ToLowerInvariant() == AppropriateJobs[i].ToLowerInvariant()) return true;
|
||||
@@ -168,7 +215,7 @@ namespace Barotrauma
|
||||
{
|
||||
orderOption = orderOption ?? "";
|
||||
|
||||
string messageTag = (givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf." : "OrderDialog.") + AITag;
|
||||
string messageTag = (givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf." : "OrderDialog.") + Identifier;
|
||||
if (!string.IsNullOrEmpty(orderOption)) messageTag += "." + orderOption;
|
||||
|
||||
if (targetCharacterName == null) targetCharacterName = "";
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace Barotrauma
|
||||
get { return aiController; }
|
||||
}
|
||||
|
||||
public AICharacter(string file, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isNetworkPlayer = false, RagdollParams ragdoll = null)
|
||||
: base(file, position, seed, characterInfo, isNetworkPlayer, ragdoll)
|
||||
public AICharacter(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isNetworkPlayer = false, RagdollParams ragdoll = null)
|
||||
: base(speciesName, position, seed, characterInfo, isNetworkPlayer, ragdoll)
|
||||
{
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!CanWalk)
|
||||
{
|
||||
DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
|
||||
//DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
@@ -214,6 +214,8 @@ namespace Barotrauma
|
||||
return SwimSlowParams;
|
||||
case AnimationType.SwimFast:
|
||||
return SwimFastParams;
|
||||
case AnimationType.NotDefined:
|
||||
return null;
|
||||
default:
|
||||
throw new NotImplementedException(type.ToString());
|
||||
}
|
||||
|
||||
@@ -285,16 +285,9 @@ namespace Barotrauma
|
||||
|
||||
public override void DragCharacter(Character target, float deltaTime)
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
Limb mouthLimb = Array.Find(Limbs, l => l != null && l.MouthPos.HasValue);
|
||||
if (mouthLimb == null) mouthLimb = GetLimb(LimbType.Head);
|
||||
|
||||
if (mouthLimb == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Character \"" + character.SpeciesName + "\" failed to eat a target (a head or a limb with a mouthpos required)");
|
||||
return;
|
||||
}
|
||||
if (target == null) { return; }
|
||||
Limb mouthLimb = GetLimb(LimbType.Head);
|
||||
if (mouthLimb == null) { return; }
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -489,9 +482,9 @@ namespace Barotrauma
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.RightFoot:
|
||||
if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID))
|
||||
if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
|
||||
{
|
||||
SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.limbParams.ID] * Dir, MainLimb, FootTorque);
|
||||
SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, MainLimb, FootTorque);
|
||||
}
|
||||
break;
|
||||
case LimbType.Tail:
|
||||
@@ -557,6 +550,9 @@ namespace Barotrauma
|
||||
movementAngle -= MathHelper.TwoPi;
|
||||
}
|
||||
|
||||
float stepLift = TargetMovement.X == 0.0f ? 0 :
|
||||
(float)Math.Sin(WalkPos * CurrentGroundedParams.StepLiftFrequency + MathHelper.Pi * CurrentGroundedParams.StepLiftOffset) * (CurrentGroundedParams.StepLiftAmount / 100);
|
||||
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
if (torso != null)
|
||||
{
|
||||
@@ -566,7 +562,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (TorsoPosition.HasValue)
|
||||
{
|
||||
Vector2 pos = colliderBottom + Vector2.UnitY * TorsoPosition.Value;
|
||||
Vector2 pos = colliderBottom + new Vector2(0, TorsoPosition.Value + stepLift);
|
||||
|
||||
if (torso != MainLimb)
|
||||
{
|
||||
@@ -588,7 +584,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (HeadPosition.HasValue)
|
||||
{
|
||||
Vector2 pos = colliderBottom + Vector2.UnitY * HeadPosition.Value;
|
||||
Vector2 pos = colliderBottom + new Vector2(0, HeadPosition.Value + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier);
|
||||
|
||||
if (head != MainLimb)
|
||||
{
|
||||
@@ -673,10 +669,10 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (playFootstepSound) { PlayImpactSound(limb); }
|
||||
#endif
|
||||
if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID))
|
||||
if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
|
||||
{
|
||||
SmoothRotateWithoutWrapping(limb,
|
||||
movementAngle + CurrentGroundedParams.FootAnglesInRadians[limb.limbParams.ID] * Dir,
|
||||
movementAngle + CurrentGroundedParams.FootAnglesInRadians[limb.Params.ID] * Dir,
|
||||
MainLimb, FootTorque);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -637,9 +637,15 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!onGround) movement = Vector2.Zero;
|
||||
if (!onGround)
|
||||
{
|
||||
movement = Vector2.Zero;
|
||||
}
|
||||
|
||||
float stepLift = TargetMovement.X == 0.0f ? 0 :
|
||||
(float)Math.Sin(WalkPos * CurrentGroundedParams.StepLiftFrequency + MathHelper.Pi * CurrentGroundedParams.StepLiftOffset) * (CurrentGroundedParams.StepLiftAmount / 100);
|
||||
|
||||
float y = colliderPos.Y;
|
||||
float y = colliderPos.Y + stepLift;
|
||||
if (TorsoPosition.HasValue)
|
||||
{
|
||||
y += TorsoPosition.Value;
|
||||
@@ -648,7 +654,7 @@ namespace Barotrauma
|
||||
MathUtils.SmoothStep(torso.SimPosition,
|
||||
new Vector2(footMid + movement.X * TorsoLeanAmount, y), getUpForce);
|
||||
|
||||
y = colliderPos.Y;
|
||||
y = colliderPos.Y + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier;
|
||||
if (HeadPosition.HasValue)
|
||||
{
|
||||
y += HeadPosition.Value;
|
||||
@@ -809,10 +815,11 @@ namespace Barotrauma
|
||||
//get the elbow to a neutral rotation
|
||||
if (Math.Abs(hand.body.AngularVelocity) < 10.0f)
|
||||
{
|
||||
LimbJoint elbow =
|
||||
GetJointBetweenLimbs(armType, hand.type) ??
|
||||
GetJointBetweenLimbs(armType, foreArmType);
|
||||
hand.body.ApplyTorque(MathHelper.Clamp(-elbow.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 10.0f);
|
||||
LimbJoint elbow = GetJointBetweenLimbs(armType, hand.type) ?? GetJointBetweenLimbs(armType, foreArmType);
|
||||
if (elbow != null)
|
||||
{
|
||||
hand.body.ApplyTorque(MathHelper.Clamp(-elbow.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1848,7 +1855,11 @@ namespace Barotrauma
|
||||
}
|
||||
var torso = GetLimb(LimbType.Torso);
|
||||
var waist = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type);
|
||||
Vector2 waistPos = waist.LimbA == upperLeg ? waist.WorldAnchorA : waist.WorldAnchorB;
|
||||
Vector2 waistPos = Vector2.Zero;
|
||||
if (waist != null)
|
||||
{
|
||||
waistPos = waist.LimbA == upperLeg ? waist.WorldAnchorA : waist.WorldAnchorB;
|
||||
}
|
||||
|
||||
//distance from waist joint to the target position
|
||||
float c = Vector2.Distance(pos, waistPos);
|
||||
|
||||
-676
@@ -1,676 +0,0 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Xml;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class HumanRagdollParams : RagdollParams
|
||||
{
|
||||
public static HumanRagdollParams GetRagdollParams(string speciesName, string fileName = null) => GetRagdollParams<HumanRagdollParams>(speciesName, fileName);
|
||||
public static HumanRagdollParams GetDefaultRagdollParams(string speciesName) => GetDefaultRagdollParams<HumanRagdollParams>(speciesName);
|
||||
}
|
||||
|
||||
class FishRagdollParams : RagdollParams
|
||||
{
|
||||
public static FishRagdollParams GetDefaultRagdollParams(string speciesName) => GetDefaultRagdollParams<FishRagdollParams>(speciesName);
|
||||
}
|
||||
|
||||
class RagdollParams : EditableParams
|
||||
{
|
||||
public const float MIN_SCALE = 0.1f;
|
||||
public const float MAX_SCALE = 2;
|
||||
|
||||
public string SpeciesName { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(-360, 360, ToolTip = "Rotation offset (in degrees) used for animations and widgets. If the sprites in the sheet are in different orientations, use the orientation of the torso for the final version of your character (while editing the character in the editor, you can change the orientation freely).")]
|
||||
public float SpritesheetOrientation { get; set; }
|
||||
|
||||
private float limbScale;
|
||||
[Serialize(1.0f, true), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
|
||||
public float LimbScale { get { return limbScale; } set { limbScale = MathHelper.Clamp(value, MIN_SCALE, MAX_SCALE); } }
|
||||
|
||||
private float jointScale;
|
||||
[Serialize(1.0f, true), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
|
||||
public float JointScale { get { return jointScale; } set { jointScale = MathHelper.Clamp(value, MIN_SCALE, MAX_SCALE); } }
|
||||
|
||||
// Don't show in the editor, because shouldn't be edited in runtime. Requires that the limb scale and the collider sizes are adjusted. TODO: automatize.
|
||||
[Serialize(1f, false)]
|
||||
public float TextureScale { get; set; }
|
||||
|
||||
[Serialize(45f, true), Editable(0f, 1000f)]
|
||||
public float ColliderHeightFromFloor { get; set; }
|
||||
|
||||
[Serialize(50f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float ImpactTolerance { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool CanEnterSubmarine { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool Draggable { get; set; }
|
||||
|
||||
private static Dictionary<string, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<string, Dictionary<string, RagdollParams>>();
|
||||
|
||||
public List<ColliderParams> ColliderParams { get; private set; } = new List<ColliderParams>();
|
||||
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
|
||||
public List<JointParams> Joints { get; private set; } = new List<JointParams>();
|
||||
|
||||
protected IEnumerable<RagdollSubParams> GetAllSubParams() =>
|
||||
ColliderParams.Select(c => c as RagdollSubParams)
|
||||
.Concat(Limbs.Select(j => j as RagdollSubParams)
|
||||
.Concat(Joints.Select(j => j as RagdollSubParams)));
|
||||
|
||||
public static string GetDefaultFileName(string speciesName) => $"{speciesName.CapitaliseFirstInvariant()}DefaultRagdoll";
|
||||
public static string GetDefaultFile(string speciesName, ContentPackage contentPackage = null)
|
||||
=> Path.Combine(GetFolder(speciesName, contentPackage), $"{GetDefaultFileName(speciesName)}.xml");
|
||||
|
||||
private static readonly object[] dummyParams = new object[]
|
||||
{
|
||||
new XAttribute("type", "Dummy"),
|
||||
new XElement("collider", new XAttribute("radius", 1)),
|
||||
new XElement("limb",
|
||||
new XAttribute("id", 0),
|
||||
new XAttribute("type", LimbType.Head.ToString()),
|
||||
new XAttribute("width", 1),
|
||||
new XAttribute("height", 1),
|
||||
new XElement("sprite",
|
||||
new XAttribute("sourcerect", $"0, 0, 1, 1")))
|
||||
};
|
||||
|
||||
public static string GetFolder(string speciesName, ContentPackage contentPackage = null)
|
||||
{
|
||||
string configFilePath = Character.GetConfigFile(speciesName, contentPackage);
|
||||
var folder = XMLExtensions.TryLoadXml(configFilePath)?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
|
||||
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
|
||||
{
|
||||
folder = Path.Combine(Path.GetDirectoryName(configFilePath), "Ragdolls") + Path.DirectorySeparatorChar;
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
public static T GetDefaultRagdollParams<T>(string speciesName) where T : RagdollParams, new() => GetRagdollParams<T>(speciesName, GetDefaultFileName(speciesName));
|
||||
|
||||
/// <summary>
|
||||
/// If the file name is left null, default file is selected. If fails, will select the default file. Note: Use the filename without the extensions, don't use the full path!
|
||||
/// If a custom folder is used, it's defined in the character info file.
|
||||
/// </summary>
|
||||
public static T GetRagdollParams<T>(string speciesName, string fileName = null) where T : RagdollParams, new()
|
||||
{
|
||||
if (!allRagdolls.TryGetValue(speciesName, out Dictionary<string, RagdollParams> ragdolls))
|
||||
{
|
||||
ragdolls = new Dictionary<string, RagdollParams>();
|
||||
allRagdolls.Add(speciesName, ragdolls);
|
||||
}
|
||||
if (string.IsNullOrEmpty(fileName) || !ragdolls.TryGetValue(fileName, out RagdollParams ragdoll))
|
||||
{
|
||||
string selectedFile = null;
|
||||
string folder = GetFolder(speciesName);
|
||||
if (Directory.Exists(folder))
|
||||
{
|
||||
var files = Directory.GetFiles(folder);
|
||||
if (files.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.");
|
||||
selectedFile = GetDefaultFile(speciesName);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// Files found, but none specified
|
||||
selectedFile = GetDefaultFile(speciesName);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = files.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).ToLowerInvariant() == fileName.ToLowerInvariant());
|
||||
if (selectedFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.");
|
||||
selectedFile = GetDefaultFile(speciesName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.");
|
||||
selectedFile = GetDefaultFile(speciesName);
|
||||
}
|
||||
if (selectedFile == null)
|
||||
{
|
||||
throw new Exception("[RagdollParams] Selected file null!");
|
||||
}
|
||||
DebugConsole.Log($"[RagdollParams] Loading ragdoll from {selectedFile}.");
|
||||
T r = new T();
|
||||
if (r.Load(selectedFile, speciesName))
|
||||
{
|
||||
if (!ragdolls.ContainsKey(r.Name))
|
||||
{
|
||||
ragdolls.Add(r.Name, r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Failed to load ragdoll {r} at {selectedFile} for the character {speciesName}. Creating a dummy file.");
|
||||
var defaultFile = GetDefaultFile(speciesName);
|
||||
if (File.Exists(defaultFile))
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Renaming the invalid file as {selectedFile}.invalid");
|
||||
// Rename the old file so that it's not lost.
|
||||
File.Move(defaultFile, defaultFile + ".invalid");
|
||||
}
|
||||
return CreateDefault<T>(defaultFile, speciesName, dummyParams);
|
||||
}
|
||||
}
|
||||
return (T)ragdoll;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a default ragdoll for the species using a predefined configuration.
|
||||
/// Note: Use only to create ragdolls for new characters, because this overrides the old ragdoll!
|
||||
/// </summary>
|
||||
public static T CreateDefault<T>(string fullPath, string speciesName, params object[] ragdollConfig) where T : RagdollParams, new()
|
||||
{
|
||||
// Remove the old ragdolls, if found.
|
||||
if (allRagdolls.ContainsKey(speciesName))
|
||||
{
|
||||
DebugConsole.NewMessage($"[RagdollParams] Removing the old ragdolls from {speciesName}.", Color.Red);
|
||||
allRagdolls.Remove(speciesName);
|
||||
}
|
||||
var ragdolls = new Dictionary<string, RagdollParams>();
|
||||
allRagdolls.Add(speciesName, ragdolls);
|
||||
var instance = new T();
|
||||
XElement ragdollElement = new XElement("Ragdoll", ragdollConfig);
|
||||
instance.doc = new XDocument(ragdollElement);
|
||||
instance.UpdatePath(fullPath);
|
||||
instance.IsLoaded = instance.Deserialize(ragdollElement);
|
||||
instance.Save();
|
||||
instance.Load(fullPath, speciesName);
|
||||
ragdolls.Add(instance.Name, instance);
|
||||
DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
|
||||
return instance as T;
|
||||
}
|
||||
|
||||
protected override void UpdatePath(string fullPath)
|
||||
{
|
||||
if (SpeciesName == null)
|
||||
{
|
||||
base.UpdatePath(fullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the key by removing and re-adding the ragdoll.
|
||||
if (allRagdolls.TryGetValue(SpeciesName, out Dictionary<string, RagdollParams> ragdolls))
|
||||
{
|
||||
ragdolls.Remove(Name);
|
||||
}
|
||||
base.UpdatePath(fullPath);
|
||||
if (ragdolls != null)
|
||||
{
|
||||
if (!ragdolls.ContainsKey(Name))
|
||||
{
|
||||
ragdolls.Add(Name, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Save(string fileNameWithoutExtension = null)
|
||||
{
|
||||
OriginalElement = MainElement;
|
||||
GetAllSubParams().ForEach(p => p.SetCurrentElementAsOriginalElement());
|
||||
Serialize();
|
||||
return base.Save(fileNameWithoutExtension, new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = true,
|
||||
NewLineOnAttributes = false
|
||||
});
|
||||
}
|
||||
|
||||
protected bool Load(string file, string speciesName)
|
||||
{
|
||||
if (Load(file))
|
||||
{
|
||||
SpeciesName = speciesName;
|
||||
CreateColliders();
|
||||
CreateLimbs();
|
||||
CreateJoints();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Reset(bool forceReload = false)
|
||||
{
|
||||
if (forceReload)
|
||||
{
|
||||
return Load(FullPath, SpeciesName);
|
||||
}
|
||||
Deserialize(OriginalElement, recursive: true);
|
||||
GetAllSubParams().ForEach(sp => sp.Reset());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void CreateColliders()
|
||||
{
|
||||
ColliderParams.Clear();
|
||||
for (int i = 0; i < MainElement.Elements("collider").Count(); i++)
|
||||
{
|
||||
var element = MainElement.Elements("collider").ElementAt(i);
|
||||
string name = i > 0 ? "Secondary Collider" : "Main Collider";
|
||||
ColliderParams.Add(new ColliderParams(element, this, name));
|
||||
}
|
||||
}
|
||||
|
||||
protected void CreateLimbs()
|
||||
{
|
||||
Limbs.Clear();
|
||||
foreach (var element in MainElement.Elements("limb"))
|
||||
{
|
||||
Limbs.Add(new LimbParams(element, this));
|
||||
}
|
||||
Limbs = Limbs.OrderBy(l => l.ID).ToList();
|
||||
}
|
||||
|
||||
protected void CreateJoints()
|
||||
{
|
||||
Joints.Clear();
|
||||
foreach (var element in MainElement.Elements("joint"))
|
||||
{
|
||||
Joints.Add(new JointParams(element, this));
|
||||
}
|
||||
}
|
||||
|
||||
protected bool Deserialize(XElement element = null, bool recursive = true)
|
||||
{
|
||||
if (base.Deserialize(element))
|
||||
{
|
||||
if (recursive)
|
||||
{
|
||||
GetAllSubParams().ForEach(p => p.Deserialize());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool Serialize(XElement element = null, bool recursive = true)
|
||||
{
|
||||
if (base.Serialize(element))
|
||||
{
|
||||
if (recursive)
|
||||
{
|
||||
GetAllSubParams().ForEach(p => p.Serialize());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Memento
|
||||
public override void CreateSnapshot()
|
||||
{
|
||||
Serialize();
|
||||
if (doc == null)
|
||||
{
|
||||
DebugConsole.ThrowError("[RagdollParams] The source XML Document is null!");
|
||||
return;
|
||||
}
|
||||
var copy = new RagdollParams
|
||||
{
|
||||
IsLoaded = true,
|
||||
doc = new XDocument(doc)
|
||||
};
|
||||
copy.CreateColliders();
|
||||
copy.CreateLimbs();
|
||||
copy.CreateJoints();
|
||||
copy.Deserialize();
|
||||
copy.Serialize();
|
||||
memento.Store(copy);
|
||||
}
|
||||
public override void Undo() => RevertTo(memento.Undo() as RagdollParams);
|
||||
public override void Redo() => RevertTo(memento.Redo() as RagdollParams);
|
||||
|
||||
private void RevertTo(RagdollParams source)
|
||||
{
|
||||
if (source.MainElement == null)
|
||||
{
|
||||
DebugConsole.ThrowError("[RagdollParams] The source XML Element of the given RagdollParams is null!");
|
||||
return;
|
||||
}
|
||||
Deserialize(source.MainElement, recursive: false);
|
||||
var sourceSubParams = source.GetAllSubParams().ToList();
|
||||
var subParams = GetAllSubParams().ToList();
|
||||
// TODO: cannot currently undo joint/limb deletion.
|
||||
if (sourceSubParams.Count != subParams.Count)
|
||||
{
|
||||
DebugConsole.ThrowError("[RagdollParams] The count of the sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < subParams.Count; i++)
|
||||
{
|
||||
var subSubParams = subParams[i].SubParams;
|
||||
if (subSubParams.Count != sourceSubParams[i].SubParams.Count)
|
||||
{
|
||||
DebugConsole.ThrowError("[RagdollParams] The count of the sub sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.");
|
||||
return;
|
||||
}
|
||||
subParams[i].Deserialize(sourceSubParams[i].Element, recursive: false);
|
||||
for (int j = 0; j < subSubParams.Count; j++)
|
||||
{
|
||||
subSubParams[j].Deserialize(sourceSubParams[i].SubParams[j].Element, recursive: false);
|
||||
// Since we cannot use recursion here, we have to go deeper manually, if necessary.
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#if CLIENT
|
||||
public void AddToEditor(ParamsEditor editor, bool alsoChildren = true)
|
||||
{
|
||||
base.AddToEditor(editor);
|
||||
if (alsoChildren)
|
||||
{
|
||||
var subParams = GetAllSubParams();
|
||||
foreach (var subParam in subParams)
|
||||
{
|
||||
subParam.AddToEditor(editor);
|
||||
new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, 10), editor.EditorBox.Content.RectTransform),
|
||||
style: null, color: Color.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class JointParams : RagdollSubParams
|
||||
{
|
||||
public JointParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
|
||||
|
||||
private string name;
|
||||
[Serialize("", true), Editable]
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = GenerateName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
set
|
||||
{
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string GenerateName() => $"Joint {Limb1} - {Limb2}";
|
||||
|
||||
[Serialize(-1, true), Editable]
|
||||
public int Limb1 { get; set; }
|
||||
|
||||
[Serialize(-1, true), Editable]
|
||||
public int Limb2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be converted to sim units.
|
||||
/// </summary>
|
||||
[Serialize("1.0, 1.0", true), Editable]
|
||||
public Vector2 Limb1Anchor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be converted to sim units.
|
||||
/// </summary>
|
||||
[Serialize("1.0, 1.0", true), Editable]
|
||||
public Vector2 Limb2Anchor { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool CanBeSevered { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool LimitEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(0f, true), Editable]
|
||||
public float UpperLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(0f, true), Editable]
|
||||
public float LowerLimit { get; set; }
|
||||
|
||||
[Serialize(0.25f, true), Editable]
|
||||
public float Stiffness { get; set; }
|
||||
}
|
||||
|
||||
class LimbParams : RagdollSubParams
|
||||
{
|
||||
public LimbParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll)
|
||||
{
|
||||
var spriteElement = element.Element("sprite");
|
||||
if (spriteElement != null)
|
||||
{
|
||||
normalSpriteParams = new SpriteParams(spriteElement, ragdoll);
|
||||
SubParams.Add(normalSpriteParams);
|
||||
}
|
||||
var damagedElement = element.Element("damagedsprite");
|
||||
if (damagedElement != null)
|
||||
{
|
||||
damagedSpriteParams = new SpriteParams(damagedElement, ragdoll);
|
||||
// Hide the damaged sprite params in the editor for now.
|
||||
//SubParams.Add(damagedSpriteParams);
|
||||
}
|
||||
var deformElement = element.Element("deformablesprite");
|
||||
if (deformElement != null)
|
||||
{
|
||||
deformSpriteParams = new SpriteParams(deformElement, ragdoll);
|
||||
SubParams.Add(deformSpriteParams);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly SpriteParams normalSpriteParams;
|
||||
public readonly SpriteParams damagedSpriteParams;
|
||||
public readonly SpriteParams deformSpriteParams;
|
||||
|
||||
private string name;
|
||||
[Serialize("", true), Editable]
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = GenerateName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
set
|
||||
{
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string GenerateName() => $"Limb {ID}";
|
||||
|
||||
/// <summary>
|
||||
/// Note that editing this in-game doesn't currently have any effect (unless the ragdoll is recreated). It should be visible, but readonly in the editor.
|
||||
/// </summary>
|
||||
[Serialize(-1, true), Editable]
|
||||
public int ID { get; set; }
|
||||
|
||||
[Serialize(LimbType.None, true), Editable]
|
||||
public LimbType Type { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool Flip { get; set; }
|
||||
|
||||
[Serialize(0, true), Editable]
|
||||
public int HealthIndex { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(ToolTip = "Higher values make AI characters prefer attacking this limb.")]
|
||||
public float AttackPriority { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float SteerForce { get; set; }
|
||||
|
||||
[Serialize("0, 0", true), Editable(ToolTip = "Only applicable if this limb is a foot. Determines the \"neutral position\" of the foot relative to a joint determined by the \"RefJoint\" parameter. For example, a value of {-100, 0} would mean that the foot is positioned on the floor, 100 units behind the reference joint.")]
|
||||
public Vector2 StepOffset { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Radius { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Height { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Width { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10000)]
|
||||
public float Mass { get; set; }
|
||||
|
||||
[Serialize(10f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float Density { get; set; }
|
||||
|
||||
[Serialize("0, 0", true), Editable(ToolTip = "The position which is used to lead the IK chain to the IK goal. Only applicable if the limb is hand or foot.")]
|
||||
public Vector2 PullPos { get; set; }
|
||||
|
||||
[Serialize(-1, true), Editable(ToolTip = "Only applicable if this limb is a foot. Determines which joint is used as the \"neutral x-position\" for the foot movement. For example in the case of a humanoid-shaped characters this would usually be the waist. The position can be offset using the StepOffset parameter.")]
|
||||
public int RefJoint { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool IgnoreCollisions { get; set; }
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string Notes { get; set; }
|
||||
|
||||
// Non-editable ->
|
||||
[Serialize(0.3f, true)]
|
||||
public float Friction { get; set; }
|
||||
|
||||
[Serialize(0.05f, true)]
|
||||
public float Restitution { get; set; }
|
||||
}
|
||||
|
||||
class SpriteParams : RagdollSubParams
|
||||
{
|
||||
public SpriteParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
|
||||
|
||||
[Serialize("0, 0, 0, 0", true), Editable]
|
||||
public Rectangle SourceRect { get; set; }
|
||||
|
||||
[Serialize("0.5, 0.5", true), Editable(DecimalCount = 2, ToolTip = "Relative to the collider.")]
|
||||
public Vector2 Origin { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(DecimalCount = 3)]
|
||||
public float Depth { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string Texture { get; set; }
|
||||
}
|
||||
|
||||
class ColliderParams : RagdollSubParams
|
||||
{
|
||||
public ColliderParams(XElement element, RagdollParams ragdoll, string name = null) : base(element, ragdoll)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
private string name;
|
||||
[Serialize("", true), Editable]
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = GenerateName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
set
|
||||
{
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Radius { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Height { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Width { get; set; }
|
||||
}
|
||||
|
||||
abstract class RagdollSubParams : ISerializableEntity
|
||||
{
|
||||
public virtual string Name { get; set; }
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
public XElement Element { get; set; }
|
||||
public XElement OriginalElement { get; protected set; }
|
||||
public List<RagdollSubParams> SubParams { get; set; } = new List<RagdollSubParams>();
|
||||
public RagdollParams Ragdoll { get; private set; }
|
||||
|
||||
public virtual string GenerateName() => Element.Name.ToString();
|
||||
|
||||
public RagdollSubParams(XElement element, RagdollParams ragdoll)
|
||||
{
|
||||
Element = element;
|
||||
OriginalElement = new XElement(element);
|
||||
Ragdoll = ragdoll;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public virtual bool Deserialize(XElement element = null, bool recursive = true)
|
||||
{
|
||||
element = element ?? Element;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.Deserialize());
|
||||
}
|
||||
return SerializableProperties != null;
|
||||
}
|
||||
|
||||
public virtual bool Serialize(XElement element = null, bool recursive = true)
|
||||
{
|
||||
element = element ?? Element;
|
||||
SerializableProperty.SerializeProperties(this, element, true);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.Serialize());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetCurrentElementAsOriginalElement()
|
||||
{
|
||||
OriginalElement = Element;
|
||||
SubParams.ForEach(sp => sp.SetCurrentElementAsOriginalElement());
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Deserialize(OriginalElement, false);
|
||||
SubParams.ForEach(sp => sp.Reset());
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public SerializableEntityEditor SerializableEntityEditor { get; protected set; }
|
||||
public virtual void AddToEditor(ParamsEditor editor)
|
||||
{
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, inGame: false, showName: true);
|
||||
SubParams.ForEach(sp => sp.AddToEditor(editor));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using LimbParams = Barotrauma.RagdollParams.LimbParams;
|
||||
using JointParams = Barotrauma.RagdollParams.JointParams;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -223,7 +225,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) continue;
|
||||
if (limb.IsSevered || !limb.body.PhysEnabled) { continue; }
|
||||
limb.body.SetTransform(Collider.SimPosition, Collider.Rotation);
|
||||
//reset pull joints (they may be somewhere far away if the character has moved from the position where animations were last updated)
|
||||
limb.PullJointEnabled = false;
|
||||
@@ -233,8 +235,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// Currently the camera cannot handle greater speeds. It starts to lag behind.
|
||||
public const float MAX_SPEED = 9;
|
||||
public const float MAX_SPEED = 15;
|
||||
|
||||
public Vector2 TargetMovement
|
||||
{
|
||||
@@ -258,6 +259,7 @@ namespace Barotrauma
|
||||
public float ImpactTolerance => RagdollParams.ImpactTolerance;
|
||||
public bool Draggable => RagdollParams.Draggable;
|
||||
public bool CanEnterSubmarine => RagdollParams.CanEnterSubmarine;
|
||||
public bool CanAttackSubmarine => Limbs.Any(l => l.attack != null && l.attack.IsValidTarget(AttackTarget.Structure));
|
||||
|
||||
public float Dir
|
||||
{
|
||||
@@ -317,7 +319,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
items = limbs?.ToDictionary(l => l.limbParams, l => l.WearingItems);
|
||||
items = limbs?.ToDictionary(l => l.Params, l => l.WearingItems);
|
||||
}
|
||||
foreach (var limbParams in RagdollParams.Limbs)
|
||||
{
|
||||
@@ -327,7 +329,7 @@ namespace Barotrauma
|
||||
limbParams.Radius = 10;
|
||||
}
|
||||
}
|
||||
foreach (var colliderParams in RagdollParams.ColliderParams)
|
||||
foreach (var colliderParams in RagdollParams.Colliders)
|
||||
{
|
||||
if (!PhysicsBody.IsValidShape(colliderParams.Radius, colliderParams.Height, colliderParams.Width))
|
||||
{
|
||||
@@ -352,11 +354,16 @@ namespace Barotrauma
|
||||
limb.WearingItems.AddRange(itemList);
|
||||
}
|
||||
}
|
||||
if (character.SpeciesName.ToLowerInvariant() == "humanhusk")
|
||||
|
||||
if (character.IsHusk)
|
||||
{
|
||||
if (Limbs.None(l => l.Name.ToLowerInvariant() == "huskappendage"))
|
||||
if (Character.TryGetConfigFile(character.ConfigPath, out XDocument configFile))
|
||||
{
|
||||
AfflictionHusk.AttachHuskAppendage(character, this);
|
||||
var mainElement = configFile.Root.IsOverride() ? configFile.Root.FirstElement() : configFile.Root;
|
||||
foreach (var huskAppendage in mainElement.GetChildElements("huskappendage"))
|
||||
{
|
||||
AfflictionHusk.AttachHuskAppendage(character, huskAppendage.GetAttributeString("affliction", string.Empty), huskAppendage, ragdoll: this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,7 +383,7 @@ namespace Barotrauma
|
||||
}
|
||||
DebugConsole.Log($"Creating colliders from {RagdollParams.Name}.");
|
||||
collider = new List<PhysicsBody>();
|
||||
foreach (ColliderParams cParams in RagdollParams.ColliderParams)
|
||||
foreach (var cParams in RagdollParams.Colliders)
|
||||
{
|
||||
if (!PhysicsBody.IsValidShape(cParams.Radius, cParams.Height, cParams.Width))
|
||||
{
|
||||
@@ -456,14 +463,12 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void SaveRagdoll(string fileNameWithoutExtension = null)
|
||||
{
|
||||
SaveJoints();
|
||||
SaveLimbs();
|
||||
RagdollParams.Save(fileNameWithoutExtension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the serializable data to the currently selected ragdoll params.
|
||||
/// Force reloading always loads the xml stored in the disk.
|
||||
/// Force reloading always loads the xml stored on the disk.
|
||||
/// </summary>
|
||||
public void ResetRagdoll(bool forceReload = false)
|
||||
{
|
||||
@@ -472,24 +477,6 @@ namespace Barotrauma
|
||||
ResetLimbs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current joint values to the serializable joint params. This method should properly handle character flipping.
|
||||
/// NOTE: Currently all the params are handled stored as SubRagdollParams and handled in the RagdollParams Save method. This method does nothing.
|
||||
/// </summary>
|
||||
public void SaveJoints()
|
||||
{
|
||||
LimbJoints.ForEach(j => j.SaveParams());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles custom serialization per limb. Currently only the attacks need to be serialized, since they cannot be stored as SubRagdollParams (because they shouldn't be decoupled with ragdolls).
|
||||
/// Note: Saving to file is not handled by this method. Calling RagdollParams.Save() after this method should work.
|
||||
/// </summary>
|
||||
public void SaveLimbs()
|
||||
{
|
||||
Limbs.ForEach(l => l.attack?.Serialize());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current joint values to the serialized joint params.
|
||||
/// </summary>
|
||||
@@ -792,17 +779,9 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb == null || limb.IsSevered) continue;
|
||||
|
||||
if (limb == null || limb.IsSevered) { continue; }
|
||||
limb.Dir = Dir;
|
||||
|
||||
if (limb.MouthPos.HasValue)
|
||||
{
|
||||
limb.MouthPos = new Vector2(
|
||||
-limb.MouthPos.Value.X,
|
||||
limb.MouthPos.Value.Y);
|
||||
}
|
||||
|
||||
limb.MouthPos = new Vector2(-limb.MouthPos.X, limb.MouthPos.Y);
|
||||
limb.MirrorPullJoint();
|
||||
}
|
||||
|
||||
@@ -1052,6 +1031,21 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private float bodyInRestTimer;
|
||||
|
||||
private float BodyInRestDelay = 1.0f;
|
||||
|
||||
public bool BodyInRest
|
||||
{
|
||||
get { return bodyInRestTimer > BodyInRestDelay; }
|
||||
set
|
||||
{
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
limb.body.PhysEnabled = !value;
|
||||
}
|
||||
bodyInRestTimer = value ? BodyInRestDelay : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public bool forceStanding;
|
||||
|
||||
public void Update(float deltaTime, Camera cam)
|
||||
@@ -1335,7 +1329,7 @@ namespace Barotrauma
|
||||
else if (Limbs.All(l => l != null && !l.body.Enabled || l.LinearVelocity.LengthSquared() < 0.001f))
|
||||
{
|
||||
bodyInRestTimer += deltaTime;
|
||||
if (bodyInRestTimer > 1.0f)
|
||||
if (bodyInRestTimer > BodyInRestDelay)
|
||||
{
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
@@ -1615,24 +1609,27 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.NetworkMember == null) return;
|
||||
|
||||
float lowestSubPos = ConvertUnits.ToSimUnits(Submarine.Loaded.Min(s => s.HiddenSubPosition.Y - s.Borders.Height - 128.0f));
|
||||
|
||||
for (int i = 0; i < character.MemState.Count; i++ )
|
||||
float lowestSubPos = float.MaxValue;
|
||||
if (Submarine.Loaded.Any())
|
||||
{
|
||||
if (character.Submarine == null)
|
||||
lowestSubPos = ConvertUnits.ToSimUnits(Submarine.Loaded.Min(s => s.HiddenSubPosition.Y - s.Borders.Height - 128.0f));
|
||||
for (int i = 0; i < character.MemState.Count; i++)
|
||||
{
|
||||
//transform in-sub coordinates to outside coordinates
|
||||
if (character.MemState[i].Position.Y > lowestSubPos)
|
||||
character.MemState[i].TransformInToOutside();
|
||||
}
|
||||
else if (currentHull?.Submarine != null)
|
||||
{
|
||||
//transform outside coordinates to in-sub coordinates
|
||||
if (character.MemState[i].Position.Y < lowestSubPos)
|
||||
character.MemState[i].TransformOutToInside(currentHull.Submarine);
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
//transform in-sub coordinates to outside coordinates
|
||||
if (character.MemState[i].Position.Y > lowestSubPos)
|
||||
character.MemState[i].TransformInToOutside();
|
||||
}
|
||||
else if (currentHull?.Submarine != null)
|
||||
{
|
||||
//transform outside coordinates to in-sub coordinates
|
||||
if (character.MemState[i].Position.Y < lowestSubPos)
|
||||
character.MemState[i].TransformOutToInside(currentHull.Submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UpdateNetPlayerPositionProjSpecific(deltaTime, lowestSubPos);
|
||||
}
|
||||
|
||||
@@ -1663,23 +1660,15 @@ namespace Barotrauma
|
||||
|
||||
public Vector2? GetMouthPosition()
|
||||
{
|
||||
Limb mouthLimb = Array.Find(Limbs, l => l != null && l.MouthPos.HasValue);
|
||||
if (mouthLimb == null) mouthLimb = GetLimb(LimbType.Head);
|
||||
if (mouthLimb == null) return null;
|
||||
|
||||
Vector2 mouthPos = mouthLimb.SimPosition;
|
||||
if (mouthLimb.MouthPos.HasValue)
|
||||
{
|
||||
float cos = (float)Math.Cos(mouthLimb.Rotation);
|
||||
float sin = (float)Math.Sin(mouthLimb.Rotation);
|
||||
mouthPos += new Vector2(
|
||||
mouthLimb.MouthPos.Value.X * cos - mouthLimb.MouthPos.Value.Y * sin,
|
||||
mouthLimb.MouthPos.Value.X * sin + mouthLimb.MouthPos.Value.Y * cos) * RagdollParams.LimbScale;
|
||||
}
|
||||
return mouthPos;
|
||||
Limb mouthLimb = GetLimb(LimbType.Head);
|
||||
if (mouthLimb == null) { return null; }
|
||||
float cos = (float)Math.Cos(mouthLimb.Rotation);
|
||||
float sin = (float)Math.Sin(mouthLimb.Rotation);
|
||||
Vector2 bodySize = mouthLimb.body.GetSize();
|
||||
Vector2 offset = new Vector2(mouthLimb.MouthPos.X * bodySize.X / 2, mouthLimb.MouthPos.Y * bodySize.Y / 2);
|
||||
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * RagdollParams.LimbScale;
|
||||
}
|
||||
|
||||
|
||||
public Vector2 GetColliderBottom()
|
||||
{
|
||||
float offset = 0.0f;
|
||||
|
||||
@@ -30,7 +30,9 @@ namespace Barotrauma
|
||||
FallBack,
|
||||
FallBackUntilCanAttack,
|
||||
PursueIfCanAttack,
|
||||
Pursue
|
||||
Pursue,
|
||||
FollowThrough,
|
||||
FollowThroughUntilCanAttack
|
||||
}
|
||||
|
||||
struct AttackResult
|
||||
@@ -65,42 +67,43 @@ namespace Barotrauma
|
||||
AppliedDamageModifiers = appliedDamageModifiers;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial class Attack : ISerializableEntity
|
||||
{
|
||||
public readonly XElement SourceElement;
|
||||
|
||||
[Serialize(AttackContext.NotDefined, true), Editable]
|
||||
[Serialize(AttackContext.NotDefined, true, description: "Is the attack used only in a specific condition?"), Editable]
|
||||
public AttackContext Context { get; private set; }
|
||||
|
||||
[Serialize(AttackTarget.Any, true), Editable]
|
||||
[Serialize(AttackTarget.Any, true, description: "Does the attack target only specific targets?"), Editable]
|
||||
public AttackTarget TargetType { get; private set; }
|
||||
|
||||
[Serialize(HitDetection.Distance, true), Editable]
|
||||
[Serialize(LimbType.None, true, description: "If not defined or set to none, the closest limb is used (default)."), Editable]
|
||||
public LimbType TargetLimbType { get; private set; }
|
||||
|
||||
[Serialize(HitDetection.Distance, true, description: "Collision detection is more accurate, but it only affects targets that are in contact with the limb."), Editable]
|
||||
public HitDetection HitDetectionType { get; private set; }
|
||||
|
||||
[Serialize(AIBehaviorAfterAttack.FallBack, true), Editable(ToolTip = "The preferred AI behavior after the attack.")]
|
||||
[Serialize(AIBehaviorAfterAttack.FallBack, true, description: "The preferred AI behavior after the attack."), Editable]
|
||||
public AIBehaviorAfterAttack AfterAttack { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable(ToolTip = "Should the ai try to reverse when aiming with this attack?")]
|
||||
[Serialize(false, true, description: "Should the AI try to reverse when aiming with this attack?"), Editable]
|
||||
public bool Reverse { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target before the AI tries to attack.")]
|
||||
[Serialize(0.0f, true, description: "The min distance from the attack limb to the target before the AI tries to attack."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
|
||||
public float Range { get; set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target to do damage. In distance based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired.")]
|
||||
[Serialize(0.0f, true, description: "The min distance from the attack limb to the target to do damage. In distance-based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
|
||||
public float DamageRange { get; set; }
|
||||
|
||||
[Serialize(0.25f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, DecimalCount = 2, ToolTip = "An approximation of the attack duration. Effectively defines the time window in which the hit can be registered. If set to too low value, it's possible that the attack won't hit the target in time.")]
|
||||
[Serialize(0.25f, true, description: "An approximation of the attack duration. Effectively defines the time window in which the hit can be registered. If set to too low value, it's possible that the attack won't hit the target in time."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, DecimalCount = 2)]
|
||||
public float Duration { get; private set; }
|
||||
|
||||
[Serialize(5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "How long the AI waits between the attacks.")]
|
||||
[Serialize(5f, true, description: "How long the AI waits between the attacks."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2)]
|
||||
public float CoolDown { get; set; } = 5;
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "Used as the attack cooldown between different kind of attacks. Does not have effect, if set to 0.")]
|
||||
[Serialize(0f, true, description: "Used as the attack cooldown between different kind of attacks. Does not have effect, if set to 0."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2)]
|
||||
public float SecondaryCoolDown { get; set; } = 0;
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2, ToolTip = "Random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases).")]
|
||||
[Serialize(0f, true, description: "A random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases)."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)]
|
||||
public float CoolDownRandomFactor { get; private set; } = 0;
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
|
||||
@@ -115,7 +118,7 @@ namespace Barotrauma
|
||||
[Serialize(0.0f, false)]
|
||||
public float Stun { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
[Serialize(false, true, description: "Can damage only Humans."), Editable]
|
||||
public bool OnlyHumans { get; private set; }
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
@@ -139,36 +142,38 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f, ToolTip = "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs). The direction of the force is towards the target that's being attacked.")]
|
||||
[Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs). The direction of the force is towards the target that's being attacked."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
public float Force { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f, ToolTip = "Applied to the attacking limb.")]
|
||||
[Serialize(0.0f, true, description: "Applied to the attacking limb."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
public float Torque { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool ApplyForcesOnlyOnce { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f, ToolTip = "Applied to the target the attack hits. The direction of the impulse is from this limb towards the target (use negative values to pull the target closer).")]
|
||||
[Serialize(0.0f, true, description: "Applied to the target the attack hits. The direction of the impulse is from this limb towards the target (use negative values to pull the target closer)."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
public float TargetImpulse { get; private set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true), Editable(ToolTip = "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards).")]
|
||||
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards)."), Editable]
|
||||
public Vector2 TargetImpulseWorld { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(-1000.0f, 1000.0f, ToolTip = "Applied to the target the attack hits. The direction of the force is from this limb towards the target (use negative values to pull the target closer).")]
|
||||
[Serialize(0.0f, true, description: "Applied to the target the attack hits. The direction of the force is from this limb towards the target (use negative values to pull the target closer)."), Editable(-1000.0f, 1000.0f)]
|
||||
public float TargetForce { get; private set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true), Editable(ToolTip = "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards).")]
|
||||
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards)."), Editable]
|
||||
public Vector2 TargetForceWorld { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
[Serialize(0.0f, true, description: "How likely the attack causes target limbs to be severed when the target is dead."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float SeverLimbsProbability { get; set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float StickChance { get; set; }
|
||||
// TODO: disabled because not synced
|
||||
//[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
//public float StickChance { get; set; }
|
||||
public float StickChance => 0f;
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
[Serialize(0.0f, true, description: ""), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float Priority { get; private set; }
|
||||
|
||||
|
||||
public IEnumerable<StatusEffect> StatusEffects
|
||||
{
|
||||
get { return statusEffects; }
|
||||
@@ -186,7 +191,7 @@ namespace Barotrauma
|
||||
//(if none, force is applied only to the limb the attack is attached to)
|
||||
public readonly List<int> ForceOnLimbIndices = new List<int>();
|
||||
|
||||
public readonly List<Affliction> Afflictions = new List<Affliction>();
|
||||
public readonly Dictionary<Affliction, XElement> Afflictions = new Dictionary<Affliction, XElement>();
|
||||
|
||||
/// <summary>
|
||||
/// Only affects ai decision making. All the conditionals has to be met in order to select the attack. TODO: allow to define conditionals using any (implemented in StatusEffect -> move from there to PropertyConditional?)
|
||||
@@ -207,7 +212,7 @@ namespace Barotrauma
|
||||
public List<Affliction> GetMultipliedAfflictions(float multiplier)
|
||||
{
|
||||
List<Affliction> multipliedAfflictions = new List<Affliction>();
|
||||
foreach (Affliction affliction in Afflictions)
|
||||
foreach (Affliction affliction in Afflictions.Keys)
|
||||
{
|
||||
multipliedAfflictions.Add(affliction.Prefab.Instantiate(affliction.Strength * multiplier, affliction.Source));
|
||||
}
|
||||
@@ -227,7 +232,7 @@ namespace Barotrauma
|
||||
public float GetTotalDamage(bool includeStructureDamage = false)
|
||||
{
|
||||
float totalDamage = includeStructureDamage ? StructureDamage : 0.0f;
|
||||
foreach (Affliction affliction in Afflictions)
|
||||
foreach (Affliction affliction in Afflictions.Keys)
|
||||
{
|
||||
totalDamage += affliction.GetVitalityDecrease(null);
|
||||
}
|
||||
@@ -236,9 +241,9 @@ namespace Barotrauma
|
||||
|
||||
public Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float range = 0.0f)
|
||||
{
|
||||
if (damage > 0.0f) Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damage));
|
||||
if (bleedingDamage > 0.0f) Afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamage));
|
||||
if (burnDamage > 0.0f) Afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamage));
|
||||
if (damage > 0.0f) Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damage), null);
|
||||
if (bleedingDamage > 0.0f) Afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamage), null);
|
||||
if (burnDamage > 0.0f) Afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamage), null);
|
||||
|
||||
Range = range;
|
||||
DamageRange = range;
|
||||
@@ -247,8 +252,7 @@ namespace Barotrauma
|
||||
|
||||
public Attack(XElement element, string parentDebugName)
|
||||
{
|
||||
SourceElement = element;
|
||||
Deserialize();
|
||||
Deserialize(element);
|
||||
|
||||
if (element.Attribute("damage") != null ||
|
||||
element.Attribute("bluntdamage") != null ||
|
||||
@@ -258,8 +262,6 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Define damage as afflictions instead of using the damage attribute (e.g. <Affliction identifier=\"internaldamage\" strength=\"10\" />).");
|
||||
}
|
||||
|
||||
DamageRange = element.GetAttributeFloat("damagerange", 0f);
|
||||
|
||||
InitProjSpecific(element);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
@@ -297,10 +299,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength");
|
||||
var affliction = afflictionPrefab.Instantiate(afflictionStrength);
|
||||
affliction.ApplyProbability = subElement.GetAttributeFloat("probability", 1.0f);
|
||||
Afflictions.Add(affliction);
|
||||
//float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength");
|
||||
//var affliction = afflictionPrefab.Instantiate(afflictionStrength);
|
||||
//Afflictions.Add(affliction, subElement);
|
||||
|
||||
break;
|
||||
case "conditional":
|
||||
@@ -310,21 +311,50 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
partial void InitProjSpecific(XElement element);
|
||||
partial void InitProjSpecific(XElement element = null);
|
||||
|
||||
public void Serialize()
|
||||
public void ReloadAfflictions(XElement element)
|
||||
{
|
||||
if (SourceElement == null) { return; }
|
||||
SerializableProperty.SerializeProperties(this, SourceElement, true);
|
||||
Afflictions.Clear();
|
||||
foreach (var subElement in element.GetChildElements("affliction"))
|
||||
{
|
||||
AfflictionPrefab afflictionPrefab;
|
||||
Affliction affliction;
|
||||
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
|
||||
afflictionPrefab = AfflictionPrefab.List.Find(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
|
||||
if (afflictionPrefab != null)
|
||||
{
|
||||
float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength");
|
||||
affliction = afflictionPrefab.Instantiate(afflictionStrength);
|
||||
}
|
||||
else
|
||||
{
|
||||
affliction = new Affliction(null, 0);
|
||||
}
|
||||
affliction.Deserialize(subElement);
|
||||
// add the affliction anyway, so that it can be shown in the editor.
|
||||
Afflictions.Add(affliction, subElement);
|
||||
}
|
||||
}
|
||||
|
||||
public void Deserialize()
|
||||
public void Serialize(XElement element)
|
||||
{
|
||||
if (SourceElement == null) { return; }
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, SourceElement);
|
||||
SerializableProperty.SerializeProperties(this, element, true);
|
||||
foreach (var affliction in Afflictions)
|
||||
{
|
||||
if (affliction.Value != null)
|
||||
{
|
||||
affliction.Key.Serialize(affliction.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Deserialize(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
ReloadAfflictions(element);
|
||||
}
|
||||
|
||||
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true)
|
||||
@@ -332,7 +362,10 @@ namespace Barotrauma
|
||||
Character targetCharacter = target as Character;
|
||||
if (OnlyHumans)
|
||||
{
|
||||
if (targetCharacter != null && targetCharacter.ConfigPath != Character.HumanConfigFile) return new AttackResult();
|
||||
if (targetCharacter != null && !targetCharacter.IsHuman)
|
||||
{
|
||||
return new AttackResult();
|
||||
}
|
||||
}
|
||||
|
||||
SetUser(attacker);
|
||||
@@ -389,7 +422,10 @@ namespace Barotrauma
|
||||
|
||||
if (OnlyHumans)
|
||||
{
|
||||
if (targetLimb.character != null && targetLimb.character.ConfigPath != Character.HumanConfigFile) return new AttackResult();
|
||||
if (targetLimb.character != null && !targetLimb.character.IsHuman)
|
||||
{
|
||||
return new AttackResult();
|
||||
}
|
||||
}
|
||||
|
||||
SetUser(attacker);
|
||||
@@ -418,7 +454,6 @@ namespace Barotrauma
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targetLimb.character.AnimController.Limbs.Cast<ISerializableEntity>().ToList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return attackResult;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -86,8 +86,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, XDocument> cachedConfigs = new Dictionary<string, XDocument>();
|
||||
|
||||
private static ushort idCounter;
|
||||
|
||||
public string Name;
|
||||
@@ -128,14 +126,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public string SpeciesName => SourceElement.GetAttributeString("name", string.Empty);
|
||||
private string _speciesName;
|
||||
public string SpeciesName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_speciesName == null)
|
||||
{
|
||||
_speciesName = CharacterConfigElement.GetAttributeString("speciesname", string.Empty).ToLowerInvariant();
|
||||
}
|
||||
return _speciesName;
|
||||
}
|
||||
set { _speciesName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: Can be null.
|
||||
/// </summary>
|
||||
public Character Character;
|
||||
|
||||
public readonly string File;
|
||||
|
||||
public Job Job;
|
||||
|
||||
@@ -190,7 +198,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (portraitBackground == null)
|
||||
{
|
||||
var portraitBackgroundElement = SourceElement.Element("portraitbackground");
|
||||
var portraitBackgroundElement = CharacterConfigElement.Element("portraitbackground");
|
||||
if (portraitBackgroundElement != null)
|
||||
{
|
||||
portraitBackground = new Sprite(portraitBackgroundElement.Element("sprite"));
|
||||
@@ -229,7 +237,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public XElement SourceElement { get; set; }
|
||||
public XElement CharacterConfigElement { get; set; }
|
||||
|
||||
public readonly string ragdollFileName = string.Empty;
|
||||
|
||||
@@ -329,7 +337,7 @@ namespace Barotrauma
|
||||
if (ragdoll == null)
|
||||
{
|
||||
string speciesName = SpeciesName;
|
||||
bool isHumanoid = SourceElement.GetAttributeBool("humanoid", false);
|
||||
bool isHumanoid = CharacterConfigElement.GetAttributeBool("humanoid", speciesName.Equals(Character.HumanSpeciesName, StringComparison.OrdinalIgnoreCase));
|
||||
ragdoll = isHumanoid
|
||||
? HumanRagdollParams.GetRagdollParams(speciesName, ragdollFileName)
|
||||
: RagdollParams.GetRagdollParams<FishRagdollParams>(speciesName, ragdollFileName) as RagdollParams;
|
||||
@@ -342,16 +350,21 @@ namespace Barotrauma
|
||||
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
|
||||
|
||||
// Used for creating the data
|
||||
public CharacterInfo(string file, string name = "", JobPrefab jobPrefab = null, string ragdollFileName = null)
|
||||
public CharacterInfo(string speciesName, string name = "", JobPrefab jobPrefab = null, string ragdollFileName = null)
|
||||
{
|
||||
if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
speciesName = Path.GetFileNameWithoutExtension(speciesName).ToLowerInvariant();
|
||||
}
|
||||
ID = idCounter;
|
||||
idCounter++;
|
||||
File = file;
|
||||
_speciesName = speciesName;
|
||||
SpriteTags = new List<string>();
|
||||
XDocument doc = GetConfig(file);
|
||||
SourceElement = doc.Root;
|
||||
XDocument doc = Character.GetConfigFile(_speciesName);
|
||||
if (doc == null) { return; }
|
||||
CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
|
||||
head = new HeadInfo();
|
||||
HasGenders = doc.Root.GetAttributeBool("genders", false);
|
||||
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
|
||||
if (HasGenders)
|
||||
{
|
||||
Head.gender = GetRandomGender();
|
||||
@@ -367,16 +380,16 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
name = "";
|
||||
if (doc.Root.Element("name") != null)
|
||||
if (CharacterConfigElement.Element("name") != null)
|
||||
{
|
||||
string firstNamePath = doc.Root.Element("name").GetAttributeString("firstname", "");
|
||||
string firstNamePath = CharacterConfigElement.Element("name").GetAttributeString("firstname", "");
|
||||
if (firstNamePath != "")
|
||||
{
|
||||
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
|
||||
Name = ToolBox.GetRandomLine(firstNamePath);
|
||||
}
|
||||
|
||||
string lastNamePath = doc.Root.Element("name").GetAttributeString("lastname", "");
|
||||
string lastNamePath = CharacterConfigElement.Element("name").GetAttributeString("lastname", "");
|
||||
if (lastNamePath != "")
|
||||
{
|
||||
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
|
||||
@@ -395,18 +408,30 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// Used for loading the data
|
||||
public CharacterInfo(XElement element)
|
||||
public CharacterInfo(XElement infoElement)
|
||||
{
|
||||
ID = idCounter;
|
||||
idCounter++;
|
||||
Name = element.GetAttributeString("name", "");
|
||||
string genderStr = element.GetAttributeString("gender", "male").ToLowerInvariant();
|
||||
File = element.GetAttributeString("file", "");
|
||||
SourceElement = GetConfig(File).Root;
|
||||
HasGenders = SourceElement.GetAttributeBool("genders", false);
|
||||
Salary = element.GetAttributeInt("salary", 1000);
|
||||
Enum.TryParse(element.GetAttributeString("race", "White"), true, out Race race);
|
||||
Enum.TryParse(element.GetAttributeString("gender", "None"), true, out Gender gender);
|
||||
Name = infoElement.GetAttributeString("name", "");
|
||||
string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant();
|
||||
Salary = infoElement.GetAttributeInt("salary", 1000);
|
||||
Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race);
|
||||
Enum.TryParse(infoElement.GetAttributeString("gender", "None"), true, out Gender gender);
|
||||
_speciesName = infoElement.GetAttributeString("speciesname", null);
|
||||
XDocument doc = null;
|
||||
if (_speciesName != null)
|
||||
{
|
||||
doc = Character.GetConfigFile(_speciesName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Backwards support (human only)
|
||||
string file = infoElement.GetAttributeString("file", "");
|
||||
doc = XMLExtensions.TryLoadXml(file);
|
||||
}
|
||||
if (doc == null) { return; }
|
||||
CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
|
||||
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
|
||||
if (HasGenders && gender == Gender.None)
|
||||
{
|
||||
gender = GetRandomGender();
|
||||
@@ -416,26 +441,26 @@ namespace Barotrauma
|
||||
gender = Gender.None;
|
||||
}
|
||||
RecreateHead(
|
||||
element.GetAttributeInt("headspriteid", 1),
|
||||
infoElement.GetAttributeInt("headspriteid", 1),
|
||||
race,
|
||||
gender,
|
||||
element.GetAttributeInt("hairindex", -1),
|
||||
element.GetAttributeInt("beardindex", -1),
|
||||
element.GetAttributeInt("moustacheindex", -1),
|
||||
element.GetAttributeInt("faceattachmentindex", -1));
|
||||
infoElement.GetAttributeInt("hairindex", -1),
|
||||
infoElement.GetAttributeInt("beardindex", -1),
|
||||
infoElement.GetAttributeInt("moustacheindex", -1),
|
||||
infoElement.GetAttributeInt("faceattachmentindex", -1));
|
||||
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
{
|
||||
if (SourceElement.Element("name") != null)
|
||||
if (CharacterConfigElement.Element("name") != null)
|
||||
{
|
||||
string firstNamePath = SourceElement.Element("name").GetAttributeString("firstname", "");
|
||||
string firstNamePath = CharacterConfigElement.Element("name").GetAttributeString("firstname", "");
|
||||
if (firstNamePath != "")
|
||||
{
|
||||
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
|
||||
Name = ToolBox.GetRandomLine(firstNamePath);
|
||||
}
|
||||
|
||||
string lastNamePath = SourceElement.Element("name").GetAttributeString("lastname", "");
|
||||
string lastNamePath = CharacterConfigElement.Element("name").GetAttributeString("lastname", "");
|
||||
if (lastNamePath != "")
|
||||
{
|
||||
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
|
||||
@@ -445,15 +470,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StartItemsGiven = element.GetAttributeBool("startitemsgiven", false);
|
||||
string personalityName = element.GetAttributeString("personality", "");
|
||||
ragdollFileName = element.GetAttributeString("ragdoll", string.Empty);
|
||||
StartItemsGiven = infoElement.GetAttributeBool("startitemsgiven", false);
|
||||
string personalityName = infoElement.GetAttributeString("personality", "");
|
||||
ragdollFileName = infoElement.GetAttributeString("ragdoll", string.Empty);
|
||||
if (!string.IsNullOrEmpty(personalityName))
|
||||
{
|
||||
personalityTrait = NPCPersonalityTrait.List.Find(p => p.Name == personalityName);
|
||||
}
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (XElement subElement in infoElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "job") continue;
|
||||
Job = new Job(subElement);
|
||||
@@ -462,20 +486,9 @@ namespace Barotrauma
|
||||
LoadHeadAttachments();
|
||||
}
|
||||
|
||||
private XDocument GetConfig(string file)
|
||||
{
|
||||
if (!cachedConfigs.TryGetValue(file, out XDocument doc))
|
||||
{
|
||||
doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc == null) { return null; }
|
||||
cachedConfigs.Add(file, doc);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
public int SetRandomHead() => HeadSpriteId = GetRandomHeadID();
|
||||
|
||||
public Gender GetRandomGender() => (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < SourceElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male;
|
||||
public Gender GetRandomGender() => (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < CharacterConfigElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male;
|
||||
public Race GetRandomRace() => new Race[] { Race.White, Race.Black, Race.Asian }.GetRandom(Rand.RandSync.Server);
|
||||
public int GetRandomHeadID() => Head.headSpriteRange != Vector2.Zero ? Rand.Range((int)Head.headSpriteRange.X, (int)Head.headSpriteRange.Y + 1, Rand.RandSync.Server) : 0;
|
||||
|
||||
@@ -491,7 +504,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (wearables == null)
|
||||
{
|
||||
var attachments = SourceElement.Element("HeadAttachments");
|
||||
var attachments = CharacterConfigElement.Element("HeadAttachments");
|
||||
if (attachments != null)
|
||||
{
|
||||
wearables = attachments.Elements("Wearable");
|
||||
@@ -503,6 +516,7 @@ namespace Barotrauma
|
||||
|
||||
public IEnumerable<XElement> FilterByTypeAndHeadID(IEnumerable<XElement> elements, WearableType targetType)
|
||||
{
|
||||
if (elements == null) { return elements; }
|
||||
return elements.Where(e =>
|
||||
{
|
||||
if (Enum.TryParse(e.GetAttributeString("type", ""), true, out WearableType type) && type != targetType) { return false; }
|
||||
@@ -522,8 +536,8 @@ namespace Barotrauma
|
||||
|
||||
private void CalculateHeadSpriteRange()
|
||||
{
|
||||
if (SourceElement == null) { return; }
|
||||
Head.headSpriteRange = SourceElement.GetAttributeVector2("headidrange", Vector2.Zero);
|
||||
if (CharacterConfigElement == null) { return; }
|
||||
Head.headSpriteRange = CharacterConfigElement.GetAttributeVector2("headidrange", Vector2.Zero);
|
||||
// If range is defined, we use it as it is
|
||||
// Else we calculate the range from the wearables.
|
||||
if (Head.headSpriteRange == Vector2.Zero)
|
||||
@@ -582,11 +596,13 @@ namespace Barotrauma
|
||||
|
||||
public void LoadHeadSprite()
|
||||
{
|
||||
// TODO: use ragdollparams instead?
|
||||
foreach (XElement limbElement in Ragdoll.MainElement.Elements())
|
||||
{
|
||||
if (limbElement.GetAttributeString("type", "").ToLowerInvariant() != "head") continue;
|
||||
if (limbElement.GetAttributeString("type", "").ToLowerInvariant() != "head") { continue; }
|
||||
|
||||
XElement spriteElement = limbElement.Element("sprite");
|
||||
if (spriteElement == null) { continue; }
|
||||
|
||||
string spritePath = spriteElement.Attribute("texture").Value;
|
||||
|
||||
@@ -605,7 +621,7 @@ namespace Barotrauma
|
||||
}
|
||||
string fileWithoutTags = Path.GetFileNameWithoutExtension(file);
|
||||
fileWithoutTags = fileWithoutTags.Split('[', ']').First();
|
||||
if (fileWithoutTags != fileName) continue;
|
||||
if (fileWithoutTags != fileName) { continue; }
|
||||
|
||||
HeadSprite = new Sprite(spriteElement, "", file);
|
||||
Portrait = new Sprite(spriteElement, "", file) { RelativeOrigin = Vector2.Zero };
|
||||
@@ -788,7 +804,7 @@ namespace Barotrauma
|
||||
|
||||
charElement.Add(
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("file", File),
|
||||
new XAttribute("speciesname", SpeciesName),
|
||||
new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"),
|
||||
new XAttribute("race", Head.race.ToString()),
|
||||
new XAttribute("salary", Salary),
|
||||
|
||||
@@ -2,14 +2,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Affliction
|
||||
class Affliction : ISerializableEntity
|
||||
{
|
||||
public readonly AfflictionPrefab Prefab;
|
||||
|
||||
public float Strength;
|
||||
|
||||
public string Name => ToString();
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable]
|
||||
public float Strength { get; set; }
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string Identifier { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "The probability for the affliction to be applied."), Editable(minValue: 0f, maxValue: 1f)]
|
||||
public float Probability { get; private set; } = 1.0f;
|
||||
|
||||
public float DamagePerSecond;
|
||||
public float DamagePerSecondTimer;
|
||||
@@ -18,11 +30,6 @@ namespace Barotrauma
|
||||
public float StrengthDiminishMultiplier = 1.0f;
|
||||
public Affliction MultiplierSource;
|
||||
|
||||
/// <summary>
|
||||
/// Probability for the affliction to be applied. Used by attacks.
|
||||
/// </summary>
|
||||
public float ApplyProbability = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Which character gave this affliction
|
||||
/// </summary>
|
||||
@@ -32,6 +39,17 @@ namespace Barotrauma
|
||||
{
|
||||
Prefab = prefab;
|
||||
Strength = strength;
|
||||
Identifier = prefab?.Identifier;
|
||||
}
|
||||
|
||||
public void Serialize(XElement element)
|
||||
{
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
}
|
||||
|
||||
public void Deserialize(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public Affliction CreateMultiplied(float multiplier)
|
||||
@@ -39,10 +57,7 @@ namespace Barotrauma
|
||||
return Prefab.Instantiate(Strength * multiplier, Source);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Affliction (" + Prefab.Name + ")";
|
||||
}
|
||||
public override string ToString() => Prefab == null ? "Affliction (Invalid)" : $"Affliction ({Prefab.Name})";
|
||||
|
||||
public float GetVitalityDecrease(CharacterHealth characterHealth)
|
||||
{
|
||||
|
||||
+114
-57
@@ -22,7 +22,7 @@ namespace Barotrauma
|
||||
{
|
||||
get { return state; }
|
||||
}
|
||||
|
||||
|
||||
public AfflictionHusk(AfflictionPrefab prefab, float strength) :
|
||||
base(prefab, strength)
|
||||
{
|
||||
@@ -96,60 +96,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateHusk(Character character)
|
||||
public void ActivateHusk(Character character)
|
||||
{
|
||||
character.NeedsAir = false;
|
||||
if (huskAppendage == null)
|
||||
{
|
||||
huskAppendage = AttachHuskAppendage(character);
|
||||
character.SetStun(0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Limb> AttachHuskAppendage(Character character, Ragdoll ragdoll = null)
|
||||
{
|
||||
var huskDoc = XMLExtensions.TryLoadXml(Character.GetConfigFile("humanhusk"));
|
||||
string pathToAppendage = huskDoc.Root.Element("huskappendage").GetAttributeString("path", string.Empty);
|
||||
XDocument doc = XMLExtensions.TryLoadXml(pathToAppendage);
|
||||
if (doc == null || doc.Root == null) { return null; }
|
||||
if (ragdoll == null)
|
||||
{
|
||||
ragdoll = character.AnimController;
|
||||
}
|
||||
if (ragdoll.Dir < 1.0f)
|
||||
{
|
||||
ragdoll.Flip();
|
||||
}
|
||||
var huskAppendages = new List<Limb>();
|
||||
var limbElements = doc.Root.Elements("limb").ToDictionary(e => e.GetAttributeString("id", null), e => e);
|
||||
foreach (var jointElement in doc.Root.Elements("joint"))
|
||||
{
|
||||
if (limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out XElement limbElement))
|
||||
huskAppendage = AttachHuskAppendage(character, Prefab.Identifier);
|
||||
if (huskAppendage != null)
|
||||
{
|
||||
JointParams jointParams = new JointParams(jointElement, ragdoll.RagdollParams);
|
||||
Limb attachLimb = ragdoll.Limbs[jointParams.Limb1];
|
||||
Limb huskAppendage = new Limb(ragdoll, character, new LimbParams(limbElement, ragdoll.RagdollParams));
|
||||
huskAppendage.body.Submarine = character.Submarine;
|
||||
huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation);
|
||||
ragdoll.AddLimb(huskAppendage);
|
||||
ragdoll.AddJoint(jointParams);
|
||||
huskAppendages.Add(huskAppendage);
|
||||
character.NeedsAir = false;
|
||||
character.SetStun(0.5f);
|
||||
}
|
||||
}
|
||||
return huskAppendages;
|
||||
}
|
||||
|
||||
private void DeactivateHusk(Character character)
|
||||
{
|
||||
character.NeedsAir = true;
|
||||
RemoveHuskAppendage(character);
|
||||
}
|
||||
|
||||
private void RemoveHuskAppendage(Character character)
|
||||
{
|
||||
if (huskAppendage == null) return;
|
||||
huskAppendage.ForEach(l => character.AnimController.RemoveLimb(l));
|
||||
huskAppendage = null;
|
||||
character.NeedsAir = character.Params.MainElement.GetAttributeBool("needsair", false);
|
||||
if (huskAppendage != null)
|
||||
{
|
||||
huskAppendage.ForEach(l => character.AnimController.RemoveLimb(l));
|
||||
huskAppendage = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Character character)
|
||||
@@ -182,7 +149,8 @@ namespace Barotrauma
|
||||
character.Enabled = false;
|
||||
Entity.Spawner.AddToRemoveQueue(character);
|
||||
|
||||
var configFile = Character.GetConfigFile("humanhusk");
|
||||
string speciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
|
||||
string configFile = Character.GetConfigFilePath(speciesName);
|
||||
|
||||
if (string.IsNullOrEmpty(configFile))
|
||||
{
|
||||
@@ -190,16 +158,7 @@ namespace Barotrauma
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
//XDocument doc = XMLExtensions.TryLoadXml(configFile);
|
||||
//if (doc?.Root == null)
|
||||
//{
|
||||
// DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file ("+configFile+") could not be read.");
|
||||
// yield return CoroutineStatus.Success;
|
||||
//}
|
||||
|
||||
//character.Info.Ragdoll = null;
|
||||
//character.Info.SourceElement = doc.Root;
|
||||
var husk = Character.Create(configFile, character.WorldPosition, character.Info.Name, character.Info, isRemotePlayer: false, hasAi: true);
|
||||
var husk = Character.Create(configFile, character.WorldPosition, character.Info.Name, character.Info, isRemotePlayer: false, hasAi: true, ragdoll: character.AnimController.RagdollParams);
|
||||
|
||||
foreach (Limb limb in husk.AnimController.Limbs)
|
||||
{
|
||||
@@ -220,7 +179,7 @@ namespace Barotrauma
|
||||
|
||||
if (character.Inventory.Items.Length != husk.Inventory.Items.Length)
|
||||
{
|
||||
string errorMsg = "Failed to move items from a human's inventory into a humanhusk's inventory (inventory sizes don't match)";
|
||||
string errorMsg = "Failed to move items from the source character's inventory into a husk's inventory (inventory sizes don't match)";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("AfflictionHusk.CreateAIHusk:InventoryMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
yield return CoroutineStatus.Success;
|
||||
@@ -234,5 +193,103 @@ namespace Barotrauma
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
public static List<Limb> AttachHuskAppendage(Character character, string afflictionIdentifier, XElement appendageDefinition = null, Ragdoll ragdoll = null)
|
||||
{
|
||||
var appendage = new List<Limb>();
|
||||
if (!(AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier) is AfflictionPrefabHusk matchingAffliction))
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!");
|
||||
return appendage;
|
||||
}
|
||||
string nonhuskedSpeciesName = GetNonHuskedSpeciesName(character.SpeciesName, matchingAffliction);
|
||||
string huskedSpeciesName = GetHuskedSpeciesName(nonhuskedSpeciesName, matchingAffliction);
|
||||
string filePath = Character.GetConfigFilePath(huskedSpeciesName);
|
||||
if (!Character.TryGetConfigFile(filePath, out XDocument huskDoc))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{filePath}': Failed to load the config file for the husk infected species with the species name '{huskedSpeciesName}'!");
|
||||
return appendage;
|
||||
}
|
||||
var mainElement = huskDoc.Root.IsOverride() ? huskDoc.Root.FirstElement() : huskDoc.Root;
|
||||
var element = appendageDefinition;
|
||||
if (element == null)
|
||||
{
|
||||
element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeString("affliction", string.Empty).Equals(afflictionIdentifier));
|
||||
}
|
||||
if (element == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{filePath}': Failed to find a huskappendage that matches the affliction with an identifier '{afflictionIdentifier}'!");
|
||||
return appendage;
|
||||
}
|
||||
string pathToAppendage = element.GetAttributeString("path", string.Empty);
|
||||
XDocument doc = XMLExtensions.TryLoadXml(pathToAppendage);
|
||||
if (doc == null) { return appendage; }
|
||||
if (ragdoll == null)
|
||||
{
|
||||
ragdoll = character.AnimController;
|
||||
}
|
||||
if (ragdoll.Dir < 1.0f)
|
||||
{
|
||||
ragdoll.Flip();
|
||||
}
|
||||
var limbElements = doc.Root.Elements("limb").ToDictionary(e => e.GetAttributeString("id", null), e => e);
|
||||
foreach (var jointElement in doc.Root.Elements("joint"))
|
||||
{
|
||||
if (limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out XElement limbElement))
|
||||
{
|
||||
var jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams);
|
||||
Limb attachLimb = null;
|
||||
if (matchingAffliction.AttachLimbId > -1)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.Params.ID == matchingAffliction.AttachLimbId);
|
||||
}
|
||||
else if (matchingAffliction.AttachLimbName != null)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.Name == matchingAffliction.AttachLimbName);
|
||||
}
|
||||
else if (matchingAffliction.AttachLimbType != LimbType.None)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.type == matchingAffliction.AttachLimbType);
|
||||
}
|
||||
if (attachLimb == null)
|
||||
{
|
||||
DebugConsole.Log("Attachment limb not defined in the affliction prefab or no matching limb could be found. Using the appendage definition as it is.");
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.Params.ID == jointParams.Limb1);
|
||||
}
|
||||
if (attachLimb != null)
|
||||
{
|
||||
jointParams.Limb1 = attachLimb.Params.ID;
|
||||
var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams)
|
||||
{
|
||||
// Ensure that we have a valid id for the new limb
|
||||
ID = ragdoll.Limbs.Length
|
||||
};
|
||||
jointParams.Limb2 = appendageLimbParams.ID;
|
||||
Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams);
|
||||
huskAppendage.body.Submarine = character.Submarine;
|
||||
huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation);
|
||||
ragdoll.AddLimb(huskAppendage);
|
||||
ragdoll.AddJoint(jointParams);
|
||||
appendage.Add(huskAppendage);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Attachment limb not found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return appendage;
|
||||
}
|
||||
|
||||
public static string GetHuskedSpeciesName(string speciesName, AfflictionPrefabHusk prefab)
|
||||
{
|
||||
return prefab.HuskedSpeciesName.Replace(AfflictionPrefabHusk.Tag, speciesName);
|
||||
}
|
||||
|
||||
public static string GetNonHuskedSpeciesName(string huskedSpeciesName, AfflictionPrefabHusk prefab)
|
||||
{
|
||||
string nonTag = prefab.HuskedSpeciesName.Remove(AfflictionPrefabHusk.Tag);
|
||||
return huskedSpeciesName.Remove(nonTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+143
-31
@@ -3,11 +3,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class CPRSettings
|
||||
{
|
||||
public static bool IsLoaded { get; private set; }
|
||||
public static float ReviveChancePerSkill { get; private set; }
|
||||
public static float ReviveChanceExponent { get; private set; }
|
||||
public static float ReviveChanceMin { get; private set; }
|
||||
@@ -31,9 +33,52 @@ namespace Barotrauma
|
||||
|
||||
DamageSkillThreshold = MathHelper.Clamp(element.GetAttributeFloat("damageskillthreshold", 40.0f), 0.0f, 100.0f);
|
||||
DamageSkillMultiplier = MathHelper.Clamp(element.GetAttributeFloat("damageskillmultiplier", 0.1f), 0.0f, 100.0f);
|
||||
IsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
class AfflictionPrefabHusk : AfflictionPrefab
|
||||
{
|
||||
public AfflictionPrefabHusk(XElement element, Type type = null) : base(element, type)
|
||||
{
|
||||
HuskedSpeciesName = element.GetAttributeString("huskedspeciesname", null);
|
||||
if (HuskedSpeciesName == null)
|
||||
{
|
||||
DebugConsole.NewMessage($"No 'huskedspeciesname' defined for the husk affliction ({Identifier}) in {element.ToString()}", Color.Orange);
|
||||
HuskedSpeciesName = "[speciesname]husk";
|
||||
}
|
||||
TargetSpecies = element.GetAttributeStringArray("targets", new string[0] { }, trim: true, convertToLowerInvariant: true);
|
||||
if (TargetSpecies.Length == 0)
|
||||
{
|
||||
DebugConsole.NewMessage($"No 'targets' defined for the husk affliction ({Identifier}) in {element.ToString()}", Color.Orange);
|
||||
TargetSpecies = new string[] { "human" };
|
||||
}
|
||||
var attachElement = element.GetChildElement("attachlimb");
|
||||
if (attachElement != null)
|
||||
{
|
||||
AttachLimbId = attachElement.GetAttributeInt("id", -1);
|
||||
AttachLimbName = attachElement.GetAttributeString("name", null);
|
||||
AttachLimbType = Enum.TryParse(attachElement.GetAttributeString("type", "none"), true, out LimbType limbType) ? limbType : LimbType.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
AttachLimbId = -1;
|
||||
AttachLimbName = null;
|
||||
AttachLimbType = LimbType.None;
|
||||
}
|
||||
}
|
||||
|
||||
// Use any of these to define which limb the appendage is attached to.
|
||||
// If multiple are defined, the order of preference is: id, name, type.
|
||||
public readonly int AttachLimbId;
|
||||
public readonly string AttachLimbName;
|
||||
public readonly LimbType AttachLimbType;
|
||||
|
||||
public readonly string HuskedSpeciesName;
|
||||
public readonly string[] TargetSpecies;
|
||||
public const string Tag = "[speciesname]";
|
||||
}
|
||||
|
||||
class AfflictionPrefab
|
||||
{
|
||||
public class Effect
|
||||
@@ -126,7 +171,6 @@ namespace Barotrauma
|
||||
public static AfflictionPrefab Bloodloss;
|
||||
public static AfflictionPrefab Pressure;
|
||||
public static AfflictionPrefab Stun;
|
||||
public static AfflictionPrefab Husk;
|
||||
|
||||
public static List<AfflictionPrefab> List = new List<AfflictionPrefab>();
|
||||
|
||||
@@ -187,44 +231,109 @@ namespace Barotrauma
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null || doc.Root == null) continue;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
switch (element.Name.ToString().ToLowerInvariant())
|
||||
DebugConsole.ThrowError("Cannot override all afflictions, because many of them are required by the main game! Please try overriding them one by one.");
|
||||
}
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
bool isOverride = element.IsOverride();
|
||||
XElement sourceElement = isOverride ? element.FirstElement() : element;
|
||||
string elementName = sourceElement.Name.ToString().ToLowerInvariant();
|
||||
string identifier = sourceElement.GetAttributeString("identifier", null);
|
||||
if (!elementName.Equals("cprsettings", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"No identifier defined for the affliction '{elementName}' in file '{filePath}'");
|
||||
continue;
|
||||
}
|
||||
var duplicate = List.FirstOrDefault(a => a.Identifier == identifier);
|
||||
if (duplicate != null)
|
||||
{
|
||||
if (isOverride)
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding an affliction or a buff with the identifier '{identifier}' using the file '{filePath}'", Color.Yellow);
|
||||
List.Remove(duplicate);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate affliction: '{identifier}' defined in {elementName} of '{filePath}'");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
string type = sourceElement.GetAttributeString("type", null);
|
||||
if (sourceElement.Name.ToString().ToLowerInvariant() == "cprsettings")
|
||||
{
|
||||
//backwards compatibility
|
||||
type = "cprsettings";
|
||||
}
|
||||
|
||||
AfflictionPrefab prefab = null;
|
||||
switch (type)
|
||||
{
|
||||
case "internaldamage":
|
||||
List.Add(InternalDamage = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "bleeding":
|
||||
List.Add(Bleeding = new AfflictionPrefab(element, typeof(AfflictionBleeding)));
|
||||
prefab = new AfflictionPrefab(sourceElement, typeof(AfflictionBleeding));
|
||||
break;
|
||||
case "burn":
|
||||
List.Add(Burn = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "oxygenlow":
|
||||
List.Add(OxygenLow = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "bloodloss":
|
||||
List.Add(Bloodloss = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "pressure":
|
||||
List.Add(Pressure = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "stun":
|
||||
List.Add(Stun = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "husk":
|
||||
case "afflictionhusk":
|
||||
List.Add(Husk = new AfflictionPrefab(element, typeof(AfflictionHusk)));
|
||||
case "huskinfection":
|
||||
prefab = new AfflictionPrefabHusk(sourceElement, typeof(AfflictionHusk));
|
||||
break;
|
||||
case "cprsettings":
|
||||
CPRSettings.Load(element);
|
||||
if (CPRSettings.IsLoaded)
|
||||
{
|
||||
if (isOverride)
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the CPR settings with '{filePath}'", Color.Yellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{filePath}': CPR settings already loaded. Add <override></override> tags as the parent of the custom CPRSettings to allow overriding the vanilla values.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
CPRSettings.Load(sourceElement);
|
||||
break;
|
||||
case "damage":
|
||||
case "burn":
|
||||
case "oxygenlow":
|
||||
case "bloodloss":
|
||||
case "stun":
|
||||
case "pressure":
|
||||
case "internaldamage":
|
||||
prefab = new AfflictionPrefab(sourceElement, typeof(Affliction));
|
||||
break;
|
||||
default:
|
||||
List.Add(new AfflictionPrefab(element));
|
||||
prefab = new AfflictionPrefab(sourceElement);
|
||||
break;
|
||||
}
|
||||
switch (identifier)
|
||||
{
|
||||
case "internaldamage":
|
||||
InternalDamage = prefab;
|
||||
break;
|
||||
case "bleeding":
|
||||
Bleeding = prefab;
|
||||
break;
|
||||
case "burn":
|
||||
Burn = prefab;
|
||||
break;
|
||||
case "oxygenlow":
|
||||
OxygenLow = prefab;
|
||||
break;
|
||||
case "bloodloss":
|
||||
Bloodloss = prefab;
|
||||
break;
|
||||
case "pressure":
|
||||
Pressure = prefab;
|
||||
break;
|
||||
case "stun":
|
||||
Stun = prefab;
|
||||
break;
|
||||
}
|
||||
if (prefab != null) { List.Add(prefab); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,12 +344,15 @@ namespace Barotrauma
|
||||
if (Bloodloss == null) DebugConsole.ThrowError("Affliction \"Bloodloss\" not defined in the affliction prefabs.");
|
||||
if (Pressure == null) DebugConsole.ThrowError("Affliction \"Pressure\" not defined in the affliction prefabs.");
|
||||
if (Stun == null) DebugConsole.ThrowError("Affliction \"Stun\" not defined in the affliction prefabs.");
|
||||
if (Husk == null) DebugConsole.ThrowError("Affliction \"Husk\" not defined in the affliction prefabs.");
|
||||
}
|
||||
|
||||
public AfflictionPrefab(XElement element, Type type = null)
|
||||
{
|
||||
typeName = type == null ? element.Name.ToString() : type.Name;
|
||||
if (typeName == "InternalDamage" && type == null)
|
||||
{
|
||||
type = typeof(Affliction);
|
||||
}
|
||||
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
|
||||
@@ -305,7 +417,7 @@ namespace Barotrauma
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find an affliction class of the type \"" + typeName + "\".");
|
||||
return;
|
||||
type = typeof(Affliction);
|
||||
}
|
||||
|
||||
constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -78,14 +79,33 @@ namespace Barotrauma
|
||||
|
||||
public const float InsufficientOxygenThreshold = 30.0f;
|
||||
public const float LowOxygenThreshold = 50.0f;
|
||||
protected float minVitality, maxVitality;
|
||||
protected float minVitality;
|
||||
|
||||
protected float maxVitality
|
||||
{
|
||||
get => Character.Params.Health.Vitality;
|
||||
set => Character.Params.Health.Vitality = value;
|
||||
}
|
||||
|
||||
public bool Unkillable;
|
||||
|
||||
//bleeding settings
|
||||
public bool DoesBleed { get; private set; }
|
||||
public bool DoesBleed
|
||||
{
|
||||
get => Character.Params.Health.DoesBleed;
|
||||
private set => Character.Params.Health.DoesBleed = value;
|
||||
}
|
||||
|
||||
public bool UseHealthWindow { get; set; }
|
||||
public bool UseHealthWindow
|
||||
{
|
||||
get => Character.Params.Health.UseHealthWindow;
|
||||
set => Character.Params.Health.UseHealthWindow = value;
|
||||
}
|
||||
|
||||
public float CrushDepth
|
||||
{
|
||||
get => Character.Params.Health.CrushDepth;
|
||||
private set => Character.Params.Health.CrushDepth = value;
|
||||
}
|
||||
|
||||
private List<LimbHealth> limbHealths = new List<LimbHealth>();
|
||||
//non-limb-specific afflictions
|
||||
@@ -102,11 +122,12 @@ namespace Barotrauma
|
||||
get { return Vitality <= 0.0f; }
|
||||
}
|
||||
|
||||
public float CrushDepth { get; private set; }
|
||||
public float PressureKillDelay { get; private set; } = 5.0f;
|
||||
|
||||
public float Vitality { get; private set; }
|
||||
|
||||
public float HealthPercentage => MathUtils.Percentage(Vitality, MaxVitality);
|
||||
|
||||
public float MaxVitality
|
||||
{
|
||||
get
|
||||
@@ -168,7 +189,6 @@ namespace Barotrauma
|
||||
{
|
||||
this.Character = character;
|
||||
Vitality = 100.0f;
|
||||
maxVitality = 100.0f;
|
||||
|
||||
DoesBleed = true;
|
||||
UseHealthWindow = false;
|
||||
@@ -185,15 +205,9 @@ namespace Barotrauma
|
||||
this.Character = character;
|
||||
InitIrremovableAfflictions();
|
||||
|
||||
CrushDepth = element.GetAttributeFloat("crushdepth", float.NegativeInfinity);
|
||||
|
||||
maxVitality = element.GetAttributeFloat("vitality", 100.0f);
|
||||
Vitality = maxVitality;
|
||||
|
||||
DoesBleed = element.GetAttributeBool("doesbleed", true);
|
||||
UseHealthWindow = element.GetAttributeBool("usehealthwindow", false);
|
||||
|
||||
minVitality = (character.ConfigPath == Character.HumanConfigFile) ? -100.0f : 0.0f;
|
||||
minVitality = character.IsHuman ? -100.0f : 0.0f;
|
||||
|
||||
limbHealths.Clear();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
@@ -225,10 +239,9 @@ namespace Barotrauma
|
||||
|
||||
public IEnumerable<Affliction> GetAllAfflictions(Func<Affliction, bool> limbHealthFilter = null)
|
||||
{
|
||||
// TODO: If there can be duplicates, we should use Union instead.
|
||||
return limbHealthFilter == null
|
||||
? afflictions.Concat(limbHealths.SelectMany(lh => lh.Afflictions))
|
||||
: afflictions.Concat(limbHealths.SelectMany(lh => lh.Afflictions.Where(limbHealthFilter)));
|
||||
? afflictions.Union(limbHealths.SelectMany(lh => lh.Afflictions))
|
||||
: afflictions.Where(limbHealthFilter).Union(limbHealths.SelectMany(lh => lh.Afflictions.Where(limbHealthFilter)));
|
||||
}
|
||||
|
||||
private LimbHealth GetMatchingLimbHealth(Limb limb) => limbHealths[limb.HealthIndex];
|
||||
@@ -240,11 +253,23 @@ namespace Barotrauma
|
||||
private IEnumerable<Affliction> GetMatchingAfflictions(LimbHealth limb, Func<Affliction, bool> predicate)
|
||||
=> limb.Afflictions.Where(predicate).Union(afflictions.Where(a => predicate(a) && GetMatchingLimbHealth(a) == limb));
|
||||
|
||||
public Affliction GetAffliction(string afflictionType, bool allowLimbAfflictions = true)
|
||||
public IEnumerable<Affliction> GetAfflictionsByType(string afflictionType, bool allowLimbAfflictions = true)
|
||||
{
|
||||
if (allowLimbAfflictions)
|
||||
{
|
||||
return GetAllAfflictions(a => a.Prefab.AfflictionType == afflictionType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return afflictions.Where(a => a.Prefab.AfflictionType == afflictionType);
|
||||
}
|
||||
}
|
||||
|
||||
public Affliction GetAffliction(string identifier, bool allowLimbAfflictions = true)
|
||||
{
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (affliction.Prefab.AfflictionType == afflictionType) return affliction;
|
||||
if (affliction.Prefab.Identifier == identifier) return affliction;
|
||||
}
|
||||
if (!allowLimbAfflictions) return null;
|
||||
|
||||
@@ -252,19 +277,30 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Affliction affliction in limbHealth.Afflictions)
|
||||
{
|
||||
if (affliction.Prefab.AfflictionType == afflictionType) return affliction;
|
||||
if (affliction.Prefab.Identifier == identifier) return affliction;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public T GetAffliction<T>(string afflictionType, bool allowLimbAfflictions = true) where T : Affliction
|
||||
public T GetAffliction<T>(string identifier, bool allowLimbAfflictions = true) where T : Affliction
|
||||
{
|
||||
return GetAffliction(afflictionType, allowLimbAfflictions) as T;
|
||||
return GetAffliction(identifier, allowLimbAfflictions) as T;
|
||||
}
|
||||
|
||||
public Affliction GetAffliction(string afflictionType, Limb limb)
|
||||
public IEnumerable<Affliction> GetAfflictionsByType(string afflictionType, Limb limb)
|
||||
{
|
||||
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
|
||||
{
|
||||
DebugConsole.ThrowError("Limb health index out of bounds. Character\"" + Character.Name +
|
||||
"\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex);
|
||||
return null;
|
||||
}
|
||||
return limbHealths[limb.HealthIndex].Afflictions.Where(a => a.Prefab.AfflictionType == afflictionType);
|
||||
}
|
||||
|
||||
public Affliction GetAffliction(string identifier, Limb limb)
|
||||
{
|
||||
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
|
||||
{
|
||||
@@ -274,7 +310,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (Affliction affliction in limbHealths[limb.HealthIndex].Afflictions)
|
||||
{
|
||||
if (affliction.Prefab.AfflictionType == afflictionType) return affliction;
|
||||
if (affliction.Prefab.Identifier == identifier) return affliction;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -520,8 +556,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) return;
|
||||
if (!Character.NeedsAir && newAffliction.Prefab == AfflictionPrefab.OxygenLow) return;
|
||||
// Currently only human can get the husk infection.
|
||||
if (newAffliction.Prefab == AfflictionPrefab.Husk && Character.SpeciesName.ToLowerInvariant() != "human") { return; }
|
||||
if (newAffliction.Prefab.AfflictionType == "huskinfection")
|
||||
{
|
||||
var huskPrefab = newAffliction.Prefab as AfflictionPrefabHusk;
|
||||
if (huskPrefab.TargetSpecies.None(s => s.Equals(Character.SpeciesName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (newAffliction.Prefab == affliction.Prefab)
|
||||
@@ -547,7 +589,10 @@ namespace Barotrauma
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
}
|
||||
|
||||
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
partial void UpdateLimbAfflictionOverlays();
|
||||
|
||||
public void Update(float deltaTime)
|
||||
@@ -671,6 +716,10 @@ namespace Barotrauma
|
||||
|
||||
var causeOfDeath = GetCauseOfDeath();
|
||||
Character.Kill(causeOfDeath.First, causeOfDeath.Second);
|
||||
#if CLIENT
|
||||
DisplayVitalityDelay = 0.0f;
|
||||
DisplayedVitality = Vitality;
|
||||
#endif
|
||||
}
|
||||
|
||||
public Pair<CauseOfDeathType, Affliction> GetCauseOfDeath()
|
||||
|
||||
@@ -1,86 +1,120 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class DamageModifier
|
||||
partial class DamageModifier : ISerializableEntity
|
||||
{
|
||||
[Serialize(1.0f, false)]
|
||||
public string Name => "Damage Modifier";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
[Serialize(1.0f, false), Editable(DecimalCount = 2)]
|
||||
public float DamageMultiplier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("0.0,360", false)]
|
||||
[Serialize("0.0,360", false), Editable]
|
||||
public Vector2 ArmorSector
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, false)]
|
||||
public bool IsArmor
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public Vector2 ArmorSectorInRadians => new Vector2(MathHelper.ToRadians(ArmorSector.X), MathHelper.ToRadians(ArmorSector.Y));
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false), Editable]
|
||||
public bool DeflectProjectiles
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string[] AfflictionIdentifiers
|
||||
[Serialize("", true), Editable]
|
||||
public string AfflictionIdentifiers
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
get
|
||||
{
|
||||
return rawAfflictionIdentifierString;
|
||||
}
|
||||
private set
|
||||
{
|
||||
rawAfflictionIdentifierString = value;
|
||||
ParseAfflictionIdentifiers();
|
||||
}
|
||||
}
|
||||
|
||||
public string[] AfflictionTypes
|
||||
[Serialize("", true), Editable]
|
||||
public string AfflictionTypes
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
get
|
||||
{
|
||||
return rawAfflictionTypeString;
|
||||
}
|
||||
private set
|
||||
{
|
||||
rawAfflictionTypeString = value;
|
||||
ParseAfflictionTypes();
|
||||
}
|
||||
}
|
||||
|
||||
private string rawAfflictionIdentifierString;
|
||||
private string rawAfflictionTypeString;
|
||||
private string[] parsedAfflictionIdentifiers;
|
||||
private string[] parsedAfflictionTypes;
|
||||
|
||||
public DamageModifier(XElement element, string parentDebugName)
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
ArmorSector = new Vector2(MathHelper.ToRadians(ArmorSector.X), MathHelper.ToRadians(ArmorSector.Y));
|
||||
|
||||
Deserialize(element);
|
||||
if (element.Attribute("afflictionnames") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.");
|
||||
}
|
||||
}
|
||||
|
||||
AfflictionIdentifiers = element.GetAttributeStringArray("afflictionidentifiers", new string[0]);
|
||||
for (int i = 0; i < AfflictionIdentifiers.Length; i++)
|
||||
private void ParseAfflictionTypes()
|
||||
{
|
||||
string[] splitValue = rawAfflictionTypeString.Split(',', ',');
|
||||
for (int i = 0; i < splitValue.Length; i++)
|
||||
{
|
||||
AfflictionIdentifiers[i] = AfflictionIdentifiers[i].ToLowerInvariant();
|
||||
splitValue[i] = splitValue[i].ToLowerInvariant().Trim();
|
||||
}
|
||||
AfflictionTypes = element.GetAttributeStringArray("afflictiontypes", new string[0]);
|
||||
for (int i = 0; i < AfflictionTypes.Length; i++)
|
||||
parsedAfflictionTypes = splitValue;
|
||||
}
|
||||
|
||||
private void ParseAfflictionIdentifiers()
|
||||
{
|
||||
string[] splitValue = rawAfflictionIdentifierString.Split(',', ',');
|
||||
for (int i = 0; i < splitValue.Length; i++)
|
||||
{
|
||||
AfflictionTypes[i] = AfflictionTypes[i].ToLowerInvariant();
|
||||
splitValue[i] = splitValue[i].ToLowerInvariant().Trim();
|
||||
}
|
||||
parsedAfflictionIdentifiers = splitValue;
|
||||
}
|
||||
|
||||
public bool MatchesAffliction(Affliction affliction)
|
||||
{
|
||||
//if no identifiers or types have been defined, the damage modifier affects all afflictions
|
||||
if (AfflictionIdentifiers.Length == 0 && AfflictionTypes.Length == 0) { return true; }
|
||||
return parsedAfflictionIdentifiers.Any(id => id.Equals(affliction.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
|| parsedAfflictionTypes.Any(t => t.Equals(affliction.Prefab.AfflictionType, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
foreach (string afflictionName in AfflictionIdentifiers)
|
||||
{
|
||||
if (affliction.Prefab.Identifier.ToLowerInvariant() == afflictionName) return true;
|
||||
}
|
||||
foreach (string afflictionType in AfflictionTypes)
|
||||
{
|
||||
if (affliction.Prefab.AfflictionType.ToLowerInvariant() == afflictionType) return true;
|
||||
}
|
||||
return false;
|
||||
public void Serialize(XElement element)
|
||||
{
|
||||
if (element == null) { return; }
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
}
|
||||
|
||||
public void Deserialize(XElement element)
|
||||
{
|
||||
if (element == null) { return; }
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,38 +50,25 @@ namespace Barotrauma
|
||||
public Job(XElement element)
|
||||
{
|
||||
string identifier = element.GetAttributeString("identifier", "").ToLowerInvariant();
|
||||
prefab = JobPrefab.List.Find(jp => jp.Identifier.ToLowerInvariant() == identifier);
|
||||
|
||||
string name = "";
|
||||
if (prefab == null)
|
||||
if (!JobPrefab.List.TryGetValue(identifier, out JobPrefab p))
|
||||
{
|
||||
name = element.GetAttributeString("name", "").ToLowerInvariant();
|
||||
prefab = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == name);
|
||||
DebugConsole.ThrowError($"Could not find the job {identifier}. Giving the character a random job.");
|
||||
p = JobPrefab.Random();
|
||||
}
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find the job \"" + name + "\" (identifier " + identifier + "). Giving the character a random job.");
|
||||
prefab = JobPrefab.List[Rand.Int(JobPrefab.List.Count)];
|
||||
}
|
||||
|
||||
prefab = p;
|
||||
skills = new Dictionary<string, Skill>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "skill") continue;
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "skill") { continue; }
|
||||
string skillIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
if (string.IsNullOrEmpty(skillIdentifier)) continue;
|
||||
if (string.IsNullOrEmpty(skillIdentifier)) { continue; }
|
||||
skills.Add(
|
||||
skillIdentifier,
|
||||
new Skill(skillIdentifier, subElement.GetAttributeFloat("level", 0)));
|
||||
}
|
||||
}
|
||||
|
||||
public static Job Random(Rand.RandSync randSync)
|
||||
{
|
||||
JobPrefab prefab = JobPrefab.List[Rand.Int(JobPrefab.List.Count - 1, randSync)];
|
||||
|
||||
return new Job(prefab);
|
||||
}
|
||||
public static Job Random(Rand.RandSync randSync = Rand.RandSync.Unsynced) => new Job(JobPrefab.Random(randSync));
|
||||
|
||||
public float GetSkillLevel(string skillIdentifier)
|
||||
{
|
||||
@@ -186,7 +173,7 @@ namespace Barotrauma
|
||||
wifiComponent.TeamID = character.TeamID;
|
||||
}
|
||||
|
||||
if (parentItem != null) parentItem.Combine(item);
|
||||
if (parentItem != null) parentItem.Combine(item, user: null);
|
||||
|
||||
foreach (XElement childItemElement in itemElement.Elements())
|
||||
{
|
||||
|
||||
@@ -2,18 +2,26 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class AutonomousObjective
|
||||
{
|
||||
public string aiTag;
|
||||
public string identifier;
|
||||
public string option;
|
||||
public float priorityModifier;
|
||||
|
||||
public AutonomousObjective(XElement element)
|
||||
{
|
||||
aiTag = element.GetAttributeString("aitag", null);
|
||||
identifier = element.GetAttributeString("identifier", null);
|
||||
|
||||
//backwards compatibility
|
||||
if (string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
identifier = element.GetAttributeString("aitag", null);
|
||||
}
|
||||
|
||||
option = element.GetAttributeString("option", null);
|
||||
priorityModifier = element.GetAttributeFloat("prioritymodifier", 1);
|
||||
priorityModifier = MathHelper.Max(priorityModifier, 0);
|
||||
@@ -22,13 +30,32 @@ namespace Barotrauma
|
||||
|
||||
partial class JobPrefab
|
||||
{
|
||||
public static List<JobPrefab> List;
|
||||
public static Dictionary<string, JobPrefab> List;
|
||||
public static JobPrefab Get(string identifier)
|
||||
{
|
||||
if (List == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Issue in the code execution order: job prefabs not loaded.");
|
||||
return null;
|
||||
}
|
||||
if (List.TryGetValue(identifier, out JobPrefab job))
|
||||
{
|
||||
return job;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't find a job prefab with the given identifier: " + identifier);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly XElement Items;
|
||||
public readonly List<string> ItemNames = new List<string>();
|
||||
public readonly List<SkillPrefab> Skills = new List<SkillPrefab>();
|
||||
public readonly List<AutonomousObjective> AutomaticOrders = new List<AutonomousObjective>();
|
||||
|
||||
public readonly List<string> AppropriateOrders = new List<string>();
|
||||
|
||||
|
||||
[Serialize("1,1,1,1", false)]
|
||||
public Color UIColor
|
||||
{
|
||||
@@ -126,6 +153,7 @@ namespace Barotrauma
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
Name = TextManager.Get("JobName." + Identifier);
|
||||
Description = TextManager.Get("JobDescription." + Identifier);
|
||||
Identifier = Identifier.ToLowerInvariant();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -133,35 +161,7 @@ namespace Barotrauma
|
||||
{
|
||||
case "items":
|
||||
Items = subElement;
|
||||
foreach (XElement itemElement in subElement.Elements())
|
||||
{
|
||||
if (itemElement.Element("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in job config \"" + Name + "\" - use identifiers instead of names to configure the items.");
|
||||
ItemNames.Add(itemElement.GetAttributeString("name", ""));
|
||||
continue;
|
||||
}
|
||||
|
||||
string itemIdentifier = itemElement.GetAttributeString("identifier", "");
|
||||
if (string.IsNullOrWhiteSpace(itemIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in job config \"" + Name + "\" - item with no identifier.");
|
||||
ItemNames.Add("");
|
||||
}
|
||||
else
|
||||
{
|
||||
var prefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in job config \"" + Name + "\" - item prefab \""+itemIdentifier+"\" not found.");
|
||||
ItemNames.Add("");
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemNames.Add(prefab.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
loadItemNames(subElement);
|
||||
break;
|
||||
case "skills":
|
||||
foreach (XElement skillElement in subElement.Elements())
|
||||
@@ -172,6 +172,44 @@ namespace Barotrauma
|
||||
case "autonomousobjectives":
|
||||
subElement.Elements().ForEach(order => AutomaticOrders.Add(new AutonomousObjective(order)));
|
||||
break;
|
||||
case "appropriateobjectives":
|
||||
case "appropriateorders":
|
||||
subElement.Elements().ForEach(order => AppropriateOrders.Add(order.GetAttributeString("identifier", "").ToLowerInvariant()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void loadItemNames(XElement parentElement)
|
||||
{
|
||||
foreach (XElement itemElement in parentElement.Elements())
|
||||
{
|
||||
if (itemElement.Element("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in job config \"" + Name + "\" - use identifiers instead of names to configure the items.");
|
||||
ItemNames.Add(itemElement.GetAttributeString("name", ""));
|
||||
continue;
|
||||
}
|
||||
|
||||
string itemIdentifier = itemElement.GetAttributeString("identifier", "");
|
||||
if (string.IsNullOrWhiteSpace(itemIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in job config \"" + Name + "\" - item with no identifier.");
|
||||
ItemNames.Add("");
|
||||
}
|
||||
else
|
||||
{
|
||||
var prefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in job config \"" + Name + "\" - item prefab \"" + itemIdentifier + "\" not found.");
|
||||
ItemNames.Add("");
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemNames.Add(prefab.Name);
|
||||
}
|
||||
}
|
||||
loadItemNames(itemElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,24 +222,45 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static JobPrefab Random()
|
||||
{
|
||||
return List[Rand.Int(List.Count)];
|
||||
}
|
||||
public static JobPrefab Random(Rand.RandSync sync = Rand.RandSync.Unsynced) => List.Values.GetRandom(sync);
|
||||
|
||||
public static void LoadAll(IEnumerable<string> filePaths)
|
||||
{
|
||||
List = new List<JobPrefab>();
|
||||
List = new Dictionary<string, JobPrefab>();
|
||||
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
JobPrefab job = new JobPrefab(element);
|
||||
List.Add(job);
|
||||
DebugConsole.ThrowError($"Error in '{filePath}': Cannot override all job prefabs, because many of them are required by the main game! Please try overriding jobs one by one.");
|
||||
}
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var job = new JobPrefab(element.FirstElement());
|
||||
if (List.TryGetValue(job.Identifier, out JobPrefab duplicate))
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the job '{duplicate.Identifier}' with another defined in '{filePath}'", Color.Yellow);
|
||||
List.Remove(duplicate.Identifier);
|
||||
}
|
||||
List.Add(job.Identifier, job);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (List.TryGetValue(element.GetAttributeString("identifier", "").ToLowerInvariant(), out JobPrefab duplicate))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{filePath}': Duplicate job definition found for: '{duplicate.Identifier}'. Use the <override> XML element as the parent of job element's definition to override the existing job.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var job = new JobPrefab(element);
|
||||
List.Add(job.Identifier, job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using LimbParams = Barotrauma.RagdollParams.LimbParams;
|
||||
using JointParams = Barotrauma.RagdollParams.JointParams;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -21,14 +23,14 @@ namespace Barotrauma
|
||||
partial class LimbJoint : RevoluteJoint
|
||||
{
|
||||
public bool IsSevered;
|
||||
public bool CanBeSevered => jointParams.CanBeSevered;
|
||||
public readonly JointParams jointParams;
|
||||
public bool CanBeSevered => Params.CanBeSevered;
|
||||
public readonly JointParams Params;
|
||||
public readonly Ragdoll ragdoll;
|
||||
public readonly Limb LimbA, LimbB;
|
||||
|
||||
public LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll) : this(limbA, limbB, Vector2.One, Vector2.One)
|
||||
{
|
||||
this.jointParams = jointParams;
|
||||
Params = jointParams;
|
||||
this.ragdoll = ragdoll;
|
||||
LoadParams();
|
||||
}
|
||||
@@ -43,63 +45,37 @@ namespace Barotrauma
|
||||
LimbB = limbB;
|
||||
}
|
||||
|
||||
public void SaveParams()
|
||||
{
|
||||
// Saving to the params is handled only in the params level.
|
||||
return;
|
||||
|
||||
jointParams.Stiffness = MaxMotorTorque;
|
||||
if (ragdoll.IsFlipped)
|
||||
{
|
||||
jointParams.Limb1Anchor = ConvertUnits.ToDisplayUnits(new Vector2(-LocalAnchorA.X, LocalAnchorA.Y) / jointParams.Ragdoll.JointScale);
|
||||
jointParams.Limb2Anchor = ConvertUnits.ToDisplayUnits(new Vector2(-LocalAnchorB.X, LocalAnchorB.Y) / jointParams.Ragdoll.JointScale);
|
||||
jointParams.UpperLimit = MathHelper.ToDegrees(-LowerLimit);
|
||||
jointParams.LowerLimit = MathHelper.ToDegrees(-UpperLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
jointParams.Limb1Anchor = ConvertUnits.ToDisplayUnits(LocalAnchorA / jointParams.Ragdoll.JointScale);
|
||||
jointParams.Limb2Anchor = ConvertUnits.ToDisplayUnits(LocalAnchorB / jointParams.Ragdoll.JointScale);
|
||||
jointParams.UpperLimit = MathHelper.ToDegrees(UpperLimit);
|
||||
jointParams.LowerLimit = MathHelper.ToDegrees(LowerLimit);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadParams()
|
||||
{
|
||||
MaxMotorTorque = jointParams.Stiffness;
|
||||
LimitEnabled = jointParams.LimitEnabled;
|
||||
if (float.IsNaN(jointParams.LowerLimit))
|
||||
MaxMotorTorque = Params.Stiffness;
|
||||
LimitEnabled = Params.LimitEnabled;
|
||||
if (float.IsNaN(Params.LowerLimit))
|
||||
{
|
||||
jointParams.LowerLimit = 0;
|
||||
Params.LowerLimit = 0;
|
||||
}
|
||||
if (float.IsNaN(jointParams.UpperLimit))
|
||||
if (float.IsNaN(Params.UpperLimit))
|
||||
{
|
||||
jointParams.UpperLimit = 0;
|
||||
Params.UpperLimit = 0;
|
||||
}
|
||||
if (ragdoll.IsFlipped)
|
||||
{
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-jointParams.Limb1Anchor.X, jointParams.Limb1Anchor.Y) * jointParams.Ragdoll.JointScale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-jointParams.Limb2Anchor.X, jointParams.Limb2Anchor.Y) * jointParams.Ragdoll.JointScale);
|
||||
UpperLimit = MathHelper.ToRadians(-jointParams.LowerLimit);
|
||||
LowerLimit = MathHelper.ToRadians(-jointParams.UpperLimit);
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Params.Ragdoll.JointScale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Params.Ragdoll.JointScale);
|
||||
UpperLimit = MathHelper.ToRadians(-Params.LowerLimit);
|
||||
LowerLimit = MathHelper.ToRadians(-Params.UpperLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(jointParams.Limb1Anchor * jointParams.Ragdoll.JointScale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(jointParams.Limb2Anchor * jointParams.Ragdoll.JointScale);
|
||||
UpperLimit = MathHelper.ToRadians(jointParams.UpperLimit);
|
||||
LowerLimit = MathHelper.ToRadians(jointParams.LowerLimit);
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Params.Ragdoll.JointScale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Params.Ragdoll.JointScale);
|
||||
UpperLimit = MathHelper.ToRadians(Params.UpperLimit);
|
||||
LowerLimit = MathHelper.ToRadians(Params.LowerLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class Limb : ISerializableEntity, ISpatialEntity
|
||||
{
|
||||
// Note: not used
|
||||
private const float LimbDensity = 15;
|
||||
private const float LimbAngularDamping = 7;
|
||||
|
||||
//how long it takes for severed limbs to fade out
|
||||
private const float SeveredFadeOutTime = 10.0f;
|
||||
|
||||
@@ -108,12 +84,12 @@ namespace Barotrauma
|
||||
/// Note that during the limb initialization, character.AnimController returns null, whereas this field is already assigned.
|
||||
/// </summary>
|
||||
public readonly Ragdoll ragdoll;
|
||||
public readonly LimbParams limbParams;
|
||||
public readonly LimbParams Params;
|
||||
|
||||
//the physics body of the limb
|
||||
public PhysicsBody body;
|
||||
|
||||
public Vector2 StepOffset => ConvertUnits.ToSimUnits(limbParams.StepOffset) * ragdoll.RagdollParams.JointScale;
|
||||
public Vector2 StepOffset => ConvertUnits.ToSimUnits(Params.StepOffset) * ragdoll.RagdollParams.JointScale;
|
||||
|
||||
public bool inWater;
|
||||
|
||||
@@ -125,19 +101,34 @@ namespace Barotrauma
|
||||
|
||||
private bool isSevered;
|
||||
private float severedFadeOutTimer;
|
||||
|
||||
public Vector2? MouthPos;
|
||||
|
||||
private Vector2? mouthPos;
|
||||
public Vector2 MouthPos
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!mouthPos.HasValue)
|
||||
{
|
||||
mouthPos = Params.MouthPos;
|
||||
}
|
||||
return mouthPos.Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
mouthPos = value;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Attack attack;
|
||||
private List<DamageModifier> damageModifiers;
|
||||
|
||||
private Direction dir;
|
||||
|
||||
public int HealthIndex => limbParams.HealthIndex;
|
||||
public float Scale => limbParams.Ragdoll.LimbScale;
|
||||
public float AttackPriority => limbParams.AttackPriority;
|
||||
public bool DoesFlip => limbParams.Flip;
|
||||
public float SteerForce => limbParams.SteerForce;
|
||||
public int HealthIndex => Params.HealthIndex;
|
||||
public float Scale => Params.Ragdoll.LimbScale;
|
||||
public float AttackPriority => Params.AttackPriority;
|
||||
public bool DoesFlip => Params.Flip;
|
||||
public float SteerForce => Params.SteerForce;
|
||||
|
||||
public Vector2 DebugTargetPos;
|
||||
public Vector2 DebugRefPos;
|
||||
@@ -198,7 +189,7 @@ namespace Barotrauma
|
||||
set { dir = (value == -1.0f) ? Direction.Left : Direction.Right; }
|
||||
}
|
||||
|
||||
public int RefJointIndex => limbParams.RefJoint;
|
||||
public int RefJointIndex => Params.RefJoint;
|
||||
|
||||
private List<WearableSprite> wearingItems;
|
||||
public List<WearableSprite> WearingItems
|
||||
@@ -291,7 +282,7 @@ namespace Barotrauma
|
||||
get { return pullJoint.LocalAnchorA; }
|
||||
}
|
||||
|
||||
public string Name => limbParams.Name;
|
||||
public string Name => Params.Name;
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
@@ -303,7 +294,7 @@ namespace Barotrauma
|
||||
{
|
||||
this.ragdoll = ragdoll;
|
||||
this.character = character;
|
||||
this.limbParams = limbParams;
|
||||
this.Params = limbParams;
|
||||
wearingItems = new List<WearableSprite>();
|
||||
dir = Direction.Right;
|
||||
body = new PhysicsBody(limbParams);
|
||||
@@ -324,19 +315,16 @@ namespace Barotrauma
|
||||
pullJoint = new FixedMouseJoint(body.FarseerBody, ConvertUnits.ToSimUnits(limbParams.PullPos * Scale))
|
||||
{
|
||||
Enabled = false,
|
||||
MaxForce = ((type == LimbType.LeftHand || type == LimbType.RightHand) ? 400.0f : 150.0f) * body.Mass
|
||||
//MaxForce = ((type == LimbType.LeftHand || type == LimbType.RightHand) ? 400.0f : 150.0f) * body.Mass
|
||||
// 150 or even 400 is too low if the joint is used for moving the character position from the mainlimb towards the collider position
|
||||
MaxForce = 1000 * Mass
|
||||
};
|
||||
|
||||
GameMain.World.AddJoint(pullJoint);
|
||||
|
||||
var element = limbParams.Element;
|
||||
if (element.Attribute("mouthpos") != null)
|
||||
{
|
||||
MouthPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("mouthpos", Vector2.Zero));
|
||||
}
|
||||
|
||||
body.BodyType = BodyType.Dynamic;
|
||||
body.FarseerBody.AngularDamping = LimbAngularDamping;
|
||||
|
||||
damageModifiers = new List<DamageModifier>();
|
||||
|
||||
@@ -403,19 +391,19 @@ namespace Barotrauma
|
||||
return AddDamage(simPosition, afflictions, playSound);
|
||||
}
|
||||
|
||||
public AttackResult AddDamage(Vector2 simPosition, List<Affliction> afflictions, bool playSound)
|
||||
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound)
|
||||
{
|
||||
List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
|
||||
//create a copy of the original affliction list to prevent modifying the afflictions of an Attack/StatusEffect etc
|
||||
afflictions = new List<Affliction>(afflictions.Where(a => Rand.Range(0.0f, 1.0f) <= a.ApplyProbability));
|
||||
for (int i = 0; i < afflictions.Count; i++)
|
||||
var afflictionsCopy = afflictions.Where(a => Rand.Range(0.0f, 1.0f) <= a.Probability).ToList();
|
||||
for (int i = 0; i < afflictionsCopy.Count; i++)
|
||||
{
|
||||
foreach (DamageModifier damageModifier in damageModifiers)
|
||||
{
|
||||
if (!damageModifier.MatchesAffliction(afflictions[i])) continue;
|
||||
if (SectorHit(damageModifier.ArmorSector, simPosition))
|
||||
if (!damageModifier.MatchesAffliction(afflictionsCopy[i])) continue;
|
||||
if (SectorHit(damageModifier.ArmorSectorInRadians, simPosition))
|
||||
{
|
||||
afflictions[i] = afflictions[i].CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
afflictionsCopy[i] = afflictionsCopy[i].CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
appliedDamageModifiers.Add(damageModifier);
|
||||
}
|
||||
}
|
||||
@@ -424,19 +412,19 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (DamageModifier damageModifier in wearable.WearableComponent.DamageModifiers)
|
||||
{
|
||||
if (!damageModifier.MatchesAffliction(afflictions[i])) continue;
|
||||
if (SectorHit(damageModifier.ArmorSector, simPosition))
|
||||
if (!damageModifier.MatchesAffliction(afflictionsCopy[i])) continue;
|
||||
if (SectorHit(damageModifier.ArmorSectorInRadians, simPosition))
|
||||
{
|
||||
afflictions[i] = afflictions[i].CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
afflictionsCopy[i] = afflictionsCopy[i].CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
appliedDamageModifiers.Add(damageModifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddDamageProjSpecific(simPosition, afflictions, playSound, appliedDamageModifiers);
|
||||
AddDamageProjSpecific(simPosition, afflictionsCopy, playSound, appliedDamageModifiers);
|
||||
|
||||
return new AttackResult(afflictions, this, appliedDamageModifiers);
|
||||
return new AttackResult(afflictionsCopy, this, appliedDamageModifiers);
|
||||
}
|
||||
|
||||
partial void AddDamageProjSpecific(Vector2 simPosition, List<Affliction> afflictions, bool playSound, List<DamageModifier> appliedDamageModifiers);
|
||||
@@ -456,7 +444,7 @@ namespace Barotrauma
|
||||
protected float GetArmorSectorRotationOffset(Vector2 armorSector)
|
||||
{
|
||||
float midAngle = MathUtils.GetMidAngle(armorSector.X, armorSector.Y);
|
||||
float spritesheetOrientation = MathHelper.ToRadians(limbParams.Ragdoll.SpritesheetOrientation);
|
||||
float spritesheetOrientation = Params.GetSpriteOrientation();
|
||||
return midAngle + spritesheetOrientation;
|
||||
}
|
||||
|
||||
@@ -495,10 +483,13 @@ namespace Barotrauma
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
|
||||
private readonly List<Body> contactBodies = new List<Body>();
|
||||
private List<Body> ignoredBodies;
|
||||
/// <summary>
|
||||
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
||||
/// </summary>
|
||||
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1)
|
||||
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
|
||||
{
|
||||
attackResult = default(AttackResult);
|
||||
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos));
|
||||
@@ -514,8 +505,11 @@ namespace Barotrauma
|
||||
case HitDetection.Distance:
|
||||
if (dist < attack.DamageRange)
|
||||
{
|
||||
List<Body> ignoredBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
|
||||
ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
|
||||
if (ignoredBodies == null)
|
||||
{
|
||||
ignoredBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
|
||||
ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
|
||||
}
|
||||
|
||||
structureBody = Submarine.PickBody(
|
||||
SimPosition, attackSimPos,
|
||||
@@ -541,46 +535,42 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case HitDetection.Contact:
|
||||
var targetBodies = new List<Body>();
|
||||
contactBodies.Clear();
|
||||
if (damageTarget is Character targetCharacter)
|
||||
{
|
||||
foreach (Limb limb in targetCharacter.AnimController.Limbs)
|
||||
{
|
||||
if (!limb.IsSevered && limb.body?.FarseerBody != null) targetBodies.Add(limb.body.FarseerBody);
|
||||
if (!limb.IsSevered && limb.body?.FarseerBody != null) contactBodies.Add(limb.body.FarseerBody);
|
||||
}
|
||||
}
|
||||
else if (damageTarget is Structure targetStructure)
|
||||
{
|
||||
if (character.Submarine == null && targetStructure.Submarine != null)
|
||||
{
|
||||
targetBodies.Add(targetStructure.Submarine.PhysicsBody.FarseerBody);
|
||||
contactBodies.Add(targetStructure.Submarine.PhysicsBody.FarseerBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetBodies.AddRange(targetStructure.Bodies);
|
||||
contactBodies.AddRange(targetStructure.Bodies);
|
||||
}
|
||||
}
|
||||
else if (damageTarget is Item)
|
||||
{
|
||||
Item targetItem = damageTarget as Item;
|
||||
if (targetItem.body?.FarseerBody != null) targetBodies.Add(targetItem.body.FarseerBody);
|
||||
if (targetItem.body?.FarseerBody != null) contactBodies.Add(targetItem.body.FarseerBody);
|
||||
}
|
||||
|
||||
if (targetBodies != null)
|
||||
ContactEdge contactEdge = body.FarseerBody.ContactList;
|
||||
while (contactEdge != null)
|
||||
{
|
||||
ContactEdge contactEdge = body.FarseerBody.ContactList;
|
||||
while (contactEdge != null)
|
||||
if (contactEdge.Contact != null &&
|
||||
contactEdge.Contact.IsTouching &&
|
||||
contactBodies.Any(b => b == contactEdge.Contact.FixtureA?.Body || b == contactEdge.Contact.FixtureB?.Body))
|
||||
{
|
||||
if (contactEdge.Contact != null &&
|
||||
contactEdge.Contact.IsTouching &&
|
||||
targetBodies.Any(b => b == contactEdge.Contact.FixtureA?.Body || b == contactEdge.Contact.FixtureB?.Body))
|
||||
{
|
||||
structureBody = targetBodies.LastOrDefault();
|
||||
wasHit = true;
|
||||
break;
|
||||
}
|
||||
contactEdge = contactEdge.Next;
|
||||
structureBody = contactBodies.LastOrDefault();
|
||||
wasHit = true;
|
||||
break;
|
||||
}
|
||||
contactEdge = contactEdge.Next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -601,11 +591,18 @@ namespace Barotrauma
|
||||
LastAttackSoundTime = SoundInterval;
|
||||
}
|
||||
#endif
|
||||
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound);
|
||||
if (damageTarget is Character targetCharacter && targetLimb != null)
|
||||
{
|
||||
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound);
|
||||
}
|
||||
else
|
||||
{
|
||||
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound);
|
||||
}
|
||||
if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.Server))
|
||||
{
|
||||
// TODO: use the hit pos?
|
||||
var localFront = body.GetLocalFront(MathHelper.ToRadians(ragdoll.RagdollParams.SpritesheetOrientation));
|
||||
var localFront = body.GetLocalFront(Params.GetSpriteOrientation());
|
||||
var from = body.FarseerBody.GetWorldPoint(localFront);
|
||||
var to = from;
|
||||
var drawPos = body.DrawPosition;
|
||||
@@ -665,7 +662,7 @@ namespace Barotrauma
|
||||
{
|
||||
PhysicsBody mainLimbBody = ragdoll.MainLimb.body;
|
||||
Body colliderBody = ragdoll.Collider.FarseerBody;
|
||||
Vector2 mainLimbLocalFront = mainLimbBody.GetLocalFront(MathHelper.ToRadians(ragdoll.RagdollParams.SpritesheetOrientation));
|
||||
Vector2 mainLimbLocalFront = mainLimbBody.GetLocalFront(ragdoll.MainLimb.Params.GetSpriteOrientation());
|
||||
if (Dir < 0)
|
||||
{
|
||||
mainLimbLocalFront.X = -mainLimbLocalFront.X;
|
||||
@@ -715,8 +712,7 @@ namespace Barotrauma
|
||||
|
||||
public void LoadParams()
|
||||
{
|
||||
attack?.Deserialize();
|
||||
pullJoint.LocalAnchorA = ConvertUnits.ToSimUnits(limbParams.PullPos * Scale);
|
||||
pullJoint.LocalAnchorA = ConvertUnits.ToSimUnits(Params.PullPos * Scale);
|
||||
LoadParamsProjSpecific();
|
||||
}
|
||||
|
||||
|
||||
+45
-19
@@ -20,30 +20,42 @@ namespace Barotrauma
|
||||
|
||||
abstract class GroundedMovementParams : AnimationParams
|
||||
{
|
||||
[Serialize("1.0, 1.0", true), Editable(DecimalCount = 2, ToolTip = "How big steps the character takes.")]
|
||||
[Serialize("1.0, 1.0", true, description: "How big steps the character takes."), Editable(DecimalCount = 2)]
|
||||
public Vector2 StepSize
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0f, true), Editable(DecimalCount = 2, ToolTip = "How high above the ground the character's head is positioned.")]
|
||||
[Serialize(0f, true, description: "How high above the ground the character's head is positioned."), Editable(DecimalCount = 2)]
|
||||
public float HeadPosition { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(DecimalCount = 2, ToolTip = "How high above the ground the character's torso is positioned.")]
|
||||
[Serialize(0f, true, description: "How high above the ground the character's torso is positioned."), Editable(DecimalCount = 2)]
|
||||
public float TorsoPosition { get; set; }
|
||||
|
||||
[Serialize(0.75f, true), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.99f, DecimalCount = 2, ToolTip = "The character's movement speed is multiplied with this value when moving backwards.")]
|
||||
[Serialize(1f, true, description: "Separate multiplier for the head lift"), Editable(MinValueFloat = 0, MaxValueFloat = 2, ValueStep = 0.1f)]
|
||||
public float StepLiftHeadMultiplier { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "How much the body raises when taking a step."), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 0.1f)]
|
||||
public float StepLiftAmount { get; set; }
|
||||
|
||||
[Serialize(-0.5f, true, description: "When does the body raise when taking a step. The default (0.5) is in the middle of the step."), Editable(MinValueFloat = -1, MaxValueFloat = 1, DecimalCount = 2, ValueStep = 0.1f)]
|
||||
public float StepLiftOffset { get; set; }
|
||||
|
||||
[Serialize(2f, true, description: "How frequently the body raises when taking a step. The default is 2 (after every step)."), Editable(MinValueFloat = 0, MaxValueFloat = 10, ValueStep = 0.1f)]
|
||||
public float StepLiftFrequency { get; set; }
|
||||
|
||||
[Serialize(0.75f, true, description: "The character's movement speed is multiplied with this value when moving backwards."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.99f, DecimalCount = 2)]
|
||||
public float BackwardsMovementMultiplier { get; set; }
|
||||
}
|
||||
|
||||
abstract class SwimParams : AnimationParams
|
||||
{
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
[Serialize(25.0f, true, description: "Turning speed (or rather a force applied on the main collider to make it turn). Note that you can set a limb-specific steering forces too (additional)."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float SteerTorque { get; set; }
|
||||
}
|
||||
|
||||
abstract class AnimationParams : EditableParams
|
||||
abstract class AnimationParams : EditableParams, IMemorizable<AnimationParams>
|
||||
{
|
||||
public string SpeciesName { get; private set; }
|
||||
public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run;
|
||||
@@ -51,11 +63,11 @@ namespace Barotrauma
|
||||
|
||||
protected static Dictionary<string, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<string, Dictionary<string, AnimationParams>>();
|
||||
|
||||
[Serialize(1.0f, true), Editable(DecimalCount = 2)]
|
||||
[Serialize(1.0f, true), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = Ragdoll.MAX_SPEED)]
|
||||
public float MovementSpeed { get; set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2,
|
||||
ToolTip = "The speed of the \"animation cycle\", i.e. how fast the character takes steps or moves the tail/legs/arms (the outcome depends what the clip is about)")]
|
||||
[Serialize(1.0f, true, description: "The speed of the \"animation cycle\", i.e. how fast the character takes steps or moves the tail/legs/arms (the outcome depends what the clip is about)"),
|
||||
Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
public float CycleSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -101,8 +113,13 @@ namespace Barotrauma
|
||||
|
||||
public static string GetFolder(string speciesName, ContentPackage contentPackage = null)
|
||||
{
|
||||
string configFilePath = Character.GetConfigFile(speciesName, contentPackage);
|
||||
var folder = XMLExtensions.TryLoadXml(configFilePath)?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
|
||||
string configFilePath = Character.GetConfigFilePath(speciesName, contentPackage);
|
||||
if (!Character.TryGetConfigFile(configFilePath, out XDocument configFile))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to load config file: {configFilePath} for '{speciesName}'");
|
||||
return string.Empty;
|
||||
}
|
||||
var folder = configFile.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
|
||||
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
|
||||
{
|
||||
folder = Path.Combine(Path.GetDirectoryName(configFilePath), "Animations");
|
||||
@@ -197,9 +214,10 @@ namespace Barotrauma
|
||||
T a = new T();
|
||||
if (a.Load(selectedFile, speciesName))
|
||||
{
|
||||
if (!anims.ContainsKey(a.Name))
|
||||
fileName = Path.GetFileNameWithoutExtension(selectedFile);
|
||||
if (!anims.ContainsKey(fileName))
|
||||
{
|
||||
anims.Add(a.Name, a);
|
||||
anims.Add(fileName, a);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -211,6 +229,8 @@ namespace Barotrauma
|
||||
return (T)anim;
|
||||
}
|
||||
|
||||
public static void ClearCache() => allAnimations.Clear();
|
||||
|
||||
public static AnimationParams Create(string fullPath, string speciesName, AnimationType animationType, Type type)
|
||||
{
|
||||
if (type == typeof(HumanWalkParams))
|
||||
@@ -275,11 +295,14 @@ namespace Barotrauma
|
||||
instance.IsLoaded = instance.Deserialize(animationElement);
|
||||
instance.Save();
|
||||
instance.Load(fullPath, speciesName);
|
||||
anims.Add(instance.Name, instance);
|
||||
anims.Add(fileName, instance);
|
||||
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
|
||||
return instance as T;
|
||||
}
|
||||
|
||||
public bool Serialize() => base.Serialize();
|
||||
public bool Deserialize() => base.Deserialize();
|
||||
|
||||
protected bool Load(string file, string speciesName)
|
||||
{
|
||||
if (Load(file))
|
||||
@@ -380,14 +403,16 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#region Memento
|
||||
protected void CreateSnapshot<T>() where T : AnimationParams, new()
|
||||
public Memento<AnimationParams> Memento { get; protected set; } = new Memento<AnimationParams>();
|
||||
public abstract void StoreSnapshot();
|
||||
protected void StoreSnapshot<T>() where T : AnimationParams, new()
|
||||
{
|
||||
Serialize();
|
||||
if (doc == null)
|
||||
{
|
||||
DebugConsole.ThrowError("[AnimationParams] The source XML Document is null!");
|
||||
return;
|
||||
}
|
||||
Serialize();
|
||||
var copy = new T
|
||||
{
|
||||
IsLoaded = true,
|
||||
@@ -395,10 +420,11 @@ namespace Barotrauma
|
||||
};
|
||||
copy.Deserialize();
|
||||
copy.Serialize();
|
||||
memento.Store(copy);
|
||||
Memento.Store(copy);
|
||||
}
|
||||
public override void Undo() => Deserialize(memento.Undo().MainElement);
|
||||
public override void Redo() => Deserialize(memento.Redo().MainElement);
|
||||
public void Undo() => Deserialize(Memento.Undo().MainElement);
|
||||
public void Redo() => Deserialize(Memento.Redo().MainElement);
|
||||
public void ClearHistory() => Memento.Clear();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+23
-23
@@ -16,7 +16,7 @@ namespace Barotrauma
|
||||
|
||||
protected static FishWalkParams Empty = new FishWalkParams();
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<FishWalkParams>();
|
||||
public override void StoreSnapshot() => StoreSnapshot<FishWalkParams>();
|
||||
}
|
||||
|
||||
class FishRunParams : FishGroundedParams
|
||||
@@ -32,7 +32,7 @@ namespace Barotrauma
|
||||
|
||||
protected static FishRunParams Empty = new FishRunParams();
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<FishRunParams>();
|
||||
public override void StoreSnapshot() => StoreSnapshot<FishRunParams>();
|
||||
}
|
||||
|
||||
class FishSwimFastParams : FishSwimParams
|
||||
@@ -43,7 +43,7 @@ namespace Barotrauma
|
||||
return GetAnimParams<FishSwimFastParams>(character.SpeciesName, AnimationType.SwimFast, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<FishSwimFastParams>();
|
||||
public override void StoreSnapshot() => StoreSnapshot<FishSwimFastParams>();
|
||||
}
|
||||
|
||||
class FishSwimSlowParams : FishSwimParams
|
||||
@@ -54,7 +54,7 @@ namespace Barotrauma
|
||||
return GetAnimParams<FishSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<FishSwimSlowParams>();
|
||||
public override void StoreSnapshot() => StoreSnapshot<FishSwimSlowParams>();
|
||||
}
|
||||
|
||||
abstract class FishGroundedParams : GroundedMovementParams, IFishAnimation
|
||||
@@ -69,38 +69,38 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
[Serialize(true, true), Editable(ToolTip = "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
|
||||
[Editable, Serialize(true, true, description: "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
|
||||
public bool Flip { get; set; }
|
||||
|
||||
[Serialize(10.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the head to the correct position.")]
|
||||
[Serialize(10.0f, true, description: "How much force is used to move the head to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float HeadMoveForce { get; set; }
|
||||
|
||||
[Serialize(10.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the torso to the correct position.")]
|
||||
[Serialize(10.0f, true, description: "How much force is used to move the torso to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float TorsoMoveForce { get; set; }
|
||||
|
||||
[Serialize(8.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the feet to the correct position.")]
|
||||
[Serialize(8.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float FootMoveForce { get; set; }
|
||||
|
||||
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the head to the correct orientation.")]
|
||||
[Serialize(50.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float HeadTorque { get; set; }
|
||||
|
||||
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the torso to the correct orientation.")]
|
||||
[Serialize(50.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float TorsoTorque { get; set; }
|
||||
|
||||
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the tail to the correct orientation.")]
|
||||
[Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float TailTorque { get; set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
|
||||
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float FootTorque { get; set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "Optional torque that's constantly applied to legs.")]
|
||||
[Serialize(0.0f, true, description: "Optional torque that's constantly applied to legs."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float LegTorque { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The angle of the collider when standing (i.e. out of water).
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(0f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ToolTip = "The angle of the character's collider when standing.")]
|
||||
[Serialize(0f, true, description: "The angle of the character's collider when standing."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
|
||||
public float ColliderStandAngle
|
||||
{
|
||||
get => MathHelper.ToDegrees(ColliderStandAngleInRadians);
|
||||
@@ -140,13 +140,13 @@ namespace Barotrauma
|
||||
|
||||
abstract class FishSwimParams : SwimParams, IFishAnimation
|
||||
{
|
||||
[Serialize(false, true), Editable(ToolTip = "TODO")]
|
||||
[Serialize(false, true, description: "TODO"), Editable]
|
||||
public bool UseSineMovement { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable(ToolTip = "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
|
||||
[Editable, Serialize(true, true, description: "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
|
||||
public bool Flip { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable(ToolTip = "If enabled, the character will simply be mirrored horizontally when it wants to turn around. If disabled, it will rotate itself to face the other direction.")]
|
||||
[Editable, Serialize(true, true, description: "If enabled, the character will simply be mirrored horizontally when it wants to turn around. If disabled, it will rotate itself to face the other direction.")]
|
||||
public bool Mirror { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable]
|
||||
@@ -155,19 +155,19 @@ namespace Barotrauma
|
||||
[Serialize(10.0f, true), Editable]
|
||||
public float WaveLength { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable(ToolTip = "Should the character face towards the direction it's heading.")]
|
||||
[Editable, Serialize(true, true, description: "Should the character face towards the direction it's heading.")]
|
||||
public bool RotateTowardsMovement { get; set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the torso to the correct orientation.")]
|
||||
[Serialize(25.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float TorsoTorque { get; set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the head to the correct orientation.")]
|
||||
|
||||
[Serialize(25.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float HeadTorque { get; set; }
|
||||
|
||||
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the tail to the correct orientation.")]
|
||||
[Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float TailTorque { get; set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
|
||||
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float FootTorque { get; set; }
|
||||
|
||||
[Serialize(null, true), Editable]
|
||||
+21
-21
@@ -10,7 +10,7 @@ namespace Barotrauma
|
||||
return GetAnimParams<HumanWalkParams>(character.SpeciesName, AnimationType.Walk, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<HumanWalkParams>();
|
||||
public override void StoreSnapshot() => StoreSnapshot<HumanWalkParams>();
|
||||
}
|
||||
|
||||
class HumanRunParams : HumanGroundedParams
|
||||
@@ -21,7 +21,7 @@ namespace Barotrauma
|
||||
return GetAnimParams<HumanRunParams>(character.SpeciesName, AnimationType.Run, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<HumanRunParams>();
|
||||
public override void StoreSnapshot() => StoreSnapshot<HumanRunParams>();
|
||||
}
|
||||
|
||||
class HumanSwimFastParams: HumanSwimParams
|
||||
@@ -33,7 +33,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<HumanSwimFastParams>();
|
||||
public override void StoreSnapshot() => StoreSnapshot<HumanSwimFastParams>();
|
||||
}
|
||||
|
||||
class HumanSwimSlowParams : HumanSwimParams
|
||||
@@ -44,7 +44,7 @@ namespace Barotrauma
|
||||
return GetAnimParams<HumanSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<HumanSwimSlowParams>();
|
||||
public override void StoreSnapshot() => StoreSnapshot<HumanSwimSlowParams>();
|
||||
}
|
||||
|
||||
abstract class HumanSwimParams : SwimParams, IHumanAnimation
|
||||
@@ -81,44 +81,44 @@ namespace Barotrauma
|
||||
}
|
||||
public float FootAngleInRadians { get; private set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
|
||||
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float FootRotateStrength { get; set; }
|
||||
}
|
||||
|
||||
abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation
|
||||
{
|
||||
[Serialize(0.3f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2, ToolTip = "How much force is used to force the character upright.")]
|
||||
[Serialize(0.3f, true, description: "How much force is used to force the character upright."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)]
|
||||
public float GetUpForce { get; set; }
|
||||
|
||||
// -- TODO: use a separate clip for crawling -> replace these when implemented.
|
||||
|
||||
[Serialize(0.65f, true), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2, ToolTip = "Height of the torso when crouching.")]
|
||||
[Serialize(0.65f, true, description: "Height of the torso when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)]
|
||||
public float CrouchingTorsoPos { get; set; }
|
||||
|
||||
[Serialize(0.65f, true), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2, ToolTip = "Height of the head when crouching.")]
|
||||
[Serialize(0.65f, true, description: "Height of the head when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)]
|
||||
public float CrouchingHeadPos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees
|
||||
/// </summary>
|
||||
[Serialize(-10f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ToolTip = "Angle of the torso when crouching.")]
|
||||
[Serialize(-10f, true, description: "Angle of the torso when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
|
||||
public float CrouchingTorsoAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees
|
||||
/// </summary>
|
||||
[Serialize(-10f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ToolTip = "Angle of the head when crouching.")]
|
||||
[Serialize(-10f, true, description: "Angle of the head when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
|
||||
public float CrouchingHeadAngle { get; set; }
|
||||
|
||||
// --
|
||||
|
||||
[Serialize(0.25f, true), Editable(DecimalCount = 2, ToolTip = "How much the character's head leans forwards when moving.")]
|
||||
[Serialize(0.25f, true, description: "How much the character's head leans forwards when moving."), Editable(DecimalCount = 2)]
|
||||
public float HeadLeanAmount { get; set; }
|
||||
|
||||
[Serialize(0.25f, true), Editable(DecimalCount = 2, ToolTip = "How much the character's torso leans forwards when moving.")]
|
||||
[Serialize(0.25f, true, description: "How much the character's torso leans forwards when moving."), Editable(DecimalCount = 2)]
|
||||
public float TorsoLeanAmount { get; set; }
|
||||
|
||||
[Serialize(15.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the feet to the correct position.")]
|
||||
[Serialize(15.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float FootMoveStrength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -135,28 +135,28 @@ namespace Barotrauma
|
||||
}
|
||||
public float FootAngleInRadians { get; private set; }
|
||||
|
||||
[Serialize(20.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
|
||||
[Serialize(20.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float FootRotateStrength { get; set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true), Editable(DecimalCount = 2, ToolTip = "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them.")]
|
||||
[Serialize("0.0, 0.0", true, description: "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them."), Editable(DecimalCount = 2)]
|
||||
public Vector2 FootMoveOffset { get; set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true), Editable(DecimalCount = 2, ToolTip = "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them.")]
|
||||
[Serialize("0.0, 0.0", true, description: "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them."), Editable(DecimalCount = 2)]
|
||||
public Vector2 CrouchingFootMoveOffset { get; set; }
|
||||
|
||||
[Serialize(10.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much torque is used to bend the characters legs when taking a step.")]
|
||||
[Serialize(10.0f, true, description: "How much torque is used to bend the characters legs when taking a step."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float LegBendTorque { get; set; }
|
||||
|
||||
[Serialize("0.4, 0.15", true), Editable(DecimalCount = 2, ToolTip = "How much the hands move along each axis.")]
|
||||
[Serialize("0.4, 0.15", true, description: "How much the hands move along each axis."), Editable(DecimalCount = 2)]
|
||||
public Vector2 HandMoveAmount { get; set; }
|
||||
|
||||
[Serialize("-0.15, 0.0", true), Editable(DecimalCount = 2, ToolTip = "Added to the calculated hand positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their hands one unit behind them.")]
|
||||
[Serialize("-0.15, 0.0", true, description: "Added to the calculated hand positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their hands one unit behind them."), Editable(DecimalCount = 2)]
|
||||
public Vector2 HandMoveOffset { get; set; }
|
||||
|
||||
[Serialize(0.7f, true), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2, ToolTip = "How much force is used to move the hands.")]
|
||||
[Serialize(0.7f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2)]
|
||||
public float HandMoveStrength { get; set; }
|
||||
|
||||
[Serialize(-1.0f, true), Editable(DecimalCount = 2, ToolTip = "The position of the hands is clamped below this (relative to the position of the character's torso).")]
|
||||
[Serialize(-1.0f, true, description: "The position of the hands is clamped below this (relative to the position of the character's torso)."), Editable(DecimalCount = 2)]
|
||||
public float HandClampY { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,602 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
#if CLIENT
|
||||
using SoundType = Barotrauma.CharacterSound.SoundType;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains character data that should be editable in the character editor.
|
||||
/// </summary>
|
||||
class CharacterParams : EditableParams
|
||||
{
|
||||
[Serialize("", true), Editable]
|
||||
public string SpeciesName { get; private set; }
|
||||
|
||||
[Serialize("", true, description: "If defined, different species of the same group are considered like the characters of the same species by the AI."), Editable]
|
||||
public string Group { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool Humanoid { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool Husk { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool NeedsAir { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool CanSpeak { get; set; }
|
||||
|
||||
[Serialize(100f, true, description: "How much noise the character makes when moving?"), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
public float Noise { get; set; }
|
||||
|
||||
[Serialize("blood", true), Editable]
|
||||
public string BloodDecal { get; private set; }
|
||||
|
||||
public readonly string File;
|
||||
|
||||
public readonly List<SubParam> SubParams = new List<SubParam>();
|
||||
public readonly List<SoundParams> Sounds = new List<SoundParams>();
|
||||
public readonly List<ParticleParams> BloodEmitters = new List<ParticleParams>();
|
||||
public readonly List<ParticleParams> GibEmitters = new List<ParticleParams>();
|
||||
public readonly List<InventoryParams> Inventories = new List<InventoryParams>();
|
||||
public HealthParams Health { get; private set; }
|
||||
public AIParams AI { get; private set; }
|
||||
|
||||
public CharacterParams(string file)
|
||||
{
|
||||
File = file;
|
||||
Load();
|
||||
}
|
||||
|
||||
protected override string GetName() => "Character Config File";
|
||||
|
||||
public override XElement MainElement => doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
|
||||
|
||||
public bool Load()
|
||||
{
|
||||
bool success = base.Load(File);
|
||||
if (string.IsNullOrEmpty(SpeciesName) && MainElement != null)
|
||||
{
|
||||
//backwards compatibility
|
||||
SpeciesName = MainElement.GetAttributeString("name", "");
|
||||
}
|
||||
CreateSubParams();
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool Save(string fileNameWithoutExtension = null)
|
||||
{
|
||||
Serialize();
|
||||
return base.Save(fileNameWithoutExtension, new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = true,
|
||||
NewLineOnAttributes = false
|
||||
});
|
||||
}
|
||||
|
||||
public override bool Reset(bool forceReload = false)
|
||||
{
|
||||
if (forceReload)
|
||||
{
|
||||
return Load();
|
||||
}
|
||||
Deserialize(OriginalElement, alsoChildren: true);
|
||||
SubParams.ForEach(sp => sp.Reset());
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CompareGroup(string group) => !string.IsNullOrWhiteSpace(group) && !string.IsNullOrWhiteSpace(Group) && group.Equals(Group, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
protected void CreateSubParams()
|
||||
{
|
||||
SubParams.Clear();
|
||||
var health = MainElement.GetChildElement("health");
|
||||
if (health != null)
|
||||
{
|
||||
Health = new HealthParams(health, this);
|
||||
SubParams.Add(Health);
|
||||
}
|
||||
// TODO: support for multiple ai elements?
|
||||
var ai = MainElement.GetChildElement("ai");
|
||||
if (ai != null)
|
||||
{
|
||||
AI = new AIParams(ai, this);
|
||||
SubParams.Add(AI);
|
||||
}
|
||||
foreach (var element in MainElement.GetChildElements("bloodemitter"))
|
||||
{
|
||||
var emitter = new ParticleParams(element, this);
|
||||
BloodEmitters.Add(emitter);
|
||||
SubParams.Add(emitter);
|
||||
}
|
||||
foreach (var element in MainElement.GetChildElements("gibemitter"))
|
||||
{
|
||||
var emitter = new ParticleParams(element, this);
|
||||
GibEmitters.Add(emitter);
|
||||
SubParams.Add(emitter);
|
||||
}
|
||||
foreach (var soundElement in MainElement.GetChildElements("sound"))
|
||||
{
|
||||
var sound = new SoundParams(soundElement, this);
|
||||
Sounds.Add(sound);
|
||||
SubParams.Add(sound);
|
||||
}
|
||||
foreach (var inventoryElement in MainElement.GetChildElements("inventory"))
|
||||
{
|
||||
var inventory = new InventoryParams(inventoryElement, this);
|
||||
Inventories.Add(inventory);
|
||||
SubParams.Add(inventory);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Deserialize(XElement element = null, bool alsoChildren = true, bool recursive = true)
|
||||
{
|
||||
if (base.Deserialize(element))
|
||||
{
|
||||
//backwards compatibility
|
||||
if (string.IsNullOrEmpty(SpeciesName))
|
||||
{
|
||||
SpeciesName = element.GetAttributeString("name", "[NAME NOT GIVEN]");
|
||||
}
|
||||
if (alsoChildren)
|
||||
{
|
||||
SubParams.ForEach(p => p.Deserialize(recursive));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Serialize(XElement element = null, bool alsoChildren = true, bool recursive = true)
|
||||
{
|
||||
if (base.Serialize(element))
|
||||
{
|
||||
if (alsoChildren)
|
||||
{
|
||||
SubParams.ForEach(p => p.Serialize(recursive));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public void AddToEditor(ParamsEditor editor, bool alsoChildren = true, bool recursive = true, int space = 0)
|
||||
{
|
||||
base.AddToEditor(editor);
|
||||
if (alsoChildren)
|
||||
{
|
||||
SubParams.ForEach(s => s.AddToEditor(editor, recursive));
|
||||
}
|
||||
if (space > 0)
|
||||
{
|
||||
new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: ParamsEditor.Color)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public bool AddSound() => TryAddSubParam(new XElement("sound"), (e, c) => new SoundParams(e, c), out _, Sounds);
|
||||
|
||||
public void AddInventory() => TryAddSubParam(new XElement("inventory", new XElement("item")), (e, c) => new InventoryParams(e, c), out _, Inventories);
|
||||
|
||||
public void AddBloodEmitter() => AddEmitter("bloodemitter");
|
||||
public void AddGibEmitter() => AddEmitter("gibemitter");
|
||||
|
||||
private void AddEmitter(string type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "gibemitter":
|
||||
TryAddSubParam(new XElement(type), (e, c) => new ParticleParams(e, c), out _, GibEmitters);
|
||||
break;
|
||||
case "bloodemitter":
|
||||
TryAddSubParam(new XElement(type), (e, c) => new ParticleParams(e, c), out _, BloodEmitters);
|
||||
break;
|
||||
default: throw new NotImplementedException(type);
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveSound(SoundParams soundParams) => RemoveSubParam(soundParams);
|
||||
public bool RemoveBloodEmitter(ParticleParams emitter) => RemoveSubParam(emitter, BloodEmitters);
|
||||
public bool RemoveGibEmitter(ParticleParams emitter) => RemoveSubParam(emitter, GibEmitters);
|
||||
public bool RemoveInventory(InventoryParams inventory) => RemoveSubParam(inventory, Inventories);
|
||||
|
||||
protected bool RemoveSubParam<T>(T subParam, IList<T> collection = null) where T : SubParam
|
||||
{
|
||||
if (subParam == null || subParam.Element == null || subParam.Element.Parent == null) { return false; }
|
||||
if (collection != null && !collection.Contains(subParam)) { return false; }
|
||||
if (!SubParams.Contains(subParam)) { return false; }
|
||||
collection?.Remove(subParam);
|
||||
SubParams.Remove(subParam);
|
||||
subParam.Element.Remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool TryAddSubParam<T>(XElement element, Func<XElement, CharacterParams, T> constructor, out T subParam, IList<T> collection = null, Func<IList<T>, bool> filter = null) where T : SubParam
|
||||
{
|
||||
subParam = constructor(element, this);
|
||||
if (collection != null && filter != null)
|
||||
{
|
||||
if (filter(collection)) { return false; }
|
||||
}
|
||||
MainElement.Add(element);
|
||||
SubParams.Add(subParam);
|
||||
collection?.Add(subParam);
|
||||
return subParam != null;
|
||||
}
|
||||
|
||||
#region Subparams
|
||||
public class SoundParams : SubParam
|
||||
{
|
||||
public override string Name => "Sound";
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string File { get; private set; }
|
||||
|
||||
#if CLIENT
|
||||
[Serialize(SoundType.Idle, true), Editable]
|
||||
public SoundType State { get; private set; }
|
||||
#endif
|
||||
|
||||
[Serialize(1000f, true), Editable(minValue: 0f, maxValue: 10000f)]
|
||||
public float Range { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(minValue: 0f, maxValue: 2.0f)]
|
||||
public float Volume { get; private set; }
|
||||
|
||||
[Serialize(Gender.None, true, description: "Is the sound gender specific?"), Editable()]
|
||||
public Gender Gender { get; private set; }
|
||||
|
||||
public SoundParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
}
|
||||
|
||||
public class ParticleParams : SubParam
|
||||
{
|
||||
private string name;
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (name == null && Element != null)
|
||||
{
|
||||
name = Element.Name.ToString().FormatCamelCaseWithSpaces();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string Particle { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(-360f, 360f, decimals: 0)]
|
||||
public float AngleMin { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(-360f, 360f, decimals: 0)]
|
||||
public float AngleMax { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(0f, 100f, decimals: 2)]
|
||||
public float ScaleMin { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(0f, 100f, decimals: 2)]
|
||||
public float ScaleMax { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(0f, 10000f, decimals: 0)]
|
||||
public float VelocityMin { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(0f, 10000f, decimals: 0)]
|
||||
public float VelocityMax { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(0f, 100f, decimals: 2)]
|
||||
public float EmitInterval { get; private set; }
|
||||
|
||||
[Serialize(0, true), Editable(0, 1000)]
|
||||
public int ParticlesPerSecond { get; private set; }
|
||||
|
||||
[Serialize(0, true), Editable(0, 1000)]
|
||||
public int ParticleAmount { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool HighQualityCollisionDetection { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool CopyEntityAngle { get; private set; }
|
||||
|
||||
public ParticleParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
}
|
||||
|
||||
public class HealthParams : SubParam
|
||||
{
|
||||
public override string Name => "Health";
|
||||
|
||||
[Serialize(100f, true, description: "How much (max) health does the character have?"), Editable(minValue: 1, maxValue: 10000f)]
|
||||
public float Vitality { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool DoesBleed { get; set; }
|
||||
|
||||
[Serialize(float.NegativeInfinity, true), Editable(minValue: float.NegativeInfinity, maxValue: 0)]
|
||||
public float CrushDepth { get; set; }
|
||||
|
||||
// Make editable?
|
||||
[Serialize(false, true)]
|
||||
public bool UseHealthWindow { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "How easily the character heals from the bleeding wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float BleedingReduction { get; private set; }
|
||||
|
||||
[Serialize(0f, true, description: "How easily the character heals from the burn wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float BurnReduction { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float ConstantHealthRegeneration { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float HealthRegenerationWhenEating { get; private set; }
|
||||
|
||||
// TODO: limbhealths, sprite?
|
||||
|
||||
public HealthParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
}
|
||||
|
||||
public class InventoryParams : SubParam
|
||||
{
|
||||
public class InventoryItem : SubParam
|
||||
{
|
||||
public override string Name => "Item";
|
||||
|
||||
[Serialize("", true, description: "Item identifier."), Editable()]
|
||||
public string Identifier { get; private set; }
|
||||
|
||||
public InventoryItem(XElement element, CharacterParams character) : base(element, character) { }
|
||||
}
|
||||
|
||||
public override string Name => "Inventory";
|
||||
|
||||
[Serialize("Any, Any", true, description: "Which slots the inventory holds? Accepted types: None, Any, RightHand, LeftHand, Head, InnerClothes, OuterClothes, Headset, and Card."), Editable()]
|
||||
public string Slots { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool AccessibleWhenAlive { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "What are the odds that this inventory is spawned on the character?"), Editable(minValue: 0f, maxValue: 1.0f)]
|
||||
public float Commonness { get; private set; }
|
||||
|
||||
public List<InventoryItem> Items { get; private set; } = new List<InventoryItem>();
|
||||
|
||||
public InventoryParams(XElement element, CharacterParams character) : base(element, character)
|
||||
{
|
||||
foreach (var itemElement in element.GetChildElements("item"))
|
||||
{
|
||||
var item = new InventoryItem(itemElement, character);
|
||||
SubParams.Add(item);
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddItem(string identifier = null)
|
||||
{
|
||||
identifier = identifier ?? "";
|
||||
var element = new XElement("item", new XAttribute("identifier", identifier));
|
||||
Element.Add(element);
|
||||
var item = new InventoryItem(element, Character);
|
||||
SubParams.Add(item);
|
||||
Items.Add(item);
|
||||
}
|
||||
|
||||
public bool RemoveItem(InventoryItem item) => RemoveSubParam(item, Items);
|
||||
}
|
||||
|
||||
public class AIParams : SubParam
|
||||
{
|
||||
public override string Name => "AI";
|
||||
|
||||
[Serialize(1.0f, true, description: "How strong other characters think this character is? Only affects AI."), Editable()]
|
||||
public float CombatStrength { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "Affects how far the character can see the targets. Used as a multiplier."), Editable(minValue: 0f, maxValue: 10f)]
|
||||
public float Sight { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "Affects how far the character can hear the targets. Used as a multiplier."), Editable(minValue: 0f, maxValue: 10f)]
|
||||
public float Hearing { get; private set; }
|
||||
|
||||
[Serialize(100f, true, description: "How much the target priority increase when the character takes damage? Additive."), Editable(minValue: -1000f, maxValue: 1000f)]
|
||||
public float AggressionHurt { get; private set; }
|
||||
|
||||
[Serialize(10f, true, description: "How much the target priority increase when the character takes damage? Additive."), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
public float AggressionGreed { get; private set; }
|
||||
|
||||
[Serialize(0f, true, description: "If the health drops below this threshold, the character flees. In percentages."), Editable(minValue: 0f, maxValue: 100f)]
|
||||
public float FleeHealthThreshold { get; private set; }
|
||||
|
||||
[Serialize(false, true, description: "Does the character attack ONLY when provoked?"), Editable()]
|
||||
public bool AttackOnlyWhenProvoked { get; private set; }
|
||||
|
||||
[Serialize(true, true, description: "When true, the character retaliates quickly when it's taking damage. Enabled by default."), Editable]
|
||||
public bool RetaliateWhenTakingDamage { get; private set; }
|
||||
|
||||
[Serialize(false, true, description: "Does the character try to break inside the sub?"), Editable()]
|
||||
public bool AggressiveBoarding { get; private set; }
|
||||
|
||||
// TODO: latchonto, swarming
|
||||
|
||||
public IEnumerable<TargetParams> Targets => targets;
|
||||
protected readonly List<TargetParams> targets = new List<TargetParams>();
|
||||
|
||||
public AIParams(XElement element, CharacterParams character) : base(element, character)
|
||||
{
|
||||
element.GetChildElements("target").ForEach(t => TryAddTarget(t, out _));
|
||||
element.GetChildElements("targetpriority").ForEach(t => TryAddTarget(t, out _));
|
||||
}
|
||||
|
||||
private bool TryAddTarget(XElement targetElement, out TargetParams target)
|
||||
{
|
||||
string tag = targetElement.GetAttributeString("tag", null);
|
||||
if (!CheckTag(tag))
|
||||
{
|
||||
target = null;
|
||||
DebugConsole.ThrowError($"Multiple targets with the same tag ('{tag}') defined! Only the first will be used!");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
target = new TargetParams(targetElement, Character);
|
||||
targets.Add(target);
|
||||
SubParams.Add(target);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryAddEmptyTarget(out TargetParams targetParams) => TryAddNewTarget("newtarget" + targets.Count, AIState.Attack, 0f, out targetParams);
|
||||
|
||||
public bool TryAddNewTarget(string tag, AIState state, float priority, out TargetParams targetParams)
|
||||
{
|
||||
var element = TargetParams.CreateNewElement(tag, state, priority);
|
||||
if (TryAddTarget(element, out targetParams))
|
||||
{
|
||||
Element.Add(element);
|
||||
}
|
||||
return targetParams != null;
|
||||
}
|
||||
|
||||
private bool CheckTag(string tag)
|
||||
{
|
||||
if (tag == null) { return false; }
|
||||
tag = tag.ToLowerInvariant();
|
||||
return targets.None(t => t.Tag == tag);
|
||||
}
|
||||
|
||||
public bool RemoveTarget(TargetParams target) => RemoveSubParam(target, targets);
|
||||
|
||||
public bool TryGetTarget(string targetTag, out TargetParams target)
|
||||
{
|
||||
target = targets.FirstOrDefault(t => t.Tag == targetTag);
|
||||
return target != null;
|
||||
}
|
||||
|
||||
public TargetParams GetTarget(string targetTag, bool throwError = true)
|
||||
{
|
||||
if (!TryGetTarget(targetTag, out TargetParams target))
|
||||
{
|
||||
if (throwError)
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a target with the tag {targetTag}!");
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
public class TargetParams : SubParam
|
||||
{
|
||||
public override string Name => "Target";
|
||||
|
||||
[Serialize("", true, description: "Can be an item tag, species name or something else. Examples: decoy, provocative, light, dead, human, crawler, wall, nasonov, sonar, door, stronger, weaker, light, human, room..."), Editable()]
|
||||
public string Tag { get; private set; }
|
||||
|
||||
[Serialize(AIState.Idle, true), Editable]
|
||||
public AIState State { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "What base priority is given to the target?"), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
public float Priority { get; set; }
|
||||
|
||||
public TargetParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
|
||||
public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { }
|
||||
|
||||
public static XElement CreateNewElement(string tag, AIState state, float priority)
|
||||
{
|
||||
return new XElement("target",
|
||||
new XAttribute("tag", tag),
|
||||
new XAttribute("state", state),
|
||||
new XAttribute("priority", priority));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SubParam : ISerializableEntity
|
||||
{
|
||||
public virtual string Name { get; set; }
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
public XElement Element { get; set; }
|
||||
public List<SubParam> SubParams { get; set; } = new List<SubParam>();
|
||||
|
||||
public CharacterParams Character { get; private set; }
|
||||
|
||||
public SubParam(XElement element, CharacterParams character)
|
||||
{
|
||||
Element = element;
|
||||
Character = character;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public virtual bool Deserialize(bool recursive = true)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, Element);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.Deserialize(true));
|
||||
}
|
||||
return SerializableProperties != null;
|
||||
}
|
||||
|
||||
public virtual bool Serialize(bool recursive = true)
|
||||
{
|
||||
SerializableProperty.SerializeProperties(this, Element, true);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.Serialize(true));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
// Don't use recursion, because the reset method might be overriden
|
||||
Deserialize(false);
|
||||
SubParams.ForEach(sp => sp.Reset());
|
||||
}
|
||||
|
||||
protected bool RemoveSubParam<T>(T subParam, IList<T> collection = null) where T : SubParam
|
||||
{
|
||||
if (subParam == null || subParam.Element == null || subParam.Element.Parent == null) { return false; }
|
||||
if (collection != null && !collection.Contains(subParam)) { return false; }
|
||||
if (!SubParams.Contains(subParam)) { return false; }
|
||||
collection?.Remove(subParam);
|
||||
SubParams.Remove(subParam);
|
||||
subParam.Element.Remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public SerializableEntityEditor SerializableEntityEditor { get; protected set; }
|
||||
public virtual void AddToEditor(ParamsEditor editor, bool recursive = true, int space = 0, ScalableFont titleFont = null)
|
||||
{
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, inGame: false, showName: true, titleFont: titleFont ?? GUI.LargeFont);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.AddToEditor(editor, true, titleFont: titleFont ?? GUI.SmallFont));
|
||||
}
|
||||
if (space > 0)
|
||||
{
|
||||
new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: new Color(20, 20, 20, 255))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+14
-12
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -32,9 +33,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public XElement MainElement => doc.Root;
|
||||
public virtual XElement MainElement => doc.Root;
|
||||
public XElement OriginalElement { get; protected set; }
|
||||
|
||||
protected virtual string GetName() => Path.GetFileNameWithoutExtension(FullPath).FormatCamelCaseWithSpaces();
|
||||
|
||||
protected virtual bool Deserialize(XElement element = null)
|
||||
{
|
||||
element = element ?? MainElement;
|
||||
@@ -67,7 +70,7 @@ namespace Barotrauma
|
||||
protected virtual void UpdatePath(string fullPath)
|
||||
{
|
||||
FullPath = fullPath;
|
||||
Name = Path.GetFileNameWithoutExtension(FullPath);
|
||||
Name = GetName();
|
||||
FileName = Path.GetFileName(FullPath);
|
||||
Folder = Path.GetDirectoryName(FullPath);
|
||||
}
|
||||
@@ -112,23 +115,22 @@ namespace Barotrauma
|
||||
|
||||
#if CLIENT
|
||||
public SerializableEntityEditor SerializableEntityEditor { get; protected set; }
|
||||
public virtual void AddToEditor(ParamsEditor editor)
|
||||
public virtual void AddToEditor(ParamsEditor editor, int space = 0)
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
DebugConsole.ThrowError("[Params] Not loaded!");
|
||||
return;
|
||||
}
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, false, true);
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, false, true, titleFont: GUI.LargeFont);
|
||||
if (space > 0)
|
||||
{
|
||||
new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: ParamsEditor.Color)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#region Memento
|
||||
public readonly Memento<EditableParams> memento = new Memento<EditableParams>();
|
||||
public abstract void CreateSnapshot();
|
||||
public abstract void Undo();
|
||||
public abstract void Redo();
|
||||
public void ClearHistory() => memento.Clear();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -34,10 +35,11 @@ namespace Barotrauma
|
||||
Decals,
|
||||
NPCConversations,
|
||||
Afflictions,
|
||||
Buffs,
|
||||
Tutorials,
|
||||
UIStyle,
|
||||
TraitorMissions
|
||||
TraitorMissions,
|
||||
EventManagerSettings,
|
||||
Orders
|
||||
}
|
||||
|
||||
public class ContentPackage
|
||||
@@ -61,7 +63,8 @@ namespace Barotrauma
|
||||
ContentType.LevelObjectPrefabs,
|
||||
ContentType.RuinConfig,
|
||||
ContentType.Outpost,
|
||||
ContentType.Afflictions
|
||||
ContentType.Afflictions,
|
||||
ContentType.Orders
|
||||
};
|
||||
|
||||
//at least one file of each these types is required in core content packages
|
||||
@@ -80,12 +83,11 @@ namespace Barotrauma
|
||||
ContentType.LevelGenerationParameters,
|
||||
ContentType.RandomEvents,
|
||||
ContentType.Missions,
|
||||
ContentType.TraitorMissions,
|
||||
ContentType.BackgroundCreaturePrefabs,
|
||||
ContentType.RuinConfig,
|
||||
ContentType.NPCConversations,
|
||||
ContentType.Afflictions,
|
||||
ContentType.UIStyle
|
||||
ContentType.UIStyle,
|
||||
ContentType.EventManagerSettings,
|
||||
ContentType.Orders
|
||||
};
|
||||
|
||||
public static IEnumerable<ContentType> CorePackageRequiredFiles
|
||||
@@ -175,7 +177,7 @@ namespace Barotrauma
|
||||
if (!Enum.TryParse(subElement.Name.ToString(), true, out ContentType type))
|
||||
{
|
||||
errorMsgs.Add("Error in content package \"" + Name + "\" - \"" + subElement.Name.ToString() + "\" is not a valid content type.");
|
||||
type = ContentType.None;
|
||||
type = ContentType.None;
|
||||
}
|
||||
Files.Add(new ContentFile(subElement.GetAttributeString("file", ""), type));
|
||||
}
|
||||
@@ -191,6 +193,29 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool? invalid;
|
||||
public bool Invalid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!invalid.HasValue)
|
||||
{
|
||||
invalid = !CheckValidity(out _);
|
||||
}
|
||||
return invalid.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> errorMessages;
|
||||
public IEnumerable<string> ErrorMessages
|
||||
{
|
||||
get
|
||||
{
|
||||
if (errorMessages == null) { CheckValidity(out _); }
|
||||
return errorMessages;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
@@ -233,6 +258,58 @@ namespace Barotrauma
|
||||
return missingContentTypes.Count == 0;
|
||||
}
|
||||
|
||||
public bool CheckValidity(out List<string> errorMessages)
|
||||
{
|
||||
this.errorMessages = errorMessages = new List<string>();
|
||||
foreach (ContentFile file in Files)
|
||||
{
|
||||
switch (file.Type)
|
||||
{
|
||||
case ContentType.Executable:
|
||||
case ContentType.ServerExecutable:
|
||||
case ContentType.None:
|
||||
case ContentType.Outpost:
|
||||
case ContentType.Submarine:
|
||||
break;
|
||||
default:
|
||||
try
|
||||
{
|
||||
XDocument.Load(file.Path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (TextManager.Initialized)
|
||||
{
|
||||
errorMessages.Add(TextManager.GetWithVariables("xmlfileinvalid",
|
||||
new string[] { "[filepath]", "[errormessage]" },
|
||||
new string[] { file.Path, e.Message }));
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessages.Add($"XML File Invalid. PATH: {file.Path}, ERROR: {e.Message}");
|
||||
#if DEBUG
|
||||
throw e;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (CorePackage && !ContainsRequiredCorePackageFiles(out List<ContentType> missingContentTypes))
|
||||
{
|
||||
errorMessages.Add(TextManager.GetWithVariables("ContentPackageCantMakeCorePackage",
|
||||
new string[2] { "[packagename]", "[missingfiletypes]" },
|
||||
new string[2] { Name, string.Join(", ", missingContentTypes) },
|
||||
new bool[2] { false, true }));
|
||||
}
|
||||
VerifyFiles(out List<string> missingFileMessages);
|
||||
|
||||
errorMessages.AddRange(missingFileMessages);
|
||||
invalid = errorMessages.Count > 0;
|
||||
return !invalid.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure all the files defined in the content package are present
|
||||
/// </summary>
|
||||
@@ -246,7 +323,6 @@ namespace Barotrauma
|
||||
//dedicated server doesn't care if the client executable is present or not
|
||||
if (file.Type == ContentType.Executable) { continue; }
|
||||
#endif
|
||||
|
||||
if (!File.Exists(file.Path))
|
||||
{
|
||||
errorMessages.Add("File \"" + file.Path + "\" not found.");
|
||||
@@ -368,12 +444,18 @@ namespace Barotrauma
|
||||
{
|
||||
case ContentType.Character:
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
string speciesName = doc.Root.GetAttributeString("name", "");
|
||||
//TODO: check non-default paths if defined
|
||||
filePaths.Add(RagdollParams.GetDefaultFile(speciesName, this));
|
||||
foreach (AnimationType animationType in Enum.GetValues(typeof(AnimationType)))
|
||||
var rootElement = doc.Root;
|
||||
var element = rootElement.IsOverride() ? rootElement.FirstElement() : rootElement;
|
||||
var speciesName = element.GetAttributeString("speciesname", element.GetAttributeString("name", ""));
|
||||
var ragdollFolder = RagdollParams.GetFolder(speciesName);
|
||||
if (Directory.Exists(ragdollFolder))
|
||||
{
|
||||
filePaths.Add(AnimationParams.GetDefaultFile(speciesName, animationType, this));
|
||||
Directory.GetFiles(ragdollFolder, "*.xml").ForEach(f => filePaths.Add(f));
|
||||
}
|
||||
var animationFolder = AnimationParams.GetFolder(speciesName);
|
||||
if (Directory.Exists(animationFolder))
|
||||
{
|
||||
Directory.GetFiles(animationFolder, "*.xml").ForEach(f => filePaths.Add(f));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -445,7 +527,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all xml files.
|
||||
/// Returns all xml files from all the loaded content packages.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> GetAllContentFiles(IEnumerable<ContentPackage> contentPackages)
|
||||
{
|
||||
@@ -478,12 +560,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
string[] files = Directory.GetFiles(folder, "*.xml");
|
||||
|
||||
List.Clear();
|
||||
|
||||
string[] files = Directory.GetFiles(folder, "*.xml");
|
||||
foreach (string filePath in files)
|
||||
{
|
||||
List.Add(new ContentPackage(filePath));
|
||||
List.Add(new ContentPackage(filePath));
|
||||
}
|
||||
|
||||
string[] modDirectories = Directory.GetDirectories("Mods");
|
||||
@@ -498,12 +581,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static void SortContentPackages()
|
||||
{
|
||||
List = List
|
||||
.OrderByDescending(p => p.CorePackage)
|
||||
.ThenByDescending(p => GameMain.Config?.SelectedContentPackages.Contains(p))
|
||||
.ThenBy(p => GameMain.Config?.SelectedContentPackages.IndexOf(p))
|
||||
.ToList();
|
||||
|
||||
if (GameMain.Config != null)
|
||||
{
|
||||
var reportList = List.Where(p => GameMain.Config.SelectedContentPackages.Contains(p));
|
||||
DebugConsole.NewMessage($"Content package load order: { new string(reportList.SelectMany(cp => cp.Name + " | ").ToArray()) }");
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Path);
|
||||
GameMain.Config.SelectedContentPackages.Remove(this);
|
||||
GameMain.Config.DeselectContentPackage(this);
|
||||
GameMain.Config.SaveNewPlayerConfig();
|
||||
}
|
||||
catch (IOException e)
|
||||
@@ -512,6 +610,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
List.Remove(this);
|
||||
SortContentPackages();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,6 +195,9 @@ namespace Barotrauma
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
#if CLIENT && WINDOWS
|
||||
if (e is SharpDX.SharpDXException) { throw; }
|
||||
#endif
|
||||
DebugConsole.ThrowError("Coroutine " + handle.Name + " threw an exception: " + e.Message + "\n" + e.StackTrace.ToString());
|
||||
handle.Exception = e;
|
||||
return true;
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace Barotrauma
|
||||
characterFiles[i] = Path.GetFileNameWithoutExtension(characterFiles[i]).ToLowerInvariant();
|
||||
}
|
||||
|
||||
foreach (JobPrefab jobPrefab in JobPrefab.List)
|
||||
foreach (JobPrefab jobPrefab in JobPrefab.List.Values)
|
||||
{
|
||||
characterFiles.Add(jobPrefab.Name);
|
||||
}
|
||||
@@ -618,7 +618,7 @@ namespace Barotrauma
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -626,6 +626,9 @@ namespace Barotrauma
|
||||
{
|
||||
Character.Controlled = null;
|
||||
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
#if CLIENT
|
||||
GameMain.Client?.SendConsoleCommand("freecam");
|
||||
#endif
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("eventmanager", "eventmanager: Toggle event manager on/off. No new random events are created when the event manager is disabled.", (string[] args) =>
|
||||
@@ -965,7 +968,6 @@ namespace Barotrauma
|
||||
}));
|
||||
|
||||
#if DEBUG
|
||||
/*TODO: reimplement
|
||||
commands.Add(new Command("simulatedlatency", "simulatedlatency [minimumlatencyseconds] [randomlatencyseconds]: applies a simulated latency to network messages. Useful for simulating real network conditions when testing the multiplayer locally.", (string[] args) =>
|
||||
{
|
||||
if (args.Count() < 2 || (GameMain.NetworkMember == null)) return;
|
||||
@@ -982,18 +984,19 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.Client.NetPeerConfiguration.SimulatedMinimumLatency = minimumLatency;
|
||||
GameMain.Client.NetPeerConfiguration.SimulatedRandomLatency = randomLatency;
|
||||
GameMain.Client.SimulatedMinimumLatency = minimumLatency;
|
||||
GameMain.Client.SimulatedRandomLatency = randomLatency;
|
||||
}
|
||||
#elif SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.NetPeerConfiguration.SimulatedMinimumLatency = minimumLatency;
|
||||
GameMain.Server.NetPeerConfiguration.SimulatedRandomLatency = randomLatency;
|
||||
GameMain.Server.SimulatedMinimumLatency = minimumLatency;
|
||||
GameMain.Server.SimulatedRandomLatency = randomLatency;
|
||||
}
|
||||
#endif
|
||||
NewMessage("Set simulated minimum latency to " + minimumLatency + " and random latency to " + randomLatency + ".", Color.White);
|
||||
}));
|
||||
|
||||
commands.Add(new Command("simulatedloss", "simulatedloss [lossratio]: applies simulated packet loss to network messages. For example, a value of 0.1 would mean 10% of the packets are dropped. Useful for simulating real network conditions when testing the multiplayer locally.", (string[] args) =>
|
||||
{
|
||||
if (args.Count() < 1 || (GameMain.NetworkMember == null)) return;
|
||||
@@ -1005,12 +1008,12 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.Client.NetPeerConfiguration.SimulatedLoss = loss;
|
||||
GameMain.Client.SimulatedLoss = loss;
|
||||
}
|
||||
#elif SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.NetPeerConfiguration.SimulatedLoss = loss;
|
||||
GameMain.Server.SimulatedLoss = loss;
|
||||
}
|
||||
#endif
|
||||
NewMessage("Set simulated packet loss to " + (int)(loss * 100) + "%.", Color.White);
|
||||
@@ -1026,16 +1029,16 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.Client.NetPeerConfiguration.SimulatedDuplicatesChance = duplicates;
|
||||
GameMain.Client.SimulatedDuplicatesChance = duplicates;
|
||||
}
|
||||
#elif SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.NetPeerConfiguration.SimulatedDuplicatesChance = duplicates;
|
||||
GameMain.Server.SimulatedDuplicatesChance = duplicates;
|
||||
}
|
||||
#endif
|
||||
NewMessage("Set packet duplication to " + (int)(duplicates * 100) + "%.", Color.White);
|
||||
}));*/
|
||||
}));
|
||||
#endif
|
||||
|
||||
//"dummy commands" that only exist so that the server can give clients permissions to use them
|
||||
@@ -1338,9 +1341,12 @@ namespace Barotrauma
|
||||
WayPoint spawnPoint = null;
|
||||
|
||||
string characterLowerCase = args[0].ToLowerInvariant();
|
||||
JobPrefab job = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == characterLowerCase || jp.Identifier.ToLowerInvariant() == characterLowerCase);
|
||||
bool human = job != null || characterLowerCase == "human";
|
||||
|
||||
if (!JobPrefab.List.TryGetValue(characterLowerCase, out JobPrefab job))
|
||||
{
|
||||
job = JobPrefab.List.Values.FirstOrDefault(jp => jp.Name?.ToLowerInvariant() == characterLowerCase);
|
||||
}
|
||||
bool human = job != null || characterLowerCase == Character.HumanSpeciesName;
|
||||
|
||||
if (args.Length > 1)
|
||||
{
|
||||
switch (args[1].ToLowerInvariant())
|
||||
@@ -1389,7 +1395,7 @@ namespace Barotrauma
|
||||
|
||||
if (human)
|
||||
{
|
||||
CharacterInfo characterInfo = new CharacterInfo(Character.HumanConfigFile, jobPrefab: job);
|
||||
CharacterInfo characterInfo = new CharacterInfo(Character.HumanSpeciesName, jobPrefab: job);
|
||||
spawnedCharacter = Character.Create(characterInfo, spawnPosition, ToolBox.RandomSeed(8));
|
||||
if (job != null)
|
||||
{
|
||||
@@ -1410,23 +1416,10 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<string> characterFiles = GameMain.Instance.GetFilesOfType(ContentType.Character);
|
||||
foreach (string characterFile in characterFiles)
|
||||
if (Character.GetConfigFilePath(args[0]) != null)
|
||||
{
|
||||
if (Path.GetFileNameWithoutExtension(characterFile).ToLowerInvariant() == args[0].ToLowerInvariant())
|
||||
{
|
||||
Character.Create(characterFile, spawnPosition, ToolBox.RandomSeed(8));
|
||||
return;
|
||||
}
|
||||
Character.Create(args[0], spawnPosition, ToolBox.RandomSeed(8));
|
||||
}
|
||||
|
||||
errorMsg = "No character matching the name \"" + args[0] + "\" found in the selected content package.";
|
||||
|
||||
//attempt to open the config from the default path (the file may still be present even if it isn't included in the content package)
|
||||
string configPath = "Content/Characters/"
|
||||
+ args[0].First().ToString().ToUpper() + args[0].Substring(1)
|
||||
+ "/" + args[0].ToLower() + ".xml";
|
||||
Character.Create(configPath, spawnPosition, ToolBox.RandomSeed(8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1437,7 +1430,23 @@ namespace Barotrauma
|
||||
|
||||
Vector2? spawnPos = null;
|
||||
Inventory spawnInventory = null;
|
||||
|
||||
|
||||
string itemName = args[0].ToLowerInvariant();
|
||||
if (!(MapEntityPrefab.Find(itemName, showErrorMessages: false) is ItemPrefab itemPrefab))
|
||||
{
|
||||
errorMsg = "Item \"" + itemName + "\" not found!";
|
||||
var matching = MapEntityPrefab.List.Find(me => me.Name.ToLowerInvariant().StartsWith(itemName) && me is ItemPrefab);
|
||||
if (matching != null)
|
||||
{
|
||||
errorMsg += $" Did you mean \"{matching.Name}\"?";
|
||||
if (matching.Name.Contains(" "))
|
||||
{
|
||||
errorMsg += $" Please note that you should surround multi-word names with quotation marks (e.q. spawnitem \"{matching.Name}\")";
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 1)
|
||||
{
|
||||
switch (args.Last())
|
||||
@@ -1461,14 +1470,7 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string itemName = args[0].ToLowerInvariant();
|
||||
if (!(MapEntityPrefab.Find(itemName) is ItemPrefab itemPrefab))
|
||||
{
|
||||
errorMsg = "Item \"" + itemName + "\" not found!";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ((spawnPos == null || spawnPos == Vector2.Zero) && spawnInventory == null)
|
||||
{
|
||||
var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
|
||||
@@ -1550,30 +1552,36 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
int parsedNum = 0;
|
||||
if (!int.TryParse(currNum, out parsedNum))
|
||||
if (!int.TryParse(currNum, out int parsedNum) || parsedNum < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (c)
|
||||
try
|
||||
{
|
||||
case 'd':
|
||||
timeSpan += new TimeSpan(parsedNum, 0, 0, 0, 0);
|
||||
break;
|
||||
case 'h':
|
||||
timeSpan += new TimeSpan(0, parsedNum, 0, 0, 0);
|
||||
break;
|
||||
case 'm':
|
||||
timeSpan += new TimeSpan(0, 0, parsedNum, 0, 0);
|
||||
break;
|
||||
case 's':
|
||||
timeSpan += new TimeSpan(0, 0, 0, parsedNum, 0);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
switch (c)
|
||||
{
|
||||
case 'd':
|
||||
timeSpan += new TimeSpan(parsedNum, 0, 0, 0, 0);
|
||||
break;
|
||||
case 'h':
|
||||
timeSpan += new TimeSpan(0, parsedNum, 0, 0, 0);
|
||||
break;
|
||||
case 'm':
|
||||
timeSpan += new TimeSpan(0, 0, parsedNum, 0, 0);
|
||||
break;
|
||||
case 's':
|
||||
timeSpan += new TimeSpan(0, 0, 0, parsedNum, 0);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
ThrowError($"{parsedNum} {c} exceeds the maximum supported time span. Using the maximum time span {TimeSpan.MaxValue} instead.");
|
||||
timeSpan = TimeSpan.MaxValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
currNum = "";
|
||||
}
|
||||
}
|
||||
@@ -1598,13 +1606,17 @@ namespace Barotrauma
|
||||
if (e != null)
|
||||
{
|
||||
error += " {" + e.Message + "}\n" + e.StackTrace;
|
||||
if (e.InnerException != null)
|
||||
{
|
||||
error += "\n\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace;
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.WriteLine(error);
|
||||
NewMessage(error, Color.Red);
|
||||
#if CLIENT
|
||||
if (createMessageBox)
|
||||
{
|
||||
new GUIMessageBox(TextManager.Get("Error"), error);
|
||||
CoroutineManager.StartCoroutine(CreateMessageBox(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1612,7 +1624,20 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if CLIENT
|
||||
private static IEnumerable<object> CreateMessageBox(string errorMsg)
|
||||
{
|
||||
while (GUI.Style == null)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
new GUIMessageBox(TextManager.Get("Error"), errorMsg);
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
#endif
|
||||
|
||||
public static void SaveLogs()
|
||||
{
|
||||
if (unsavedMessages.Count == 0) return;
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Barotrauma
|
||||
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) continue;
|
||||
if (itemContainer.Combine(item)) break; // Placement successful
|
||||
if (itemContainer.Combine(item, user: null)) break; // Placement successful
|
||||
}
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Barotrauma
|
||||
{
|
||||
const float IntensityUpdateInterval = 5.0f;
|
||||
|
||||
private List<ScriptedEvent> events;
|
||||
private readonly List<ScriptedEvent> events;
|
||||
|
||||
private Level level;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Barotrauma
|
||||
|
||||
private float roundDuration;
|
||||
|
||||
private List<ScriptedEventSet> selectedEventSets;
|
||||
private readonly List<ScriptedEventSet> selectedEventSets;
|
||||
|
||||
private EventManagerSettings settings;
|
||||
|
||||
@@ -168,14 +168,17 @@ namespace Barotrauma
|
||||
if (eventSet.EventPrefabs.Count > 0)
|
||||
{
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
var newEvent = eventSet.EventPrefabs[rand.NextInt32() % eventSet.EventPrefabs.Count].CreateInstance();
|
||||
newEvent.Init(true);
|
||||
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
||||
events.Add(newEvent);
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventSet.EventPrefabs, eventSet.EventPrefabs.Select(e => e.Commonness).ToList(), rand);
|
||||
if (eventPrefab != null)
|
||||
{
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
newEvent.Init(true);
|
||||
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
||||
events.Add(newEvent);
|
||||
}
|
||||
}
|
||||
if (eventSet.ChildSets.Count > 0)
|
||||
{
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
|
||||
if (newEventSet != null) selectedEventSets.Add(newEventSet);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -28,17 +28,42 @@ namespace Barotrauma
|
||||
|
||||
static EventManagerSettings()
|
||||
{
|
||||
Load(Path.Combine("Content", "EventManagerSettings.xml"));
|
||||
foreach (string file in GameMain.Instance.GetFilesOfType(ContentType.EventManagerSettings))
|
||||
{
|
||||
Load(file);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Load(string file)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
if (doc == null) { return; }
|
||||
var mainElement = doc.Root;
|
||||
bool allowOverriding = false;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
List.Add(new EventManagerSettings(subElement));
|
||||
mainElement = doc.Root.FirstElement();
|
||||
allowOverriding = true;
|
||||
}
|
||||
foreach (XElement subElement in mainElement.Elements())
|
||||
{
|
||||
var element = subElement.IsOverride() ? subElement.FirstElement() : subElement;
|
||||
string name = element.Name.ToString();
|
||||
var duplicate = List.FirstOrDefault(e => e.Name.ToString().Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
if (duplicate != null)
|
||||
{
|
||||
if (allowOverriding || subElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the existing preset '{name}' in the event manager settings using the file '{file}'", Color.Yellow);
|
||||
List.Remove(duplicate);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{file}': Another element with the name '{name}' found! Each element must have a unique name. Use <override></override> tags if you want to override an existing preset.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
List.Add(new EventManagerSettings(element));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace Barotrauma
|
||||
|
||||
items.Add(item);
|
||||
|
||||
if (parent != null) parent.Combine(item);
|
||||
if (parent != null) parent.Combine(item, user: null);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Barotrauma
|
||||
if (Winner == Character.TeamType.None || string.IsNullOrEmpty(base.SuccessMessage)) { return ""; }
|
||||
|
||||
//disable success message for now if it hasn't been translated
|
||||
if (!TextManager.ContainsTag("MissionSuccess." + Prefab.Identifier)) { return ""; }
|
||||
if (!TextManager.ContainsTag("MissionSuccess." + Prefab.TextIdentifier)) { return ""; }
|
||||
|
||||
var loser = Winner == Character.TeamType.Team1 ?
|
||||
Character.TeamType.Team2 :
|
||||
@@ -49,9 +49,9 @@ namespace Barotrauma
|
||||
{
|
||||
descriptions = new string[]
|
||||
{
|
||||
TextManager.Get("MissionDescriptionNeutral." + prefab.Identifier, true) ?? prefab.ConfigElement.GetAttributeString("descriptionneutral", ""),
|
||||
TextManager.Get("MissionDescription1." + prefab.Identifier, true) ?? prefab.ConfigElement.GetAttributeString("description1", ""),
|
||||
TextManager.Get("MissionDescription2." + prefab.Identifier, true) ?? prefab.ConfigElement.GetAttributeString("description2", "")
|
||||
TextManager.Get("MissionDescriptionNeutral." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("descriptionneutral", ""),
|
||||
TextManager.Get("MissionDescription1." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("description1", ""),
|
||||
TextManager.Get("MissionDescription2." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("description2", "")
|
||||
};
|
||||
|
||||
for (int i = 0; i < descriptions.Length; i++)
|
||||
@@ -64,8 +64,8 @@ namespace Barotrauma
|
||||
|
||||
teamNames = new string[]
|
||||
{
|
||||
TextManager.Get("MissionTeam1." + prefab.Identifier, true) ?? prefab.ConfigElement.GetAttributeString("teamname1", "Team A"),
|
||||
TextManager.Get("MissionTeam2." + prefab.Identifier, true) ?? prefab.ConfigElement.GetAttributeString("teamname2", "Team B")
|
||||
TextManager.Get("MissionTeam1." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("teamname1", "Team A"),
|
||||
TextManager.Get("MissionTeam2." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("teamname2", "Team B")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -15,7 +16,7 @@ namespace Barotrauma
|
||||
Combat
|
||||
}
|
||||
|
||||
class MissionPrefab
|
||||
partial class MissionPrefab
|
||||
{
|
||||
public static readonly List<MissionPrefab> List = new List<MissionPrefab>();
|
||||
|
||||
@@ -27,7 +28,7 @@ namespace Barotrauma
|
||||
{ MissionType.Combat, typeof(CombatMission) },
|
||||
};
|
||||
|
||||
private ConstructorInfo constructor;
|
||||
private readonly ConstructorInfo constructor;
|
||||
|
||||
public readonly MissionType type;
|
||||
|
||||
@@ -35,6 +36,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
public readonly string TextIdentifier;
|
||||
|
||||
public readonly string Name;
|
||||
public readonly string Description;
|
||||
public readonly string SuccessMessage;
|
||||
@@ -61,10 +64,34 @@ namespace Barotrauma
|
||||
foreach (string file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc?.Root == null) continue;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
if (doc == null) { continue; }
|
||||
bool allowOverride = false;
|
||||
var mainElement = doc.Root;
|
||||
if (mainElement.IsOverride())
|
||||
{
|
||||
allowOverride = true;
|
||||
mainElement = mainElement.FirstElement();
|
||||
}
|
||||
|
||||
foreach (XElement sourceElement in mainElement.Elements())
|
||||
{
|
||||
var element = sourceElement.IsOverride() ? sourceElement.FirstElement() : sourceElement;
|
||||
var identifier = element.GetAttributeString("identifier", string.Empty);
|
||||
var duplicate = List.Find(m => m.Identifier == identifier);
|
||||
if (duplicate != null)
|
||||
{
|
||||
if (allowOverride || sourceElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding a mission with the identifier '{identifier}' using the file '{file}'", Color.Yellow);
|
||||
List.Remove(duplicate);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate mission found with the identifier '{identifier}' in file '{file}'! Add <override></override> tags as the parent of the mission definition to allow overriding.");
|
||||
// TODO: Don't allow adding duplicates when the issue with multiple missions is solved.
|
||||
//continue;
|
||||
}
|
||||
}
|
||||
List.Add(new MissionPrefab(element));
|
||||
}
|
||||
}
|
||||
@@ -75,15 +102,16 @@ namespace Barotrauma
|
||||
ConfigElement = element;
|
||||
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
TextIdentifier = element.GetAttributeString("textidentifier", null) ?? Identifier;
|
||||
|
||||
Name = TextManager.Get("MissionName." + Identifier, true) ?? element.GetAttributeString("name", "");
|
||||
Description = TextManager.Get("MissionDescription." + Identifier, true) ?? element.GetAttributeString("description", "");
|
||||
Name = TextManager.Get("MissionName." + TextIdentifier, true) ?? element.GetAttributeString("name", "");
|
||||
Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", "");
|
||||
Reward = element.GetAttributeInt("reward", 1);
|
||||
|
||||
Commonness = element.GetAttributeInt("commonness", 1);
|
||||
|
||||
SuccessMessage = TextManager.Get("MissionSuccess." + Identifier, true) ?? element.GetAttributeString("successmessage", "Mission completed successfully");
|
||||
FailureMessage = TextManager.Get("MissionFailure." + Identifier, true) ?? "";
|
||||
SuccessMessage = TextManager.Get("MissionSuccess." + TextIdentifier, true) ?? element.GetAttributeString("successmessage", "Mission completed successfully");
|
||||
FailureMessage = TextManager.Get("MissionFailure." + TextIdentifier, true) ?? "";
|
||||
if (string.IsNullOrEmpty(FailureMessage) && TextManager.ContainsTag("missionfailed"))
|
||||
{
|
||||
FailureMessage = TextManager.Get("missionfailed", returnNull: true) ?? "";
|
||||
@@ -93,7 +121,7 @@ namespace Barotrauma
|
||||
FailureMessage = element.GetAttributeString("failuremessage", "");
|
||||
}
|
||||
|
||||
SonarLabel = TextManager.Get("MissionSonarLabel." + Identifier, true) ?? element.GetAttributeString("sonarlabel", "");
|
||||
SonarLabel = TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ?? element.GetAttributeString("sonarlabel", "");
|
||||
|
||||
MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false);
|
||||
SingleplayerOnly = element.GetAttributeBool("singleplayeronly", false);
|
||||
@@ -110,8 +138,8 @@ namespace Barotrauma
|
||||
case "message":
|
||||
int index = Messages.Count;
|
||||
|
||||
Headers.Add(TextManager.Get("MissionHeader" + index + "." + Identifier, true) ?? subElement.GetAttributeString("header", ""));
|
||||
Messages.Add(TextManager.Get("MissionMessage" + index + "." + Identifier, true) ?? subElement.GetAttributeString("text", ""));
|
||||
Headers.Add(TextManager.Get("MissionHeader" + index + "." + TextIdentifier, true) ?? subElement.GetAttributeString("header", ""));
|
||||
Messages.Add(TextManager.Get("MissionMessage" + index + "." + TextIdentifier, true) ?? subElement.GetAttributeString("text", ""));
|
||||
break;
|
||||
case "locationtype":
|
||||
AllowedLocationTypes.Add(new Pair<string, string>(
|
||||
@@ -139,7 +167,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
constructor = missionClasses[type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public bool IsAllowed(Location from, Location to)
|
||||
{
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma
|
||||
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) continue;
|
||||
if (itemContainer.Combine(item)) break; // Placement successful
|
||||
if (itemContainer.Combine(item, user: null)) break; // Placement successful
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -12,6 +13,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly string MusicType;
|
||||
|
||||
public float Commonness;
|
||||
|
||||
public ScriptedEventPrefab(XElement element)
|
||||
{
|
||||
ConfigElement = element;
|
||||
@@ -30,6 +33,7 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".");
|
||||
}
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
}
|
||||
|
||||
public ScriptedEvent CreateInstance()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -85,8 +85,9 @@ namespace Barotrauma
|
||||
|
||||
public float GetCommonness(Level level)
|
||||
{
|
||||
return Commonness.ContainsKey(level.GenerationParams.Name) ?
|
||||
Commonness[level.GenerationParams.Name] : Commonness[""];
|
||||
string key = level.GenerationParams?.Name ?? "";
|
||||
return Commonness.ContainsKey(key) ?
|
||||
Commonness[key] : Commonness[""];
|
||||
}
|
||||
|
||||
public static void LoadPrefabs()
|
||||
@@ -103,12 +104,19 @@ namespace Barotrauma
|
||||
foreach (string configFile in configFiles)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configFile);
|
||||
if (doc == null) continue;
|
||||
if (doc == null) { continue; }
|
||||
|
||||
var mainElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding all random events using the file {configFile}", Color.Yellow);
|
||||
List.Clear();
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
if (element.Name.ToString().ToLowerInvariant() != "eventset") continue;
|
||||
if (element.Name.ToString().ToLowerInvariant() != "eventset") { continue; }
|
||||
List.Add(new ScriptedEventSet(element, i.ToString()));
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,18 @@ namespace Barotrauma.Extensions
|
||||
{
|
||||
public static class RectangleExtensions
|
||||
{
|
||||
public static Rectangle Multiply(this Rectangle rect, float f)
|
||||
{
|
||||
Vector2 location = new Vector2(rect.X, rect.Y) * f;
|
||||
return new Rectangle(new Point((int)location.X, (int)location.Y), rect.MultiplySize(f));
|
||||
}
|
||||
|
||||
public static Rectangle Divide(this Rectangle rect, float f)
|
||||
{
|
||||
Vector2 location = new Vector2(rect.X, rect.Y) / f;
|
||||
return new Rectangle(new Point((int)location.X, (int)location.Y), rect.DivideSize(f));
|
||||
}
|
||||
|
||||
public static Point DivideSize(this Rectangle rect, float f)
|
||||
{
|
||||
return new Point((int)(rect.Width / f), (int)(rect.Height / f));
|
||||
|
||||
@@ -23,6 +23,11 @@ namespace Barotrauma
|
||||
}
|
||||
return new string(newString.SelectMany(str => str.ToCharArray()).ToArray());
|
||||
}
|
||||
|
||||
public static string Remove(this string s, string substring)
|
||||
{
|
||||
return s.Replace(substring, string.Empty);
|
||||
}
|
||||
|
||||
public static string Remove(this string s, Func<char, bool> predicate)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Barotrauma
|
||||
public bool CheatsEnabled;
|
||||
|
||||
const int InitialMoney = 8700;
|
||||
public const int HullRepairCost = 500, ItemRepairCost = 500;
|
||||
public const int HullRepairCost = 500, ItemRepairCost = 500, ShuttleReplaceCost = 1000;
|
||||
|
||||
protected bool watchmenSpawned;
|
||||
protected Character startWatchman, endWatchman;
|
||||
@@ -21,7 +21,7 @@ namespace Barotrauma
|
||||
//key = dialog flag, double = Timing.TotalTime when the line was last said
|
||||
private Dictionary<string, double> dialogLastSpoken = new Dictionary<string, double>();
|
||||
|
||||
public bool PurchasedHullRepairs, PurchasedItemRepairs;
|
||||
public bool PurchasedHullRepairs, PurchasedLostShuttles, PurchasedItemRepairs;
|
||||
|
||||
protected Map map;
|
||||
public Map Map
|
||||
@@ -83,7 +83,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
wall.AddDamage(i, -100000.0f);
|
||||
wall.AddDamage(i, -wall.Prefab.Health);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,7 @@ namespace Barotrauma
|
||||
}
|
||||
PurchasedItemRepairs = false;
|
||||
}
|
||||
PurchasedLostShuttles = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -169,8 +170,8 @@ namespace Barotrauma
|
||||
string seed = outpost == Level.Loaded.StartOutpost ? map.SelectedLocation.Name : map.CurrentLocation.Name;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
JobPrefab watchmanJob = JobPrefab.List.Find(jp => jp.Identifier == "watchman");
|
||||
CharacterInfo characterInfo = new CharacterInfo(Character.HumanConfigFile, jobPrefab: watchmanJob);
|
||||
JobPrefab watchmanJob = JobPrefab.Get("watchman");
|
||||
CharacterInfo characterInfo = new CharacterInfo(Character.HumanSpeciesName, jobPrefab: watchmanJob);
|
||||
var spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition,
|
||||
Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end"));
|
||||
InitializeWatchman(spawnedCharacter);
|
||||
|
||||
@@ -7,48 +7,23 @@ namespace Barotrauma
|
||||
class GameModePreset
|
||||
{
|
||||
public static List<GameModePreset> List = new List<GameModePreset>();
|
||||
|
||||
public ConstructorInfo Constructor
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public readonly ConstructorInfo Constructor;
|
||||
|
||||
public string Identifier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public readonly string Name;
|
||||
public readonly string Description;
|
||||
|
||||
public bool IsSinglePlayer
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public readonly string Identifier;
|
||||
|
||||
public readonly bool IsSinglePlayer;
|
||||
|
||||
//are clients allowed to vote for this gamemode
|
||||
public bool Votable
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//TODO: translate mission descriptions
|
||||
public string Description
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public readonly bool Votable;
|
||||
|
||||
public GameModePreset(string identifier, Type type, bool isSinglePlayer = false, bool votable = true)
|
||||
{
|
||||
Name = TextManager.Get("GameMode." + identifier);
|
||||
Description = TextManager.Get("GameModeDescription." + identifier, returnNull: true) ?? "";
|
||||
Identifier = identifier;
|
||||
|
||||
Constructor = type.GetConstructor(new Type[] { typeof(GameModePreset), typeof(object) });
|
||||
@@ -70,23 +45,10 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
new GameModePreset("singleplayercampaign", typeof(SinglePlayerCampaign), true);
|
||||
new GameModePreset("tutorial", typeof(TutorialMode), true);
|
||||
new GameModePreset("devsandbox", typeof(GameMode), true)
|
||||
{
|
||||
Description = "Single player sandbox mode for debugging."
|
||||
};
|
||||
new GameModePreset("devsandbox", typeof(GameMode), true);
|
||||
#endif
|
||||
new GameModePreset("sandbox", typeof(GameMode), false)
|
||||
{
|
||||
Description = "A game mode with no specific objectives."
|
||||
};
|
||||
|
||||
new GameModePreset("mission", typeof(MissionMode), false)
|
||||
{
|
||||
Description = "The crew must work together to complete a specific task, such as retrieving "
|
||||
+ "an alien artifact or killing a creature that's terrorizing nearby outposts. The game ends "
|
||||
+ "when the task is completed or everyone in the crew has died."
|
||||
};
|
||||
|
||||
new GameModePreset("sandbox", typeof(GameMode), false);
|
||||
new GameModePreset("mission", typeof(MissionMode), false);
|
||||
new GameModePreset("multiplayercampaign", typeof(MultiPlayerCampaign), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,11 +166,11 @@ namespace Barotrauma
|
||||
|
||||
if (Submarine == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine not selected");
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine not selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (reloadSub || Submarine.MainSub != Submarine) Submarine.Load(true);
|
||||
if (reloadSub || Submarine.MainSub != Submarine) { Submarine.Load(true); }
|
||||
Submarine.MainSub = Submarine;
|
||||
if (loadSecondSub)
|
||||
{
|
||||
@@ -184,6 +184,12 @@ namespace Barotrauma
|
||||
Submarine.MainSubs[1].Load(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (Submarine.IsFileCorrupted)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine file corrupted.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (level != null)
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma
|
||||
JobPrefab job = location.Type.GetRandomHireable();
|
||||
if (job == null) { return; }
|
||||
|
||||
availableCharacters.Add(new CharacterInfo(Character.HumanConfigFile, "", job));
|
||||
availableCharacters.Add(new CharacterInfo(Character.HumanSpeciesName, "", job));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System.Xml;
|
||||
using System.IO;
|
||||
using Barotrauma.Extensions;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Tutorials;
|
||||
@@ -205,7 +206,7 @@ namespace Barotrauma
|
||||
{
|
||||
voiceChatVolume = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
GameMain.SoundManager?.SetCategoryGainMultiplier("voip", voiceChatVolume * 20.0f, 0);
|
||||
GameMain.SoundManager?.SetCategoryGainMultiplier("voip", voiceChatVolume * 30.0f, 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -215,7 +216,7 @@ namespace Barotrauma
|
||||
get { return microphoneVolume; }
|
||||
set
|
||||
{
|
||||
microphoneVolume = MathHelper.Clamp(value, 0.1f, 5.0f);
|
||||
microphoneVolume = MathHelper.Clamp(value, 0.2f, 10.0f);
|
||||
}
|
||||
}
|
||||
public string Language
|
||||
@@ -224,26 +225,45 @@ namespace Barotrauma
|
||||
set { TextManager.Language = value; }
|
||||
}
|
||||
|
||||
public readonly HashSet<ContentPackage> SelectedContentPackages = new HashSet<ContentPackage>();
|
||||
public readonly List<ContentPackage> SelectedContentPackages = new List<ContentPackage>();
|
||||
|
||||
public void SelectContentPackage(ContentPackage contentPackage)
|
||||
{
|
||||
if (!SelectedContentPackages.Contains(contentPackage))
|
||||
{
|
||||
SelectedContentPackages.Add(contentPackage);
|
||||
ContentPackage.SortContentPackages();
|
||||
}
|
||||
}
|
||||
|
||||
public void DeselectContentPackage(ContentPackage contentPackage)
|
||||
{
|
||||
if (SelectedContentPackages.Contains(contentPackage))
|
||||
{
|
||||
SelectedContentPackages.Remove(contentPackage);
|
||||
ContentPackage.SortContentPackages();
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<string> selectedContentPackagePaths = new HashSet<string>();
|
||||
|
||||
public string MasterServerUrl { get; set; }
|
||||
public string RemoteContentUrl { get; set; }
|
||||
public bool AutoCheckUpdates { get; set; }
|
||||
public bool WasGameUpdated { get; set; }
|
||||
|
||||
private string defaultPlayerName;
|
||||
public string DefaultPlayerName
|
||||
private string playerName;
|
||||
public string PlayerName
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultPlayerName ?? "";
|
||||
return string.IsNullOrWhiteSpace(playerName) ? Steam.SteamManager.GetUsername() : playerName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (defaultPlayerName != value)
|
||||
if (playerName != value)
|
||||
{
|
||||
defaultPlayerName = value;
|
||||
playerName = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,11 +441,11 @@ namespace Barotrauma
|
||||
GraphicsWidth = 1024;
|
||||
GraphicsHeight = 768;
|
||||
MasterServerUrl = "";
|
||||
SelectedContentPackages.Add(ContentPackage.List.Any() ? ContentPackage.List[0] : new ContentPackage(""));
|
||||
SelectContentPackage(ContentPackage.List.Any() ? ContentPackage.List[0] : new ContentPackage(""));
|
||||
jobPreferences = new List<string>();
|
||||
foreach (JobPrefab job in JobPrefab.List)
|
||||
foreach (string job in JobPrefab.List.Keys)
|
||||
{
|
||||
jobPreferences.Add(job.Identifier);
|
||||
jobPreferences.Add(job);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -435,6 +455,7 @@ namespace Barotrauma
|
||||
SetDefaultBindings(doc, legacy: false);
|
||||
|
||||
MasterServerUrl = doc.Root.GetAttributeString("masterserverurl", MasterServerUrl);
|
||||
RemoteContentUrl = doc.Root.GetAttributeString("remotecontenturl", RemoteContentUrl);
|
||||
WasGameUpdated = doc.Root.GetAttributeBool("wasgameupdated", WasGameUpdated);
|
||||
VerboseLogging = doc.Root.GetAttributeBool("verboselogging", VerboseLogging);
|
||||
SaveDebugConsoleLogs = doc.Root.GetAttributeBool("savedebugconsolelogs", SaveDebugConsoleLogs);
|
||||
@@ -465,6 +486,7 @@ namespace Barotrauma
|
||||
doc.Root.Add(
|
||||
new XAttribute("language", TextManager.Language),
|
||||
new XAttribute("masterserverurl", MasterServerUrl),
|
||||
new XAttribute("remotecontenturl", RemoteContentUrl),
|
||||
new XAttribute("autocheckupdates", AutoCheckUpdates),
|
||||
new XAttribute("musicvolume", musicVolume),
|
||||
new XAttribute("soundvolume", soundVolume),
|
||||
@@ -556,7 +578,7 @@ namespace Barotrauma
|
||||
doc.Root.Add(gameplay);
|
||||
|
||||
var playerElement = new XElement("player",
|
||||
new XAttribute("name", defaultPlayerName ?? ""),
|
||||
new XAttribute("name", playerName ?? ""),
|
||||
new XAttribute("headindex", CharacterHeadIndex),
|
||||
new XAttribute("gender", CharacterGender),
|
||||
new XAttribute("race", CharacterRace),
|
||||
@@ -653,6 +675,7 @@ namespace Barotrauma
|
||||
{
|
||||
var missingPackagePaths = new List<string>();
|
||||
var incompatiblePackages = new List<ContentPackage>();
|
||||
var invalidPackages = new List<ContentPackage>();
|
||||
SelectedContentPackages.Clear();
|
||||
foreach (string path in contentPackagePaths)
|
||||
{
|
||||
@@ -664,24 +687,35 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!matchingContentPackage.IsCompatible())
|
||||
{
|
||||
DebugConsole.NewMessage(
|
||||
$"Content package \"{matchingContentPackage.Name}\" is not compatible with this version of Barotrauma (game version: {GameMain.Version}, content package version: {matchingContentPackage.GameVersion})",
|
||||
Color.Red);
|
||||
incompatiblePackages.Add(matchingContentPackage);
|
||||
}
|
||||
else if (!matchingContentPackage.CheckValidity(out List<string> errorMessages))
|
||||
{
|
||||
DebugConsole.NewMessage(
|
||||
$"Content package \"{matchingContentPackage.Name}\" is invalid: " + string.Join(", ", errorMessages),
|
||||
Color.Red);
|
||||
invalidPackages.Add(matchingContentPackage);
|
||||
//never consider the vanilla content package invalid
|
||||
//(otherwise a player might brick the game by, for example, deleting vanilla content files)
|
||||
if (matchingContentPackage == GameMain.VanillaContent)
|
||||
{
|
||||
SelectedContentPackages.Add(matchingContentPackage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedContentPackages.Add(matchingContentPackage);
|
||||
}
|
||||
}
|
||||
|
||||
ContentPackage.SortContentPackages();
|
||||
TextManager.LoadTextPacks(SelectedContentPackages);
|
||||
|
||||
foreach (ContentPackage contentPackage in SelectedContentPackages)
|
||||
{
|
||||
bool packageOk = contentPackage.VerifyFiles(out List<string> errorMessages);
|
||||
if (!packageOk)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\":\n" + string.Join("\n", errorMessages));
|
||||
continue;
|
||||
}
|
||||
foreach (ContentFile file in contentPackage.Files)
|
||||
{
|
||||
ToolBox.IsProperFilenameCase(file.Path);
|
||||
@@ -691,7 +725,7 @@ namespace Barotrauma
|
||||
EnsureCoreContentPackageSelected();
|
||||
|
||||
//save to get rid of the invalid selected packages in the config file
|
||||
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
|
||||
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0 || invalidPackages.Count > 0) { SaveNewPlayerConfig(); }
|
||||
|
||||
//display error messages after all content packages have been loaded
|
||||
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
|
||||
@@ -699,10 +733,15 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError(TextManager.GetWithVariable("ContentPackageNotFound", "[packagepath]", missingPackagePath));
|
||||
}
|
||||
foreach (ContentPackage invalidPackage in invalidPackages)
|
||||
{
|
||||
DebugConsole.ThrowError(TextManager.GetWithVariable("InvalidContentPackage", "[packagename]", invalidPackage.Name), createMessageBox: true);
|
||||
}
|
||||
foreach (ContentPackage incompatiblePackage in incompatiblePackages)
|
||||
{
|
||||
DebugConsole.ThrowError(TextManager.GetWithVariables(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage",
|
||||
new string[3] { "[packagename]", "[packageversion]", "[gameversion]" }, new string[3] { incompatiblePackage.Name, incompatiblePackage.GameVersion.ToString(), GameMain.Version.ToString() }));
|
||||
new string[3] { "[packagename]", "[packageversion]", "[gameversion]" }, new string[3] { incompatiblePackage.Name, incompatiblePackage.GameVersion.ToString(), GameMain.Version.ToString() }),
|
||||
createMessageBox: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,14 +751,14 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.VanillaContent != null)
|
||||
{
|
||||
SelectedContentPackages.Add(GameMain.VanillaContent);
|
||||
SelectContentPackage(GameMain.VanillaContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
|
||||
if (availablePackage != null)
|
||||
{
|
||||
SelectedContentPackages.Add(availablePackage);
|
||||
SelectContentPackage(availablePackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -837,7 +876,9 @@ namespace Barotrauma
|
||||
doc.Root.Add(keyMappingElement);
|
||||
for (int i = 0; i < keyMapping.Length; i++)
|
||||
{
|
||||
if (keyMapping[i].MouseButton == null)
|
||||
var key = keyMapping[i];
|
||||
if (key == null) { continue; }
|
||||
if (key.MouseButton == null)
|
||||
{
|
||||
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].Key));
|
||||
}
|
||||
@@ -857,7 +898,7 @@ namespace Barotrauma
|
||||
doc.Root.Add(gameplay);
|
||||
|
||||
var playerElement = new XElement("player",
|
||||
new XAttribute("name", defaultPlayerName ?? ""),
|
||||
new XAttribute("name", playerName ?? ""),
|
||||
new XAttribute("headindex", CharacterHeadIndex),
|
||||
new XAttribute("gender", CharacterGender),
|
||||
new XAttribute("race", CharacterRace),
|
||||
@@ -945,7 +986,7 @@ namespace Barotrauma
|
||||
XElement playerElement = doc.Root.Element("player");
|
||||
if (playerElement != null)
|
||||
{
|
||||
defaultPlayerName = playerElement.GetAttributeString("name", defaultPlayerName);
|
||||
playerName = playerElement.GetAttributeString("name", playerName);
|
||||
CharacterHeadIndex = playerElement.GetAttributeInt("headindex", CharacterHeadIndex);
|
||||
if (Enum.TryParse(playerElement.GetAttributeString("gender", "none"), true, out Gender g))
|
||||
{
|
||||
@@ -1041,7 +1082,7 @@ namespace Barotrauma
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "contentpackage":
|
||||
string path = System.IO.Path.GetFullPath(subElement.GetAttributeString("path", ""));
|
||||
string path = Path.GetFullPath(subElement.GetAttributeString("path", ""));
|
||||
selectedContentPackagePaths.Add(path);
|
||||
break;
|
||||
}
|
||||
@@ -1102,7 +1143,7 @@ namespace Barotrauma
|
||||
VoiceSetting = VoiceMode.Disabled;
|
||||
VoiceCaptureDevice = null;
|
||||
NoiseGateThreshold = -45;
|
||||
windowMode = WindowMode.Fullscreen;
|
||||
windowMode = WindowMode.BorderlessWindowed;
|
||||
losMode = LosMode.Transparent;
|
||||
useSteamMatchmaking = true;
|
||||
requireSteamAuthentication = true;
|
||||
@@ -1123,9 +1164,9 @@ namespace Barotrauma
|
||||
DynamicRangeCompressionEnabled = true;
|
||||
VoipAttenuationEnabled = true;
|
||||
voiceChatVolume = 0.5f;
|
||||
microphoneVolume = 1.0f;
|
||||
microphoneVolume = 5.0f;
|
||||
AutoCheckUpdates = true;
|
||||
defaultPlayerName = string.Empty;
|
||||
playerName = string.Empty;
|
||||
HUDScale = 1;
|
||||
InventoryScale = 1;
|
||||
AutoUpdateWorkshopItems = true;
|
||||
|
||||
@@ -42,17 +42,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public int DockingDir { get; private set; }
|
||||
|
||||
[Serialize("32.0,32.0", false)]
|
||||
[Serialize("32.0,32.0", false, description: "How close the docking port has to be to another port to dock.")]
|
||||
public Vector2 DistanceTolerance { get; set; }
|
||||
|
||||
[Serialize(32.0f, false)]
|
||||
[Serialize(32.0f, false, description: "How close together the docking ports are forced when docked.")]
|
||||
public float DockedDistance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, false)]
|
||||
[Serialize(true, false, description: "Is the port horizontal.")]
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get;
|
||||
@@ -189,9 +189,7 @@ namespace Barotrauma.Items.Components
|
||||
GameMain.GameScreen.Cam.Shake = Vector2.Distance(DockingTarget.item.Submarine.Velocity, item.Submarine.Velocity);
|
||||
}
|
||||
|
||||
DockingDir = IsHorizontal ?
|
||||
Math.Sign(DockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
|
||||
Math.Sign(DockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
|
||||
DockingDir = GetDir(DockingTarget);
|
||||
DockingTarget.DockingDir = -DockingDir;
|
||||
|
||||
if (door != null && DockingTarget.door != null)
|
||||
@@ -230,9 +228,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!(joint is WeldJoint))
|
||||
{
|
||||
DockingDir = IsHorizontal ?
|
||||
Math.Sign(DockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
|
||||
Math.Sign(DockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
|
||||
DockingDir = GetDir(DockingTarget);
|
||||
DockingTarget.DockingDir = -DockingDir;
|
||||
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f);
|
||||
@@ -312,7 +308,7 @@ namespace Barotrauma.Items.Components
|
||||
joint.CollideConnected = true;
|
||||
}
|
||||
|
||||
public int GetDir()
|
||||
public int GetDir(DockingPort dockingTarget = null)
|
||||
{
|
||||
if (DockingDir != 0) { return DockingDir; }
|
||||
|
||||
@@ -325,7 +321,12 @@ namespace Barotrauma.Items.Components
|
||||
Math.Sign(door.Item.WorldPosition.Y - door.LinkedGap.linkedTo[0].WorldPosition.Y);
|
||||
}
|
||||
}
|
||||
|
||||
if (dockingTarget != null)
|
||||
{
|
||||
return IsHorizontal ?
|
||||
Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
|
||||
Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
|
||||
}
|
||||
if (item.Submarine != null)
|
||||
{
|
||||
return IsHorizontal ?
|
||||
@@ -964,57 +965,5 @@ namespace Barotrauma.Items.Components
|
||||
msg.Write(hulls != null && hulls[0] != null && hulls[1] != null && gap != null);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
bool isDocked = msg.ReadBoolean();
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (hulls[i] == null) continue;
|
||||
item.linkedTo.Remove(hulls[i]);
|
||||
hulls[i].Remove();
|
||||
hulls[i] = null;
|
||||
}
|
||||
|
||||
if (gap != null)
|
||||
{
|
||||
item.linkedTo.Remove(gap);
|
||||
gap.Remove();
|
||||
gap = null;
|
||||
}
|
||||
|
||||
if (isDocked)
|
||||
{
|
||||
ushort dockingTargetID = msg.ReadUInt16();
|
||||
|
||||
bool isLocked = msg.ReadBoolean();
|
||||
|
||||
Entity targetEntity = Entity.FindEntityByID(dockingTargetID);
|
||||
if (targetEntity == null || !(targetEntity is Item))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid docking port network event (can't dock to " + targetEntity.ToString() + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
DockingTarget = (targetEntity as Item).GetComponent<DockingPort>();
|
||||
if (DockingTarget == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid docking port network event (" + targetEntity + " doesn't have a docking port component)");
|
||||
return;
|
||||
}
|
||||
|
||||
Dock(DockingTarget);
|
||||
|
||||
if (isLocked)
|
||||
{
|
||||
Lock(isNetworkMessage: true, forcePosition: true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Undock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components
|
||||
public bool CanBeWelded = true;
|
||||
|
||||
private float stuck;
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "How badly stuck the door is (in percentages). If the percentage reaches 100, the door needs to be cut open to make it usable again.")]
|
||||
public float Stuck
|
||||
{
|
||||
get { return stuck; }
|
||||
@@ -74,10 +74,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(3.0f, true), Editable]
|
||||
[Serialize(3.0f, true, description: "How quickly the door opens."), Editable]
|
||||
public float OpeningSpeed { get; private set; }
|
||||
|
||||
[Serialize(3.0f, true), Editable]
|
||||
[Serialize(3.0f, true, description: "How quickly the door closes."), Editable]
|
||||
public float ClosingSpeed { get; private set; }
|
||||
|
||||
public bool? PredictedState { get; private set; }
|
||||
@@ -121,10 +121,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool IsHorizontal { get; private set; }
|
||||
|
||||
[Serialize("0.0,0.0,0.0,0.0", false)]
|
||||
[Serialize("0.0,0.0,0.0,0.0", false, description: "Position and size of the window on the door. The upper left corner is 0,0. Set the width and height to 0 if you don't want the door to have a window.")]
|
||||
public Rectangle Window { get; set; }
|
||||
|
||||
[Editable, Serialize(false, true)]
|
||||
[Editable, Serialize(false, true, description: "Is the door currently open.")]
|
||||
public bool IsOpen
|
||||
{
|
||||
get { return isOpen; }
|
||||
@@ -135,7 +135,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "If the door has integrated buttons, it can be opened by interacting with it directly (instead of using buttons wired to it).")]
|
||||
public bool HasIntegratedButtons { get; private set; }
|
||||
|
||||
public float OpenState
|
||||
@@ -153,7 +153,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Characters and items cannot pass through impassable doors. Useful for things such as ducts that should only let water and air through.")]
|
||||
public bool Impassable
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -48,28 +48,28 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(100.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
|
||||
[Serialize(100.0f, true, description: "How far the discharge can travel from the item."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
|
||||
public float Range
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(10.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, ToolTip = "How much further can the discharge be carried when moving across walls.")]
|
||||
[Serialize(10.0f, true, description: "How much further can the discharge be carried when moving across walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
public float RangeMultiplierInWalls
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.25f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
[Serialize(0.25f, true, description: "The duration of an individual discharge (in seconds)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float Duration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, true), Editable()]
|
||||
[Serialize(false, true, "If set to true, the discharge cannot travel inside the submarine nor shock anyone inside."), Editable]
|
||||
public bool OutdoorsOnly
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Barotrauma.Items.Components
|
||||
get { return item.body ?? body; }
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
[Serialize(false, true, description: "Is the item currently attached to a wall (only valid if Attachable is set to true).")]
|
||||
public bool Attached
|
||||
{
|
||||
get { return attached && item.ParentInventory == null; }
|
||||
@@ -50,56 +50,58 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
[Serialize(true, true, description: "Can the item be pointed to a specific direction or do the characters always hold it in a static pose.")]
|
||||
public bool Aimable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Should the character adjust its pose when aiming with the item. Most noticeable underwater, where the character will rotate its entire body to face the direction the item is aimed at.")]
|
||||
public bool ControlPose
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the item be attached to walls.")]
|
||||
public bool Attachable
|
||||
{
|
||||
get { return attachable; }
|
||||
set { attachable = value; }
|
||||
}
|
||||
|
||||
[Serialize(true, false)]
|
||||
[Serialize(true, false, description: "Can the item be reattached to walls after it has been deattached (only valid if Attachable is set to true).")]
|
||||
public bool Reattachable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Should the item be attached to a wall by default when it's placed in the submarine editor.")]
|
||||
public bool AttachedByDefault
|
||||
{
|
||||
get { return attachedByDefault; }
|
||||
set { attachedByDefault = value; }
|
||||
}
|
||||
|
||||
[Serialize("0.0,0.0", false),Editable]
|
||||
[Editable, Serialize("0.0,0.0", false, description: "The position the character holds the item at (in pixels, as an offset from the character's shoulder)."+
|
||||
" For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards.")]
|
||||
public Vector2 HoldPos
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(holdPos); }
|
||||
set { holdPos = ConvertUnits.ToSimUnits(value); }
|
||||
}
|
||||
|
||||
[Serialize("0.0,0.0", false)]
|
||||
[Serialize("0.0,0.0", false, description: "The position the character holds the item at when aiming (in pixels, as an offset from the character's shoulder)."+
|
||||
" Works similarly as HoldPos, except that the position is rotated according to the direction the player is aiming at. For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards when aiming directly to the right.")]
|
||||
public Vector2 AimPos
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(aimPos); }
|
||||
set { aimPos = ConvertUnits.ToSimUnits(value); }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false), Editable]
|
||||
[Editable, Serialize(0.0f, false, description: "The rotation at which the character holds the item (in degrees, relative to the rotation of the character's hand).")]
|
||||
public float HoldAngle
|
||||
{
|
||||
get { return MathHelper.ToDegrees(holdAngle); }
|
||||
@@ -107,21 +109,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private Vector2 swingAmount;
|
||||
[Serialize("0.0,0.0", false), Editable]
|
||||
[Editable, Serialize("0.0,0.0", false, description: "How much the item swings around when aiming/holding it (in pixels, as an offset from AimPos/HoldPos).")]
|
||||
public Vector2 SwingAmount
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(swingAmount); }
|
||||
set { swingAmount = ConvertUnits.ToSimUnits(value); }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false), Editable]
|
||||
[Editable, Serialize(0.0f, false, description: "How fast the item swings around when aiming/holding it (only valid if SwingAmount is set).")]
|
||||
public float SwingSpeed { get; set; }
|
||||
|
||||
[Serialize(false, false), Editable]
|
||||
[Editable, Serialize(false, false, description: "Should the item swing around when it's being held.")]
|
||||
public bool SwingWhenHolding { get; set; }
|
||||
[Serialize(false, false), Editable]
|
||||
[Editable, Serialize(false, false, description: "Should the item swing around when it's being aimed.")]
|
||||
public bool SwingWhenAiming { get; set; }
|
||||
[Serialize(false, false), Editable]
|
||||
[Editable, Serialize(false, false, description: "Should the item swing around when it's being used (for example, when firing a weapon or a welding tool).")]
|
||||
public bool SwingWhenUsing { get; set; }
|
||||
|
||||
public Holdable(Item item, XElement element)
|
||||
@@ -189,9 +191,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement)
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
{
|
||||
base.Load(componentElement);
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
|
||||
if (usePrefabValues)
|
||||
{
|
||||
//this needs to be loaded regardless
|
||||
Attached = componentElement.GetAttributeBool("attached", attached);
|
||||
}
|
||||
|
||||
if (attachable)
|
||||
{
|
||||
prevMsg = DisplayMsg;
|
||||
@@ -221,24 +230,24 @@ namespace Barotrauma.Items.Components
|
||||
item.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
if (Pusher != null) Pusher.Enabled = false;
|
||||
if (item.body != null) item.body.Enabled = true;
|
||||
|
||||
if (Pusher != null) { Pusher.Enabled = false; }
|
||||
if (item.body != null){ item.body.Enabled = true; }
|
||||
IsActive = false;
|
||||
|
||||
if (picker == null)
|
||||
{
|
||||
if (dropper == null) return;
|
||||
if (dropper == null) { return; }
|
||||
picker = dropper;
|
||||
}
|
||||
if (picker.Inventory == null) return;
|
||||
if (picker.Inventory == null) { return; }
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
if (item.body != null)
|
||||
{
|
||||
item.body.ResetDynamics();
|
||||
Limb heldHand;
|
||||
Limb arm;
|
||||
Limb heldHand, arm;
|
||||
Vector2 diff = Vector2.Zero;
|
||||
if (picker.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
|
||||
{
|
||||
heldHand = picker.AnimController.GetLimb(LimbType.LeftHand);
|
||||
@@ -249,11 +258,18 @@ namespace Barotrauma.Items.Components
|
||||
heldHand = picker.AnimController.GetLimb(LimbType.RightHand);
|
||||
arm = picker.AnimController.GetLimb(LimbType.RightArm);
|
||||
}
|
||||
|
||||
float xDif = (heldHand.SimPosition.X - arm.SimPosition.X) / 2f;
|
||||
float yDif = (heldHand.SimPosition.Y - arm.SimPosition.Y) / 2.5f;
|
||||
//hand simPosition is actually in the wrist so need to move the item out from it slightly
|
||||
item.SetTransform(heldHand.SimPosition + new Vector2(xDif, yDif), 0.0f);
|
||||
if (heldHand != null && arm != null)
|
||||
{
|
||||
//hand simPosition is actually in the wrist so need to move the item out from it slightly
|
||||
diff = new Vector2(
|
||||
(heldHand.SimPosition.X - arm.SimPosition.X) / 2f,
|
||||
(heldHand.SimPosition.Y - arm.SimPosition.Y) / 2.5f);
|
||||
item.SetTransform(heldHand.SimPosition + diff, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.SetTransform(picker.SimPosition, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
picker.DeselectItem(item);
|
||||
|
||||
@@ -16,14 +16,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float deattachTimer;
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
[Serialize(1.0f, false, description: "How long it takes to deattach the item from the level walls (in seconds).")]
|
||||
public float DeattachDuration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "How far along the item is to being deattached. When the timer goes above DeattachDuration, the item is deattached.")]
|
||||
public float DeattachTimer
|
||||
{
|
||||
get { return deattachTimer; }
|
||||
|
||||
@@ -15,38 +15,32 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool hitting;
|
||||
|
||||
private Attack attack;
|
||||
|
||||
private float range;
|
||||
|
||||
private Character user;
|
||||
|
||||
private float reload;
|
||||
|
||||
private float reloadTimer;
|
||||
|
||||
private HashSet<Entity> hitTargets = new HashSet<Entity>();
|
||||
private readonly Attack attack;
|
||||
|
||||
public Character User
|
||||
{
|
||||
get { return user; }
|
||||
}
|
||||
private readonly HashSet<Entity> hitTargets = new HashSet<Entity>();
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public Character User { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false, description: "An estimation of how close the item has to be to the target for it to hit. Used by AI characters to determine when they're close enough to hit a target.")]
|
||||
public float Range
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(range); }
|
||||
set { range = ConvertUnits.ToSimUnits(value); }
|
||||
}
|
||||
|
||||
[Serialize(0.5f, false)]
|
||||
[Serialize(0.5f, false, description: "How long the user has to wait before they can hit with the weapon again (in seconds).")]
|
||||
public float Reload
|
||||
{
|
||||
get { return reload; }
|
||||
set { reload = Math.Max(0.0f, value); }
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the weapon hit multiple targets per swing.")]
|
||||
public bool AllowHitMultiple
|
||||
{
|
||||
get;
|
||||
@@ -85,6 +79,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (hitPos < MathHelper.PiOver4) { return false; }
|
||||
|
||||
ActivateNearbySleepingCharacters();
|
||||
reloadTimer = reload;
|
||||
|
||||
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
|
||||
@@ -162,7 +157,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
hitPos = MathUtils.WrapAnglePi(hitPos - deltaTime * 15f);
|
||||
ac.HoldItem(deltaTime, item, handlePos, new Vector2(2, 0), Vector2.Zero, false, hitPos, holdAngle + hitPos); // aimPos not used -> zero (new Vector2(-0.3f, 0.2f)), holdPos new Vector2(0.6f, -0.1f)
|
||||
if (hitPos < -MathHelper.PiOver4 * 1.2f)
|
||||
if (hitPos < -MathHelper.PiOver2)
|
||||
{
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
@@ -172,12 +167,36 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate sleeping ragdolls that are close enough to hit with the weapon (otherwise the collision will not be registered)
|
||||
/// </summary>
|
||||
private void ActivateNearbySleepingCharacters()
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!c.Enabled || !c.AnimController.BodyInRest) { continue; }
|
||||
//do a broad check first
|
||||
if (Math.Abs(c.WorldPosition.X - item.WorldPosition.X) > 1000.0f) { continue; }
|
||||
if (Math.Abs(c.WorldPosition.Y - item.WorldPosition.Y) > 1000.0f) { continue; }
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
float hitRange = 2.0f;
|
||||
if (Vector2.DistanceSquared(limb.SimPosition, item.SimPosition) < hitRange * hitRange)
|
||||
{
|
||||
c.AnimController.BodyInRest = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetUser(Character character)
|
||||
{
|
||||
if (user == character) { return; }
|
||||
if (user != null && user.Removed) { user = null; }
|
||||
if (User == character) { return; }
|
||||
if (User != null && User.Removed) { User = null; }
|
||||
|
||||
user = character;
|
||||
User = character;
|
||||
|
||||
if (item.body?.FarseerBody == null || item.Removed ||
|
||||
!GameMain.World.BodyList.Contains(item.body.FarseerBody))
|
||||
@@ -185,9 +204,9 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (user != null)
|
||||
if (User != null)
|
||||
{
|
||||
foreach (Limb limb in user.AnimController.Limbs)
|
||||
foreach (Limb limb in User.AnimController.Limbs)
|
||||
{
|
||||
if (limb.body.FarseerBody != null && GameMain.World.BodyList.Contains(limb.body.FarseerBody))
|
||||
{
|
||||
@@ -216,18 +235,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool OnCollision(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
if (user == null || user.Removed)
|
||||
if (User == null || User.Removed)
|
||||
{
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
user = null;
|
||||
User = null;
|
||||
}
|
||||
|
||||
Character targetCharacter = null;
|
||||
Limb targetLimb = null;
|
||||
Structure targetStructure = null;
|
||||
|
||||
attack?.SetUser(user);
|
||||
attack?.SetUser(User);
|
||||
|
||||
if (f2.Body.UserData is Limb)
|
||||
{
|
||||
@@ -283,16 +302,16 @@ namespace Barotrauma.Items.Components
|
||||
if (targetLimb != null)
|
||||
{
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
attack.DoDamageToLimb(user, targetLimb, item.WorldPosition, 1.0f);
|
||||
attack.DoDamageToLimb(User, targetLimb, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
targetCharacter.LastDamageSource = item;
|
||||
attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
|
||||
attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetStructure != null)
|
||||
{
|
||||
attack.DoDamage(user, targetStructure, item.WorldPosition, 1.0f);
|
||||
attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -326,7 +345,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: user);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: User);
|
||||
}
|
||||
|
||||
if (DeleteOnUse)
|
||||
|
||||
@@ -10,27 +10,22 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Propulsion : ItemComponent
|
||||
{
|
||||
enum UsableIn
|
||||
public enum UseEnvironment
|
||||
{
|
||||
Air, Water, Both
|
||||
};
|
||||
|
||||
private float force;
|
||||
|
||||
private float useState;
|
||||
|
||||
private UsableIn usableIn;
|
||||
|
||||
[Serialize(0.0f, false), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
public float Force
|
||||
{
|
||||
get { return force; }
|
||||
set { force = value; }
|
||||
}
|
||||
[Serialize(UseEnvironment.Both, false, description: "Can the item be used in air, underwater or both.")]
|
||||
public UseEnvironment UsableIn { get; set; }
|
||||
|
||||
[Serialize(0.0f, false, description: "The force to apply to the user's body."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
public float Force { get; set; }
|
||||
|
||||
#if CLIENT
|
||||
private string particles;
|
||||
[Serialize("", false)]
|
||||
[Serialize("", false, description: "The name of the particle prefab the item emits when used.")]
|
||||
public string Particles
|
||||
{
|
||||
get { return particles; }
|
||||
@@ -41,19 +36,6 @@ namespace Barotrauma.Items.Components
|
||||
public Propulsion(Item item, XElement element)
|
||||
: base(item,element)
|
||||
{
|
||||
switch (element.GetAttributeString("usablein", "both").ToLowerInvariant())
|
||||
{
|
||||
case "air":
|
||||
usableIn = UsableIn.Air;
|
||||
break;
|
||||
case "water":
|
||||
usableIn = UsableIn.Water;
|
||||
break;
|
||||
case "both":
|
||||
default:
|
||||
usableIn = UsableIn.Both;
|
||||
break;
|
||||
}
|
||||
ResetSoundRange();
|
||||
}
|
||||
|
||||
@@ -67,18 +49,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
if (usableIn == UsableIn.Air) return true;
|
||||
if (UsableIn == UseEnvironment.Air) return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (usableIn == UsableIn.Water) return true;
|
||||
if (UsableIn == UseEnvironment.Water) return true;
|
||||
}
|
||||
|
||||
Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position);
|
||||
//move upwards if the cursor is at the position of the character
|
||||
if (!MathUtils.IsValid(dir)) dir = Vector2.UnitY;
|
||||
|
||||
Vector2 propulsion = dir * force;
|
||||
Vector2 propulsion = dir * Force;
|
||||
|
||||
if (character.AnimController.InWater) character.AnimController.TargetMovement = dir;
|
||||
|
||||
|
||||
@@ -15,28 +15,28 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Vector2 barrelPos;
|
||||
|
||||
[Serialize("0.0,0.0", false)]
|
||||
[Serialize("0.0,0.0", false, description: "The position of the barrel as an offset from the item's center (in pixels). Determines where the projectiles spawn.")]
|
||||
public string BarrelPos
|
||||
{
|
||||
get { return XMLExtensions.Vector2ToString(ConvertUnits.ToDisplayUnits(barrelPos)); }
|
||||
set { barrelPos = ConvertUnits.ToSimUnits(XMLExtensions.ParseVector2(value)); }
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
[Serialize(1.0f, false, description: "How long the user has to wait before they can fire the weapon again (in seconds).")]
|
||||
public float Reload
|
||||
{
|
||||
get { return reload; }
|
||||
set { reload = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "Random spread applied to the firing angle of the projectiles when used by a character with sufficient skills to use the weapon (in degrees).")]
|
||||
public float Spread
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "Random spread applied to the firing angle of the projectiles when used by a character with insufficient skills to use the weapon (in degrees).")]
|
||||
public float UnskilledSpread
|
||||
{
|
||||
get;
|
||||
@@ -109,30 +109,21 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
projectile = item.GetComponent<Projectile>();
|
||||
if (projectile != null) break;
|
||||
}
|
||||
//projectile not found, see if one of the contained items contains projectiles
|
||||
if (projectile == null)
|
||||
{
|
||||
foreach (Item item in containedItems)
|
||||
var containedSubItems = item.ContainedItems;
|
||||
if (containedSubItems == null) { continue; }
|
||||
foreach (Item subItem in containedSubItems)
|
||||
{
|
||||
var containedSubItems = item.ContainedItems;
|
||||
if (containedSubItems == null) { continue; }
|
||||
foreach (Item subItem in containedSubItems)
|
||||
projectile = subItem.GetComponent<Projectile>();
|
||||
//apply OnUse statuseffects to the container in case it has to react to it somehow
|
||||
//(play a sound, spawn more projectiles, reduce condition...)
|
||||
if (subItem.Condition > 0.0f)
|
||||
{
|
||||
projectile = subItem.GetComponent<Projectile>();
|
||||
//apply OnUse statuseffects to the container in case it has to react to it somehow
|
||||
//(play a sound, spawn more projectiles, reduce condition...)
|
||||
if (subItem.Condition > 0.0f)
|
||||
{
|
||||
subItem.GetComponent<ItemContainer>()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime);
|
||||
}
|
||||
if (projectile != null) break;
|
||||
subItem.GetComponent<ItemContainer>()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime);
|
||||
}
|
||||
if (projectile != null) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectile == null) return true;
|
||||
|
||||
|
||||
@@ -6,9 +6,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
#if CLIENT
|
||||
using Barotrauma.Particles;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -25,41 +22,58 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Vector2 debugRayStartPos, debugRayEndPos;
|
||||
|
||||
[Serialize("Both", false)]
|
||||
[Serialize("Both", false, description: "Can the item be used in air, water or both.")]
|
||||
public UseEnvironment UsableIn
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "The distance at which the item can repair targets.")]
|
||||
public float Range { get; set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "Random spread applied to the firing angle when used by a character with sufficient skills to use the tool (in degrees).")]
|
||||
public float Spread
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "Random spread applied to the firing angle when used by a character with insufficient skills to use the tool (in degrees).")]
|
||||
public float UnskilledSpread
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How many units of damage the item removes from structures per second.")]
|
||||
public float StructureFixAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "How much the item decreases the size of fires per second.")]
|
||||
public float ExtinguishAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Serialize("0.0,0.0", false)]
|
||||
[Serialize("0.0,0.0", false, description: "The position of the barrel as an offset from the item's center (in pixels).")]
|
||||
public Vector2 BarrelPos { get; set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the item repair things through walls.")]
|
||||
public bool RepairThroughWalls { get; set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the item repair multiple things at once, or will it only affect the first thing the ray from the barrel hits.")]
|
||||
public bool RepairMultiple { get; set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the item repair things through holes in walls.")]
|
||||
public bool RepairThroughHoles { get; set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "The probability of starting a fire somewhere along the ray fired from the barrel (for example, 0.1 = 10% chance to start a fire during a second of use).")]
|
||||
public float FireProbability { get; set; }
|
||||
|
||||
[Serialize(0.0f, false, description: "Force applied to the entity the ray hits.")]
|
||||
public float TargetForce { get; set; }
|
||||
|
||||
public Vector2 TransformedBarrelPos
|
||||
{
|
||||
get
|
||||
@@ -164,10 +178,12 @@ namespace Barotrauma.Items.Components
|
||||
if (item.Submarine != null) { rayStart += item.Submarine.SimPosition; }
|
||||
}
|
||||
|
||||
float spread = MathHelper.ToRadians(MathHelper.Lerp(UnskilledSpread, Spread, degreeOfSuccess));
|
||||
float angle = item.body.Rotation + spread * Rand.Range(-0.5f, 0.5f);
|
||||
Vector2 rayEnd = rayStart +
|
||||
ConvertUnits.ToSimUnits(new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
(float)Math.Sin(item.body.Rotation)) * Range * item.body.Dir);
|
||||
(float)Math.Cos(angle),
|
||||
(float)Math.Sin(angle)) * Range * item.body.Dir);
|
||||
|
||||
List<Body> ignoredBodies = new List<Body>();
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
@@ -319,6 +335,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return true; }
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new ISerializableEntity[] { targetStructure });
|
||||
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
|
||||
targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user);
|
||||
|
||||
@@ -341,15 +358,43 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (targetCharacter.Removed) { return false; }
|
||||
targetCharacter.LastDamageSource = item;
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List<ISerializableEntity>() { targetCharacter });
|
||||
Limb closestLimb = null;
|
||||
float closestDist = float.MaxValue;
|
||||
foreach (Limb limb in targetCharacter.AnimController.Limbs)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(item.SimPosition, limb.SimPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestLimb = limb;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestLimb != null && !MathUtils.NearlyEqual(TargetForce, 0.0f))
|
||||
{
|
||||
Vector2 dir = closestLimb.WorldPosition - item.WorldPosition;
|
||||
dir = dir.LengthSquared() < 0.0001f ? Vector2.UnitY : Vector2.Normalize(dir);
|
||||
closestLimb.body.ApplyForce(dir * TargetForce, maxVelocity: 10.0f);
|
||||
}
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse,
|
||||
closestLimb == null ? new ISerializableEntity[] { targetCharacter } : new ISerializableEntity[] { targetCharacter, closestLimb });
|
||||
FixCharacterProjSpecific(user, deltaTime, targetCharacter);
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is Limb targetLimb)
|
||||
{
|
||||
if (targetLimb.character == null || targetLimb.character.Removed) { return false; }
|
||||
|
||||
if (!MathUtils.NearlyEqual(TargetForce, 0.0f))
|
||||
{
|
||||
Vector2 dir = targetLimb.WorldPosition - item.WorldPosition;
|
||||
dir = dir.LengthSquared() < 0.0001f ? Vector2.UnitY : Vector2.Normalize(dir);
|
||||
targetLimb.body.ApplyForce(dir * TargetForce, maxVelocity: 10.0f);
|
||||
}
|
||||
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List<ISerializableEntity>() { targetLimb.character, targetLimb });
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new ISerializableEntity[] { targetLimb.character, targetLimb });
|
||||
FixCharacterProjSpecific(user, deltaTime, targetLimb.character);
|
||||
return true;
|
||||
}
|
||||
@@ -359,6 +404,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
|
||||
|
||||
if (targetItem.body != null && !MathUtils.NearlyEqual(TargetForce, 0.0f))
|
||||
{
|
||||
Vector2 dir = targetItem.WorldPosition - item.WorldPosition;
|
||||
dir = dir.LengthSquared() < 0.0001f ? Vector2.UnitY : Vector2.Normalize(dir);
|
||||
targetItem.body.ApplyForce(dir * TargetForce, maxVelocity: 10.0f);
|
||||
}
|
||||
|
||||
var levelResource = targetItem.GetComponent<LevelResource>();
|
||||
if (levelResource != null && levelResource.IsActive &&
|
||||
levelResource.requiredItems.Any() &&
|
||||
@@ -509,6 +561,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
effect.Apply(actionType, deltaTime, item, targets);
|
||||
}
|
||||
else if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
effect.Apply(actionType, deltaTime, item, targets.Where(t => t is Character));
|
||||
}
|
||||
else if (effect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
effect.Apply(actionType, deltaTime, item, targets.Where(t => t is Limb));
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
// Hard-coded progress bars for welding doors stuck.
|
||||
// A general purpose system could be better, but it would most likely require changes in the way we define the status effects in xml.
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool midAir;
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
[Serialize(1.0f, false, description: "The impulse applied to the physics body of the item when thrown. Higher values make the item be thrown faster.")]
|
||||
public float ThrowForce
|
||||
{
|
||||
get { return throwForce; }
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
Vector2 DrawSize { get; }
|
||||
|
||||
void Draw(SpriteBatch spriteBatch, bool editing);
|
||||
void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Barotrauma.Items.Components
|
||||
protected CoroutineHandle delayedCorrectionCoroutine;
|
||||
protected float correctionTimer;
|
||||
|
||||
[Editable, Serialize(0.0f, false)]
|
||||
[Editable, Serialize(0.0f, false, description: "How long it takes to pick up the item (in seconds).")]
|
||||
public float PickingTime
|
||||
{
|
||||
get;
|
||||
@@ -114,45 +114,42 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, false)] //Editable for doors to do their magic
|
||||
[Editable, Serialize(false, false, description: "Can the item be picked up (or interacted with, if the pick action does something else than picking up the item).")] //Editable for doors to do their magic
|
||||
public bool CanBePicked
|
||||
{
|
||||
get { return canBePicked; }
|
||||
set { canBePicked = value; }
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Should the interface of the item (if it has one) be drawn when the item is equipped.")]
|
||||
public bool DrawHudWhenEquipped
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the item be selected by interacting with it.")]
|
||||
public bool CanBeSelected
|
||||
{
|
||||
get { return canBeSelected; }
|
||||
set { canBeSelected = value; }
|
||||
}
|
||||
|
||||
//Transfer conditions between same prefab items
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the item be combined with other items of the same type.")]
|
||||
public bool CanBeCombined
|
||||
{
|
||||
get { return canBeCombined; }
|
||||
set { canBeCombined = value; }
|
||||
}
|
||||
|
||||
//Remove item if combination results in 0 condition
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Should the item be removed if combining it with an other item causes the condition of this item to drop to 0.")]
|
||||
public bool RemoveOnCombined
|
||||
{
|
||||
get { return removeOnCombined; }
|
||||
set { removeOnCombined = value; }
|
||||
}
|
||||
|
||||
//Can the "Use" action be triggered by characters or just other items/statuseffects
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the \"Use\" action of the item be triggered by characters or just other items/StatusEffects.")]
|
||||
public bool CharacterUsable
|
||||
{
|
||||
get { return characterUsable; }
|
||||
@@ -160,7 +157,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
//Remove item if combination results in 0 condition
|
||||
[Serialize(true, false), Editable(ToolTip = "Can the properties of the component be edited in-game (only applicable if the component has in-game editable properties).")]
|
||||
[Serialize(true, false, description: "Can the properties of the component be edited in-game (only applicable if the component has in-game editable properties)."), Editable()]
|
||||
public bool AllowInGameEditing
|
||||
{
|
||||
get;
|
||||
@@ -179,7 +176,7 @@ namespace Barotrauma.Items.Components
|
||||
protected set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Should the item be deleted when it's used.")]
|
||||
public bool DeleteOnUse
|
||||
{
|
||||
get;
|
||||
@@ -196,7 +193,7 @@ namespace Barotrauma.Items.Components
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
[Editable, Serialize("", true, translationTextTag: "ItemMsg")]
|
||||
[Editable, Serialize("", true, translationTextTag: "ItemMsg", description: "A text displayed next to the item when it's highlighted (generally instructs how to interact with the item, e.g. \"[Mouse1] Pick up\").")]
|
||||
public string Msg
|
||||
{
|
||||
get;
|
||||
@@ -213,7 +210,7 @@ namespace Barotrauma.Items.Components
|
||||
/// <summary>
|
||||
/// How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).
|
||||
/// </summary>
|
||||
[Serialize(0f, false)]
|
||||
[Serialize(0f, false, description: "How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).")]
|
||||
public float CombatPriority { get; private set; }
|
||||
|
||||
public ItemComponent(Item item, XElement element)
|
||||
@@ -400,7 +397,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Combine(Item item)
|
||||
public virtual bool Combine(Item item, Character user)
|
||||
{
|
||||
if (canBeCombined && this.item.Prefab == item.Prefab && item.Condition > 0.0f && this.item.Condition > 0.0f)
|
||||
{
|
||||
@@ -670,9 +667,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Load(XElement componentElement)
|
||||
public virtual void Load(XElement componentElement, bool usePrefabValues)
|
||||
{
|
||||
if (componentElement == null) return;
|
||||
if (componentElement == null || usePrefabValues) { return; }
|
||||
foreach (XAttribute attribute in componentElement.Attributes())
|
||||
{
|
||||
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemContainer : ItemComponent, IDrawableComponent
|
||||
{
|
||||
private List<RelatedItem> containableItems;
|
||||
public ItemInventory Inventory;
|
||||
|
||||
private List<Pair<Item, StatusEffect>> itemsWithStatusEffects;
|
||||
@@ -17,7 +16,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//how many items can be contained
|
||||
private int capacity;
|
||||
[Serialize(5, false)]
|
||||
[Serialize(5, false, description: "How many items can be contained inside this item.")]
|
||||
public int Capacity
|
||||
{
|
||||
get { return capacity; }
|
||||
@@ -25,18 +24,19 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private bool hideItems;
|
||||
[Serialize(true, false)]
|
||||
[Serialize(true, false, description: "Should the items contained inside this item be hidden."
|
||||
+ " If set to false, you should use the ItemPos and ItemInterval properties to determine where the items get rendered.")]
|
||||
public bool HideItems
|
||||
{
|
||||
get { return hideItems; }
|
||||
set
|
||||
{
|
||||
set
|
||||
{
|
||||
hideItems = value;
|
||||
Drawable = !hideItems;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, false)]
|
||||
[Serialize(true, false, description: "Should the inventory of this item be visible when the item is selected.")]
|
||||
public bool DrawInventory
|
||||
{
|
||||
get;
|
||||
@@ -44,28 +44,23 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "If set to true, interacting with this item will make the character interact with the contained item(s), automatically picking them up if they can be picked up.")]
|
||||
public bool AutoInteractWithContained
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("0.5,0.5", false)]
|
||||
public Vector2 HudPos { get; set; }
|
||||
[Serialize(5, false)]
|
||||
[Serialize(5, false, description: "How many inventory slots the inventory has per row.")]
|
||||
public int SlotsPerRow { get; set; }
|
||||
|
||||
public List<RelatedItem> ContainableItems
|
||||
{
|
||||
get { return containableItems; }
|
||||
}
|
||||
public List<RelatedItem> ContainableItems { get; private set; }
|
||||
|
||||
public ItemContainer(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
Inventory = new ItemInventory(item, this, capacity, HudPos, SlotsPerRow);
|
||||
containableItems = new List<RelatedItem>();
|
||||
Inventory = new ItemInventory(item, this, capacity, SlotsPerRow);
|
||||
ContainableItems = new List<RelatedItem>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -78,7 +73,7 @@ namespace Barotrauma.Items.Components
|
||||
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - containable with no identifiers.");
|
||||
continue;
|
||||
}
|
||||
containableItems.Add(containable);
|
||||
ContainableItems.Add(containable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -94,7 +89,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
|
||||
RelatedItem ri = containableItems.Find(x => x.MatchesItem(containedItem));
|
||||
RelatedItem ri = ContainableItems.Find(x => x.MatchesItem(containedItem));
|
||||
if (ri != null)
|
||||
{
|
||||
itemsWithStatusEffects.RemoveAll(i => i.First == containedItem);
|
||||
@@ -118,8 +113,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool CanBeContained(Item item)
|
||||
{
|
||||
if (containableItems.Count == 0) return true;
|
||||
return (containableItems.Find(x => x.MatchesItem(item)) != null);
|
||||
if (ContainableItems.Count == 0) return true;
|
||||
return (ContainableItems.Find(x => x.MatchesItem(item)) != null);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -189,9 +184,10 @@ namespace Barotrauma.Items.Components
|
||||
return (picker != null);
|
||||
}
|
||||
|
||||
public override bool Combine(Item item)
|
||||
public override bool Combine(Item item, Character user)
|
||||
{
|
||||
if (!containableItems.Any(x => x.MatchesItem(item))) return false;
|
||||
if (!ContainableItems.Any(x => x.MatchesItem(item))) { return false; }
|
||||
if (user != null && !user.CanAccessInventory(Inventory)) { return false; }
|
||||
|
||||
if (Inventory.TryPutItem(item, null))
|
||||
{
|
||||
@@ -286,20 +282,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement)
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
{
|
||||
base.Load(componentElement);
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
|
||||
string containedString = componentElement.GetAttributeString("contained", "");
|
||||
|
||||
string[] itemIdStrings = containedString.Split(',');
|
||||
|
||||
itemIds = new ushort[itemIdStrings.Length];
|
||||
for (int i = 0; i < itemIdStrings.Length; i++)
|
||||
{
|
||||
ushort id = 0;
|
||||
if (!ushort.TryParse(itemIdStrings[i], out id)) continue;
|
||||
|
||||
if (!ushort.TryParse(itemIdStrings[i], out ushort id)) { continue; }
|
||||
itemIds[i] = id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
|
||||
partial class Controller : ItemComponent, IServerSerializable
|
||||
{
|
||||
//where the limbs of the user should be positioned when using the controller
|
||||
private List<LimbPos> limbPositions;
|
||||
private readonly List<LimbPos> limbPositions;
|
||||
|
||||
private Direction dir;
|
||||
|
||||
@@ -51,7 +51,9 @@ namespace Barotrauma.Items.Components
|
||||
get { return user; }
|
||||
}
|
||||
|
||||
[Serialize(false, false), Editable(ToolTip = "When enabled, the item will continuously send out a 0/1 signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out 1 when interacted with.")]
|
||||
public IEnumerable<LimbPos> LimbPositions { get { return limbPositions; } }
|
||||
|
||||
[Editable, Serialize(false, false, description: "When enabled, the item will continuously send out a 0/1 signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out 1 when interacted with.")]
|
||||
public bool IsToggle
|
||||
{
|
||||
get;
|
||||
@@ -146,7 +148,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, user);
|
||||
|
||||
if (limbPositions.Count == 0) return;
|
||||
if (limbPositions.Count == 0) { return; }
|
||||
|
||||
user.AnimController.Anim = AnimController.Animation.UsingConstruction;
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float prevVoltage;
|
||||
|
||||
[Editable(0.0f, 10000000.0f, ToolTip = "The amount of force exerted on the submarine when the engine is operating at full power."),
|
||||
Serialize(2000.0f, true)]
|
||||
[Editable(0.0f, 10000000.0f),
|
||||
Serialize(2000.0f, true, description: "The amount of force exerted on the submarine when the engine is operating at full power.")]
|
||||
public float MaxForce
|
||||
{
|
||||
get { return maxForce; }
|
||||
@@ -33,7 +33,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize("0.0,0.0", true)]
|
||||
[Editable, Serialize("0.0,0.0", true,
|
||||
description: "The position of the propeller as an offset from the item's center (in pixels)."+
|
||||
" Determines where the particles spawn and the position that causes characters to take damage from the engine if the PropellerDamage is defined.")]
|
||||
public Vector2 PropellerPos
|
||||
{
|
||||
get;
|
||||
@@ -148,6 +150,16 @@ namespace Barotrauma.Items.Components
|
||||
force = MathHelper.Lerp(force, 0.0f, 0.1f);
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
PropellerPos = new Vector2(-PropellerPos.X, PropellerPos.Y);
|
||||
}
|
||||
|
||||
public override void FlipY(bool relativeToSub)
|
||||
{
|
||||
PropellerPos = new Vector2(PropellerPos.X, -PropellerPos.Y);
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
|
||||
|
||||
@@ -22,23 +22,23 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool hasPower;
|
||||
|
||||
private Dictionary<Hull, HullData> hullDatas;
|
||||
private readonly Dictionary<Hull, HullData> hullDatas;
|
||||
|
||||
[Editable(ToolTip = "Does the machine require inputs from water detectors in order to show the water levels inside rooms."), Serialize(false, true)]
|
||||
[Editable, Serialize(false, true, description: "Does the machine require inputs from water detectors in order to show the water levels inside rooms.")]
|
||||
public bool RequireWaterDetectors
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "Does the machine require inputs from oxygen detectors in order to show the oxygen levels inside rooms."), Serialize(true, true)]
|
||||
[Editable, Serialize(true, true, description: "Does the machine require inputs from oxygen detectors in order to show the oxygen levels inside rooms.")]
|
||||
public bool RequireOxygenDetectors
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "Should damaged walls be displayed by the machine."), Serialize(true, true)]
|
||||
[Editable, Serialize(true, true, description: "Should damaged walls be displayed by the machine.")]
|
||||
public bool ShowHullIntegrity
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "How much oxygen the machine generates when operating at full power."), Serialize(400.0f, true)]
|
||||
[Editable, Serialize(400.0f, true, description: "How much oxygen the machine generates when operating at full power.")]
|
||||
public float GeneratedAmount
|
||||
{
|
||||
get { return generatedAmount; }
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool hasPower;
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
[Serialize(0.0f, true, description: "How fast the item is currently pumping water (-100 = full speed out, 100 = full speed in). Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
||||
public float FlowPercentage
|
||||
{
|
||||
get { return flowPercentage; }
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(80.0f, false)]
|
||||
[Serialize(80.0f, false, description: "How fast the item pumps water in/out when operating at 100%.")]
|
||||
public float MaxFlow
|
||||
{
|
||||
get { return maxFlow; }
|
||||
|
||||
@@ -51,6 +51,8 @@ namespace Barotrauma.Items.Components
|
||||
const float AIUpdateInterval = 0.2f;
|
||||
private float aiUpdateTimer;
|
||||
|
||||
private Character lastAIUser;
|
||||
|
||||
private Character lastUser;
|
||||
private Character LastUser
|
||||
{
|
||||
@@ -63,7 +65,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(0.0f, float.MaxValue, ToolTip = "How much power (kW) the reactor generates when operating at full capacity."), Serialize(10000.0f, true)]
|
||||
[Editable(0.0f, float.MaxValue), Serialize(10000.0f, true, description: "How much power (kW) the reactor generates when operating at full capacity.")]
|
||||
public float MaxPowerOutput
|
||||
{
|
||||
get { return maxPowerOutput; }
|
||||
@@ -73,21 +75,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until a meltdown occurs."), Serialize(120.0f, true)]
|
||||
[Editable(0.0f, float.MaxValue), Serialize(120.0f, true, description: "How long the temperature has to stay critical until a meltdown occurs.")]
|
||||
public float MeltdownDelay
|
||||
{
|
||||
get { return meltDownDelay; }
|
||||
set { meltDownDelay = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until the reactor catches fire."), Serialize(30.0f, true)]
|
||||
[Editable(0.0f, float.MaxValue), Serialize(30.0f, true, description: "How long the temperature has to stay critical until the reactor catches fire.")]
|
||||
public float FireDelay
|
||||
{
|
||||
get { return fireDelay; }
|
||||
set { fireDelay = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
[Serialize(0.0f, true, description: "Current temperature of the reactor (0% - 100%). Indended to be used by StatusEffect conditionals.")]
|
||||
public float Temperature
|
||||
{
|
||||
get { return temperature; }
|
||||
@@ -98,7 +100,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
[Serialize(0.0f, true, description: "Current fission rate of the reactor (0% - 100%). Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
|
||||
public float FissionRate
|
||||
{
|
||||
get { return fissionRate; }
|
||||
@@ -109,7 +111,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
[Serialize(0.0f, true, description: "Current turbine output of the reactor (0% - 100%). Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
|
||||
public float TurbineOutput
|
||||
{
|
||||
get { return turbineOutput; }
|
||||
@@ -120,7 +122,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.2f, true), Editable(0.0f, 1000.0f, ToolTip = "How fast the condition of the contained fuel rods deteriorates.")]
|
||||
[Serialize(0.2f, true, description: "How fast the condition of the contained fuel rods deteriorates per second."), Editable(0.0f, 1000.0f)]
|
||||
public float FuelConsumptionRate
|
||||
{
|
||||
get { return fuelConsumptionRate; }
|
||||
@@ -131,7 +133,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
[Serialize(false, true, description: "Is the temperature currently critical. Intended to be used by StatusEffect conditionals (setting the value from XML has no effect).")]
|
||||
public bool TemperatureCritical
|
||||
{
|
||||
get { return temperature > allowedTemperature.Y; }
|
||||
@@ -143,7 +145,7 @@ namespace Barotrauma.Items.Components
|
||||
private float targetFissionRate;
|
||||
private float targetTurbineOutput;
|
||||
|
||||
[Serialize(false, true)]
|
||||
[Serialize(false, true, description: "Is the automatic temperature control currently on. Indended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
|
||||
public bool AutoTemp
|
||||
{
|
||||
get { return autoTemp; }
|
||||
@@ -193,6 +195,18 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#endif
|
||||
|
||||
//if an AI character was using the item on the previous frame but not anymore, turn autotemp on
|
||||
// (= bots turn autotemp back on when leaving the reactor)
|
||||
if (lastAIUser != null)
|
||||
{
|
||||
if (lastAIUser.SelectedConstruction != item && lastAIUser.CanInteractWith(item))
|
||||
{
|
||||
AutoTemp = true;
|
||||
unsentChanges = true;
|
||||
lastAIUser = null;
|
||||
}
|
||||
}
|
||||
|
||||
prevAvailableFuel = AvailableFuel;
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
@@ -562,7 +576,7 @@ namespace Barotrauma.Items.Components
|
||||
character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f);
|
||||
}
|
||||
|
||||
LastUser = character;
|
||||
LastUser = lastAIUser = character;
|
||||
|
||||
switch (objective.Option.ToLowerInvariant())
|
||||
{
|
||||
|
||||
@@ -65,35 +65,37 @@ namespace Barotrauma.Items.Components
|
||||
private bool useDirectionalPing = false;
|
||||
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
|
||||
|
||||
private Sprite pingCircle, directionalPingCircle, screenOverlay, screenBackground;
|
||||
private Sprite pingCircle, directionalPingCircle;
|
||||
private Sprite screenOverlay, screenBackground;
|
||||
|
||||
private Sprite sonarBlip;
|
||||
private Sprite lineSprite;
|
||||
|
||||
private bool aiPingCheckPending;
|
||||
|
||||
//the float value is a timer used for disconnecting the transducer if no signal is received from it for 1 second
|
||||
private List<ConnectedTransducer> connectedTransducers;
|
||||
private readonly List<ConnectedTransducer> connectedTransducers;
|
||||
|
||||
public IEnumerable<SonarTransducer> ConnectedTransducers
|
||||
{
|
||||
get { return connectedTransducers.Select(t => t.Transducer); }
|
||||
}
|
||||
|
||||
[Serialize(DefaultSonarRange, false)]
|
||||
[Serialize(DefaultSonarRange, false, description: "The maximum range of the sonar.")]
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Should the sonar display the walls of the submarine it is inside.")]
|
||||
public bool DetectSubmarineWalls
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false), Editable(ToolTip = "Does the sonar have to be connected to external transducers to work.")]
|
||||
[Editable, Serialize(false, false, description: "Does the sonar have to be connected to external transducers to work.")]
|
||||
public bool UseTransducers
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -74,9 +74,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(0.0f, 1.0f, decimals: 3, ToolTip = "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
|
||||
+" Can be used to compensate if the ballast tanks are too large/small relative to the size of the submarine."), Serialize(0.5f, true)]
|
||||
|
||||
[Editable(0.0f, 1.0f, decimals: 3),
|
||||
Serialize(0.5f, true, description: "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
|
||||
+ " Can be used to compensate if the ballast tanks are too large/small relative to the size of the submarine.")]
|
||||
public float NeutralBallastLevel
|
||||
{
|
||||
get { return neutralBallastLevel; }
|
||||
@@ -86,7 +87,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(1000.0f, true)]
|
||||
[Serialize(1000.0f, true, description: "How close the docking port has to be to another docking port for the docking mode to become active.")]
|
||||
public float DockingAssistThreshold
|
||||
{
|
||||
get;
|
||||
@@ -521,98 +522,5 @@ namespace Barotrauma.Items.Components
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, IReadMessage msg, Barotrauma.Networking.Client c)
|
||||
{
|
||||
bool autoPilot = msg.ReadBoolean();
|
||||
bool dockingButtonClicked = msg.ReadBoolean();
|
||||
Vector2 newSteeringInput = targetVelocity;
|
||||
bool maintainPos = false;
|
||||
Vector2? newPosToMaintain = null;
|
||||
bool headingToStart = false;
|
||||
|
||||
if (autoPilot)
|
||||
{
|
||||
maintainPos = msg.ReadBoolean();
|
||||
if (maintainPos)
|
||||
{
|
||||
newPosToMaintain = new Vector2(
|
||||
msg.ReadSingle(),
|
||||
msg.ReadSingle());
|
||||
}
|
||||
else
|
||||
{
|
||||
headingToStart = msg.ReadBoolean();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newSteeringInput = new Vector2(msg.ReadSingle(), msg.ReadSingle());
|
||||
}
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
user = c.Character;
|
||||
AutoPilot = autoPilot;
|
||||
|
||||
if (dockingButtonClicked)
|
||||
{
|
||||
item.SendSignal(0, "1", "toggle_docking", sender: Character.Controlled);
|
||||
}
|
||||
|
||||
if (!AutoPilot)
|
||||
{
|
||||
steeringInput = newSteeringInput;
|
||||
steeringAdjustSpeed = MathHelper.Lerp(0.2f, 1.0f, c.Character.GetSkillLevel("helm") / 100.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaintainPos = newPosToMaintain != null;
|
||||
posToMaintain = newPosToMaintain;
|
||||
|
||||
if (posToMaintain == null)
|
||||
{
|
||||
LevelStartSelected = headingToStart;
|
||||
LevelEndSelected = !headingToStart;
|
||||
UpdatePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
LevelStartSelected = false;
|
||||
LevelEndSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
//notify all clients of the changed state
|
||||
unsentChanges = true;
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Barotrauma.Networking.Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(autoPilot);
|
||||
|
||||
if (!autoPilot)
|
||||
{
|
||||
//no need to write steering info if autopilot is controlling
|
||||
msg.Write(steeringInput.X);
|
||||
msg.Write(steeringInput.Y);
|
||||
msg.Write(targetVelocity.X);
|
||||
msg.Write(targetVelocity.Y);
|
||||
msg.Write(steeringAdjustSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(posToMaintain != null);
|
||||
if (posToMaintain != null)
|
||||
{
|
||||
msg.Write(((Vector2)posToMaintain).X);
|
||||
msg.Write(((Vector2)posToMaintain).Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(LevelStartSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float charge;
|
||||
|
||||
private float rechargeVoltage, outputVoltage;
|
||||
private float rechargeVoltage;
|
||||
|
||||
//how fast the battery can be recharged
|
||||
private float maxRechargeSpeed;
|
||||
@@ -38,39 +38,38 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("0,0", true)]
|
||||
[Serialize("0,0", true, description: "The position of the progress bar indicating the charge of the item. In pixels as an offset from the upper left corner of the sprite.")]
|
||||
public Vector2 IndicatorPosition
|
||||
{
|
||||
get { return indicatorPosition; }
|
||||
set { indicatorPosition = value; }
|
||||
}
|
||||
|
||||
[Serialize("0,0", true)]
|
||||
[Serialize("0,0", true, description: "The size of the progress bar indicating the charge of the item (in pixels).")]
|
||||
public Vector2 IndicatorSize
|
||||
{
|
||||
get { return indicatorSize; }
|
||||
set { indicatorSize = value; }
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
[Serialize(false, true, description: "Should the progress bar indicating the charge of the item fill up horizontally or vertically.")]
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
set { isHorizontal = value; }
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "Maximum output of the device when fully charged (kW)."), Serialize(10.0f, true)]
|
||||
[Editable, Serialize(10.0f, true, description: "Maximum output of the device when fully charged (kW).")]
|
||||
public float MaxOutPut { set; get; }
|
||||
|
||||
[Serialize(10.0f, true), Editable(ToolTip = "The maximum capacity of the device (kW * min). "+
|
||||
"For example, a value of 1000 means the device can output 100 kilowatts of power for 10 minutes, or 1000 kilowatts for 1 minute.")]
|
||||
[Editable, Serialize(10.0f, true, description: "The maximum capacity of the device (kW * min). For example, a value of 1000 means the device can output 100 kilowatts of power for 10 minutes, or 1000 kilowatts for 1 minute.")]
|
||||
public float Capacity
|
||||
{
|
||||
get { return capacity; }
|
||||
set { capacity = Math.Max(value, 1.0f); }
|
||||
}
|
||||
|
||||
[Editable, Serialize(0.0f, true)]
|
||||
[Editable, Serialize(0.0f, true, description: "The current charge of the device.")]
|
||||
public float Charge
|
||||
{
|
||||
get { return charge; }
|
||||
@@ -92,15 +91,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float ChargePercentage => MathUtils.Percentage(Charge, Capacity);
|
||||
|
||||
[Serialize(10.0f, true), Editable(ToolTip = "How fast the device can be recharged. "+
|
||||
"For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")]
|
||||
[Editable, Serialize(10.0f, true, description: "How fast the device can be recharged. For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")]
|
||||
public float MaxRechargeSpeed
|
||||
{
|
||||
get { return maxRechargeSpeed; }
|
||||
set { maxRechargeSpeed = Math.Max(value, 1.0f); }
|
||||
}
|
||||
|
||||
[Serialize(10.0f, true), Editable]
|
||||
[Editable, Serialize(10.0f, true, description: "The current recharge speed of the device.")]
|
||||
public float RechargeSpeed
|
||||
{
|
||||
get { return rechargeSpeed; }
|
||||
@@ -223,7 +221,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
rechargeVoltage = 0.0f;
|
||||
outputVoltage = 0.0f;
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
@@ -241,7 +238,10 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
RechargeSpeed = maxRechargeSpeed * aiRechargeTargetRatio;
|
||||
#if CLIENT
|
||||
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
|
||||
if (rechargeSpeedSlider != null)
|
||||
{
|
||||
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
character.Speak(TextManager.GetWithVariables("DialogChargeBatteries", new string[2] { "[itemname]", "[rate]" },
|
||||
@@ -258,7 +258,10 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
RechargeSpeed = 0.0f;
|
||||
#if CLIENT
|
||||
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
|
||||
if (rechargeSpeedSlider != null)
|
||||
{
|
||||
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
|
||||
}
|
||||
#endif
|
||||
character.Speak(TextManager.GetWithVariables("DialogStopChargingBatteries", new string[2] { "[itemname]", "[rate]" },
|
||||
new string[2] { item.Name, ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString() },
|
||||
@@ -280,7 +283,10 @@ namespace Barotrauma.Items.Components
|
||||
float rechargeRate = MathHelper.Clamp(tempSpeed / 100.0f, 0.0f, 1.0f);
|
||||
RechargeSpeed = rechargeRate * MaxRechargeSpeed;
|
||||
#if CLIENT
|
||||
rechargeSpeedSlider.BarScroll = rechargeRate;
|
||||
if (rechargeSpeedSlider != null)
|
||||
{
|
||||
rechargeSpeedSlider.BarScroll = rechargeRate;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -290,10 +296,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
rechargeVoltage = Math.Min(power, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputVoltage = power;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,30 +41,30 @@ namespace Barotrauma.Items.Components
|
||||
get { return powerLoad; }
|
||||
}
|
||||
|
||||
[Serialize(true, true), Editable(ToolTip = "Can the item be damaged if too much power is supplied to the power grid.")]
|
||||
[Editable, Serialize(true, true, description: "Can the item be damaged if too much power is supplied to the power grid.")]
|
||||
public bool CanBeOverloaded
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(2.0f, true), Editable(MinValueFloat = 1.0f, ToolTip =
|
||||
[Editable(MinValueFloat = 1.0f), Serialize(2.0f, true, description:
|
||||
"How much power has to be supplied to the grid relative to the load before item starts taking damage. "
|
||||
+"E.g. a value of 2 means that the grid has to be receiving twice as much power as the devices in the grid are consuming.")]
|
||||
+ "E.g. a value of 2 means that the grid has to be receiving twice as much power as the devices in the grid are consuming.")]
|
||||
public float OverloadVoltage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.15f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, ToolTip = "The probability for a fire to start when the item breaks.")]
|
||||
[Serialize(0.15f, true, description: "The probability for a fire to start when the item breaks."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float FireProbability
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Is the item currently overloaded. Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
|
||||
public bool Overload
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace Barotrauma.Items.Components
|
||||
//the maximum amount of power the item can draw from connected items
|
||||
protected float powerConsumption;
|
||||
|
||||
[Serialize(0.5f, true), Editable(ToolTip = "The minimum voltage required for the device to function. "+
|
||||
"The voltage is calculated as power / powerconsumption, meaning that a device "+
|
||||
[Editable, Serialize(0.5f, true, description: "The minimum voltage required for the device to function. " +
|
||||
"The voltage is calculated as power / powerconsumption, meaning that a device " +
|
||||
"with a power consumption of 1000 kW would need at least 500 kW of power to work if the minimum voltage is set to 0.5.")]
|
||||
public float MinVoltage
|
||||
{
|
||||
@@ -31,14 +31,14 @@ namespace Barotrauma.Items.Components
|
||||
set { minVoltage = value; }
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "How much power the device draws (or attempts to draw) from the electrical grid."), Serialize(0.0f, true)]
|
||||
[Editable, Serialize(0.0f, true, description: "How much power the device draws (or attempts to draw) from the electrical grid when active.")]
|
||||
public float PowerConsumption
|
||||
{
|
||||
get { return powerConsumption; }
|
||||
set { powerConsumption = value; }
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
[Serialize(false, true, description: "Is the device currently active. Inactive devices don't consume power.")]
|
||||
public override bool IsActive
|
||||
{
|
||||
get { return base.IsActive; }
|
||||
@@ -52,21 +52,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
[Serialize(0.0f, true, description: "The current power consumption of the device. Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
|
||||
public float CurrPowerConsumption
|
||||
{
|
||||
get {return currPowerConsumption; }
|
||||
set { currPowerConsumption = value; }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
[Serialize(0.0f, true, description: "The current voltage of the item (calculated as power consumption / available power). Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
|
||||
public float Voltage
|
||||
{
|
||||
get { return voltage; }
|
||||
set { voltage = Math.Max(0.0f, value); }
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "Can the item be damaged by electomagnetic pulses."), Serialize(true, true)]
|
||||
[Editable, Serialize(true, true, description: "Can the item be damaged by electomagnetic pulses.")]
|
||||
public bool VulnerableToEMP
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -57,14 +57,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float persistentStickJointTimer;
|
||||
|
||||
[Serialize(10.0f, false)]
|
||||
[Serialize(10.0f, false, description: "The impulse applied to the physics body of the item when it's launched. Higher values make the projectile faster.")]
|
||||
public float LaunchImpulse
|
||||
{
|
||||
get { return launchImpulse; }
|
||||
set { launchImpulse = value; }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "The rotation of the item relative to the rotation of the weapon when launched (in degrees).")]
|
||||
public float LaunchRotation
|
||||
{
|
||||
get { return MathHelper.ToDegrees(LaunchRotationRadians); }
|
||||
@@ -77,7 +77,7 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "When set to true, the item can stick to any target it hits.")]
|
||||
//backwards compatibility, can stick to anything
|
||||
public bool DoesStick
|
||||
{
|
||||
@@ -85,49 +85,52 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the item stick to the character it hits.")]
|
||||
public bool StickToCharacters
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the item stick to the structure it hits.")]
|
||||
public bool StickToStructures
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Can the item stick to the item it hits.")]
|
||||
public bool StickToItems
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Hitscan projectiles cast a ray forwards and immediately hit whatever the ray hits. "+
|
||||
"It is recommended to use hitscans for very fast-moving projectiles such as bullets, because using extremely fast launch velocities may cause physics glitches.")]
|
||||
public bool Hitscan
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1, false)]
|
||||
[Serialize(1, false, description: "How many hitscans should be done when the projectile is launched. "
|
||||
+ "Multiple hitscans can be used to simulate weapons that fire multiple projectiles at the same time" +
|
||||
" without having to actually use multiple projectile items, for example shotguns.")]
|
||||
public int HitScanCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "Should the item be deleted when it hits something.")]
|
||||
public bool RemoveOnHit
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "Random spread applied to the launch angle of the projectile (in degrees).")]
|
||||
public float Spread
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -21,64 +21,63 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float LastActiveTime;
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "How fast the condition of the item deteriorates per second.")]
|
||||
[Serialize(0.0f, true, description: "How fast the condition of the item deteriorates per second."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2)]
|
||||
public float DeteriorationSpeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2, ToolTip = "Minimum initial delay before the item starts to deteriorate.")]
|
||||
[Serialize(0.0f, true, description: "Minimum initial delay before the item starts to deteriorate."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
|
||||
public float MinDeteriorationDelay
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2, ToolTip = "Maximum initial delay before the item starts to deteriorate.")]
|
||||
[Serialize(0.0f, true, description: "Maximum initial delay before the item starts to deteriorate."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
|
||||
public float MaxDeteriorationDelay
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(50.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The item won't deteriorate spontaneously if the condition is below this value. For example, if set to 10, the condition will spontaneously drop to 10 and then stop dropping (unless the item is damaged further by external factors). Percentages of max condition.")]
|
||||
[Serialize(50.0f, true, description: "The item won't deteriorate spontaneously if the condition is below this value. For example, if set to 10, the condition will spontaneously drop to 10 and then stop dropping (unless the item is damaged further by external factors). Percentages of max condition."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float MinDeteriorationCondition
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0f, true)]
|
||||
[Serialize(0f, true, description: "How low a traitor must get the item's condition for it to start breaking down.")]
|
||||
public float MinSabotageCondition
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(80.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The condition of the item has to be below this before the repair UI becomes usable. Percentages of max condition.")]
|
||||
[Serialize(80.0f, true, description: "The condition of the item has to be below this before the repair UI becomes usable. Percentages of max condition."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float ShowRepairUIThreshold
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(100.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The amount of time it takes to fix the item with insufficient skill levels.")]
|
||||
[Serialize(100.0f, true, description: "The amount of time it takes to fix the item with insufficient skill levels."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float FixDurationLowSkill
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(10.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The amount of time it takes to fix the item with sufficient skill levels.")]
|
||||
[Serialize(10.0f, true, description: "The amount of time it takes to fix the item with sufficient skill levels."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float FixDurationHighSkill
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
//if enabled, the deterioration timer will always run regardless if the item is being used or not
|
||||
[Serialize(false, false)]
|
||||
[Serialize(false, false, description: "If set to true, the deterioration timer will always run regardless if the item is being used or not.")]
|
||||
public bool DeteriorateAlways
|
||||
{
|
||||
get;
|
||||
@@ -199,7 +198,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
deteriorationTimer -= deltaTime;
|
||||
deteriorationTimer -= deltaTime * GetDeteriorationDelayMultiplier();
|
||||
#if SERVER
|
||||
if (deteriorationTimer <= 0.0f) { item.CreateServerEvent(this); }
|
||||
#endif
|
||||
@@ -336,7 +335,7 @@ namespace Barotrauma.Items.Components
|
||||
else if (ic is Pump pump)
|
||||
{
|
||||
//pumps don't deteriorate if they're not running
|
||||
if (Math.Abs(pump.FlowPercentage) > 1.0f) { return true; }
|
||||
if (Math.Abs(pump.FlowPercentage) > 1.0f && pump.IsActive) { return true; }
|
||||
}
|
||||
else if (ic is Reactor reactor)
|
||||
{
|
||||
@@ -357,6 +356,26 @@ namespace Barotrauma.Items.Components
|
||||
return DeteriorateAlways;
|
||||
}
|
||||
|
||||
private float GetDeteriorationDelayMultiplier()
|
||||
{
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic is Engine engine)
|
||||
{
|
||||
return Math.Abs(engine.Force) / 100.0f;
|
||||
}
|
||||
else if (ic is Pump pump)
|
||||
{
|
||||
return Math.Abs(pump.FlowPercentage) / 100.0f;
|
||||
}
|
||||
else if (ic is Reactor reactor)
|
||||
{
|
||||
return (reactor.FissionRate + reactor.TurbineOutput) / 200.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
private void UpdateFixAnimation(Character character)
|
||||
{
|
||||
character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f));
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (i == ropeBodies.Length - 2)
|
||||
{
|
||||
item.Combine(projectile);
|
||||
item.Combine(projectile, user: null);
|
||||
ropeBodies[ropeBodies.Length - 1].Enabled = false;
|
||||
IsActive = false;
|
||||
}
|
||||
@@ -221,7 +221,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//attempt to recontain the projectile in the launcher
|
||||
//eq automatically reload a spear into a speargun when picking the spear up
|
||||
if (!projectile.body.Enabled) item.Combine(projectile);
|
||||
if (!projectile.body.Enabled) item.Combine(projectile, user: null);
|
||||
|
||||
foreach (PhysicsBody b in ropeBodies)
|
||||
{
|
||||
|
||||
@@ -11,25 +11,29 @@ namespace Barotrauma.Items.Components
|
||||
protected float[] timeSinceReceived;
|
||||
|
||||
protected float[] receivedSignal;
|
||||
|
||||
|
||||
//the output is sent if both inputs have received a signal within the timeframe
|
||||
protected float timeFrame;
|
||||
|
||||
[InGameEditable(MinValueFloat = -999999.0f, MaxValueFloat = 999999.0f), Serialize(999999.0f, true)]
|
||||
[Serialize(999999.0f, true, description: "The output of the item is restricted below this value."),
|
||||
InGameEditable(MinValueFloat = -999999.0f, MaxValueFloat = 999999.0f)]
|
||||
public float ClampMax
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[InGameEditable(MinValueFloat = -999999.0f, MaxValueFloat = 999999.0f), Serialize(-999999.0f, true)]
|
||||
[Serialize(-999999.0f, true, description: "The output of the item is restricted above this value."),
|
||||
InGameEditable(MinValueFloat = -999999.0f, MaxValueFloat = 999999.0f)]
|
||||
public float ClampMin
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true)]
|
||||
[InGameEditable(DecimalCount = 2),
|
||||
Serialize(0.0f, true, description: "The item must have received signals to both inputs within this timeframe to output the sum of the signals." +
|
||||
" If set to 0, the inputs must be received at the same time.")]
|
||||
public float TimeFrame
|
||||
{
|
||||
get { return timeFrame; }
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
|
||||
//the output is sent if both inputs have received a signal within the timeframe
|
||||
protected float timeFrame;
|
||||
|
||||
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true)]
|
||||
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The item sends the output if both inputs have received a non-zero signal within the timeframe. If set to 0, the inputs must receive a signal at the same time.")]
|
||||
public float TimeFrame
|
||||
{
|
||||
get { return timeFrame; }
|
||||
@@ -23,14 +23,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("1", true)]
|
||||
[InGameEditable, Serialize("1", true, description: "The signal sent when both inputs have received a non-zero signal.")]
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set { output = value; }
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("", true)]
|
||||
[InGameEditable, Serialize("", true, description: "The signal sent when both inputs have not received a non-zero signal (if empty, no signal is sent).")]
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private List<ushort> disconnectedWireIds;
|
||||
|
||||
[Serialize(false, true), Editable(ToolTip = "Locked connection panels cannot be rewired in-game.")]
|
||||
[Editable, Serialize(false, true, description: "Locked connection panels cannot be rewired in-game.")]
|
||||
public bool Locked
|
||||
{
|
||||
get;
|
||||
@@ -171,9 +171,9 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Load(XElement element)
|
||||
public override void Load(XElement element, bool usePrefabValues)
|
||||
{
|
||||
base.Load(element);
|
||||
base.Load(element, usePrefabValues);
|
||||
|
||||
List<Connection> loadedConnections = new List<Connection>();
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace Barotrauma.Items.Components
|
||||
public bool ContinuousSignal;
|
||||
public bool State;
|
||||
public string Connection;
|
||||
[Serialize("", false, translationTextTag = "Label.")]
|
||||
[Serialize("", false, translationTextTag: "Label.", description: "The text displayed on this button/tickbox."), Editable]
|
||||
public string Label { get; set; }
|
||||
[Serialize("1", false)]
|
||||
[Serialize("1", false, description: "The signal sent out when this button is pressed or this tickbox checked."), Editable]
|
||||
public string Signal { get; set; }
|
||||
|
||||
public string Name => "CustomInterfaceElement";
|
||||
@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private string[] labels;
|
||||
[Serialize("", true)]
|
||||
[Serialize("", true, description: "The texts displayed on the buttons/tickboxes, separated by commas.")]
|
||||
public string Labels
|
||||
{
|
||||
get { return string.Join(",", labels); }
|
||||
@@ -55,7 +55,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
private string[] signals;
|
||||
[Serialize("", true)]
|
||||
[Serialize("", true, description: "The signals sent when the buttons are pressed or the tickboxes checked, separated by commas.")]
|
||||
public string Signals
|
||||
{
|
||||
//use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
|
||||
|
||||
@@ -9,9 +9,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public readonly string Signal;
|
||||
public readonly float SignalStrength;
|
||||
public float SendTimer;
|
||||
//in number of frames
|
||||
public int SendTimer;
|
||||
//in number of frames
|
||||
public int SendDuration;
|
||||
|
||||
public DelayedSignal(string signal, float signalStrength, float sendTimer)
|
||||
public DelayedSignal(string signal, float signalStrength, int sendTimer)
|
||||
{
|
||||
Signal = signal;
|
||||
SignalStrength = signalStrength;
|
||||
@@ -19,25 +22,35 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
const int SignalQueueSize = 500;
|
||||
private int signalQueueSize;
|
||||
private int delayTicks;
|
||||
|
||||
private Queue<DelayedSignal> signalQueue;
|
||||
|
||||
private DelayedSignal prevQueuedSignal;
|
||||
|
||||
[InGameEditable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f, DecimalCount = 2), Serialize(1.0f, true)]
|
||||
private float delay;
|
||||
[InGameEditable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f, DecimalCount = 2), Serialize(1.0f, true, description: "How long the item delays the signals (in seconds).")]
|
||||
public float Delay
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get { return delay; }
|
||||
set
|
||||
{
|
||||
if (value == delay) { return; }
|
||||
delay = value;
|
||||
delayTicks = (int)(delay / Timing.Step);
|
||||
signalQueueSize = delayTicks * 2;
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable(ToolTip = "Should the component discard previously received signals when a new one is received."), Serialize(false, true)]
|
||||
[InGameEditable, Serialize(false, true, description: "Should the component discard previously received signals when a new one is received.")]
|
||||
public bool ResetWhenSignalReceived
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[InGameEditable(ToolTip = "Should the component discard previously received signals when the incoming signal changes."), Serialize(false, true)]
|
||||
[InGameEditable, Serialize(false, true, description: "Should the component discard previously received signals when the incoming signal changes.")]
|
||||
public bool ResetWhenDifferentSignalReceived
|
||||
{
|
||||
get;
|
||||
@@ -55,13 +68,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (var val in signalQueue)
|
||||
{
|
||||
val.SendTimer -= deltaTime;
|
||||
val.SendTimer -= 1;
|
||||
}
|
||||
|
||||
while (signalQueue.Count > 0 && signalQueue.Peek().SendTimer <= 0.0f)
|
||||
while (signalQueue.Count > 0 && signalQueue.Peek().SendTimer <= 0)
|
||||
{
|
||||
var signalOut = signalQueue.Dequeue();
|
||||
var signalOut = signalQueue.Peek();
|
||||
signalOut.SendDuration -= 1;
|
||||
item.SendSignal(0, signalOut.Signal, "signal_out", null, signalStrength: signalOut.SignalStrength);
|
||||
if (signalOut.SendDuration <= 0) { signalQueue.Dequeue(); } else { break; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +85,28 @@ namespace Barotrauma.Items.Components
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "signal_in":
|
||||
if (signalQueue.Count >= SignalQueueSize) return;
|
||||
if (ResetWhenSignalReceived) signalQueue.Clear();
|
||||
if (signalQueue.Count >= signalQueueSize) { return; }
|
||||
if (ResetWhenSignalReceived) { prevQueuedSignal = null; signalQueue.Clear(); }
|
||||
if (ResetWhenDifferentSignalReceived && signalQueue.Count > 0 && signalQueue.Peek().Signal != signal)
|
||||
{
|
||||
prevQueuedSignal = null;
|
||||
signalQueue.Clear();
|
||||
}
|
||||
signalQueue.Enqueue(new DelayedSignal(signal, signalStrength, Delay));
|
||||
|
||||
if (prevQueuedSignal != null &&
|
||||
prevQueuedSignal.Signal == signal &&
|
||||
MathUtils.NearlyEqual(prevQueuedSignal.SignalStrength, signalStrength) &&
|
||||
((prevQueuedSignal.SendTimer + prevQueuedSignal.SendDuration == delayTicks) || (prevQueuedSignal.SendTimer <= 0 && prevQueuedSignal.SendDuration > 0)))
|
||||
{
|
||||
prevQueuedSignal.SendDuration += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
prevQueuedSignal = new DelayedSignal(signal, signalStrength, delayTicks)
|
||||
{
|
||||
SendDuration = 1
|
||||
};
|
||||
signalQueue.Enqueue(prevQueuedSignal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,21 @@ namespace Barotrauma.Items.Components
|
||||
//the output is sent if both inputs have received a signal within the timeframe
|
||||
protected float timeFrame;
|
||||
|
||||
[InGameEditable, Serialize("1", true)]
|
||||
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signals are equal.")]
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set { output = value; }
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("", true)]
|
||||
[InGameEditable, Serialize("", true, description: "The signal this item outputs when the received signals are not equal.")]
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set { falseOutput = value; }
|
||||
}
|
||||
|
||||
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true)]
|
||||
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The maximum amount of time between the received signals. If set to 0, the signals must be received at the same time.")]
|
||||
public float TimeFrame
|
||||
{
|
||||
get { return timeFrame; }
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public PhysicsBody ParentBody;
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 2048.0f), Serialize(100.0f, true)]
|
||||
[Serialize(100.0f, true, description: "The range of the emitted light. Higher values are more performance-intensive."),
|
||||
Editable(MinValueFloat = 0.0f, MaxValueFloat = 2048.0f)]
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
@@ -40,8 +41,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float Rotation;
|
||||
|
||||
[Editable(ToolTip = "Should structures cast shadows when light from this light source hits them. "+
|
||||
"Disabling shadows increases the performance of the game, and is recommended for lights with a short range."), Serialize(true, true)]
|
||||
[Editable, Serialize(true, true, description: "Should structures cast shadows when light from this light source hits them. " +
|
||||
"Disabling shadows increases the performance of the game, and is recommended for lights with a short range.")]
|
||||
public bool CastShadows
|
||||
{
|
||||
get { return castShadows; }
|
||||
@@ -54,8 +55,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "Lights drawn behind submarines don't cast any shadows and are much faster to draw than shadow-casting lights. "+
|
||||
"It's recommended to enable this on decorative lights outside the submarine's hull."), Serialize(false, true)]
|
||||
[Editable, Serialize(false, true, description: "Lights drawn behind submarines don't cast any shadows and are much faster to draw than shadow-casting lights. " +
|
||||
"It's recommended to enable this on decorative lights outside the submarine's hull.")]
|
||||
public bool DrawBehindSubs
|
||||
{
|
||||
get { return drawBehindSubs; }
|
||||
@@ -68,7 +69,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true)]
|
||||
[Editable, Serialize(false, true, description: "Is the light currently on.")]
|
||||
public bool IsOn
|
||||
{
|
||||
get { return IsActive; }
|
||||
@@ -83,7 +84,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
|
||||
public float Flicker
|
||||
{
|
||||
get { return flicker; }
|
||||
@@ -93,7 +94,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(0.0f, true)]
|
||||
[Editable, Serialize(0.0f, true, description: "How rapidly the light blinks on and off (in Hz). 0 = no blinking.")]
|
||||
public float BlinkFrequency
|
||||
{
|
||||
get { return blinkFrequency; }
|
||||
@@ -103,7 +104,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("1.0,1.0,1.0,1.0", true)]
|
||||
[InGameEditable, Serialize("255,255,255,255", true, description: "The color of the emitted light (R,G,B,A).")]
|
||||
public Color LightColor
|
||||
{
|
||||
get { return lightColor; }
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class MemoryComponent : ItemComponent
|
||||
{
|
||||
[InGameEditable, Serialize("", true)]
|
||||
[InGameEditable, Serialize("", true, description: "The currently stored signal the item outputs.")]
|
||||
public string Value
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -9,32 +9,23 @@ namespace Barotrauma.Items.Components
|
||||
partial class MotionSensor : ItemComponent
|
||||
{
|
||||
private const float UpdateInterval = 0.1f;
|
||||
|
||||
private string output, falseOutput;
|
||||
|
||||
private bool motionDetected;
|
||||
|
||||
private float rangeX, rangeY;
|
||||
|
||||
private Vector2 detectOffset;
|
||||
|
||||
private float updateTimer;
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool MotionDetected
|
||||
{
|
||||
get { return motionDetected; }
|
||||
set { motionDetected = value; }
|
||||
}
|
||||
[Serialize(false, false, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
||||
public bool MotionDetected { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
[Editable, Serialize(false, true, description: "Should the sensor only detect the movement of humans?")]
|
||||
public bool OnlyHumans
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize(0.0f, true)]
|
||||
[InGameEditable, Serialize(0.0f, true, description: "Horizontal detection range.")]
|
||||
public float RangeX
|
||||
{
|
||||
get { return rangeX; }
|
||||
@@ -43,7 +34,7 @@ namespace Barotrauma.Items.Components
|
||||
rangeX = MathHelper.Clamp(value, 0.0f, 1000.0f);
|
||||
}
|
||||
}
|
||||
[InGameEditable, Serialize(0.0f, true)]
|
||||
[InGameEditable, Serialize(0.0f, true, description: "Vertical movement detection range.")]
|
||||
public float RangeY
|
||||
{
|
||||
get { return rangeY; }
|
||||
@@ -53,7 +44,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("0,0", true), Editable(ToolTip = "The position to detect the movement at relative to the item. For example, 0,100 would detect movement 100 units above the item.")]
|
||||
[Editable, Serialize("0,0", true, description: "The position to detect the movement at relative to the item. For example, 0,100 would detect movement 100 units above the item.")]
|
||||
public Vector2 DetectOffset
|
||||
{
|
||||
get { return detectOffset; }
|
||||
@@ -65,21 +56,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("1", true)]
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set { output = value; }
|
||||
}
|
||||
[InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected movement.")]
|
||||
public string Output { get; set; }
|
||||
|
||||
[InGameEditable, Serialize("", true)]
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set { falseOutput = value; }
|
||||
}
|
||||
[InGameEditable, Serialize("", true, description: "The signal the item outputs when it has not detected movement.")]
|
||||
public string FalseOutput { get; set; }
|
||||
|
||||
[Editable(ToolTip = "How fast the objects within the detector's range have to be moving (in m/s).", DecimalCount = 3), Serialize(0.01f, true)]
|
||||
[Editable(DecimalCount = 3), Serialize(0.01f, true, description: "How fast the objects within the detector's range have to be moving (in m/s).")]
|
||||
public float MinimumVelocity
|
||||
{
|
||||
get;
|
||||
@@ -88,7 +71,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
|
||||
public MotionSensor(Item item, XElement element)
|
||||
: base (item, element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
@@ -101,21 +84,21 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
string signalOut = motionDetected ? output : falseOutput;
|
||||
string signalOut = MotionDetected ? Output : FalseOutput;
|
||||
|
||||
if (!string.IsNullOrEmpty(signalOut)) item.SendSignal(1, signalOut, "state_out", null);
|
||||
|
||||
updateTimer -= deltaTime;
|
||||
if (updateTimer > 0.0f) return;
|
||||
|
||||
motionDetected = false;
|
||||
MotionDetected = false;
|
||||
updateTimer = UpdateInterval;
|
||||
|
||||
if (item.body != null && item.body.Enabled)
|
||||
{
|
||||
if (Math.Abs(item.body.LinearVelocity.X) > MinimumVelocity || Math.Abs(item.body.LinearVelocity.Y) > MinimumVelocity)
|
||||
{
|
||||
motionDetected = true;
|
||||
MotionDetected = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +109,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (OnlyHumans && c.ConfigPath != Character.HumanConfigFile) { continue; }
|
||||
if (OnlyHumans && !c.IsHuman) { continue; }
|
||||
|
||||
//do a rough check based on the position of the character's collider first
|
||||
//before the more accurate limb-based check
|
||||
@@ -140,11 +123,20 @@ namespace Barotrauma.Items.Components
|
||||
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) continue;
|
||||
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
|
||||
{
|
||||
motionDetected = true;
|
||||
MotionDetected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
detectOffset.X = -detectOffset.X;
|
||||
}
|
||||
public override void FlipY(bool relativeToSub)
|
||||
{
|
||||
detectOffset.Y = -detectOffset.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float phase;
|
||||
|
||||
[InGameEditable, Serialize(WaveType.Pulse, true)]
|
||||
[InGameEditable, Serialize(WaveType.Pulse, true, description: "What kind of a signal the item outputs." +
|
||||
" Pulse: periodically sends out a signal of 1." +
|
||||
" Sine: sends out a sine wave oscillating between -1 and 1." +
|
||||
" Square: sends out a signal that alternates between 0 and 1.")]
|
||||
public WaveType OutputType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[InGameEditable(DecimalCount = 2), Serialize(1.0f, true)]
|
||||
[InGameEditable(DecimalCount = 2), Serialize(1.0f, true, description: "How fast the signal oscillates, or how fast the pulses are sent (in Hz).")]
|
||||
public float Frequency
|
||||
{
|
||||
get { return frequency; }
|
||||
|
||||
@@ -16,16 +16,16 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool nonContinuousOutputSent;
|
||||
|
||||
[InGameEditable, Serialize("1", true)]
|
||||
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the regular expression.")]
|
||||
public string Output { get; set; }
|
||||
|
||||
[InGameEditable, Serialize("0", true)]
|
||||
[Serialize("0", true, description: "The signal this item outputs when the received signal does not match the regular expression.")]
|
||||
public string FalseOutput { get; set; }
|
||||
|
||||
[Serialize(true, true), InGameEditable(ToolTip = "Should the component keep sending the output even after it stops receiving a signal, or only send an output when it receives a signal.")]
|
||||
[InGameEditable, Serialize(true, true, description: "Should the component keep sending the output even after it stops receiving a signal, or only send an output when it receives a signal.")]
|
||||
public bool ContinuousOutput { get; set; }
|
||||
|
||||
[InGameEditable, Serialize("", true)]
|
||||
[InGameEditable, Serialize("", true, description: "The regular expression used to check the incoming signals.")]
|
||||
public string Expression
|
||||
{
|
||||
get { return expression; }
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Barotrauma.Items.Components
|
||||
{ "signal_in5", "signal_out5" }
|
||||
};
|
||||
|
||||
[Editable, Serialize(1000.0f, true)]
|
||||
[Editable, Serialize(1000.0f, true, description: "The maximum amount of power that can pass through the item.")]
|
||||
public float MaxPower
|
||||
{
|
||||
get { return maxPower; }
|
||||
@@ -32,7 +32,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true)]
|
||||
[Editable, Serialize(false, true, description: "Can the relay currently pass power and signals through it.")]
|
||||
public bool IsOn
|
||||
{
|
||||
get
|
||||
|
||||
@@ -4,29 +4,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class SignalCheckComponent : ItemComponent
|
||||
{
|
||||
private string output, falseOutput;
|
||||
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the target signal.")]
|
||||
public string Output { get; set; }
|
||||
[InGameEditable, Serialize("0", true, description: "The signal this item outputs when the received signal does not match the target signal.")]
|
||||
public string FalseOutput { get; set; }
|
||||
|
||||
private string targetSignal;
|
||||
|
||||
[InGameEditable, Serialize("1", true)]
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set { output = value; }
|
||||
}
|
||||
[InGameEditable, Serialize("0", true)]
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set { falseOutput = value; }
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("", true)]
|
||||
public string TargetSignal
|
||||
{
|
||||
get { return targetSignal; }
|
||||
set { targetSignal = value; }
|
||||
}
|
||||
[InGameEditable, Serialize("", true, description: "The value to compare the received signals against.")]
|
||||
public string TargetSignal { get; set; }
|
||||
|
||||
public SignalCheckComponent(Item item, XElement element)
|
||||
: base(item, element)
|
||||
@@ -38,17 +22,17 @@ namespace Barotrauma.Items.Components
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "signal_in":
|
||||
string signalOut = (signal == targetSignal) ? output : falseOutput;
|
||||
string signalOut = (signal == TargetSignal) ? Output : FalseOutput;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(signalOut)) return;
|
||||
item.SendSignal(stepsTaken, signalOut, "signal_out", sender, signalStrength);
|
||||
|
||||
break;
|
||||
case "set_output":
|
||||
output = signal;
|
||||
Output = signal;
|
||||
break;
|
||||
case "set_targetsignal":
|
||||
targetSignal = signal;
|
||||
TargetSignal = signal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class SmokeDetector : ItemComponent
|
||||
{
|
||||
[Serialize(50.0f, false)]
|
||||
[Serialize(50.0f, false, description: "How large the fire has to be for the detector to react to it.")]
|
||||
public float FireSizeThreshold
|
||||
{
|
||||
get; set;
|
||||
|
||||
@@ -4,27 +4,17 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class WaterDetector : ItemComponent
|
||||
{
|
||||
private string output, falseOutput;
|
||||
|
||||
//how often the detector can switch from state to another
|
||||
const float StateSwitchInterval = 1.0f;
|
||||
|
||||
private bool isInWater;
|
||||
private float stateSwitchDelay;
|
||||
|
||||
[InGameEditable, Serialize("1", true)]
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set { output = value; }
|
||||
}
|
||||
[InGameEditable, Serialize("1", true, description: "The signal the item sends out when it's underwater.")]
|
||||
public string Output { get; set; }
|
||||
|
||||
[InGameEditable, Serialize("0", true)]
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set { falseOutput = value; }
|
||||
}
|
||||
[InGameEditable, Serialize("0", true, description: "The signal the item sends out when it's not underwater.")]
|
||||
public string FalseOutput { get; set; }
|
||||
|
||||
public WaterDetector(Item item, XElement element)
|
||||
: base(item, element)
|
||||
@@ -64,7 +54,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
string signalOut = isInWater ? output : falseOutput;
|
||||
string signalOut = isInWater ? Output : FalseOutput;
|
||||
if (!string.IsNullOrEmpty(signalOut))
|
||||
{
|
||||
item.SendSignal(0, signalOut, "signal_out", null);
|
||||
|
||||
@@ -19,17 +19,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private string prevSignal;
|
||||
|
||||
[Serialize(Character.TeamType.None, false)]
|
||||
[Serialize(Character.TeamType.None, false, description: "WiFi components can only communicate with components that have the same Team ID.")]
|
||||
public Character.TeamType TeamID { get; set; }
|
||||
|
||||
[Serialize(20000.0f, false)]
|
||||
[Serialize(20000.0f, false, description: "How close the recipient has to be to receive a signal from this WiFi component.")]
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set { range = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize(1, true)]
|
||||
[InGameEditable, Serialize(1, true, description: "WiFi components can only communicate with components that use the same channel.")]
|
||||
public int Channel
|
||||
{
|
||||
get { return channel; }
|
||||
@@ -39,25 +39,24 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(ToolTip =
|
||||
"If enabled, any signals received from another chat-linked wifi component are displayed "+
|
||||
"as chat messages in the chatbox of the player holding the item."), Serialize(false, false)]
|
||||
[Editable, Serialize(false, false, description: "If enabled, any signals received from another chat-linked wifi component are displayed " +
|
||||
"as chat messages in the chatbox of the player holding the item.")]
|
||||
public bool LinkToChat
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "How many seconds have to pass between signals for a message to be displayed in the chatbox. "+
|
||||
"Setting this to a very low value is not recommended, because it may cause an excessive amount of chat messages to be created "+
|
||||
"if there are chat-linked wifi components that transmit a continuous signal."), Serialize(1.0f, true)]
|
||||
[Editable, Serialize(1.0f, true, description: "How many seconds have to pass between signals for a message to be displayed in the chatbox. " +
|
||||
"Setting this to a very low value is not recommended, because it may cause an excessive amount of chat messages to be created " +
|
||||
"if there are chat-linked wifi components that transmit a continuous signal.")]
|
||||
public float MinChatMessageInterval
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "If set to true, the component will only create chat messages when the received signal changes."), Serialize(false, true)]
|
||||
[Editable, Serialize(false, true, description: "If set to true, the component will only create chat messages when the received signal changes.")]
|
||||
public bool DiscardDuplicateChatMessages
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -55,6 +55,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool Hidden;
|
||||
|
||||
private float removeNodeDelay;
|
||||
|
||||
private bool locked;
|
||||
public bool Locked
|
||||
{
|
||||
@@ -71,7 +73,7 @@ namespace Barotrauma.Items.Components
|
||||
get { return connections; }
|
||||
}
|
||||
|
||||
[Serialize(5000.0f, false)]
|
||||
[Serialize(5000.0f, false, description: "The maximum distance the wire can extend (in pixels).")]
|
||||
public float MaxLength
|
||||
{
|
||||
get;
|
||||
@@ -255,17 +257,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
ClearConnections(dropper);
|
||||
ClearConnections(dropper);
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (nodes.Count == 0) return;
|
||||
removeNodeDelay -= deltaTime;
|
||||
if (nodes.Count == 0) { return; }
|
||||
|
||||
Submarine sub = null;
|
||||
if (connections[0] != null && connections[0].Item.Submarine != null) sub = connections[0].Item.Submarine;
|
||||
if (connections[1] != null && connections[1].Item.Submarine != null) sub = connections[1].Item.Submarine;
|
||||
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
|
||||
if (connections[1] != null && connections[1].Item.Submarine != null) { sub = connections[1].Item.Submarine; }
|
||||
|
||||
if (Screen.Selected != GameMain.SubEditorScreen)
|
||||
{
|
||||
@@ -354,10 +357,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null) return false;
|
||||
#if CLIENT
|
||||
if (character == Character.Controlled && character.SelectedConstruction != null) return false;
|
||||
#endif
|
||||
if (character == null) { return false; }
|
||||
if (character == Character.Controlled && character.SelectedConstruction != null) { return false; }
|
||||
if (Screen.Selected == GameMain.SubEditorScreen && !PlayerInput.LeftButtonClicked())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newNodePos != Vector2.Zero && canPlaceNode && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance)
|
||||
{
|
||||
@@ -384,11 +389,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
if (nodes.Count > 1)
|
||||
if (nodes.Count > 1 && removeNodeDelay <= 0.0f)
|
||||
{
|
||||
nodes.RemoveAt(nodes.Count - 1);
|
||||
UpdateSections();
|
||||
}
|
||||
removeNodeDelay = 0.1f;
|
||||
|
||||
Drawable = IsActive || sections.Count > 0;
|
||||
return true;
|
||||
@@ -668,9 +674,9 @@ namespace Barotrauma.Items.Components
|
||||
UpdateSections();
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement)
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
{
|
||||
base.Load(componentElement);
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
|
||||
string nodeString = componentElement.GetAttributeString("nodes", "");
|
||||
if (nodeString == "") return;
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Character user;
|
||||
|
||||
[Serialize("0,0", false)]
|
||||
[Serialize("0,0", false, description: "The position of the barrel relative to the upper left corner of the base sprite (in pixels).")]
|
||||
public Vector2 BarrelPos
|
||||
{
|
||||
get
|
||||
@@ -57,21 +57,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
[Serialize(0.0f, false, description: "The impulse applied to the physics body of the projectile (the higher the impulse, the faster the projectiles are launched).")]
|
||||
public float LaunchImpulse
|
||||
{
|
||||
get { return launchImpulse; }
|
||||
set { launchImpulse = value; }
|
||||
}
|
||||
|
||||
[Serialize(5.0f, false), Editable(0.0f, 1000.0f)]
|
||||
[Editable(0.0f, 1000.0f), Serialize(5.0f, false, description: "The period of time the user has to wait between shots.")]
|
||||
public float Reload
|
||||
{
|
||||
get { return reloadTime; }
|
||||
set { reloadTime = value; }
|
||||
}
|
||||
|
||||
[Serialize("0.0,0.0", true), Editable]
|
||||
[Editable, Serialize("0.0,0.0", true, description: "The range at which the barrel can rotate. TODO")]
|
||||
public Vector2 RotationLimits
|
||||
{
|
||||
get
|
||||
@@ -94,39 +94,49 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(5.0f, false), Editable(0.0f, 1000.0f, DecimalCount = 2)]
|
||||
[Editable(0.0f, 1000.0f, DecimalCount = 2),
|
||||
Serialize(5.0f, false, description: "How much torque is applied to rotate the barrel when the item is used by a character"
|
||||
+ " with insufficient skills to operate it. Higher values make the barrel rotate faster.")]
|
||||
public float SpringStiffnessLowSkill
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
[Serialize(2.0f, false), Editable(0.0f, 1000.0f, DecimalCount = 2)]
|
||||
[Editable(0.0f, 1000.0f, DecimalCount = 2),
|
||||
Serialize(2.0f, false, description: "How much torque is applied to rotate the barrel when the item is used by a character"
|
||||
+ " with sufficient skills to operate it. Higher values make the barrel rotate faster.")]
|
||||
public float SpringStiffnessHighSkill
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(50.0f, false), Editable(0.0f, 1000.0f, DecimalCount = 2)]
|
||||
[Editable(0.0f, 1000.0f, DecimalCount = 2),
|
||||
Serialize(50.0f, false, description: "How much torque is applied to resist the movement of the barrel when the item is used by a character"
|
||||
+ " with insufficient skills to operate it. Higher values make the aiming more \"snappy\", stopping the barrel from swinging around the direction it's being aimed at.")]
|
||||
public float SpringDampingLowSkill
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
[Serialize(10.0f, false), Editable(0.0f, 1000.0f, DecimalCount = 2)]
|
||||
[Editable(0.0f, 1000.0f, DecimalCount = 2),
|
||||
Serialize(10.0f, false, description: "How much torque is applied to resist the movement of the barrel when the item is used by a character"
|
||||
+ " with sufficient skills to operate it. Higher values make the aiming more \"snappy\", stopping the barrel from swinging around the direction it's being aimed at.")]
|
||||
public float SpringDampingHighSkill
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false), Editable(0.0f, 100.0f, DecimalCount = 2)]
|
||||
[Editable(0.0f, 100.0f, DecimalCount = 2),
|
||||
Serialize(1.0f, false, description: "Maximum angular velocity of the barrel when used by a character with insufficient skills to operate it.")]
|
||||
public float RotationSpeedLowSkill
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
[Serialize(5.0f, false), Editable(0.0f, 100.0f, DecimalCount = 2)]
|
||||
[Editable(0.0f, 100.0f, DecimalCount = 2),
|
||||
Serialize(5.0f, false, description: "Maximum angular velocity of the barrel when used by a character with sufficient skills to operate it."),]
|
||||
public float RotationSpeedHighSkill
|
||||
{
|
||||
get;
|
||||
@@ -134,7 +144,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private float baseRotationRad;
|
||||
[Serialize(0.0f, true), Editable(0.0f, 360.0f)]
|
||||
[Editable(0.0f, 360.0f), Serialize(0.0f, true, description: "The angle of the turret's base in degrees.")]
|
||||
public float BaseRotation
|
||||
{
|
||||
get { return MathHelper.ToDegrees(baseRotationRad); }
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Barotrauma
|
||||
case WearableType.Husk:
|
||||
case WearableType.Herpes:
|
||||
Limb = LimbType.Head;
|
||||
HideLimb = false;
|
||||
HideLimb = type == WearableType.Husk || type == WearableType.Herpes;
|
||||
HideOtherWearables = false;
|
||||
InheritLimbDepth = true;
|
||||
InheritTextureScale = true;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly Entity Owner;
|
||||
|
||||
protected int capacity;
|
||||
protected readonly int capacity;
|
||||
|
||||
public Item[] Items;
|
||||
protected bool[] hideEmptySlot;
|
||||
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
get { return capacity; }
|
||||
}
|
||||
|
||||
public Inventory(Entity owner, int capacity, Vector2? centerPos = null, int slotsPerRow = 5)
|
||||
public Inventory(Entity owner, int capacity, int slotsPerRow = 5)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Barotrauma
|
||||
//there's already an item in the slot
|
||||
if (Items[i] != null && allowCombine)
|
||||
{
|
||||
if (Items[i].Combine(item))
|
||||
if (Items[i].Combine(item, user))
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Items[i] != null);
|
||||
return true;
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace Barotrauma
|
||||
protected set;
|
||||
}
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", true), Editable(ToolTip = "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true.")]
|
||||
[Editable, Serialize("1.0,1.0,1.0,1.0", true, description: "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true.")]
|
||||
public Color ContainerColor
|
||||
{
|
||||
get;
|
||||
@@ -505,7 +505,7 @@ namespace Barotrauma
|
||||
get { return ownInventory; }
|
||||
}
|
||||
|
||||
[Serialize(false, true), Editable(ToolTip =
|
||||
[Editable, Serialize(false, true, description:
|
||||
"Enable if you want to display the item HUD side by side with another item's HUD, when linked together. " +
|
||||
"Disclaimer: It's possible or even likely that the views block each other, if they were not designed to be viewed together!")]
|
||||
public bool DisplaySideBySideWhenLinked { get; set; }
|
||||
@@ -611,8 +611,6 @@ namespace Barotrauma
|
||||
break;
|
||||
case "aitarget":
|
||||
aiTarget = new AITarget(this, subElement);
|
||||
aiTarget.SoundRange = aiTarget.MinSoundRange;
|
||||
aiTarget.SightRange = aiTarget.MinSightRange;
|
||||
break;
|
||||
default:
|
||||
ItemComponent ic = ItemComponent.Load(subElement, this, itemPrefab.ConfigFile);
|
||||
@@ -1157,11 +1155,9 @@ namespace Barotrauma
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
//aitarget goes silent/invisible if the components don't keep it active
|
||||
if (aiTarget != null)
|
||||
{
|
||||
aiTarget.SightRange -= deltaTime * (aiTarget.MaxSightRange / aiTarget.FadeOutTime);
|
||||
aiTarget.SoundRange -= deltaTime * (aiTarget.MaxSoundRange / aiTarget.FadeOutTime);
|
||||
aiTarget.Update(deltaTime);
|
||||
}
|
||||
|
||||
bool broken = condition <= 0.0f;
|
||||
@@ -1794,13 +1790,13 @@ namespace Barotrauma
|
||||
if (remove) { Spawner?.AddToRemoveQueue(this); }
|
||||
}
|
||||
|
||||
public bool Combine(Item item)
|
||||
public bool Combine(Item item, Character user)
|
||||
{
|
||||
if (item == this) { return false; }
|
||||
bool isCombined = false;
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
if (ic.Combine(item)) { isCombined = true; }
|
||||
if (ic.Combine(item, user)) { isCombined = true; }
|
||||
}
|
||||
#if CLIENT
|
||||
if (isCombined) { GameMain.Client?.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Combine, item.ID }); }
|
||||
@@ -1976,7 +1972,7 @@ namespace Barotrauma
|
||||
SerializableProperty property = allProperties[propertyIndex].Second;
|
||||
if (inGameEditableOnly && parentObject is ItemComponent ic)
|
||||
{
|
||||
if (!ic.AllowInGameEditing) allowEditing = false;
|
||||
if (!ic.AllowInGameEditing) { allowEditing = false; }
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && !CanClientAccess(sender))
|
||||
@@ -2138,18 +2134,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool thisIsOverride = element.GetAttributeBool("isoverride", false);
|
||||
|
||||
//if we're overriding a non-overridden item in a sub/assembly xml or vice versa,
|
||||
//use the values from the prefab instead of loading them from the sub/assembly xml
|
||||
bool usePrefabValues = thisIsOverride != prefab.IsOverride;
|
||||
List<ItemComponent> unloadedComponents = new List<ItemComponent>(item.components);
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
ItemComponent component = unloadedComponents.Find(x => x.Name == subElement.Name.ToString());
|
||||
if (component == null) { continue; }
|
||||
|
||||
component.Load(subElement);
|
||||
component.Load(subElement, usePrefabValues);
|
||||
unloadedComponents.Remove(component);
|
||||
}
|
||||
if (usePrefabValues)
|
||||
{
|
||||
//use prefab scale when overriding a non-overridden item or vice versa
|
||||
item.Scale = prefab.ConfigElement.GetAttributeFloat(item.scale, "scale", "Scale");
|
||||
}
|
||||
|
||||
if (element.GetAttributeBool("flippedx", false)) item.FlipX(false);
|
||||
if (element.GetAttributeBool("flippedy", false)) item.FlipY(false);
|
||||
if (element.GetAttributeBool("flippedx", false)) { item.FlipX(false); }
|
||||
if (element.GetAttributeBool("flippedy", false)) { item.FlipY(false); }
|
||||
|
||||
float condition = element.GetAttributeFloat("condition", item.MaxCondition);
|
||||
item.condition = MathHelper.Clamp(condition, 0, item.MaxCondition);
|
||||
@@ -2179,8 +2184,9 @@ namespace Barotrauma
|
||||
new XAttribute("identifier", Prefab.Identifier),
|
||||
new XAttribute("ID", ID));
|
||||
|
||||
if (FlippedX) element.Add(new XAttribute("flippedx", true));
|
||||
if (FlippedY) element.Add(new XAttribute("flippedy", true));
|
||||
if (Prefab.IsOverride) { element.Add(new XAttribute("isoverride", "true")); }
|
||||
if (FlippedX) { element.Add(new XAttribute("flippedx", true)); }
|
||||
if (FlippedY) { element.Add(new XAttribute("flippedy", true)); }
|
||||
|
||||
if (condition < Prefab.Health)
|
||||
{
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Barotrauma
|
||||
get { return container; }
|
||||
}
|
||||
|
||||
public ItemInventory(Item owner, ItemContainer container, int capacity, Vector2? centerPos = null, int slotsPerRow = 5)
|
||||
: base(owner, capacity, centerPos, slotsPerRow)
|
||||
public ItemInventory(Item owner, ItemContainer container, int capacity, int slotsPerRow = 5)
|
||||
: base(owner, capacity, slotsPerRow)
|
||||
{
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user