(ce8e185aa) Merge branch 'dev' of https://github.com/Regalis11/Barotrauma-development into dev
This commit is contained in:
@@ -12,6 +12,8 @@ namespace Barotrauma
|
||||
{
|
||||
partial class EnemyAIController : AIController
|
||||
{
|
||||
public static bool DisableEnemyAI;
|
||||
|
||||
class WallTarget
|
||||
{
|
||||
public Vector2 Position;
|
||||
@@ -249,7 +251,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public TargetingPriority GetTargetingPriority(string targetTag)
|
||||
private TargetingPriority GetTargetingPriority(string targetTag)
|
||||
{
|
||||
if (targetingPriorities.TryGetValue(targetTag, out TargetingPriority priority))
|
||||
{
|
||||
@@ -485,7 +487,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsProperlyLatched)
|
||||
if (!IsProperlyLatchedOnSub)
|
||||
{
|
||||
UpdateWallTarget();
|
||||
}
|
||||
@@ -1001,7 +1003,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateEating(float deltaTime)
|
||||
{
|
||||
if (SelectedAiTarget == null)
|
||||
if (SelectedAiTarget == null) //SelectedAiTarget.Entity is Character c && !c.IsDead
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
@@ -1038,14 +1040,14 @@ namespace Barotrauma
|
||||
|
||||
#region Targeting
|
||||
|
||||
private bool IsProperlyLatched => LatchOntoAI != null && LatchOntoAI.IsAttached && SelectedAiTarget?.Entity == wallTarget?.Structure;
|
||||
private bool IsProperlyLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub && SelectedAiTarget?.Entity == wallTarget?.Structure;
|
||||
|
||||
//goes through all the AItargets, evaluates how preferable it is to attack the target,
|
||||
//whether the Character can see/hear the target and chooses the most preferable target within
|
||||
//sight/hearing range
|
||||
public AITarget UpdateTargets(Character character, out TargetingPriority priority)
|
||||
{
|
||||
if (IsProperlyLatched)
|
||||
if (IsProperlyLatchedOnSub)
|
||||
{
|
||||
// If attached to a valid target, just keep the target.
|
||||
// Priority not used in this case.
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace Barotrauma
|
||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
|
||||
{
|
||||
Character.Speak(
|
||||
newOrder.GetChatMessage("", Character.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order);
|
||||
newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,7 +286,7 @@ namespace Barotrauma
|
||||
|
||||
if (Character.PressureTimer > 50.0f && Character.CurrentHull != null)
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.RoomName), null, 0, "pressure", 30.0f);
|
||||
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.DisplayName), null, 0, "pressure", 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -388,6 +388,18 @@ namespace Barotrauma
|
||||
buttonPressCooldown = ButtonPressInterval;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!door.HasRequiredItems(character, false) && shouldBeOpen)
|
||||
{
|
||||
currentPath.Unreachable = true;
|
||||
return;
|
||||
}
|
||||
|
||||
door.Item.TryInteract(character, false, true, true);
|
||||
buttonPressCooldown = ButtonPressInterval;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -414,20 +426,12 @@ namespace Barotrauma
|
||||
if (!nextNode.Waypoint.ConnectedDoor.HasRequiredItems(character, false)) { return null; }
|
||||
}
|
||||
|
||||
if (!canBreakDoors)
|
||||
{
|
||||
//door closed and the character can't open doors -> node can't be traversed
|
||||
if (!canOpenDoors || character.LockHands) return null;
|
||||
|
||||
var doorButtons = nextNode.Waypoint.ConnectedDoor.Item.GetConnectedComponents<Controller>();
|
||||
if (!doorButtons.Any()) return null;
|
||||
|
||||
foreach (Controller button in doorButtons)
|
||||
{
|
||||
if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) !=
|
||||
Math.Sign(node.Position.X - nextNode.Position.X)) continue;
|
||||
Math.Sign(node.Position.X - nextNode.Position.X)) { continue; }
|
||||
|
||||
if (!button.HasRequiredItems(character, false)) return null;
|
||||
if (!button.HasRequiredItems(character, false)) { return null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace Barotrauma
|
||||
get { return attachJoints.Count > 0; }
|
||||
}
|
||||
|
||||
public bool IsAttachedToSub => IsAttached && attachTargetBody?.UserData is Entity entity && (entity is Submarine sub || entity?.Submarine != null);
|
||||
|
||||
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
|
||||
{
|
||||
attachToWalls = element.GetAttributeBool("attachtowalls", false);
|
||||
@@ -207,10 +209,10 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
|
||||
if (attachTargetBody != null && deattachTimer < 0.0f)
|
||||
if (IsAttached && attachTargetBody != null && deattachTimer < 0.0f)
|
||||
{
|
||||
Entity entity = attachTargetBody.UserData as Entity;
|
||||
Submarine attachedSub = entity is Submarine ? (Submarine)entity : entity?.Submarine;
|
||||
Submarine attachedSub = entity is Submarine sub ? sub : entity?.Submarine;
|
||||
if (attachedSub != null)
|
||||
{
|
||||
float velocity = attachedSub.Velocity == Vector2.Zero ? 0.0f : attachedSub.Velocity.Length();
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Barotrauma
|
||||
private AIObjectiveContainItem reloadWeaponObjective;
|
||||
private Hull retreatTarget;
|
||||
private AIObjectiveGoTo retreatObjective;
|
||||
private AIObjectiveFindSafety findSafety;
|
||||
|
||||
private float coolDownTimer;
|
||||
|
||||
@@ -60,7 +61,9 @@ namespace Barotrauma
|
||||
{
|
||||
Enemy = enemy;
|
||||
coolDownTimer = CoolDown;
|
||||
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 0;
|
||||
findSafety = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>();
|
||||
findSafety.Priority = 0;
|
||||
findSafety.unreachable.Clear();
|
||||
Mode = mode;
|
||||
if (Enemy == null)
|
||||
{
|
||||
@@ -175,7 +178,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
|
||||
{
|
||||
retreatTarget = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(new List<Hull>() { character.CurrentHull });
|
||||
retreatTarget = findSafety.FindBestHull(new List<Hull>() { character.CurrentHull });
|
||||
}
|
||||
if (retreatTarget != null)
|
||||
{
|
||||
@@ -235,6 +238,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
}
|
||||
}
|
||||
@@ -264,6 +268,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (target != null && target == Enemy)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
}
|
||||
}
|
||||
@@ -275,7 +280,6 @@ namespace Barotrauma
|
||||
{
|
||||
abandon = true;
|
||||
SteeringManager.Reset();
|
||||
//HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
|
||||
+16
-5
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
const float SearchHullInterval = 3.0f;
|
||||
const float clearUnreachableInterval = 30;
|
||||
|
||||
private List<Hull> unreachable = new List<Hull>();
|
||||
public readonly List<Hull> unreachable = new List<Hull>();
|
||||
|
||||
private float currenthullSafety;
|
||||
private float unreachableClearTimer;
|
||||
@@ -60,11 +60,16 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
divingGearObjective = null;
|
||||
// Reset the timer so that we get a safe hull target.
|
||||
searchHullTimer = 0;
|
||||
// Reduce the timer so that we get a safe hull target faster.
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
}
|
||||
}
|
||||
|
||||
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
}
|
||||
|
||||
if (unreachableClearTimer > 0)
|
||||
{
|
||||
unreachableClearTimer -= deltaTime;
|
||||
@@ -188,10 +193,17 @@ namespace Barotrauma
|
||||
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
|
||||
hullSafety *= distanceFactor;
|
||||
//skip the hull if the safety is already less than the best hull
|
||||
//(no need to do the expensive pathfinding if we already know we're not going to choose this hull)
|
||||
if (hullSafety < bestValue) { continue; }
|
||||
// Each unsafe node reduces the hull safety value.
|
||||
// Ignore current hull, because otherwise the would block all paths from the current hull to the target hull.
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
|
||||
if (path.Unreachable) { continue; }
|
||||
if (path.Unreachable)
|
||||
{
|
||||
unreachable.Add(hull);
|
||||
continue;
|
||||
}
|
||||
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
|
||||
hullSafety /= 1 + unsafeNodes;
|
||||
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
|
||||
@@ -219,7 +231,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Huge preference for closer targets
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace Barotrauma
|
||||
private float standStillTimer;
|
||||
private float walkDuration;
|
||||
|
||||
private readonly List<Hull> targetHulls = new List<Hull>(20);
|
||||
private readonly List<float> hullWeights = new List<float>(20);
|
||||
|
||||
public AIObjectiveIdle(Character character) : base(character, "")
|
||||
{
|
||||
standStillTimer = Rand.Range(-10.0f, 10.0f);
|
||||
@@ -106,7 +109,7 @@ namespace Barotrauma
|
||||
if (isCurrentHullOK)
|
||||
{
|
||||
// Check that there is no unsafe or forbidden hulls on the way to the target
|
||||
// Only do this when the current hull is ok, because otherwise the would block all paths from the current hull to the target hull.
|
||||
// Only do this when the current hull is ok, because otherwise would block all paths from the current hull to the target hull.
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition);
|
||||
if (path.Unreachable ||
|
||||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull)))
|
||||
@@ -128,8 +131,8 @@ namespace Barotrauma
|
||||
character.AIController.SelectTarget(currentTarget.AiTarget);
|
||||
string errorMsg = null;
|
||||
#if DEBUG
|
||||
bool isRoomNameFound = currentTarget.RoomName != null;
|
||||
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.RoomName : currentTarget.ToString()) + ")";
|
||||
bool isRoomNameFound = currentTarget.DisplayName != null;
|
||||
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")";
|
||||
#endif
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsg);
|
||||
PathSteering.SetPath(path);
|
||||
@@ -230,13 +233,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Hull> targetHulls = new List<Hull>(20);
|
||||
private readonly List<float> hullWeights = new List<float>(20);
|
||||
|
||||
private void FindTargetHulls()
|
||||
{
|
||||
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
|
||||
|
||||
targetHulls.Clear();
|
||||
hullWeights.Clear();
|
||||
foreach (var hull in Hull.hullList)
|
||||
@@ -266,7 +265,6 @@ namespace Barotrauma
|
||||
hullWeights.Add(weight);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool IsForbidden(Hull hull)
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Barotrauma
|
||||
if (character.SelectedCharacter == null)
|
||||
{
|
||||
character?.Speak(TextManager.Get("DialogFoundUnconsciousTarget")
|
||||
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.RoomName),
|
||||
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName),
|
||||
null, 1.0f,
|
||||
"foundunconscioustarget" + targetCharacter.Name, 60.0f);
|
||||
}
|
||||
|
||||
@@ -527,7 +527,12 @@ namespace Barotrauma
|
||||
|
||||
Limb leftLeg = GetLimb(LimbType.LeftLeg);
|
||||
Limb rightLeg = GetLimb(LimbType.RightLeg);
|
||||
|
||||
|
||||
float limpAmount =
|
||||
character.CharacterHealth.GetAfflictionStrength("damage", leftFoot, true) +
|
||||
character.CharacterHealth.GetAfflictionStrength("damage", rightFoot, true);
|
||||
limpAmount = MathHelper.Clamp(limpAmount / 100.0f, 0.0f, 1.0f);
|
||||
|
||||
float walkCycleMultiplier = 1.0f;
|
||||
if (Stairs != null)
|
||||
{
|
||||
@@ -556,13 +561,17 @@ namespace Barotrauma
|
||||
|
||||
float walkPosX = (float)Math.Cos(WalkPos);
|
||||
float walkPosY = (float)Math.Sin(WalkPos);
|
||||
|
||||
|
||||
|
||||
Vector2 stepSize = StepSize.Value;
|
||||
stepSize.X *= walkPosX;
|
||||
stepSize.Y *= walkPosY;
|
||||
stepSize.Y *= walkPosY;
|
||||
|
||||
float footMid = colliderPos.X;// (leftFoot.SimPosition.X + rightFoot.SimPosition.X) / 2.0f;
|
||||
float footMid = colliderPos.X;
|
||||
if (limpAmount > 0.0f)
|
||||
{
|
||||
//make the footpos oscillate when limping
|
||||
footMid += ((float)Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * 0.3f);
|
||||
}
|
||||
|
||||
movement = overrideTargetMovement == Vector2.Zero ?
|
||||
MathUtils.SmoothStep(movement, TargetMovement, movementLerp) :
|
||||
@@ -576,7 +585,7 @@ namespace Barotrauma
|
||||
movement.Y = 0.0f;
|
||||
|
||||
if (torso == null) { return; }
|
||||
|
||||
|
||||
bool isNotRemote = true;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer;
|
||||
|
||||
@@ -680,7 +689,7 @@ namespace Barotrauma
|
||||
|
||||
//make the character limp if the feet are damaged
|
||||
float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength("damage", foot, true);
|
||||
footPos *= MathHelper.Lerp(1.0f, 0.5f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f));
|
||||
footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f));
|
||||
|
||||
if (onSlope && Stairs == null)
|
||||
{
|
||||
|
||||
+1
@@ -84,6 +84,7 @@ namespace Barotrauma
|
||||
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
|
||||
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
|
||||
{
|
||||
//DebugConsole.NewMessage("[RagollParams] Using the default folder.");
|
||||
folder = GetDefaultFolder(speciesName);
|
||||
}
|
||||
return folder;
|
||||
|
||||
@@ -692,7 +692,7 @@ namespace Barotrauma
|
||||
ImpactProjSpecific(impact, f1.Body);
|
||||
}
|
||||
|
||||
public void SeverLimbJoint(LimbJoint limbJoint)
|
||||
public void SeverLimbJoint(LimbJoint limbJoint, bool playSound = true)
|
||||
{
|
||||
if (!limbJoint.CanBeSevered || limbJoint.IsSevered)
|
||||
{
|
||||
@@ -721,7 +721,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint);
|
||||
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound = true);
|
||||
|
||||
private void GetConnectedLimbs(List<Limb> connectedLimbs, List<LimbJoint> checkedJoints, Limb limb)
|
||||
{
|
||||
|
||||
@@ -1103,14 +1103,14 @@ namespace Barotrauma
|
||||
if (leftFoot != null)
|
||||
{
|
||||
float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", leftFoot, true);
|
||||
speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f));
|
||||
speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
var rightFoot = AnimController.GetLimb(LimbType.RightFoot);
|
||||
if (rightFoot != null)
|
||||
{
|
||||
float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", rightFoot, true);
|
||||
speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f));
|
||||
speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
return speed;
|
||||
|
||||
@@ -231,7 +231,8 @@ namespace Barotrauma
|
||||
: afflictions.Concat(limbHealths.SelectMany(lh => lh.Afflictions.Where(limbHealthFilter)));
|
||||
}
|
||||
|
||||
private LimbHealth GetMathingLimbHealth(Affliction affliction) => limbHealths[Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb).HealthIndex];
|
||||
private LimbHealth GetMatchingLimbHealth(Limb limb) => limbHealths[limb.HealthIndex];
|
||||
private LimbHealth GetMathingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb));
|
||||
|
||||
/// <summary>
|
||||
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
|
||||
|
||||
@@ -255,8 +255,10 @@ namespace Barotrauma
|
||||
|
||||
commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) =>
|
||||
{
|
||||
ThrowError("Karma has not been fully implemented yet, and is disabled in this version of Barotrauma.");
|
||||
return;
|
||||
HumanAIController.DisableCrewAI = true;
|
||||
NewMessage("Crew AI disabled", Color.Red);
|
||||
// This is probably not where it should be?
|
||||
//ThrowError("Karma has not been fully implemented yet, and is disabled in this version of Barotrauma.");
|
||||
/*if (GameMain.Server == null) return;
|
||||
GameMain.Server.KarmaEnabled = !GameMain.Server.KarmaEnabled;*/
|
||||
}));
|
||||
@@ -264,7 +266,19 @@ namespace Barotrauma
|
||||
commands.Add(new Command("enablecrewai", "enablecrewai: Enable the AI of the NPCs in the crew.", (string[] args) =>
|
||||
{
|
||||
HumanAIController.DisableCrewAI = false;
|
||||
NewMessage("Crew AI enabled", Color.White);
|
||||
NewMessage("Crew AI enabled", Color.Green);
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("disableenemyai", "disableenemyai: Disable the AI of the Enemy characters (monsters).", (string[] args) =>
|
||||
{
|
||||
EnemyAIController.DisableEnemyAI = true;
|
||||
NewMessage("Enemy AI disabled", Color.Red);
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("enableenemyai", "enableenemyai: Enable the AI of the Enemy characters (monsters).", (string[] args) =>
|
||||
{
|
||||
EnemyAIController.DisableEnemyAI = false;
|
||||
NewMessage("Enemy AI enabled", Color.Green);
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("botcount", "botcount [x]: Set the number of bots in the crew in multiplayer.", null));
|
||||
|
||||
@@ -209,6 +209,7 @@ namespace Barotrauma
|
||||
|
||||
//isActive = false;
|
||||
|
||||
bool spawnReady = false;
|
||||
if (spawnPending)
|
||||
{
|
||||
//wait until there are no submarines at the spawnpos
|
||||
@@ -219,25 +220,31 @@ namespace Barotrauma
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos) < minDist * minDist) return;
|
||||
}
|
||||
|
||||
spawnPending = false;
|
||||
|
||||
//+1 because Range returns an integer less than the max value
|
||||
int amount = Rand.Range(minAmount, maxAmount + 1, Rand.RandSync.Server);
|
||||
monsters = new Character[amount];
|
||||
|
||||
monsters = new List<Character>();
|
||||
float offsetAmount = spawnPosType == Level.PositionType.MainPath ? 1000 : 100;
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
bool isClient = false;
|
||||
{
|
||||
CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
bool isClient = false;
|
||||
#if CLIENT
|
||||
isClient = GameMain.Client != null;
|
||||
isClient = GameMain.Client != null;
|
||||
#endif
|
||||
|
||||
monsters[i] = Character.Create(
|
||||
characterFile, spawnPos + Rand.Vector(100.0f, Rand.RandSync.Server),
|
||||
i.ToString(), null, isClient, true, true);
|
||||
monsters.Add(Character.Create(characterFile, spawnPos + Rand.Vector(offsetAmount, Rand.RandSync.Server), i.ToString(), null, isClient, true, true));
|
||||
if (monsters.Count == amount)
|
||||
{
|
||||
spawnReady = true;
|
||||
}
|
||||
}, Rand.Range(0f, amount / 2, Rand.RandSync.Server));
|
||||
}
|
||||
|
||||
spawnPending = false;
|
||||
}
|
||||
|
||||
if (!spawnReady) { return; }
|
||||
|
||||
Entity targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null) targetEntity = (Entity)Character.Controlled;
|
||||
|
||||
@@ -8,6 +8,22 @@ namespace Barotrauma
|
||||
{
|
||||
public static class StringFormatter
|
||||
{
|
||||
public static string Replace(this string s, string replacement, Func<char, bool> predicate)
|
||||
{
|
||||
var newString = new string[s.Length];
|
||||
for (int i = 0; i < s.Length; i++)
|
||||
{
|
||||
char letter = s[i];
|
||||
string newLetter = letter.ToString();
|
||||
if (predicate(letter))
|
||||
{
|
||||
newLetter = replacement;
|
||||
}
|
||||
newString[i] = newLetter;
|
||||
}
|
||||
return new string(newString.SelectMany(str => str.ToCharArray()).ToArray());
|
||||
}
|
||||
|
||||
public static string Remove(this string s, Func<char, bool> predicate)
|
||||
{
|
||||
return new string(s.ToCharArray().Where(c => !predicate(c)).ToArray());
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
new GUIMessageBox("", TextManager.Get("CargoSpawnNotification").Replace("[roomname]", cargoRoom.RoomName));
|
||||
new GUIMessageBox("", TextManager.Get("CargoSpawnNotification").Replace("[roomname]", cargoRoom.DisplayName));
|
||||
#endif
|
||||
|
||||
Dictionary<ItemContainer, int> availableContainers = new Dictionary<ItemContainer, int>();
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Barotrauma
|
||||
public bool CheatsEnabled;
|
||||
|
||||
const int InitialMoney = 4700;
|
||||
public const int HullRepairCost = 500, ItemRepairCost = 500;
|
||||
|
||||
protected bool watchmenSpawned;
|
||||
protected Character startWatchman, endWatchman;
|
||||
@@ -20,6 +21,8 @@ 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;
|
||||
|
||||
protected Map map;
|
||||
public Map Map
|
||||
{
|
||||
@@ -70,6 +73,37 @@ namespace Barotrauma
|
||||
watchmenSpawned = false;
|
||||
startWatchman = null;
|
||||
endWatchman = null;
|
||||
|
||||
if (PurchasedHullRepairs)
|
||||
{
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine == null || wall.Submarine.IsOutpost) { continue; }
|
||||
if (wall.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(wall.Submarine))
|
||||
{
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
wall.AddDamage(i, -100000.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
PurchasedHullRepairs = false;
|
||||
}
|
||||
if (PurchasedItemRepairs)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == null || item.Submarine.IsOutpost) { continue; }
|
||||
if (item.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(item.Submarine))
|
||||
{
|
||||
if (item.GetComponent<Items.Components.Repairable>() != null)
|
||||
{
|
||||
item.Condition = item.Health;
|
||||
}
|
||||
}
|
||||
}
|
||||
PurchasedItemRepairs = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Barotrauma
|
||||
public bool ChromaticAberrationEnabled { get; set; }
|
||||
|
||||
public bool MuteOnFocusLost { get; set; }
|
||||
public bool UseDirectionalVoiceChat { get; set; }
|
||||
|
||||
public enum VoiceMode
|
||||
{
|
||||
@@ -149,6 +150,9 @@ namespace Barotrauma
|
||||
|
||||
public bool EnableMouseLook { get; set; } = true;
|
||||
|
||||
public bool CrewMenuOpen { get; set; } = true;
|
||||
public bool ChatOpen { get; set; } = true;
|
||||
|
||||
private bool unsavedSettings;
|
||||
public bool UnsavedSettings
|
||||
{
|
||||
@@ -826,6 +830,8 @@ namespace Barotrauma
|
||||
SoundVolume = audioSettings.GetAttributeFloat("soundvolume", SoundVolume);
|
||||
MusicVolume = audioSettings.GetAttributeFloat("musicvolume", MusicVolume);
|
||||
VoiceChatVolume = audioSettings.GetAttributeFloat("voicechatvolume", VoiceChatVolume);
|
||||
MuteOnFocusLost = audioSettings.GetAttributeBool("muteonfocuslost", false);
|
||||
UseDirectionalVoiceChat = audioSettings.GetAttributeBool("usedirectionalvoicechat", true);
|
||||
string voiceSettingStr = audioSettings.GetAttributeString("voicesetting", "Disabled");
|
||||
VoiceCaptureDevice = audioSettings.GetAttributeString("voicecapturedevice", "");
|
||||
NoiseGateThreshold = audioSettings.GetAttributeFloat("noisegatethreshold", -45);
|
||||
@@ -844,6 +850,9 @@ namespace Barotrauma
|
||||
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", AimAssistAmount);
|
||||
EnableMouseLook = doc.Root.GetAttributeBool("enablemouselook", EnableMouseLook);
|
||||
|
||||
CrewMenuOpen = doc.Root.GetAttributeBool("crewmenuopen", CrewMenuOpen);
|
||||
ChatOpen = doc.Root.GetAttributeBool("chatopen", ChatOpen);
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -1019,7 +1028,9 @@ namespace Barotrauma
|
||||
new XAttribute("requiresteamauthentication", requireSteamAuthentication),
|
||||
new XAttribute("autoupdateworkshopitems", AutoUpdateWorkshopItems),
|
||||
new XAttribute("aimassistamount", aimAssistAmount),
|
||||
new XAttribute("enablemouselook", EnableMouseLook));
|
||||
new XAttribute("enablemouselook", EnableMouseLook),
|
||||
new XAttribute("chatopen", ChatOpen),
|
||||
new XAttribute("crewmenuopen", CrewMenuOpen));
|
||||
|
||||
if (!ShowUserStatisticsPrompt)
|
||||
{
|
||||
@@ -1055,6 +1066,8 @@ namespace Barotrauma
|
||||
audio.ReplaceAttributes(
|
||||
new XAttribute("musicvolume", musicVolume),
|
||||
new XAttribute("soundvolume", soundVolume),
|
||||
new XAttribute("muteonfocuslost", MuteOnFocusLost),
|
||||
new XAttribute("usedirectionalvoicechat", UseDirectionalVoiceChat),
|
||||
new XAttribute("voicesetting", VoiceSetting),
|
||||
new XAttribute("voicecapturedevice", VoiceCaptureDevice ?? ""),
|
||||
new XAttribute("noisegatethreshold", NoiseGateThreshold));
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Barotrauma
|
||||
switch (SlotTypes[i])
|
||||
{
|
||||
//case InvSlotType.Head:
|
||||
case InvSlotType.OuterClothes:
|
||||
//case InvSlotType.OuterClothes:
|
||||
case InvSlotType.LeftHand:
|
||||
case InvSlotType.RightHand:
|
||||
hideEmptySlot[i] = true;
|
||||
@@ -176,6 +176,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] != null && Items[i] != item)
|
||||
{
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
if (!Items[i].AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(Items[i], character, new List<InvSlotType> { InvSlotType.Any }, true))
|
||||
{
|
||||
free = false;
|
||||
@@ -195,6 +198,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] == null)
|
||||
{
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
bool removeFromOtherSlots = item.ParentInventory != this;
|
||||
if (placedInSlot == -1 && inWrongSlot)
|
||||
{
|
||||
@@ -248,7 +254,9 @@ namespace Barotrauma
|
||||
GameAnalyticsManager.AddErrorEventOnce("CharacterInventory.TryPutItem:IndexOutOfRange", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(SlotTypes[index])) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
//there's already an item in the slot
|
||||
if (Items[index] != null)
|
||||
{
|
||||
@@ -273,7 +281,9 @@ namespace Barotrauma
|
||||
foreach (InvSlotType allowedSlot in allowedSlots)
|
||||
{
|
||||
if (!allowedSlot.HasFlag(SlotTypes[index])) continue;
|
||||
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(allowedSlot)) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] != null && Items[i] != item)
|
||||
|
||||
@@ -221,29 +221,64 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (item.Condition <= RepairThreshold) return true; //For repairing
|
||||
|
||||
var idCard = character.Inventory.FindItemByIdentifier("idcard");
|
||||
hasValidIdCard = requiredItems.Any(ri => ri.Value.Any(r => r.MatchesItem(idCard)));
|
||||
Msg = requiredItems.None() || hasValidIdCard ? "ItemMsgOpen" : "ItemMsgForceOpenCrowbar";
|
||||
ParseMsg();
|
||||
if (addMessage)
|
||||
{
|
||||
msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText);
|
||||
}
|
||||
|
||||
//this is a bit pointless atm because if canBePicked is false it won't allow you to do Pick() anyway, however it's still good for future-proofing.
|
||||
return requiredItems.Any() ? base.HasRequiredItems(character, addMessage, msg) : canBePicked;
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
return item.Condition <= RepairThreshold ? true : base.Pick(picker);
|
||||
if (item.Condition <= RepairThreshold) { return true; }
|
||||
if (requiredItems.None()) { return false; }
|
||||
if (HasRequiredItems(picker, false) && hasValidIdCard) { return false; }
|
||||
return base.Pick(picker);
|
||||
}
|
||||
|
||||
public override bool OnPicked(Character picker)
|
||||
{
|
||||
if (item.Condition <= RepairThreshold) return true; //repairs
|
||||
if (requiredItems.Any() && !hasValidIdCard)
|
||||
{
|
||||
ForceOpen(ActionType.OnPicked);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ForceOpen(ActionType actionType)
|
||||
{
|
||||
SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true); //crowbar function
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnPicked, item.WorldPosition, picker);
|
||||
PlaySound(actionType, item.WorldPosition, picker);
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
//can only be selected if the item is broken
|
||||
return item.Condition <= RepairThreshold;
|
||||
if (item.Condition <= RepairThreshold) return true; //repairs
|
||||
bool hasRequiredItems = HasRequiredItems(character, false);
|
||||
if (requiredItems.None() || hasRequiredItems && hasValidIdCard)
|
||||
{
|
||||
float originalPickingTime = PickingTime;
|
||||
PickingTime = 0;
|
||||
ForceOpen(ActionType.OnUse);
|
||||
PickingTime = originalPickingTime;
|
||||
}
|
||||
else if (hasRequiredItems)
|
||||
{
|
||||
#if CLIENT
|
||||
GUI.AddMessage(accessDeniedTxt, Color.Red);
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
|
||||
@@ -34,6 +34,20 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsActive
|
||||
{
|
||||
get { return base.IsActive; }
|
||||
set
|
||||
{
|
||||
base.IsActive = value;
|
||||
if (!value)
|
||||
{
|
||||
nodes.Clear();
|
||||
charactersInRange.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(100.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
|
||||
public float Range
|
||||
{
|
||||
@@ -126,14 +140,19 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes.Clear();
|
||||
charactersInRange.Clear();
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
voltage = 0.0f;
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
base.UpdateBroken(deltaTime, cam);
|
||||
nodes.Clear();
|
||||
charactersInRange.Clear();
|
||||
}
|
||||
|
||||
private void Discharge()
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f);
|
||||
|
||||
@@ -58,13 +58,6 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool Aimable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool ControlPose
|
||||
{
|
||||
|
||||
@@ -11,6 +11,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
private float lastSentDeattachTimer;
|
||||
|
||||
private PhysicsBody trigger;
|
||||
|
||||
private Holdable holdable;
|
||||
|
||||
private float deattachTimer;
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
public float DeattachDuration
|
||||
{
|
||||
@@ -49,13 +55,7 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private PhysicsBody trigger;
|
||||
|
||||
private Holdable holdable;
|
||||
|
||||
private float deattachTimer;
|
||||
|
||||
|
||||
public LevelResource(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
@@ -142,8 +142,6 @@ namespace Barotrauma.Items.Components
|
||||
pickTimer / requiredTime,
|
||||
Color.Red, Color.Green);
|
||||
#endif
|
||||
|
||||
picker.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f));
|
||||
|
||||
picker.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f));
|
||||
pickTimer += CoroutineManager.DeltaTime;
|
||||
|
||||
@@ -322,6 +322,7 @@ namespace Barotrauma.Items.Components
|
||||
// If the character is climbing, ignore the check, because we cannot aim while climbing.
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Use(deltaTime, character);
|
||||
}
|
||||
else
|
||||
@@ -337,11 +338,11 @@ namespace Barotrauma.Items.Components
|
||||
sinTime = 0;
|
||||
if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leaksfixed", 10.0f);
|
||||
character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.DisplayName), null, 0.0f, "leaksfixed", 10.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogLeakFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leakfixed", 10.0f);
|
||||
character.Speak(TextManager.Get("DialogLeakFixed").Replace("[roomname]", leak.FlowTargetHull.DisplayName), null, 0.0f, "leakfixed", 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ namespace Barotrauma.Items.Components
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
[Editable, Serialize("", true)]
|
||||
[Editable, Serialize("", true, translationTextTag: "ItemMsg")]
|
||||
public string Msg
|
||||
{
|
||||
get;
|
||||
@@ -538,7 +538,6 @@ namespace Barotrauma.Items.Components
|
||||
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return 0.0f;
|
||||
}
|
||||
float average = skillSuccessSum / requiredSkills.Count;
|
||||
|
||||
float skillSuccessSum = 0.0f;
|
||||
for (int i = 0; i < requiredSkills.Count; i++)
|
||||
@@ -555,7 +554,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public virtual void FlipY(bool relativeToSub) { }
|
||||
|
||||
public bool HasRequiredContainedItems(bool addMessage)
|
||||
public bool HasRequiredContainedItems(bool addMessage, string msg = null)
|
||||
{
|
||||
if (!requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) return true;
|
||||
if (item.OwnInventory == null) return false;
|
||||
@@ -582,33 +581,52 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!requiredItems.Any()) return true;
|
||||
if (character.Inventory == null) return false;
|
||||
|
||||
bool hasRequiredItems = false;
|
||||
bool canContinue = true;
|
||||
if (requiredItems.ContainsKey(RelatedItem.RelationType.Equipped))
|
||||
{
|
||||
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Equipped])
|
||||
{
|
||||
if (character.SelectedItems.FirstOrDefault(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)) == null)
|
||||
canContinue = CheckItems(ri, character.SelectedItems);
|
||||
if (!canContinue) { break; }
|
||||
}
|
||||
}
|
||||
if (canContinue)
|
||||
{
|
||||
if (requiredItems.ContainsKey(RelatedItem.RelationType.Picked))
|
||||
{
|
||||
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Picked])
|
||||
{
|
||||
#if CLIENT
|
||||
if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red);
|
||||
#endif
|
||||
return false;
|
||||
if (!CheckItems(ri, character.Inventory.Items)) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requiredItems.ContainsKey(RelatedItem.RelationType.Picked))
|
||||
{
|
||||
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Picked])
|
||||
{
|
||||
if (character.Inventory.Items.FirstOrDefault(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)) == null)
|
||||
{
|
||||
|
||||
#if CLIENT
|
||||
if (!hasRequiredItems && addMessage && !string.IsNullOrEmpty(msg))
|
||||
{
|
||||
GUI.AddMessage(msg, Color.Red);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
return hasRequiredItems;
|
||||
|
||||
bool CheckItems(RelatedItem relatedItem, IEnumerable<Item> itemList)
|
||||
{
|
||||
bool Predicate(Item it) => it != null && it.Condition > 0.0f && relatedItem.MatchesItem(it);
|
||||
bool shouldBreak = false;
|
||||
if (relatedItem.IsOptional)
|
||||
{
|
||||
if (!hasRequiredItems)
|
||||
{
|
||||
hasRequiredItems = itemList.Any(Predicate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hasRequiredItems = itemList.Any(Predicate);
|
||||
if (!hasRequiredItems)
|
||||
{
|
||||
shouldBreak = true;
|
||||
}
|
||||
}
|
||||
if (!hasRequiredItems)
|
||||
@@ -620,8 +638,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
return !shouldBreak;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Character user = null)
|
||||
@@ -766,6 +782,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
newRequiredItem.statusEffects = prevRequiredItem.statusEffects;
|
||||
newRequiredItem.Msg = prevRequiredItem.Msg;
|
||||
newRequiredItem.IsOptional = prevRequiredItem.IsOptional;
|
||||
}
|
||||
|
||||
if (!requiredItems.ContainsKey(newRequiredItem.Type))
|
||||
|
||||
@@ -343,5 +343,7 @@ namespace Barotrauma.Items.Components
|
||||
limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
|
||||
}
|
||||
}
|
||||
|
||||
partial void HideHUDs(bool value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,19 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class CustomInterface : ItemComponent, IClientSerializable, IServerSerializable
|
||||
{
|
||||
class CustomInterfaceElement
|
||||
class CustomInterfaceElement : ISerializableEntity
|
||||
{
|
||||
public bool ContinuousSignal;
|
||||
public bool State;
|
||||
public string Label, Connection, Signal;
|
||||
public string Connection;
|
||||
[Serialize("", false, translationTextTag = "Label.")]
|
||||
public string Label { get; set; }
|
||||
[Serialize("1", false)]
|
||||
public string Signal { get; set; }
|
||||
|
||||
public string Name => "CustomInterfaceElement";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
|
||||
|
||||
public List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
|
||||
@@ -33,7 +41,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private string[] labels;
|
||||
[Serialize("", true), Editable()]
|
||||
[Serialize("", true)]
|
||||
public string Labels
|
||||
{
|
||||
get { return string.Join(",", labels); }
|
||||
@@ -48,7 +56,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
private string[] signals;
|
||||
[Serialize("", true), Editable()]
|
||||
[Serialize("", true)]
|
||||
public string Signals
|
||||
{
|
||||
//use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -66,6 +67,8 @@ namespace Barotrauma
|
||||
|
||||
public LightComponent LightComponent { get; set; }
|
||||
|
||||
public int Variant { get; set; }
|
||||
|
||||
private Gender _gender;
|
||||
/// <summary>
|
||||
/// None = Any/Not Defined -> no effect.
|
||||
@@ -112,30 +115,65 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Note: this constructor cannot initialize automatically, because the gender is unknown at this point. We only know it when the item is equipped.
|
||||
/// </summary>
|
||||
public WearableSprite(XElement subElement, Wearable wearable)
|
||||
public WearableSprite(XElement subElement, Wearable wearable, int variant = 0)
|
||||
{
|
||||
Type = WearableType.Item;
|
||||
WearableComponent = wearable;
|
||||
Variant = Math.Max(variant, 0);
|
||||
SpritePath = ParseSpritePath(subElement.GetAttributeString("texture", string.Empty));
|
||||
SourceElement = subElement;
|
||||
}
|
||||
|
||||
private string ParseSpritePath(string texturePath) => texturePath.Contains("/") ? texturePath : $"{Path.GetDirectoryName(WearableComponent.Item.Prefab.ConfigFile)}/{texturePath}";
|
||||
|
||||
public void RefreshPath()
|
||||
{
|
||||
if (Variant > 0)
|
||||
{
|
||||
// Restore the tag so that we can parse it again.
|
||||
ReplaceNumbersWith("[VARIANT]");
|
||||
}
|
||||
ParsePath(true);
|
||||
}
|
||||
|
||||
private void ReplaceNumbersWith(string replacement)
|
||||
{
|
||||
var fileName = Path.GetFileName(SpritePath);
|
||||
var path = Path.GetDirectoryName(SpritePath);
|
||||
fileName = fileName.Replace(replacement, c => char.IsNumber(c));
|
||||
SpritePath = Path.Combine(path, fileName);
|
||||
}
|
||||
|
||||
private void ParsePath(bool parseSpritePath)
|
||||
{
|
||||
if (_gender != Gender.None)
|
||||
{
|
||||
SpritePath = SpritePath.Replace("[GENDER]", (_gender == Gender.Female) ? "female" : "male");
|
||||
}
|
||||
SpritePath = SpritePath.Replace("[VARIANT]", Variant.ToString());
|
||||
if (!File.Exists(SpritePath))
|
||||
{
|
||||
// If the variant does not exist, parse the path so that it uses first variant.
|
||||
Variant = 1;
|
||||
ReplaceNumbersWith(Variant.ToString());
|
||||
}
|
||||
if (parseSpritePath)
|
||||
{
|
||||
Sprite.ParseTexturePath(file: SpritePath);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInitialized { get; private set; }
|
||||
public void Init(Gender gender = Gender.None)
|
||||
{
|
||||
if (IsInitialized) { return; }
|
||||
_gender = SpritePath.Contains("[GENDER]") ? gender : Gender.None;
|
||||
if (_gender != Gender.None)
|
||||
{
|
||||
SpritePath = SpritePath.Replace("[GENDER]", (_gender == Gender.Female) ? "female" : "male");
|
||||
}
|
||||
ParsePath(false);
|
||||
if (Sprite != null)
|
||||
{
|
||||
Sprite.Remove();
|
||||
}
|
||||
Sprite = new Sprite(SourceElement, "", SpritePath);
|
||||
Sprite = new Sprite(SourceElement, file: SpritePath);
|
||||
Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true);
|
||||
HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
|
||||
HideOtherWearables = SourceElement.GetAttributeBool("hideotherwearables", false);
|
||||
@@ -170,7 +208,7 @@ namespace Barotrauma.Items.Components
|
||||
get { return damageModifiers; }
|
||||
}
|
||||
|
||||
public Wearable (Item item, XElement element) : base(item, element)
|
||||
public Wearable(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
this.item = item;
|
||||
|
||||
@@ -197,7 +235,7 @@ namespace Barotrauma.Items.Components
|
||||
limbType[i] = (LimbType)Enum.Parse(typeof(LimbType),
|
||||
subElement.GetAttributeString("limb", "Head"), true);
|
||||
|
||||
wearableSprites[i] = new WearableSprite(subElement, this);
|
||||
wearableSprites[i] = new WearableSprite(subElement, this, variant);
|
||||
|
||||
foreach (XElement lightElement in subElement.Elements())
|
||||
{
|
||||
|
||||
@@ -801,7 +801,12 @@ namespace Barotrauma
|
||||
if (findNewHull) FindHull();
|
||||
}
|
||||
|
||||
partial void SetActiveSprite();
|
||||
public void SetActiveSprite()
|
||||
{
|
||||
SetActiveSpriteProjSpecific();
|
||||
}
|
||||
|
||||
partial void SetActiveSpriteProjSpecific();
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
@@ -1432,13 +1437,13 @@ namespace Barotrauma
|
||||
selectHit = picker.IsKeyHit(ic.SelectKey);
|
||||
|
||||
#if CLIENT
|
||||
//if the cursor is on a UI component, disable interaction with the left mouse button
|
||||
//to prevent accidentally selecting items when clicking UI elements
|
||||
if (picker == Character.Controlled && GUI.MouseOn != null)
|
||||
{
|
||||
if (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) pickHit = false;
|
||||
if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) selectHit = false;
|
||||
}
|
||||
//if the cursor is on a UI component, disable interaction with the left mouse button
|
||||
//to prevent accidentally selecting items when clicking UI elements
|
||||
if (picker == Character.Controlled && GUI.MouseOn != null)
|
||||
{
|
||||
if (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) pickHit = false;
|
||||
if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) selectHit = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1608,6 +1613,7 @@ namespace Barotrauma
|
||||
if (remove) { Spawner?.AddToRemoveQueue(this); }
|
||||
}
|
||||
|
||||
List<ColoredText> texts = new List<ColoredText>();
|
||||
public List<ColoredText> GetHUDTexts(Character character)
|
||||
{
|
||||
texts.Clear();
|
||||
@@ -1617,6 +1623,13 @@ namespace Barotrauma
|
||||
if (!ic.CanBePicked && !ic.CanBeSelected) continue;
|
||||
if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue;
|
||||
|
||||
Color color = Color.Gray;
|
||||
bool hasRequiredSkillsAndItems = ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false);
|
||||
if (hasRequiredSkillsAndItems)
|
||||
{
|
||||
color = Color.Cyan;
|
||||
}
|
||||
|
||||
texts.Add(new ColoredText(ic.DisplayMsg, color, false));
|
||||
}
|
||||
|
||||
@@ -2054,6 +2067,8 @@ namespace Barotrauma
|
||||
public virtual void Reset()
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, Prefab.ConfigElement);
|
||||
Sprite.ReloadXML();
|
||||
SpriteDepth = Sprite.Depth;
|
||||
components.ForEach(c => c.Reset());
|
||||
}
|
||||
|
||||
|
||||
@@ -621,10 +621,25 @@ namespace Barotrauma
|
||||
|
||||
string treatmentIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
|
||||
|
||||
var matchingAffliction = AfflictionPrefab.List.Find(a => a.Identifier == treatmentIdentifier);
|
||||
if (matchingAffliction != null)
|
||||
List<AfflictionPrefab> matchingAfflictions = AfflictionPrefab.List.FindAll(a => a.Identifier == treatmentIdentifier || a.AfflictionType == treatmentIdentifier);
|
||||
if (matchingAfflictions.Count == 0)
|
||||
{
|
||||
matchingAffliction.TreatmentSuitability.Add(identifier, subElement.GetAttributeFloat("suitability", 0.0f));
|
||||
DebugConsole.ThrowError("Error in item prefab \"" + Name + "\" - couldn't define as a treatment, no treatments with the identifier or type \"" + treatmentIdentifier + "\" were found.");
|
||||
continue;
|
||||
}
|
||||
|
||||
float suitability = subElement.GetAttributeFloat("suitability", 0.0f);
|
||||
foreach (AfflictionPrefab matchingAffliction in matchingAfflictions)
|
||||
{
|
||||
if (matchingAffliction.TreatmentSuitability.ContainsKey(identifier))
|
||||
{
|
||||
matchingAffliction.TreatmentSuitability[identifier] =
|
||||
Math.Max(matchingAffliction.TreatmentSuitability[identifier], suitability);
|
||||
}
|
||||
else
|
||||
{
|
||||
matchingAffliction.TreatmentSuitability.Add(identifier, suitability);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace Barotrauma
|
||||
Container
|
||||
}
|
||||
|
||||
public bool IsOptional { get; set; }
|
||||
|
||||
private string[] identifiers;
|
||||
|
||||
private string[] excludedIdentifiers;
|
||||
@@ -138,7 +140,8 @@ namespace Barotrauma
|
||||
{
|
||||
element.Add(
|
||||
new XAttribute("identifiers", JoinedIdentifiers),
|
||||
new XAttribute("type", type.ToString()));
|
||||
new XAttribute("type", type.ToString()),
|
||||
new XAttribute("optional", IsOptional));
|
||||
|
||||
if (excludedIdentifiers.Length > 0)
|
||||
{
|
||||
|
||||
@@ -90,6 +90,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private string roomName;
|
||||
[Editable, Serialize("", true, translationTextTag: "RoomName.")]
|
||||
public string RoomName
|
||||
{
|
||||
get { return roomName; }
|
||||
set
|
||||
{
|
||||
if (roomName == value) { return; }
|
||||
roomName = value;
|
||||
DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName;
|
||||
}
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
get
|
||||
@@ -817,17 +836,17 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (roomItems.Contains("reactor"))
|
||||
return TextManager.Get("ReactorRoom");
|
||||
return "RoomName.ReactorRoom";
|
||||
else if (roomItems.Contains("engine"))
|
||||
return TextManager.Get("EngineRoom");
|
||||
return "RoomName.EngineRoom";
|
||||
else if (roomItems.Contains("steering") && roomItems.Contains("sonar"))
|
||||
return TextManager.Get("CommandRoom");
|
||||
return "RoomName.CommandRoom";
|
||||
else if (roomItems.Contains("ballast"))
|
||||
return TextManager.Get("Ballast");
|
||||
return "RoomName.Ballast";
|
||||
|
||||
if (ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor != null))
|
||||
{
|
||||
return TextManager.Get("Airlock");
|
||||
return "RoomName.Airlock";
|
||||
}
|
||||
|
||||
Rectangle subRect = Submarine.CalculateDimensions();
|
||||
@@ -847,7 +866,7 @@ namespace Barotrauma
|
||||
else
|
||||
roomPos |= Alignment.Right;
|
||||
|
||||
return TextManager.Get("Sub" + roomPos.ToString());
|
||||
return "RoomName.Sub" + roomPos.ToString();
|
||||
}
|
||||
|
||||
public static Hull Load(XElement element, Submarine submarine)
|
||||
|
||||
@@ -144,6 +144,20 @@ namespace Barotrauma
|
||||
get { return spriteColor; }
|
||||
set { spriteColor = value; }
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true)]
|
||||
public bool UseDropShadow
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("0,0", true), Editable(ToolTip = "The position of the drop shadow relative to the structure. If set to zero, the shadow is positioned automatically so that it points towards the sub's center of mass.")]
|
||||
public Vector2 DropShadowOffset
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
@@ -299,8 +313,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// Only add ai targets automatically to walls
|
||||
if (aiTarget == null && HasBody && Tags.Contains("wall"))
|
||||
// Only add ai targets automatically to submarine/outpost walls
|
||||
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null)
|
||||
{
|
||||
aiTarget = new AITarget(this);
|
||||
}
|
||||
@@ -1139,6 +1153,13 @@ namespace Barotrauma
|
||||
if (element.GetAttributeBool("flippedx", false)) s.FlipX(false);
|
||||
if (element.GetAttributeBool("flippedy", false)) s.FlipY(false);
|
||||
SerializableProperty.DeserializeProperties(s, element);
|
||||
|
||||
//structures with a body drop a shadow by default
|
||||
if (element.Attribute("usedropshadow") == null)
|
||||
{
|
||||
s.UseDropShadow = prefab.Body;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,14 @@ namespace Barotrauma
|
||||
break;
|
||||
case "backgroundsprite":
|
||||
sp.BackgroundSprite = new Sprite(subElement, lazyLoad: true);
|
||||
|
||||
if (subElement.Attribute("sourcerect") == null && sp.sprite != null)
|
||||
{
|
||||
sp.BackgroundSprite.SourceRect = sp.sprite.SourceRect;
|
||||
sp.BackgroundSprite.size = sp.sprite.size;
|
||||
sp.BackgroundSprite.size.X *= sp.sprite.SourceRect.Width;
|
||||
sp.BackgroundSprite.size.Y *= sp.sprite.SourceRect.Height;
|
||||
sp.BackgroundSprite.RelativeOrigin = subElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f));
|
||||
}
|
||||
if (subElement.GetAttributeBool("fliphorizontal", false))
|
||||
sp.BackgroundSprite.effects = SpriteEffects.FlipHorizontally;
|
||||
if (subElement.GetAttributeBool("flipvertical", false))
|
||||
|
||||
@@ -417,7 +417,10 @@ namespace Barotrauma
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
{
|
||||
item.Indestructible = true;
|
||||
if (item.GetComponent<Repairable>() != null)
|
||||
{
|
||||
item.Indestructible = true;
|
||||
}
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic is ConnectionPanel connectionPanel)
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public OrderChatMessage(Order order, string orderOption, Entity targetEntity, Character targetCharacter, Character sender)
|
||||
: this(order, orderOption,
|
||||
order.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.RoomName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption),
|
||||
order.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption),
|
||||
targetEntity, targetCharacter, sender)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Barotrauma
|
||||
base.Deselect();
|
||||
|
||||
#if CLIENT
|
||||
GameMain.Config.SaveNewPlayerConfig();
|
||||
GameMain.SoundManager.SetCategoryMuffle("default", false);
|
||||
GUI.ClearMessages();
|
||||
#endif
|
||||
|
||||
@@ -87,7 +87,6 @@ namespace Barotrauma
|
||||
|
||||
public string FullPath { get; private set; }
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return FilePath + ": " + sourceRect;
|
||||
@@ -107,25 +106,7 @@ namespace Barotrauma
|
||||
{
|
||||
this.lazyLoad = lazyLoad;
|
||||
SourceElement = element;
|
||||
if (file == "")
|
||||
{
|
||||
file = SourceElement.GetAttributeString("texture", "");
|
||||
}
|
||||
if (file == "")
|
||||
{
|
||||
DebugConsole.ThrowError("Sprite " + SourceElement + " doesn't have a texture specified!");
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
LoadTexture(ref sourceVector, ref shouldReturn, preMultipliedAlpha);
|
||||
}
|
||||
FilePath = path + file;
|
||||
if (!string.IsNullOrEmpty(FilePath))
|
||||
{
|
||||
FullPath = Path.GetFullPath(FilePath);
|
||||
}
|
||||
|
||||
if (!ParseTexturePath(path, file)) { return; }
|
||||
Name = SourceElement.GetAttributeString("name", null);
|
||||
Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero);
|
||||
preMultipliedAlpha = preMultiplyAlpha ?? SourceElement.GetAttributeBool("premultiplyalpha", true);
|
||||
@@ -269,6 +250,29 @@ namespace Barotrauma
|
||||
ID = GetID(SourceElement);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ParseTexturePath(string path = "", string file = "")
|
||||
{
|
||||
if (file == "")
|
||||
{
|
||||
file = SourceElement.GetAttributeString("texture", "");
|
||||
}
|
||||
if (file == "")
|
||||
{
|
||||
DebugConsole.ThrowError("Sprite " + SourceElement + " doesn't have a texture specified!");
|
||||
return false;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (!path.EndsWith("/")) path += "/";
|
||||
}
|
||||
FilePath = path + file;
|
||||
if (!string.IsNullOrEmpty(FilePath))
|
||||
{
|
||||
FullPath = Path.GetFullPath(FilePath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -252,6 +252,28 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<KeyValuePair<string, string>> GetAllTagTextPairs()
|
||||
{
|
||||
if (!textPacks.ContainsKey(Language))
|
||||
{
|
||||
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
|
||||
Language = "English";
|
||||
if (!textPacks.ContainsKey(Language))
|
||||
{
|
||||
throw new Exception("No text packs available in English!");
|
||||
}
|
||||
}
|
||||
|
||||
List<KeyValuePair<string, string>> allText = new List<KeyValuePair<string, string>>();
|
||||
|
||||
foreach (TextPack textPack in textPacks[Language])
|
||||
{
|
||||
allText.AddRange(textPack.GetAllTagTextPairs());
|
||||
}
|
||||
|
||||
return allText;
|
||||
}
|
||||
|
||||
public static string ReplaceGenderPronouns(string text, Gender gender)
|
||||
{
|
||||
if (gender == Gender.Male)
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace Barotrauma
|
||||
text = text.Replace("&", "&");
|
||||
text = text.Replace("<", "<");
|
||||
text = text.Replace(">", ">");
|
||||
text = text.Replace(""", "\"");
|
||||
infoList.Add(text);
|
||||
}
|
||||
}
|
||||
@@ -62,6 +63,20 @@ namespace Barotrauma
|
||||
return textList;
|
||||
}
|
||||
|
||||
public List<KeyValuePair<string, string>> GetAllTagTextPairs()
|
||||
{
|
||||
var pairs = new List<KeyValuePair<string, string>>();
|
||||
foreach (KeyValuePair<string, List<string>> kvp in texts)
|
||||
{
|
||||
foreach (string line in kvp.Value)
|
||||
{
|
||||
pairs.Add(new KeyValuePair<string, string>(kvp.Key, line));
|
||||
}
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public void CheckForDuplicates(int index)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user